keystonemiddleware-4.21.0/0000775000175100017510000000000013225161130015513 5ustar zuulzuul00000000000000keystonemiddleware-4.21.0/tools/0000775000175100017510000000000013225161130016653 5ustar zuulzuul00000000000000keystonemiddleware-4.21.0/tools/tox_install.sh0000777000175100017510000000203613225160624021564 0ustar zuulzuul00000000000000#!/usr/bin/env bash # Client constraint file contains this client version pin that is in conflict # with installing the client from source. We should remove the version pin in # the constraints file before applying it for from-source installation. CONSTRAINTS_FILE="$1" shift 1 set -e # NOTE(tonyb): Place this in the tox enviroment's log dir so it will get # published to logs.openstack.org for easy debugging. localfile="$VIRTUAL_ENV/log/upper-constraints.txt" if [[ "$CONSTRAINTS_FILE" != http* ]]; then CONSTRAINTS_FILE="file://$CONSTRAINTS_FILE" fi # NOTE(tonyb): need to add curl to bindep.txt if the project supports bindep curl "$CONSTRAINTS_FILE" --insecure --progress-bar --output "$localfile" pip install -c"$localfile" openstack-requirements # This is the main purpose of the script: Allow local installation of # the current repo. It is listed in constraints file and thus any # install will be constrained and we need to unconstrain it. edit-constraints "$localfile" -- "$CLIENT_NAME" pip install -c"$localfile" -U "$@" exit $? keystonemiddleware-4.21.0/HACKING.rst0000666000175100017510000000121413225160624017320 0ustar zuulzuul00000000000000Keystone Style Commandments =========================== - Step 1: Read the OpenStack Style Commandments https://docs.openstack.org/hacking/latest/ - Step 2: Read on Exceptions ---------- When dealing with exceptions from underlying libraries, translate those exceptions to an instance or subclass of ClientException. ======= Testing ======= Keystone Middleware uses testtools and testr for its unittest suite and its test runner. Basic workflow around our use of tox and testr can be found at https://wiki.openstack.org/testr. If you'd like to learn more in depth: https://testtools.readthedocs.org/ https://testrepository.readthedocs.org/ keystonemiddleware-4.21.0/test-requirements.txt0000666000175100017510000000136413225160624021771 0ustar zuulzuul00000000000000# The order of packages is significant, because pip processes them in the order # of appearance. Changing the order has an impact on the overall integration # process, which may cause wedges in the gate later. hacking<0.11,>=0.10.0 flake8-docstrings==0.2.1.post1 # MIT coverage!=4.4,>=4.0 # Apache-2.0 cryptography!=2.0,>=1.9 # BSD/Apache-2.0 fixtures>=3.0.0 # Apache-2.0/BSD mock>=2.0.0 # BSD oslotest>=1.10.0 # Apache-2.0 reno>=2.5.0 # Apache-2.0 requests-mock>=1.1.0 # Apache-2.0 stevedore>=1.20.0 # Apache-2.0 testrepository>=0.0.18 # Apache-2.0/BSD testresources>=2.0.0 # Apache-2.0/BSD testtools>=2.2.0 # MIT os-testr>=1.0.0 # Apache-2.0 python-memcached>=1.56 # PSF WebTest>=2.0.27 # MIT # Bandit security code scanner bandit>=1.1.0 # Apache-2.0 keystonemiddleware-4.21.0/CONTRIBUTING.rst0000666000175100017510000000104713225160624020167 0ustar zuulzuul00000000000000If you would like to contribute to the development of OpenStack, you must follow the steps in this page: https://docs.openstack.org/infra/manual/developers.html Once those steps have been completed, changes to OpenStack should be submitted for review via the Gerrit tool, following the workflow documented at: https://docs.openstack.org/infra/manual/developers.html#development-workflow Pull requests submitted through GitHub will be ignored. Bugs should be filed on Launchpad, not GitHub: https://bugs.launchpad.net/keystonemiddleware keystonemiddleware-4.21.0/ChangeLog0000664000175100017510000012050613225161127017277 0ustar zuulzuul00000000000000CHANGES ======= 4.21.0 ------ * Imported Translations from Zanata 4.20.0 ------ * cfg.CONF must not be used directly * Fix docs builds * Log TokenNotFound at INFO level instead of WARNING * rel-note and doc for lazy loading of oslo\_cache * lazy loading of oslo\_cache 4.19.0 ------ * Updated from global requirements * Use oslo\_cache in auth\_token middleware * Remove setting of version/release from releasenotes * Updated from global requirements * Imported Translations from Zanata 4.18.0 ------ * Updated from global requirements * Updated from global requirements * Updated from global requirements * Fix py3 byte/string error * Rename auth\_uri to www\_authenticate\_uri * Updates for stestr * Issue a deprecation warning for validating PKI tokens * Fix gate error caused by mocked URLs * Correct docs usage of keystoneauth1 session * Update config docs to reflect non-deprecated methods * Add doc8 rule and check doc/source files * Updated from global requirements * Remove use of positional decorator * strip whitespace from token * Update reno for stable/pike * Remove notice about system time * Updated from global requirements * Updated from global requirements * Update comment about fetch token kwargs 4.17.0 ------ * Update URLs in documentation * Updated from global requirements * Enable sphinx todo extension * Replace six.iteritems() with .items() * Change locations of docs for intersphinx * Redundant adminURL in test\_gives\_v2\_catalog * Switch from oslosphinx to openstackdocstheme * Updated from global requirements * Using assertFalse(A) instead of assertEqual(False, A) * Removing double spaces * Updated from global requirements * Fix html\_last\_updated\_fmt for Python3 * add a log when the option in conf can't be identitied * Updated from global requirements * Updated from global requirements 4.16.0 ------ * Fix oslo.messaging deprecation of get\_transport * Updated from global requirements * Replace pycrypto with cryptography * Updated from global requirements * Update driver config parameter from string to list * Updated from global requirements * Remove log translations * Added "warning-is-error" sphinx check for docs * Updated from global requirements * Imported Translations from Zanata 4.15.0 ------ * Remove deprecated oslo.messaging aliases parameter * Pass located tests directory in oslo debug * Remove old comment referencing fixed bug * Bump the token deferral message from info to debug * Remove unused logging import * Updated from global requirements * Fixed man\_pages no value warning when making docs * Use https for \*.openstack.org references * Imported Translations from Zanata * Updated from global requirements * Update reno for stable/ocata 4.14.0 ------ * Updated from global requirements * fix broken links * use oslo.log instead of logging * Removes unnecessary utf-8 coding * Remove references to Python 3.4 * Switch tox unit test command to use ostestr 4.13.1 ------ * Add Constraints support * Auth token, set the correct charset 4.13.0 ------ * Limit deprecated token message to single warning * auth\_token: set correct charset when replying with 401 * Updated from global requirements 4.12.0 ------ * Pass ?allow\_expired * Updated from global requirements * clean up a few doc building warnings * Add docutils contraint on 0.13.1 to fix building * Updated from global requirements * Updated from global requirements * Updated from global requirements 4.11.0 ------ * Drop MANIFEST.in - it's not needed by pbr * Show team and repo badges on README * Updated from global requirements * Deprecate PKI token format options * Updated from global requirements * Mock log only after app creation * Updated from global requirements * Update .coveragerc after the removal of respective directory * Updated from global requirements * Updated from global requirements * Updated from global requirements * Updated from global requirements * Updated from global requirements * Updated from global requirements * Updated from global requirements * Add service token to user token plugin * Specify that unknown arguments can be passed to fetch\_token * Enable release notes translation * Changed the home-page link 4.10.0 ------ * Return and use an app wherever possible * Refactor audit tests to use create\_middleware * Use oslo\_messaging conf fixture * Extract oslo\_messaging specific audit tests * Use the mocking fixture in notifier tests * Updated from global requirements * Use method constant\_time\_compare from oslo.utils * Raise NotImplementedError instead of NotImplemented * Updated from global requirements * Updated from global requirements * Update code to use Newton as the code name * standardize release note page ordering * Update reno for stable/newton * Globalize authentication failure error * Updated from global requirements 4.9.0 ----- * Updated from global requirements * Updated from global requirements * Updated from global requirements * Updated from global requirements 4.8.0 ----- * Updated from global requirements * Updated from global requirements * Fix description of option \`cache\` 4.7.0 ----- * Add Python 3.5 classifier * Updated from global requirements * Updated from global requirements * Updated from global requirements * Use jsonutils instead of ast for loading the service catalog * Use AccessInfo in UserAuthPlugin instead of custom * Remove the \_is\_v2 and \_is\_v3 helpers * Remove oslo-incubator 4.6.0 ----- * Updated from global requirements * Use extras for oslo.messaging dependency * Refactor API tests to not run middleware * Refactor audit api tests into their own file * Refactor create\_event onto the api object * Extract a common notifier pattern * Break out the API piece into its own file * Use createfile fixture in audit test * Move audit into its own folder * use local config options if available in audit middleware * Use oslo.config fixture in audit tests * Pop oslo\_config\_config before doing paste convert * Updated from global requirements * Fix typo 'olso' to 'oslo' * Config: no need to set default=None * Fix an issue with oslo\_config\_project paste config * Updated from global requirements * Pass X\_IS\_ADMIN\_PROJECT header from auth\_token * Clean up middleware architecture * Updated from global requirements * Add a fixture method to add your own token data * Move auth token opts calculation into auth\_token * Make audit middleware use common config object * Consolidate user agent calculation * Create a Config object * Updated from global requirements * Updated from global requirements * Improve documentation for auth\_uri * PEP257: Ignore D203 because it was deprecated * Updated from global requirements * Use method split\_path from oslo.utils * Updated from global requirements * Make sure audit can handle API requests which does not require a token * Updated from global requirements * Updated from global requirements * Updated from global requirements * Determine project name from oslo\_config or local config 4.5.1 ----- * Fix AttributeError on cached-invalid token checks 4.5.0 ----- * Updated from global requirements * Updated from global requirements * Fix D105: Missing docstring in magic method (PEP257) * Fix D200: One-line docstring should fit on one line with quotes (PEP257) * Fix D202: No blank lines allowed after function docstring (PEP257) * Adding audit middleware specific notification driver conf * remove old options from documentation * generate sample config automatically * Return default value for pkg\_version if missing * Updated from global requirements * Fix D204 PEP257 violation and enable D301 and D209 * Fix D400 PEP257 violation * Fix D401 PEP257 violation and enable H403 * Update config options * s3token config with auth URI * Updated from global requirements * Return JSON for Unauthorized message * Updated from global requirements * Fix doc build if git is absent * PEP257: add flake8-docstring testing * Only confirm token binding on one token * Create signing\_dir upon first usage * Updated from global requirements * Updated from global requirements * Handle cache invalidate outside cache object * Update reno for stable/mitaka * Remove bandit.yaml in favor of defaults * use the same context across a request * Updated from global requirements * Update documentation for running tests * Updated from global requirements * Add back a bandit tox job 4.3.0 ----- * argparse expects a list not a dictionary * update deprecation message to indicate when deprecations were made * Updated from global requirements * Split oslo\_config and list all opts * Updated from global requirements * Make pep8 \*the\* linting interface * Remove clobbering of passed oslo\_config\_config * Updated from global requirements * Use positional instead of keystoneclient version * Updated from global requirements * Remove Babel from requirements.txt 4.2.0 ----- * Updated from global requirements * Deprecate in-process cache * Revert "Disable memory caching of tokens" * Revert "Don't cache signed tokens" * Updated from global requirements * Remove bandit tox environment * Remove unnecessary \_reject\_request function * Group common PKI validation code - Refactor * Group common PKI validation code - Tests * Remove except Exception handler * Fix tests to work with keystoneauth1 2.2.0 * Bandit profile updates * Replace deprecated library function os.popen() with subprocess 4.1.0 ----- * Add project\_name to the auth\_token fixture * Revert "Stop using private keystoneclient functions" * create release notes for ksm 4.1.0 * Don't cache signed tokens * Disable memory caching of tokens * Updated from global requirements * Use oslo\_config choices support * Stop using private keystoneclient functions * Use fixture for mock patch * auth\_token verify revocation by audit\_id * Updated from global requirements * Deprecated tox -downloadcache option removed * Updated from global requirements * Make BaseAuthProtocol public * Use load\_from\_options\_getter for auth plugins * Configuration is outdated * Updated from global requirements * Use keystoneauth for auth\_token fixture * Don't list deprecated opts in sample config * Updated from global requirements * Put py34 first in the env order of tox 4.0.0 ----- * Add release notes for keystonemiddleware * Updated from global requirements * Adding parse of protocol v4 of AWS auth to ec2\_token * Add a mock-fixture for keystonemiddleware auth\_protocol * Add domain and trust details to user plugin * Remove py26 target from tox.ini * Use keystoneauth * Updated from global requirements * Address hacking check H405 * update middlewarearchitecture.rst * Make "Auth Token confirmed use of %s apis" debug level * Define entry points for filter factories for Paste Deployment * Updated from global requirements * Updated from global requirements 3.0.0 ----- * Updated from global requirements * drop use of norm\_ns 2.4.1 ----- * Updated from global requirements * Straighten up exceptions imports * Separate setting catalog on headers from others 2.4.0 ----- * Updated from global requirements * Updated from global requirements * Remove auth headers in AuthProtocol * Use request helpers for token\_info/token\_auth * Make \_\_all\_\_ immutable * Move response status check to the call * only make token invalid when it really is * auto-generate release history * Add shields.io version/downloads links/badges into README.rst * Updated from global requirements * Change ignore-errors to ignore\_errors * Ensure auth\_plugin options are in generated CONF * Cleanup a few auth\_token comments 2.3.0 ----- * Updated from global requirements * Remove unused group parameter from tests * auth\_token tests use clean config * Docstring updates * Use ConnectionRefused for auth\_token tests 2.2.0 ----- * Seperate standalone cache tests * Import \_memcache\_pool normally * Create Environment cache pool * Handle memcache pool arguments collectively * Updated from global requirements * Allow specifying a region name to auth\_token * Updated from global requirements * Allow to use oslo.config without global CONF * Updated from global requirements * Updated from global requirements * Updated from global requirements * Updated from global requirements * Move common request processing to base class * Fix rst * py34 not py33 is tested and supported * Refactor extract method for offline validation * Send the correct user-agent to Keystone * Move enforcement and time validation to base class * Separate the fetch and validate parts of auth\_token * Fixes modules index generated by Sphinx 2.1.0 ----- * Add token\_auth helper to request * Add user\_token and service\_token to request * Create a simple base class from AuthProtocol * Switch from deprecated oslo\_utils.timeutils.strtime * Updated from global requirements * Refactor \_confirm\_token\_bind takes AccessInfo * Make token bind work with a request * Rename \_LOG to log in auth\_token middleware * Don't allow webob to set a default content type * Prevent a UnicodeDecodeError in the s3token middleware * Remove install\_venv\_common and fix typo in memorycache 2.0.0 ----- * Ensure cache keys are a known/fixed length * Updated from global requirements * Refactor request methods onto request object * validate\_token returns AccessInfo * Updated from global requirements * Fixes a spelling error in a test name * Remove custom header handling * Unit tests catch deprecated function usage * Common base class for unit tests * Stop using function deprecated in py34 * Move bandit requirement to test-requirements.txt * Fetch user token from request rather than env * Remove the \_msg\_format function * Base use webob * Don't rely on token\_info for header building * Move project included validation * Depend on keystoneclient for expiration checking * Don't store expire into memcache * Removes discover from test-reqs * Drop py2.6 support for keystone middleware * Create new user plugin tests * Add an explicit test failure condition when auth\_token is missing * Fixup test-requirements-py3.txt * Fix list\_opts test to not check all deps * Refactor certificate fetch functions * tox env for Bandit * Cleanup token hashes generated by cache * Updated from global requirements * Improved handling of endpoints missing urls * Refactor: extract echo\_app from enclosing class * Add keystone v3 API to fetch revocation list * Simplify request making in auth\_token tests * Change auth\_token to use keystoneclient * Deprecate auth\_token authentication * Updated from global requirements 1.6.1 ----- * Ignore cover directory * Remove superfluous / spammy log line * Drop use of 'oslo' namespace package * Port keystonemiddleware to Python 3 * Remove unused iso8601 dependency * Update README to work with release tools 1.6.0 ----- * Uncap library requirements for liberty * Remove retry parameter * Fix s3\_token middleware parsing insecure option * Updated from global requirements * Pull echo service out of auth\_token * Fix typos in keystonemiddleware * Rename requests mock object in testing * Update auth\_token config docs * Crosslink to other sites that are owned by Keystone * Move \_memcache\_pool into auth\_token * Move unit tests into tests.unit 1.5.0 ----- * Allow loading auth plugins via overrides * Updated from global requirements * Delay denial when service token is invalid * Updated from global requirements * Move UserAuthPlugin into its own file * Extract IdentityServer into file * Extract all TokenCache related classes to file * Break default auth plugin into file * Extract revocations to file * Extract SigningDirectory into file * Separate exceptions into their own file * Updated from global requirements * Updated from global requirements * Move auth\_token into its own folder * Updated from global requirements 1.4.0 ----- * Refactor auth\_token revocation list members to new class * Refactor extract class for signing directory * Turn our auth plugin into a token interface * iso expires should be returned in one place * move add event creation logic to keystonemiddleware * Updated from global requirements * Sync with oslo-incubator * Use oslo.context instead of incubator code * Refactor auth\_uri handling * make audit event scoped to request session and not middleware * Updated from global requirements * Remove custom string truth handling * Updated from global requirements * incorrect reference in enabling audit middleware * Updated from global requirements * Enforce check F821 and H304 * Switch from oslo.config to oslo\_config * Switch from oslo.serialization to oslo\_serialization * Switch from oslo.utils to oslo\_utils * Add python-memcached to test-requirements * Correct failures for check E122 * Correct failures for check H703 * Updated from global requirements * Correct failures for check H238 * Move to hacking 0.10 * Updated from global requirements * Use a test fixture for mocking time * Fix environ keys missing HTTP\_ prefix * support micro version if sent * Fix passing parameters to log message * Correct incorrect rst in docstrings * remove unused variable in \_IdentityServer 1.3.1 ----- * Fix auth\_token does version request for no token * Adds Memcached dependencies doc * fallback to online validation if offline validation fails 1.3.0 ----- * documentation for audit middleware * remove the unused method \_will\_expire\_soon * Updated from global requirements * Use newer requests-mock syntax * Allow loading other auth methods in auth\_token * Auth token tests create temp cert directory * Add a test to ensure the version check error * Split identity server into v2 and v3 * Workflow documentation is now in infra-manual * Use real discovery object in auth\_token middleware * Updated from global requirements * Make everything in audit middleware private * Updated from global requirements * Adding audit middleware to keystonemiddleware * Fix paste config option conversion for auth options * Auth token supports deprecated names for paste conf options * Correct tests to use strings in conf * Change occurrences of keystone to identity server * Updated from global requirements * Updated from global requirements * Updated from global requirements * I18n * Adds space after # in comments * Update python-keystoneclient reference * Use Discovery fixtures for auth token tests * Convert authentication into a plugin * Add versions to requests * Use an adapter in IdentityServer * Use connection retrying from keystoneclient * Updated from global requirements * Use correct name of oslo debugger script * Use new ksc features in User Token Plugin * Remove netaddr package requirement * add context to keystonemiddleware * Updated from global requirements * Improve help strings * Updated from global requirements * Changing the value type of http\_connect\_timeout * Revert "Support service user and project in non-default domain" * Replace httpretty with requests-mock * Encode middleware error message as bytes * Docstring cleanup * Remove HTTP\_X\_STORAGE\_TOKEN doc * Fix reference to middleware architecture doc * Clean up the middleware docs * Update oslo-incubator and switch to oslo.{utils,serialization} * Refactor auth\_token cache 1.2.0 ----- * Add an optional advanced pool of memcached clients * Fix auth\_token for old oslo.config * Support service user and project in non-default domain * Add composite authentication support * Fix test failure after discovery hack * Updated from global requirements * BaseAuthTokenMiddlewareTest.setUp call super normally * Remove unused iso8601 * Use oslo\_debug\_helper and remove our own version * convert the conf value into correct type * Always add auth URI to unauthorized requests * Work toward Python 3.4 support and testing * warn against sorting requirements * Always supply a username to auth\_token tests setup * Create an Auth Plugin to pass to users * Updated from global requirements 1.1.1 ----- * Hash for PKIZ * auth\_token cached token handling * Add a test for re-caching a token * Updated from global requirements * Remove intersphinx mappings * Use oslosphinx in keystonemiddlware for documentation * Updated from global requirements * Convert auth\_token middleware to use sessions 1.1.0 ----- * Updated from global requirements * Remove mox dependency * move webob from test-requirements to requirements * remove unused dep: stevedore * remove unused dep: prettytable * Example JSON files should be human-readable * Updated from global requirements * Mark keystonemiddleware as being a universal wheel * Use keystoneclient fixtures in middleware tests * prefer identity API v3 over v2 in auth\_token * Clean up openstack-common.conf * Sync with oslo-incubator 569979adf * Refactor auth\_token, move identity server members to class 1.0.0 ----- * Expose an entry point to list auth\_token middleware config options * Privatize Everything * Privatize Everything * add CONTRIBUTING.rst * add README * Update setup.cfg to remove keystoneclient ref * Bring over debug\_helper.sh * Update requirement files * Update .gitignore files * Correct Doc location and update for middleware only * Move Docs to the right location * Remove .update-venv * Update middleware and tests for new package * Update requirements * Update MANIFEST.in * Remove unused testing files from keystoneclient * Move examples split to new location * Move ec2\_token to new location * Add in original keystoneclient test-requirements.txt * Initial oslo-incubator sync * Cleanup unused testr.conf file * Move tests to new location * Moving middleware to new location * Initial commit * Fix 500 error if request body is not JSON object * auth\_token \_cache\_get checks token expired * auth\_token \_cache\_get checks token expired * Using six.u('') instead of u'' * Session Documentation * Link to docstrings in using-api-v3 * Refactor auth\_token token cache members to class * Refactor auth\_token token cache members to class * Add service\_name to URL discovery * Don't use mock non-exist method assert\_called\_once * Remove \_factory methods from auth plugins * Make get\_oauth\_params conditional for specific oauthlib versions * Changes exception raised by v3.trusts.update() * Add role assignments as concept in Client API V3 docs * Fix tests to use UUID strings rather than ints for IDs * Clean up oauth auth plugin code * Add endpoint handling to Token/Endpoint auth * Add support for extensions-list * auth\_token middleware hashes tokens with configurable algorithm * auth\_token middleware hashes tokens with configurable algorithm * Remove left over vim headers * Add /role\_assignments endpoint support * Authenticate via oauth * Auth Plugin invalidation * Move DisableModuleFixture to utils * replace string format arguments with function parameters * Fixes an erroneous type check in a test * auth\_token hashes PKI token once * auth\_token hashes PKI token once * Compressed Signature and Validation * Compressed Signature and Validation * Compressed Signature and Validation * OAuth request/access token and consumer support for oauth client API * Regions Management * Discovery URL querying functions * Move auth\_token tests not requiring v2/v3 to new class * Cached tokens aren't expired * Cached tokens aren't expired * Move auth\_token cache pool tests out of NoMemcache * Fixed the size limit tests in Python 3 * Make auth\_token return a V2 Catalog * Make auth\_token return a V2 Catalog * Fix client fixtures * fixed typos found by RETF rules * fixed typos found by RETF rules * auth\_token configurable check of revocations for cached * auth\_token configurable check of revocations for cached * Remove unused AdjustedBaseAuthTokenMiddlewareTest * auth\_token test remove unused fake\_app parameter * Fix typo in BaseAuthTokenMiddlewareTest * Enhance tests for auth\_token middleware * Limited use trusts * Debug log when token found in revocation list * Ensure that cached token is not revoked * Fix the catalog format of a sample token * remove universal\_newlines * replace double quotes with single * Deprecate admin\_token option in auth\_token * Create a V3 Token Generator * Implement endpoint filtering functionality on the client side * Fix typo of ANS1 to ASN1 * Fix typo of ANS1 to ASN1 * Add new error for invalid response * Rename HTTPError -> HttpError * Add CRUD operations for Federation Mapping Rules * Don't use generic kwargs in v2 Token Generation * Update docs for auth\_token middleware config options * Allow session to return an error response object * Add service name to catalog * Hash functions support different hash algorithms * Add CRUD operations for Identity Providers * eliminate race condition fetching certs * eliminate race condition fetching certs * Allow passing auth plugin as a parameter * Prefer () to continue line per PEP8 * Prefer () to continue line per PEP8 * Use \`HttpNotImplemented\` in \`tests.v3.test\_trusts\` * Ensure JSON headers in Auth Requests * Create a test token generator and use it * Safer noqa handling * Rename request\_uri to identity\_uri * Tests should use identity\_uri by default * Replace auth fragements with identity\_uri * Replace auth fragements with identity\_uri * Remove releases.rst from keystone docs * Handle URLs via the session and auth\_plugins * Add a method for changing a user's password in v3 * sanity check memcached availability before running tests against it * Change the default version discovery URLs * add functional test for cache pool * Add a positional decorator * add pooling for cache references * add pooling for cache references * use v3 api to get certificates * use v3 api to get certificates * Don't use a connection pool unless provided * Reference docstring for auth\_token fields * Docs link to middlewarearchitecture * Uses explicit imports for \_ * Discover should support other services * Replace httplib.HTTPSConnection in ec2\_token * Revert "Add request/access token and consumer..." * Revert "Authenticate via oauth" * Fix doc build errors * Fix doc build errors * Fix doc build errors * Generate module docs * Authenticate via oauth * Add request/access token and consumer support for keystoneclient * Add 'methods' to all v3 test tokens * Use AccessInfo in auth\_token middleware * Add 'methods' to all v3 test tokens * Handle Token/Endpoint authentication * Split sample PKI token generation * Fix retry logic * Fix state modifying catalog tests * Remove reference to non-existent shell doc * increase default revocation\_cache\_time * Make keystoneclient not log auth tokens * improve configuration help text in auth\_token * Log the command output on CertificateConfigError * V3 xml responses should use v3 namespace * Enforce scope mutual exclusion for trusts * Token Revocation Extension * Atomic write of certificate files and revocation list * Privatize auth construction parameters * Set the right permissions for signing\_dir in tests * deprecate XML support in favor of JSON * Capitalize Client API title consistently * Remove http\_handler config option in auth\_token * Rely on OSLO.config * Use admin\_prefix consistently * demonstrate auth\_token behavior with a simple echo service * Remove redundant default value None for dict.get * Remove redundant default value None for dict.get * correct typo of config option name in error message * remove extra indentation * refer to non-deprecated config option in help * Create V3 Auth Plugins * Create V2 Auth Plugins * Fix role\_names call from V3 AccessInfo * Interactive prompt for create user * Replace assertEqual(None, \*) with assertIsNone in tests * Ensure domains.list filtered results are correct * Test query-string for list actions with filter arguments * Fix keystone command man page * Add link to the v3 client api doc * Fix references to auth\_token in middlewarearchitecture doc * Use WebOb directly in ec2\_token middleware * Don't use private last\_request variable * Python: Pass bytes to derive\_keys() * Use WebOb directly for locale testing * Make sure to unset all variable starting with OS\_ * Python3: use six.moves.urllib.parse.quote instead of urllib.quote * Remove vim header * Remove vim header * Remove vim header * Python3: httpretty.last\_request().body is now bytes * Python3: fix test\_insecure * Deprecate s3\_token middleware * Python3: webob.Response.body must be bytes * Python 3: call functions from memcache\_crypt.py with bytes as input * Python 3: call functions from memcache\_crypt.py with bytes as input * Use requests library in S3 middleware * Use requests library in S3 middleware * Python 3: make tests from v2\_0/test\_access.py pass * Python 3: make tests from v2\_0/test\_access.py pass * Create Authentication Plugins * Fix debug curl commands for included data * Add back --insecure option to CURL debug * Use HTTPretty in S3 test code * Provide a conversion function for creating session * Update reference to middlewarearchitecture doc * Update middlewarearchitecture config options docs * Remove support for old Swift memcache interface * Remove support for old Swift memcache interface * Replace urllib/urlparse with six.moves.\* * Python 3: fix tests/test\_utils.py * Python 3: Fix an str vs bytes issue in tempfile * Return role names by AccessInfo.role\_names * Copy s3\_token middleware from keystone * Copy s3\_token middleware from keystone * build auth context from middleware * Fix E12x warnings found by Pep8 1.4.6 * Fix typos in documents and comments * Fix typos in documents and comments * Consistently support kwargs across all v3 CRUD Manager ops * Use six to make dict work in Python 2 and Python 3 * Python 3: set webob.Response().body to a bytes value * Remove test\_print\_{dict,list}\_unicode\_without\_encode * Tests use cleanUp rather than tearDown * Adjust import items according to hacking import rule * Adjust import items according to hacking import rule * Adjust import items according to hacking import rule * Replace assertTrue with explicit assertIsInstance * Fix discover command failed to read extension list issue * Fix incorrect assertTrue usage * Make assertQueryStringIs usage simpler * auth\_token tests use assertIs/Not/None * Make common log import consistent * Python 3: Use HTTPMessage.get() rather than HTTPMessage.getheader() * auth\_token tests close temp file descriptor * Tests cleanup temporary files * Removes use of timeutils.set\_time\_override * Controllable redirect handling * Verify token binding in auth\_token middleware * Verify token binding in auth\_token middleware * Fix auth\_token middleware test invalid cross-device link issue * Add unit tests for generic/shell.py * Rename using-api.rst to using-api-v2.rst * Documents keystone v3 API usage - part 1 * v3 test utils, don't modify input parameter * Fix error in v3 credentials create/update * Rename instead of writing directly to revoked file * Correctly handle auth\_url/token authentication * Remove debug specific handling * Fix missed management\_url setter in v3 client * Add service catalog to domain scoped token fixture * Change assertEquals to assertIsNone * Avoid meaningless comparison that leads to a TypeError * Python3: replace urllib by six.moves.urllib * Fix --debug handling in the shell * Rename tokenauth to authtoken in the doc * use six.StringIO for compatibility with io.StringIO in python3 * Properly handle Regions in keystoneclient * Use testresources for example files * Discover supported APIs * Warn user about unsupported API version * Add workaround for OSError raised by Popen.communicate() * Use assertIn where appropriate * Extract a base Session object * Do not format messages before they are logged * keystoneclient requires an email address when creating a user * Fix typo in keystoneclient * Encode the text before print it to console * Opt-out of service catalog * Opt-out of service catalog * Opt-out of service catalog * Remove deprecated auth\_token middleware * "publicurl" should be required on endpoint-create * Update the management url for every fetched token * Fix python3 incompatible use of urlparse * Convert revocation list file last modified to UTC * Convert revocation list file last modified to UTC * Migrate the keystone.common.cms to keystoneclient * Migrate the keystone.common.cms to keystoneclient * Avoid returning stale token via auth\_token property * Remove SERVICE\_TOKEN and SERVICE\_ENDPOINT env vars * Make ROOTDIR determination more robust * Replace OpenStack LLC with OpenStack Foundation * Replace OpenStack LLC with OpenStack Foundation * Replace OpenStack LLC with OpenStack Foundation * Replace OpenStack LLC with OpenStack Foundation * Add AssertRequestHeaderEqual test helper and make use of it * python3: Make iteritems py3k compat * Normalize datetimes to account for tz * Normalize datetimes to account for tz * assertEquals is deprecated, use assertEqual (H602) * remove the nova dependency in the ec2\_token middleware * Fix H202 assertRaises Exception * Fix H202 assertRaises Exception * Refactor for testability of an upcoming change * Refactor for testability of an upcoming change * Allow v2 client authentication with trust\_id * Fix misused assertTrue in unit tests * Add auth\_uri in conf to avoid unnecessary warning * Move tests in keystoneclient * Set example timestamps to 2038-01-18T21:14:07Z * Replace HttpConnection in auth\_token with Requests * Replace HttpConnection in auth\_token with Requests * Support client generate literal ipv6 auth\_uri base on auth\_host * Log user info in auth\_token middleware * Changed header from LLC to Foundation based on trademark policies * python3: Use from future import unicode\_literals * Fix and enable gating on F841 * Use OSLO jsonutils instead of json module * Allow configure the number of http retries * Use hashed token for invalid PKI token cache key * Make auth\_token middleware fetching respect prefix * Move all opens in auth\_token to be in context * Refactor Keystone to use unified logging from Oslo * Refactor verify signing dir logic * Fixes files with wrong bitmode * Don't cache tokens as invalid on network errors * Fix a typo in fetch\_revocation\_list * auth\_uri (public ep) should not default to auth\_\* values (admin ep) * Adds help in keystone\_authtoken config opts * python3: Add basic compatibility support * remove swift dependency of s3 middleware * flake8: fix alphabetical imports and enable H306 * Drop webob from auth\_token.py * no logging on cms failure * rm improper assert syntax * Fix and enable gating on H402 * Raise key length defaults * Fix auth\_token.py bad signing\_dir log message * Fix and enable H401 * Revert environment module usage in middleware * Fix the cache interface to use time= by default * Change memcache config entry name in Keystone to be consistent with Oslo * Change memcache config entry name in Keystone to be consistent with Oslo * Fix memcache encryption middleware * Fix memcache encryption middleware * Isolate eventlet code into environment * Provide keystone CLI man page * Check Expiry * Check Expiry * import only modules (flake8 H302) * Satisfy flake8 import rules F401 and F403 * Default signing\_dir to secure temp dir (bug 1181157) * Use testr instead of nose * Securely create signing\_dir (bug 1174608) * adding notes about dealing with exceptions in the client * Fix v3 with UUID and memcache expiring * Fix v3 with UUID and memcache expiring * Allow keystoneclient to work with older keystone installs * Wrap config module and require manual setup (bug 1143998) * Config value for revocation list timeout * Cache tokens using memorycache from oslo * Cache tokens using memorycache from oslo * xml\_body returns backtrace on XMLSyntaxError * Make auth\_token lazy load the auth\_version * Doc info and other readability improvements * Retry http\_request and json\_request failure * Use v2.0 api by default in auth\_token middleware * Fix auth-token middleware to understand v3 tokens * Fix auth-token middleware to understand v3 tokens * Remove test dep on name of dir (bug 1124283) * bug 1131840: fix auth and token data for XML translation * Rework S3Token middleware tests * v3 token API * Use oslo-config-2013.1b3 * Allow configure auth\_token http connect timeout * Allow configure auth\_token http connect timeout * Fix spelling mistakes * Mark password config options with secret * Fixes 'not in' operator usage * Fix thinko in self.middleware.cert\_file\_missing * Limit the size of HTTP requests * Blueprint memcache-protection: enable memcache value encryption/integrity check * Blueprint memcache-protection: enable memcache value encryption/integrity check * Warning message is not logged for valid token-less request * Use os.path to find ~/keystone-signing (bug 1078947) * Remove iso8601 dep in favor of openstack.common * remove unused import * Bug 1052674: added support for Swift cache * URL-encode user-supplied tokens (bug 974319) * Fix middleware logging for swift * Remove swift auth * Don't try to split a list of memcache servers * Import auth\_token middleware from keystoneclient * Throw validation response into the environment * Add auth-token code to keystoneclient, along with supporting files * Add auth-token code to keystoneclient, along with supporting files * Use the right subprocess based on os monkeypatch * Make initial structural changes to keystoneclient in preparation to moving auth\_token here from keystone. No functional change should occur from this commit (even though it did refresh a newer copy of openstack.common.setup.py, none of the newer updates are in functions called from this client) * fixes bug 1074172 * HACKING compliance: consistent use of 'except' * auth\_token hash pki key PKI tokens on hash in memcached when accessed by auth\_token middelware * Move 'opentack.context' and 'openstack.params' definitions to keystone.common.wsgi * Replace refs to 'Keystone API' with 'Identity API' * replacing PKI token detection from content length to content prefix. (bug 1060389) * updating base keystoneclient documentation * updating keystoneclient doc theme * Backslash continuation cleanup * Check for expected cfg impl (bug 1043479) * Fix PEP8 issues * Fix auth\_token middleware to fetch revocation list as admin * allow middleware configuration from app config * Change underscores in new cert options to dashes * PKI Token revocation * Use user home dir as default for cache * Set default signing\_dir based on os USER * Test for Cert by name * Cryptographically Signed tokens * Prevent service catalog injection in auth\_token * Admin Auth URI prefix * Support 2-way SSL with Keystone server if it is configured to enforce 2-way SSL. See also https://review.openstack.org/#/c/7706/ for the corresponding review for the 2-way SSL addition to Keystone * Change CLI options to use dashes * Keystone should use openstack.common.jsonutils * Removed unused import * Reorder imports by full module path * Pass serviceCatalog in auth\_token middleware * 400 on unrecognized content type (bug 1012282) * PEP8 fixes * Move docs to doc * fix importing of optional modules in auth\_token * blueprint 2-way-ssl * Fixes some pep8 warning/errors * Update swift\_auth documentation * Add ACL check using : format * Use X\_USER\_NAME and X\_ROLES headers * Allow other middleware overriding authentication * Backslash continuation removal (Keystone folsom-1) * Added 'NormalizingFilter' middleware * Make sure we parse delay\_auth\_decision as boolean * Exit on error in a S3 way * Add a \_ at the end of reseller\_prefix default * additional logging to support debugging auth issue * Add support to swift\_auth for tokenless authz * Improve swift\_auth test coverage + Minor fixes * S3 tokens cleanups * updating docs to include creating service accts * Rename tokenauth to authtoken * Remove nova-specific middlewares * Remove glance\_auth\_token middleware * Update username -> name in token response * Refactor keystone.common.logging use (bug 948224) * Allow connect to another tenant * Improved legacy tenancy resolution (bug 951933) * Fix iso8601 import/use and date comparaison * Add simple set of tests for auth\_token middleware * Add token caching via memcache * Added license header (bug 929663) * Make sure we have a port number before int it * HTTP\_AUTHORIZATION was used in proxy mode * Add reseller admin capability * improve auth\_token middleware * Unpythonic code in redux in auth\_token.py * Handle KeyError in \_get\_admin\_auth\_token * Provide request to Middleware.process\_response() * Set tenantName to 'admin' in get\_admin\_auth\_token * XML de/serialization (bug 928058) * Update auth\_token middleware so it sets X\_USER\_ID * Fix case of admin role in middleware * Remove extraneous \_validate\_claims() arg * Fix copyright dates and remove duplicate Apache licenses * Re-adds admin\_pass/user to auth\_tok middleware * Update docs for Swift and S3 middlewares * Added Apache 2.0 License information * Update swift token middleware * Add s3\_token * Fixes role checking for admin check * Add tests for core middleware * termie all the things * be more safe with getting json aprams * fix keystoneclient tests * pep8 cleanup * doc updates * fix middleware * update some names * fix some imports * re-indent * check for membership * add more middleware * woops * add legacy middleware keystonemiddleware-4.21.0/requirements.txt0000666000175100017510000000122513225160624021010 0ustar zuulzuul00000000000000# The order of packages is significant, because pip processes them in the order # of appearance. Changing the order has an impact on the overall integration # process, which may cause wedges in the gate later. keystoneauth1>=3.3.0 # Apache-2.0 oslo.cache>=1.26.0 # Apache-2.0 oslo.config>=5.1.0 # Apache-2.0 oslo.context>=2.19.2 # Apache-2.0 oslo.i18n>=3.15.3 # Apache-2.0 oslo.log>=3.30.0 # Apache-2.0 oslo.serialization!=2.19.1,>=2.18.0 # Apache-2.0 oslo.utils>=3.31.0 # Apache-2.0 pbr!=2.1.0,>=2.0.0 # Apache-2.0 pycadf!=2.0.0,>=1.1.0 # Apache-2.0 python-keystoneclient>=3.8.0 # Apache-2.0 requests>=2.14.2 # Apache-2.0 six>=1.10.0 # MIT WebOb>=1.7.1 # MIT keystonemiddleware-4.21.0/LICENSE0000666000175100017510000002717413225160624016544 0ustar zuulzuul00000000000000Copyright (c) 2009 Jacob Kaplan-Moss - initial codebase (< v2.1) Copyright (c) 2011 Rackspace - OpenStack extensions (>= v2.1) Copyright (c) 2011 Nebula, Inc - Keystone refactor (>= v2.7) All rights reserved. Apache License Version 2.0, January 2004 http://www.apache.org/licenses/ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 1. Definitions. "License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document. "Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License. "Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. "You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License. "Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files. "Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types. "Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below). "Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof. "Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution." "Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work. 2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form. 3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed. 4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions: (a) You must give any other recipients of the Work or Derivative Works a copy of this License; and (b) You must cause any modified files to carry prominent notices stating that You changed the files; and (c) You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and (d) If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License. You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License. 5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions. 6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file. 7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License. 8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages. 9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability. --- License for python-keystoneclient versions prior to 2.1 --- All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. 3. Neither the name of this project nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. keystonemiddleware-4.21.0/AUTHORS0000664000175100017510000001515613225161127016601 0ustar zuulzuul00000000000000Adam Young Alan Pevec Alexander Makarov Alexei Kornienko Alistair Coles Alvaro Lopez Garcia AmalaBasha Andreas Jaeger Andreas Jaeger Andrey Kurilin Andrey Pavlov Anh Tran Anthony Young Arun Kant Boris Bobrov Boris Bobrov Brad Pokorny Brant Knudson Brian Cline Brian Lamar Brian Waldon Bryan D. Payne Bryan Davidson Chang Bo Guo ChangBo Guo(gcb) Chmouel Boudjnah Chmouel Boudjnah Chris Dent Christian Berendt Christian Schwede Chuck Short Chuck Thier Clark Boylan Clint Byrum Colleen Murphy Colleen Murphy Cyril Roelandt D G Lee Dan Prince Dan Radez Daniel Gollub Davanum Srinivas Dave Chen David Höppner <0xffea@gmail.com> David Stanek Dazhao Dean Troyer Deepak Dirk Mueller Divyesh Khandeshi Dmitry Tantsur Dolph Mathews Donagh McCabe Doug Hellmann Emilien Macchi Eoghan Glynn Eric Brown Eric Guo Flavio Percoco Florent Flament Gage Hugo Gordon Chung Guang Yee Guang Yee Hangdong Zhang Harry Rybacki Henry Nash Igor A. Lukyanenkov Ilya Kharin Ilya Pekelny Iswarya_Vakati Jaewoo Park Jakub Ruzicka Jamie Lennox Jamie Lennox Jamie Lennox Janonymous Jeremy Stanley Jesse Andrews Ji-Wei Joe Gordon Joe Gordon Joe Heck Joel Friedly John Dennis Julien Danjou Kevin Benton Kevin L. Mitchell Kieran Spear Kui Shi Kun Huang Lance Bragstad Lance Bragstad Lauren Taylor Lei Zhang Liem Nguyen Lin Hua Cheng LiuNanke Lucas Alvares Gomes Marek Denis Maru Newby Masahito Muroi Matt Fischer Matthew Edmonds Matthew Treinish Matthieu Huin Mehdi Abaakouk Mehdi Abaakouk Michael J Fork Mitsuhiro SHIGEMATSU Monty Taylor Morgan Fainberg Morgan Fainberg Mouad Benchchaoui Navid Pustchi Nguyen Hung Phuong OndÅ™ej Nový OpenStack Release Bot Pete Zaitcev Peter Portante Pádraig Brady Qiu Yu Rafael Durán Castañeda Rodrigo Duarte Sousa Rodrigo Duarte Sousa Roman Bodnarchuk Roxana Gherle Samuel de Medeiros Queiroz Sean Perry Sean Winn Shevek Spencer Yu Steve Martinelli Steve Martinelli Steven Hardy Stuart McLaren Thomas Bechtold Thomas Goirand Thomas Goirand Thomas Herve Thomas Herve Tin Lam Tin Lam Tom Cocozzello Tom Fifield Tony Breeds Tristan Cacqueray Van Hung Pham Victor Stinner Vishvananda Ishaya Wu Wenxiang Yaguang Tang Yatin Kumbhare Zhenguo Niu Zhi Yan Liu ZhiQiang Fan ZhiQiang Fan Zhongyue Luo Zhongyue Luo Zuul ankita_wagh ayoung bhagyashris gordon chung guang-yee guang-yee huangtianhua iswarya_vakati ji-xuepeng lawrancejing lingyongxu liuxiaoyang lrqrun mathrock nachiappan-veerappan-nachiappan termie ubuntu wanghong xingzhou xuhaigang zhang-jinnan zlyqqq keystonemiddleware-4.21.0/.stestr.conf0000666000175100017510000000011713225160624017774 0ustar zuulzuul00000000000000[DEFAULT] test_path=${OS_TEST_PATH:-./keystonemiddleware/tests/unit} top_dir=./keystonemiddleware-4.21.0/PKG-INFO0000664000175100017510000000470013225161130016611 0ustar zuulzuul00000000000000Metadata-Version: 1.1 Name: keystonemiddleware Version: 4.21.0 Summary: Middleware for OpenStack Identity Home-page: https://docs.openstack.org/developer/keystonemiddleware/ Author: OpenStack Author-email: openstack-dev@lists.openstack.org License: UNKNOWN Description-Content-Type: UNKNOWN Description: ======================== Team and repository tags ======================== .. image:: https://governance.openstack.org/badges/keystonemiddleware.svg :target: https://governance.openstack.org/reference/tags/index.html .. Change things from this point on Middleware for the OpenStack Identity API (Keystone) ==================================================== .. image:: https://img.shields.io/pypi/v/keystonemiddleware.svg :target: https://pypi.python.org/pypi/keystonemiddleware/ :alt: Latest Version .. image:: https://img.shields.io/pypi/dm/keystonemiddleware.svg :target: https://pypi.python.org/pypi/keystonemiddleware/ :alt: Downloads This package contains middleware modules designed to provide authentication and authorization features to web services other than `Keystone `. The most prominent module is ``keystonemiddleware.auth_token``. This package does not expose any CLI or Python API features. For information on contributing, see ``CONTRIBUTING.rst``. * License: Apache License, Version 2.0 * Documentation: https://docs.openstack.org/keystonemiddleware/latest/ * Source: https://git.openstack.org/cgit/openstack/keystonemiddleware * Bugs: https://bugs.launchpad.net/keystonemiddleware For any other information, refer to the parent project, Keystone: https://github.com/openstack/keystone Platform: UNKNOWN Classifier: Environment :: OpenStack Classifier: Intended Audience :: Information Technology Classifier: Intended Audience :: System Administrators Classifier: License :: OSI Approved :: Apache Software License Classifier: Operating System :: POSIX :: Linux Classifier: Programming Language :: Python Classifier: Programming Language :: Python :: 2 Classifier: Programming Language :: Python :: 2.7 Classifier: Programming Language :: Python :: 3 Classifier: Programming Language :: Python :: 3.5 keystonemiddleware-4.21.0/config-generator/0000775000175100017510000000000013225161130020744 5ustar zuulzuul00000000000000keystonemiddleware-4.21.0/config-generator/keystonemiddleware.conf0000666000175100017510000000015313225160624025522 0ustar zuulzuul00000000000000[DEFAULT] output_file = etc/keystone.conf.sample wrap_width = 79 namespace = keystonemiddleware.auth_token keystonemiddleware-4.21.0/tox.ini0000666000175100017510000000372213225160624017043 0ustar zuulzuul00000000000000[tox] minversion = 2.0 skipsdist = True envlist = py35,py27,pep8,releasenotes [testenv] usedevelop = True install_command = {toxinidir}/tools/tox_install.sh {env:UPPER_CONSTRAINTS_FILE:https://git.openstack.org/cgit/openstack/requirements/plain/upper-constraints.txt} {opts} {packages} setenv = VIRTUAL_ENV={envdir} BRANCH_NAME=master CLIENT_NAME=keystonemiddleware OS_STDOUT_CAPTURE=1 OS_STDERR_CAPTURE=1 OS_TEST_TIMEOUT=60 TESTS_DIR=./keystonemiddleware/tests/unit/ deps = -r{toxinidir}/requirements.txt -r{toxinidir}/test-requirements.txt .[audit_notifications] commands = stestr run {posargs} [testenv:pep8] commands = flake8 bandit -r keystonemiddleware -x tests -n5 [testenv:bandit] # NOTE(browne): This is required for the integration test job of the bandit # project. Please do not remove. commands = bandit -r keystonemiddleware -x tests -n5 [testenv:venv] commands = {posargs} [testenv:cover] commands = python setup.py testr --coverage --testr-args='{posargs}' [testenv:debug] commands = oslo_debug_helper -t keystonemiddleware/tests {posargs} [flake8] # D100: Missing docstring in public module # D101: Missing docstring in public class # D102: Missing docstring in public method # D103: Missing docstring in public function # D104: Missing docstring in public package # D203: 1 blank line required before class docstring (deprecated in pep257) ignore = D100,D101,D102,D103,D104,D203 show-source = True exclude = .venv,.tox,dist,doc,*egg,build [testenv:docs] deps = -r{toxinidir}/doc/requirements.txt commands= doc8 doc/source python setup.py build_sphinx [testenv:releasenotes] deps = -r{toxinidir}/doc/requirements.txt commands = sphinx-build -a -E -W -d releasenotes/build/doctrees -b html releasenotes/source releasenotes/build/html [hacking] import_exceptions = keystonemiddleware.i18n [doc8] extensions = .rst, .yaml # lines should not be longer than 79 characters. max-line-length = 79 keystonemiddleware-4.21.0/examples/0000775000175100017510000000000013225161130017331 5ustar zuulzuul00000000000000keystonemiddleware-4.21.0/examples/pki/0000775000175100017510000000000013225161130020114 5ustar zuulzuul00000000000000keystonemiddleware-4.21.0/examples/pki/private/0000775000175100017510000000000013225161130021566 5ustar zuulzuul00000000000000keystonemiddleware-4.21.0/examples/pki/private/cakey.pem0000666000175100017510000000325013225160624023376 0ustar zuulzuul00000000000000-----BEGIN PRIVATE KEY----- MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQCl8906EaRpibQF cCBWfxzLi5x/XpZ9iL6UX92NrSJxcDbaGws7s+GtjgDy8UOEonesRWTeqQEZtHpC 3/UHHOnsA8F6ha/pq9LioqT7RehCnZCLBJwh5Ct+lclpWs15SkjJD2LTDkjox0eA 9nOBx+XDlWyU/GAyqx5Wsvg/Kxr0iod9/4IcJdnSdUjq4v0Cxg/zNk08XPJX+F0b UDhgdUf7JrAmmS5LA8wphRnbIgtVsf6VN9HrbqtHAJDxh8gEfuwdhEW1df1fBtZ+ 6WMIF3IRSbIsZELFB6sqcyRj7HhMoWMkdEyPb2f8mq61MzTgE6lJGIyTRvEoFie7 qtGADIofAgMBAAECggEBAJ47X3y2xaU7f0KQHsVafgI2JAnuDl+zusOOhJlJs8Wl 0Sc1EgjjAxOQiqcaE96rap//qqYDTuFLjCenkuItV32KNzizr3+GLZWaruRHS6X4 xpFG2/gUrsQL3fdudOxpP+01lmzW+f25xRvZ4VilWRabquSDntWxA0R3cOwKFbGD uuwbTw3pBrRfCk/2IdpQtRrvvkVIFiYT6b/zeCQzhp4RETbC0oxqcEEOIUGmimAV 9cbwafinxCo54cOfX4JAh3j7Mp3eQUymoFk5gnmIeVe0QmpH2VkN7eItrhEvHKOk On7a5xvQ8s3wqPV5ZawHQcqar/p3QnGkiT6a+8LkIMECgYEA2iJ2DprTGZFRN0M7 Yj4WLsSC3/GKK8eYsKG3TvMrmPqUDaiWLIvBoc1Le59x9eoF7Mha+WX+cAFL+GTg 1sB+PUZZStpf1R1tGvMldvpQ+5GplUBpuQe4J0n5rCG6+5jkvSr7xO+G1B+C3GFq KR3iltiW5WJRVwh2k8yGvx3agyUCgYEAwsKFX82F7O+9IVud1JSQWmZMiyEK+DEX JRnwx4HBuWr+AZqbb0grRRb6x8JTUOD4T7DZGxTaAdfzzRjKU2sBAO8VCgaj2Auv 5nsbvfXvrmDDCqwoaD2PMy+kgFvE0QTh65tzuGXl1IgpIYSC1JwnP6kOeUDbqE+k UXzfVZzDdvMCgYByk9dfJIPt0h7O4Em4+NO+DQqRhtYE2PqjDM60cZZc7IIICp2X GHHFA4i6jq3Vde9WyIbAqYpUWtoExzgylTm6BdGxN7NOxf4hQcZUEHepLIHfG85s mlloibrTZ4RH06+SjZlhgE9Z7JNYHvMcVc5HXc0k/9ep15AxYiUFDjFQ4QKBgG7i k089U4/X2wWgBNdgkmN1tQTNllJCmNvdzhG41dQ8j0vYe8C7BS+76qJLCGaW/6lX lfRuRcUg78UI5UDjPloKxR7FMwmxdb+yvdPEr2bH3qQ36nWW/u30pSMTnJYownwD MLp/AYCk2U4lBNwJ3+rF1ODCRY2pcnOWtg0nSL5zAoGAWRoOinogEnOodJzO7eB3 TmL6M9QMyrAPBDsCnduJ8yW5mMUNod139YbSDxZPYwTLhK/GiHP/7OvLV5hg0s4s QKnNaMeEowX7dyEO4ehnbfzysxXPKLRVhWhN6MCUc71NMxqr7QkuCXAjJS6/G21+ Im3+Xb3Scq+UZghR+jiEZF0= -----END PRIVATE KEY----- keystonemiddleware-4.21.0/examples/pki/private/ssl_key.pem0000666000175100017510000000325013225160624023753 0ustar zuulzuul00000000000000-----BEGIN PRIVATE KEY----- MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQDL06AaJROwHPgJ 9tcySSBepzJ81jYars2sMvLjyuvdiIBbhWvbS/a9Tw3WgL8H6OALkHiOU/f0A6Rp v8dGDIDsxZQVjT/4SLaQUOeDM+9bfkKHpSd9G3CsdSSZgOH08n+MyZ7slPHfUHLY Wso0SJD0vAi1gmGDlSM/mmhhHTpCDGo6Wbwqare6JNeTCGJTJYwrxtoMCh/W1Zrs lPC5lFvlHD7KBBf6IU2A8Xh/dUa3p5pmQeHPW8Em90DzIB1qH0DRXl3KANc24xYR R45pPCVkk6vFsy6P0JwwpnkszB+LcK6CEsJhLsOYvQFsiQfSZ8m7YGhgrMLxtop4 YEPirGGrAgMBAAECggEATwvbY0hNwlb5uqOIAXBqpUqiQdexU9fG26lGmSDxKBDv 9o5frcRgBDrMWwvDCgY+HT4CAvB9kJx4/qnpVjkzJp/ZNiJ5VIiehIlbv348rXbh xkk+bz5dDATCFOXuu1fwL2FhyM5anwhMAav0DyK1VLQ3jGzr9GO6L8hqAn+bQFFu 6ngiODwfhBMl5aRoL9UOBEhccK07znrH0JGRz+3+5Cdz59Xw91Bv210LhNNDL58+ 0JD0N+YztVOQd2bgwo0bQbOEijzmYq+0mjoqAnJh1/++y7PlIPs0AnPgqSnFPx9+ 6FsQEVRgk5Uq3kvPLaP4nT2y6MDZSp+ujYldvJhyQQKBgQDuX2pZIJMZ4aFnkG+K TmJ5wsLa/u9an0TmvAL9RLtBpVpQNKD8cQ+y8PUZavXDbAIt5NWqZVnTbCR79Dnd mZKblwcHhtsyA5f89el5KcxY2BREWdHdTnJpNd7XRlUECmzvX1zGj77lA982PhII yflRBRV3vqLkgC8vfoYgRyRElwKBgQDa5jnLdx/RahfYMOgn1HE5o4hMzLR4Y0Dd +gELshcUbPqouoP5zOb8WOagVJIgZVOSN+/VqbilVYrqRiNTn2rnoxs+HHRdaJNN 3eXllD4J2HfC2BIj1xSpIdyh2XewAJqw9IToHNB29QUhxOtgwseHciPG6JaKH2ik kqGKH/EKDQKBgFFAftygiOPCkCTgC9UmANUmOQsy6N2H+pF3tsEj43xt44oBVnqW A1boYXNnjRwuvdNs9BPf9i1l6E3EItFRXrLgWQoMwryakv0ryYh+YeRKyyW9RBbe fYs1TJ8unx4Ae79gTxxztQsVNcmkgLs0NWKTjAzEE3w14V+cDhYEie1DAoGBAJdI V5cLrBzBstsB6eBlDR9lqrRRIUS2a8U9m+1mVlcSfiWQSdehSd4K3tDdwePLw3ch W4qR8n+pYAlLEe0gFvUhn5lMdwt7U5qUCeehjUKmrRYm2FqWsbu2IFJnBjXIJSC4 zQXRrC0aZ0KQYpAL7XPpaVp1slyhGmPqxuO78Y0dAoGBAMHo3EIMwu9rfuGwFodr GFsOZhfJqgo5GDNxxf89Q9WWpMDTCdX+wdBTrN/wsMbBuwIDHrUuRnk6D5CWRjSk /ikCgHN3kOtrbL8zzqRomGAIIWKYGFEIGe1GHVGo5r//HXHdPxFXygvruQ/xbOA4 RGvmDiji8vVDq7Shho8I6KuT -----END PRIVATE KEY----- keystonemiddleware-4.21.0/examples/pki/private/signing_key.pem0000666000175100017510000000325413225160624024614 0ustar zuulzuul00000000000000-----BEGIN PRIVATE KEY----- MIIEvwIBADANBgkqhkiG9w0BAQEFAASCBKkwggSlAgEAAoIBAQDM+VrILLl962VH S8EKWVzdkaOy0OoxGZ63gajM7VTm8AbgtVnYibIOnVZQuz1XbftIGNXPFhYNUypr LnMXrEEsnxgD4PvU/4bETG+stdricX6d1oKqsNFNR7F7zImiR/OzGhp7dONwccxf kfX4QHA5Ogso+XMfSdC72SRDszeCeGUcjuo/w2WSLW95SuVvcZLqE/pk3Q2TkCZ1 8hvNfLoln43QpC469a7srUXATqOJ2mPNvL6E/wOyPefmAoCoG44lFoR3k2jZjBEI hstJxmH7XgvqErBzpcWd29dms8xz5PNwYdns9CIfb3GaHvQ6r5RTl37/avDrGHOW KOoD01xLAgMBAAECggEAaIi22qWsh+JYCW9B6NRAPyN6V8Sh2x6UykOO4cwb45b/ +vOh+YPn0fo9vfhvxTnq0A8SY4WBA5SpanYK7kTEDEyqw7em1y7l/RB6V5t7IMb+ 6uIuS3zXkVEB3AApJSEK0Ql7/gBTydHPh+H5jnzWfujyLhhhtNBBarvH+drZcWio lWx8RERN4cH+3DZD/xxjH2Ff+X1XMvb8Xcup7MlWi2FtREg7LttLNWNK25iWjciP QwfWQIrURRJrD2IrOr9V2nuIEvRqRRBoO+pxJT2sC48NJ3hiKV2GtSQe2nRpQJ47 f9MEsF5KVQOOn+aQ60EKOI0MpNPmpiCZ5hFvBrNuOQKBgQD6vueEdI9eJgz5YN+t XWdpNippv35RTD8R4bQcE6GqIUXOmtQFS2wPJLn7nisZUsGMNEs36Yl0T9iow63r 5GNAfgzpqN1XZqaSMwAdxKmlBNYpAkVXHhv+1jN+9diDYmoj9T+3Q6Zvk5e/Liyp 6i+TsDppwmmr2utWajhyJ7owFwKBgQDRROncTztGDYLfRcrIoYsPo79KQ8tqwd2a 07Usch2kplTqojCUmmhMMFgV2eZPPiCjnEy2bAYh9I/oj7xG6EwApXTshZdCpivC rbUV64MakRTUP8IvM6PdI+apkJRsRUi/bSyIbcRlvEoCMNZhfj/5VY6w/jlwrPJj oBOCXBlB7QKBgQDGEbEeX1i03UfYYh6uep7qbEAaooqsu5cCkBDPMO6+TmQvLPyY Zhio6bEEQs/2w/lhwBk+xHqw5zXVMiWbtiB03F1k4eBeXxbrW+AWo7gCQ4zMfh+6 Dm284wVwn9D1D/OaDevT31uEvcjb2ySq3/PPLSEnU8xXVaoa6/NEsX8Q5wKBgQCm 2smULWBXZKJ6n00mVxdnqun0rsVcI6Mrta14+KwGAdEnG5achdivFsTE924YtLKV gSPxN4RUQokTprc52jHvOf1WMNYAADpYCOSfy55G6nKvIP8VX5lB00Qw4uRUx5FP gB7H0K2NaGmiAYqNRXqAtOUG3kyyOFMzeAjWIdTJqQKBgQCHzY1c7sS1vv7mPEkr 6CpwoaEbZeFnWoHBA8Rd82psqfYsVJIRwk5Id8zgDSEmoEi8hQ9UrYbrFpLK77xq EYSxLQHTNlM0G3lyEsv/gJhwYYhdTYiW3Cx3F6Y++jyn9O/+hFMyQvuesAL7DUYE ptEfvzFprpQUpByXkIpuJub6fg== -----END PRIVATE KEY----- keystonemiddleware-4.21.0/examples/pki/certs/0000775000175100017510000000000013225161130021234 5ustar zuulzuul00000000000000keystonemiddleware-4.21.0/examples/pki/certs/ssl_cert.pem0000666000175100017510000000245613225160624023575 0ustar zuulzuul00000000000000-----BEGIN CERTIFICATE----- MIIDpjCCAo4CARAwDQYJKoZIhvcNAQEFBQAwgZ4xCjAIBgNVBAUTATUxCzAJBgNV BAYTAlVTMQswCQYDVQQIEwJDQTESMBAGA1UEBxMJU3Vubnl2YWxlMRIwEAYDVQQK EwlPcGVuU3RhY2sxETAPBgNVBAsTCEtleXN0b25lMSUwIwYJKoZIhvcNAQkBFhZr ZXlzdG9uZUBvcGVuc3RhY2sub3JnMRQwEgYDVQQDEwtTZWxmIFNpZ25lZDAgFw0x MzA5MTMxNjI1NDNaGA8yMDcyMDMwNzE2MjU0M1owgZAxCzAJBgNVBAYTAlVTMQsw CQYDVQQIEwJDQTESMBAGA1UEBxMJU3Vubnl2YWxlMRIwEAYDVQQKEwlPcGVuU3Rh Y2sxETAPBgNVBAsTCEtleXN0b25lMSUwIwYJKoZIhvcNAQkBFhZrZXlzdG9uZUBv cGVuc3RhY2sub3JnMRIwEAYDVQQDEwlsb2NhbGhvc3QwggEiMA0GCSqGSIb3DQEB AQUAA4IBDwAwggEKAoIBAQDL06AaJROwHPgJ9tcySSBepzJ81jYars2sMvLjyuvd iIBbhWvbS/a9Tw3WgL8H6OALkHiOU/f0A6Rpv8dGDIDsxZQVjT/4SLaQUOeDM+9b fkKHpSd9G3CsdSSZgOH08n+MyZ7slPHfUHLYWso0SJD0vAi1gmGDlSM/mmhhHTpC DGo6Wbwqare6JNeTCGJTJYwrxtoMCh/W1ZrslPC5lFvlHD7KBBf6IU2A8Xh/dUa3 p5pmQeHPW8Em90DzIB1qH0DRXl3KANc24xYRR45pPCVkk6vFsy6P0JwwpnkszB+L cK6CEsJhLsOYvQFsiQfSZ8m7YGhgrMLxtop4YEPirGGrAgMBAAEwDQYJKoZIhvcN AQEFBQADggEBAAjU7YomUx/U56p1KWHvr1B7oczHF8fPHYbuk5c/N81WOJeSRy+P 5ZGZ2UPjvqqXByv+78YWMKGY1BZ/2doeWuydr0sdSxEwmIUBYxFpujuYY+0AjS/n mMr1ZijK7TJssteKM7/MClzghUhPweDZrAg3ff1hbhK5QSy+9UPxUqLH44tfYSVC /BzM6se0p5ToM0bwdsa8TofaBRE1L1IW/Hg4VIGOoKs0R0uLm7+Oot2me2cEuZ6h Wls6MED8ND1Nz8EAKwndkeDu2iMM+qx/YFp6K8BQ5E5nXd2rbUZUlQMp1WbUlZ87 KvC98aT0UYIq6uo1Lx/dQvJs7faAkYd4lmE= -----END CERTIFICATE----- keystonemiddleware-4.21.0/examples/pki/certs/cacert.pem0000666000175100017510000000255713225160624023222 0ustar zuulzuul00000000000000-----BEGIN CERTIFICATE----- MIID1jCCAr6gAwIBAgIJAJOtRP2+wrM/MA0GCSqGSIb3DQEBBQUAMIGeMQowCAYD VQQFEwE1MQswCQYDVQQGEwJVUzELMAkGA1UECBMCQ0ExEjAQBgNVBAcTCVN1bm55 dmFsZTESMBAGA1UEChMJT3BlblN0YWNrMREwDwYDVQQLEwhLZXlzdG9uZTElMCMG CSqGSIb3DQEJARYWa2V5c3RvbmVAb3BlbnN0YWNrLm9yZzEUMBIGA1UEAxMLU2Vs ZiBTaWduZWQwIBcNMTMwOTEzMTYyNTQyWhgPMjA3MjAzMDcxNjI1NDJaMIGeMQow CAYDVQQFEwE1MQswCQYDVQQGEwJVUzELMAkGA1UECBMCQ0ExEjAQBgNVBAcTCVN1 bm55dmFsZTESMBAGA1UEChMJT3BlblN0YWNrMREwDwYDVQQLEwhLZXlzdG9uZTEl MCMGCSqGSIb3DQEJARYWa2V5c3RvbmVAb3BlbnN0YWNrLm9yZzEUMBIGA1UEAxML U2VsZiBTaWduZWQwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCl8906 EaRpibQFcCBWfxzLi5x/XpZ9iL6UX92NrSJxcDbaGws7s+GtjgDy8UOEonesRWTe qQEZtHpC3/UHHOnsA8F6ha/pq9LioqT7RehCnZCLBJwh5Ct+lclpWs15SkjJD2LT Dkjox0eA9nOBx+XDlWyU/GAyqx5Wsvg/Kxr0iod9/4IcJdnSdUjq4v0Cxg/zNk08 XPJX+F0bUDhgdUf7JrAmmS5LA8wphRnbIgtVsf6VN9HrbqtHAJDxh8gEfuwdhEW1 df1fBtZ+6WMIF3IRSbIsZELFB6sqcyRj7HhMoWMkdEyPb2f8mq61MzTgE6lJGIyT RvEoFie7qtGADIofAgMBAAGjEzARMA8GA1UdEwEB/wQFMAMBAf8wDQYJKoZIhvcN AQEFBQADggEBAJRMdEwAdN+crqI9dBLYlbBbnQ8xr9mk+REMdz9+SKhDCNdVisWU iLEZvK/aozrsRsDi81JjS4Tz0wXo8zsPPoDnXgDYEicNPTKifbPKgHdDIGFOwBKn y2cF6fHEn8n3KIBrDCNY6rHcYGZ7lbq/8eF0GoYQboPiuYesvVpynPmIK5/Mmire EuuZALAe1IFqqFt+l6tiJU2JWUFjLkFARMOD14qFZm+SInl64toi08j6gdou+NMW 7GEMbVHwNTafM/TgFN5j0yP9SAnYubckLSyH6hwR+rM8dztP5769joxQfnc9O/Bn TBD9KFpeQv6VJWLAxiIKcQCRTTDJLZZ0MQI= -----END CERTIFICATE----- keystonemiddleware-4.21.0/examples/pki/certs/middleware.pem0000666000175100017510000000572613225160624024077 0ustar zuulzuul00000000000000-----BEGIN CERTIFICATE----- MIIDpjCCAo4CARAwDQYJKoZIhvcNAQEFBQAwgZ4xCjAIBgNVBAUTATUxCzAJBgNV BAYTAlVTMQswCQYDVQQIEwJDQTESMBAGA1UEBxMJU3Vubnl2YWxlMRIwEAYDVQQK EwlPcGVuU3RhY2sxETAPBgNVBAsTCEtleXN0b25lMSUwIwYJKoZIhvcNAQkBFhZr ZXlzdG9uZUBvcGVuc3RhY2sub3JnMRQwEgYDVQQDEwtTZWxmIFNpZ25lZDAgFw0x MzA5MTMxNjI1NDNaGA8yMDcyMDMwNzE2MjU0M1owgZAxCzAJBgNVBAYTAlVTMQsw CQYDVQQIEwJDQTESMBAGA1UEBxMJU3Vubnl2YWxlMRIwEAYDVQQKEwlPcGVuU3Rh Y2sxETAPBgNVBAsTCEtleXN0b25lMSUwIwYJKoZIhvcNAQkBFhZrZXlzdG9uZUBv cGVuc3RhY2sub3JnMRIwEAYDVQQDEwlsb2NhbGhvc3QwggEiMA0GCSqGSIb3DQEB AQUAA4IBDwAwggEKAoIBAQDL06AaJROwHPgJ9tcySSBepzJ81jYars2sMvLjyuvd iIBbhWvbS/a9Tw3WgL8H6OALkHiOU/f0A6Rpv8dGDIDsxZQVjT/4SLaQUOeDM+9b fkKHpSd9G3CsdSSZgOH08n+MyZ7slPHfUHLYWso0SJD0vAi1gmGDlSM/mmhhHTpC DGo6Wbwqare6JNeTCGJTJYwrxtoMCh/W1ZrslPC5lFvlHD7KBBf6IU2A8Xh/dUa3 p5pmQeHPW8Em90DzIB1qH0DRXl3KANc24xYRR45pPCVkk6vFsy6P0JwwpnkszB+L cK6CEsJhLsOYvQFsiQfSZ8m7YGhgrMLxtop4YEPirGGrAgMBAAEwDQYJKoZIhvcN AQEFBQADggEBAAjU7YomUx/U56p1KWHvr1B7oczHF8fPHYbuk5c/N81WOJeSRy+P 5ZGZ2UPjvqqXByv+78YWMKGY1BZ/2doeWuydr0sdSxEwmIUBYxFpujuYY+0AjS/n mMr1ZijK7TJssteKM7/MClzghUhPweDZrAg3ff1hbhK5QSy+9UPxUqLH44tfYSVC /BzM6se0p5ToM0bwdsa8TofaBRE1L1IW/Hg4VIGOoKs0R0uLm7+Oot2me2cEuZ6h Wls6MED8ND1Nz8EAKwndkeDu2iMM+qx/YFp6K8BQ5E5nXd2rbUZUlQMp1WbUlZ87 KvC98aT0UYIq6uo1Lx/dQvJs7faAkYd4lmE= -----END CERTIFICATE----- -----BEGIN PRIVATE KEY----- MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQDL06AaJROwHPgJ 9tcySSBepzJ81jYars2sMvLjyuvdiIBbhWvbS/a9Tw3WgL8H6OALkHiOU/f0A6Rp v8dGDIDsxZQVjT/4SLaQUOeDM+9bfkKHpSd9G3CsdSSZgOH08n+MyZ7slPHfUHLY Wso0SJD0vAi1gmGDlSM/mmhhHTpCDGo6Wbwqare6JNeTCGJTJYwrxtoMCh/W1Zrs lPC5lFvlHD7KBBf6IU2A8Xh/dUa3p5pmQeHPW8Em90DzIB1qH0DRXl3KANc24xYR R45pPCVkk6vFsy6P0JwwpnkszB+LcK6CEsJhLsOYvQFsiQfSZ8m7YGhgrMLxtop4 YEPirGGrAgMBAAECggEATwvbY0hNwlb5uqOIAXBqpUqiQdexU9fG26lGmSDxKBDv 9o5frcRgBDrMWwvDCgY+HT4CAvB9kJx4/qnpVjkzJp/ZNiJ5VIiehIlbv348rXbh xkk+bz5dDATCFOXuu1fwL2FhyM5anwhMAav0DyK1VLQ3jGzr9GO6L8hqAn+bQFFu 6ngiODwfhBMl5aRoL9UOBEhccK07znrH0JGRz+3+5Cdz59Xw91Bv210LhNNDL58+ 0JD0N+YztVOQd2bgwo0bQbOEijzmYq+0mjoqAnJh1/++y7PlIPs0AnPgqSnFPx9+ 6FsQEVRgk5Uq3kvPLaP4nT2y6MDZSp+ujYldvJhyQQKBgQDuX2pZIJMZ4aFnkG+K TmJ5wsLa/u9an0TmvAL9RLtBpVpQNKD8cQ+y8PUZavXDbAIt5NWqZVnTbCR79Dnd mZKblwcHhtsyA5f89el5KcxY2BREWdHdTnJpNd7XRlUECmzvX1zGj77lA982PhII yflRBRV3vqLkgC8vfoYgRyRElwKBgQDa5jnLdx/RahfYMOgn1HE5o4hMzLR4Y0Dd +gELshcUbPqouoP5zOb8WOagVJIgZVOSN+/VqbilVYrqRiNTn2rnoxs+HHRdaJNN 3eXllD4J2HfC2BIj1xSpIdyh2XewAJqw9IToHNB29QUhxOtgwseHciPG6JaKH2ik kqGKH/EKDQKBgFFAftygiOPCkCTgC9UmANUmOQsy6N2H+pF3tsEj43xt44oBVnqW A1boYXNnjRwuvdNs9BPf9i1l6E3EItFRXrLgWQoMwryakv0ryYh+YeRKyyW9RBbe fYs1TJ8unx4Ae79gTxxztQsVNcmkgLs0NWKTjAzEE3w14V+cDhYEie1DAoGBAJdI V5cLrBzBstsB6eBlDR9lqrRRIUS2a8U9m+1mVlcSfiWQSdehSd4K3tDdwePLw3ch W4qR8n+pYAlLEe0gFvUhn5lMdwt7U5qUCeehjUKmrRYm2FqWsbu2IFJnBjXIJSC4 zQXRrC0aZ0KQYpAL7XPpaVp1slyhGmPqxuO78Y0dAoGBAMHo3EIMwu9rfuGwFodr GFsOZhfJqgo5GDNxxf89Q9WWpMDTCdX+wdBTrN/wsMbBuwIDHrUuRnk6D5CWRjSk /ikCgHN3kOtrbL8zzqRomGAIIWKYGFEIGe1GHVGo5r//HXHdPxFXygvruQ/xbOA4 RGvmDiji8vVDq7Shho8I6KuT -----END PRIVATE KEY----- keystonemiddleware-4.21.0/examples/pki/certs/signing_cert.pem0000666000175100017510000000245613225160624024432 0ustar zuulzuul00000000000000-----BEGIN CERTIFICATE----- MIIDpTCCAo0CAREwDQYJKoZIhvcNAQEFBQAwgZ4xCjAIBgNVBAUTATUxCzAJBgNV BAYTAlVTMQswCQYDVQQIEwJDQTESMBAGA1UEBxMJU3Vubnl2YWxlMRIwEAYDVQQK EwlPcGVuU3RhY2sxETAPBgNVBAsTCEtleXN0b25lMSUwIwYJKoZIhvcNAQkBFhZr ZXlzdG9uZUBvcGVuc3RhY2sub3JnMRQwEgYDVQQDEwtTZWxmIFNpZ25lZDAgFw0x MzA5MTMxNjI1NDNaGA8yMDcyMDMwNzE2MjU0M1owgY8xCzAJBgNVBAYTAlVTMQsw CQYDVQQIEwJDQTESMBAGA1UEBxMJU3Vubnl2YWxlMRIwEAYDVQQKEwlPcGVuU3Rh Y2sxETAPBgNVBAsTCEtleXN0b25lMSUwIwYJKoZIhvcNAQkBFhZrZXlzdG9uZUBv cGVuc3RhY2sub3JnMREwDwYDVQQDEwhLZXlzdG9uZTCCASIwDQYJKoZIhvcNAQEB BQADggEPADCCAQoCggEBAMz5WsgsuX3rZUdLwQpZXN2Ro7LQ6jEZnreBqMztVObw BuC1WdiJsg6dVlC7PVdt+0gY1c8WFg1TKmsucxesQSyfGAPg+9T/hsRMb6y12uJx fp3Wgqqw0U1HsXvMiaJH87MaGnt043BxzF+R9fhAcDk6Cyj5cx9J0LvZJEOzN4J4 ZRyO6j/DZZItb3lK5W9xkuoT+mTdDZOQJnXyG818uiWfjdCkLjr1ruytRcBOo4na Y828voT/A7I95+YCgKgbjiUWhHeTaNmMEQiGy0nGYfteC+oSsHOlxZ3b12azzHPk 83Bh2ez0Ih9vcZoe9DqvlFOXfv9q8OsYc5Yo6gPTXEsCAwEAATANBgkqhkiG9w0B AQUFAAOCAQEAmaYE98kOQWu6DV84ZcZP/OdT8eeu3vdB247nRj+6+GYItN/Gzqt4 HVvz7c+FVTolCcAQQ+z3XGswI9fIJ78Hb0p9CgnLprc3L7Xtk60Im59Xlf3tcurn r/ZnSDcjRBXKiEDrSM0VrhAnc0GoSeb6aDWopec+1hWOWfBVAg9R8yJgU9sUgO3O 0gimGyrw8eubmNhckSQLJTunUTsrkcBjuSg63wAD9OqCiX6c2eoQr+0YBp2eV2/n aOiJXWNLbeueMKSYiJNyyvM/dlON7/56cdwDTzKzgD34TImouM5VKipUwCX1ovLu ITLzALzpqFFzc8ugV9pMgUKtDbZoPp9EEA== -----END CERTIFICATE----- keystonemiddleware-4.21.0/examples/pki/gen_pki.sh0000777000175100017510000001341613225160624022105 0ustar zuulzuul00000000000000#!/bin/bash # Copyright 2012 OpenStack Foundation # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. # These functions generate the certificates and signed tokens for the tests. DIR=`dirname "$0"` CURRENT_DIR=`cd "$DIR" && pwd` CERTS_DIR=$CURRENT_DIR/certs PRIVATE_DIR=$CURRENT_DIR/private CMS_DIR=$CURRENT_DIR/cms function rm_old { rm -rf $CERTS_DIR/*.pem rm -rf $PRIVATE_DIR/*.pem } function cleanup { rm -rf *.conf > /dev/null 2>&1 rm -rf index* > /dev/null 2>&1 rm -rf *.crt > /dev/null 2>&1 rm -rf newcerts > /dev/null 2>&1 rm -rf *.pem > /dev/null 2>&1 rm -rf serial* > /dev/null 2>&1 } function generate_ca_conf { echo ' [ req ] default_bits = 2048 default_keyfile = cakey.pem default_md = default prompt = no distinguished_name = ca_distinguished_name x509_extensions = ca_extensions [ ca_distinguished_name ] serialNumber = 5 countryName = US stateOrProvinceName = CA localityName = Sunnyvale organizationName = OpenStack organizationalUnitName = Keystone emailAddress = keystone@openstack.org commonName = Self Signed [ ca_extensions ] basicConstraints = critical,CA:true ' > ca.conf } function generate_ssl_req_conf { echo ' [ req ] default_bits = 2048 default_keyfile = keystonekey.pem default_md = default prompt = no distinguished_name = distinguished_name [ distinguished_name ] countryName = US stateOrProvinceName = CA localityName = Sunnyvale organizationName = OpenStack organizationalUnitName = Keystone commonName = localhost emailAddress = keystone@openstack.org ' > ssl_req.conf } function generate_cms_signing_req_conf { echo ' [ req ] default_bits = 2048 default_keyfile = keystonekey.pem default_md = default prompt = no distinguished_name = distinguished_name [ distinguished_name ] countryName = US stateOrProvinceName = CA localityName = Sunnyvale organizationName = OpenStack organizationalUnitName = Keystone commonName = Keystone emailAddress = keystone@openstack.org ' > cms_signing_req.conf } function generate_signing_conf { echo ' [ ca ] default_ca = signing_ca [ signing_ca ] dir = . database = $dir/index.txt new_certs_dir = $dir/newcerts certificate = $dir/certs/cacert.pem serial = $dir/serial private_key = $dir/private/cakey.pem default_days = 21360 default_crl_days = 30 default_md = default policy = policy_any [ policy_any ] countryName = supplied stateOrProvinceName = supplied localityName = optional organizationName = supplied organizationalUnitName = supplied emailAddress = supplied commonName = supplied ' > signing.conf } function setup { touch index.txt echo '10' > serial generate_ca_conf mkdir newcerts } function check_error { if [ $1 != 0 ] ; then echo "Failed! rc=${1}" echo 'Bailing ...' cleanup exit $1 else echo 'Done' fi } function generate_ca { echo 'Generating New CA Certificate ...' openssl req -x509 -newkey rsa:2048 -days 21360 -out $CERTS_DIR/cacert.pem -keyout $PRIVATE_DIR/cakey.pem -outform PEM -config ca.conf -nodes check_error $? } function ssl_cert_req { echo 'Generating SSL Certificate Request ...' generate_ssl_req_conf openssl req -newkey rsa:2048 -keyout $PRIVATE_DIR/ssl_key.pem -keyform PEM -out ssl_req.pem -outform PEM -config ssl_req.conf -nodes check_error $? #openssl req -in req.pem -text -noout } function cms_signing_cert_req { echo 'Generating CMS Signing Certificate Request ...' generate_cms_signing_req_conf openssl req -newkey rsa:2048 -keyout $PRIVATE_DIR/signing_key.pem -keyform PEM -out cms_signing_req.pem -outform PEM -config cms_signing_req.conf -nodes check_error $? #openssl req -in req.pem -text -noout } function issue_certs { generate_signing_conf echo 'Issuing SSL Certificate ...' openssl ca -in ssl_req.pem -config signing.conf -batch check_error $? openssl x509 -in $CURRENT_DIR/newcerts/10.pem -out $CERTS_DIR/ssl_cert.pem check_error $? echo 'Issuing CMS Signing Certificate ...' openssl ca -in cms_signing_req.pem -config signing.conf -batch check_error $? openssl x509 -in $CURRENT_DIR/newcerts/11.pem -out $CERTS_DIR/signing_cert.pem check_error $? } function create_middleware_cert { cp $CERTS_DIR/ssl_cert.pem $CERTS_DIR/middleware.pem cat $PRIVATE_DIR/ssl_key.pem >> $CERTS_DIR/middleware.pem } function check_openssl { echo 'Checking openssl availability ...' which openssl check_error $? } JSON_FILES="${CMS_DIR}/auth_token_revoked.json ${CMS_DIR}/auth_token_unscoped.json ${CMS_DIR}/auth_token_scoped.json ${CMS_DIR}/auth_token_scoped_expired.json ${CMS_DIR}/revocation_list.json ${CMS_DIR}/auth_v3_token_scoped.json ${CMS_DIR}/auth_v3_token_revoked.json" function gen_sample_cms { for json_file in $JSON_FILES do openssl cms -sign -in $json_file -nosmimecap -signer $CERTS_DIR/signing_cert.pem -inkey $PRIVATE_DIR/signing_key.pem -outform PEM -nodetach -nocerts -noattr -out ${json_file/.json/.pem} done } keystonemiddleware-4.21.0/examples/pki/run_all.sh0000777000175100017510000000146413225160624022125 0ustar zuulzuul00000000000000#!/bin/bash -x # Copyright 2012 OpenStack Foundation # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. # This script generates the crypto necessary for the SSL tests. . gen_pki.sh check_openssl rm_old cleanup setup generate_ca ssl_cert_req cms_signing_cert_req issue_certs create_middleware_cert gen_sample_cms cleanup keystonemiddleware-4.21.0/examples/pki/gen_cmsz.py0000666000175100017510000001011513225160624022302 0ustar zuulzuul00000000000000#!/usr/bin/python # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. import json import os from keystoneclient.common import cms from keystoneclient import utils CURRENT_DIR = os.path.abspath(os.path.dirname(__file__)) def make_filename(*args): return os.path.join(CURRENT_DIR, *args) def generate_revocation_list(): REVOKED_TOKENS = ['auth_token_revoked', 'auth_v3_token_revoked'] revoked_list = [] for token in REVOKED_TOKENS: with open(make_filename('cms', '%s.pkiz' % name), 'r') as f: token_data = f.read() id = utils.hash_signed_token(token_data.encode('utf-8')) revoked_list.append({ 'id': id, "expires": "2112-08-14T17:58:48Z" }) with open(make_filename('cms', '%s.pem' % name), 'r') as f: pem_data = f.read() token_data = cms.cms_to_token(pem_data).encode('utf-8') id = utils.hash_signed_token(token_data) revoked_list.append({ 'id': id, "expires": "2112-08-14T17:58:48Z" }) revoked_json = json.dumps({"revoked": revoked_list}) with open(make_filename('cms', 'revocation_list.json'), 'w') as f: f.write(revoked_json) encoded = cms.pkiz_sign(revoked_json, SIGNING_CERT_FILE_NAME, SIGNING_KEY_FILE_NAME) with open(make_filename('cms', 'revocation_list.pkiz'), 'w') as f: f.write(encoded) encoded = cms.cms_sign_data(revoked_json, SIGNING_CERT_FILE_NAME, SIGNING_KEY_FILE_NAME) with open(make_filename('cms', 'revocation_list.pem'), 'w') as f: f.write(encoded) CA_CERT_FILE_NAME = make_filename('certs', 'cacert.pem') SIGNING_CERT_FILE_NAME = make_filename('certs', 'signing_cert.pem') SIGNING_KEY_FILE_NAME = make_filename('private', 'signing_key.pem') EXAMPLE_TOKENS = ['auth_token_revoked', 'auth_token_unscoped', 'auth_token_scoped', 'auth_token_scoped_expired', 'auth_v3_token_scoped', 'auth_v3_token_revoked'] # Helper script to generate the sample data for testing # the signed tokens using the existing JSON data for the # MII-prefixed tokens. Uses the keys and certificates # generated in gen_pki.sh. def generate_der_form(name): derfile = make_filename('cms', '%s.der' % name) with open(derfile, 'w') as f: derform = cms.cms_sign_data(text, SIGNING_CERT_FILE_NAME, SIGNING_KEY_FILE_NAME, cms.PKIZ_CMS_FORM) f.write(derform) for name in EXAMPLE_TOKENS: json_file = make_filename('cms', name + '.json') pkiz_file = make_filename('cms', name + '.pkiz') with open(json_file, 'r') as f: string_data = f.read() # validate the JSON try: token_data = json.loads(string_data) except ValueError as v: raise SystemExit('%s while processing token data from %s: %s' % (v, json_file, string_data)) text = json.dumps(token_data).encode('utf-8') # Uncomment to record the token uncompressed, # useful for debugging # generate_der_form(name) encoded = cms.pkiz_sign(text, SIGNING_CERT_FILE_NAME, SIGNING_KEY_FILE_NAME) # verify before writing cms.pkiz_verify(encoded, SIGNING_CERT_FILE_NAME, CA_CERT_FILE_NAME) with open(pkiz_file, 'w') as f: f.write(encoded) generate_revocation_list() keystonemiddleware-4.21.0/examples/pki/cms/0000775000175100017510000000000013225161130020676 5ustar zuulzuul00000000000000keystonemiddleware-4.21.0/examples/pki/cms/auth_token_scoped_expired.pkiz0000666000175100017510000000347513225160624027035 0ustar zuulzuul00000000000000PKIZ_eJylVtlyozgUfddXzHuqK2xOzCObMdiSzW7pzUCMwchLbNavH4GT6kmnM5OZcZWrQEhH555z75V-_GA_1TAt9IcGveHlB4CWNW8cbC9OxNrXCVKcRDuxsWuhaeqTpCmO0Wq-Mlez4FXPoGYO44lkat7F9KxYBLpjzJUtG4ynRpZFzy-dvccCKhMR5qtcfbaO7PlIzlgIdbxx97EpH63ilEXiNY_p7AaIZz1Zmi3EQsvHUZAvNSUn0eSQmPI5Prr9-2QcubdtNAmDQ8OAlXw7d7lEP9Vg2Rsd6qRmWSgV9E8S6hNhKeJ22WMOF4RCgeRYgDzsnR5FgYR93BCK6Eovc1xgAUA_3Vt5k1lHuyRCWcf5yKgjUXqOhck6pndWbHeObOwKR-0HFmCg8X9YgIHGTxYqj2l7xnzo-drI5JTO3WaVT2voW-K4gSa1qyITUY_rtDBqgAo3RxT3hNoF7oMe6ZAn_n6PCpViAUuryM5RgVskGPku5K4MlHvZqOUgrnUkNYjn4Y05MXwoY-o2sVBW6RztYrOstncr482GLZzfbXtz7RibswoLQQ7-rW2_6DUBsDh0g2D_1QnwFfJH4K_FBR_VPXQr3xrU_SwYLW84SssRkIYVmav1wAgkvHxlD69Jx5Bnt3TnNRmrB0aTf1s4qVNqfJni4JtiDcnFjcnFvP-r9eCfvB92Tmh43EZydff-TeiDXA32AxbnQKlM6GQfz76Tgc6gUQW9qYBMSwCkYGQoKpAPOdiH5co0BGiSghTZBFNLQIUh4nuiNWlkM73Qt4rpt_H-Llzwt7lOUR1vVD41PzeajdCeY3rrwWgHz8tLjbWvQQfWlUZ6QjhJRLd-z8Kv0h18w8Ke6cOjThZgLjW_pvzggvfd7vM7cPAZ_btNJWigrtQgLSw2YMsbb1jsThLzTYPILVm853R--FLAQQswCPi2uGbCjdnGaqF8matnloHjJKuwGugrN6hj9rcD6DtPSE-eYO9uwZ02243OqnSgzDoP223PwijJ-O52aRQM9v4ssPf5M7kCwyC8Z9qBbFCR0LJJzbemYk742GyGb2dy14MbwFkYu23ktNaRu9fC28eG9bmCRPs6Nllt5LY8xJ5u2NGW35klVL6yTT70S8A8ZQuC95Y2PHdWyf1COeyZrbuxqfrvFTqAwRwMKB8ayDvg8VMn7tj5WcL83bER9K7BV7uwOEdLxzBK-Ux0Vi8bXobYUjt2zCsJ1gA7_5ts6zQZkVqtUCw1Q6GqBL7iB63WK_b9HftKGfrQuTaag_XQcSyjsXXHNzwAVcVU-MBQW2gHYljFx1JgKVxC12oMZZy8MJpynZhhFYguuztcW8NX1nfgqw8041a-bBDHaoHZGTRW89fbykGd7ckr2ZR9arIWFqj1AJTcgapYtI8Auk5jZONOutHcfBK11JqhM2GAhEVkfLjeKEjNDpf9ITflhlNZ-DOgKB67B2niTXTXpH1IYeWIT09VZWNhm5pu_7LFotenk40hKN5tMWmeLuGz5F_p9Lw8CZct2Exj5Vhc1ig3oPTgy6G0cGOnnYclRPPLjp6a5elZauAxWJk7U3pep74japd2cbW6ykoJIP5aWuX7hwdztjNlszcnrfuwmnC8LJSzZ11Osktpha621jm0Jdw6epycXy3yWK5odqWiC66rXBCk-CJeBffxOaJazV2mNJhOt4l2eFXI3o0Wt2oBV3SWRiePSlr56B_UY9dRTz2YEvCb9bK-zFdQrRHO5cuZqx5fIiHT1CZ3-SQq7Cpz7MNRvjxORbSpQnmy7B7YRZI_16hsr-B6Pb2IF9vVHjxzkSbJLjhEi9h4DOIVBeNd1ED6z3vpnxbOkgI=keystonemiddleware-4.21.0/examples/pki/cms/auth_token_scoped.json0000666000175100017510000000557713225160624025316 0ustar zuulzuul00000000000000{ "access": { "token": { "expires": "2038-01-18T21:14:07Z", "id": "placeholder", "tenant": { "id": "tenant_id1", "enabled": true, "description": null, "name": "tenant_name1" }, "audit_ids": [ "SLIXlXQUQZWUi9VJrqdXqA" ] }, "serviceCatalog": [ { "endpoints_links": [], "endpoints": [ { "adminURL": "http://127.0.0.1:8776/v1/64b6f3fbcc53435e8a60fcf89bb6617a", "region": "regionOne", "internalURL": "http://127.0.0.1:8776/v1/64b6f3fbcc53435e8a60fcf89bb6617a", "publicURL": "http://127.0.0.1:8776/v1/64b6f3fbcc53435e8a60fcf89bb6617a" } ], "type": "volume", "name": "volume" }, { "endpoints_links": [], "endpoints": [ { "adminURL": "http://127.0.0.1:9292/v1", "region": "regionOne", "internalURL": "http://127.0.0.1:9292/v1", "publicURL": "http://127.0.0.1:9292/v1" } ], "type": "image", "name": "glance" }, { "endpoints_links": [], "endpoints": [ { "adminURL": "http://127.0.0.1:8774/v1.1/64b6f3fbcc53435e8a60fcf89bb6617a", "region": "regionOne", "internalURL": "http://127.0.0.1:8774/v1.1/64b6f3fbcc53435e8a60fcf89bb6617a", "publicURL": "http://127.0.0.1:8774/v1.1/64b6f3fbcc53435e8a60fcf89bb6617a" } ], "type": "compute", "name": "nova" }, { "endpoints_links": [], "endpoints": [ { "adminURL": "http://127.0.0.1:35357/v2.0", "region": "RegionOne", "internalURL": "http://127.0.0.1:35357/v2.0", "publicURL": "http://127.0.0.1:5000/v2.0" } ], "type": "identity", "name": "keystone" } ], "user": { "username": "user_name1", "roles_links": [ "role1", "role2" ], "id": "user_id1", "roles": [ { "name": "role1" }, { "name": "role2" } ], "name": "user_name1" } } } keystonemiddleware-4.21.0/examples/pki/cms/auth_v3_token_revoked.json0000666000175100017510000000551713225160624026102 0ustar zuulzuul00000000000000{ "token": { "catalog": [ { "endpoints": [ { "adminURL": "http://127.0.0.1:8776/v1/64b6f3fbcc53435e8a60fcf89bb6617a", "region": "regionOne", "internalURL": "http://127.0.0.1:8776/v1/64b6f3fbcc53435e8a60fcf89bb6617a", "publicURL": "http://127.0.0.1:8776/v1/64b6f3fbcc53435e8a60fcf89bb6617a" } ], "endpoints_links": [], "type": "volume", "name": "volume" }, { "endpoints": [ { "adminURL": "http://127.0.0.1:9292/v1", "region": "regionOne", "internalURL": "http://127.0.0.1:9292/v1", "publicURL": "http://127.0.0.1:9292/v1" } ], "endpoints_links": [], "type": "image", "name": "glance" }, { "endpoints": [ { "adminURL": "http://127.0.0.1:8774/v1.1/64b6f3fbcc53435e8a60fcf89bb6617a", "region": "regionOne", "internalURL": "http://127.0.0.1:8774/v1.1/64b6f3fbcc53435e8a60fcf89bb6617a", "publicURL": "http://127.0.0.1:8774/v1.1/64b6f3fbcc53435e8a60fcf89bb6617a" } ], "endpoints_links": [], "type": "compute", "name": "nova" }, { "endpoints": [ { "adminURL": "http://127.0.0.1:35357/v3", "region": "RegionOne", "internalURL": "http://127.0.0.1:35357/v3", "publicURL": "http://127.0.0.1:5000/v3" } ], "endpoints_links": [], "type": "identity", "name": "keystone" } ], "expires_at": "2038-01-18T21:14:07Z", "project": { "enabled": true, "description": null, "name": "tenant_name1", "id": "tenant_id1", "domain": { "id": "domain_id1", "name": "domain_name1" } }, "user": { "name": "revoked_username1", "id": "revoked_user_id1", "domain": { "id": "domain_id1", "name": "domain_name1" } }, "roles": [ { "name": "role1" }, { "name": "role2" } ], "methods": [ "password" ] } } keystonemiddleware-4.21.0/examples/pki/cms/auth_token_scoped_expired.json0000666000175100017510000000545413225160624027030 0ustar zuulzuul00000000000000{ "access": { "token": { "expires": "2010-06-02T14:47:34Z", "id": "placeholder", "tenant": { "id": "tenant_id1", "enabled": true, "description": null, "name": "tenant_name1" } }, "serviceCatalog": [ { "endpoints_links": [], "endpoints": [ { "adminURL": "http://127.0.0.1:8776/v1/64b6f3fbcc53435e8a60fcf89bb6617a", "region": "regionOne", "internalURL": "http://127.0.0.1:8776/v1/64b6f3fbcc53435e8a60fcf89bb6617a", "publicURL": "http://127.0.0.1:8776/v1/64b6f3fbcc53435e8a60fcf89bb6617a" } ], "type": "volume", "name": "volume" }, { "endpoints_links": [], "endpoints": [ { "adminURL": "http://127.0.0.1:9292/v1", "region": "regionOne", "internalURL": "http://127.0.0.1:9292/v1", "publicURL": "http://127.0.0.1:9292/v1" } ], "type": "image", "name": "glance" }, { "endpoints_links": [], "endpoints": [ { "adminURL": "http://127.0.0.1:8774/v1.1/64b6f3fbcc53435e8a60fcf89bb6617a", "region": "regionOne", "internalURL": "http://127.0.0.1:8774/v1.1/64b6f3fbcc53435e8a60fcf89bb6617a", "publicURL": "http://127.0.0.1:8774/v1.1/64b6f3fbcc53435e8a60fcf89bb6617a" } ], "type": "compute", "name": "nova" }, { "endpoints_links": [], "endpoints": [ { "adminURL": "http://127.0.0.1:35357/v2.0", "region": "RegionOne", "internalURL": "http://127.0.0.1:35357/v2.0", "publicURL": "http://127.0.0.1:5000/v2.0" } ], "type": "identity", "name": "keystone" } ], "user": { "username": "user_name1", "roles_links": [ "role1", "role2" ], "id": "user_id1", "roles": [ { "name": "role1" }, { "name": "role2" } ], "name": "user_name1" } } } keystonemiddleware-4.21.0/examples/pki/cms/revocation_list.pem0000666000175100017510000000267013225160624024623 0ustar zuulzuul00000000000000-----BEGIN CMS----- MIIEGAYJKoZIhvcNAQcCoIIECTCCBAUCAQExCTAHBgUrDgMCGjCCAiUGCSqGSIb3 DQEHAaCCAhYEggISew0KICAgICJyZXZva2VkIjogWw0KICAgICAgICB7DQogICAg ICAgICAgICAiZXhwaXJlcyI6ICIyMTEyLTA4LTE0VDE3OjU4OjQ4WiIsDQogICAg ICAgICAgICAiaWQiOiAiZGM1N2VhMTcxZDJmOTNlNGZmNWZhMDFmZTU3MTFmMmEi DQogICAgICAgIH0sDQogICAgICAgIHsNCiAgICAgICAgICAgICJleHBpcmVzIjog IjIxMTItMDgtMTRUMTc6NTg6NDhaIiwNCiAgICAgICAgICAgICJpZCI6ICI0OTQ4 ZmI0NmY4OGM0MWFmOTBiNjUyMTNhNDhiYWVmNyINCiAgICAgICAgfSwNCiAgICAg ICAgew0KICAgICAgICAgICAgImV4cGlyZXMiOiAiMjExMi0wOC0xNFQxNzo1ODo0 OFoiLA0KICAgICAgICAgICAgImlkIjogImRjNTdlYTE3MWQyZjkzZTRmZjVmYTAx ZmU1NzExZjJhIg0KICAgICAgICB9LA0KICAgICAgICB7DQogICAgICAgICAgICAi ZXhwaXJlcyI6ICIyMTEyLTA4LTE0VDE3OjU4OjQ4WiIsDQogICAgICAgICAgICAi aWQiOiAiNDk0OGZiNDZmODhjNDFhZjkwYjY1MjEzYTQ4YmFlZjciDQogICAgICAg IH0NCiAgICBdDQp9DQoxggHKMIIBxgIBATCBpDCBnjEKMAgGA1UEBRMBNTELMAkG A1UEBhMCVVMxCzAJBgNVBAgTAkNBMRIwEAYDVQQHEwlTdW5ueXZhbGUxEjAQBgNV BAoTCU9wZW5TdGFjazERMA8GA1UECxMIS2V5c3RvbmUxJTAjBgkqhkiG9w0BCQEW FmtleXN0b25lQG9wZW5zdGFjay5vcmcxFDASBgNVBAMTC1NlbGYgU2lnbmVkAgER MAcGBSsOAwIaMA0GCSqGSIb3DQEBAQUABIIBAGMtzsHJdosl27LoRWYHGknORRWE K0E9a7Bm4ZDt0XiGn0opGWpXF3Kj+7q86Ph1qcG9vZy20e2V+8n5696//OgMGCZe QNbkOv70c0pkICMqczv4RaNF+UPetwDdv+p0WV8nLH5dDVc8Pp8B4T6fN6vXHXA2 GMWxxn8SpF9bvP8S5VCAt7wsvmhWJpJVYe6bOdYzlhR0yLJzv4GvHtPVP+cBz6nS uJguvt77MfQU97pOaDbvfmsJRUf/L3Fd93KbgLTzFPEhddTs1oD9pSDckncnZwua 9nIDn2iFNB/NfZrbqy+owM0Nt5j1m4dcPX/qm0J9DAhKGeDUbIu+81yL308= -----END CMS----- keystonemiddleware-4.21.0/examples/pki/cms/auth_token_revoked.pkiz0000666000175100017510000000353513225160624025474 0ustar zuulzuul00000000000000PKIZ_eJylVtly4jgUfddXzHuqK9jGED_Mgze8BInYeEF-8wJeBYTF29ePbEh3p9OZycxQRZUtS_eee87Rlb59oz9J1Qz0hwzXw8s3AA1DZxpsPh8CI6tjJFqxfKBjnSLL0pMli5bayo6oS6l7UlIoawUd31qavH7V1kbEAcVSdTGkg4mrpunG3nZmhllUxRzMV7k0N_b0eR8cMespeGNnkSbsjeKQ-tw5j8jiAoK1MTNkk43Ylol8N1_KYh74fBlrwjHa2_3bZOzbl9DnPbdsaGAxD3V7EiuHGix7tUPdtFkW4hU6hynqY3bJ4XbZ4wkuAgLZIMcsZGBv9ch3p9jBTUAQWSlVjgvMAugkmZE3qbE3q4Ct6igfEXWBnxwjln-JyA0VzT4JNuYV--07FGCA8X9QgAHGDxQSg0l7xIy3duQRySHR7WaVP9XQMbgxgTxtV0XKoR7XSaHWABV2jgjuA2IWuHd7pEAmcLIMFRLBLJ6ufDNHBW4Rq-Y7b3KmQSfbjVQN5Br7oAaR7l2oEsOHKiJ2E7HVNdHRLtKqa3iTMtps6EL9JttdtX2kLa6YdXPwb2X7hS8ewKLsBsL-qxLgs8jvA39OLnjPbtmtHGNg9yNhpLpgP6nGgMS7BrpUD4hAzAhn-nCKOxp5cUl26yal-4HCZO4L-Toh6qcWB18kazDXZDQX1f5n6cE_aT9kjom3D33hetP-TnQpXAf5Aa1zgFTFhM-ixVccaA0cXeH6iUWawYKgoGAIKpADJ7D3qpWmslALiqBIeUwMFhUqh29GaxLfpHyhL22m39b7u3LB33qdoDraSEyifWw0G7Y9RuTSg1EOhhGWMm1fAw-0K43wWI-PObt-c-FndgdfkLCn_DCoE1iYT5tfLT-osP5q9_ldcPAx-lebittARaxBUhh0wBQ262GxzcfanQPfrmi9x0QvPyVw4AIMBN4X15S40W10L1RbXTpSB46TjMJoYJ9eoKJeoJO5sFBn0LFmUElCcINNs5HFNRkg085Ds2W0jCoY3-0u8d1B3h8b7G3-QriCYRDenFYGG1TEpGoS7d5UNJ6JtGb4dgxufEyG4LSMXehbrbGf3PbC_WND-1wR-FkdaXRv5KYw1J5s6NGW35DFRDjTJO_6JaCa0gXuW0sbnjujmvwC2awSIpwC396NAW-GG9fcA3j9zwfmvfN29Lyk5ZkfXDoicYzR-kMJTMx63c8Lg00wKFJuOK-_Geo7T2_lfp8D7pPupDDCztFkMT40aaprYqpK0NBK-t9C69DIIlY8y1qojcpA69zIFlYAHdDUxvTcXl1CsdRExlVlCcrWRG3VQrSkFHmSGDuyh5iI8HxCFhS-uoaSOM4FcgZNh5OqqEIT7KMTtNVGacZMS7XJlsGm6hONti9HraAMv99M6MXEFG3sgx_b1hOjIdD-FmhJhC7oVRdKxphJbOHSZb1zkEtO6CfXwKfXH5oMSA1ePDdTRcwOjWL9fFdSJckS6bVHFfF1IvDP-CWbCmXy9NpVu_BpqcRivc16oLGr4hK_vmoz1BDkvSxetosqVk-l6J5X-elhpsFty70GHNfuNX6VQnbGwedWP0pnp9wFMTBTn1wV_hryDJ7He69j2piEh31eh4yyeDTnVnOUqwekOJskWmXPiGm6R-UlY4xz-ZjMe0C6bus-TBfLy45cLuHM19gyW1Df1s5JbjUu1XU3FphSW7XS6UnvrDYL42XW7YvwyD-fOhBCxpuHZbEsrSeTeY6cR3W5TY66RQ4MmmvZUYXRflFI5uuWEecPjMA9If-BMIFQZVOb04E_O0ai7my7iTy3iyjLPXa6O678kDwyBSTepGIrln2AO_U4mzlzS-TU7WP1_DJr_vwTjHdVFSk_7q1_AfJ_mjc=keystonemiddleware-4.21.0/examples/pki/cms/auth_token_revoked.json0000666000175100017510000000550213225160624025464 0ustar zuulzuul00000000000000{ "access": { "token": { "expires": "2038-01-18T21:14:07Z", "id": "placeholder", "tenant": { "id": "tenant_id1", "enabled": true, "description": null, "name": "tenant_name1" } }, "serviceCatalog": [ { "endpoints_links": [], "endpoints": [ { "adminURL": "http://127.0.0.1:8776/v1/64b6f3fbcc53435e8a60fcf89bb6617a", "region": "regionOne", "internalURL": "http://127.0.0.1:8776/v1/64b6f3fbcc53435e8a60fcf89bb6617a", "publicURL": "http://127.0.0.1:8776/v1/64b6f3fbcc53435e8a60fcf89bb6617a" } ], "type": "volume", "name": "volume" }, { "endpoints_links": [], "endpoints": [ { "adminURL": "http://127.0.0.1:9292/v1", "region": "regionOne", "internalURL": "http://127.0.0.1:9292/v1", "publicURL": "http://127.0.0.1:9292/v1" } ], "type": "image", "name": "glance" }, { "endpoints_links": [], "endpoints": [ { "adminURL": "http://127.0.0.1:8774/v1.1/64b6f3fbcc53435e8a60fcf89bb6617a", "region": "regionOne", "internalURL": "http://127.0.0.1:8774/v1.1/64b6f3fbcc53435e8a60fcf89bb6617a", "publicURL": "http://127.0.0.1:8774/v1.1/64b6f3fbcc53435e8a60fcf89bb6617a" } ], "type": "compute", "name": "nova" }, { "endpoints_links": [], "endpoints": [ { "adminURL": "http://127.0.0.1:35357/v2.0", "region": "RegionOne", "internalURL": "http://127.0.0.1:35357/v2.0", "publicURL": "http://127.0.0.1:5000/v2.0" } ], "type": "identity", "name": "keystone" } ], "user": { "username": "revoked_username1", "roles_links": [ "role1", "role2" ], "id": "revoked_user_id1", "roles": [ { "name": "role1" }, { "name": "role2" } ], "name": "revoked_username1" } } } keystonemiddleware-4.21.0/examples/pki/cms/auth_token_scoped.pem0000666000175100017510000001137513225160624025117 0ustar zuulzuul00000000000000-----BEGIN CMS----- MIIN5QYJKoZIhvcNAQcCoIIN1jCCDdICAQExDTALBglghkgBZQMEAgEwggvqBgkq hkiG9w0BBwGgggvbBIIL13sNCiAgICAiYWNjZXNzIjogew0KICAgICAgICAidG9r ZW4iOiB7DQogICAgICAgICAgICAiZXhwaXJlcyI6ICIyMDM4LTAxLTE4VDIxOjE0 OjA3WiIsDQogICAgICAgICAgICAiaWQiOiAicGxhY2Vob2xkZXIiLA0KICAgICAg ICAgICAgInRlbmFudCI6IHsNCiAgICAgICAgICAgICAgICAiaWQiOiAidGVuYW50 X2lkMSIsDQogICAgICAgICAgICAgICAgImVuYWJsZWQiOiB0cnVlLA0KICAgICAg ICAgICAgICAgICJkZXNjcmlwdGlvbiI6IG51bGwsDQogICAgICAgICAgICAgICAg Im5hbWUiOiAidGVuYW50X25hbWUxIg0KICAgICAgICAgICAgfSwNCiAgICAgICAg ICAgICJhdWRpdF9pZHMiOiBbDQogICAgICAgICAgICAgICAgIlNMSVhsWFFVUVpX VWk5VkpycWRYcUEiDQogICAgICAgICAgICBdDQogICAgICAgIH0sDQogICAgICAg ICJzZXJ2aWNlQ2F0YWxvZyI6IFsNCiAgICAgICAgICAgIHsNCiAgICAgICAgICAg ICAgICAiZW5kcG9pbnRzX2xpbmtzIjogW10sDQogICAgICAgICAgICAgICAgImVu ZHBvaW50cyI6IFsNCiAgICAgICAgICAgICAgICAgICAgew0KICAgICAgICAgICAg ICAgICAgICAgICAgImFkbWluVVJMIjogImh0dHA6Ly8xMjcuMC4wLjE6ODc3Ni92 MS82NGI2ZjNmYmNjNTM0MzVlOGE2MGZjZjg5YmI2NjE3YSIsDQogICAgICAgICAg ICAgICAgICAgICAgICAicmVnaW9uIjogInJlZ2lvbk9uZSIsDQogICAgICAgICAg ICAgICAgICAgICAgICAiaW50ZXJuYWxVUkwiOiAiaHR0cDovLzEyNy4wLjAuMTo4 Nzc2L3YxLzY0YjZmM2ZiY2M1MzQzNWU4YTYwZmNmODliYjY2MTdhIiwNCiAgICAg ICAgICAgICAgICAgICAgICAgICJwdWJsaWNVUkwiOiAiaHR0cDovLzEyNy4wLjAu MTo4Nzc2L3YxLzY0YjZmM2ZiY2M1MzQzNWU4YTYwZmNmODliYjY2MTdhIg0KICAg ICAgICAgICAgICAgICAgICB9DQogICAgICAgICAgICAgICAgXSwNCiAgICAgICAg ICAgICAgICAidHlwZSI6ICJ2b2x1bWUiLA0KICAgICAgICAgICAgICAgICJuYW1l IjogInZvbHVtZSINCiAgICAgICAgICAgIH0sDQogICAgICAgICAgICB7DQogICAg ICAgICAgICAgICAgImVuZHBvaW50c19saW5rcyI6IFtdLA0KICAgICAgICAgICAg ICAgICJlbmRwb2ludHMiOiBbDQogICAgICAgICAgICAgICAgICAgIHsNCiAgICAg ICAgICAgICAgICAgICAgICAgICJhZG1pblVSTCI6ICJodHRwOi8vMTI3LjAuMC4x OjkyOTIvdjEiLA0KICAgICAgICAgICAgICAgICAgICAgICAgInJlZ2lvbiI6ICJy ZWdpb25PbmUiLA0KICAgICAgICAgICAgICAgICAgICAgICAgImludGVybmFsVVJM IjogImh0dHA6Ly8xMjcuMC4wLjE6OTI5Mi92MSIsDQogICAgICAgICAgICAgICAg ICAgICAgICAicHVibGljVVJMIjogImh0dHA6Ly8xMjcuMC4wLjE6OTI5Mi92MSIN CiAgICAgICAgICAgICAgICAgICAgfQ0KICAgICAgICAgICAgICAgIF0sDQogICAg ICAgICAgICAgICAgInR5cGUiOiAiaW1hZ2UiLA0KICAgICAgICAgICAgICAgICJu YW1lIjogImdsYW5jZSINCiAgICAgICAgICAgIH0sDQogICAgICAgICAgICB7DQog ICAgICAgICAgICAgICAgImVuZHBvaW50c19saW5rcyI6IFtdLA0KICAgICAgICAg ICAgICAgICJlbmRwb2ludHMiOiBbDQogICAgICAgICAgICAgICAgICAgIHsNCiAg ICAgICAgICAgICAgICAgICAgICAgICJhZG1pblVSTCI6ICJodHRwOi8vMTI3LjAu MC4xOjg3NzQvdjEuMS82NGI2ZjNmYmNjNTM0MzVlOGE2MGZjZjg5YmI2NjE3YSIs DQogICAgICAgICAgICAgICAgICAgICAgICAicmVnaW9uIjogInJlZ2lvbk9uZSIs DQogICAgICAgICAgICAgICAgICAgICAgICAiaW50ZXJuYWxVUkwiOiAiaHR0cDov LzEyNy4wLjAuMTo4Nzc0L3YxLjEvNjRiNmYzZmJjYzUzNDM1ZThhNjBmY2Y4OWJi NjYxN2EiLA0KICAgICAgICAgICAgICAgICAgICAgICAgInB1YmxpY1VSTCI6ICJo dHRwOi8vMTI3LjAuMC4xOjg3NzQvdjEuMS82NGI2ZjNmYmNjNTM0MzVlOGE2MGZj Zjg5YmI2NjE3YSINCiAgICAgICAgICAgICAgICAgICAgfQ0KICAgICAgICAgICAg ICAgIF0sDQogICAgICAgICAgICAgICAgInR5cGUiOiAiY29tcHV0ZSIsDQogICAg ICAgICAgICAgICAgIm5hbWUiOiAibm92YSINCiAgICAgICAgICAgIH0sDQogICAg ICAgICAgICB7DQogICAgICAgICAgICAgICAgImVuZHBvaW50c19saW5rcyI6IFtd LA0KICAgICAgICAgICAgICAgICJlbmRwb2ludHMiOiBbDQogICAgICAgICAgICAg ICAgICAgIHsNCiAgICAgICAgICAgICAgICAgICAgICAgICJhZG1pblVSTCI6ICJo dHRwOi8vMTI3LjAuMC4xOjM1MzU3L3YyLjAiLA0KICAgICAgICAgICAgICAgICAg ICAgICAgInJlZ2lvbiI6ICJSZWdpb25PbmUiLA0KICAgICAgICAgICAgICAgICAg ICAgICAgImludGVybmFsVVJMIjogImh0dHA6Ly8xMjcuMC4wLjE6MzUzNTcvdjIu MCIsDQogICAgICAgICAgICAgICAgICAgICAgICAicHVibGljVVJMIjogImh0dHA6 Ly8xMjcuMC4wLjE6NTAwMC92Mi4wIg0KICAgICAgICAgICAgICAgICAgICB9DQog ICAgICAgICAgICAgICAgXSwNCiAgICAgICAgICAgICAgICAidHlwZSI6ICJpZGVu dGl0eSIsDQogICAgICAgICAgICAgICAgIm5hbWUiOiAia2V5c3RvbmUiDQogICAg ICAgICAgICB9DQogICAgICAgIF0sDQogICAgICAgICJ1c2VyIjogew0KICAgICAg ICAgICAgInVzZXJuYW1lIjogInVzZXJfbmFtZTEiLA0KICAgICAgICAgICAgInJv bGVzX2xpbmtzIjogWw0KICAgICAgICAgICAgICAgICJyb2xlMSIsDQogICAgICAg ICAgICAgICAgInJvbGUyIg0KICAgICAgICAgICAgXSwNCiAgICAgICAgICAgICJp ZCI6ICJ1c2VyX2lkMSIsDQogICAgICAgICAgICAicm9sZXMiOiBbDQogICAgICAg ICAgICAgICAgew0KICAgICAgICAgICAgICAgICAgICAibmFtZSI6ICJyb2xlMSIN CiAgICAgICAgICAgICAgICB9LA0KICAgICAgICAgICAgICAgIHsNCiAgICAgICAg ICAgICAgICAgICAgIm5hbWUiOiAicm9sZTIiDQogICAgICAgICAgICAgICAgfQ0K ICAgICAgICAgICAgXSwNCiAgICAgICAgICAgICJuYW1lIjogInVzZXJfbmFtZTEi DQogICAgICAgIH0NCiAgICB9DQp9DQoxggHOMIIBygIBATCBpDCBnjEKMAgGA1UE BRMBNTELMAkGA1UEBhMCVVMxCzAJBgNVBAgTAkNBMRIwEAYDVQQHEwlTdW5ueXZh bGUxEjAQBgNVBAoTCU9wZW5TdGFjazERMA8GA1UECxMIS2V5c3RvbmUxJTAjBgkq hkiG9w0BCQEWFmtleXN0b25lQG9wZW5zdGFjay5vcmcxFDASBgNVBAMTC1NlbGYg U2lnbmVkAgERMAsGCWCGSAFlAwQCATANBgkqhkiG9w0BAQEFAASCAQCgtkCXRzS8 s7WjZCsKDhMt6q5JQIm7x6EMKCBaOABQG9EOVIAyqfoJDdjDtz9rZEPO3UVTpPkg VjtA0QV97qT8bX55AcCkk7kBRDOKTtco5GOGwjMxL+GWbIwWiB7DKIP4RA6NLZtF WxUbLBY+OgBSiayuHqSx+Rd08QC9oHf25wRkTNp3VFPxtAleDmASzdAoIafoS+FB Po+9WuTaGdeya7S+ms4SSyXf9cdMKGv010R/aMINWUWaBrkB4wlespYLmKH/XzwS pENRIdbI9XHEOYTWKqul5tucA3p21IA24ND6acl9CXHr3KeqXpRwclSZ38Kg/23T 92D+SowEjlGf -----END CMS----- keystonemiddleware-4.21.0/examples/pki/cms/auth_token_unscoped.json0000666000175100017510000000105113225160624025640 0ustar zuulzuul00000000000000{ "access": { "token": { "expires": "2112-08-17T15:35:34Z", "id": "01e032c996ef4406b144335915a41e79" }, "serviceCatalog": {}, "user": { "username": "user_name1", "roles_links": [], "id": "c9c89e3be3ee453fbf00c7966f6d3fbd", "roles": [ { "name": "role1" }, { "name": "role2" } ], "name": "user_name1" } } } keystonemiddleware-4.21.0/examples/pki/cms/revocation_list.der0000666000175100017510000000000013225160624024575 0ustar zuulzuul00000000000000keystonemiddleware-4.21.0/examples/pki/cms/revocation_list.pkiz0000666000175100017510000000222513225160624025013 0ustar zuulzuul00000000000000PKIZ_eJx9VEuPszgQvPMr9h6NQgIhk8N3MMaACTaBmJdvCZMxGMhjkgmPX79kRtq9rNYXq0ul6u7qVr-9Tc9EDqZ_QbJ_BW8KwdhiXe5tLxyXz4KCsICXCQstCMHYQRCiHjLgmiL-sgSBjpzwpHPg_ubs8VFTrBC54DCBsYqEsL3T4A0848_DMqmxvIhUu1c8K7tD5jXFgA0M8UAYGnwGdJ8hVUkspAUy1gMZ6mmF7xh6Vw5fRK_Ox1jjKerpaNekzVdkGau8zRe8RR1JeUNZ0SskzYd87218aK5xm-iF00wVkCqoQEUk6kmldgFUe2qHk9BlEVgXNbAvlQ9BdUjDSnkRqVWrgcOnn7eBVUpq2SWXdZfLfDGJjDkL9by1Gy6L6nPfianN5uSa16JNRuXVJ5a4Jww_iCUehEUxYYVBmTCoVR5w1QncNj9-4DaSlH00OUMaScNhSjIqnEUtl0mbM9DzNl7QEfVceiU-q3fs_r-BL_-U_zYQq8FUNm-xSttcDxyiktRuA2ZWVMaTCC2n6qo8TVqFDt4my9ReCHc77YTZC2wCBs2rBc2zRFsChAMWMTIjYlKGfALq37gkMElIr8AReKagiQkEAzU1SYQ7BHIrCUMXdQ37SFffp4yXRyfukQThL_fCYLzpeLpiyodjy8OIIgLef5RhT_B-mawKLXoe27j3GJCmqG9lXTmbTjVhiKZmHs0po-pxuWqU0PlRGn-EhtWzaIvetsD-NxNhcEGbo5OLeNmcj21SA_FKVjjm_h6ADh8UAtR_9npaaxOEMTAnLwBePp4BLmXIWNlG3VbvrrPtiQexUW7rJVjJVTHLKFesvvOb53c2y3nfroKr_4HPWybJU5LKEN9F1blaEoPLEt9um4GU7jwrV4_30NvPxp29rpSZE9w6fjULI9zSqsSXWt34unwcYvmpzz_XiIe0nEtSfz6-gVaWj2__0JzrPF0PCCzvtnI-rXdREidG9V7NbmsBV_6mymo9HLTrEoxi53yWtrEjc_U6DtJ71MbzfWfCehrqqf-qb0q011N5z0mktafnQvrah6d2TEBxvsEi0o7hw_LnxL3Gxs2AJyPULAcZZR0GOHJPZzRX6GXHb1Y-J5pO3aO8k1ulj14d6C75KgSo8sN8zOaD2Y1P9P2F_yg_dwhR69-b9Dc2l4GQkeystonemiddleware-4.21.0/examples/pki/cms/auth_token_unscoped.pkiz0000666000175100017510000000213113225160624025644 0ustar zuulzuul00000000000000PKIZ_eJx9VMmSozgQvfMVfa-oMAbbVRzmIAlZCFvQGLHewAs72MaY5esHuzt65tSKUEiZkS_z5RL5-TkfiAk1fiBmv4RPgVGq7kCg75qQps-jAawjamYd4QiBwUHAwgPiQIOJc1cThkg-67lDkH0jNo1lQbWwBqJZaQc4SXB2HvU0kIzyKLPMzOAXred_HV4DyVUD_5DGRKlp3iRnWWwp0kUhlh5lnNEN1dos9NM-8vXyOM4yoiPjeNxzsNpzLLsqXpo5e13Ry-gLfA0R3QizYc88p2eTnpu8kEIvEA0VSEGO55dNBi8Gw8PibCObtq7sEchO_szqd1DhWClt6BuXmJRd9It27Nt9Qqt1GnvOLP8GlEoXeMuS2e_oYywNb6YC3T6-_m_8dshxdpmdzPV4g14501p_xsQZab08_WEx44S_RHnnOL-56bGV6TlTUDlT6DmiwY0qqIKeESYLJg-kMA8LJoVZiHTl4otDkmi7ub1wSCgEHMGrimCd4x0DCQFLB8MDgwbHewYKIrwVKUOuywY0AR0mhgtBwkFhQHagPQaB6lqWhvuSn7x1d_bDuZXOgHNgvWwFCBqOHKUPvTU_kW0eTfjAwPc7EhoYtSV3fZQPz7hyBp2DHCbFLS0yovQiRBb2hG31KM--IcbSurTI29H0djSun8fqOGxVYP9ixThaGmVMgsSRyjqu3AIk-CAwcCTQbk3Q04gB8c-IzhMKgeUAONcCbO8atS73i3mAGF0iWEaZWKcHN11FAj1_r8a1F5ZGKDWGyD468ZlOstqwRb1jnp5-5fK-M-cJvXSTbE6Vxqs4Sg9dUQdNcSuE_Cfc3JzH-fqxLruP-wpoqpNGV9iP8lMuzsmGtUkY1PCeUyJHQ7Nl2vfJslSkKOoJWpOw21fD1JDztsjbyx27Hw95icVWut-JOC6a_SUK-k1AmpUrNtpjm3T5osNNEn608g1lsSOgZBVvppgUhx2vm-5ate56rZynjSgam_tr6J7awn9y4n5Lth48bJRdy6Wx8m52ju7IE1Z-G92-ldZegIXrbm6gHJuBT63Ss1g3be9i5-ZTVotYxMm5WNrPXaB2_PpzsPt_hPdKwYb633r5FzKfcIU=keystonemiddleware-4.21.0/examples/pki/cms/auth_v3_token_scoped.pkiz0000666000175100017510000000377113225160624025724 0ustar zuulzuul00000000000000PKIZ_eJy9V0tzozoT3etX3H1qKoBNEhbfgpexMJKNjXloZyDmJWwnNs9f_wnsSWYyuXUzc6uuq7yQQN2n-_RpNd--sZ-iGxD_paLNsPgGEISmIwfm4khgWkdYtiP1yPZWjqqqTqHKtt5qjmwpCU3SIlGIjXQ50ZskiddKUryAtMgMqeEUpTEStqkqEM5Xh3MWG9Ir8abZMlMeYcnT2EhrMkfDOoQHJY0meBJOzAJAyp2hanah0NKogw9wdmEHxDT0tuxlOYtK6UwcPdtvmuS5M6vA4ynMjwk8mHVobDsAD3xsqXJG_LTZ-SaNeCmNVWZIhR3S0NRy5NZy9KmrwXaZ69wylydeBgenDTP-AoiHucEis16EAp_u3mDTYvRUruvQm51CKp2IpmeDs7CcXchmcMJCuB4S9-PmDSosXQbVPBPPHoxx0cGlw8HduJZZfobnIucLtABoM8L5IbY1ZcaqeCaNe7fnBfFxHpW0iQ1ahxnzboh8aLQSGCwHwowLvLYmb0l0KzJXaoaMe08srZjnjpSz_AY_JQZ_AuE1IXxUNiO83XzNRdqxtnq9w920sXK5Qs5xivtIsCZBa_UBF-SkRAJhjhEPUG_32NtOAydoSInLpUazIGePnDiFWTPQRYlwg83oJl58CgVxFZbbMV-AZf8UsrijkqSBcOV-gE78IS_NmPXYN89XRlIunssPVvfUojyqkDptgJXrD0uN1VUmCWjzJGADCiTHZVDiHDuIQ71Ll4YuIIPkJE_EoIQCzvVJcE1uB66Qpreqcw87T6ocQaTwwCp0fv6Opgw8fGNJ4YOyPQXdNXfgT5P3PXfgj5Lnjvrhnn2FgissUodzdyjPD0X1fd-ULFX5tD7A3xXIF-tDBCgvuiHGr3D-GeXgdzgfKXegiEbK_yMaxX8KEXxGzTUEegm8mI4Hf2hxRGjTsMRvCFkIYhEZ0pCcfjjoTT6BXc6K0KPVFYXbhWPLM4_xfN2AZfZUIwdORsjqlPW9ZIJ7u45zvfqKNsBHcfxuUt8KibWx82cQ_wkh-F35fkQIfpf3j7SDT-TLjfLN9Rrn64xh60lp5kG_7bGGeOKkKc6VMhCC6dIzM4DzoMXC9cL4nrTb1XUtmkKqBjX6w31xWIuRca2HQJAu0dzlwC8SLsU6Lt_uQnZHrJtQYIm-XawfBQVGa976MlxpXxETGkJxIsYCGt8HP8GmP8O-NpFf-sUNAStvFZ7BF5oG84h43DEJd79SCbZ_IOEfHYJPPPJIkxtGZf-JhDcfmyv4IOGCqZPb-Wvxo4x3gitGEzYrvEufjwS3A_9muBjOgF-Hi3evsY9pRH-aE07kKrTR-23AGOhiteC7BYO-33m3xtKZjqPTIJyla9ed7VzePS1dsogOs8KbzxRIeWnvGCqQoymb-eYLNvspCBoF-z8j-9iocqC5tj3TG51H9rlR7XFt6I3pbnvdQnJhyPxWB6qCVJvTWz2XbSXBriJHjupiPixFMWY9goW2QYo8vqymyHQmCg0pZhMNfkVrvQFaM1q29Ca1iE97NmBW7BBFKjLUzYuxgeFEs3VTXgfeOxOuHA6GDpgDgyWrlDrS61ukwNGT3CJrK7hnkinOzosrNq2pMvOmNoEZQAJlb6spMlSQzBngBy-KbG9lNuoqsl45jyd9AeeC-HheWe3ZcDV83l82hJcKyxTugoXTmR29W7ggfMi9NIj3U057PbLunu_O-6Pf76PznSIHxJRq4e7OOIWL7KTwPgcP9f2rd7_dRKUwebBCDmgngUi2KFhknc5gFhThttK4Je6NbWFO4GIz0T3rsfJW4mql2yo1yqqtlZnzjLO21O874K2f7p-3F08ISRVMDf_iXbz5PD_K8sTuT0er8oTnKn5NWsdHyHVR99DQbfas-vv01XjSVsATVN47Wg1furyTLmYXI0p8ob7Xl6tjv6sXjplX6K40Nz4WV013XF_UIgmX3fSurGfTwwJ0j4vLEa_um-eE7-4VWqYvq8eX-zbZTFYPl2htaOZRdlYzh4P_A-M3io619--V_wMk2UFAkeystonemiddleware-4.21.0/examples/pki/cms/auth_v3_token_revoked.pkiz0000666000175100017510000000364113225160624026102 0ustar zuulzuul00000000000000PKIZ_eJylVsmSozgQvesr5l7R0Symyhz6wG5oS5jFgLixtDEY7PLC-vUjYXd31Sw1PTOOcNgIZerle7no0yfykTXDRL8p0KMPnwA0zdWywNbXU2zuuwxJTqacyNpiUhRZXCqSow2KL63kYntRC6gYFVnfLQ3FOxuemfJAdbSVlNBFSSuK6PpttJiUu9VpaT6bq2uZrawuaYIqV-7PcSjscTPU8fzsjiAPt1dTsQ4px-6TcFHapfxiNsI-Dbfkv1TGhnjDYd1G3Lw2mGVfmE19MKsT-XU7kIb6a1qLr7GqlTuPvvxpnBtBi0OBeW_s1hmHxiSSmSQUW0A9pcfgmipvPB_dOm30NtffOkb73NCvKZdRlCkJlThna3A3iLt0Fdxiz6ThEGO3T7m6zVfw--Z9bLAEaeD5NHbFOuUrt7fLZQegb_LrSmqhshjsquDRhLu80jpUuSVq8BQ3VoWn7YRUyMb-fo8qucEcXtihVaIKDwBxWrlWpDJrgiON6Y7IqmOu7tKD2D5QvaYkrIzyo79HASiM_4MCUBg_UKyCMjXqKggseJdpz-Qr6Xk9LgdYZfSAfl1pz7aa8agUOegtOYAMk4srck6DKuRDBk5BbRsaB424iqtCwI3JoUrjsWeJEVXj6AqZ8ZC5Ea8kkdj6rm_Qxiu5S4juGSteye8lG0ms-i2nMn6X7Y4sv5L8qCg_4N_K9p6vwwhs36SE_WclwN95fuf4A3LBO3Z9U4Azu38mLAnZfcxtZ4ekIg-ZIVJEE4i44TVtbhP1HLKsuFbeV2PaiBz-IMXBr5FFk8uhIbVU-7fSg4-1n08e4zB_TbnFjOg70T4nzPIDUsItqfuRlO_1lzJQoRwthvWEGVzFDYBcXGIOsnByJhRuF9jHfdygxlbrElfkjZ_v50Q7yixpZa-Y_aVi-ut4_ypc8FGuY068kRxg_txo0I7kRZvwsARUjihirrTjEh5oV6LwLnFUT7nxIwv_Nt3BP0tI-dnyax5Pdy4eKV7ONh64SyRs0uaeZbQa44hW3hBsD_09C1cuk6mnbj1pIxqpIsS5f5oIJyxAI5FlnGH2eWiRMkb_ZMhCVepnREc2B_TUfFX3j9hfYzILcqNmvn1A3J03Nqe2ZLAETGKIh3vzIKPM0KeMz7usccpZlSZYZEY9xhHa4ciZkcFKmmyF6aHHDMDWnZHAGpB66hF7evQF8RpH8N0AefSILjXIhDr-VA08oI8pN9Sw_J4LwRRH5mNOut08_h7D9o3U8zwFhPXdvOhrDxWcPwzV-kD7A333xpiEFHcJFxxAxNPT7jDho3XFyvtNjz074pzAZ8WdbyhSduqLYmUAqdBkaBoH8v0GnVOvSFgNHEfXeo2FzrVXnPnZ0Hor2E7aGkoHQ2K3miJDxWG0AWiV5MgFCmQp85UAsWkjCDkpbRKSB2XpvnkPLZ-X67RGDA7RBbpar_az4zXQ-v36R977Wg0V-OP6Qm4vluTikIQhZDwhswmklDo63h2tG3EE8aRtoWzOJ0kDXG-54BqXsp-EeRuHjiKR0-Qe61_7hSrtT73qvL1PaTKQHXo30qTi8A1d3G3mrSX5pubCKREZlaxEeZF0qnqe3Gq0mmcvvB763tW0W69v-s-RDqpRgZnLY1x4BMViY3G8gDiW3cTRsolW2uc0MOVLyz_fal5dtTiSq7TstR2f2eNmoWKwQVmIxW25t-zzywnrqrEbO_VsuJd1bWtQ1vTyKWg3ngtbQfl80c8Xd0wydeAbqJRPVxcMHty3SBcuQd0vfX_h9ofRwuYUcmWwGJJ8SL7mJRwCzcebvLt5SqHwT_LGzgaxZ3aFBBzm5Ww_7faNib7K_nR4sXH7ujkdrPPlZSva8pNYtf1zPY0o6XtJv52T6LwNfIlbdkJvSQxA-XNVOzJ7Vlipvh6Dk_2UC0vmcxS3tiN9-QLmC62G1J-X298BCSOhiw==keystonemiddleware-4.21.0/examples/pki/cms/auth_v3_token_revoked.pem0000666000175100017510000001126413225160624025706 0ustar zuulzuul00000000000000-----BEGIN CMS----- MIINrQYJKoZIhvcNAQcCoIINnjCCDZoCAQExCTAHBgUrDgMCGjCCC7oGCSqGSIb3 DQEHAaCCC6sEggunew0KICAgICJ0b2tlbiI6IHsNCiAgICAgICAgImNhdGFsb2ci OiBbDQogICAgICAgICAgICB7DQogICAgICAgICAgICAgICAgImVuZHBvaW50cyI6 IFsNCiAgICAgICAgICAgICAgICAgICAgew0KICAgICAgICAgICAgICAgICAgICAg ICAgImFkbWluVVJMIjogImh0dHA6Ly8xMjcuMC4wLjE6ODc3Ni92MS82NGI2ZjNm YmNjNTM0MzVlOGE2MGZjZjg5YmI2NjE3YSIsDQogICAgICAgICAgICAgICAgICAg ICAgICAicmVnaW9uIjogInJlZ2lvbk9uZSIsDQogICAgICAgICAgICAgICAgICAg ICAgICAiaW50ZXJuYWxVUkwiOiAiaHR0cDovLzEyNy4wLjAuMTo4Nzc2L3YxLzY0 YjZmM2ZiY2M1MzQzNWU4YTYwZmNmODliYjY2MTdhIiwNCiAgICAgICAgICAgICAg ICAgICAgICAgICJwdWJsaWNVUkwiOiAiaHR0cDovLzEyNy4wLjAuMTo4Nzc2L3Yx LzY0YjZmM2ZiY2M1MzQzNWU4YTYwZmNmODliYjY2MTdhIg0KICAgICAgICAgICAg ICAgICAgICB9DQogICAgICAgICAgICAgICAgXSwNCiAgICAgICAgICAgICAgICAi ZW5kcG9pbnRzX2xpbmtzIjogW10sDQogICAgICAgICAgICAgICAgInR5cGUiOiAi dm9sdW1lIiwNCiAgICAgICAgICAgICAgICAibmFtZSI6ICJ2b2x1bWUiDQogICAg ICAgICAgICB9LA0KICAgICAgICAgICAgew0KICAgICAgICAgICAgICAgICJlbmRw b2ludHMiOiBbDQogICAgICAgICAgICAgICAgICAgIHsNCiAgICAgICAgICAgICAg ICAgICAgICAgICJhZG1pblVSTCI6ICJodHRwOi8vMTI3LjAuMC4xOjkyOTIvdjEi LA0KICAgICAgICAgICAgICAgICAgICAgICAgInJlZ2lvbiI6ICJyZWdpb25PbmUi LA0KICAgICAgICAgICAgICAgICAgICAgICAgImludGVybmFsVVJMIjogImh0dHA6 Ly8xMjcuMC4wLjE6OTI5Mi92MSIsDQogICAgICAgICAgICAgICAgICAgICAgICAi cHVibGljVVJMIjogImh0dHA6Ly8xMjcuMC4wLjE6OTI5Mi92MSINCiAgICAgICAg ICAgICAgICAgICAgfQ0KICAgICAgICAgICAgICAgIF0sDQogICAgICAgICAgICAg ICAgImVuZHBvaW50c19saW5rcyI6IFtdLA0KICAgICAgICAgICAgICAgICJ0eXBl IjogImltYWdlIiwNCiAgICAgICAgICAgICAgICAibmFtZSI6ICJnbGFuY2UiDQog ICAgICAgICAgICB9LA0KICAgICAgICAgICAgew0KICAgICAgICAgICAgICAgICJl bmRwb2ludHMiOiBbDQogICAgICAgICAgICAgICAgICAgIHsNCiAgICAgICAgICAg ICAgICAgICAgICAgICJhZG1pblVSTCI6ICJodHRwOi8vMTI3LjAuMC4xOjg3NzQv djEuMS82NGI2ZjNmYmNjNTM0MzVlOGE2MGZjZjg5YmI2NjE3YSIsDQogICAgICAg ICAgICAgICAgICAgICAgICAicmVnaW9uIjogInJlZ2lvbk9uZSIsDQogICAgICAg ICAgICAgICAgICAgICAgICAiaW50ZXJuYWxVUkwiOiAiaHR0cDovLzEyNy4wLjAu MTo4Nzc0L3YxLjEvNjRiNmYzZmJjYzUzNDM1ZThhNjBmY2Y4OWJiNjYxN2EiLA0K ICAgICAgICAgICAgICAgICAgICAgICAgInB1YmxpY1VSTCI6ICJodHRwOi8vMTI3 LjAuMC4xOjg3NzQvdjEuMS82NGI2ZjNmYmNjNTM0MzVlOGE2MGZjZjg5YmI2NjE3 YSINCiAgICAgICAgICAgICAgICAgICAgfQ0KICAgICAgICAgICAgICAgIF0sDQog ICAgICAgICAgICAgICAgImVuZHBvaW50c19saW5rcyI6IFtdLA0KICAgICAgICAg ICAgICAgICJ0eXBlIjogImNvbXB1dGUiLA0KICAgICAgICAgICAgICAgICJuYW1l IjogIm5vdmEiDQogICAgICAgICAgICB9LA0KICAgICAgICAgICAgew0KICAgICAg ICAgICAgICAgICJlbmRwb2ludHMiOiBbDQogICAgICAgICAgICAgICAgICAgIHsN CiAgICAgICAgICAgICAgICAgICAgICAgICJhZG1pblVSTCI6ICJodHRwOi8vMTI3 LjAuMC4xOjM1MzU3L3YzIiwNCiAgICAgICAgICAgICAgICAgICAgICAgICJyZWdp b24iOiAiUmVnaW9uT25lIiwNCiAgICAgICAgICAgICAgICAgICAgICAgICJpbnRl cm5hbFVSTCI6ICJodHRwOi8vMTI3LjAuMC4xOjM1MzU3L3YzIiwNCiAgICAgICAg ICAgICAgICAgICAgICAgICJwdWJsaWNVUkwiOiAiaHR0cDovLzEyNy4wLjAuMTo1 MDAwL3YzIg0KICAgICAgICAgICAgICAgICAgICB9DQogICAgICAgICAgICAgICAg XSwNCiAgICAgICAgICAgICAgICAiZW5kcG9pbnRzX2xpbmtzIjogW10sDQogICAg ICAgICAgICAgICAgInR5cGUiOiAiaWRlbnRpdHkiLA0KICAgICAgICAgICAgICAg ICJuYW1lIjogImtleXN0b25lIg0KICAgICAgICAgICAgfQ0KICAgICAgICBdLA0K ICAgICAgICAiZXhwaXJlc19hdCI6ICIyMDM4LTAxLTE4VDIxOjE0OjA3WiIsDQog ICAgICAgICJwcm9qZWN0Ijogew0KICAgICAgICAgICAgImVuYWJsZWQiOiB0cnVl LA0KICAgICAgICAgICAgImRlc2NyaXB0aW9uIjogbnVsbCwNCiAgICAgICAgICAg ICJuYW1lIjogInRlbmFudF9uYW1lMSIsDQogICAgICAgICAgICAiaWQiOiAidGVu YW50X2lkMSIsDQogICAgICAgICAgICAiZG9tYWluIjogew0KICAgICAgICAgICAg ICAgICJpZCI6ICJkb21haW5faWQxIiwNCiAgICAgICAgICAgICAgICAibmFtZSI6 ICJkb21haW5fbmFtZTEiDQogICAgICAgICAgICB9DQogICAgICAgIH0sDQogICAg ICAgICJ1c2VyIjogew0KICAgICAgICAgICAgIm5hbWUiOiAicmV2b2tlZF91c2Vy bmFtZTEiLA0KICAgICAgICAgICAgImlkIjogInJldm9rZWRfdXNlcl9pZDEiLA0K ICAgICAgICAgICAgImRvbWFpbiI6IHsNCiAgICAgICAgICAgICAgICAiaWQiOiAi ZG9tYWluX2lkMSIsDQogICAgICAgICAgICAgICAgIm5hbWUiOiAiZG9tYWluX25h bWUxIg0KICAgICAgICAgICAgfQ0KICAgICAgICB9LA0KICAgICAgICAicm9sZXMi OiBbDQogICAgICAgICAgICB7DQogICAgICAgICAgICAgICAgIm5hbWUiOiAicm9s ZTEiDQogICAgICAgICAgICB9LA0KICAgICAgICAgICAgew0KICAgICAgICAgICAg ICAgICJuYW1lIjogInJvbGUyIg0KICAgICAgICAgICAgfQ0KICAgICAgICBdLA0K ICAgICAgICAibWV0aG9kcyI6IFsNCiAgICAgICAgICAgICJwYXNzd29yZCINCiAg ICAgICAgXQ0KICAgIH0NCn0NCjGCAcowggHGAgEBMIGkMIGeMQowCAYDVQQFEwE1 MQswCQYDVQQGEwJVUzELMAkGA1UECBMCQ0ExEjAQBgNVBAcTCVN1bm55dmFsZTES MBAGA1UEChMJT3BlblN0YWNrMREwDwYDVQQLEwhLZXlzdG9uZTElMCMGCSqGSIb3 DQEJARYWa2V5c3RvbmVAb3BlbnN0YWNrLm9yZzEUMBIGA1UEAxMLU2VsZiBTaWdu ZWQCAREwBwYFKw4DAhowDQYJKoZIhvcNAQEBBQAEggEAwFCjl3GSGrlil3cLwS11 1gtc6K3gBSMbc7LviIFk4KDRBvHWEHT1fs/Q4T0Y12P97Uaxh47f2sNgdbsDKSE8 K/KCeMy+0I7Eo3iDoXKcIRPux1sXFhOX36qLPpY4eWd3Q77MiUPng+78qA3AMPPl wEcfb2OaYsWmVi9jGsDfAvksF/WO5dg+G9m2l+zcboIJswsKbBJnM5bn8EDHk7bg YuMnOzqZsoymr6sehOPQ8QTV6kIj1w/gmtkaIH2QtBo78hCqjZ+cFeYy4zDk2HJg Mf7PDm0hx1G0hJMVxdNzkWoFvLreTzRselsrXrx8Gejof92JyKuBjZq0kBpphOHG 6w== -----END CMS----- keystonemiddleware-4.21.0/examples/pki/cms/auth_v3_token_scoped.json0000666000175100017510000000765713225160624025727 0ustar zuulzuul00000000000000{ "token": { "audit_ids": [ "SLIXlXQUQZWUi9VJrqdXqA" ], "methods": [ "password" ], "roles": [ { "name": "role1" }, { "name": "role2" } ], "expires_at": "2038-01-18T21:14:07Z", "project": { "id": "tenant_id1", "domain": { "id": "domain_id1", "name": "domain_name1" }, "enabled": true, "description": null, "name": "tenant_name1" }, "catalog": [ { "endpoints": [ { "interface": "admin", "url": "http://127.0.0.1:8776/v1/64b6f3fbcc53435e8a60fcf89bb6617a", "region": "regionOne" }, { "interface": "internal", "url": "http://127.0.0.1:8776/v1/64b6f3fbcc53435e8a60fcf89bb6617a", "region": "regionOne" }, { "interface": "public", "url": "http://127.0.0.1:8776/v1/64b6f3fbcc53435e8a60fcf89bb6617a", "region": "regionOne" } ], "type": "volume", "name": "volume" }, { "endpoints": [ { "interface": "admin", "url": "http://127.0.0.1:9292/v1", "region": "regionOne" }, { "interface": "internal", "url": "http://127.0.0.1:9292/v1", "region": "regionOne" }, { "interface": "public", "url": "http://127.0.0.1:9292/v1", "region": "regionOne" } ], "type": "image", "name": "glance" }, { "endpoints": [ { "interface": "admin", "url": "http://127.0.0.1:8774/v1.1/64b6f3fbcc53435e8a60fcf89bb6617a", "region": "regionOne" }, { "interface": "internal", "url": "http://127.0.0.1:8774/v1.1/64b6f3fbcc53435e8a60fcf89bb6617a", "region": "regionOne" }, { "interface": "public", "url": "http://127.0.0.1:8774/v1.1/64b6f3fbcc53435e8a60fcf89bb6617a", "region": "regionOne" } ], "type": "compute", "name": "nova" }, { "endpoints": [ { "interface": "admin", "url": "http://127.0.0.1:35357/v3", "region": "RegionOne" }, { "interface": "internal", "url": "http://127.0.0.1:35357/v3", "region": "RegionOne" }, { "interface": "public", "url": "http://127.0.0.1:5000/v3", "region": "RegionOne" } ], "type": "identity", "name": "keystone" } ], "user": { "domain": { "id": "domain_id1", "name": "domain_name1" }, "name": "user_name1", "id": "user_id1" } } } keystonemiddleware-4.21.0/examples/pki/cms/auth_token_unscoped.pem0000666000175100017510000000276513225160624025465 0ustar zuulzuul00000000000000-----BEGIN CMS----- MIIERgYJKoZIhvcNAQcCoIIENzCCBDMCAQExCTAHBgUrDgMCGjCCAlMGCSqGSIb3 DQEHAaCCAkQEggJAew0KICAgICJhY2Nlc3MiOiB7DQogICAgICAgICJ0b2tlbiI6 IHsNCiAgICAgICAgICAgICJleHBpcmVzIjogIjIxMTItMDgtMTdUMTU6MzU6MzRa IiwNCiAgICAgICAgICAgICJpZCI6ICIwMWUwMzJjOTk2ZWY0NDA2YjE0NDMzNTkx NWE0MWU3OSINCiAgICAgICAgfSwNCiAgICAgICAgInNlcnZpY2VDYXRhbG9nIjog e30sDQogICAgICAgICJ1c2VyIjogew0KICAgICAgICAgICAgInVzZXJuYW1lIjog InVzZXJfbmFtZTEiLA0KICAgICAgICAgICAgInJvbGVzX2xpbmtzIjogW10sDQog ICAgICAgICAgICAiaWQiOiAiYzljODllM2JlM2VlNDUzZmJmMDBjNzk2NmY2ZDNm YmQiLA0KICAgICAgICAgICAgInJvbGVzIjogWw0KICAgICAgICAgICAgICAgIHsN CiAgICAgICAgICAgICAgICAgICAgIm5hbWUiOiAicm9sZTEiDQogICAgICAgICAg ICAgICAgfSwNCiAgICAgICAgICAgICAgICB7DQogICAgICAgICAgICAgICAgICAg ICJuYW1lIjogInJvbGUyIg0KICAgICAgICAgICAgICAgIH0NCiAgICAgICAgICAg IF0sDQogICAgICAgICAgICAibmFtZSI6ICJ1c2VyX25hbWUxIg0KICAgICAgICB9 DQogICAgfQ0KfQ0KMYIByjCCAcYCAQEwgaQwgZ4xCjAIBgNVBAUTATUxCzAJBgNV BAYTAlVTMQswCQYDVQQIEwJDQTESMBAGA1UEBxMJU3Vubnl2YWxlMRIwEAYDVQQK EwlPcGVuU3RhY2sxETAPBgNVBAsTCEtleXN0b25lMSUwIwYJKoZIhvcNAQkBFhZr ZXlzdG9uZUBvcGVuc3RhY2sub3JnMRQwEgYDVQQDEwtTZWxmIFNpZ25lZAIBETAH BgUrDgMCGjANBgkqhkiG9w0BAQEFAASCAQAXNWXYv3q2EcEjigKDJEOvnKBGTHeV o9iwYmtdJ2kKtbuZiSGOcWymxNtv//IPMmNDWZ/uwDZt37YdPwCMRJa79h6dastD 5slEZGMxgFekm/1yqpV2F7xGqGIED2rNTeBlVnYS6ZOL8hCqekPb1OqXZ3vDaHtQ rrBzNP8RbWS4MyUoVZtSEYANjJVp/zou/pYASml9iNPPKrl2xRgYuzaAirVIiTZt QZY4LQYnHdVBLTZ0fQQugohTba789ix0U79ReQrIOqnBD3OnmN0uRovu5s1HYyre c67FixOpNgA4IBFsqYG2feP6ZF1zCmAaRYX4LpprZLGzg/aPHxqjXGsT -----END CMS----- keystonemiddleware-4.21.0/examples/pki/cms/auth_token_revoked.pem0000666000175100017510000001123313225160624025272 0ustar zuulzuul00000000000000-----BEGIN CMS----- MIINnQYJKoZIhvcNAQcCoIINjjCCDYoCAQExCTAHBgUrDgMCGjCCC6oGCSqGSIb3 DQEHAaCCC5sEgguXew0KICAgICJhY2Nlc3MiOiB7DQogICAgICAgICJ0b2tlbiI6 IHsNCiAgICAgICAgICAgICJleHBpcmVzIjogIjIwMzgtMDEtMThUMjE6MTQ6MDda IiwNCiAgICAgICAgICAgICJpZCI6ICJwbGFjZWhvbGRlciIsDQogICAgICAgICAg ICAidGVuYW50Ijogew0KICAgICAgICAgICAgICAgICJpZCI6ICJ0ZW5hbnRfaWQx IiwNCiAgICAgICAgICAgICAgICAiZW5hYmxlZCI6IHRydWUsDQogICAgICAgICAg ICAgICAgImRlc2NyaXB0aW9uIjogbnVsbCwNCiAgICAgICAgICAgICAgICAibmFt ZSI6ICJ0ZW5hbnRfbmFtZTEiDQogICAgICAgICAgICB9DQogICAgICAgIH0sDQog ICAgICAgICJzZXJ2aWNlQ2F0YWxvZyI6IFsNCiAgICAgICAgICAgIHsNCiAgICAg ICAgICAgICAgICAiZW5kcG9pbnRzX2xpbmtzIjogW10sDQogICAgICAgICAgICAg ICAgImVuZHBvaW50cyI6IFsNCiAgICAgICAgICAgICAgICAgICAgew0KICAgICAg ICAgICAgICAgICAgICAgICAgImFkbWluVVJMIjogImh0dHA6Ly8xMjcuMC4wLjE6 ODc3Ni92MS82NGI2ZjNmYmNjNTM0MzVlOGE2MGZjZjg5YmI2NjE3YSIsDQogICAg ICAgICAgICAgICAgICAgICAgICAicmVnaW9uIjogInJlZ2lvbk9uZSIsDQogICAg ICAgICAgICAgICAgICAgICAgICAiaW50ZXJuYWxVUkwiOiAiaHR0cDovLzEyNy4w LjAuMTo4Nzc2L3YxLzY0YjZmM2ZiY2M1MzQzNWU4YTYwZmNmODliYjY2MTdhIiwN CiAgICAgICAgICAgICAgICAgICAgICAgICJwdWJsaWNVUkwiOiAiaHR0cDovLzEy Ny4wLjAuMTo4Nzc2L3YxLzY0YjZmM2ZiY2M1MzQzNWU4YTYwZmNmODliYjY2MTdh Ig0KICAgICAgICAgICAgICAgICAgICB9DQogICAgICAgICAgICAgICAgXSwNCiAg ICAgICAgICAgICAgICAidHlwZSI6ICJ2b2x1bWUiLA0KICAgICAgICAgICAgICAg ICJuYW1lIjogInZvbHVtZSINCiAgICAgICAgICAgIH0sDQogICAgICAgICAgICB7 DQogICAgICAgICAgICAgICAgImVuZHBvaW50c19saW5rcyI6IFtdLA0KICAgICAg ICAgICAgICAgICJlbmRwb2ludHMiOiBbDQogICAgICAgICAgICAgICAgICAgIHsN CiAgICAgICAgICAgICAgICAgICAgICAgICJhZG1pblVSTCI6ICJodHRwOi8vMTI3 LjAuMC4xOjkyOTIvdjEiLA0KICAgICAgICAgICAgICAgICAgICAgICAgInJlZ2lv biI6ICJyZWdpb25PbmUiLA0KICAgICAgICAgICAgICAgICAgICAgICAgImludGVy bmFsVVJMIjogImh0dHA6Ly8xMjcuMC4wLjE6OTI5Mi92MSIsDQogICAgICAgICAg ICAgICAgICAgICAgICAicHVibGljVVJMIjogImh0dHA6Ly8xMjcuMC4wLjE6OTI5 Mi92MSINCiAgICAgICAgICAgICAgICAgICAgfQ0KICAgICAgICAgICAgICAgIF0s DQogICAgICAgICAgICAgICAgInR5cGUiOiAiaW1hZ2UiLA0KICAgICAgICAgICAg ICAgICJuYW1lIjogImdsYW5jZSINCiAgICAgICAgICAgIH0sDQogICAgICAgICAg ICB7DQogICAgICAgICAgICAgICAgImVuZHBvaW50c19saW5rcyI6IFtdLA0KICAg ICAgICAgICAgICAgICJlbmRwb2ludHMiOiBbDQogICAgICAgICAgICAgICAgICAg IHsNCiAgICAgICAgICAgICAgICAgICAgICAgICJhZG1pblVSTCI6ICJodHRwOi8v MTI3LjAuMC4xOjg3NzQvdjEuMS82NGI2ZjNmYmNjNTM0MzVlOGE2MGZjZjg5YmI2 NjE3YSIsDQogICAgICAgICAgICAgICAgICAgICAgICAicmVnaW9uIjogInJlZ2lv bk9uZSIsDQogICAgICAgICAgICAgICAgICAgICAgICAiaW50ZXJuYWxVUkwiOiAi aHR0cDovLzEyNy4wLjAuMTo4Nzc0L3YxLjEvNjRiNmYzZmJjYzUzNDM1ZThhNjBm Y2Y4OWJiNjYxN2EiLA0KICAgICAgICAgICAgICAgICAgICAgICAgInB1YmxpY1VS TCI6ICJodHRwOi8vMTI3LjAuMC4xOjg3NzQvdjEuMS82NGI2ZjNmYmNjNTM0MzVl OGE2MGZjZjg5YmI2NjE3YSINCiAgICAgICAgICAgICAgICAgICAgfQ0KICAgICAg ICAgICAgICAgIF0sDQogICAgICAgICAgICAgICAgInR5cGUiOiAiY29tcHV0ZSIs DQogICAgICAgICAgICAgICAgIm5hbWUiOiAibm92YSINCiAgICAgICAgICAgIH0s DQogICAgICAgICAgICB7DQogICAgICAgICAgICAgICAgImVuZHBvaW50c19saW5r cyI6IFtdLA0KICAgICAgICAgICAgICAgICJlbmRwb2ludHMiOiBbDQogICAgICAg ICAgICAgICAgICAgIHsNCiAgICAgICAgICAgICAgICAgICAgICAgICJhZG1pblVS TCI6ICJodHRwOi8vMTI3LjAuMC4xOjM1MzU3L3YyLjAiLA0KICAgICAgICAgICAg ICAgICAgICAgICAgInJlZ2lvbiI6ICJSZWdpb25PbmUiLA0KICAgICAgICAgICAg ICAgICAgICAgICAgImludGVybmFsVVJMIjogImh0dHA6Ly8xMjcuMC4wLjE6MzUz NTcvdjIuMCIsDQogICAgICAgICAgICAgICAgICAgICAgICAicHVibGljVVJMIjog Imh0dHA6Ly8xMjcuMC4wLjE6NTAwMC92Mi4wIg0KICAgICAgICAgICAgICAgICAg ICB9DQogICAgICAgICAgICAgICAgXSwNCiAgICAgICAgICAgICAgICAidHlwZSI6 ICJpZGVudGl0eSIsDQogICAgICAgICAgICAgICAgIm5hbWUiOiAia2V5c3RvbmUi DQogICAgICAgICAgICB9DQogICAgICAgIF0sDQogICAgICAgICJ1c2VyIjogew0K ICAgICAgICAgICAgInVzZXJuYW1lIjogInJldm9rZWRfdXNlcm5hbWUxIiwNCiAg ICAgICAgICAgICJyb2xlc19saW5rcyI6IFsNCiAgICAgICAgICAgICAgICAicm9s ZTEiLA0KICAgICAgICAgICAgICAgICJyb2xlMiINCiAgICAgICAgICAgIF0sDQog ICAgICAgICAgICAiaWQiOiAicmV2b2tlZF91c2VyX2lkMSIsDQogICAgICAgICAg ICAicm9sZXMiOiBbDQogICAgICAgICAgICAgICAgew0KICAgICAgICAgICAgICAg ICAgICAibmFtZSI6ICJyb2xlMSINCiAgICAgICAgICAgICAgICB9LA0KICAgICAg ICAgICAgICAgIHsNCiAgICAgICAgICAgICAgICAgICAgIm5hbWUiOiAicm9sZTIi DQogICAgICAgICAgICAgICAgfQ0KICAgICAgICAgICAgXSwNCiAgICAgICAgICAg ICJuYW1lIjogInJldm9rZWRfdXNlcm5hbWUxIg0KICAgICAgICB9DQogICAgfQ0K fQ0KMYIByjCCAcYCAQEwgaQwgZ4xCjAIBgNVBAUTATUxCzAJBgNVBAYTAlVTMQsw CQYDVQQIEwJDQTESMBAGA1UEBxMJU3Vubnl2YWxlMRIwEAYDVQQKEwlPcGVuU3Rh Y2sxETAPBgNVBAsTCEtleXN0b25lMSUwIwYJKoZIhvcNAQkBFhZrZXlzdG9uZUBv cGVuc3RhY2sub3JnMRQwEgYDVQQDEwtTZWxmIFNpZ25lZAIBETAHBgUrDgMCGjAN BgkqhkiG9w0BAQEFAASCAQAxJMbNZf0/IWg/+/ciWQr9yuW9M48hQdaHcN+t6qvZ OlPev8N1tP8pNTupW9LXt0N8ZU/8AzPLPeRXHqd4lzuDV6ttesfLL3Ag410o4Elb Aum11Y1kDGlbwnaYoD9m07FML1ZfOWJ81Z0CITVGGRX90e+jlYjtnmdshmi2saVl r/Sae6ta52gjptaZE9tOu42uXlfhWNuC0/W7lRuWbWSHZENZWtTHHz2Q+v/HxORf jY3kwSaVEkx9faQ9Npy6J+rSQg+lIMRAYw/rFWedEsP9MzHKBcKTXid0yIQ2ox1r 1Em3WapL1FDpwJtHaaL92WTEQulpxJUcmzPgEd5H78+Q -----END CMS----- keystonemiddleware-4.21.0/examples/pki/cms/auth_token_scoped.pkiz0000666000175100017510000000362113225160624025306 0ustar zuulzuul00000000000000PKIZ_eJylVkuXojgY3edXzL5OnwLUKlnMgrdBwAJDgOx4KK-gVimC_PoJWFVdM93z6DNuNEFu7nfv98i3b-wjawZ0flPs7bj4BmwIV8s8MtdHAotr6khuqhzZ3nxQFFlcKpKr9SqSLDmneVHnMnFtTcq1Ls_DmZzXr6CoS0PsOFnujJxtHmUI9cXqXEaBU5HQGWB1zHc3k0uEC01K-ATZMxIWXRyaNL3BJwAVeLNVe24hqbeQNscq7DeVxm0qaRaU8AwV80QU9qJidomhVyQoronh0fT-jAMkWBTJwS03pfwMG9xGgXkmwbTm0gOmliKV8bSWyswYny-4UKC1vZ0AWhAFPB1pwoNHk0ZvM11sx733P9QsjCptaJcZ9DqFYCz4xOjFETgKcQ3i0NvHgTfFGtxMhDQaJXrhYazHmMenDSbr9KDXwUqXIeWnF1MB37KGVsR3CpAZ-jkR0pFywsRiLLwuEWibreyPvYIY_CmheIvuWhyzlddtyuXVRnAGrEpqbWXOhMtnzhBds0q7OpVXOk00kMasosEfHNXmCSoKp5KbSIjmm8AsnSrqHUErwUSpwYc4ENu7FiYlAou3Flty1-GUMH3Shomt_8gCjDT-Dwsw0phYrHCZGLTC2LQnJk3BZSvpybote7tKxwM6q9KeNmo6c0pRsLdLwTGgAEjFzmmcykE2Zw-YbgxNsA1SkSpfRA0UnEqbRVtTDLddPuYJWcnXmOVCyotn9v0GxnSE-iUbWWQr2rG4xxiFROj5JPAndiw_Ln_d3zPA0TXwq7Z916u-bRC8AiZY-X-cAH-H_An8L-KCT3URXNiTun8v2M_0AhO9QD-8U20_i6vJzqzyKsIALeVeqZ-AdyC2p9cgCWj7n7xXRnbz3hoiLqpIYwukjASbB_bgDk7gzyMUdaRxmo1Ky6hij1BWwLL7Lmg5CXcjQXZKhMVL0twtBiMlEo7Ue-zX3dQ44pXHperxag3azbmNLJjA6Dh3hpSzZlFvfUl18F8q7p_cAL8S78_CBZ_xHvjJHtYj69QQx8QZQqE_Jc3l3q14bmqiu1B-d8m5JqHMs470Q763yYwwQPbC2MK_AE5As7Hlexem3aQZ-AfRBlahvHNj4ZTz7ieObEdHwFdLfsGRT3DwHV3mo6Y_Rfy_VaHf2arEagWytSmCX8n7aUqx4cJmBLf7YbA0F7oLHTYDF_TDkSx0xhE2zcPp91jOrJlMU2pcU_EO8D6Fbqzb0D8zOLM-IZ4J-ugZ429Y3lnTejwYwAMemHBsOrn9u9JseOJPy77YOx1gf1bnnc1k4wfyHnN_Lul38AmEsdiHvGhHUB4qRZHS43h36EAeu11O5r1SSVDOHSxLPpKQ3yuDZN7XEZIoRrZ77hQ3UrHrQq0zVRdpW1uWDCDxvib3tunPcJscqMBygNoe7DRp-vNa6-hLypT3Z14RCedeQ9LLHfiMFO1CwYfy9tbvYPf1qlPLekHeSEiHzGDN1ZevI1B6B2Lpbh5sz-2Alk8nqVp3QSToG6g7J8IACYtI-8ndSHW_HqLJQHYlLc81aX3lauEoClh6VuT6CVmW_Xx4cUKMVpistrF-8znERbl2fHvMwv1Zg7ipXuENxJolYFGlM8EwxIGkw0pI51zZPri711NwFfOy9-h2eDMzXGe6HAtPSqjDtyZSZq0lXBUA-dVBNQ9FszxyDqe-1DG0sq2P0nb_-vCoLDptv3s43RpcnC1-vVPWh6J_uR7D1-xVklHsgVJt1t5DSq3mbKql9HradSuMTCoWQ_HywKdLk7-01l5nbWlbqI8WXjxrwgYhdFwe0MF9AUVO9lb9XD9JQ2Ku-TjaCYawm8_np5i1w2pmP9qSdKH5rttzT12SxPlSXOs3xXe0U6N6BnD2jNsSSlK1ffBnwirm-se3_a7NcLsk-e-_g-lCqznq98vtH9MPoOI=keystonemiddleware-4.21.0/examples/pki/cms/auth_v3_token_scoped.pem0000666000175100017510000001433013225160624025521 0ustar zuulzuul00000000000000-----BEGIN CMS----- MIISOAYJKoZIhvcNAQcCoIISKTCCEiUCAQExDTALBglghkgBZQMEAgEwghA9Bgkq hkiG9w0BBwGgghAuBIIQKnsNCiAgICAidG9rZW4iOiB7DQogICAgICAgICJhdWRp dF9pZHMiOiBbDQogICAgICAgICAgICAiU0xJWGxYUVVRWldVaTlWSnJxZFhxQSIN CiAgICAgICAgXSwNCiAgICAgICAgIm1ldGhvZHMiOiBbDQogICAgICAgICAgICAi cGFzc3dvcmQiDQogICAgICAgIF0sDQogICAgICAgICJyb2xlcyI6IFsNCiAgICAg ICAgICAgIHsNCiAgICAgICAgICAgICAgICAibmFtZSI6ICJyb2xlMSINCiAgICAg ICAgICAgIH0sDQogICAgICAgICAgICB7DQogICAgICAgICAgICAgICAgIm5hbWUi OiAicm9sZTIiDQogICAgICAgICAgICB9DQogICAgICAgIF0sDQogICAgICAgICJl eHBpcmVzX2F0IjogIjIwMzgtMDEtMThUMjE6MTQ6MDdaIiwNCiAgICAgICAgInBy b2plY3QiOiB7DQogICAgICAgICAgICAiaWQiOiAidGVuYW50X2lkMSIsDQogICAg ICAgICAgICAiZG9tYWluIjogew0KICAgICAgICAgICAgICAgICJpZCI6ICJkb21h aW5faWQxIiwNCiAgICAgICAgICAgICAgICAibmFtZSI6ICJkb21haW5fbmFtZTEi DQogICAgICAgICAgICB9LA0KICAgICAgICAgICAgImVuYWJsZWQiOiB0cnVlLA0K ICAgICAgICAgICAgImRlc2NyaXB0aW9uIjogbnVsbCwNCiAgICAgICAgICAgICJu YW1lIjogInRlbmFudF9uYW1lMSINCiAgICAgICAgfSwNCiAgICAgICAgImNhdGFs b2ciOiBbDQogICAgICAgICAgICB7DQogICAgICAgICAgICAgICAgImVuZHBvaW50 cyI6IFsNCiAgICAgICAgICAgICAgICAgICAgew0KICAgICAgICAgICAgICAgICAg ICAgICAgImludGVyZmFjZSI6ICJhZG1pbiIsDQogICAgICAgICAgICAgICAgICAg ICAgICAidXJsIjogImh0dHA6Ly8xMjcuMC4wLjE6ODc3Ni92MS82NGI2ZjNmYmNj NTM0MzVlOGE2MGZjZjg5YmI2NjE3YSIsDQogICAgICAgICAgICAgICAgICAgICAg ICAicmVnaW9uIjogInJlZ2lvbk9uZSINCiAgICAgICAgICAgICAgICAgICAgfSwN CiAgICAgICAgICAgICAgICAgICAgew0KICAgICAgICAgICAgICAgICAgICAgICAg ImludGVyZmFjZSI6ICJpbnRlcm5hbCIsDQogICAgICAgICAgICAgICAgICAgICAg ICAidXJsIjogImh0dHA6Ly8xMjcuMC4wLjE6ODc3Ni92MS82NGI2ZjNmYmNjNTM0 MzVlOGE2MGZjZjg5YmI2NjE3YSIsDQogICAgICAgICAgICAgICAgICAgICAgICAi cmVnaW9uIjogInJlZ2lvbk9uZSINCiAgICAgICAgICAgICAgICAgICAgfSwNCiAg ICAgICAgICAgICAgICAgICAgew0KICAgICAgICAgICAgICAgICAgICAgICAgImlu dGVyZmFjZSI6ICJwdWJsaWMiLA0KICAgICAgICAgICAgICAgICAgICAgICAgInVy bCI6ICJodHRwOi8vMTI3LjAuMC4xOjg3NzYvdjEvNjRiNmYzZmJjYzUzNDM1ZThh NjBmY2Y4OWJiNjYxN2EiLA0KICAgICAgICAgICAgICAgICAgICAgICAgInJlZ2lv biI6ICJyZWdpb25PbmUiDQogICAgICAgICAgICAgICAgICAgIH0NCiAgICAgICAg ICAgICAgICBdLA0KICAgICAgICAgICAgICAgICJ0eXBlIjogInZvbHVtZSIsDQog ICAgICAgICAgICAgICAgIm5hbWUiOiAidm9sdW1lIg0KICAgICAgICAgICAgfSwN CiAgICAgICAgICAgIHsNCiAgICAgICAgICAgICAgICAiZW5kcG9pbnRzIjogWw0K ICAgICAgICAgICAgICAgICAgICB7DQogICAgICAgICAgICAgICAgICAgICAgICAi aW50ZXJmYWNlIjogImFkbWluIiwNCiAgICAgICAgICAgICAgICAgICAgICAgICJ1 cmwiOiAiaHR0cDovLzEyNy4wLjAuMTo5MjkyL3YxIiwNCiAgICAgICAgICAgICAg ICAgICAgICAgICJyZWdpb24iOiAicmVnaW9uT25lIg0KICAgICAgICAgICAgICAg ICAgICB9LA0KICAgICAgICAgICAgICAgICAgICB7DQogICAgICAgICAgICAgICAg ICAgICAgICAiaW50ZXJmYWNlIjogImludGVybmFsIiwNCiAgICAgICAgICAgICAg ICAgICAgICAgICJ1cmwiOiAiaHR0cDovLzEyNy4wLjAuMTo5MjkyL3YxIiwNCiAg ICAgICAgICAgICAgICAgICAgICAgICJyZWdpb24iOiAicmVnaW9uT25lIg0KICAg ICAgICAgICAgICAgICAgICB9LA0KICAgICAgICAgICAgICAgICAgICB7DQogICAg ICAgICAgICAgICAgICAgICAgICAiaW50ZXJmYWNlIjogInB1YmxpYyIsDQogICAg ICAgICAgICAgICAgICAgICAgICAidXJsIjogImh0dHA6Ly8xMjcuMC4wLjE6OTI5 Mi92MSIsDQogICAgICAgICAgICAgICAgICAgICAgICAicmVnaW9uIjogInJlZ2lv bk9uZSINCiAgICAgICAgICAgICAgICAgICAgfQ0KICAgICAgICAgICAgICAgIF0s DQogICAgICAgICAgICAgICAgInR5cGUiOiAiaW1hZ2UiLA0KICAgICAgICAgICAg ICAgICJuYW1lIjogImdsYW5jZSINCiAgICAgICAgICAgIH0sDQogICAgICAgICAg ICB7DQogICAgICAgICAgICAgICAgImVuZHBvaW50cyI6IFsNCiAgICAgICAgICAg ICAgICAgICAgew0KICAgICAgICAgICAgICAgICAgICAgICAgImludGVyZmFjZSI6 ICJhZG1pbiIsDQogICAgICAgICAgICAgICAgICAgICAgICAidXJsIjogImh0dHA6 Ly8xMjcuMC4wLjE6ODc3NC92MS4xLzY0YjZmM2ZiY2M1MzQzNWU4YTYwZmNmODli YjY2MTdhIiwNCiAgICAgICAgICAgICAgICAgICAgICAgICJyZWdpb24iOiAicmVn aW9uT25lIg0KICAgICAgICAgICAgICAgICAgICB9LA0KICAgICAgICAgICAgICAg ICAgICB7DQogICAgICAgICAgICAgICAgICAgICAgICAiaW50ZXJmYWNlIjogImlu dGVybmFsIiwNCiAgICAgICAgICAgICAgICAgICAgICAgICJ1cmwiOiAiaHR0cDov LzEyNy4wLjAuMTo4Nzc0L3YxLjEvNjRiNmYzZmJjYzUzNDM1ZThhNjBmY2Y4OWJi NjYxN2EiLA0KICAgICAgICAgICAgICAgICAgICAgICAgInJlZ2lvbiI6ICJyZWdp b25PbmUiDQogICAgICAgICAgICAgICAgICAgIH0sDQogICAgICAgICAgICAgICAg ICAgIHsNCiAgICAgICAgICAgICAgICAgICAgICAgICJpbnRlcmZhY2UiOiAicHVi bGljIiwNCiAgICAgICAgICAgICAgICAgICAgICAgICJ1cmwiOiAiaHR0cDovLzEy Ny4wLjAuMTo4Nzc0L3YxLjEvNjRiNmYzZmJjYzUzNDM1ZThhNjBmY2Y4OWJiNjYx N2EiLA0KICAgICAgICAgICAgICAgICAgICAgICAgInJlZ2lvbiI6ICJyZWdpb25P bmUiDQogICAgICAgICAgICAgICAgICAgIH0NCiAgICAgICAgICAgICAgICBdLA0K ICAgICAgICAgICAgICAgICJ0eXBlIjogImNvbXB1dGUiLA0KICAgICAgICAgICAg ICAgICJuYW1lIjogIm5vdmEiDQogICAgICAgICAgICB9LA0KICAgICAgICAgICAg ew0KICAgICAgICAgICAgICAgICJlbmRwb2ludHMiOiBbDQogICAgICAgICAgICAg ICAgICAgIHsNCiAgICAgICAgICAgICAgICAgICAgICAgICJpbnRlcmZhY2UiOiAi YWRtaW4iLA0KICAgICAgICAgICAgICAgICAgICAgICAgInVybCI6ICJodHRwOi8v MTI3LjAuMC4xOjM1MzU3L3YzIiwNCiAgICAgICAgICAgICAgICAgICAgICAgICJy ZWdpb24iOiAiUmVnaW9uT25lIg0KICAgICAgICAgICAgICAgICAgICB9LA0KICAg ICAgICAgICAgICAgICAgICB7DQogICAgICAgICAgICAgICAgICAgICAgICAiaW50 ZXJmYWNlIjogImludGVybmFsIiwNCiAgICAgICAgICAgICAgICAgICAgICAgICJ1 cmwiOiAiaHR0cDovLzEyNy4wLjAuMTozNTM1Ny92MyIsDQogICAgICAgICAgICAg ICAgICAgICAgICAicmVnaW9uIjogIlJlZ2lvbk9uZSINCiAgICAgICAgICAgICAg ICAgICAgfSwNCiAgICAgICAgICAgICAgICAgICAgew0KICAgICAgICAgICAgICAg ICAgICAgICAgImludGVyZmFjZSI6ICJwdWJsaWMiLA0KICAgICAgICAgICAgICAg ICAgICAgICAgInVybCI6ICJodHRwOi8vMTI3LjAuMC4xOjUwMDAvdjMiLA0KICAg ICAgICAgICAgICAgICAgICAgICAgInJlZ2lvbiI6ICJSZWdpb25PbmUiDQogICAg ICAgICAgICAgICAgICAgIH0NCiAgICAgICAgICAgICAgICBdLA0KICAgICAgICAg ICAgICAgICJ0eXBlIjogImlkZW50aXR5IiwNCiAgICAgICAgICAgICAgICAibmFt ZSI6ICJrZXlzdG9uZSINCiAgICAgICAgICAgIH0NCiAgICAgICAgXSwNCiAgICAg ICAgInVzZXIiOiB7DQogICAgICAgICAgICAiZG9tYWluIjogew0KICAgICAgICAg ICAgICAgICJpZCI6ICJkb21haW5faWQxIiwNCiAgICAgICAgICAgICAgICAibmFt ZSI6ICJkb21haW5fbmFtZTEiDQogICAgICAgICAgICB9LA0KICAgICAgICAgICAg Im5hbWUiOiAidXNlcl9uYW1lMSIsDQogICAgICAgICAgICAiaWQiOiAidXNlcl9p ZDEiDQogICAgICAgIH0NCiAgICB9DQp9DQoxggHOMIIBygIBATCBpDCBnjEKMAgG A1UEBRMBNTELMAkGA1UEBhMCVVMxCzAJBgNVBAgTAkNBMRIwEAYDVQQHEwlTdW5u eXZhbGUxEjAQBgNVBAoTCU9wZW5TdGFjazERMA8GA1UECxMIS2V5c3RvbmUxJTAj BgkqhkiG9w0BCQEWFmtleXN0b25lQG9wZW5zdGFjay5vcmcxFDASBgNVBAMTC1Nl bGYgU2lnbmVkAgERMAsGCWCGSAFlAwQCATANBgkqhkiG9w0BAQEFAASCAQBBvzoh 0iSPMQhuRCAtTG3cPhyewvf554MPjbGQnu8mYmmfyxl7gMmWkTAmyckAsSv4mS6/ 4SQj9WCn4T1lFkhUz7WWjCwt6fWWp3mzF8Nl/kMsJKDwlxDGbPzsyewXIUsw11sz q/Qxs7qGxQ1vYWnaWQ3hC3oZw7cOswKRJicdP439iVPvfqR9CDbK55sPP+ewZRgQ YJ3Uc/xDizxepudFJj9+VHKceA37/sVK0ataNe2uHLHwVBYPwOppMckP169QBw8x QYh9h+kcOAyZ5psiUzCpLKnlMiYDrVcTGxnTeiVHxKXxj/MERNhR1Y4lEr0ZHJ+p Y6p3FBP2VUCefaRh -----END CMS----- keystonemiddleware-4.21.0/examples/pki/cms/revocation_list.json0000666000175100017510000000077613225160624025020 0ustar zuulzuul00000000000000{ "revoked": [ { "expires": "2112-08-14T17:58:48Z", "id": "dc57ea171d2f93e4ff5fa01fe5711f2a" }, { "expires": "2112-08-14T17:58:48Z", "id": "4948fb46f88c41af90b65213a48baef7" }, { "expires": "2112-08-14T17:58:48Z", "id": "dc57ea171d2f93e4ff5fa01fe5711f2a" }, { "expires": "2112-08-14T17:58:48Z", "id": "4948fb46f88c41af90b65213a48baef7" } ] } keystonemiddleware-4.21.0/examples/pki/cms/auth_token_scoped_expired.pem0000666000175100017510000001117713225160624026637 0ustar zuulzuul00000000000000-----BEGIN CMS----- MIINhwYJKoZIhvcNAQcCoIINeDCCDXQCAQExCTAHBgUrDgMCGjCCC5QGCSqGSIb3 DQEHAaCCC4UEgguBew0KICAgICJhY2Nlc3MiOiB7DQogICAgICAgICJ0b2tlbiI6 IHsNCiAgICAgICAgICAgICJleHBpcmVzIjogIjIwMTAtMDYtMDJUMTQ6NDc6MzRa IiwNCiAgICAgICAgICAgICJpZCI6ICJwbGFjZWhvbGRlciIsDQogICAgICAgICAg ICAidGVuYW50Ijogew0KICAgICAgICAgICAgICAgICJpZCI6ICJ0ZW5hbnRfaWQx IiwNCiAgICAgICAgICAgICAgICAiZW5hYmxlZCI6IHRydWUsDQogICAgICAgICAg ICAgICAgImRlc2NyaXB0aW9uIjogbnVsbCwNCiAgICAgICAgICAgICAgICAibmFt ZSI6ICJ0ZW5hbnRfbmFtZTEiDQogICAgICAgICAgICB9DQogICAgICAgIH0sDQog ICAgICAgICJzZXJ2aWNlQ2F0YWxvZyI6IFsNCiAgICAgICAgICAgIHsNCiAgICAg ICAgICAgICAgICAiZW5kcG9pbnRzX2xpbmtzIjogW10sDQogICAgICAgICAgICAg ICAgImVuZHBvaW50cyI6IFsNCiAgICAgICAgICAgICAgICAgICAgew0KICAgICAg ICAgICAgICAgICAgICAgICAgImFkbWluVVJMIjogImh0dHA6Ly8xMjcuMC4wLjE6 ODc3Ni92MS82NGI2ZjNmYmNjNTM0MzVlOGE2MGZjZjg5YmI2NjE3YSIsDQogICAg ICAgICAgICAgICAgICAgICAgICAicmVnaW9uIjogInJlZ2lvbk9uZSIsDQogICAg ICAgICAgICAgICAgICAgICAgICAiaW50ZXJuYWxVUkwiOiAiaHR0cDovLzEyNy4w LjAuMTo4Nzc2L3YxLzY0YjZmM2ZiY2M1MzQzNWU4YTYwZmNmODliYjY2MTdhIiwN CiAgICAgICAgICAgICAgICAgICAgICAgICJwdWJsaWNVUkwiOiAiaHR0cDovLzEy Ny4wLjAuMTo4Nzc2L3YxLzY0YjZmM2ZiY2M1MzQzNWU4YTYwZmNmODliYjY2MTdh Ig0KICAgICAgICAgICAgICAgICAgICB9DQogICAgICAgICAgICAgICAgXSwNCiAg ICAgICAgICAgICAgICAidHlwZSI6ICJ2b2x1bWUiLA0KICAgICAgICAgICAgICAg ICJuYW1lIjogInZvbHVtZSINCiAgICAgICAgICAgIH0sDQogICAgICAgICAgICB7 DQogICAgICAgICAgICAgICAgImVuZHBvaW50c19saW5rcyI6IFtdLA0KICAgICAg ICAgICAgICAgICJlbmRwb2ludHMiOiBbDQogICAgICAgICAgICAgICAgICAgIHsN CiAgICAgICAgICAgICAgICAgICAgICAgICJhZG1pblVSTCI6ICJodHRwOi8vMTI3 LjAuMC4xOjkyOTIvdjEiLA0KICAgICAgICAgICAgICAgICAgICAgICAgInJlZ2lv biI6ICJyZWdpb25PbmUiLA0KICAgICAgICAgICAgICAgICAgICAgICAgImludGVy bmFsVVJMIjogImh0dHA6Ly8xMjcuMC4wLjE6OTI5Mi92MSIsDQogICAgICAgICAg ICAgICAgICAgICAgICAicHVibGljVVJMIjogImh0dHA6Ly8xMjcuMC4wLjE6OTI5 Mi92MSINCiAgICAgICAgICAgICAgICAgICAgfQ0KICAgICAgICAgICAgICAgIF0s DQogICAgICAgICAgICAgICAgInR5cGUiOiAiaW1hZ2UiLA0KICAgICAgICAgICAg ICAgICJuYW1lIjogImdsYW5jZSINCiAgICAgICAgICAgIH0sDQogICAgICAgICAg ICB7DQogICAgICAgICAgICAgICAgImVuZHBvaW50c19saW5rcyI6IFtdLA0KICAg ICAgICAgICAgICAgICJlbmRwb2ludHMiOiBbDQogICAgICAgICAgICAgICAgICAg IHsNCiAgICAgICAgICAgICAgICAgICAgICAgICJhZG1pblVSTCI6ICJodHRwOi8v MTI3LjAuMC4xOjg3NzQvdjEuMS82NGI2ZjNmYmNjNTM0MzVlOGE2MGZjZjg5YmI2 NjE3YSIsDQogICAgICAgICAgICAgICAgICAgICAgICAicmVnaW9uIjogInJlZ2lv bk9uZSIsDQogICAgICAgICAgICAgICAgICAgICAgICAiaW50ZXJuYWxVUkwiOiAi aHR0cDovLzEyNy4wLjAuMTo4Nzc0L3YxLjEvNjRiNmYzZmJjYzUzNDM1ZThhNjBm Y2Y4OWJiNjYxN2EiLA0KICAgICAgICAgICAgICAgICAgICAgICAgInB1YmxpY1VS TCI6ICJodHRwOi8vMTI3LjAuMC4xOjg3NzQvdjEuMS82NGI2ZjNmYmNjNTM0MzVl OGE2MGZjZjg5YmI2NjE3YSINCiAgICAgICAgICAgICAgICAgICAgfQ0KICAgICAg ICAgICAgICAgIF0sDQogICAgICAgICAgICAgICAgInR5cGUiOiAiY29tcHV0ZSIs DQogICAgICAgICAgICAgICAgIm5hbWUiOiAibm92YSINCiAgICAgICAgICAgIH0s DQogICAgICAgICAgICB7DQogICAgICAgICAgICAgICAgImVuZHBvaW50c19saW5r cyI6IFtdLA0KICAgICAgICAgICAgICAgICJlbmRwb2ludHMiOiBbDQogICAgICAg ICAgICAgICAgICAgIHsNCiAgICAgICAgICAgICAgICAgICAgICAgICJhZG1pblVS TCI6ICJodHRwOi8vMTI3LjAuMC4xOjM1MzU3L3YyLjAiLA0KICAgICAgICAgICAg ICAgICAgICAgICAgInJlZ2lvbiI6ICJSZWdpb25PbmUiLA0KICAgICAgICAgICAg ICAgICAgICAgICAgImludGVybmFsVVJMIjogImh0dHA6Ly8xMjcuMC4wLjE6MzUz NTcvdjIuMCIsDQogICAgICAgICAgICAgICAgICAgICAgICAicHVibGljVVJMIjog Imh0dHA6Ly8xMjcuMC4wLjE6NTAwMC92Mi4wIg0KICAgICAgICAgICAgICAgICAg ICB9DQogICAgICAgICAgICAgICAgXSwNCiAgICAgICAgICAgICAgICAidHlwZSI6 ICJpZGVudGl0eSIsDQogICAgICAgICAgICAgICAgIm5hbWUiOiAia2V5c3RvbmUi DQogICAgICAgICAgICB9DQogICAgICAgIF0sDQogICAgICAgICJ1c2VyIjogew0K ICAgICAgICAgICAgInVzZXJuYW1lIjogInVzZXJfbmFtZTEiLA0KICAgICAgICAg ICAgInJvbGVzX2xpbmtzIjogWw0KICAgICAgICAgICAgICAgICJyb2xlMSIsDQog ICAgICAgICAgICAgICAgInJvbGUyIg0KICAgICAgICAgICAgXSwNCiAgICAgICAg ICAgICJpZCI6ICJ1c2VyX2lkMSIsDQogICAgICAgICAgICAicm9sZXMiOiBbDQog ICAgICAgICAgICAgICAgew0KICAgICAgICAgICAgICAgICAgICAibmFtZSI6ICJy b2xlMSINCiAgICAgICAgICAgICAgICB9LA0KICAgICAgICAgICAgICAgIHsNCiAg ICAgICAgICAgICAgICAgICAgIm5hbWUiOiAicm9sZTIiDQogICAgICAgICAgICAg ICAgfQ0KICAgICAgICAgICAgXSwNCiAgICAgICAgICAgICJuYW1lIjogInVzZXJf bmFtZTEiDQogICAgICAgIH0NCiAgICB9DQp9DQoxggHKMIIBxgIBATCBpDCBnjEK MAgGA1UEBRMBNTELMAkGA1UEBhMCVVMxCzAJBgNVBAgTAkNBMRIwEAYDVQQHEwlT dW5ueXZhbGUxEjAQBgNVBAoTCU9wZW5TdGFjazERMA8GA1UECxMIS2V5c3RvbmUx JTAjBgkqhkiG9w0BCQEWFmtleXN0b25lQG9wZW5zdGFjay5vcmcxFDASBgNVBAMT C1NlbGYgU2lnbmVkAgERMAcGBSsOAwIaMA0GCSqGSIb3DQEBAQUABIIBALYxBjRE hecjo98fUdki3cwcpGU8zY8XHQa4x15WGkPxkI1HwSYaId/WjrOWP2CxmT3vVe7Z lqV2a0YmdPx9zdDm09VmoiZr3HxYaNzXztT817dECYINCgz33EnansIyPHG2hjOR 4Gt7R26MXf+AIRiCNuCFZPnHI1pfCbwuky9/iBokvE9mThA+bVrUPZd/2+jp4s3B n3+fbC+FCoZ5t522wGgEtVyMNvC90Wvvuf2mx7baXNo4/0ZG8C86lT+qmMe22zlf +DxmJl149p419zdv6rzTU7p2OeTBnkdw1GsEqKyvtHYxzAjLYjiJo6jyaERXBaLm /J7ZRSBmhHoLuWk= -----END CMS----- keystonemiddleware-4.21.0/releasenotes/0000775000175100017510000000000013225161130020204 5ustar zuulzuul00000000000000keystonemiddleware-4.21.0/releasenotes/source/0000775000175100017510000000000013225161130021504 5ustar zuulzuul00000000000000keystonemiddleware-4.21.0/releasenotes/source/conf.py0000666000175100017510000002212013225160624023011 0ustar zuulzuul00000000000000# Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or # implied. # See the License for the specific language governing permissions and # limitations under the License. # keystonemiddleware Release Notes documentation build configuration file, # created by sphinx-quickstart on Tue Nov 3 17:40:50 2015. # # This file is execfile()d with the current directory set to its # containing dir. # # Note that not all possible configuration values are present in this # autogenerated file. # # All configuration values have a default; values that are commented out # serve to show the default. # If extensions (or modules to document with autodoc) are in another directory, # add these directories to sys.path here. If the directory is relative to the # documentation root, use os.path.abspath to make it absolute, like shown here. # sys.path.insert(0, os.path.abspath('.')) # -- General configuration ------------------------------------------------ # If your documentation needs a minimal Sphinx version, state it here. # needs_sphinx = '1.0' # Add any Sphinx extension module names here, as strings. They can be # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom # ones. extensions = [ 'openstackdocstheme', 'reno.sphinxext', ] # Add any paths that contain templates here, relative to this directory. templates_path = ['_templates'] # The suffix of source filenames. source_suffix = '.rst' # The encoding of source files. # source_encoding = 'utf-8-sig' # The master toctree document. master_doc = 'index' # General information about the project. project = u'keystonemiddleware Release Notes' copyright = u'2015, Keystone Developers' # Release notes do not need a version number in the title, they # cover multiple releases. # The full version, including alpha/beta/rc tags. release = '' # The short X.Y version. version = '' # The language for content autogenerated by Sphinx. Refer to documentation # for a list of supported languages. # language = None # There are two options for replacing |today|: either, you set today to some # non-false value, then it is used: # today = '' # Else, today_fmt is used as the format for a strftime call. # today_fmt = '%B %d, %Y' # List of patterns, relative to source directory, that match files and # directories to ignore when looking for source files. exclude_patterns = [] # The reST default role (used for this markup: `text`) to use for all # documents. # default_role = None # If true, '()' will be appended to :func: etc. cross-reference text. # add_function_parentheses = True # If true, the current module name will be prepended to all description # unit titles (such as .. function::). # add_module_names = True # If true, sectionauthor and moduleauthor directives will be shown in the # output. They are ignored by default. # show_authors = False # The name of the Pygments (syntax highlighting) style to use. pygments_style = 'sphinx' # A list of ignored prefixes for module index sorting. # modindex_common_prefix = [] # If true, keep warnings as "system message" paragraphs in the built documents. # keep_warnings = False # -- Options for HTML output ---------------------------------------------- # The theme to use for HTML and HTML Help pages. See the documentation for # a list of builtin themes. html_theme = 'openstackdocs' # Theme options are theme-specific and customize the look and feel of a theme # further. For a list of options available for each theme, see the # documentation. # html_theme_options = {} # Add any paths that contain custom themes here, relative to this directory. # html_theme_path = [] # The name for this set of Sphinx documents. If None, it defaults to # " v documentation". # html_title = None # A shorter title for the navigation bar. Default is the same as html_title. # html_short_title = None # The name of an image file (relative to this directory) to place at the top # of the sidebar. # html_logo = None # The name of an image file (within the static path) to use as favicon of the # docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32 # pixels large. # html_favicon = None # Add any paths that contain custom static files (such as style sheets) here, # relative to this directory. They are copied after the builtin static files, # so a file named "default.css" will overwrite the builtin "default.css". html_static_path = ['_static'] # Add any extra paths that contain custom files (such as robots.txt or # .htaccess) here, relative to this directory. These files are copied # directly to the root of the documentation. # html_extra_path = [] # If not '', a 'Last updated on:' timestamp is inserted at every page bottom, # using the given strftime format. # html_last_updated_fmt = '%b %d, %Y' html_last_updated_fmt = '%Y-%m-%d %H:%M' # If true, SmartyPants will be used to convert quotes and dashes to # typographically correct entities. # html_use_smartypants = True # Custom sidebar templates, maps document names to template names. # html_sidebars = {} # Additional templates that should be rendered to pages, maps page names to # template names. # html_additional_pages = {} # If false, no module index is generated. # html_domain_indices = True # If false, no index is generated. # html_use_index = True # If true, the index is split into individual pages for each letter. # html_split_index = False # If true, links to the reST sources are added to the pages. # html_show_sourcelink = True # If true, "Created using Sphinx" is shown in the HTML footer. Default is True. # html_show_sphinx = True # If true, "(C) Copyright ..." is shown in the HTML footer. Default is True. # html_show_copyright = True # If true, an OpenSearch description file will be output, and all pages will # contain a tag referring to it. The value of this option must be the # base URL from which the finished HTML is served. # html_use_opensearch = '' # This is the file name suffix for HTML files (e.g. ".xhtml"). # html_file_suffix = None # Output file base name for HTML help builder. htmlhelp_basename = 'keystonemiddlewareReleaseNotesdoc' # -- Options for LaTeX output --------------------------------------------- latex_elements = { # The paper size ('letterpaper' or 'a4paper'). # 'papersize': 'letterpaper', # The font size ('10pt', '11pt' or '12pt'). # 'pointsize': '10pt', # Additional stuff for the LaTeX preamble. # 'preamble': '', } # Grouping the document tree into LaTeX files. List of tuples # (source start file, target name, title, # author, documentclass [howto, manual, or own class]). latex_documents = [ ('index', 'keystonemiddlewareReleaseNotes.tex', u'keystonemiddleware Release Notes Documentation', u'Keystone Developers', 'manual'), ] # The name of an image file (relative to this directory) to place at the top of # the title page. # latex_logo = None # For "manual" documents, if this is true, then toplevel headings are parts, # not chapters. # latex_use_parts = False # If true, show page references after internal links. # latex_show_pagerefs = False # If true, show URL addresses after external links. # latex_show_urls = False # Documents to append as an appendix to all manuals. # latex_appendices = [] # If false, no module index is generated. # latex_domain_indices = True # -- Options for manual page output --------------------------------------- # One entry per manual page. List of tuples # (source start file, name, description, authors, manual section). man_pages = [ ('index', 'keystonemiddlewarereleasenotes', u'keystonemiddleware Release Notes Documentation', [u'Keystone Developers'], 1) ] # If true, show URL addresses after external links. # man_show_urls = False # -- Options for Texinfo output ------------------------------------------- # Grouping the document tree into Texinfo files. List of tuples # (source start file, target name, title, author, # dir menu entry, description, category) texinfo_documents = [ ('index', 'keystonemiddlewareReleaseNotes', u'keystonemiddleware Release Notes Documentation', u'Keystone Developers', 'keystonemiddlewareReleaseNotes', 'Middleware for the OpenStack Identity service.', 'Miscellaneous'), ] # Documents to append as an appendix to all manuals. # texinfo_appendices = [] # If false, no module index is generated. # texinfo_domain_indices = True # How to display URL addresses: 'footnote', 'no', or 'inline'. # texinfo_show_urls = 'footnote' # If true, do not generate a @detailmenu in the "Top" node's menu. # texinfo_no_detailmenu = False # -- Options for Internationalization output ------------------------------ locale_dirs = ['locale/'] # -- Options for openstackdocstheme ------------------------------------------- repository_name = 'openstack/keystonemiddleware' bug_project = 'keystonemiddleware' bug_tag = '' keystonemiddleware-4.21.0/releasenotes/source/locale/0000775000175100017510000000000013225161130022743 5ustar zuulzuul00000000000000keystonemiddleware-4.21.0/releasenotes/source/locale/en_GB/0000775000175100017510000000000013225161130023715 5ustar zuulzuul00000000000000keystonemiddleware-4.21.0/releasenotes/source/locale/en_GB/LC_MESSAGES/0000775000175100017510000000000013225161130025502 5ustar zuulzuul00000000000000keystonemiddleware-4.21.0/releasenotes/source/locale/en_GB/LC_MESSAGES/releasenotes.po0000666000175100017510000004406513225160643030556 0ustar zuulzuul00000000000000# Andi Chandler , 2017. #zanata msgid "" msgstr "" "Project-Id-Version: keystonemiddleware Release Notes\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2017-12-20 21:10+0000\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "PO-Revision-Date: 2017-12-23 11:14+0000\n" "Last-Translator: Andi Chandler \n" "Language-Team: English (United Kingdom)\n" "Language: en-GB\n" "X-Generator: Zanata 3.9.6\n" "Plural-Forms: nplurals=2; plural=(n != 1)\n" msgid "4.1.0" msgstr "4.1.0" msgid "4.12.0" msgstr "4.12.0" msgid "4.16.0" msgstr "4.16.0" msgid "4.18.0" msgstr "4.18.0" msgid "4.19.0-4" msgstr "4.19.0-4" msgid "4.2.0" msgstr "4.2.0" msgid "4.3.0" msgstr "4.3.0" msgid "4.5.0" msgstr "4.5.0" msgid "4.6.0" msgstr "4.6.0" msgid "" "A new configuration option for the s3token middleware called auth_uri can be " "used to set the URI to be used for authentication. This replaces auth_host, " "auth_port, and auth_protocol." msgstr "" "A new configuration option for the s3token middleware called auth_uri can be " "used to set the URI to be used for authentication. This replaces auth_host, " "auth_port, and auth_protocol." msgid "Add the `X_IS_ADMIN_PROJECT` header." msgstr "Add the `X_IS_ADMIN_PROJECT` header." msgid "" "Added the `X_IS_ADMIN_PROJECT` header to authenticated headers. This has the " "string value of 'True' or 'False' and can be used to enforce admin project " "policies." msgstr "" "Added the `X_IS_ADMIN_PROJECT` header to authenticated headers. This has the " "string value of 'True' or 'False' and can be used to enforce admin project " "policies." msgid "" "AuthToken middleware will now allow fetching an expired token when a valid " "service token is present. This service token must contain any one of the " "roles specified in ``service_token_roles``." msgstr "" "AuthToken middleware will now allow fetching an expired token when a valid " "service token is present. This service token must contain any one of the " "roles specified in ``service_token_roles``." msgid "Bug Fixes" msgstr "Bug Fixes" msgid "Current Series Release Notes" msgstr "Current Series Release Notes" msgid "Deprecation Notes" msgstr "Deprecation Notes" msgid "" "Fetching expired tokens when using a valid service token is now allowed. " "This will help with long running operations that must continue between " "services longer than the original expiry of the token." msgstr "" "Fetching expired tokens when using a valid service token is now allowed. " "This will help with long running operations that must continue between " "services longer than the original expiry of the token." msgid "" "For backwards compatibility the ``service_token_roles_required`` option in " "``[keystone_authtoken]`` was added. The option defaults to ``False`` and has " "been immediately deprecated. This will allow the current behaviour that " "service tokens are validated but not checked for roles to continue. The " "option should be set to ``True`` as soon as possible. The option will " "default to ``True`` in a future release." msgstr "" "For backwards compatibility the ``service_token_roles_required`` option in " "``[keystone_authtoken]`` was added. The option defaults to ``False`` and has " "been immediately deprecated. This will allow the current behaviour that " "service tokens are validated but not checked for roles to continue. The " "option should be set to ``True`` as soon as possible. The option will " "default to ``True`` in a future release." msgid "Mitaka Series Release Notes" msgstr "Mitaka Series Release Notes" msgid "New Features" msgstr "New Features" msgid "Newton Series Release Notes" msgstr "Newton Series Release Notes" msgid "Ocata Series Release Notes" msgstr "Ocata Series Release Notes" msgid "Pike Series Release Notes" msgstr "Pike Series Release Notes" msgid "Prelude" msgstr "Prelude" msgid "Security Issues" msgstr "Security Issues" msgid "" "Service tokens are compared against a list of possible roles for validity. " "This will ensure that only services are submitting tokens as an ``X-Service-" "Token``. For backwards compatibility, if ``service_token_roles_required`` is " "not set, a warning will be emitted. To enforce the check properly, set " "``service_token_roles_required`` to ``True``. It currently defaults to " "``False``" msgstr "" "Service tokens are compared against a list of possible roles for validity. " "This will ensure that only services are submitting tokens as an ``X-Service-" "Token``. For backwards compatibility, if ``service_token_roles_required`` is " "not set, a warning will be emitted. To enforce the check properly, set " "``service_token_roles_required`` to ``True``. It currently defaults to " "``False``" msgid "" "Set the ``service_token_roles`` to a list of roles that services may have. " "The likely list is ``service`` or ``admin``. Any ``service_token_roles`` may " "apply to accept the service token. Ensure service users have one of these " "roles so interservice communication continues to work correctly. When " "verified, set the ``service_token_roles_required`` flag to ``True`` to " "enforce this behaviour. This will become the default setting in future " "releases." msgstr "" "Set the ``service_token_roles`` to a list of roles that services may have. " "The likely list is ``service`` or ``admin``. Any ``service_token_roles`` may " "apply to accept the service token. Ensure service users have one of these " "roles so interservice communication continues to work correctly. When " "verified, set the ``service_token_roles_required`` flag to ``True`` to " "enforce this behaviour. This will become the default setting in future " "releases." msgid "" "The auth_host, auth_port, and auth_protocol configuration options to the " "s3token middleware are now deprecated." msgstr "" "The auth_host, auth_port, and auth_protocol configuration options to the " "s3token middleware are now deprecated." msgid "" "The auth_uri parameter of keystone_authtoken is deprecated in favor of " "www_authenticate_uri. The auth_uri option was often confused with the " "auth_url parameter of the keystoneauth plugin, which was also effectively " "always required. The parameter refers to the WWW-Authenticate header that is " "returned when the user needs to be redirected to the Identity service for " "authentication." msgstr "" "The auth_uri parameter of keystone_authtoken is deprecated in favour of " "www_authenticate_uri. The auth_uri option was often confused with the " "auth_url parameter of the keystoneauth plugin, which was also effectively " "always required. The parameter refers to the WWW-Authenticate header that is " "returned when the user needs to be redirected to the Identity service for " "authentication." msgid "Upgrade Notes" msgstr "Upgrade Notes" msgid "" "With the release of 4.2.0 of keystonemiddleware we no longer recommend using " "the in-process token cache. In-process caching may result in inconsistent " "validation, poor UX and race conditions. It is recommended that the " "`memcached_servers` option is set in the `keystone_authtoken` configuration " "section of the various services (e.g. nova, glance, ...) with the endpoint " "of running memcached server(s). When the feature is removed, not setting the " "`memcached_servers` option will cause keystone to validate tokens more " "frequently, increasing load. In production, use of caching is highly " "recommended. This feature is deprecated as of 4.2.0 and is targeted for " "removal in keystonemiddleware 5.0.0 or in the `O` development cycle, " "whichever is later." msgstr "" "With the release of 4.2.0 of keystonemiddleware we no longer recommend using " "the in-process token cache. In-process caching may result in inconsistent " "validation, poor UX and race conditions. It is recommended that the " "`memcached_servers` option is set in the `keystone_authtoken` configuration " "section of the various services (e.g. nova, glance, ...) with the endpoint " "of running memcached server(s). When the feature is removed, not setting the " "`memcached_servers` option will cause keystone to validate tokens more " "frequently, increasing load. In production, use of caching is highly " "recommended. This feature is deprecated as of 4.2.0 and is targeted for " "removal in keystonemiddleware 5.0.0 or in the `O` development cycle, " "whichever is later." msgid "" "[`bug 1333951 `_] Add support for parsing AWS v4 for ec2." msgstr "" "[`bug 1333951 `_] Add support for parsing AWS v4 for ec2." msgid "" "[`bug 1423973 `_] Use oslo.config choices for config options." msgstr "" "[`bug 1423973 `_] Use oslo.config choices for config options." msgid "" "[`bug 1490804 `_] The " "auth_token middleware validates the token's audit IDs during offline token " "validation if the Identity server includes audit IDs in the token revocation " "list." msgstr "" "[`bug 1490804 `_] The " "auth_token middleware validates the token's audit IDs during offline token " "validation if the Identity server includes audit IDs in the token revocation " "list." msgid "" "[`bug 1490804 `_] " "[`CVE-2015-7546 `_] A bug is fixed where an attacker could avoid token " "revocation when the PKI or PKIZ token provider is used. The complete " "remediation for this vulnerability requires the corresponding fix in the " "Identity (keystone) project." msgstr "" "[`bug 1490804 `_] " "[`CVE-2015-7546 `_] A bug is fixed where an attacker could avoid token " "revocation when the PKI or PKIZ token provider is used. The complete " "remediation for this vulnerability requires the corresponding fix in the " "Identity (Keystone) project." msgid "" "[`bug 1523311 `_] Do not list deprecated opts in sample config." msgstr "" "[`bug 1523311 `_] Do not list deprecated opts in sample config." msgid "" "[`bug 1540022 `_] The auth_token middleware will now accept a conf setting " "named ``oslo_config_config``. If this is set its value must be an existing " "oslo_config `ConfigOpts`. ``olso_config_config`` takes precedence over " "``oslo_config_project``. This feature is useful to applications that are " "instantiating the auth_token middleware themselves and wish to use an " "existing configuration." msgstr "" "[`bug 1540022 `_] The auth_token middleware will now accept a conf setting " "named ``oslo_config_config``. If this is set its value must be an existing " "oslo_config `ConfigOpts`. ``olso_config_config`` takes precedence over " "``oslo_config_project``. This feature is useful to applications that are " "instantiating the auth_token middleware themselves and wish to use an " "existing configuration." msgid "" "[`bug 1540022 `_] The auth_token middleware will now accept a conf setting " "named ``oslo_config_config``. If this is set its value must be an existing " "oslo_config `ConfigOpts`. ``oslo_config_config`` takes precedence over " "``oslo_config_project``. This feature is useful to applications that are " "instantiating the auth_token middleware themselves and wish to use an " "existing configuration." msgstr "" "[`bug 1540022 `_] The auth_token middleware will now accept a conf setting " "named ``oslo_config_config``. If this is set its value must be an existing " "oslo_config `ConfigOpts`. ``oslo_config_config`` takes precedence over " "``oslo_config_project``. This feature is useful to applications that are " "instantiating the auth_token middleware themselves and wish to use an " "existing configuration." msgid "" "[`bug 1540115 `_] Optional dependencies can now be installed using `extras`. " "To install audit related libraries, use ``pip install " "keystonemiddleware[audit_nofications]``. Refer to keystonemiddleware " "documentation for further information." msgstr "" "[`bug 1540115 `_] Optional dependencies can now be installed using `extras`. " "To install audit related libraries, use ``pip install " "keystonemiddleware[audit_nofications]``. Refer to keystonemiddleware " "documentation for further information." msgid "" "[`bug 1544840 `_] Adding audit middleware specific notification related " "configuration to allow a different notification driver and transport for " "audit if needed." msgstr "" "[`bug 1544840 `_] Adding audit middleware specific notification related " "configuration to allow a different notification driver and transport for " "audit if needed." msgid "" "[`bug 1583690 `_] For services such as Swift, which may not be utilizing " "oslo_config, we need to be able to determine the project name from local " "config. If project name is specified in both local config and oslo_config, " "the one in local config will be used instead. In case project is " "undetermined (i.e. not set), we use taxonomy.UNKNOWN as an indicator so " "operators can take corrective actions." msgstr "" "[`bug 1583690 `_] For services such as Swift, which may not be utilising " "oslo_config, we need to be able to determine the project name from local " "config. If project name is specified in both local config and oslo_config, " "the one in local config will be used instead. In case project is " "undetermined (i.e. not set), we use taxonomy.UNKNOWN as an indicator so " "operators can take corrective actions." msgid "" "[`bug 1583699 `_] Some service APIs (such as Swift list public containers) do " "not require a token. Therefore, there will be no identity or service catalog " "information available. In these cases, audit now fills in the default (i.e. " "taxonomy.UNKNOWN) for both initiator and target instead of raising an " "exception." msgstr "" "[`bug 1583699 `_] Some service APIs (such as Swift list public containers) do " "not require a token. Therefore, there will be no identity or service " "catalogue information available. In these cases, audit now fills in the " "default (i.e. taxonomy.UNKNOWN) for both initiator and target instead of " "raising an exception." msgid "" "[`bug 1583702 `_] Some services such as Swift does not use Oslo (global) " "config. In that case, the options are conveyed via local config. This patch " "utilized an established pattern in auth_token middleware, which is to first " "look for the given option in local config, then Oslo global config." msgstr "" "[`bug 1583702 `_] Some services such as Swift does not use Oslo (global) " "config. In that case, the options are conveyed via local config. This patch " "utilized an established pattern in auth_token middleware, which is to first " "look for the given option in local config, then Oslo global config." msgid "" "[`bug 1677308 `_] Removes ``pycrypto`` dependency as the library is " "unmaintained, and replaces it with the ``cryptography`` library." msgstr "" "[`bug 1677308 `_] Removes ``pycrypto`` dependency as the library is " "unmaintained, and replaces it with the ``cryptography`` library." msgid "" "[`bug 1677308 `_] There is no upgrade impact when switching from ``pycrypto`` " "to ``cryptography``. All data will be encrypted and decrypted using " "identical blocksize, padding, algorithm (AES) and mode (CBC). Data " "previously encrypted using ``pycrypto`` can be decrypted using both " "``pycrypto`` and ``cryptography``. The same is true of data encrypted using " "``cryptography``." msgstr "" "[`bug 1677308 `_] There is no upgrade impact when switching from ``pycrypto`` " "to ``cryptography``. All data will be encrypted and decrypted using " "identical blocksize, padding, algorithm (AES) and mode (CBC). Data " "previously encrypted using ``pycrypto`` can be decrypted using both " "``pycrypto`` and ``cryptography``. The same is true of data encrypted using " "``cryptography``." msgid "" "[`bug 1737115 `_] Last release have accidentaly make python-memcached a hard " "dependency, this have changed back to an optional one." msgstr "" "[`bug 1737115 `_] Last release had accidentally made python-memcached a hard " "dependency, this has changed it back to an optional one." msgid "" "[`bug 1737119 `_] If the application was not using the global cfg.CONF " "object, the configuration was not read from the configuration file. This " "have been fixed." msgstr "" "[`bug 1737119 `_] If the application was not using the global cfg.CONF " "object, the configuration was not read from the configuration file. This " "have been fixed." msgid "keystonemiddleware Release Notes" msgstr "keystonemiddleware Release Notes" keystonemiddleware-4.21.0/releasenotes/source/locale/ko_KR/0000775000175100017510000000000013225161130023750 5ustar zuulzuul00000000000000keystonemiddleware-4.21.0/releasenotes/source/locale/ko_KR/LC_MESSAGES/0000775000175100017510000000000013225161130025535 5ustar zuulzuul00000000000000keystonemiddleware-4.21.0/releasenotes/source/locale/ko_KR/LC_MESSAGES/releasenotes.po0000666000175100017510000000262013225160624030577 0ustar zuulzuul00000000000000# Ian Y. Choi , 2017. #zanata msgid "" msgstr "" "Project-Id-Version: keystonemiddleware Release Notes 4.14.1\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2017-04-05 12:59+0000\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "PO-Revision-Date: 2017-01-19 04:08+0000\n" "Last-Translator: Ian Y. Choi \n" "Language-Team: Korean (South Korea)\n" "Language: ko-KR\n" "X-Generator: Zanata 3.9.6\n" "Plural-Forms: nplurals=1; plural=0\n" msgid "4.1.0" msgstr "4.1.0" msgid "4.12.0" msgstr "4.12.0" msgid "4.2.0" msgstr "4.2.0" msgid "4.3.0" msgstr "4.3.0" msgid "4.5.0" msgstr "4.5.0" msgid "4.6.0" msgstr "4.6.0" msgid "Bug Fixes" msgstr "버그 수정" msgid "Current Series Release Notes" msgstr "최신 ì‹œë¦¬ì¦ˆì— ëŒ€í•œ 릴리즈 노트" msgid "Deprecation Notes" msgstr "ì§€ì› ì¢…ë£Œëœ ê¸°ëŠ¥ 노트" msgid "Mitaka Series Release Notes" msgstr "Mitaka ì‹œë¦¬ì¦ˆì— ëŒ€í•œ 릴리즈 노트" msgid "New Features" msgstr "새로운 기능" msgid "Newton Series Release Notes" msgstr "Newton ì‹œë¦¬ì¦ˆì— ëŒ€í•œ 릴리즈 노트" msgid "Ocata Series Release Notes" msgstr "Ocata ì‹œë¦¬ì¦ˆì— ëŒ€í•œ 릴리즈 노트" msgid "Security Issues" msgstr "보안 ì´ìŠˆ" msgid "Upgrade Notes" msgstr "업그레ì´ë“œ 노트" msgid "keystonemiddleware Release Notes" msgstr "keystonemiddleware 릴리즈 노트" keystonemiddleware-4.21.0/releasenotes/source/locale/fr/0000775000175100017510000000000013225161130023352 5ustar zuulzuul00000000000000keystonemiddleware-4.21.0/releasenotes/source/locale/fr/LC_MESSAGES/0000775000175100017510000000000013225161130025137 5ustar zuulzuul00000000000000keystonemiddleware-4.21.0/releasenotes/source/locale/fr/LC_MESSAGES/releasenotes.po0000666000175100017510000000227613225160624030210 0ustar zuulzuul00000000000000# Gérald LONLAS , 2016. #zanata msgid "" msgstr "" "Project-Id-Version: keystonemiddleware Release Notes 4.14.1\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2017-04-05 12:59+0000\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "PO-Revision-Date: 2016-10-22 05:26+0000\n" "Last-Translator: Gérald LONLAS \n" "Language-Team: French\n" "Language: fr\n" "X-Generator: Zanata 3.9.6\n" "Plural-Forms: nplurals=2; plural=(n > 1)\n" msgid "4.1.0" msgstr "4.1.0" msgid "4.2.0" msgstr "4.2.0" msgid "4.3.0" msgstr "4.3.0" msgid "4.5.0" msgstr "4.5.0" msgid "4.6.0" msgstr "4.6.0" msgid "Bug Fixes" msgstr "Corrections de bugs" msgid "Current Series Release Notes" msgstr "Note de la release actuelle" msgid "Deprecation Notes" msgstr "Notes dépréciées " msgid "Mitaka Series Release Notes" msgstr "Note de release pour Mitaka" msgid "New Features" msgstr "Nouvelles fonctionnalités" msgid "Newton Series Release Notes" msgstr "Note de release pour Newton" msgid "Security Issues" msgstr "Problèmes de sécurités" msgid "keystonemiddleware Release Notes" msgstr "Note de release de keystonemiddleware" keystonemiddleware-4.21.0/releasenotes/source/newton.rst0000666000175100017510000000021613225160624023560 0ustar zuulzuul00000000000000============================= Newton Series Release Notes ============================= .. release-notes:: :branch: origin/stable/newton keystonemiddleware-4.21.0/releasenotes/source/_static/0000775000175100017510000000000013225161130023132 5ustar zuulzuul00000000000000keystonemiddleware-4.21.0/releasenotes/source/_static/.placeholder0000666000175100017510000000000013225160624025414 0ustar zuulzuul00000000000000keystonemiddleware-4.21.0/releasenotes/source/unreleased.rst0000666000175100017510000000016013225160624024373 0ustar zuulzuul00000000000000============================== Current Series Release Notes ============================== .. release-notes:: keystonemiddleware-4.21.0/releasenotes/source/mitaka.rst0000666000175100017510000000023213225160624023512 0ustar zuulzuul00000000000000=================================== Mitaka Series Release Notes =================================== .. release-notes:: :branch: origin/stable/mitaka keystonemiddleware-4.21.0/releasenotes/source/index.rst0000666000175100017510000000027213225160624023357 0ustar zuulzuul00000000000000================================== keystonemiddleware Release Notes ================================== .. toctree:: :maxdepth: 1 unreleased pike ocata newton mitaka keystonemiddleware-4.21.0/releasenotes/source/ocata.rst0000666000175100017510000000021213225160624023331 0ustar zuulzuul00000000000000============================ Ocata Series Release Notes ============================ .. release-notes:: :branch: origin/stable/ocata keystonemiddleware-4.21.0/releasenotes/source/_templates/0000775000175100017510000000000013225161130023641 5ustar zuulzuul00000000000000keystonemiddleware-4.21.0/releasenotes/source/_templates/.placeholder0000666000175100017510000000000013225160624026123 0ustar zuulzuul00000000000000keystonemiddleware-4.21.0/releasenotes/source/pike.rst0000666000175100017510000000021713225160624023177 0ustar zuulzuul00000000000000=================================== Pike Series Release Notes =================================== .. release-notes:: :branch: stable/pike keystonemiddleware-4.21.0/releasenotes/notes/0000775000175100017510000000000013225161130021334 5ustar zuulzuul00000000000000keystonemiddleware-4.21.0/releasenotes/notes/bug-1544840-a534127f8663e40f.yaml0000666000175100017510000000037613225160624025723 0ustar zuulzuul00000000000000--- features: - > [`bug 1544840 `_] Adding audit middleware specific notification related configuration to allow a different notification driver and transport for audit if needed. keystonemiddleware-4.21.0/releasenotes/notes/s3token_auth_uri-490c1287d90b9df7.yaml0000666000175100017510000000052713225160624027673 0ustar zuulzuul00000000000000--- features: - A new configuration option for the s3token middleware called auth_uri can be used to set the URI to be used for authentication. This replaces auth_host, auth_port, and auth_protocol. deprecations: - The auth_host, auth_port, and auth_protocol configuration options to the s3token middleware are now deprecated. keystonemiddleware-4.21.0/releasenotes/notes/bug-1737119-4afe548d28fbf8bb.yaml0000666000175100017510000000037213225160624026310 0ustar zuulzuul00000000000000--- fixes: - > [`bug 1737119 `_] If the application was not using the global cfg.CONF object, the configuration was not read from the configuration file. This have been fixed. ././@LongLink0000000000000000000000000000015300000000000011214 Lustar 00000000000000keystonemiddleware-4.21.0/releasenotes/notes/authprotocol-accepts-oslo-config-config-a37212b60f58e154.yamlkeystonemiddleware-4.21.0/releasenotes/notes/authprotocol-accepts-oslo-config-config-a37212b60f58e150000666000175100017510000000076013225160624033101 0ustar zuulzuul00000000000000--- features: - > [`bug 1540022 `_] The auth_token middleware will now accept a conf setting named ``oslo_config_config``. If this is set its value must be an existing oslo_config `ConfigOpts`. ``oslo_config_config`` takes precedence over ``oslo_config_project``. This feature is useful to applications that are instantiating the auth_token middleware themselves and wish to use an existing configuration. ././@LongLink0000000000000000000000000000014700000000000011217 Lustar 00000000000000keystonemiddleware-4.21.0/releasenotes/notes/deprecate-caching-tokens-in-process-a412b0f1dea84cb9.yamlkeystonemiddleware-4.21.0/releasenotes/notes/deprecate-caching-tokens-in-process-a412b0f1dea84cb9.ya0000666000175100017510000000146713225160624033161 0ustar zuulzuul00000000000000--- deprecations: - > With the release of 4.2.0 of keystonemiddleware we no longer recommend using the in-process token cache. In-process caching may result in inconsistent validation, poor UX and race conditions. It is recommended that the `memcached_servers` option is set in the `keystone_authtoken` configuration section of the various services (e.g. nova, glance, ...) with the endpoint of running memcached server(s). When the feature is removed, not setting the `memcached_servers` option will cause keystone to validate tokens more frequently, increasing load. In production, use of caching is highly recommended. This feature is deprecated as of 4.2.0 and is targeted for removal in keystonemiddleware 5.0.0 or in the `O` development cycle, whichever is later. keystonemiddleware-4.21.0/releasenotes/notes/bug-1490804-87c0ff8e764945c1.yaml0000666000175100017510000000124513225160624026016 0ustar zuulzuul00000000000000--- features: - > [`bug 1490804 `_] The auth_token middleware validates the token's audit IDs during offline token validation if the Identity server includes audit IDs in the token revocation list. security: - > [`bug 1490804 `_] [`CVE-2015-7546 `_] A bug is fixed where an attacker could avoid token revocation when the PKI or PKIZ token provider is used. The complete remediation for this vulnerability requires the corresponding fix in the Identity (keystone) project. keystonemiddleware-4.21.0/releasenotes/notes/rename-auth-uri-d223d883f5898aee.yaml0000666000175100017510000000065613225160624027501 0ustar zuulzuul00000000000000--- deprecations: - | The auth_uri parameter of keystone_authtoken is deprecated in favor of www_authenticate_uri. The auth_uri option was often confused with the auth_url parameter of the keystoneauth plugin, which was also effectively always required. The parameter refers to the WWW-Authenticate header that is returned when the user needs to be redirected to the Identity service for authentication. keystonemiddleware-4.21.0/releasenotes/notes/x-is-admin-project-header-97f1882e209fe727.yaml0000666000175100017510000000036013225160624031171 0ustar zuulzuul00000000000000--- prelude: > - Add the `X_IS_ADMIN_PROJECT` header. features: - Added the `X_IS_ADMIN_PROJECT` header to authenticated headers. This has the string value of 'True' or 'False' and can be used to enforce admin project policies. keystonemiddleware-4.21.0/releasenotes/notes/bug-1583702-a4469dc1556878b9.yaml0000666000175100017510000000060613225160624025736 0ustar zuulzuul00000000000000--- fixes: - > [`bug 1583702 `_] Some services such as Swift does not use Oslo (global) config. In that case, the options are conveyed via local config. This patch utilized an established pattern in auth_token middleware, which is to first look for the given option in local config, then Oslo global config. keystonemiddleware-4.21.0/releasenotes/notes/bug-1737115-fa3d41e3d3cd7177.yaml0000666000175100017510000000033513225160624026131 0ustar zuulzuul00000000000000--- fixes: - | [`bug 1737115 `_] Last release have accidentaly make python-memcached a hard dependency, this have changed back to an optional one. keystonemiddleware-4.21.0/releasenotes/notes/allow-expired-5ddbabcffc5678af.yaml0000666000175100017510000000335613225160624027616 0ustar zuulzuul00000000000000--- prelude: > Fetching expired tokens when using a valid service token is now allowed. This will help with long running operations that must continue between services longer than the original expiry of the token. features: - AuthToken middleware will now allow fetching an expired token when a valid service token is present. This service token must contain any one of the roles specified in ``service_token_roles``. - Service tokens are compared against a list of possible roles for validity. This will ensure that only services are submitting tokens as an ``X-Service-Token``. For backwards compatibility, if ``service_token_roles_required`` is not set, a warning will be emitted. To enforce the check properly, set ``service_token_roles_required`` to ``True``. It currently defaults to ``False`` upgrade: - Set the ``service_token_roles`` to a list of roles that services may have. The likely list is ``service`` or ``admin``. Any ``service_token_roles`` may apply to accept the service token. Ensure service users have one of these roles so interservice communication continues to work correctly. When verified, set the ``service_token_roles_required`` flag to ``True`` to enforce this behaviour. This will become the default setting in future releases. deprecations: - For backwards compatibility the ``service_token_roles_required`` option in ``[keystone_authtoken]`` was added. The option defaults to ``False`` and has been immediately deprecated. This will allow the current behaviour that service tokens are validated but not checked for roles to continue. The option should be set to ``True`` as soon as possible. The option will default to ``True`` in a future release. keystonemiddleware-4.21.0/releasenotes/notes/bug-1583699-dba4fe6c057e2be5.yaml0000666000175100017510000000063513225160624026313 0ustar zuulzuul00000000000000--- fixes: - > [`bug 1583699 `_] Some service APIs (such as Swift list public containers) do not require a token. Therefore, there will be no identity or service catalog information available. In these cases, audit now fills in the default (i.e. taxonomy.UNKNOWN) for both initiator and target instead of raising an exception. keystonemiddleware-4.21.0/releasenotes/notes/bug-1583690-da67472d7afff0bf.yaml0000666000175100017510000000077213225160624026313 0ustar zuulzuul00000000000000--- features: - > [`bug 1583690 `_] For services such as Swift, which may not be utilizing oslo_config, we need to be able to determine the project name from local config. If project name is specified in both local config and oslo_config, the one in local config will be used instead. In case project is undetermined (i.e. not set), we use taxonomy.UNKNOWN as an indicator so operators can take corrective actions. keystonemiddleware-4.21.0/releasenotes/notes/bug-1677308-a2fa7de67f21cd84.yaml0000666000175100017510000000127613225160624026231 0ustar zuulzuul00000000000000--- fixes: - | [`bug 1677308 `_] Removes ``pycrypto`` dependency as the library is unmaintained, and replaces it with the ``cryptography`` library. upgrade: - | [`bug 1677308 `_] There is no upgrade impact when switching from ``pycrypto`` to ``cryptography``. All data will be encrypted and decrypted using identical blocksize, padding, algorithm (AES) and mode (CBC). Data previously encrypted using ``pycrypto`` can be decrypted using both ``pycrypto`` and ``cryptography``. The same is true of data encrypted using ``cryptography``. keystonemiddleware-4.21.0/releasenotes/notes/bug_1540115-677cf5016bc46348.yaml0000666000175100017510000000051713225160624025775 0ustar zuulzuul00000000000000--- features: - > [`bug 1540115 `_] Optional dependencies can now be installed using `extras`. To install audit related libraries, use ``pip install keystonemiddleware[audit_nofications]``. Refer to keystonemiddleware documentation for further information. keystonemiddleware-4.21.0/releasenotes/notes/ksm_4.1.0-3cd78446d8e63616.yaml0000666000175100017510000000064113225160624025637 0ustar zuulzuul00000000000000--- fixes: - > [`bug 1523311 `_] Do not list deprecated opts in sample config. - > [`bug 1333951 `_] Add support for parsing AWS v4 for ec2. - > [`bug 1423973 `_] Use oslo.config choices for config options. keystonemiddleware-4.21.0/releasenotes/notes/.placeholder0000666000175100017510000000000013225160624023616 0ustar zuulzuul00000000000000keystonemiddleware-4.21.0/.coveragerc0000666000175100017510000000016113225160624017643 0ustar zuulzuul00000000000000[run] branch = True source = keystonemiddleware omit = keystonemiddleware/tests/* [report] ignore_errors = True keystonemiddleware-4.21.0/.testr.conf0000666000175100017510000000047713225160624017622 0ustar zuulzuul00000000000000[DEFAULT] test_command= OS_STDOUT_CAPTURE=${OS_STDOUT_CAPTURE:-1} \ OS_STDERR_CAPTURE=${OS_STDERR_CAPTURE:-1} \ OS_LOG_CAPTURE=${OS_LOG_CAPTURE:-1} \ ${PYTHON:-python} -m subunit.run discover -t ./ ./keystonemiddleware/tests $LISTOPT $IDOPTION test_id_option=--load-list $IDFILE test_list_option=--list keystonemiddleware-4.21.0/README.rst0000666000175100017510000000254313225160624017217 0ustar zuulzuul00000000000000======================== Team and repository tags ======================== .. image:: https://governance.openstack.org/badges/keystonemiddleware.svg :target: https://governance.openstack.org/reference/tags/index.html .. Change things from this point on Middleware for the OpenStack Identity API (Keystone) ==================================================== .. image:: https://img.shields.io/pypi/v/keystonemiddleware.svg :target: https://pypi.python.org/pypi/keystonemiddleware/ :alt: Latest Version .. image:: https://img.shields.io/pypi/dm/keystonemiddleware.svg :target: https://pypi.python.org/pypi/keystonemiddleware/ :alt: Downloads This package contains middleware modules designed to provide authentication and authorization features to web services other than `Keystone `. The most prominent module is ``keystonemiddleware.auth_token``. This package does not expose any CLI or Python API features. For information on contributing, see ``CONTRIBUTING.rst``. * License: Apache License, Version 2.0 * Documentation: https://docs.openstack.org/keystonemiddleware/latest/ * Source: https://git.openstack.org/cgit/openstack/keystonemiddleware * Bugs: https://bugs.launchpad.net/keystonemiddleware For any other information, refer to the parent project, Keystone: https://github.com/openstack/keystone keystonemiddleware-4.21.0/keystonemiddleware.egg-info/0000775000175100017510000000000013225161130023104 5ustar zuulzuul00000000000000keystonemiddleware-4.21.0/keystonemiddleware.egg-info/requires.txt0000664000175100017510000000051013225161127025506 0ustar zuulzuul00000000000000keystoneauth1>=3.3.0 oslo.cache>=1.26.0 oslo.config>=5.1.0 oslo.context>=2.19.2 oslo.i18n>=3.15.3 oslo.log>=3.30.0 oslo.serialization!=2.19.1,>=2.18.0 oslo.utils>=3.31.0 pbr!=2.1.0,>=2.0.0 pycadf!=2.0.0,>=1.1.0 python-keystoneclient>=3.8.0 requests>=2.14.2 six>=1.10.0 WebOb>=1.7.1 [audit_notifications] oslo.messaging>=5.29.0 keystonemiddleware-4.21.0/keystonemiddleware.egg-info/entry_points.txt0000664000175100017510000000052213225161127026407 0ustar zuulzuul00000000000000[oslo.config.opts] keystonemiddleware.auth_token = keystonemiddleware.auth_token._opts:list_opts [paste.filter_factory] audit = keystonemiddleware.audit:filter_factory auth_token = keystonemiddleware.auth_token:filter_factory ec2_token = keystonemiddleware.ec2_token:filter_factory s3_token = keystonemiddleware.s3_token:filter_factory keystonemiddleware-4.21.0/keystonemiddleware.egg-info/SOURCES.txt0000664000175100017510000001417013225161130024773 0ustar zuulzuul00000000000000.coveragerc .stestr.conf .testr.conf AUTHORS CONTRIBUTING.rst ChangeLog HACKING.rst LICENSE README.rst babel.cfg requirements.txt setup.cfg setup.py test-requirements.txt tox.ini config-generator/keystonemiddleware.conf doc/.gitignore doc/Makefile doc/requirements.txt doc/ext/__init__.py doc/ext/apidoc.py doc/source/audit.rst doc/source/conf.py doc/source/history.rst doc/source/index.rst doc/source/installation.rst doc/source/middlewarearchitecture.rst doc/source/images/audit.png doc/source/images/graphs_authComp.svg doc/source/images/graphs_authCompDelegate.svg examples/pki/gen_cmsz.py examples/pki/gen_pki.sh examples/pki/run_all.sh examples/pki/certs/cacert.pem examples/pki/certs/middleware.pem examples/pki/certs/signing_cert.pem examples/pki/certs/ssl_cert.pem examples/pki/cms/auth_token_revoked.json examples/pki/cms/auth_token_revoked.pem examples/pki/cms/auth_token_revoked.pkiz examples/pki/cms/auth_token_scoped.json examples/pki/cms/auth_token_scoped.pem examples/pki/cms/auth_token_scoped.pkiz examples/pki/cms/auth_token_scoped_expired.json examples/pki/cms/auth_token_scoped_expired.pem examples/pki/cms/auth_token_scoped_expired.pkiz examples/pki/cms/auth_token_unscoped.json examples/pki/cms/auth_token_unscoped.pem examples/pki/cms/auth_token_unscoped.pkiz examples/pki/cms/auth_v3_token_revoked.json examples/pki/cms/auth_v3_token_revoked.pem examples/pki/cms/auth_v3_token_revoked.pkiz examples/pki/cms/auth_v3_token_scoped.json examples/pki/cms/auth_v3_token_scoped.pem examples/pki/cms/auth_v3_token_scoped.pkiz examples/pki/cms/revocation_list.der examples/pki/cms/revocation_list.json examples/pki/cms/revocation_list.pem examples/pki/cms/revocation_list.pkiz examples/pki/private/cakey.pem examples/pki/private/signing_key.pem examples/pki/private/ssl_key.pem keystonemiddleware/__init__.py keystonemiddleware/ec2_token.py keystonemiddleware/exceptions.py keystonemiddleware/fixture.py keystonemiddleware/i18n.py keystonemiddleware/opts.py keystonemiddleware/s3_token.py keystonemiddleware.egg-info/PKG-INFO keystonemiddleware.egg-info/SOURCES.txt keystonemiddleware.egg-info/dependency_links.txt keystonemiddleware.egg-info/entry_points.txt keystonemiddleware.egg-info/not-zip-safe keystonemiddleware.egg-info/pbr.json keystonemiddleware.egg-info/requires.txt keystonemiddleware.egg-info/top_level.txt keystonemiddleware/_common/__init__.py keystonemiddleware/_common/config.py keystonemiddleware/audit/__init__.py keystonemiddleware/audit/_api.py keystonemiddleware/audit/_notifier.py keystonemiddleware/auth_token/__init__.py keystonemiddleware/auth_token/_auth.py keystonemiddleware/auth_token/_base.py keystonemiddleware/auth_token/_cache.py keystonemiddleware/auth_token/_exceptions.py keystonemiddleware/auth_token/_identity.py keystonemiddleware/auth_token/_memcache_crypt.py keystonemiddleware/auth_token/_opts.py keystonemiddleware/auth_token/_request.py keystonemiddleware/auth_token/_revocations.py keystonemiddleware/auth_token/_signing_dir.py keystonemiddleware/auth_token/_user_plugin.py keystonemiddleware/echo/__init__.py keystonemiddleware/echo/__main__.py keystonemiddleware/echo/service.py keystonemiddleware/locale/en_GB/LC_MESSAGES/keystonemiddleware.po keystonemiddleware/locale/ko_KR/LC_MESSAGES/keystonemiddleware.po keystonemiddleware/tests/__init__.py keystonemiddleware/tests/unit/__init__.py keystonemiddleware/tests/unit/client_fixtures.py keystonemiddleware/tests/unit/test_ec2_token_middleware.py keystonemiddleware/tests/unit/test_entry_points.py keystonemiddleware/tests/unit/test_fixtures.py keystonemiddleware/tests/unit/test_opts.py keystonemiddleware/tests/unit/test_s3_token_middleware.py keystonemiddleware/tests/unit/utils.py keystonemiddleware/tests/unit/audit/__init__.py keystonemiddleware/tests/unit/audit/base.py keystonemiddleware/tests/unit/audit/test_audit_api.py keystonemiddleware/tests/unit/audit/test_audit_middleware.py keystonemiddleware/tests/unit/audit/test_audit_oslo_messaging.py keystonemiddleware/tests/unit/audit/test_logging_notifier.py keystonemiddleware/tests/unit/auth_token/__init__.py keystonemiddleware/tests/unit/auth_token/base.py keystonemiddleware/tests/unit/auth_token/test_auth.py keystonemiddleware/tests/unit/auth_token/test_auth_token_middleware.py keystonemiddleware/tests/unit/auth_token/test_base_middleware.py keystonemiddleware/tests/unit/auth_token/test_cache.py keystonemiddleware/tests/unit/auth_token/test_config.py keystonemiddleware/tests/unit/auth_token/test_connection_pool.py keystonemiddleware/tests/unit/auth_token/test_memcache_crypt.py keystonemiddleware/tests/unit/auth_token/test_request.py keystonemiddleware/tests/unit/auth_token/test_revocations.py keystonemiddleware/tests/unit/auth_token/test_signing_dir.py keystonemiddleware/tests/unit/auth_token/test_user_auth_plugin.py releasenotes/notes/.placeholder releasenotes/notes/allow-expired-5ddbabcffc5678af.yaml releasenotes/notes/authprotocol-accepts-oslo-config-config-a37212b60f58e154.yaml releasenotes/notes/bug-1490804-87c0ff8e764945c1.yaml releasenotes/notes/bug-1544840-a534127f8663e40f.yaml releasenotes/notes/bug-1583690-da67472d7afff0bf.yaml releasenotes/notes/bug-1583699-dba4fe6c057e2be5.yaml releasenotes/notes/bug-1583702-a4469dc1556878b9.yaml releasenotes/notes/bug-1677308-a2fa7de67f21cd84.yaml releasenotes/notes/bug-1737115-fa3d41e3d3cd7177.yaml releasenotes/notes/bug-1737119-4afe548d28fbf8bb.yaml releasenotes/notes/bug_1540115-677cf5016bc46348.yaml releasenotes/notes/deprecate-caching-tokens-in-process-a412b0f1dea84cb9.yaml releasenotes/notes/ksm_4.1.0-3cd78446d8e63616.yaml releasenotes/notes/rename-auth-uri-d223d883f5898aee.yaml releasenotes/notes/s3token_auth_uri-490c1287d90b9df7.yaml releasenotes/notes/x-is-admin-project-header-97f1882e209fe727.yaml releasenotes/source/conf.py releasenotes/source/index.rst releasenotes/source/mitaka.rst releasenotes/source/newton.rst releasenotes/source/ocata.rst releasenotes/source/pike.rst releasenotes/source/unreleased.rst releasenotes/source/_static/.placeholder releasenotes/source/_templates/.placeholder releasenotes/source/locale/en_GB/LC_MESSAGES/releasenotes.po releasenotes/source/locale/fr/LC_MESSAGES/releasenotes.po releasenotes/source/locale/ko_KR/LC_MESSAGES/releasenotes.po tools/tox_install.shkeystonemiddleware-4.21.0/keystonemiddleware.egg-info/pbr.json0000664000175100017510000000005613225161127024571 0ustar zuulzuul00000000000000{"git_version": "f1f2c4a", "is_release": true}keystonemiddleware-4.21.0/keystonemiddleware.egg-info/dependency_links.txt0000664000175100017510000000000113225161127027160 0ustar zuulzuul00000000000000 keystonemiddleware-4.21.0/keystonemiddleware.egg-info/not-zip-safe0000664000175100017510000000000113225161111025331 0ustar zuulzuul00000000000000 keystonemiddleware-4.21.0/keystonemiddleware.egg-info/PKG-INFO0000664000175100017510000000470013225161127024210 0ustar zuulzuul00000000000000Metadata-Version: 1.1 Name: keystonemiddleware Version: 4.21.0 Summary: Middleware for OpenStack Identity Home-page: https://docs.openstack.org/developer/keystonemiddleware/ Author: OpenStack Author-email: openstack-dev@lists.openstack.org License: UNKNOWN Description-Content-Type: UNKNOWN Description: ======================== Team and repository tags ======================== .. image:: https://governance.openstack.org/badges/keystonemiddleware.svg :target: https://governance.openstack.org/reference/tags/index.html .. Change things from this point on Middleware for the OpenStack Identity API (Keystone) ==================================================== .. image:: https://img.shields.io/pypi/v/keystonemiddleware.svg :target: https://pypi.python.org/pypi/keystonemiddleware/ :alt: Latest Version .. image:: https://img.shields.io/pypi/dm/keystonemiddleware.svg :target: https://pypi.python.org/pypi/keystonemiddleware/ :alt: Downloads This package contains middleware modules designed to provide authentication and authorization features to web services other than `Keystone `. The most prominent module is ``keystonemiddleware.auth_token``. This package does not expose any CLI or Python API features. For information on contributing, see ``CONTRIBUTING.rst``. * License: Apache License, Version 2.0 * Documentation: https://docs.openstack.org/keystonemiddleware/latest/ * Source: https://git.openstack.org/cgit/openstack/keystonemiddleware * Bugs: https://bugs.launchpad.net/keystonemiddleware For any other information, refer to the parent project, Keystone: https://github.com/openstack/keystone Platform: UNKNOWN Classifier: Environment :: OpenStack Classifier: Intended Audience :: Information Technology Classifier: Intended Audience :: System Administrators Classifier: License :: OSI Approved :: Apache Software License Classifier: Operating System :: POSIX :: Linux Classifier: Programming Language :: Python Classifier: Programming Language :: Python :: 2 Classifier: Programming Language :: Python :: 2.7 Classifier: Programming Language :: Python :: 3 Classifier: Programming Language :: Python :: 3.5 keystonemiddleware-4.21.0/keystonemiddleware.egg-info/top_level.txt0000664000175100017510000000002313225161127025637 0ustar zuulzuul00000000000000keystonemiddleware keystonemiddleware-4.21.0/doc/0000775000175100017510000000000013225161130016260 5ustar zuulzuul00000000000000keystonemiddleware-4.21.0/doc/requirements.txt0000666000175100017510000000111413225160624021552 0ustar zuulzuul00000000000000# The order of packages is significant, because pip processes them in the order # of appearance. Changing the order has an impact on the overall integration # process, which may cause wedges in the gate later. # For generating sphinx documentation doc8>=0.6.0 # Apache-2.0 openstackdocstheme>=1.17.0 # Apache-2.0 reno>=2.5.0 # Apache-2.0 sphinx>=1.6.2 # BSD # For autodoc builds mock>=2.0.0 # BSD oslotest>=1.10.0 # Apache-2.0 requests-mock>=1.1.0 # Apache-2.0 testresources>=2.0.0 # Apache-2.0/BSD python-memcached>=1.56 # PSF WebTest>=2.0.27 # MIT oslo.messaging>=5.29.0 # Apache-2.0 keystonemiddleware-4.21.0/doc/source/0000775000175100017510000000000013225161130017560 5ustar zuulzuul00000000000000keystonemiddleware-4.21.0/doc/source/conf.py0000666000175100017510000001720413225160624021074 0ustar zuulzuul00000000000000# keystonemiddleware documentation build configuration file, created by # sphinx-quickstart on Sun Dec 6 14:19:25 2009. # # This file is execfile()d with the current directory set to its containing # dir. # # Note that not all possible configuration values are present in this # autogenerated file. # # All configuration values have a default; values that are commented out # serve to show the default. from __future__ import unicode_literals import os import sys import pbr.version sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), '..', '..'))) # NOTE(blk-u): Path for our Sphinx extension, remove when # https://launchpad.net/bugs/1260495 is fixed. sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), '..'))) # If extensions (or modules to document with autodoc) are in another directory, # add these directories to sys.path here. If the directory is relative to the # documentation root, use os.path.abspath to make it absolute, like shown here. #sys.path.append(os.path.abspath('.')) # -- General configuration ---------------------------------------------------- # Add any Sphinx extension module names here, as strings. They can be # extensions # coming with Sphinx (named 'sphinx.ext.*') or your custom ones. extensions = ['sphinx.ext.autodoc', 'sphinx.ext.todo', 'sphinx.ext.coverage', 'sphinx.ext.intersphinx', 'openstackdocstheme', 'oslo_config.sphinxconfiggen' ] config_generator_config_file = '../../config-generator/keystonemiddleware.conf' sample_config_basename = '_static/keystonemiddleware' todo_include_todos = True # Add any paths that contain templates here, relative to this directory. #templates_path = ['_templates'] # The suffix of source filenames. source_suffix = '.rst' # The encoding of source files. #source_encoding = 'utf-8' # The master toctree document. master_doc = 'index' # General information about the project. project = 'keystonemiddleware' copyright = 'OpenStack Contributors' # The version info for the project you're documenting, acts as replacement for # |version| and |release|, also used in various other places throughout the # built documents. version_info = pbr.version.VersionInfo('keystonemiddleware') # The short X.Y version. version = version_info.version_string() # The full version, including alpha/beta/rc tags. release = version_info.release_string() # The language for content autogenerated by Sphinx. Refer to documentation # for a list of supported languages. #language = None # There are two options for replacing |today|: either, you set today to some # non-false value, then it is used: #today = '' # Else, today_fmt is used as the format for a strftime call. #today_fmt = '%B %d, %Y' # List of documents that shouldn't be included in the build. #unused_docs = [] # List of directories, relative to source directory, that shouldn't be searched # for source files. exclude_trees = [] # The reST default role (used for this markup: `text`) to use for all # documents. #default_role = None # If true, '()' will be appended to :func: etc. cross-reference text. add_function_parentheses = True # If true, the current module name will be prepended to all description # unit titles (such as .. function::). add_module_names = True # If true, sectionauthor and moduleauthor directives will be shown in the # output. They are ignored by default. #show_authors = False # The name of the Pygments (syntax highlighting) style to use. pygments_style = 'sphinx' # A list of ignored prefixes for module index sorting. modindex_common_prefix = ['keystonemiddleware.'] # Grouping the document tree for man pages. # List of tuples 'sourcefile', 'target', 'title', 'Authors name', 'manual' #man_pages = [] # -- Options for HTML output -------------------------------------------------- # The theme to use for HTML and HTML Help pages. Major themes that come with # Sphinx are currently 'default' and 'sphinxdoc'. #html_theme_path = ["."] #html_theme = '_theme' html_theme = 'openstackdocs' # Theme options are theme-specific and customize the look and feel of a theme # further. For a list of options available for each theme, see the # documentation. #html_theme_options = {} # Add any paths that contain custom themes here, relative to this directory. #html_theme_path = [] # The name for this set of Sphinx documents. If None, it defaults to # " v documentation". #html_title = None # A shorter title for the navigation bar. Default is the same as html_title. #html_short_title = None # The name of an image file (relative to this directory) to place at the top # of the sidebar. #html_logo = None # The name of an image file (within the static path) to use as favicon of the # docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32 # pixels large. #html_favicon = None # Add any paths that contain custom static files (such as style sheets) here, # relative to this directory. They are copied after the builtin static files, # so a file named "default.css" will overwrite the builtin "default.css". html_static_path = ['_static'] # If not '', a 'Last updated on:' timestamp is inserted at every page bottom, # using the given strftime format. html_last_updated_fmt = '%Y-%m-%d %H:%M' # If true, SmartyPants will be used to convert quotes and dashes to # typographically correct entities. #html_use_smartypants = True # Custom sidebar templates, maps document names to template names. #html_sidebars = {} # Additional templates that should be rendered to pages, maps page names to # template names. #html_additional_pages = {} # If false, no module index is generated. #html_use_modindex = True # If false, no index is generated. #html_use_index = True # If true, the index is split into individual pages for each letter. #html_split_index = False # If true, links to the reST sources are added to the pages. #html_show_sourcelink = True # If true, an OpenSearch description file will be output, and all pages will # contain a tag referring to it. The value of this option must be the # base URL from which the finished HTML is served. #html_use_opensearch = '' # If nonempty, this is the file name suffix for HTML files (e.g. ".xhtml"). #html_file_suffix = '' # Output file base name for HTML help builder. htmlhelp_basename = 'keystonemiddlewaredoc' # -- Options for LaTeX output ------------------------------------------------- # The paper size ('letter' or 'a4'). #latex_paper_size = 'letter' # The font size ('10pt', '11pt' or '12pt'). #latex_font_size = '10pt' # Grouping the document tree into LaTeX files. List of tuples # (source start file, target name, title, author, documentclass [howto/manual]) # . latex_documents = [ ('index', 'keystonmiddleware.tex', 'keystonemiddleware Documentation', 'Nebula Inc, based on work by Rackspace and Jacob Kaplan-Moss', 'manual'), ] # The name of an image file (relative to this directory) to place at the top of # the title page. #latex_logo = None # For "manual" documents, if this is true, then toplevel headings are parts, # not chapters. #latex_use_parts = False # Additional stuff for the LaTeX preamble. #latex_preamble = '' # Documents to append as an appendix to all manuals. #latex_appendices = [] # If false, no module index is generated. #latex_use_modindex = True keystoneclient = 'https://docs.openstack.org/python-keystoneclient/latest/' intersphinx_mapping = {'keystoneclient': (keystoneclient, None), } # -- Options for openstackdocstheme ------------------------------------------- repository_name = 'openstack/keystonemiddleware' bug_project = 'keystonemiddleware' bug_tag = '' keystonemiddleware-4.21.0/doc/source/images/0000775000175100017510000000000013225161130021025 5ustar zuulzuul00000000000000keystonemiddleware-4.21.0/doc/source/images/graphs_authComp.svg0000666000175100017510000000570613225160624024713 0ustar zuulzuul00000000000000 AuthComp AuthComp Auth Component AuthComp->Reject Reject Unauthenticated Requests Service OpenStack Service AuthComp->Service Forward Authenticated Requests Start->AuthComp keystonemiddleware-4.21.0/doc/source/images/graphs_authCompDelegate.svg0000666000175100017510000000702013225160624026335 0ustar zuulzuul00000000000000 AuthCompDelegate AuthComp Auth Component AuthComp->Reject Reject Requests Indicated by the Service Service OpenStack Service AuthComp->Service Forward Requests with Identiy Status Service->AuthComp Send Response OR Reject Message Start->AuthComp keystonemiddleware-4.21.0/doc/source/images/audit.png0000666000175100017510000013714613225160624022666 0ustar zuulzuul00000000000000‰PNG  IHDRzú6«csRGB®ÎégAMA± üa pHYsÃÃÇo¨d½ûIDATx^íý °]Õu§‹«êV×¥º*}]]i·’îKn:÷Óî„Dé6îNbµ“¨ã‡:¸ã$81Û+± ²16ŒÀØ€mÆB @(B„$ÂèÑÃz=8z $Áa,Žóÿ3–¦§ÆzìµÏ~œ½Ïþ¾úÕ®5Çk®¹æZkï9Æ^{íïÔÒ PgH7@!Ýu†tÔÒ PgH7@!Ýu†tÔÒ PgH7@!Ýu†tÔÒ PgH7@!Ýu†tÔÒ PgH7@!Ýu†tÔÒ PgH7@!Ýu†tÔÒ PgH7@!Ýu†tÔÒ PgH7@!Ýu†tÔÒ PgH7@!Ýu†tÔÒ PgH7@!Ýu†tÔÒ PgH7@!Ýu†tÔÒ PgH7@!Ýu†tÔÒ PgH7@!Ýu†tÔÒ PgH7@!Ýu†tÔÒ PgªK7Œ>ARnm’¾6¥·É–F^·n]b:ARÑ2ƒ¶k׮ŋ[—&L˜ åC‡%u'°Z‘Y¬#Úz²4(ÂŽˆyóæ%Ö$'sã7.[¶ìÍ7ßLœZï H7Ô‡dK|b:ARу¦H;éÍÉÈžx XOô9)ÔuöíÛ§±ª±MëU ±ž ±f¡M‡ŒCbj­ÃêÕ«ã¯Çâb2ƒL1oÞê°Mªõ=™T ªdiW4’ÕJŒ†PÂF] fÎ")ŸlI–"Ì§Ì Øè…Îk»Ö¡µÒñ Ô…ú¤Âõˆ˜ybˆã·LÀIõ‰uàlH–ˆÃB#d’rÖÉZÜaWND‘x¤Hª.f½ª“IEV7*îo¸Z’rÊ¢·¡•<‡°SqZ!ó™Eaý‰‘%dSþ8'…[±<áÝP'€Šá4ˆ÷Î,")Pñî†àà([~$³Àœ)qR8yªl›£ŠÌy»‚«éyu˜v&åM„v4Á¶Z‘9'Oê¢Õ[È5{«âåà ®†@Éc’ÕrF#îU:@ˆ÷(1Eí$åK²!£Û;o.1ÀŽKæoÌ]žêBÝÒ j†@:äBÎù„³*œ…æ ´`–8%aaghV˜%D‰aÓVVI¹°Ã¡ëaÜa>i’êk3œ¯fV,³¿¡W<‡Ð:´iuÕŠ®Ï™˜ƒHÊ…$®'œ“‰b¸>í­Pï8V + ÛGYÒãlEaÅj µA#¿ý™%¼Þ°lðÃ;{è9t6 ÅÅ4!× lÞnÓæ0«V&É65µ)nz¬mŽÏöú6 Óã0¶¢°ÆÃ„9DãaVo¡AaŠ³Â _¸bL²ZÖ¾´N¯VL÷ÙŠ")ç…jÁb“Ä4ÐHú @©Oº!3/`ÅðµsðÑ1–1ÃIÒx!À³³Ð0‹° °Ø’YI¹\‡ÓýfI“T8hm9}6‹2û,.–«¤1‘”S$Õù1‰ë ç¤p¢ò#VV oVãœÊõ'Mèƒý0æñ[°Y2ÉtsÅð&àH¦Œ)n¼ñÆxД>Ì`…-!,pp’U ÷}˜ÖMÊ'üC²ÃŠÂ-V Oغ…9”'Y-‹Ðáæðzµb:ÃbE‘”OX’‰bùAˆ‡.Œ§â¯08Ðêöì†@b*ô‰ ;)`–xfI¹„%)®"SþZ"1lŒIªO8XšÍRhfVUríʱëÄÞâZC׆Bb]$éw®4Iu¾CLâzÂ9)¤ŠiœƒÄTèS[7¼1‰ð^“~?ÑX…ôª‘TœèIxÛ5ä~ž`$“Åâ ¤Ý0S XURÈ™'u™_æ¥qVI¹´Cy’ÕNF{í¾.M*jèRRHÓ8+á&CóÿÌ ÔÎЧ’êÂFDR®Æ’ W‰)-‘˜N6Æ$Õ'Bj0#L •Ú·6iÙ\ì¢â@R"¤$Ê\Kæ)Š‹iœƒÄTèS’ø§4i›{R®´‰Ä)rÓûHÐè%u9SÍp“røV,L\/^¾ÌV›ræ«I]twC¸Z˜%ç`E‘”K;”'YíÄŠö嫈;,Ì(’rõ]J ©bç`Å€b"õ-©€¯w7T^K$¦“1Iuä`ù‚p¥ ³—Üßðfroñý?¡Y…Á[¶lI'5Ò„U\ÚBWšÞ ÕÛ¸}óeŠi’ê¬}™Åªpo Žð¦–”+m"qJ¹i@4,áxÙ&¦ˆ") ©ubÍ™ô&…Åð—ÝŸ«×ø›v«²„ïÍS„u­˜ÆjERNYÂ÷‘aëVæPždµhÅÐx}„>[èBž±¢°bºKIáDqƒàH?ÒêHÃÓ áÂ7Ò¸«(„ŽÁ!$ãd˜YDR.aI …«ˆÄt˜îpè0Kš¤úd‡©f,¹¿"\<"\Fb6—”ó{¿÷…§È¶o‘Y ïq†"ÆjER 1å´Yn`á")WÚDâ”ãÞEb€Ž'™ æ¦ÖIy`æ"[aµîAûÂ23ö“ù½‡o%mnb …3æ`EaE‘”OXÂýÔÖfèŒ0‡ò$«E+Ƴèø„-Zà"£ž„y¾ÆJÅt—’‰â A$¦ƒ÷3©†úÑðtC¸}H—œ¥Íì¤ WQ8'ÌA„h6¤$„YDR.aI …«ˆÄtÂÞì="î0Ÿ4IõÉáì7ÌXrE¼º®ÌÄ:@¸íÒ-ùÖ»¥ ï"1h-)œ(º·‰j¯êÌbyÂ[­ËΗ*²eaµy$N'ÜBþÅ ›sèdlŠ˜G˜Z»x˜Æ+Ê5‡ôiaݤœš¦*‚0K§j-´œ˜¢ÉpRŽ,ñÖã夺4Éj'¯¢µœ˜²ú,KR—?\ÂâÕ-¨vDˆb\Àu¡áé‘冫Hd:ȘTX£f“r¾%)®"Sdt§l\Lºx’r„Z¶ƒ]¥!7_É™h[!Ÿ§2‡ÕŠÌ¢Þ&D™«:10Ê?t#¼S”!l4¤uáýÈFÌ–…Õæ‘8EnjG{dÃ+2‡ ¨.ÝбXØ™ö€ƒtƒÇ2 "|¡îPˆo[€ð± çÝã¦#Ë—®1"ÖöÏžj%WûôiÿO\+gç CíÁ;¦»Úž+/µ’«uoøðŸ:‡ã»·‡ÚâÆåéj«j\M¹ÚâÆÕZ¨•ªm\ûj+6®qqmqãwqãéÃ]Uã®VŠk‹Ï¥=QKP)ÝðΛ¦#Ëu›ÙþÙÏ„ZÉÕöw"ª•³sPƒ¡öàÌÛ\mÏ•B­äj]ã>ü'Îáø®-¡¶¸qyºÚªWS®¶¸qµj¥j×¾„ÚŠkGœC\[ÜxÅÃ]ÜxúpWÕ¸«•âÚâs©î'êÏÖ®M®…áÈÁc?¿êÓï½ÖÍxÎ[2ù½ÏÆšµvÞèyצ%»ó¼nÅ,çczô¹‡œçø¥SiýÖÇKzÜýçyöC79“s“œƒI«;7mÂù˜Ô%ç©n;“Äyj(œ)íɰ;•ö´çÐ{žg[ {Ú“aO˹IÎÁԾÞçÙÃ.9·ÖöSg^zÚ¬ ï½´î½W¶÷ëgûÞ{ã`Ï‘§ÎúÖyËæ<øÂÆãïü"ùü†Ú91÷{»÷€f±Žm~.ÔJ®ög«—ǵrvj0Ôö½¸ÛÕÆS}ÉÕºÆßX·Æ9¼ûfo¨-n\ž®¶ªÆÕ”«-n\­…Z©ÚƵ/¡¶bãÚç×7^ñp7ž>ÜU5îj¥¸¶ø\jѵeÓ -º‡âR4q)Ju>Q—/}ûÈ‘äZެ}q«åÞ7ýͱ&.›©ÙÒŠç—lÛþ„›!„B¨ùšµv^ø2à”Û¾~Þ²9=G_M>Åa°ïéYyò÷U¡j¥ønÃèÑo¬_Ÿ\W9¥¸Q§«Ü=BíÇ/ß}ïƒïÜ yÌu+f­ÝüH<³A!„PëhýÖÇ'.›yÆ=W…¤ÃøU<ö³ä3ª‡¡Úu|à^û#+V$×U¤Ê×ðJ7Ìݱ®vrìK4 „B¨Ô³såø¥S-éðàîç“Ow¨b„jé„jÖpI7<ö³3ܤÙÉYú_¥"„B¨dI‡þo޽’|ÌC•ã T»H7 T“úŸŽ1,žÝ°þ•O›}õˆ):óÒ»p³„Bµ«^ÛõÞ/ßI>ï¡4Ä8Õ.Ò Õ¤’—P‹3wǺSnûúˆ)qÏUég}#„B¨½õòæ÷Þé[{¨'ùà‡ô<¸áÃⲎªJuH7p)¢NÖ0H7L|æQû…çÙÝt|ÿÓ~‚‚B¡ö׊œrÛ×Ïzdÿ”Y©‰B¨*õß ¾bEÅ;Á‹Ò ý¤ÚE¨CÔîé†õ¯¼h¹†‰Ëfºy B!„†V<¿ä}Ó/Ñ'þYÌH&P‘ÔÄ!TµJ@º¡lµýÝ ïô=øÜ"Ö€B {mÛþ„e.]»8™@1©‰B¨j•€tBÙjïtÃ/ßíÿ1gj:‚B¡a©Ï/±»çîX—L €ÔÄ!TµJ@º¡l½Ý{ çÊË÷´ç³—^Ûåf!­£Ñ'X÷øÜ¼*gB-¾ç–¤O£GÏ»}’«M*NæÆk.]¶àö7w>•éÓÒ˜Ìüá5‰ëÀw­yÈù „B™ºéÉ»GL¹è”Û¾¾þ•“ùdÁóéª]uxv—"êt½{<¹Ú‹Ÿíwó–RI=ákÿœWåìC¨¤C'(®ÑÞŇÄZ¸kq¢!fÙ‚Û'B!”©qO1å¢Óï½6™@üûBµ«ÿLÁ¥ˆ:]í–nX{¨gÅÞVÿ ECànpH¬-“nØ×õHÒ¡È;$Öâ4AbÊßµ-+$YpB¡2:¾ÿéëVÌ:øÚ¾dfYã T»H7 T³Ú-ÝpƼï÷ÿhó§óÝ䣥”иkˤ–-¸Ýú3ïöI¶àn40£ˆ!qïYD°8…[Â&ÞÜùTØ®jƒ'B!TA/oîŠä@ŒƒPí"Ý€PÍj«tÃ/l1å¢ÓfM8¾ÿi?íh%Yü,,–Žop0»i_×#!×B|A¸û ΄Að<ôüãñ#\#ºñšKmEþ¶P2?’X#{RÎO7$Õ';„íŠØ®}TOdÔ«–3µ¡}´þÛþš±x ÌXUË¡!„PkéÍCÉüRã T»H7 T“ÚëŸ)Ž¿ó‹Sg}kÄ”‹ZÿŸ/-d¯m]¡×8€7»–Ǭ^|Wp±qž%DÚŽ8ÆÎ”uOØ"ò³ˆ`‘j¼»A*î›í`Œ,!/˜Œ¶`ý·bÜ¥$Ò{„:³há|g—2ßMtÆí‚ì2 jqµQº¡çè«#¦\tÊÔ¯µøSLIÌz"¬µoÝí;v³ « ±·MfQU°Øý vc¿^]­éÍO)h_|Ï-!¨ÎÇÉ|Ôx°Ø†DøVߊm"¾¯Áy:{Z[V.P?]ÞÁrRRÎÂ9X1Vñ@ ¬”s« „jeyïwú¿ŠØ»5™.@Ljâ×Yô¨aÒ®5  ÙÑD‹KoÇnW«2»Y¯¡H·Cº¡Q²‘5\fAÃ-c·•B'õ–᪤¼w“ ëÖÆ²‡U†Ò—ÊðP¥ì!‘g=Ð?¤ObÖQk|ƒƒ-ˆLÏ>bÊE§Í¾:™+À Þ>r¤çÊ šé¹¹_Ý•9ÿWQÆtt#‹E™™UµvÕr«Õ«ûŽ6Ý œ©xCVÿâCn2ªÏWLø†¤Ì%mN=ÔêòqUR0jÓ™ÁTA¯œÌ9ïg)yM…¨ŸéÕËìfEYÔ²!säƒòÚ1£ZÐrf'cÙ¶JŽX£T‚¢tC“/EWúò“ÑYL6¾éci/¶˜dŒDñÅ©±ŸøØôÛnÑŠêOú7jGv÷nb¹ ;K2œBgª½e‘½Ño@VUþÝ'íß~j—?Âüå»ï½´ÎM2ZVIÌzrÔê~;`Æó‡‡„„€ß~æ âŸ?H‰5Ú\R. ›]!Ù‘”KDà‰_¾gØÙ™ÏÅ4cÈ•„-†ƒÄ™‚XV+œÝT0P5¶ŒB¨uÚ¬ #¦\´öP…g¹uƒx¾¦Í§è5s )›'»ù¿µ£ª`QƒvËCÀ*&,Z8ß šr«¨×`‘œT¼!ÕÚÏÛ yÚžZDãöHrë ]\ £âE æF âîÇÊëd¨Ík*tÀ†ÅPgºev³À'=j<O™òÚ±N†ãkÄ4U5bSɯf‹Ò M¾u…Ø‚U¨²‹'œŽRñ±ÔᑟŠ2*×rzuóN… kÁšµ‹$®•¬çá„3Yçm­L‡Xæ0ˆ‹ÐúhÄ´paç]*yþí§vI7ÍM/ZYIÌzrÔ¾o7Ì’ K!$ÿLaŠWÿ¤` ‰ËY¤ï¡H+ü#†KjHáwÖš-‹Ø'S‰_‰f¢4·ðƒë["{"ƒdEaE§‚ª±e„B-¨qO1墛6¬L& 0À b›9óy§ôüß,š'‹ÂM•5ÿ·Ù²Š Ä€ŠòQ˜ ZE4aÆnR™Ù~Å M¿íä]­ÉÇ<­ÛòׂdöXrÖæÂ·ŒÖŸø ZkÁ: 7E¡ñâ^9åuR*nÊu@µÈè5¬n>rЂZÊóÑ«Z–,zRÑF@naÝXyí˜Ñì™´u«±ÆihÒ µ\Š:*Z¶1 ®ª0|6¾ÇRãžÞ´—rÖ²ü‹/†LY ¶¬‹\Ë–¼²]pÛµco§B¦C,s°³§üEhû+;#ô”waëÕº-g-Hf/x#h3µKºáÍ—Ýô¢••Ĭ©¨5¾¡ ãì@@Æà`Š"ᾜQt l(ïkü°Jx@cPøC‹ÕmYÄ>™Jü =3wV„€_ÒžÆw(Ú£°/‰)gCUcË!„ZPSWÏ1å¢ñ«H& 0À bÍlc\mžlž¬9¶&Þ’Í®Ã|Ûd±M¹Mš6ËÂI6ÿÁRf¶_qCã¾øõ-Ô:ɳ IK{gUÂP‹©b¯œ :YÜ”uÀ­+KÜI³TÜÍ´OúI6þ!êL+ÝN™NV;bÓФ´«1®6O6²v…Øx…sÑ.ž0 eŽ¥ŽŠj'8¨Xpñ¸‹!Sj!Bë¡kÐv!>c,+ÜÒNæPíE˜F¼\Ø’ÜÜ~û·“Ú%Ýpô€›^´²’˜5µÆ_¼Çö]k 75h!<¦ÑÉ|ÜLjÙR ªÕr¹Ó7/˜BÔ-OW¥ØÛªlC¶,bŸL%~•<÷u=~U!´S锇´zñ]¡“òfÁâT0PR--#„j5ÍýéüS.:ë‘É„hòWªšÞkÍm9Î5Hš6Ë[$YÂ\:ÓÁÍäËÌö+nHÓþt#Aª*¿ã’Ûº Eºñнr*èdqS™p”T¬¸›iŸÌM+@Ko1Vº2¬vħv½»AÒB(Ú²¬ªÌ±twX­ËPÄr‡0-k0nA‡S–8£a»`ï&’TToÊ9ŒIæé%ŸR™µnâñ r>Å[©öݧؿ]Ô÷âî þÓ7Ö¯O®‡VeÛk‡Ö¿ðŒ›^ „BÙ³zÅ–åGÞúy2i€ãhJo“g½º|AÜü?Ä ÁA’%“àfÅàor3ù2³ýþV²ˆûc1Ž,sçÜG:’ìaG2¥½S`1E ®U1˜[7J±ò:i+¦±¦2;à†HR±x7¥´OæZ¶E‰³¥×*ÓI-gR0b R[>»!X4^6d2ªJf·–m9ÈKûZÞâdÉB_[6É¿àbHËÎéî­ëµ¢Éîˆ;l}PŸÕ=I.ÁaéΙƒ;½$3±ñ±åØ_ÒVb£¡kÙùô·’E8q«}÷)ðo•¼„†œ³™1bÊEv=àf!„Bïz>™1À †ðŸ)ÒÓr…<ÂüÙ‚¿iг}·ÉMÔòXô¤i|ÍÉ’žðÙ÷»šÿ¯]µÜšµX 8È¢¢ë¡$£°Ub‡™,nJ ª-"IÅ‚Ý4¥}dÑþÆɶ¸há|gJ·S¦“Z¶k±špÕ!ÝÐü¦»¡@‘ªŒ6 f×r™ciçŸ ºT U/'5¢ÚLÆ¥~Tpv–t¨ö"´ÚØ_r'¥aí'nµï>yþí¢vI7œ¹à¦S.Z¿õ¤G'"„Bõë¥uÉŒbR¿FHsi7O¶È""2o1Ž•ª8£…Nn¶ï|*n(–ELq ¥bA £Ù¾[\¬aC‘Žtªê•“ëdqS™p”T,ØMSÚGûžÞ´½:{Pº2¬eÄꯥúq-6F™#kCi÷ÈÁŒ%¥|²Ø‚âÞPUñbpR¯T«Wõ!–]À!Š–EÅ‚³³¤ƒ©ø”jþPµï>iÿvQ»¤N›}õˆ)Ù³ÚO/B!„$H“šø5B™óû6.„'ìhœÒ6·Áb’g°Ø <ö©¸!'9Ç=WÑÅP±T«à"¶¸à"/Ò©¶WNZ7t²¸©Ìh]c‹Š»iJûئãhT²ˆ¿»uJ·S¦“5ŽXU‚ÖM7ØEbÇIf,,eÑI¯ëÙE³Ç–âH;¯Öº§þXÑvÁ½›Ä*éPíE˜®mÂ<ãQ±ø²tþí¢öJ7ø‰B!„ Ò¤&~Pæü¿{ëzÃWq a4‘–EQ€ü%MæµJ ì†ùÈb÷k9¡X#‹Îï/Ž}*nÈ6aUÖB˜Åúàb1Ií„–µ–ºç¶.»9‹©b¯œ :YÜ”ŠéX·cKñnšÒ>aÓmH]²ov ~I!¥Û)ÓÉjG¬±*Aë¦$\!³”?–?‹8Ì–¬M;<™C¬Ãÿò SÁÙMªÒº¶l» ÆC­SI‡ô ŸRªµn¨ho.Z®ûµov[=\ØRúR)öo‘n@!„ÐpœÌ»ÇYöèÛ½Üܯî²P"=ÿwóp¹ÉG–€œø—ÈšHÛ*Bóüà$-0MÂ3} 6¢'a“ù°¢$·Ð^Y­e†Ö•´Ô³KPq¯œ*v2¯©ÌdQÁnš2}ܦå`aQÒí”ïdùk¬JP”nhò¥˜>ÿÂ1ˆ‡ÏoÞ±´£%´ÛµzñÅ˪E;»ÉikßvA‹bUtÈ<½Ln—E|J5á ¨â…í.•bÿvÑ»oöY¾ôí#G’ë¡U!Ý€B¡<6kBÿ<"ñ8üæHSt¹ÄR•ÍÌ¥ôLÞ$‡àS ‚ UìÃëù¿PUÁºU¼éX=Ë7•)­[°›¦<ÙË‚ 2Ûʓ֕œ±iêÿ¿Ñ£+þ‹_Qº¡e/ESµÇ2VÃP©à”ŠG£Ao@[—Tå.•bÿöлǓ‹¡…!Ý€B¡<‘nHÓâ1NåÍöjšJÞ ÞÆé”'Þ€ê¦vH7œýø]£ç]ëæ!„Bé†4¤ª]¤:W¼ÕMínè'5·@!„’H7¤1ÎôÛný›&BÍé†Îo@uQ»<»¡ŸÔÜ!„BH"݆¡ÚEº¡šÔ.ÿLÑOjnB!$‘nHÓwðà†ÿÉëÖ¸¹B¨¼ênàRD,Ò !„jw ×tûÇï¿é¦Áß…ššø!„ªRÿà+VT¼‹Ò ý¤ÚE¨CÔ é†}Ï.ÿþ·n‰5wêìØaÚ÷owqí#÷ü‹«Ý´ü‘P›n\þ¡Vª¶q5j+6îjµ­¸¶¸qí…«]½èÁP+¹Z׸ÆÐ9”oüõí«\mÅÆµJ¨­¶qw¸‹WS®6>Ü/>ÜŧwUs¢š*žKåçD *>—ªjœÕTñ\*ßø0>Q«Ò°L7ülíÚgN?½¦yZj⇪Z% Ý€P¶Ú%ݰöPÏŠç—¸¹EE½õâúWölYµô‰?ûð×Ç~tBÐ%þðµžçƒÎû‡ïƵR\{ýU·»Ú‡î]jW/ý±«•¨•ªm\ †ÚŠ»Zm+®-n\{ájgLžj%Wë×:‡òïèZãj+6®UBmµ»Ã]ܸšrµñá®Øxñá.n<}¸«jœÕTñ\*ß8'jPñ¹TU㜨¦ŠçRùƇىz¤gCßþçܧyY /47Ó í™ÓOÿÙÚµ‰i¤&~¡ªUÒ e«]Ò §Í¾ºÿ[ 7±(Ô»ûŸ=¼gÛ¡=Û'^~ûï¾Ü¡îuaŠƒB¡–Õ;/v¹ÏôRvì½îºw×ö÷a©‰B¨j•€tBÙz»÷@Ï•—ïéI®…V¥ÚtæåüÓ¾·cÓ†C{¶?¾dÙÈ‘ç¸/šB!Ôšzá¹U]-rŸì•'Ãóéª]ý±Òĉc%‰P¾Þ­-qÞªM7\xþµÿñ·Ïß·có¡=Û¥?ýãKþâ#ßt³„Bµ þú¯®üï¼Ð}²WV›£dã_þeM?8þ}¡ÚU‡¦àRD®á˜nøß>ÿËãn²\ƒô£ï9òœMkžr„Bµšî™>¯ÿS;z®d)µ3gÍZõ¾÷­9õÔ:þ¾µÉ1΢…ó'ýàZ[ÖÂÜ9w†ªFhï ÛFŒ±vÕrgNû‰ûâ´pø¥íìôÛn‰k‹Õ ãÓ‘n¨¿jy³Ð™í,5*}©Ô}µ\ÉÃDÃ.Ý  Ц)÷ÞõPH7¬]¹šßS „Bm¡=ŸÑ§¶û‹ÊjOì¦#zü^fÑÌÇfì6K×¼]˯¿úRìPwÙ—ýøagNÒZb;´2ê„ñiŽH7ÔYµ¼Yè„®ûií.•FlB úJ&véMP4M±7}í­Xü¨›Ð „B¨õá?¾ø³w•û|/Pûþæsgž¹ê}ï;8kVR®ÍŒq4—¶)´‡±ŸøX£om†}8ïà†ukµ¬×Ø!–ÑÂù±¥^ã“n¹Óԉ醆õZÞ,º·®×ºW ä.•ºo¢ª+yXªª¦[×gÔúäAQUºá‚s®9ó¿Žs ¦WS³„Bµ .7éïÿ¢û|/Pû¦ÞX¿¾ïàÁ¤PWšãØÚ¦Ó R´¬ vìÐÕ+œnY)ä±/D¥ åICáê5>é–;M˜nhÜQ’7‹b5ú­¤ª+yXªªtƒ>å¼küø¤ÜDÎ~ü®Ñó®us‹<ýâÅçÖ®\ír Ò«{6¹Ù B!„ZP+?zë÷g¸Ï÷µWº¡yßܤ&~&êaº®¹´‹ ²ô·zªÕвgÞO}ø¥žµ«–Ë!’”‰¬ñî­ëÝT°é°¹Ðgù¤û ÉèV¯ªÙ`wý”% £<Ç~âc™ã#YÇä|Å„ohA2O3Úøh Æ!oóZî4ÕáŸ)úIµÛPéhU< 3O‹ŠG½LËîüŽ¥uÃyYðfQpÊÆþq±ø,—ä™ÙsÙC—‚%½<¸ö­qkDUWò0VUé±küxù¿±~}Rn&©¹EžÞØwÒÏ(‚^éÙìf3!„jMÛ»Î}¾¨]Ò o9²ý¼óšw¯hjâ×Ù<<½,Ùd[St/ZoóÔÜÛŒF¡¨vÜ¿T  bzo+–[]’%®-ØtfŸí‹ØØMRQÆðŒ¹A4›×OU…¸FÄ8sçÜ™¬|ûöÔ6§n[LghÓñºnë"ä¼–;Q%h•tCɳ0ï´(8êƒ8¿Cm©øf÷-\±OX«Ì*R™ž»6­˜Ù¾»Š¤‚öÕ‚V·åâ+y«Útƒ>לzª>&“r3IÍ-òôúÞM.Ñ`ÚÚõ NBµ…ÞØ»Þ}¾¨-Ò ?[»ö™ÓOÿÉ)§ì½îºÄÔhR¿&˦뚊kŠ®™¶æÛÍêÕŒ¨hAŠümEÍö5ÓsU?Ð= Ò’§jCÐ!·EWÜtfŸå,cˆ¼L¶kypÍô³¤äo-«Y-HÖ‚Í®ÍÉhdüKùâAÎk¹U‚–H7èUu¦O‹¼£^mËáR//ׂV·3r܉_"Ù˯R²ç®M+¦ÛO_EÛGÕ¦Ä+>¨UößtSRn©¹Ežz÷lv‰Óï¾Ü…ã&¹Ù B!„ZPGz6¸Ï÷µxºáÝãÇ_¸ôRMŸž9ýô¦Þ"ššø5Yaºîì6i'䚥Ë'œÔHAÜá$OÍùÑTqÓy}¶ BÎÁ¢bØÊàš-ègURË.b›sËaZn¥t˨´Dº¡äYXñ´HõA_6å•Ù7u#Þ®ù¸Ô@ñ*%{^n(®Ší£A¤DÏĉǶmK M#5·ÈÔ7ßqÃõ³]¢Átöß|ç/>òM7›i¨þé çüÚ¯ýÚÞMk‚eǺ•²ZvùÈ"ÿP\ò/w|ïªË>ù‰¿”}ÂÅ_V18Çzâ¡{å6ÐÞ¯ÉyʤkºV,ެ*¶É_ëªå¸'&YBÌRÐŽT¾6^n#­zì~­¬ÿUmZóxì?8iÌÀK[T—œÑ)é\±Cz¹|7ÊÈZv¸3c(Zq;ñ2B¨Õµbų-rŸòyjñtƒ&Zšn½pé¥Í|ÞöÛGŽô\9A3=7÷k¦ÜL>HÓuÙQ7±eAD(æµlÒT?¯¶â¦óZ¶ÿì ?Øpò3æ×lA?«’qáaææÜ¦•vH·Ü‰*AQº¡i—âàÎÂ2G}ЗMye¶ blt>eV©¶çq1³}7\ÛGýj‡?Âì'5·ÈÔßuÅÿþ««\¢Áôµ 'ÿîûǹÙLC¥˜JO>ñн -»|d±€Va˜‚=ó‰‘1Î\H.þrRw2wÞzCð1K(Ʋ*aÛe=7Ì/§U¾6^nu­XìÒ.™Ôžq°vœ1Oê’K-9Yk™ÄÅ˵ËZË$Œ˜Ã*ƒVÜN¼ŒjG]Õíÿþ7ÏsŸòyjýST|Ô\Ýi…ÇágN×%3‰¿ÕºŠÆm2ˆkUL·¤Ù¾ÔàÜ9w¾~ò- ™„MçµlßY† Â2¡¶¿‰,*6›×Ϫ¤\x˜¹¹t\Y<È’Š®åNSɯf‹Ò M»íø¥)> Ó§…Šî¨÷·’EÅó»¼2[pÆâb¦QË™äõ<.f¶ï†KË™„öQ¿Ú!ÝpÓ†•—Íts‹Lýá_úò¸›\¢Áô£ï9òœ=ŸqšÆÉ’ ÷Ýyk°Üyë fÑkœ0‹% ìžü!¹°cÝÊ ™…x³è5ÄœòW –ª(ÈY•6bé Y¬ÂUeªØ3®—ÛEpwIfÿí°êX8{µÊlµ÷=»Ü}ÐçªÅh~~ÁÑúéÕ:…xÛîP(¾vÕr«²°<´ ‹Šé–cuo]oIÍüÃóTÖf¬°i-Ë!³ekÍ<µ b¨êot°Ífö³*i]fnÎJYJ·Üij¿tƒËXÅg¡;-$ÝQïow°çwIe¶ k#6:Ÿ2«hYÈÓ)¯çqÑU™ÜpiYX›±Bû¨_5¤Þ=~¼9·ž6ûêþo-ÜÄ"¥·zžÖÔdRÎ)Ì}Xµ«—þØMh'Åü.¦ ?¯pv-Ëb9-ˆPe²U‚Ýî†?¾ˆß(! ¬”€Y•e:âïɵì"v>!ò½«.«ª6^–´_áNŠaq» Y—âûDì'$fQ­…"ð8)cF ¬ìaÀ37š'µGõ¶V(9{A—$­ŒªÒt‰ #xw5Ýr,[ËcÅaÙ «’ ºaF7±ÌÁ¥Øž¹l¨6Ý}*v&oYíX›éq+h!4TÒçuÿ§ö¢Ýg}®Z‰½×]÷“SN90ujR Z9Ýyr,ÞîÛA7ÉÏk9- ªCj â¦ Z¶¦œÛ‚âšPUK³&×Ϫ¤]x˜¹97†YJ·Üij§tÃàÎÂ2G½öó»¢2[p ã|ʬRmÏãbfûn¸*¶ú5ØtûÇ?súé/\ziRn$%Ó ûž]®©É·ßï ¦ÍëÖ}⣗73Ý Y-G`y‡ØŠq ' ò|ò0'« É…` ˆú“6[Vû-!®.S/K±§ -«*¸)8”%Üû۱܄#xZqÂ@îCƒfƼ¦¥}‘Cævb{q—l_b,ã0O©¸«ÖT^ll«8c¬Ø!,Û‚aURA7ÌâF8–98£ÛÓË!øÚz¼;“·ìVŒkñ8#„†D;ºÖè3}îÔÙî³>W­Á±mÛž;óLÿò/ûL¬CA+§ìñj!"HKµWœüÛg7«Ïk9Sò ÑSÅM·¬*ulúm·¸@½ÆfMr\l¯]ÊJZ.d)Ýr§©Ò ƒ; Ýi!¥z]ÎïbY Ún|_€‘Åw"¯RmÏ㢫2¹áªØ>ê{q÷†ÿé ’¼cܸŸœrJž±\2ݰzуšš<¾d™K4½Ü³ÅÍf- ¶í¶»ÙÁB2‹¦b»-$¦¡£•Ó Š#X•|¤µ«–k£ëXH‚Ø*%kãeó´e“Š–v‰i‹É-´A°‹³ÚŠŠòŸ•Ý‘ 6š–e‹âÕû›Ë!v“òºd©pÜSì&Uìª6á,±úÛÊÂ9/KÅÝðõûË2 kY1^™&ÛGõÁŠe:“·wÒ,¶\Õ)j¦>÷ß}ðŽ9î³>W-ÀöóÎò›­üÏÒá—zlÞP¤ž\   µ i!¬^в’B±€s+ÞtqËV+´àªÑlq?ËK[±GX$ss.PÒZŃl>®åNSÿW³£GWünµ(ÝÐÌKqg¡;-¤Ì£>ˆ–«’µ°háüøªˆÁ'l¥Ì*RU=‹®Ê”9\í£Ó âðܹj¡Ñ¿N,™nz÷nq)†X/ïÙî¦2–…ʽ+€Ñ²ÅQñ÷óí§ƒOIn ³å0a d _˜kY·`‰‰íÁ-V¨Ò¦µÎ‰ Ôg´/§-ñrÚ’^Njµ×Z°DÃÞèw 6’æ&Ùà8¬*^Ž-ibŸ t•Yb>9ðÿ£!<6tI£m¡@7/Å4Á!øÄ– ‚*Sì·Ši2Ó2‡˜ôˆ™=½œ¶Ørš<ÏôrÚbËi‚3Bh¨t¤gƒû”ÏÓÚͬxqg2i:šù?—¥HMüZPšÒKÎhzýÕ—òªÊ¨ eSE‡Á©ÚfëÕ 5¢sÆŠ*3ȃkyø¨Eé†~\‹WígUÞQ¯½åL©MêÛ—¼ò«]Eeš´Ý~›ªötƒØø—¹ê}ïkèGlùtÃ+{·¹ƒ“›Ê4Z$ÛmÛd†*g·Û¼ ü%‹·33’ªDzÙ)TY'-¶·8Ù:3PŸÑN¼œ¶ÄËiKz9ÕZF=Ñ^ÛWÍzµÝ,ÁMqû’¹Cb_† «—cKšØ'(]•¶¤UÜ%“‚mûR]{î[qnVL‚Ol *¨2ÅyË¡˜&Ó9­ŠRì/§-¶œ&Ï3½œ¶ØršàŒBýòEÿAŸ©¡ú#̾ƒ_}ôѤЂ¤&~¡ªU‚–K7´¨‡ÜAI bÔ|Õ%Ýp¼§çgk×&…ÆP>Ýà’ NçþÃõþã‹ÝT¦Ñ²¨Òng°Þd)³‡<‚¤¢HgÌnËv?‚Zµ±bÏxÙ)® Y½†NÔg´c{¾–wátqm¼lž¶œ–íã}¿F‰b7;äÝèa»,ñ²©x£NéÕÓ–´œëR,»—$TÅËR™®ºUbT™b‡¼e©¸Î9­ŠRìcËîü çdùÎä-§-U¡&ëÝ»Ü}¦†$Ýð꣮9õT©ånj¤&~¡ªUÒ µŠtÃpU]Ò Mঠ+'.›éæiýô‘EkW®v)†X—]2õwß?ÎMe-û®Û’ ö0“ÒV«×`·pZñUøÒ[ñªÃWú’%)Å÷8hÙ6$Ì/;ÅU÷ºNÔg´c=W,'b=)Y/ÛNÙï#$Ë&ÈߊҀo?|†¸]XãÁÇÆJƂ͙*n4– H}¥þæNn0-óÉëÒ'nf±Z—lðúUã»j¡x|VÄêo«°«±CÞ²TÜß²[ÉSìcËÖ~½pN–ïLÞrÚRÜ&Bhhõö‹ëÜg}¦šœnxûÈ‘íç§T×gÛ¶-±¶ï?~dÙ£o÷ps?„Pu*AQºK±Œ¿Ô3¢Êg¥bÔ|½ûfï‘åKõ©™\­Ljn‘Özÿ¿vád—bˆ5éúÙ#G6û/î,z1\j ±¦ƒo_·:”Æq¯d‘Xš‚ûócÅU¸áGõVL/+´˜Ùp ŽâÚxÙyaëRøUB°ØÈÄ¡ ÅŠi¬6^6UÜh,KÄGÍüC1SÅ]J׺üŽºgÅŠ]µS(¬îdþÎ+vH/—ì†m9S¤ØÇ–ãs;ôD*ß™¼å´¥ªS!ÔLÝzCÿ§ö¾g—»û´š™nxcýú§O;måˆ/\ziëÞ×ÐÿLP»«ŠäRD®wëöI©Ýþ‚15·HK“’‰—ßîR ±¦ÝrŸ|6­yÊMh*Å-Isò÷ä rkVl³ä_î—b!Ũòw>’NÅä!XRlÿĉ_˜Ì[‚\•5¢×`‰âeI;byûj½|m¼,ÉÓr Bû²$& §Ã×Î’Åÿn] ¶lUñrPñFcÙ1Š;`k…bž º$©Á0Úq²Àî1‰AqW-s‘ybH¶–3ÆŠâ媺aÆPL«¢ƒûز¶¨ iAÛÕrð”Jv&o9ÓR<Ρ¡Òc÷/ѧöŽU»û´š™nPìðÜ™g6úg¤µCŒƒPí*y'8é„òU¿tà —^ú“SNiÔ]…©¹…Ó[=OkR2í–û\Š!Ö’–6?Ý€ÚZÒ;c‹H‹S!ÃC!‹8B¨É²tÃêEºOü´†êQ‘­ 1Bµ‹tB5«~醾ƒלzêÆ¿üˤ\_Rs §}Ï.פäÎÛïw)†X–nÐôÅMh*Ð'?ñ—î×.­ u)¾a؈tB(hÓš§ô©ýÈ=ÿâ>ñÓjtºaÿM7µÇ/O#ˆqª]¤ªIo÷è¹òr]ÉõP3gÍÒuxîܤ\GRs §MËѤdÉK]ŠÁé•=[Ül¡bu­XÜ‚½º4,ïù'Ý€ ²tÃÜ©³Ý'~Zg?tÓè…““ C]9¶mÛsgž©¹f8‰©M ÆA¨v‘n@¨&5âŸ)6Œ½æÔSëû%@™¦Ø÷ìòk.¿í¹µ?uù§Wölu³„*Êž‘áŒC(u&~€åpé„P¬‘#Ïùþ·nqŸøÙj{¯»î'§œ¢YMMÕ0úÜðá?ycÝ7÷C•WÒ \Ѝ“ÕˆtñmÛôÙ\ß/N›}uÿM’nb‘Ò‘½›\r!­—I7 „Bm¢£{×»Ïú\Õ•wß0z´æHÛÏ;¯í~Fñ+R?„PUêÿ¿+*¾ ¥úIµ‹P‡¨éÑwð`²T'J¦z÷lvÉ…´H7 „Bí¢×÷ MºA˜:µoj8‰ÔÄÕW{_Ø6bĈµ«–;û0S‡ìf®J@ºaøkÈ/uÀYÚB J7Բ醽[\r!­ï^}ÇõWÝîf3!„jA½¾gƒû¬Ï¤IMüP}eȲ?ììÃL²›¹*AK¤¿Ô£ã4ý¶[b£Âc-œÑ 4´—¶Û¦á0K7¼²w«K.¤õç¹tÜç¿çf3!„jI5/Ýðê£öLœ˜†©‰ªE“~p­‹ÚZ0Ow²v‘n¨HK¤ì8é –î­ëe¹bÂ7‚¥qjÄ™7„Ú«=½uJ7¬[­f¬ºÿ3…cÿM7Õå‡eÒ ží¿ÿe—\Hëã½â¯ÿêÊÔl!„B-§oO˜zÅWopŸøi­ÝüÈŠw&“†êyûÈ‘íç·rĈFý™÷PÀóéê.ÊI-‡§;Y»:9ÝÐ+MœX1Vj‰GEºtÃ믾4ö“¿Ô|§FœyC¨ôî4ó2hô`6û`½{<¹ê.®Uï{ßæ³ÎJÊ5P2Ýð§üu—\Hëì¿ùÎØNp³„Bµ Æ}þ{ý³o¸Oü´N›5¡ž0(^}ôÑ5§žú“SNÙ{Ýu‰iXÐÌßÓ<ܺ·®×l<%E:kW-—=XbY­[%(å&ŸÌFŠ[Ìaúµr0) ‹dÑêrpöXª’›¦èWLøF܈µº– :i*³¡ Šû%YƒÁ!¯“æi Aj_r–¼-Z³ªröNPþ™¢i—¢§Fêæí~Aû&Y zžîp)5,Ý Lªëë•Lʃ¥dºáã½Â%ÒúÇÏÿà/>òM7›Aµ¦u-•®|âîo>~'BƒÖ OÎÓ‰ôøú»lõò†G<9sßÃßÛ={ܰ—vS;{¸k¡„2÷ùï:ãKî?­A§Ï«‰ÊsgžylÛ¶Ä4\hrŒ£)¨E7†ý~|ÑÂùIyĈq_üB<}ÕdX–¤nçšUôdB›[Ô¤."„cjaì'>–XÐ;¬kîœ;X#ÖIíf^'Må7$•Ù/­7h_`çuR‚–õ·`¶åŠ[´Ýt-tˆ†&Ý CbGH¯Zvµy²ãdGÝΆôy¦ÖòÎE»\Ý**ʨÀUËîD±ÓNö¼3/½ŠÐåQÕun*èvZéÆ'tÅë¡¡W{Åý-ÞzÜm5o(ŽâÝ/>Xy=wm†m•U#Ó BákN=õÝã5må¦ +'.›éæNŸüØe%Ó ðû_r³„PK©gdz·øG¿vû×ÿàoK·öGÿðÌd„­±+nÔ‰ôû®þõ™—^¸tÆÁÝëÜ)×L½øØ ›.ÿ­_û·Û®ü­×þî¾›ÿ˰—vS;»ñ’‘¿ùû^å¤XW\2¹¡éÍO†ÙM &§4ÿÔœYSPI ²èUFÍ«5ѵ)®^ÃZš6k–«ÚP”Cü»¸ÙÌF*¶`½ZÑVWÜdo½†>[Ѻ­íš,ÕZä£É1£Ùóö´ª I÷KíÈbc«¢^å/·¼Nºq0im¹â­Y×B‡hhÒ vx -»Ú<Ùq’¿Îr-Äg¡©ø\TQ˲É2vv&Ù²­+é„0»^mÓîÌÓ«6'YÔª¢[ñ&lEëUs.!+êÕŠ&p[Vû™»cF³º«ÖHÁþÊb>™[ÝÎ{в^ v?¯}½ô<ïЗUƒÓ o¬_ÿ“SNÙ5~|R4©¹…ÓY»ìì¿ùŽK.¤µvåêå‹s³™æèÕÏì_zÓŽ}lË5¿§)W×#†‡´/Ú£îÿlßÃßÓ>º½îZøÂ}—¨WÒsÿô¯\‡QžZäPÎæáߺû[o¹à­Å¾÷(BuÔ½Ç~¡ëÖ÷Ï™8$w:ôn[µãGí¾æý¯/ø»¾'¾Ô:úàßïþÞïm»ö¿¼²y™œ<}{ÂÔ¼ÿ‹î?­Z~L1\irºASÓ`ÑDWÍ]ÃìT2K(¦¥Úq_üB(¦›•d‰}œ\ šr»-jõð|4›äÛ\Ú¤ù³,aZž–jÕfl)ÓÉAlÈÉíWºA)LþUå:iá†E"Aò‘1¶ÄÊ<®…ÑФ4Ü1®6OvœtðôšyT<-ÂŒ#IÃù¤6ݹ+ö4ÙæÜmvæ…`ØúÜÌK¨äõ ¢ÛºtµL#fÉÜzèvñ»€“»ž¥tûÅ=×ꇾXÍùgŠ.½tëÙg'…A“š[8Mãw¯¾Ã%2õjÏF7›i‚oùöº¯9ýÐíöÚܳ4år“°ö•öE{tøŽ1;¯ýÝM—ŸvpÕÝnß›¬ž9_Ú|Ùoì™ô‡ê•t|é8×a”§!?”w¯û»Å?úàCßULè¢D„ê¨é¯ÍÿÀ}W_¾ü.w6Tžœ¹ñÿ÷¡á®»Tï=c7~ãÔOLvC”©oO˜:rä9î?­òé†cÛ¶uqÆë×'åáK“Ó nöžžÍ¦çóNÎ!³ÙâF\­Šé9¶&Õ¶¬ªtS²TO•éä 6ääTì …¢“<]'Õ=kÉÌÝìµåÝ ¶º‹ó¥Šç¢VQÑ~:Š¡Ež*æªr]ÍÜœµiþ%¤mUÜ¢¤¢ÛºtµL#’ŠÅ[/~p*Ó~qÏ‹}±š“n¨©¹…ÓÛû×¹´Bž^ݳÉÍf- €Ìû”›r ?)^ÝrùØuǹCòÝøá®…›'ž¾÷‡$ÅP»†äP~ý‰Yg¯™ì"C„¤<öý[V-p'aƒôÊæe›'ü‡7:×]h«Ÿ?r¾>Ë<ÍaÓš§~òÐCî?­’醽×]÷“SNYsê©í1ó©ÔįÊœ?§g³é¯VÔ$Öf¹¸VÅâiyq öu]ˆ•\qÀ7ƒª"ù2ìo4‹‚ ï—–]7b¥kÕ=kÉÌÝìÕáŸ)úIµ[¬Ã5?»ÁÂQ·nÿQÍ">UÔy`Ëd†*IÁ§ùërrߥËîμ´E²Nªå¸ØÌK¨Ìõ ©è:_—®–iDR±xëi‡X»oRÑ­n>iBÏ }±†Sºá­Ÿsi…<59ÝÐ}ãŸí¹aTGÀû§üñ¶ïþ‘‡FëàÓ÷müÆ©Ói¦šy(Ÿ|~Åî»ú¡w–¸˜¡iÁ[‹ëžomß¾ÖŠP÷94ë£îúêpéízóÄÓÝ@eªoÿ:÷‰ŸVÅtƒ¢…çÎpßÕ~läÆoþÆpz^OõóGÎßpÑÿUñ[eÒ ýʧïàÁ½×]×A75R¿F(sþœžÍº¯–5ÃEÉMw+NË+¶`y߀ڗ¸š9;{äï¦ñeb‡j7$çâý²C¼&iÕ[VUf'ã¨ÓâaE- âXtJЊéIÇU–p®”<uø%­ëÎ$'·-úL•ìܪêÛþú^B¯“ЏÚË4"©X¼õô؆wÙ‹¯g)Ý~Uƒ,ÏøÐ«ïÅÝ>ü§M{`Ò©SWŽñʃ&åÒœ6ûêþ›$ÝÄâdý¯MX0÷a—\H«™é†?úh'?ìµ¹gm¹æ÷ܘ4Býl¸ì7x^CãÔ„CùWýðÒw¸8¡&ès]S.\:Ãu”Þ 6^2Ò]S(¨ûªßyqÙ­nÐbíÙøÌŠz}û*÷¡Ÿ!8™·é¹rÂñ][ÜܯîÊœ?§§£nFmE­¥Õ5ÅÕ|X³ßءⴼb ªR1F>a~®É¹ù[ ’ªÔ¦Ûb,»XÝ[×[;;)U»¡Šûuxà/ðÄ¢…óå£W9„ÖÒ”¬yÆwCëÕj+nQvs–ÎR ŠÒ M¾u8ƒ%œ|vë{ÉsÑn‹qL.iݰ¢Fqtš>óÂæìL•³¥?T kÉnk‹dgd(Öý ½Ê¼LéÝ©KWË4"UÜzÁ»@ÅÝ— –­(Å=·ÆÍnëÆ‡¾²üG˜ŽçÎ÷,·iT_5úPþúÌKùóKiÁk Οp¾t×®»Ì2gßœÛÖÝ6cËŒàS»\›“WO=z´6:Jß{qΙ÷]ëNÈ:jÏýW¼ðƒ3Ü…‚öÝü_vÏç-é†AÓ´§)®Ñ,Ú«R^ d¹YqæTßìå»!OmÔKªü†ÊŒŒ<ŸÌNÊßb‡LÕ~,†¥Ú)ÝP•t° Ž·BJ…²Îh*^QU™—‡ì'_yo=Vźøz0åíN•ïjžÊl]»¹•2×s^û²g®›g¯¬æ¦Ä®ñãrÊ)Uý‚£Ó ŠÍ¡¹yU§©â<².zþâÇßË5Z>”ºÀ]Øú²Ô€u¡èÝÕNóÍs ^=ïj³¨*¸UÔœ}sl-‡ú¬Z×f(†Õ;JN7èÂÑåã.(Dº¡q´Eº¡qZ;ð<8wïÝ׫hئ ¤Ë&3-‡Ð ÕôtûÇ?}ÚiUÝàPÇtÃÊÇW^=aÚžLU»^¸÷¢½7ý‘›Wušš“nxîŸþnh´H7¤uã²-Jãoïj§tºáúÅ×›Å2zÕ¶âÛ2¥Õm-‡å\›.ÝPrÃF¤†VõJ7¬ÝüÈŠw&“ ÃÓ ¯Ÿø%òÜ9w*n’ìvà+N~†BÅêÄtƒÝŠ?èoõŠÕäGEúL–ÊQÇtƒôJÏ&7›i„˜bJÍI7t]0ÂmÕ]¤Ò ·6söÍqƒP:Ý`Æ…o&¿4ÉtH+¤n\v£–ƒ‚CܦK7”Üİ醡U½Ò §ÍšÐ?O€ˆO7H —æÎ¹ÓB§±ŸøØ¤\Ë} ¨Zu\ºÁuñã@ªE%/¡!§LºaùÂ%›×­s™…LõîÙâf3SL‰tðé§ðkÅä¶ À>vXðÚÅíRl4KˆóMö$ÉV±ÖB¨/çÐŽfl™aÚ\°§z•é·)Å醊›Ðºr°» ‹5k{g>j-vhM ËtÃk³ƒxé?ŽuU ð÷VeL¿âSÛæ|ÁÕþtÆgƒ¥ÑªøÞ²iÍS×^që¾g—»}'Ò iÞ=~\ÎÛ½ÜÜ!T^ý_ÍŽ]ñ«Ù¢t—"êdµKºaí¡žÏ/qs §cûŸwi…<‘n4Õœrÿý8{}Eºa؈tƒ“ý’¯8þÆña9(ý@…âfQŒÖN üÌ!øÄí„åssÒêVë’&×·ò›™ˆ€üC­­«×ÐH¼§-«a™nøéŒÏjüí}~ß‹«îýÎß œ“ù«m©tƒt|ÿsî?-Ò Ù¤&~¡ªU‚¢tC?®E„:Fí’nè'5·p"ÝP•lŠIº!Sîû=Rs²3­,Ò NöK »£ÁþÙAÄßö‡;XÒé†Pkò‘™np¹ ‘÷̈A§ 6nè¸~ñõZ{mƒ· Ý ÉgÁ¾öI¿%¦_ñ©¸Ê%¬n‚ Ý0ÜHMüBU«¤ÊV¦–<°täÈs»‰›ÍÔ]-•n°I§¢è`QQnÈ”›pÛÄ=½z)}\ZV¤b…ÀÛò ^[`ÅxK.¤—Òé»-B„L˜9Á,™é)ó‰´BºA ª… «umºbÞ&¬oq‚#ä&¬Ú‘§ý¤¢-4üÒ ‡ú¢ŽÂô+>~R¡…PëÞßì]HdÖ6A¤Kj⇪Z% Ý€P¶†Mºáõí«¾{ÅÔçÖþ4Î,dª“Ó šGËÀô’tC¶â ·Ýéྫྷ—ÒÇ¥eEº!–ýä!þõDú÷.†—Òa¼C Ó§d.À)¤VëÚ,¹ 3ÞxâRH7hY¡¶xdCÐðK7,½ù3: ö ½wiùÉ[õWÁñûÛk3‡|í“éÚæˆtCãàãÕG%àÙ eëÝ7{,_úö‘#ÉõÐʤæ±ö=»|äÈsî¼ý~—\H«­Ó š>ÚÔP\úc5§ Uînÿøç6} Øw鶬­V«7b~Ùîé†ms¾`ƒsø¡/ƪ²[⣞¦›”Ýóηc†:ó¸Hª GY ñSÜÂQÎ;j™«‹H7Äê?6i‚x‡g.„?t1¼$7³X>RÉ\€“jÍíÆèŸ)fl™aµ®Í2›ÆLT+Ÿô.·…†_ºÁÞ(ì]ëÉ[ÏÕrÈ&HîÇÐT\ëÞXªŠï-›Ö<Å?S þ™¡ÚU‡GEr)¢N׻Ǔ‹¡ÅIÍ-bYº¡Ìa¶uºÁæ…1á;++†tCzç‰ßù­¶Ó 7=y÷ÄgM& 01Bµ«äà¤ÊW;¤Î~ü®Ñó®us‹X’nØ6ç .¶ ñäÀ¼1;Ý iAÅ8øpO~Ü«¢}%^÷)f[§lLD|kCHØP«hnæcU–ž°d·ÈYËò”¿¤ù½ù»ãBž*†ÈÁj3ZX7³cµ‹tCP¸‘AÑu¬ð Gs“%.JáoB|nÅ 3'ø¸v†*Ý ™1~>…Sz—ÛBÃ,Ý`oi îÊU\ÛÕ+ÝÐ/8bœVÐÞ¶9 JëõW_:üR3¶ˆH7 T³Ú!ÝpÚì«ûo’t‹HåÓ +_ù‰^¾zéÝ„¦îjÄS‘¤¦’ö•»"U+V›nþ×}ŠÙÖé†-(¼U–8Hc#© ½–uŒ´lÑ~¸3"ü>BµaØÝq [1m±å°z|Ô‚§#8×(Ò A6°á.† ð û=EüÇ ß\ÿ…dãã"ígz 9‹¼tCx,¥ìV4»S-醼M„ÇXÆmª6ü‡k§]4ÌÒ vÒ„¯Ü§ƒSqm#Dº¡q4-ÆQD=bĈµ«–;{ƒ4ý¶[Æ}ñ ÎØšZöã‡52Š¥9µò@‘nèWݯºŠ ¶Ñu^QN:¶øX½Ý{ çÊËu$×C«RÇtƒÔ¦„¾¾Ž ‘ªCl馌.¬•ÜåouŸb¶{ºAcnÉöV•ÕÛM¡ÖÂ] ¦ w~ÎrÂÒխ˜¶Ø²ŒVŒZðtpwC}~IžÑþ®Â¢ôp@ JHÿ.C>–ƒÈK7H¡#ØcÕ’n27vPØ^˜›í¯”n§-4œÒ áq3±1¼9؃`ܧƒSqm#Dº¡q49Ý ˆÑÙ¤I?¸V›sÆÖ”BõÖ›&Eïc?ñ1•qÅ„o(*iÍ»-ÔUõpîœ;½Dº¡_u¿ê*6ØF×y±,—ÖÐ÷Ç«vùgŠŠéûgŠ•¯t™…L½Ò³ÙÍf¡ºO1mÊ"Xû"=Dª‡[÷­¦Œ—Ú·ôv¾Š"3p­£Ú=Ý e{"ƒ°±RôÓ²„‚½ÆIŠ…?ø{ù¸qw\Bãá@Ø}Ñáö «Í ì]%íc*®m„*¾·ìèZóW›°iù#îs?CmÈÒgvõýâ¤PoúÜðá?ycÝ7÷«»H7dª{ëzõsúµÎÞ4Ùqû‰iÄL*m— 4ý¶[ÔUglÕ!ÝдK±qªûUW±Áºoq¨¤7íK|Õ©¸háüP¬]ƒ«º÷¡@Ã&Ý Ûÿ¼K+ä©·é†gŸ^ë,NuŸb†x2&¤,ûpéûÊ˰Ul93p­£jO7”‰TšnÅ豢ꤞáÎsb:}͘>.™‡_dX1ï¨w¬FÕr(ç>ôã³.›­×Ã;×»ª vI7 B øÃïòTÆÇI«d¦꨼M,|s¡UiÁUµ£jL7|ü’;?|ጛg?œ÷fÕÌtC;ªÌ{K™?ÂìW2ö²{Ç|ýî9OllTÒ!5ñk„H7djîœ;‡¶Ÿv\4\Árø¥YD þlaíªåê˜B3go • (ÝÐk±ÝT÷«®bƒírBÚ¯ø²¬]ƒ«º÷¡@¤-M1?ÿݹ+ž|ÊÙƒ1ÅTt¾<·°ÖbT“}+uéÀ? ÚÃÉãôAÈG˜Ñò­ŸnPŒú·ßº{ñÒΫîé÷ý^øIEø‡UÙ0Tkv“9‡ô„Iãìñ8»ã"Å«'ñÝ Z¼®p«E5¦F]0MRTöƒY‹wo~Ö9HÃ8Ý€Z\µ§Âé}û}¥sj¤ŠU¯tC›þ3ÅØËîµó§QI‡ÔįŠÓ hµ4ªVQ¥´Š«’‚qúµñwŠUÔŠ$ÇSkYÒMÉâž;˜v+èI^7LªÕZ²WŒØÇ~âcWLø†3šÊ7b²ÞÊ_+šÊ¬(·tLaIw€¬?ÚDÅq“d)é–·ÁYµ’-«Mu¬5O‘\M…´Pº¡àä–‚QnòÉ»VuØtl2¯ºLoTªØ`™-ÚYU1)UìfµÚ–m(-sp;⊒vYŠ-ÁGKi»¶¨eí—Þ´ ÉbÆàdµÎh’½âXåÛœëC¨­x¡L7,˜ûð'>zùŽ®5n6S­Âóóß›y§SL©.éç¿ýÖÝK—=éjMuO7Ô( øCnbx¨–C¹xé ;ˆAnyÐ]5¤ÐP©^éÓŸu–K:ðYP¬z¥N›5¡žÐn„tƒ©þI‡Ôį²¹ë¢…ómÒk¸H[“Øq_üBR7€Šñ,×Q€ ÍÁ¦»šT[Ñ[±µäoF+JÚ,î¹iÓo»%¬RÜ“¼nØŠñ£DP8Y7ÒÁsU˜¬ó ‚sKËöÅy*Ž1l4=Úñ0ƒ^©x FXËî´i• %Ò éßÜRý‚kU*¾êœ*nTªØ`E·a²„Ú b7-_~*Æau»lô;»æÙf·u-Û£F´³ZH÷DFEþ±ÑTíX‰øˆdö¡âZµ¨Ó Ón¹oäÈs6­É½+¡¤ÜóÂIó]øÄSªcºÁ”yGIK¥ì¾aOj6ªåPêÅ1è3WßûÀÃË͇t*Õ7Ý`Š“|‹tƒ;y¤z&R¿FȦÓBÓlM¶5Aµ mlk²­9­jCQ Sƒƒ5¢é½5¢µÚÑ،֬æÞ!+jY³âP”9X˜`R1„TÅ=Éì†ìz5£5«¢–åiEZZWµbUÛˆdÝ íØÀj­WŲ}±P"ȶ: þHé¨h›“OðW±ªá­¸y#,¹ ©…T‚–H7èøUu™ièÓ×ªŽœù˜CæU«âF+6Xf‹–Ó §¶ΛXnZP³§fÜ õªýR Z¶²å w¾æÙf·ue±¢|´ É"™%4%ÙŽX ±ÊŒ•¶UpD2ûPq­ZÔ.ÿL±öPÏŠç—¸¹…SɦhPºÁ'˜bJuO7˜>ÿݹ«W­ >-•n°bèÕÙÛ]ƒ8”»7?»âɧ$Å]îÆÒ¥$‡ÿãG_sA BÍÑ7žžýG—NÑ»JZé;×®½}‘ó9óŸow§t%¶ßùÏ|èö‰çœóܨšlÌW/ýñ_}lÂáçŸÔý×Þ›Öe?ºOUqº¡÷gÇ.¸aqZ7Ì[cî}½ÎÇ4ëÑõ‰Ç VoÞç|L‹VoO&M'Ì!ÐÕ}Àù˜®ºseâqÍCœiÚ¢®Äã²8SÚS[q>iM~৉wõ49ÝP<O+s®îÑ\7݈[QoC¸«eEãš'‡¹´ÍÏ㙼SÜ`f7¤ÌžÈ’Ú¨ÕªµØXm#’ú–´G±%O¶/6’TTk!žÏìgj‡·â>æ°d ګÿL1„éÓPÌ}ç“yO±ªm°Ì-ï•yÞÄ*p«xjê´–BU,5˜nÖõ0ïÌNÛUÔº¡(¹ËÌŠz Aƒ;:i]ÒªØlTº¡ïïŒùúÝîCÑÔ¨ó“Ó Óæ,r>¦{ùÿÂüæ-9SùydÚ3óÖ§+¯úSÌ¿ÿ7,AnH%çPRNš¿ä‚SÝvQÝõÙ¯^åF>ÈGÉìþÕYöåg®Î¸[ØdO×Ó*ƒø1Åücó¿úÓ¯þ÷Yÿ}Ô´QŸ~äÓö=è*£ï½8çƒ3¿oùM§ôŸM<ûôZç£óÜÕAnypë†gH=Kƒ³xò×ݨšlÌï™>/¤ºÖ,OkãOW¨*N7hŠ¢‰GZÝûzÍ!pôXŸó1õ<’xœ ÷gÇœé@ïÑÄã²8SÚS[É›JIš#¥W©ŠVK7ÈM±€Í–q­Š®çcr-ÇßPZ#zµØ´EÏô$³’ù¤)OÔZl´UÒä5"¹ï2]±X¶/–´®[Qµ²ÇÉÖ²ñ”ª^Ù3 ûh¤GX²#+gZµYºAÃWíe殨5~U49§âº¢)n°¢ƒIç™,:“tþ¤óÜú·‘E85µ¬ÿXv=[Þ™¶gnHF  -k0ã–cÉž®JUñ‘TL÷¡âZƒ×`ïnÈûnÔÇùÉé†[V» Çþé7>ÿå[¶mÙèÒ ™w7ÜœuÏB½înøø%wª)¦˜RפÿQòî†Ã;×[hêtÖe³ÝðÆ²¡æî†&hþw>åå/C‚íÞœñä™ÿ|ûf-á\Ué†Åï,þÇ•ÿ8jÚ¨Îø f™í*¯F<»Á æÀgA±48Å?Ôš1yNH7h8=»¡öDƒÑRéûºNÀÚUËå/YP2‘%ö1¥§Ö¡)­n ¶¹î­ëíFãø×Å=Éì†$£0ÿXyQõD±±ÚF$uÒÖR'- P å|ò¤–城)‚¬Yg´µâ«jxUaׂÂ>jYé–ìÈÊÁÙ‡Ví”nÜeæ®(-ÇESúª ª¸Ñþæ ¬è¤sÎBñ±9Š4eºõo£ðÔT­6jËNvꛚJûdÚ37d9<õVýÑ‚úïLªÎèzRñˆH*º>”Ykðlº¡Ù¤æNøû_úÇÏÿÀåÒÚ¼nÝcdÜÉ_­2§˜ýšSL©â<²¢fßÿ¸dS<Ô¤š ¥;|Nšï²xÕÞÝðOOýÓ%]—Ì?6ßÙªVõM7ĉŸŪøÞrë ³GŽ<çõí«Ü‡¾ÓðH7Ô+Ñ`´TºAÓþð%¢©Ì\=sº›6ZL®CUa -‹&ðV&Å=Éì†d¡¾3H-¤Û©¶I]Õ^¨WjJÒ‚s(í‹‹)b©ñtlÄôê,%‡·â>æ°äN‰Q;¥ŠOn)sôOÉ«.¨âF+6Xí-0Πȃœ[ÅS3óz0Ù‰îÆÍõ0ïÌNÛU R.0Ù-œ®7ç`*3Vˆ¤¢ëC™µ§vùgŠ~Rs §üÌw¾{õ.¹©ÞülfrSÌ8ú51Å”jO7Ü<ûáxœ¥¿ýÖÝœø;é†&¨^é†Ï\}oæ Åé{@ƒ3"TÕ+ÝN4˜ø,(VÅ÷–oO˜:rä9î?­›ž¼{â3&†ö!¤ê›h0Z*Ý eM‰CQr“ÿÌFì ?E ÁbSq,’Â]YìëL9˜Q›Ó&Ô ½OI>=É Ò=)VÉÝIKý׺¡(õ6ïöçìd}p1E,ëO1“F¼Åª†·â>æ°ˆÖQ;¥4|ƒ¸ÌܵZòª ’½x£¬v‹’ª Îì Ø­â©iñõ ËÀ.079„*µ#‹–¼3;mWÑö&›¤Á̬5•+-IE·•2k Ní’n8ûñ»FÏ»ÖÍ-œÞÞÿ¬K+äéÕžn63…)f:Ñ`bŠ)Õ7Ýð·ßº{ñÒÎA"ÝÐÕx(ÿü«³$—'Š•—nˆÐ0ýÐtW‹Píª=Ý—h0ñYP¬Šï-›Ö<õ“‡ºOülµ!c/»·‰†_‘šø5BeB+ÊGΚ*k«¹nìÙˆÝV,OÙãû£ãM61á›v­b–øV©¸'™ÝÔs³%MòÕTÚ3HÎ!Ò1UlĶ.Ÿ°Šªd‰Q‡- J;;™ƒëC¬Ð ‘œ5 7¬è<ËoÉ} ÅX²ôv¨T‡¦è'Õn#¤á C_þ2³µB±üUgª¸ÑŠ –Ù¢Õj’9h[¡6¨À­â©iy5a׃^­5«µueÌë¡VQ1øØmuYÔ”]Ì&5.»ˆóNeƪâ‘Ò}(³ÖàÔ.é†Óf_Ý“¤›X¤äÒ yªWº!/Ñ`’)æ¥ÿ8vôèÑûï¿ÀÙ‡JõJ7|þ»s3 &Ò MP‡RWÊáë1V:Ý0ûÈì¦= aüãÏŸpþÂ7:; š±eÆmën›whž³Z®A;ul¿¼jL7ìÞü¬³8Õë³Àþd7ðƒ¯}ò§3>ë|ÚQeÞ[Þ}Ñ?.:[mHïÏŽ%K "5ñk„2§Ù.„ÑLÞBYCÎ’‚CÞ\]“a›ô M€åfódçfuyKˆ´J0š½ 'yÝ´¢íT@íÈèÜ‚l+ÎX܈dÑnÅÚ) ‚:¦@³fµœvv2mÎÙc¹þh[qøT~x­ª`óFXû˜io • %Ò e;? ¦¤…à9úv´bKÉ«ÎTq£RÅ+:Ø·ú†<Ý.»Ÿš’º¡­'u'ÿ^Cn¡ÊzèÆ-sl3íj*ìlœY°T‚ˆ»”VűÒêHºeÖœ:0Ý0í–ûfLžã¦2ƒPAôk’tƒMC‡SºAã\ña„¤š Úe±Òé†Y½³>|÷‡›ð€†»vÝ¥«F᮳+¾zÞզɫ'§“ZÑj6»*…Ía]sPk霛iξ9ÎÍ$ç—Ýh>×/¾>Ï­A²÷–ôžZ®A횊z MS醊ª×gÁ½ßù;´˜¥7ƹ B?ñY5µðïìÍQ‡§Njâ7´ÒtZscg,#­U<¯Vƒî‰¤ˬk!ºBg7•iDëÚ´?6j­´±v©ÙúްTfƒ,N¬{ꣴDºÁTËÉ«ªs¢ÌF+6XÆ¡âV¤ŠnÅêC^­ªÊI±´ š3ŽýÄÇÆågciõâž”<"®õ:yb »tÃ6—\Hëã½bìG'¸©L#T—)fµ³Àùç°J7”Q›¦ê2ÅoZœÐ„tÃ}ÉÏ%&Ìœ 1œ¼zr°,xmÁùÎï¿–"d ¦ØÇU]¿øú¤âd\8é–Ò[NÄaù ½Z"ö¯»l‹K7ÌÙ7Ç,Á¡ij¯tC¸£!Üì-{QûÎÞ•K7øûlAšÔÄ5Mš·+~Ž¿¼¬Vk~”n™÷ ´µg]qò/Ç[H%h¡tjSéRoD*qÈÕ÷âî þÓ7Ö¯O®…V¥Lºáæk¦~ö3×¹äBZí˜n(? ˜|’nhÕeŠß´8¡¡‡rëÖeÿ~êÿúï³þûÇ|Ü…‚–bõ‹ft¸_–G¸qÙ²Û] *†µ¤ò‰kÍ_(œ6¿q¼YTUà&-xmAp².M˜9Aý±.©AK7¨hÄþu—mBÝsöA+Ý YÜH6AmšnÐû¼XpPÕô+>eF-l›ó…På~gg© kÊš „Ü¥jðµOÊ¢uã{(¬©ÝóÎׂôÚÃãBÕàTñ½eÆä9s§ÎvŸøÙ‚“yûÈ‘ž+'ßµÅÍýPÓ4ý¶[E;cy½~âÇæsçÜi·6Û-­–Vv‡K¬´ˆÞ}³÷ÈŠºš’ë*‡¢t—"*#û-ƒ»Ý`˜¨þ³Lºá;—Þ|ÆïÙ%ÒúøG¯8ï¾ëf3PíSÌ‚Y`ޜҌ6§”]Ëš†Úâé£Ö²-ªhÓÐÚ5œÒ aôôª©vl¯8ÅOlUSü w~Î,ÁÇVÉk¤jС|vã’ K.5mÔLû£F? !S3¶Ì°Ñ »«_¨*¥0™Ûõ‹¯·yë¹ýz2zë°Z+†tCÜTæÛHº5¹ÙºIù‡ú¢Ù­Šï-c?:á“»Ì}â§Õ¦ÿLÑPšö8|”§Ã?¯åæk…sçÜiaÈØO|L­ ¿û$íTü3ù–RÉ;Á‹Ò \Ѝ¢,¹Ø²—A­.é†ïë–‘#ÏqÉ…´Îøý/ûü÷Ül¦jPº¡äœR ËVU~úh„kѰI7(òOÆåf/s86°ÕNñ-a Ëb¼öð¸ÌF¤Êq÷Ÿ÷çw~äÖå×ý·~ÉÍ‘ýœaÂÌ Áb÷¤åàdn ¤'¯žÜ?úÿèÑ ™y„p'Eˆ¨K¦Šêg¼)´°`>ô* »µäîãñ h+®ÊBv Lûr¶*+6(Y›G¾îjßg7è­À¢}½˜%/aµá]Ýš ΖÁ ï6¡5óWÑÞl[V5ýŠOÉ.Ù*µ¨LºáÂó+ü5•tÚ¬ ýóˆ ÆA¨v‘n@¨f —tÃ7ß1rä9ûvlŽ“ iýÇß>ÿ’ èf3P]¦˜y³Àâ9å¶9_°ˆ4|Ó^~ú¨¢U…MÔ¢á‘n£gy¡‘’‡#o`ónæ1’Ñj-NxòÖsm-×HãT¯C¹oçÚ¸¸uë2[Ðî‚ÀæÈc½Z1ÜGPü{ø%…–C. þ†ßšÁbr·B7û‰„)8Ç žB} I‡»vÝåîn°*ùKªUƒêXpp­?á|ù!ø·]„‡J¨6Î,Ä-”i_hY­UlPÒ²ƒ¥9jßtCx‡ïÁÙYlÙÞ[$k*ï½(¬ë°Õã庨â{Ëüþ—.ùçï»Oü´H7¤!ÆA¨v‘n@¨fµCº¡ŸÔÜÂiîÔÙ#GžóÜÚŸºü‚ÓÄ Ó»‰›Í4BH7¸dÚb˦Æ!hUÓGûŽ=ÌDkÑðH7¤‡=Ïî,¶œ7°UMñ%‹DøU…äiœj?”[·.»ö±ËμýÌÅ?媤¡J7؆ WÁ³³dÊ‚g½ZÑÂõøî€oKl×k\täeBRÀPÐnöÐçà™–m(tØŠçGÿý9ÐÀ¯öÚŠ!çRqX2Ûá9V,n0$qB¯š£vü1…]øÂÞÜ;OÚbËîͤÚ÷¢8=šª]ß[þÓû¿øýoÝâ>ñÓ"݆¡ÚEº¡šÔ.Šì'5·pZvß‚‘#Ïy|É2—_pzeÏV7•i†0Ý`?õáUMm£a&Z‹H7ˆ¼Í;¸;FRxB„°Û%$×HãTË¡|vã’‹ú§QÓFyû™¹8ÜÑ«½Ò æ?’À,!Nñ¶ƒ,e Z+·˜¢§¥-ÆIëd^ºAQÇä#eÞUŠÒ@¾A-d:˜Ê·_²ÁL·&¨MŸÝ§•õ.10r¿ªµ;­ô¦aE»ßÍ~¥·+º÷"sÖ{Kh-¼wÅ*¨œ*¾·è“}Ú÷owŸøi‘nHCŒƒPí"Ý€PM*y µ©¹…ÓëÛWmfµK.¤õrÏ7•iê˜nHÏóæ”V«‰ ­¾¯júhë†MÔ¢á‘n£g?¦Ð°/8¬èÖŠ%§ø–B’³…!¿à1c#4èC¹zý£¦²4¸_RÄj‘tƒBh³Ä¡¸“b~óQä/7)¤B;o‹°–Ɍ黂C…X'C”ûÄ?X˜¿dÛ Eɬÿe²Uµ_¦A)Ó­ jÓtƒ= Xè­#Ô:BÆÙ=}Æ¥BSÂÞ[2[3g[Î|›œ*¾·|÷ò[7-Ä}â§Eº!Í»Ç+Ày»÷€›û!„Ê«ÿ«ÙÑ£+~5[”nàRD¬á”nÞØ÷¼K.¤Õ»§Ò %gaNiE›*Õ²­•·bz-É"Ø0­EÃ#Ý ¥G/Ï.2‡ä¶ª)¾…j*d%l+éF¤Zåkow–´†*Ý`_ˇø?XDÞ-ýr6‡4Z×|,Þa-I¡µÃm™neÇó!Jµá .ýaþé¢4àîÓ ¡ŸÎ¡Úö+6hÒ²ƒ¥9j—tƒe3ãwf»Á!~’‹Y„œ]FÀV×;ÉÒ›?£w-ÇM…|D0Æ­‰¼ïEV¬]ß[Ží}Î}ÖgŠtC6©‰B¨j• (ÝÐk¡ŽÑ0K7Û_9ÝðJÏf7•iê5Å,žº9e<T\jÅý–œ>ÊMŰ¹Z4lÒ ’„ Ôô+>†T*y8ÌSÅx`KNñÍ-d¬(·¸(â–ë®’‡rëÖe¹ø3óþÖÙ+j¨Ò .0–BÐ;aæ„ð5»âäJ°ÚÉ«'«6(<¯Ñ~ aÍ [eá› C³¡´[ž¬ýþѾeITkÅð Ç` «X%ìfz¯Üü[18ÄO£TqÐíç5h C,ÍQ»¤†«*¾·ßOº¡R?„PÕ*鄲5ÌÒ Ç_Üà’ NÓn¹oäÈs6­yÊÍf!¦˜ÒpJ7t¸*Ê¥ÏÞ3îþóÂ\mE UºÁ~ÿ‰£?!FUᇠîÞ‡°'8Z¼F²|„)¸K¦ÂFÕ‡¾…¦Â¦…ŒrV߬(gÅð– !ڷ톢d!øé5(Ù‚^Íaí7h²ÛFⵚ#Ò C«Šï-o•K7¬ÝüÈŠw&¤&~¡ªUÒ e«]Ò eþSºášé÷ÞõK1IJtÞϸÙL#ÄS"Ý0lT|('>rq™4è×g^zï±ì/4Tyÿ† 0ØâaCõŒÿ¹LÒAu+ײVXïWhk!–ØÚ³§ÂuC­Åi‹øI óîȸk×]¡ÿ꘭~3b{ŠR:ø7CvÛ£àPmûÁh¤”l-_ÓL5:ݰçþ+^øÁî‚BAÅï-;ºÖ¼¸a•û¬Ïœ ?G¨>*Ïn@(_íðG˜%Ó ÿ탞ý7ßq)†X/¿ý7ósn6Ó ½pïE{oú#7¯ê45'ÝðÜ?ý«ãKø”D$Êëî›»jŠ3V¥÷ϹjÊ+÷¹8°9ˆOz¬@BzEï.1„Rg,›©t•,qV¢ZiÇ 6'UÛ~AƒáZ:<8]ùÂ]ÿóI£ö/½©ûšÓÝ…‚öLúÃî»Ä ZÐM×Ý1rä9î³>Wp2<¡ÚU‡GEr)¢N×0J7|òc—ýùG.u)†X_wÓüþ—Ül¦AÚûзwï?»yU§©9é†ç/þwo>”<, 5Hñ¡|~óc–\´ôÙ{Â!¨]õÐ/Ýq‡‹›#÷Í<ÙŸ‰ŽlÑ4}®kÊ…Kg¸²Ž:ܵpã%#Ý…‚º¯ú—Ýê-è’ ø÷Ñ}Öç N†¡ÚUòNpÒ åk¥.<ÿÚ3~ÿË.Åëì¿ùÎØNp³™éÀ“3·]ù[n^ÕiÚý½ÿ¼÷¡o»‘©»¶\ó{¯Í=ËmÕWv(ã4ܺü:w jÑŒÕ~ä±ï»8°9š—zð!j¾ì—éß›4A¸ïêÇ×ÿØõÕÆoþÆÑÿÞ]SHúù#ço¸èÿzugî/Ïû‡ï~äO¾æ>ës'CŒƒPí"Ý€PÍFé†ïë–‘#Ïq)†X_8ùÛ¦ºÙLƒ¤ùÓs_ù×~“ÿƯýÛ—7<êF¦îÚ=û‚}7ÐmÕWO]òïΚý—á »·ÿÄ…up÷º3æ^3T¿§¸k×]ñCPó5cËŒ!É5\³wöèßsgcݵɵ;¯ýOîšBÒÞ~°gNÑ-‡c?:áìO^á>ës'CŒƒPí"Ý€PMz»÷@Ï•—ë*H®‡V¥dºáΛûä¹yÝ:—e:ܳÕMeª?úè¡áfW£×æžÕßAjXê®Ã] 7_ö<¾¡q²C9aÉE5> ¡XO>¿â÷]ýÐ;K\@ˆPƒ´à­Å¿uÏ·¶oÌÃM«U÷94ë£îÊêp™÷©ÍOwåô¿ÿ¥ Ï¿Ö}Ögêì‡n½pr2i€ˆqª]¤ªIÃìŸ)6-dÎí÷íÛ±Ùe‚^ÞÓÔtÃ+›—mžðŽ-ùœ›cu‚üo¾ì7w-tcÒ í¾ë û~ôß\P-Úñèç8o¬šy(¿þĬ³×4û PÇê#}ÿ–U ÜIØ ÙÇO™ úù#ç—ycù_pçÍw¸ÏúL6kBÿ<"Þ>r¤gâ•ÛÏû¬fz±Þ1ó½_¾c:þÂ.W»aô‡C­ôôi§Åµ’V µjßÕÆY¾ÌÕVl<®­¶qíi¨•ŠO‹ µwµÚV\[ܸöÂÕVÕ¸ÆÐ97®a µ’«­Øx|¸‹¯x.7^|¸‡ðD}ûµÞUï{_Mé]ŠÛÏ;/þv7ãìŒè™8ÑÕœ5+©{ï=uÅÕªñ¤n€âÆåìjã}«Ø¸«í?"Š×^¸Úªß0z´s(n\Ã˜Ô àj+6¯âÆ-K­%u7^|¸+6ž>ÜåOîŠ'TÕx|°Z“’ééèÞç]Š!ÖË=[ÜT¦ÑêØ{h+Þ"[_½ºó™ÍWþ¯/ø;× 4=¹è¾t÷Ÿš6jôíÜñèç›y(î^÷w‹ôÁ‡¾;$Љ:GÓ_›ÿû®¾|ù]î l¨<9sã7þïN¾å-¨÷ž±¿qê'&»!JëͽϹOù<‘nÈC3OMóbõ<˜ÔÙ?ô\ëžÀÿ³µkƒVIê*5® ËÕVl<© ÚÆmÛ–Ô Pܸœ]­LêJ4îjµ­¤b€âƵ®¶ªÆ5†Î¡¸ñ8ô®¶bãñá.n¼â¹TÜx‹Ÿ¨qk™¥D|Dº»IÅœú&×xú*nœS_Ô~ê;‡¤b€ª{Õº¤æ™:¶oƒK1Äz¥g³›Ê4AÝ7þÙžFuÔ­þû§üñ¶ïþ‘‡FëàÓ÷iþzdÞ§\gPy=¹èþ÷O¤`©n€6 5·ÈÔ[/>çR AÓn¹ï/>òÍ=sŸ€Ý8õÌùÒæË~£"aM%·\þvÝqnÁ“Ƨþ‡8L<}ï?Ès§‡žýÙ»>¬W-í¡ìÙñìß-þѯÝþõ?xàÛÕK¸ïê_Ÿyé…KgܽÎrÍԋݰéòßÚøµ»íÊßê(m¼däÆoþƾ…W¹)Ð[ûI7@@º ýIÍ-2õüòGV>¾Ò%L—]2õ?þöùn*Ó4)Þòít_sú¡Ûÿìµ¹g ?¾cÌÎkwÓå§\u·Û÷&Ë’;{&ý¡ë!Jëù9—œ±u¥´¨k)BõÒÜѧ—7!Ô ú‹|óìO^á>åóDº†Ò íOjn‘©Gîù—‘#ÏY»rµË5ìÛ±Yö믺ÝÍfê4}|öÿü̼¿}`-×B¨¥õûü+¾zƒû”ÏÓÚͬxqg2ah.¤Ú›ò„¹cÕã#Gžsï]¹tÃsk*û}w6é/ÖjmÝzÒàwoÿI\D¡T׊úÔ~ðŽ9îS¾HCé€ö¦|ºAú÷¿yÞÄËowé©·g“›Í 4¼µôÙ{ÆÝÞ¨i£žÝ¸ÄU!„P+ë¾;ŒyÎ¦å¸ø" ¤Ú›ªÒ ×|óæ;o¿ßå¤W{6ºÙ BÃR‡wwÍþÉÍŸý?GMuÖ=Ÿà ¡¶ÓõWÝþ;¿}¾û|¯ €!‚t@{SUºAzeÏ—kxyÏv7•Ah¸jîª)£¦wÿyKŸ½ÇU!„P[èÈž ²†Ò íMµé†×÷nré†WöluS„†«ïîz~ócΈBm¤7ö®wŸì•0DnhoªM7qCœk¸÷®‡>ý7×ìÙøŒ›Í 4 ´ø§³>?ÿï/|ð!„ÚWoíÎ}²ëì‡n½pr2ih.¤Ú›jÓ ïîöþ¹KBºáì¿ùÎüþ—ÜT¡¶Vü€½jÙ9 „PûêÝ»Ü'{±N›5¡ž0nhoÎ~ü®Ñó®us‹Í:{äÈs_²ìОí›×­ûÍßüÜ—LvS„ÚZgÝó Ѐ~:Ô½î?½ÿ‹úwŸìÅ"ÝCé€ö'5·(Ð[=Oàý_üßuÕ¡=Û/»dêoþæçvt­q„ÚZKŸ½‡4 „†Ÿ®¿êöªÿ“t )¤ÚŸ—Ö¹¹E±¼cŽæ+pÆWôzÉE? ó˜ÕKüí Sc=vÿ’P»gã3®vÆä9¡VRÑ9Ä„PS®¶ªÆoºîçPܸö%Ô¢ñ¸¶¸ñMkžrµ÷ݹ ÔJÅËÙÕªÁP[±qW+ŵŧwqãÚ‘¸¶ÚÆãÃ-¹Z×xñ¹ä?ÿ»_ýÄÌ?»1i¿âáæD5¹Ã͉jj܉*Uu.U{¢Öx.5´ñ¸–Õ4ˆuÜç¿÷ïó¼Kþùûî3½¢H7ÀBº ý9ô¼›[TÔ÷®¸åÿ÷;ÿxÆþ§ÃÏ?ŒËî[ðÉ]+¾cSž®öНÞj%CܸšrµU5~Á9×8‡âƵ/¡vǵÅoZþˆ«½ùš©¡V*n\ή6þÚªbã®VŠk‹OîâÆµ#qmµÇ‡[rµ®ñâsÉÿ«¿úúŸ}ùï?xÓš6ê/¦ýyׯ9V[ñps¢šÜáæD5ÕýDUÕ¹Tí‰Zã¹ÔÐÆãZNTÓàNÔKþùûoõ<ì%uÊÔ¯‘n€¡‚t@ûóÊv7·@hxkãæy£gþɨi£¾ò๫×ßéjB™ŽìY=bÊE§ÎúV2ah.¤ÚŸ×v»éBÃ[}û×^ûØW{º;;B¡Xë·>>bÊEgÌû~2ah.¤Úžµ»ŸîÙ¹ÒÍ0NZôô¦­˜èŒ!„*jÛžgÖ¿òb2ch.¤Ú››6¬1å¢ñKOú(BÃC½»—M[1qÌ]ÿcÔ´QŸžûW®!„Peýl_2ch:¤Ú›_Ø8bÊEg=0ÉO/jsM^vù‡nÿ h@¡šôÆÁdÆÐtH7´7=G_1å¢÷M¿äøþªŸVP+kÁš®zäË< !„jÒ[G“@Ó!ÝÐöœ~ïµ#¦\ôèsùBí£¾ýk=ý£ÞÝËœ!„ÐàupC2W H7´=ŸytÄ”‹Æ=<ÅO2jÅh˜õäIÿEBhÐêÙ¹òø«;“¹ÀP@º íY{¨gÄ”‹N›5ÁÍ3jqÝûäU|™4 „P#ô— ¾§éÁŠÉ8ÀAº`8pê¬oiJ±vó#nªP+«oÿÚ±wá !Twßÿô)S¿¦¹Á‘·~žÌšé€áÀ¥k_·fO‹D-®¾ýk7nžçŒ!„ê®›ž¼{Ä”‹Î\pS2Q H7 ~ùnÿ¡R„ZAá ºýƒ<!„ªãûŸ>uæ¥ü’†Ò È7¹ BC®žîÅ< !„š©K|ûˆ)õÈŒdz0DnFpƒj=M[1ñC·ðÚǾz`çc® !„PÝup÷Oì© ë_y1™ ¤†Ç_sÓ„š¯£{ŸŒ—ã"B¡†êº³FL¹èìÇïJ&Cé€áÆÔ§ï×TÃM>j‚zw/›õä5cîú7üø® !„P“txÓŠý;ûY2-:H7 +¶½vè”Û¾>bÊEs:ßÏ?j˜â4\¶ø‹ÝÛt!„š¡ƒÏ½÷N_2'jH7 7¦n^5bÊE§LýÚÚÍøYB PïîeºýƒÒ ?þh@¡¡ÔñדÙ@ @º`2nå}#¦\têÌK{v®ô„ Õëïä !4ÄzóP2h H7 CŽ¿ó‹Ñ '˜rÑ鳯ܶý ?A¨õî^6mÅıwÑ‚«B!Ô|Ù³úì‡nZ»kM2 hH7 O޼õó3æ}Ä”‹Þ7ý2¨.ŠР~7BC.}ÄŸ6k‚>îO›}µ>ú“I@k@º`Ørü_œýø]£¼ùø.7;A¨ZÍyêúQÓFžù'< !„ZDv=ð¾é—Œ˜rÑó¾ßsôÕäã e Ý0Ì9þÎ/Þ{÷ï½¼ÕÍQªJö'—< !„ZAÇ÷?=qÙÌS.’Î~ü®þÏz€Öƒt@gðËwß;zབྷֹù B™²4ÌzògG!4ä ? nÚ°2ù h=H7tïþbý Ïœ:óÒ©«ç¸¹ B¦ø “—]îjB ¹Ž÷n?}ÎwÏ\pÓÚC=Éç;@KBº ³¸tíbû>äôÙWŽ_:uÅóKÜ$u²¾ò๣¦s×ÿ˜¼ìrþx!„ZN/o}ï­£ú4?xìgö±ÐÊnè8|a£ýi…é}Ó/9oñÍü{’f=yÍ¢§Ô·­³#„j²ŽìYýès_:õ´YÆ?~û{?ÛÿÞÛüñ´¤:”/î¿êÓf_mI‡žýϹÇIŽ{xÊÄe3nzòîØGÒ|Èù˜f­ç<{v®t>¦»pžë·>î|Lé{1dq>¦´§¶â|LÚ–óTÏIýwž çcÒ˜”ôtn’s05hØí áé »Sƒ†Ýyæ {Úsh‡=ϳ-†=íɰ;u°çy¶Ô°Üý“Ñó®=eê×ÂW¦Ñ ''Þmé€NgÛk‡¦n^•Œ·~üØ«n®c:í®‰ý·qFêéÝç|L£ø¡ó\Ñó¼ó1÷øÎsÖ¦'iâÚ‡œ§,ÎÇ”öÔVœIÛržê¹ó1©ÿÎS£á|L“’žÎMr¦ºû¿¾õü?¿÷3šñ!iõî'Ì“awªû°KåÏö´çÐ{žg[ {Ú“awê„aÏól©a?ò³—U;ûêÑ '_õÀúW^L>Ú Ò Áñw~±âÅi¥ŸJ•癞yëçÎÇ´íµC‰Ç û™ó1¥ÿT\çcJ{j+ÎÇ”þù«zî|Lêâq†ó1¥ÿ,Ï3©Žp¦:ûœ­Oüí‚sFMõ‘»þ|Z×´Þc½‰ÞR‡=¨üÙžöÚaÏól‹aO{2ìN0ìyž-5ìBöô†ÚÒ Dï±ÞO/øô¢í‹úÞéKL €tÀpæÀÑ«÷­N Í‚tÀðdã¡—=qÙ¨i£ÆÞ;614 Ò Ã¥»–ž·ð¼QÓF¹{Œ{@@s Ý0ܸxéÅ=ï¯y@ !¤ÚžGÄ™…£}G“%€!‚t@Óu ë⥚6jÎÆ9‰   ÝЖ,Ú¾èÓ >Í 5!ÝÐ~ôë5mh€–…t@{àÒ Ý½ÝÉ@ëAº Õ±4Œ¹{LRhyH7´.î ünÚÒ -ʵO];jÚ¨O/ø4h€¶ƒt@‹Òs¤§ë@WRh+H7´ö€†[$e€v†tÀ? Aˉ !Ý0dôës÷˜ð€†Ä ÐþnJf­ŸÅ`øAº ytèºjåUüÍ {H74œ¾wúm_ô×óþzÔ´QcïÛÝÛT SH74–¥»–ÚÎ[xž–+À°†t@c±¸ä ÐQn¨3«÷­ÞxhcRèHH7Ô‡ø /½8±t$õI7¼õÖ[ÇŽ{ýõ×{OpôèQYúúxú:tÓº¦ñ€€@Mé†7ß|óðáÃ/¼ðBw>===/¿üòÏþód€áÈ‹/¸ì‰Ëø €1ÈtÃo¼±gÏž$£Pù¿ùæ›Éúm~( êtÃÏþóýû÷')„êѺÜéíKü€†=++œLué†#GŽìر#É µ v’Ú„Þc½á ,¾€\@U¤^~ùå$ap2®Û2áÑçþëÌÕ¿~ó“#¾¿Â¤å?¹sìË×oMüNF­%í´«÷­þÐŒ]µòªîÞîÄ9”M7¼ôÒKIž bÊÊçÿß[Ÿ )†<½Ú*y&ëD:t(i %9pô@²4@ï±Þd )•nxõÕW“ Á >³ùýÓV¹´B±ä¯µ’õO –“m´ }ïôÍÙ8gì½c?½àÓ‰ ª¡rºá7ÞHr'˜²òùsÓ¯~7Q^Z+}›ƒÚO¶0ÔôëüÓÉ£gæ µP!ÝðöÛo»gCÞ¸|ƒK"T+µ´5ÀÎ;µ•d{Cʧ|ÚÐÐs¤'1@õTH7¸ÇCÞ÷ôæÿó†•.}P­Ô‚ÚIZ –ÇF.^¼xô æÍ›—XëAÒèèÑI9Ë"Ôd)‡dœOfѨ¸!¨ž#=<  vŠÒ o½õV’`õÆmñOÔ"µ£Ö’vþsÐ78$qù k=HZŒÚLÊ',ûöí›0aB(æa«äá|2‹%7Uq´ï¨= _LÔ¢tƒû7ŠÏ.xÆe ÒÚû³ã¶®³§¥Ö’vÜ ŠÃ-,È’ÔÕLÒb~ŸTWÊ$N9$N'“Ô¨M ¤ê„{@C×®¤êDnºá—¿üeüÔ†åë·VüÅÜ­‡“•K¤ÔZ|ƒÃîÝ»“5«aÙ²e‡Ï›7ÏdIêjÆI9ER]:Ý”K¬@º t÷vš6JºjåUZN¬PWrÓ o¾ùf’  â­ q®A¸ÚL}eQWÒú?ÿùÏ“•Ksã7Z®ÞÚ„ ’º˜]$åÓÉÆeË–Ùo/^IõÉk%Kæ“&©Îw‰Ç Ÿ¤PbC¡ÏzÕ²ºTD+îÛ·ÏjæÌ™I@g3gãÐÐPrÓ ‡NÒüÆ-?qÉ‚ +žz!ü†"à|2õþi«’Öxå•W’•ËñÚk¯Y8mOˆ 78Èn†ER 1EF…â‰i‹áÄ£š,@LRï >I¡Ò†âN²„ŒCbŠö¥¾Òh Žöµ~ÖÅK/NÊÐrÓ ûöíKÒÝÝ ŸÙì2±’Þ{ïð±¾d©\ºAZ¾~k²îî_|1Y¹ëÖ­³(zË–-*êÕŠ²›ƒaF‘”HL'Œ¡©™3gZ¸gÌG$å–¤prËi§,J-'…“7´zõj3Úã*Â.ÈnVᦆøÞ€aÏ£®}êÚÍø= ¡ï_½A@£ÉM7¼ð I »ûê¥ë]š –ùïýÙñ°,b‡ݸ|C²îî={ö$+—#dìv†ðó‡]fIy€Ätš OšÔ‚Y„YDR.̤Iœ²H<*µœNÞPèsR>ávߊ"ì@ç0ù§“GMõ¡â CBnº!É ð•E].Mëð±¾¹[Ûr²rétÄGŸK¶ÑݽsçÎdårX,}ã7&å“å˜rbõÄTÏ'¦T9÷i—&ÈS²~étƒû;ÌdåìÚµ+ ¦³PmâW)VÏ,‰)2&åµÒ$N…n‰GNËIáäSÎÁŠÃ{@w1´Ù醷ß~;I pö}?ui‚<%ë—N7\ðà³É6HV.ÁâÅ‹“`: Õ&~•bõÌ¢‘˜"cR.\+MâTè–xä´œNn!1å7›Tn Ý‰Ð0­kZb€¡¦?¦ˆ•¬\:Ýÿ˜b÷îÝÉÊ%Hÿ)CŒj¿¬¨;ü¥…0KøW‹†>»!)g‘xä´œNnÁ=º"ÕŠ¤ 0¼è=Ö{ñÒ‹ÃzŽô$ДzTäõOlpi‚<%+—N7LYù|²jyèÐ! ¤ã»Œtâ „åË–-SñÍ7ß a>á_-T%‡L‘” ³i§B·Ä#§å¤pr áŸ)lB~$üÛ¥…†_yä+<  5ÉM7ìß¿?Itw/_¿Õ¥ ò”¬\:ݰzã¶dÝÝHV®Dˆ´í/0cBâ üdø‡È@x¢¤0çD|÷Dâ‘ àãt¤Ä£0ìOí² Å’¿ÖJÖ?ZN¶À£®}êÚÍøPwowb€áH©tƒèííM2®Ûòñ9OÿA濹éÉOÎ}Ú=¬ÁP›Ië0܉Ð0ù§“ùcK€áMÙtƒxýõ×ݯ*SV>ÿÙÏü×™«¥sÓ“¶pÁƒÏÞ½fSâq2jG­%íÀpgÑöE£¦{ïØ[ðÇ–@éñóŸÿ|çÎIÎ`°¨µ“´@ß;}+zV$èªK7ˆ·ß~Ûý;fyvìØ¡uÕBÒ GzŽô\ûÔµW­¼*)@çQuºÁøÅ/~ñÒK/%Y„r:tHk%ëÀp$~@ô®i‰:A¦Œ·ß~ûõ×_?pà@Þ3d饗äóî»ï&ëÀ0å‚Åð€0jJ7Äüò—¿t!Ô™J.‰¡`ÑöEö€†¿ž÷×< é„ZQÉ%1ܰ憯<òþrjtB-§­Ç¶^°ø‚îÞîäÂh0mì=Ö›êA ¥Žö½µëV÷¬~„:Pº šóÏKw-=oáyÚÖäŸNNLõ …Ò ü3B¦F§Žö³qNx@âí‹x@ÔÒ µœnè{§ïC3>Ä qn@¨åÔˆtƒ®¯diÖ …tB-§ú¦ì šñ¡£}G@ƒ!Ý€PËiß;ûºtÕ˜Ðê³ÖÏs÷]V< šL ¥†Ó?SLš>éÜqçÆ–®½]q±vÅ jyĈKÖ. ÔîJ®Š˜Ö5mÔ´Q< †„J7ˆ¾÷ú\ÐÕ¿b¼bõÙ‹f;{-š8i¢Ú ÅùËæ«¨×`©¨¯ï3vŒÖrÈ®Z× ¥ªjÐÚtx“¶5iú$gGõUrIÔ@ï±Þž#=I ¹tzºÁu¡ØÞUÕ"—nXÕ½J–'6<,*'8¬cê•AohЖål醊íË"Oõ6½]UÙ‚jó’/Öxܱ°–†E-¯ê^ª¤ÆõÇV >±Ê´9ˆÞZ³n•F¨ä?Sô½Ó7zÖhÐ-H ¥÷Ï -¶×k: P,…£ZÑâX{‚C\+)¶”Q¯±1J°v³Ív>ÁZ;v'ELºÙ*™URÜ +´¯ñqσˆcxkD±´ …%Ô¹Ž…µl— µüѹÅ+j9ú’mƽWÏë­Îwnb:y•F¨üa.ݵ”4@ Òöé†8n qlIMš>IkÙò’µK´ìþÜ¡LºAa§ŠŠ?Äîx}‡Zánð±(×Ú)þþ<È|òö(nÐóÚ׫:&£ÅÉ*Z/‡¸óѾ(J/Ó1·–V±ˆ]¯æ ‹ùÔ«?ñ€«¨Wu&ì{UmÊA+ÊþFDó‘ƒ$Û¨*Ö¬¤µÌÞ •O7´&mŸnPãj‹%ÿð E**È µ’ŢʹtƒE×ñWèÇ>ÁÆí¨˜—J0Ù* ƒµVPØŠk°LûÖOy‹írkD¡xpÈ”¹¹tƒ7YÜÿ€Æ«˜ÝŸôŠ’ÿ%Ût½µ¯¥¢ë­vÇ­ÕP‘n€v§sïn°Ûâ4âIY,p5)€—%ã%Ûb(Ú=ö¡hr>å§œlGXÅ5X¦ýÌ~Ê¢èt#™2·¼ž˜ÜîKñ*¦A÷gÌØ1y9‘Áµ©¢3ªèzkw7¤;Ó ‘n€v§sŸÝ`É…UÝ«‚šì»ñ8¤L¢’‹¥µMÎ'åªèZ'[%ÏÇ5X¦}Y2 ¡{º‘L¹Že®åv_ŠW –L*öGƼah ƒâ6ÓFÓ›°FMé<‰sRЮ¾]Óº¦éŠH® €v£…Ò ½ÇzÏ_|þšÞ5.ôj„ìûLÎ~ T–âXz`¥¶I7ÈÓ)DÎZ–ƒÛÙ´Ì-4ž¹–Û})^%X„õ!VÅþȘ7,MVÝæªîUΨbæ&äi·9Œið£"¥äªhOZ(Ý šöG˜Š--ÂŒƒRɾÁ‘¤ýàÂE§æ“WÌ4ªe׎Šy1³ÉVÉóq –i?ó‡±ÒdÊu,s-ÕºmÅ«˜Ý…úy+®ÍÙ‹fËÿ¬Fż‘—ä)‡ð<ˆ)¹$Ú“M7äÅ¥IN=ñ¯ †bpÁb?ÁˆƒUKRÄ>é(WÅp‡¦l•¼ ×5X¦ýt?ÒdÊu,s­ÌtC½úc+Æÿ!²càï$BUÅ6Õ“p¿ƒd'Cl1ŸPLKùˆº(¹$Ú“NL7Ø?Gä};íBM-Ë2{ÑìøFz½…©fT`œç“ŽœÍÍV‰#ç [eÐé†tûê§õJFùK²«ý‚F2enÕ¦êØ;|BE>zµÁWUÉ6…ÜT¥ÎØ}(jÄLéÞÚ&¬M«-HjÔEÉ%ОtbºAq£ÂEE’În²`RQ¥ÜÚ·ßB ²§ci5eQnð±F‚ƒ,*† WR³a•©ÑÝAT‡¨ÞÉ5X²}­óÛ2Zmº‘L¹Že®•¢úöGŠÐh‡ª2mÎ^4Ûîƒ0\®AJ÷6öWUÅQªQü3´;ú¨Èj¥ø3„¬yR([ÑÇI«Ä÷ð×]yíË.9cTßþh´ ÖÊlS–'RêIñFUëz›Ùf#DºÚJ7 î0*¯8ÝÐÊ"ÝíéÔA"ÝÐH7 Ò¦O4ú)µ‹t´;¤j9‘n€v‡tB­¨äªhOZ(Ý šóG˜µ¾’K =!Ý€P+*¹$ÚÒ µ¢’K =!Ý€PËië±­,¾ »·;¹0ÚJ7í;zk×­Žnp¡B&þ™ÚJ7ðÏ™H7@»Cº¡–éhwH7Åø#FÌ^4ÛÙ¥¯ï3vŒj yNš>©koWpØtx“ì2 Bµ‹t´;žnèÚÛe©„1cǸ*ÉjU5qÒD“9?±á‰ØAö°Êp’ö+3 ƒ-Ò ÐîtzºaêÜ©#FŒ°×DJgìv±ãõ™ÃIÃx×Z\ûÞÙ×u ëhßÑäÚh7Z(Ý0$ÿL1fì˜IÓ'åý&"3›`¹‰UÝ«òœäc Olx"3£1Ù|Ù-á$£ª–¬]bµzUWCmh9Hµ±CPñV¬ÖöÈ$7µk㯯)sEÔ8%W@{ÒBéÑ÷^Ÿ º*…ߊ¨-`Opˆk% ¹]6A‘yX+Ó!–9(’·ö…,Vµéð¦øÁÂÚ ²îf/š­ iÁj­z þRì`*ÞŠjÏwnR1ð³ËVXJ%¦8¥‚ê®ä’hO::Ý0iú$Ò¶¼dí-ë5ÔJ™ÙKXX^2Ý 0^k­ê^¥P?ܧ`FkGEk6NFØŠZKµêXȘC™tCÅ­ØX­¤ÖäcžÖsùkA2;jš’K =éètƒÂéð …Ó]‡ZÉBî8›`Y‰à–vp2ÅüÎn·¨6X¬¡?éÇIXBX±Lº¡âVÎw®ÛåXò,Ø5Ô8íêÛ5­kÚ£’  Ýh¡tCß;}OxZ– ½$KÄñ¼}󒊺%»¿@!z¸ dºÁ%$û ƒ3Ê‚ÿL‡8›P&ÝPq+vwCº{&Uìjœøg hwZ(ÝÐ䦰äªîU]?ì^€8ö¶dAH7È!NO‡‚˜ÜÒñ¼Œ™„û ¬üMq6¡Lº¡¿‰,â»-ldÑÞ¹_LÈ^°k¨q"ÝíN‡¦ì7™œ;îÜàV2›0ètƒjBÀoÁß4¸tƒÛ„äÒ «ºWÙmcN<*Ò$ é†!éhw:4Ýbu‡ÛWýÕþVbé†ÌŸ9IJžíÇ ®e·VÅ­Äzbà0Âc$I7 ‰H7@»Ó¡é†¼8ÜBî©s§Z±qéûá†ûiF¬´ƒå„­åÐUÉ:¤Š[q’s¼/*Æ?»@Méhw:1Ý`ñ+ޱ—nØ1ð•V%iÉÚ%j'xÚÏ=ä#‹ýØAË.Kb-Ì^4;þ5DìPq+Ö¾UY qnÂ,Ö­ì¨ÑâŸ) Ýi¡tCï±Þ󟿦w ½ê.ÅÏŠ¢B;»Ébl…ßZ¶ÄDA6¡¢ƒÚ±ˆÝÙ%­«UÅøð;I‘¿% ŇaþÁAK@˜ƒ¶å̧`+vûƒa©‡°¢$·Ðø6 Ô%W@{ÒBéÑ÷^Ÿ º:Avs3©*dÒÙIµÁ¡@[©Ø÷hIÔ%—@{Bº¡”™n@ÃRÉ%Оnh'‘nè%—@{Bº¡4iú¤sÇëŒhø‰¦€v§‰P‹‹t´;-”nhÚa"Ôâ"Ýíé„ZN¤ Ý!Ý€Pˉt´;¤j9m=¶õ‚Åt÷v'×@»Aº¡VTrU´'-”nü&B¦ä’hOH7 †hÒôIçŽ;×Qy%—@{Ò¹éÃ#FŒ˜8i¢³ÇR­|Æ_1ÞÙ›/õA=™½h¶³K;^ß1fìÕòT¨ßµ·+8l:¼Ivƒ¥ ²¡sÆ Ì.-Y»DÆÌ}ì@%—@{Ò¡éEãŠl E¹®Ö4Ù|ÕZ$漢¬Ð[uÆUIV«*Eø&s~bñƒìa•&¨8ÝîÒªîU²4'³£í¶xRƒGE@»ÓB醣}GoíºuÃÑ .ôj„,ÜUp«(=ïžÙåP67GSçNUì5$‚Ò¡»Ý; v¼¾#Ó¡ *7×%»ACRσOãÔüѨVü&´;-”nhæ?S„p×bxƒ¢zÙõš6+*^²vÉüeóÓ+JÁ(7ù¬ê^ª‚ä“W•–âðIÓ'eþA ûm¿¬ýL§Ú÷hÇë;4\jÄr™ãäºd?ÉÛ´¶¨–­Y“–óœc7[7î­­¨mi‹Zpþ’­¢½ÐžÆv“jm¡`LÖŽëvy‘n€v§íÓ ü,²Õkfˆ˜)Eƒa-(8wªRo rvùÛC*ºWF¢¶¢oïZ¨ø•~H|hÙÂò¸V û놵2‚·GêIp¬“Ù‹f»qsŠ»”w׆:¦ÁéoîÁGíÇE“õÁ~“7ȶ­˜0,éqÐ>V;RA·Ë‹t´;mŸnˆc¿:VTîZ×*È”Å~Þoí‡*…ñ²„¯µ-ªï8°–p*•›š2mÅ䬢E¿’jã˜6-ó·eÅÒZ¶ˆ:(Þ— KLØV2‚±G´‡¶bV«î…x;4âºd{”N÷¨ۢ킊¶GZÑŠZ–%øK6Pª ËéAÖ«mZëjA vmN²Ô€Š6ñ&lEëUæ8HÖŽ2»]^¤ Ýiûtƒb¹W›' -OÇðF*VÔ²|Š›UxR ’µ‡©’5b1§œ]m±´bþÕ¥tãñ¾˜l‚[Ú¡XeöH–àcÇo ,NÖ¦6¤×x[AÖ¦˜Éö= …ú£¢#“Ša‹9ö4¥wA²£1ÇAªØí’"Ýíw7ôµ ƒC•µªÒrÖòüeóƒE²¯ÊÍh_¼;‡|·ïv¶¢ÊìQì£mÅþ&׈Sè’¹¥nÙ¦,!Ú×**†$‘C;Ѭ*m7¶dnÎÚ T©b·Kjß;ûºtí;š\íF ¥÷Ï ª-äÓk°+ÊEàñ—Ò.puñ¤$7E³XâZ]Pꌖ23vŒ¶'Ò2ÏUݫԂɺ·o‡tƒBçc‡°³iÉ¡Ú=ЇeÀÝÇØéq‹wI=—ܱh2yÆ>ê³-[~!TIƒ,»´E²Nªå¸XœnÐr&q·K*¹*Ú“J7ˆ¾÷ú\ÐÕ Åᮤx^EÅ¥ZV‡.ž´d„BÙ%k—¨ÉÂÚà”Zû±Q‹Ó‘v݇ŸI³%·/i; nâaÑBXrãæwÉGú[H9ʼn˼hôl B^ (oeq£!‹ë€¤ÍÉnðÅ2é†ÐÛ â¤R¦’K =!ÝXì»ýt^ÀÅ“ \Ý7ÕÎÁZvAiæ?)Hê§ãd“±¦BÔj²t@ˆŸeQÑÅϱŠ·G±KOƒ\—l|,Ýc²ÃŠ™²ƒ¥uí''*:SzUt£¡HoΚ ¿×¨8R™n—TrI´'¤‹ÅövÆßE»xRËîkpaZËj'nÄ|2¿â–Ý…¾Ay±«ÅÏ!8Oï‹S±ƒªÊìQA˜mwÄÉ ÔƒCZé.Yz"Äöé63¥®JZ×eLœÜ¶Ttþ¶¹°u“;jÇA*ÙíŠÚÕ·kZ×´G$@»ÑB醾wúž>ð´-z5BépWQ¥,ÂÝkàâI+*æT Š*‘º/Æ­e!»Â×UÝ«,÷äËn«Kv«ftºiàÏòn|°öm9½/NÅ%÷¨ ̶¡³ýŠ¿7â”î’±Uì®P c¥Á”¿ë†Ý!BòÅd±Óƒlë­¥ÂæÔ V‘³;j’ì¶V°Hñ8H%»]Qü3´;-”nÜ?S N™Á¼E¡ c£‹'µ¢¢†ÂHI ÁÁ‚R…©öE·G­±Ý¢âPËšÍû@ÜUÛ—8tw*v(¹G®ŸnXÔO ³Å¹ãÎÕ*ÖÃàà”Ù%5"£:cEùØVª’1øK–éÎ^<Èr½ y ·99¸›ÊŒƒT¦ÛEºÚM7Ô(E¹ê§¥ÅnyUC¢‚®–—Z¨6®.£â±3v̹у3c¯¨*íµ3J²×e/Š·^,Ò Ðîn¨³a†tj´žxŒÅðmÒ Ðîn¨³H74Sö3Ì›ÚZ¤ Ý!ÝPgmx*AæÓQ}µc์îÃCü3´;-”nè=Ö{þâó×ô®q¡B¨äªhOZ(Ý úÞësAB©ä’hOH7 ÔŠJ. €ö„tB­¨ä’hOH7 ÔrâŸ) ÝáQ‘µœH7@»ÓBé†áñG˜Õ.Ò Ðîn@¨åDºÚÒ ¿Ò¤é“Îw®3"Ô|‘n€v‡tï4qÒÄ#F8c{iþ²ùçŽ;W{!ÆŒ3uîÔ¯ïp>Òø+ÆËaö¢ÙÎ.É_+Z B­iXä™nÇyÊ{.Y»Äy"ÓÖc[/X|Awowrm´¤~¥A¤,wÆjU—F$K"œ;î\µ6ÙüIÓ'©¨8ß¹uííêõ³ª$«U•ze²ü…,Olx¢ØÓû˜òé€ÖtïäTûŽ]1°l´’…ëÁARÑE°eÖrJ7b-ÄÉ‚êÙ‹f;»d=o[HËBúLÎ-÷¶#á žNe<-Å`-ÄY…q é-Ët@ëСé†Ì€3ŽxXc(Je"Ø2k9e6’^Åýâ –UÇÒvo‚^ãˆ]*™D°g[”LL8•ñ´4vÄâŸZdþ˜"–µOº uèÐtCúYŒ±‹`Ѳ»Å ÷ªè’ eÖrJ7bÝs™k'ï–¡H'#Ô”-äuÃÂûà–™ÑÚ³õM7HòÑÐMš>)s4 îÝ(Ÿnp-·²’« =i¡tCß;}OxzWß.w5BöËŸŠQBÛüQ‹Wívź ׃d+Z#ê—YË)݈ºgkÍ^4ÛÚ±{2Ia’›­"O5%)J›¶{â_(ÄR•ÝÚ ’=v³´‚¹S [EÒ(i»*šƒµЦtº!=έ¬äªhOZ(Ý úÞësAW㤘ӂX¡HX!«…£ÁA!zˆ´…ÂTI ÁÁ|B#$—YË)݈-`6äP1BÖ*¶ eT•õA»ûÙZü["&F»3{Ñìô]jYµeÒ é6 õ*v³¬°žÄr£!Ô+Û5©dºAþéqnY%—@{Ò¹é“"ÕµfJ¡r:úu’ƒ‹ÆË¬å”nÄT±‡i b•v‘v­ÚuÊçVSrI´'žn@¨µ«o×´®iŽH. €v£CŸÝ€P+‹¦€v§…Ò Íüg „ZY¤ Ý!Ý€Pˉt´;¤j9‘n€v‡tB-'Ò Ðîn@¨åÄ?S@»ÓBé†Þc½ç/>Mïz!ÔJ® €ö¤…Ò ¢ï½>t!Ô™J. €ö„tB­¨ä’hOH7´‡&MŸtî¸s Wí{g_×®£}G“  ÝèÐtÃŽ×wŒ;fÄ Æ_1^ñüªîUέu4qÒDõÓÑpÿLíN ¥šùÏ]{»½;Fa¼i í0bö¢ÙγEDº¡£DºÚŽN7(†–M‡7Ùý;^ߌ­#Ò %Ò Ðînø•ÑBzUÅFI–ùËæ?±á‰¼L„9¤W”¬jÉÚ%›orURXEKÁ.i[²hEÛ(醎éhwH7üʘN7„[.) bì åVйãÎM*Åø8aa}XÕ½Jvsˆ³f1f/šMº¡£DºÚÒ 'YâØ1ð8ÉñWŒ· ‚Š–IÙU”=cR¯jpþ²ùa]ÉÒ*Ê.gµ`ëJ¶EÛ„Ö•§%#âfeY²v‰–ea]4¼µõØÖ _ÐÝÛ\íŠìN¤åBfÁ4uîTCrARð/ˤé“ò$KX•»ÂîP°Ü„úLéu-!‚ {%W@{ÒBéÑ´?ÂŒÓ –kÐrœkì§±E’%Ü¡ UÒÉSæºö »ýA²>„bP準ªpF4Œ•\íIG§ÃÇE—;%“à¦åЂSf•m%Ü‘—nQ8#é†NSrI´'¤‹=[!þûƒþßJ8ÙÏ%Ì¡ Ýn‚Òº²Ï^4;.’n@™J. €ö„tCb±ç2ÄŠÌüQC¬1OptFSfÕ’µKdÔ«óÒ öãŽ2F4\Å£" Ýi¡tÃѾ£·vݺáèz5Bétƒ4iú$ã3÷ËBú@ÚñúµœY%Yþ"Ü‘—nHo×ò"XÐð„ íN ¥†ö0¥UÝ«d ÏVØ1ðg–²Ì_6_þ’Â~­á#f/š­Z½ÊßjúVõĆ'ìö„ðK ÉúN7Ø}Ö”ºdIÌÛ%Ðpéhw:4Ý`™—n\T/7ùÈòŸe®ê^e÷,!U!¹uÕ²»Ù!/Ý ©Yë‰Pûò´¤ƒsCÃU¤ ÝéÐtCµRÀ/9cЦÛ jUg(ÊkÐ+¢véhwH7 Ôr"Ýíé„ZQÉUО´Pº¡ï¾§<½«o—‹»ê@%W@{ÒBéÑ÷^Ÿ ºêL%—@{Bº¡VTrI´'¤j9íêÛ5­kÚ£’  ÝàÙ µœøg hwZ(ÝÀ?S d"Ýíé„ZN¤ Ý!Ý€Pˉt´;¤j9‘n€v‡tB-'þ™ÚJ7í;z[×m;î|ã½7ô:jÚ¨X_XüÙƒ>~ïǃ­hšÜ5ÙÕÎß>?Ô>uà)W[±ñ¸¶ÚÆ/_yy¨•Š—³«Uƒ¡¶bã®VÛŠk‹×^¸Úª×:‡âÆ5Œ¡VrµwqãÏ¥âÆ‹wãNÔäªhOZ(ÝÓ÷N_×®XݽÝIÝmtZ%©¸QÂÕöëMêò®¶bãIÅÕ6Þs¤'© ¸q9»Z5˜Ô•hÜÕj[IÅÅk/\mUk Cqãî«{W[±ñøp7^ñ\*n¼øp7ôDh_Z4Ýí é¨3¤ În€:Cºê é¨3¤ În€:Cºê é¨3¤ În€:Cºê é¨3¤ În€:Cºê é¨3¤ În€:Cºê é¨3¤ În€:Cºê é¨3¤ În€:CºêÊ{ïýÿßÛdÓ¬TŸ¼IEND®B`‚keystonemiddleware-4.21.0/doc/source/audit.rst0000666000175100017510000000761513225160624021442 0ustar zuulzuul00000000000000.. Copyright 2014 IBM Corp Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. .. _middleware: ================ Audit middleware ================ The Keystone middleware library provides an optional WSGI middleware filter which allows the ability to audit API requests for each component of OpenStack. The audit middleware filter utilises environment variables to build the CADF event. .. figure:: ./images/audit.png :width: 100% :align: center :alt: Figure 1: Audit middleware in Nova pipeline The figure above shows the middleware in Nova's pipeline. Enabling audit middleware ========================= To enable auditing, oslo.messaging_ should be installed. If not, the middleware will log the audit event instead. Auditing can be enabled for a specific project by editing the project's api-paste.ini file to include the following filter definition: :: [filter:audit] paste.filter_factory = keystonemiddleware.audit:filter_factory audit_map_file = /etc/nova/api_audit_map.conf The filter should be included after Keystone middleware's auth_token middleware so it can utilise environment variables set by auth_token. Below is an example using Nova's WSGI pipeline:: [composite:openstack_compute_api_v2] use = call:nova.api.auth:pipeline_factory noauth = faultwrap sizelimit noauth ratelimit osapi_compute_app_v2 keystone = faultwrap sizelimit authtoken keystonecontext ratelimit audit osapi_compute_app_v2 keystone_nolimit = faultwrap sizelimit authtoken keystonecontext audit osapi_compute_app_v2 .. _oslo.messaging: http://www.github.com/openstack/oslo.messaging Configure audit middleware ========================== To properly audit api requests, the audit middleware requires an api_audit_map.conf to be defined. The project's corresponding api_audit_map.conf file is included in the `pyCADF library`_. The location of the mapping file should be specified explicitly by adding the path to the 'audit_map_file' option of the filter definition:: [filter:audit] paste.filter_factory = keystonemiddleware.audit:filter_factory audit_map_file = /etc/nova/api_audit_map.conf Additional options can be set:: [filter:audit] paste.filter_factory = pycadf.middleware.audit:filter_factory audit_map_file = /etc/nova/api_audit_map.conf service_name = test # opt to set HTTP_X_SERVICE_NAME environ variable ignore_req_list = GET,POST # opt to ignore specific requests Audit middleware can be configured to use its own exclusive notification driver and topic(s) value. This can be useful when the service is already using oslo messaging notifications and wants to use a different driver for auditing e.g. service has existing notifications sent to queue via 'messagingv2' and wants to send audit notifications to a log file via 'log' driver. Example shown below:: [audit_middleware_notifications] driver = log When audit events are sent via 'messagingv2' or 'messaging', middleware can specify a transport URL if its transport URL needs to be different from the service's own messaging transport setting. Other Transport related settings are read from oslo messaging sections defined in service configuration e.g. 'oslo_messaging_rabbit'. Example shown below:: [audit_middleware_notifications] driver = messaging transport_url = rabbit://user2:passwd@host:5672/another_virtual_host .. _pyCADF library: https://github.com/openstack/pycadf/tree/master/etc/pycadf keystonemiddleware-4.21.0/doc/source/middlewarearchitecture.rst0000666000175100017510000002766713225160624025065 0ustar zuulzuul00000000000000.. Copyright 2011-2013 OpenStack Foundation All Rights Reserved. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. ======================= Middleware Architecture ======================= Abstract ======== The keystonemiddleware architecture supports a common authentication protocol in use between the OpenStack projects. By using keystone as a common authentication and authorization mechanism, various OpenStack projects can leverage the existing authentication and authorization systems in use. In this document, we describe the architecture and responsibilities of the authentication middleware which acts as the internal API mechanism for OpenStack projects based on the WSGI standard. This documentation describes the implementation in :class:`keystonemiddleware.auth_token` Specification Overview ====================== 'Authentication' is the process of determining that users are who they say they are. Typically, 'authentication protocols' such as HTTP Basic Auth, Digest Access, public key, token, etc, are used to verify a user's identity. In this document, we define an 'authentication component' as a software module that implements an authentication protocol for an OpenStack service. Bearer tokens are currently the most common authentication protocol used within OpenStack. At a high level, an authentication middleware component is a proxy that intercepts HTTP calls from clients and populates HTTP headers in the request context for other WSGI middleware or applications to use. The general flow of the middleware processing is: * clear any existing authorization headers to prevent forgery * collect the token from the existing HTTP request headers * validate the token * if valid, populate additional headers representing the identity that has been authenticated and authorized * if invalid, or no token present, reject the request (HTTPUnauthorized) or pass along a header indicating the request is unauthorized (configurable in the middleware) * if the keystone service is unavailable to validate the token, reject the request with HTTPServiceUnavailable. .. _authComponent: Authentication Component ------------------------ The following shows the default behavior of an Authentication Component deployed in front of an OpenStack service. .. image:: images/graphs_authComp.svg :width: 100% :height: 180 :alt: An Authentication Component The Authentication Component, or middleware, will reject any unauthenticated requests, only allowing authenticated requests through to the OpenStack service. .. _authComponentDelegated: Authentication Component (Delegated Mode) ----------------------------------------- The Authentication Component may be configured to operate in a 'delegated mode'. In this mode, the decision to reject or accept an unauthenticated client is delegated to the OpenStack service. Here, requests are forwarded to the OpenStack service with an identity status message that indicates whether the identity of the client has been confirmed or is indeterminate. The consuming OpenStack service decides whether or not a rejection message should be sent to the client. .. image:: images/graphs_authCompDelegate.svg :width: 100% :height: 180 :alt: An Authentication Component (Delegated Mode) .. _deployStrategies: Deployment Strategy =================== The middleware is intended to be used inline with OpenStack WSGI components, based on the Oslo WSGI middleware class. It is typically deployed as a configuration element in a paste configuration pipeline of other middleware components, with the pipeline terminating in the service application. The middleware conforms to the python WSGI standard [PEP-333]_. In initializing the middleware, a configuration item (which acts like a python dictionary) is passed to the middleware with relevant configuration options. Configuration ------------- The middleware is configured within the config file of the main application as a WSGI component. Example for the auth_token middleware: .. code-block:: ini [app:myService] paste.app_factory = myService:app_factory [pipeline:main] pipeline = authtoken myService [filter:authtoken] paste.filter_factory = keystonemiddleware.auth_token:filter_factory .. literalinclude:: _static/keystonemiddleware.conf.sample If the ``auth_plugin`` configuration option is set, you may need to refer to the `Authentication Plugins `_ document for how to configure the auth_token middleware. For services which have a separate paste-deploy ini file, auth_token middleware can be alternatively configured in [keystone_authtoken] section in the main config file. For example in nova, all middleware parameters can be removed from ``api-paste.ini``: .. code-block:: ini [filter:authtoken] paste.filter_factory = keystonemiddleware.auth_token:filter_factory and set in ``nova.conf``: .. code-block:: ini [DEFAULT] auth_strategy=keystone [keystone_authtoken] identity_uri = http://127.0.0.1:35357 admin_user = admin admin_password = SuperSekretPassword admin_tenant_name = service # Any of the options that could be set in api-paste.ini can be set here. .. NOTE:: Middleware parameters in paste config take priority and must be removed to use options in the [keystone_authtoken] section. The following is an example of a service's auth_token middleware configuration when ``auth_plugin`` is set to ``password``. .. code-block:: ini [keystone_authtoken] auth_plugin = password project_domain_name = Default project_name = service user_domain_name = Default username = nova password = ServicePassword auth_url = http://127.0.0.1:35357 # Any of the options that could be set in api-paste.ini can be set here. If the service doesn't use the global oslo.config object (CONF), then the oslo config project name can be set it in paste config and keystonemiddleware will load the project configuration itself. Optionally the location of the configuration file can be set if oslo.config is not able to discover it. .. code-block:: ini [filter:authtoken] paste.filter_factory = keystonemiddleware.auth_token:filter_factory oslo_config_project = nova # oslo_config_file = /not_discoverable_location/nova.conf Improving response time ----------------------- Validating the identity of every client on every request can impact performance for both the OpenStack service and the identity service. As a result, keystonemiddleware is configurable to cache authentication responses from the identity service in-memory. It is worth noting that tokens invalidated after they've been stored in the cache may continue to work. Deployments using `memcached`_ may use the following keystonemiddleware configuration options instead of an in-memory cache. * ``memcached_servers``: (optional) if defined, the memcached server(s) to use for caching. It will be ignored if Swift MemcacheRing is used instead. * ``token_cache_time``: (optional, default 300 seconds) Set to -1 to disable caching completely. When deploying auth_token middleware with Swift, user may elect to use Swift MemcacheRing instead of the local Keystone memcache. The Swift MemcacheRing object is passed in from the request environment and it defaults to 'swift.cache'. However it could be different, depending on deployment. To use Swift MemcacheRing, you must provide the ``cache`` option. * ``cache``: (optional) if defined, the environment key where the Swift MemcacheRing object is stored. Memcached dependencies ====================== In order to use `memcached`_ it is necessary to install the `python-memcached`_ library. If data stored in `memcached`_ will need to be encrypted it is also necessary to install the `pycrypto`_ library. These libs are not listed in the requirements.txt file. .. _`memcached`: http://memcached.org/ .. _`python-memcached`: https://pypi.python.org/pypi/python-memcached .. _`pycrypto`: https://pypi.python.org/pypi/pycrypto Memcache Protection =================== When using `memcached`_, tokens and authentication responses are stored in the cache as raw data. In the event the cache is compromised, all token and authentication responses will be readable. To mitigate this risk, ``auth_token`` middleware provides an option to authenticate and optionally encrypt the token data stored in the cache. * ``memcache_security_strategy``: (optional) if defined, indicate whether token data should be authenticated or authenticated and encrypted. Acceptable values are ``MAC`` or ``ENCRYPT``. If ``MAC``, token data is authenticated (with HMAC) in the cache. If ``ENCRYPT``, token data is encrypted and authenticated in the cache. If the value is not one of these options or empty, ``auth_token`` will raise an exception on initialization. * ``memcache_secret_key``: (optional, mandatory if ``memcache_security_strategy`` is defined) this string is used for key derivation. If ``memcache_security_strategy`` is defined and ``memcache_secret_key`` is absent, ``auth_token`` will raise an exception on initialization. Exchanging User Information =========================== The middleware expects to find a token representing the user with the header ``X-Auth-Token`` or ``X-Storage-Token``. `X-Storage-Token` is supported for swift/cloud files and for legacy Rackspace use. If the token isn't present and the middleware is configured to not delegate auth responsibility, it will respond to the HTTP request with HTTPUnauthorized, returning the header ``WWW-Authenticate`` with the value `Keystone uri='...'` to indicate where to request a token. The URI returned is configured with the ``www_authenticate_uri`` option. The authentication middleware extends the HTTP request with the header ``X-Identity-Status``. If a request is successfully authenticated, the value is set to `Confirmed`. If the middleware is delegating the auth decision to the service, then the status is set to `Invalid` if the auth request was unsuccessful. An ``X-Service-Token`` header may also be included with a request. If present, and the value of ``X-Auth-Token`` or ``X-Storage-Token`` has not caused the request to be denied, then the middleware will attempt to validate the value of ``X-Service-Token``. If valid, the authentication middleware extends the HTTP request with the header ``X-Service-Identity-Status`` having value `Confirmed` and also extends the request with additional headers representing the identity authenticated and authorised by the token. If ``X-Service-Token`` is present and its value is invalid and the ``delay_auth_decision`` option is True then the value of ``X-Service-Identity-Status`` is set to `Invalid` and no further headers are added. Otherwise if ``X-Service-Token`` is present and its value is invalid then the middleware will respond to the HTTP request with HTTPUnauthorized, regardless of the validity of the ``X-Auth-Token`` or ``X-Storage-Token`` values. Extended the request with additional User Information ----------------------------------------------------- :py:class:`keystonemiddleware.auth_token.AuthProtocol` extends the request with additional information if the user has been authenticated. See the "What we add to the request for use by the OpenStack service" section in :py:mod:`keystonemiddleware.auth_token` for the list of fields set by the auth_token middleware. References ========== .. [PEP-333] pep0333 Phillip J Eby. 'Python Web Server Gateway Interface v1.0.'' http://www.python.org/dev/peps/pep-0333/. keystonemiddleware-4.21.0/doc/source/history.rst0000666000175100017510000000003513225160624022022 0ustar zuulzuul00000000000000.. include:: ../../ChangeLog keystonemiddleware-4.21.0/doc/source/installation.rst0000666000175100017510000000110513225160624023021 0ustar zuulzuul00000000000000============== Installation ============== Install using pip ----------------- At the command line:: $ pip install keystonemiddleware Or, if you want to use it in a virtualenvwrapper:: $ mkvirtualenv keystonemiddleware $ pip install keystonemiddleware Install optional dependencies ----------------------------- Certain keystonemiddleware features are only available if specific libraries are available. These libraries can be installed using pip as well. To install support for audit notifications:: $ pip install keystonemiddleware[audit_notifications] keystonemiddleware-4.21.0/doc/source/index.rst0000666000175100017510000000312513225160624021433 0ustar zuulzuul00000000000000Python Middleware for OpenStack Identity API (Keystone) ======================================================= This is the middleware provided for integrating with the OpenStack Identity API and handling authorization enforcement based upon the data within the OpenStack Identity tokens. Also included is middleware that provides the ability to create audit events based on API requests. Contents: .. toctree:: :maxdepth: 1 middlewarearchitecture audit installation Related Identity Projects ========================= In addition to creating the Python Middleware for OpenStack Identity API, the Keystone team also provides `Identity Service`_, as well as `Python Client Library`_. .. _`Identity Service`: https://docs.openstack.org/keystone/latest/ .. _`Python Client Library`: https://docs.openstack.org/python-keystoneclient/latest/ Release Notes ============= .. toctree:: :maxdepth: 1 history Contributing ============ Code is hosted `on GitHub`_. Submit bugs to the Keystone project on `Launchpad`_. Submit code to the ``openstack/keystonemiddleware`` project using `Gerrit`_. .. _on GitHub: https://github.com/openstack/keystonemiddleware .. _Launchpad: https://launchpad.net/keystonemiddleware .. _Gerrit: https://docs.openstack.org/infra/manual/developers.html#development-workflow Run tests with ``tox -e py27`` if running with python 2.7. See the ``tox.ini`` file for other test environment options. Code Documentation ================== .. toctree:: :maxdepth: 1 api/modules Indices and tables ================== * :ref:`genindex` * :ref:`modindex` * :ref:`search` keystonemiddleware-4.21.0/doc/.gitignore0000666000175100017510000000002313225160624020254 0ustar zuulzuul00000000000000build/ source/api/ keystonemiddleware-4.21.0/doc/Makefile0000666000175100017510000000616213225160624017736 0ustar zuulzuul00000000000000# Makefile for Sphinx documentation # # You can set these variables from the command line. SPHINXOPTS = SPHINXBUILD = sphinx-build SPHINXSOURCE = source PAPER = BUILDDIR = build # Internal variables. PAPEROPT_a4 = -D latex_paper_size=a4 PAPEROPT_letter = -D latex_paper_size=letter ALLSPHINXOPTS = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) $(SPHINXSOURCE) .PHONY: help clean html dirhtml pickle json htmlhelp qthelp latex changes linkcheck doctest help: @echo "Please use \`make ' where is one of" @echo " html to make standalone HTML files" @echo " dirhtml to make HTML files named index.html in directories" @echo " pickle to make pickle files" @echo " json to make JSON files" @echo " htmlhelp to make HTML files and a HTML help project" @echo " qthelp to make HTML files and a qthelp project" @echo " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter" @echo " changes to make an overview of all changed/added/deprecated items" @echo " linkcheck to check all external links for integrity" @echo " doctest to run all doctests embedded in the documentation (if enabled)" clean: -rm -rf $(BUILDDIR)/* html: $(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html @echo @echo "Build finished. The HTML pages are in $(BUILDDIR)/html." dirhtml: $(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml @echo @echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml." pickle: $(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle @echo @echo "Build finished; now you can process the pickle files." json: $(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json @echo @echo "Build finished; now you can process the JSON files." htmlhelp: $(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp @echo @echo "Build finished; now you can run HTML Help Workshop with the" \ ".hhp project file in $(BUILDDIR)/htmlhelp." qthelp: $(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp @echo @echo "Build finished; now you can run "qcollectiongenerator" with the" \ ".qhcp project file in $(BUILDDIR)/qthelp, like this:" @echo "# qcollectiongenerator $(BUILDDIR)/qthelp/keystonemiddleware.qhcp" @echo "To view the help file:" @echo "# assistant -collectionFile $(BUILDDIR)/qthelp/keystonemiddleware.qhc" latex: $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex @echo @echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex." @echo "Run \`make all-pdf' or \`make all-ps' in that directory to" \ "run these through (pdf)latex." changes: $(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes @echo @echo "The overview file is in $(BUILDDIR)/changes." linkcheck: $(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck @echo @echo "Link check complete; look for any errors in the above output " \ "or in $(BUILDDIR)/linkcheck/output.txt." doctest: $(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest @echo "Testing of doctests in the sources finished, look at the " \ "results in $(BUILDDIR)/doctest/output.txt." keystonemiddleware-4.21.0/doc/ext/0000775000175100017510000000000013225161130017060 5ustar zuulzuul00000000000000keystonemiddleware-4.21.0/doc/ext/__init__.py0000666000175100017510000000000013225160624021170 0ustar zuulzuul00000000000000keystonemiddleware-4.21.0/doc/ext/apidoc.py0000666000175100017510000000310113225160624020675 0ustar zuulzuul00000000000000# Copyright 2014 OpenStack Foundation # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. # NOTE(blk-u): Uncomment the [pbr] section in setup.cfg and remove this # Sphinx extension when https://launchpad.net/bugs/1260495 is fixed. import os.path as path from sphinx import apidoc # NOTE(blk-u): pbr will run Sphinx multiple times when it generates # documentation. Once for each builder. To run this extension we use the # 'builder-inited' hook that fires at the beginning of a Sphinx build. # We use ``run_already`` to make sure apidocs are only generated once # even if Sphinx is run multiple times. run_already = False def run_apidoc(app): global run_already if run_already: return run_already = True package_dir = path.abspath(path.join(app.srcdir, '..', '..', 'keystonemiddleware')) source_dir = path.join(app.srcdir, 'api') apidoc.main(['apidoc', package_dir, '-f', '-H', 'keystonemiddleware Modules', '-o', source_dir]) def setup(app): app.connect('builder-inited', run_apidoc) keystonemiddleware-4.21.0/setup.cfg0000666000175100017510000000345013225161130017340 0ustar zuulzuul00000000000000[metadata] name = keystonemiddleware summary = Middleware for OpenStack Identity description-file = README.rst author = OpenStack author-email = openstack-dev@lists.openstack.org home-page = https://docs.openstack.org/developer/keystonemiddleware/ classifier = Environment :: OpenStack Intended Audience :: Information Technology Intended Audience :: System Administrators License :: OSI Approved :: Apache Software License Operating System :: POSIX :: Linux Programming Language :: Python Programming Language :: Python :: 2 Programming Language :: Python :: 2.7 Programming Language :: Python :: 3 Programming Language :: Python :: 3.5 [files] packages = keystonemiddleware [extras] audit_notifications = oslo.messaging>=5.29.0 # Apache-2.0 [global] setup-hooks = pbr.hooks.setup_hook [entry_points] oslo.config.opts = keystonemiddleware.auth_token = keystonemiddleware.auth_token._opts:list_opts paste.filter_factory = auth_token = keystonemiddleware.auth_token:filter_factory audit = keystonemiddleware.audit:filter_factory ec2_token = keystonemiddleware.ec2_token:filter_factory s3_token = keystonemiddleware.s3_token:filter_factory [build_sphinx] source-dir = doc/source build-dir = doc/build all_files = 1 warning-is-error = 1 [pbr] autodoc_tree_index_modules = True autodoc_tree_excludes = setup.py [upload_sphinx] upload-dir = doc/build/html [compile_catalog] directory = keystonemiddleware/locale domain = keystonemiddleware [update_catalog] domain = keystonemiddleware output_dir = keystonemiddleware/locale input_file = keystonemiddleware/locale/keystonemiddleware.pot [extract_messages] keywords = _ gettext ngettext l_ lazy_gettext mapping_file = babel.cfg output_file = keystonemiddleware/locale/keystonemiddleware.pot [wheel] universal = 1 [egg_info] tag_build = tag_date = 0 keystonemiddleware-4.21.0/keystonemiddleware/0000775000175100017510000000000013225161130021412 5ustar zuulzuul00000000000000keystonemiddleware-4.21.0/keystonemiddleware/_common/0000775000175100017510000000000013225161130023041 5ustar zuulzuul00000000000000keystonemiddleware-4.21.0/keystonemiddleware/_common/__init__.py0000666000175100017510000000000013225160624025151 0ustar zuulzuul00000000000000keystonemiddleware-4.21.0/keystonemiddleware/_common/config.py0000666000175100017510000001310313225160624024667 0ustar zuulzuul00000000000000# Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. import pkg_resources from oslo_config import cfg from oslo_log import log as logging import pbr from keystonemiddleware import exceptions from keystonemiddleware.i18n import _ CONF = cfg.CONF _NOT_SET = object() _LOG = logging.getLogger(__name__) def _conf_values_type_convert(group_name, all_options, conf): """Convert conf values into correct type.""" if not conf: return {} opts = {} opt_types = {} for group, options in all_options: # only accept paste overrides for the primary group if group != group_name: continue for o in options: type_dest = (getattr(o, 'type', str), o.dest) opt_types[o.dest] = type_dest # Also add the deprecated name with the same type and dest. for d_o in o.deprecated_opts: opt_types[d_o.name] = type_dest break for k, v in conf.items(): dest = k try: if v is not None: type_, dest = opt_types[k] v = type_(v) except KeyError: # nosec # This option is not known to auth_token. v is not converted. _LOG.warning( 'The option "%s" in conf is not known to auth_token', k) except ValueError as e: raise exceptions.ConfigurationError( _('Unable to convert the value of %(key)s option into correct ' 'type: %(ex)s') % {'key': k, 'ex': e}) opts[dest] = v return opts class Config(object): def __init__(self, name, group_name, all_options, conf): local_oslo_config = conf.pop('oslo_config_config', None) local_config_project = conf.pop('oslo_config_project', None) local_config_file = conf.pop('oslo_config_file', None) # NOTE(wanghong): If options are set in paste file, all the option # values passed into conf are string type. So, we should convert the # conf value into correct type. self.paste_overrides = _conf_values_type_convert(group_name, all_options, conf) # NOTE(sileht, cdent): If we don't want to use oslo.config global # object there are two options: set "oslo_config_project" in # paste.ini and the middleware will load the configuration with a # local oslo.config object or the caller which instantiates # AuthProtocol can pass in an existing oslo.config as the # value of the "oslo_config_config" key in conf. If both are # set "olso_config_config" is used. if local_config_project and not local_oslo_config: config_files = [local_config_file] if local_config_file else None local_oslo_config = cfg.ConfigOpts() local_oslo_config([], project=local_config_project, default_config_files=config_files, validate_default_values=True) if local_oslo_config: for group, opts in all_options: local_oslo_config.register_opts(opts, group=group) self.name = name self.oslo_conf_obj = local_oslo_config or cfg.CONF self.group_name = group_name self._user_agent = None def get(self, name, group=_NOT_SET): # try config from paste-deploy first try: return self.paste_overrides[name] except KeyError: if group is _NOT_SET: group = self.group_name return self.oslo_conf_obj[group][name] @property def project(self): """Determine a project name from all available config sources. The sources are checked in the following order: 1. The paste-deploy config for auth_token middleware 2. The keystone_authtoken or base group in the project's config 3. The oslo.config CONF.project property """ try: return self.get('project', group=self.group_name) except cfg.NoSuchOptError: try: # CONF.project will exist only if the service uses # oslo.config. It will only be set when the project # calls CONF(...) and when not set oslo.config oddly # raises a NoSuchOptError exception. return self.oslo_conf_obj.project except cfg.NoSuchOptError: return None @property def user_agent(self): if not self._user_agent: project = self.project or '' if project: try: version = pkg_resources.get_distribution(project).version except pkg_resources.DistributionNotFound: version = "unknown" project = "%s/%s " % (project, version) self._user_agent = "%skeystonemiddleware.%s/%s" % ( project, self.name, pbr.version.VersionInfo('keystonemiddleware').version_string()) return self._user_agent keystonemiddleware-4.21.0/keystonemiddleware/tests/0000775000175100017510000000000013225161130022554 5ustar zuulzuul00000000000000keystonemiddleware-4.21.0/keystonemiddleware/tests/__init__.py0000666000175100017510000000000013225160624024664 0ustar zuulzuul00000000000000keystonemiddleware-4.21.0/keystonemiddleware/tests/unit/0000775000175100017510000000000013225161130023533 5ustar zuulzuul00000000000000keystonemiddleware-4.21.0/keystonemiddleware/tests/unit/audit/0000775000175100017510000000000013225161130024641 5ustar zuulzuul00000000000000keystonemiddleware-4.21.0/keystonemiddleware/tests/unit/audit/test_logging_notifier.py0000666000175100017510000000276613225160624031623 0ustar zuulzuul00000000000000# Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. import fixtures import mock from keystonemiddleware.tests.unit.audit import base class TestLoggingNotifier(base.BaseAuditMiddlewareTest): def setUp(self): p = 'keystonemiddleware.audit._notifier.oslo_messaging' f = fixtures.MockPatch(p, None) self.messaging_fixture = self.useFixture(f) super(TestLoggingNotifier, self).setUp() def test_api_request_no_messaging(self): app = self.create_simple_app() with mock.patch('keystonemiddleware.audit._LOG.info') as log: app.get('/foo/bar', extra_environ=self.get_environ_header()) # Check first notification with only 'request' call_args = log.call_args_list[0][0] self.assertEqual('audit.http.request', call_args[1]['event_type']) # Check second notification with request + response call_args = log.call_args_list[1][0] self.assertEqual('audit.http.response', call_args[1]['event_type']) keystonemiddleware-4.21.0/keystonemiddleware/tests/unit/audit/__init__.py0000666000175100017510000000000013225160624026751 0ustar zuulzuul00000000000000keystonemiddleware-4.21.0/keystonemiddleware/tests/unit/audit/base.py0000666000175100017510000000575113225160624026146 0ustar zuulzuul00000000000000# Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. from oslo_config import fixture as cfg_fixture from oslo_messaging import conffixture as msg_fixture from oslotest import createfile import webob.dec from keystonemiddleware import audit from keystonemiddleware.tests.unit import utils audit_map_content = """ [custom_actions] reboot = start/reboot os-migrations/get = read [path_keywords] action = None os-hosts = host os-migrations = None reboot = None servers = server [service_endpoints] compute = service/compute """ class BaseAuditMiddlewareTest(utils.MiddlewareTestCase): PROJECT_NAME = 'keystonemiddleware' def setUp(self): super(BaseAuditMiddlewareTest, self).setUp() self.audit_map_file_fixture = self.useFixture( createfile.CreateFileWithContent('audit', audit_map_content)) self.cfg = self.useFixture(cfg_fixture.Config()) self.msg = self.useFixture(msg_fixture.ConfFixture(self.cfg.conf)) self.cfg.conf([], project=self.PROJECT_NAME) def create_middleware(self, cb, **kwargs): @webob.dec.wsgify def _do_cb(req): return cb(req) kwargs.setdefault('audit_map_file', self.audit_map) kwargs.setdefault('service_name', 'pycadf') return audit.AuditMiddleware(_do_cb, **kwargs) @property def audit_map(self): return self.audit_map_file_fixture.path @staticmethod def get_environ_header(req_type=None): env_headers = {'HTTP_X_SERVICE_CATALOG': '''[{"endpoints_links": [], "endpoints": [{"adminURL": "http://admin_host:8774", "region": "RegionOne", "publicURL": "http://public_host:8774", "internalURL": "http://internal_host:8774", "id": "resource_id"}], "type": "compute", "name": "nova"}]''', 'HTTP_X_USER_ID': 'user_id', 'HTTP_X_USER_NAME': 'user_name', 'HTTP_X_AUTH_TOKEN': 'token', 'HTTP_X_PROJECT_ID': 'tenant_id', 'HTTP_X_IDENTITY_STATUS': 'Confirmed'} if req_type: env_headers['REQUEST_METHOD'] = req_type return env_headers keystonemiddleware-4.21.0/keystonemiddleware/tests/unit/audit/test_audit_api.py0000666000175100017510000004021713225160624030226 0ustar zuulzuul00000000000000# Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. import uuid from pycadf import cadftaxonomy as taxonomy import webob from keystonemiddleware import audit from keystonemiddleware.tests.unit.audit import base class AuditApiLogicTest(base.BaseAuditMiddlewareTest): def get_payload(self, method, url, audit_map=None, body=None, environ=None): audit_map = audit_map or self.audit_map environ = environ or self.get_environ_header() req = webob.Request.blank(url, body=body, method=method, environ=environ, remote_addr='192.168.0.1') middleware = audit.OpenStackAuditApi(audit_map) return middleware._create_event(req).as_dict() def test_get_list(self): path = '/v2/' + str(uuid.uuid4()) + '/servers' url = 'http://admin_host:8774' + path payload = self.get_payload('GET', url) self.assertEqual(payload['action'], 'read/list') self.assertEqual(payload['typeURI'], 'http://schemas.dmtf.org/cloud/audit/1.0/event') self.assertEqual(payload['outcome'], 'pending') self.assertEqual(payload['eventType'], 'activity') self.assertEqual(payload['target']['name'], 'nova') self.assertEqual(payload['target']['id'], 'resource_id') self.assertEqual(payload['target']['typeURI'], 'service/compute/servers') self.assertEqual(len(payload['target']['addresses']), 3) self.assertEqual(payload['target']['addresses'][0]['name'], 'admin') self.assertEqual(payload['target']['addresses'][0]['url'], 'http://admin_host:8774') self.assertEqual(payload['initiator']['id'], 'user_id') self.assertEqual(payload['initiator']['name'], 'user_name') self.assertEqual(payload['initiator']['project_id'], 'tenant_id') self.assertEqual(payload['initiator']['host']['address'], '192.168.0.1') self.assertEqual(payload['initiator']['typeURI'], 'service/security/account/user') self.assertNotEqual(payload['initiator']['credential']['token'], 'token') self.assertEqual(payload['initiator']['credential']['identity_status'], 'Confirmed') self.assertNotIn('reason', payload) self.assertNotIn('reporterchain', payload) self.assertEqual(payload['observer']['id'], 'target') self.assertEqual(path, payload['requestPath']) def test_get_read(self): url = 'http://admin_host:8774/v2/%s/servers/%s' % (uuid.uuid4().hex, uuid.uuid4().hex) payload = self.get_payload('GET', url) self.assertEqual(payload['target']['typeURI'], 'service/compute/servers/server') self.assertEqual(payload['action'], 'read') self.assertEqual(payload['outcome'], 'pending') def test_get_unknown_endpoint(self): url = 'http://unknown:8774/v2/' + str(uuid.uuid4()) + '/servers' payload = self.get_payload('GET', url) self.assertEqual(payload['action'], 'read/list') self.assertEqual(payload['outcome'], 'pending') self.assertEqual(payload['target']['name'], 'unknown') self.assertEqual(payload['target']['id'], 'unknown') self.assertEqual(payload['target']['typeURI'], 'unknown') def test_get_unknown_endpoint_default_set(self): with open(self.audit_map, "w") as f: f.write("[DEFAULT]\n") f.write("target_endpoint_type = compute\n") f.write("[path_keywords]\n") f.write("servers = server\n\n") f.write("[service_endpoints]\n") f.write("compute = service/compute") url = 'http://unknown:8774/v2/' + str(uuid.uuid4()) + '/servers' payload = self.get_payload('GET', url) self.assertEqual(payload['action'], 'read/list') self.assertEqual(payload['outcome'], 'pending') self.assertEqual(payload['target']['name'], 'nova') self.assertEqual(payload['target']['id'], 'resource_id') self.assertEqual(payload['target']['typeURI'], 'service/compute/servers') def test_put(self): url = 'http://admin_host:8774/v2/' + str(uuid.uuid4()) + '/servers' payload = self.get_payload('PUT', url) self.assertEqual(payload['target']['typeURI'], 'service/compute/servers') self.assertEqual(payload['action'], 'update') self.assertEqual(payload['outcome'], 'pending') def test_delete(self): url = 'http://admin_host:8774/v2/' + str(uuid.uuid4()) + '/servers' payload = self.get_payload('DELETE', url) self.assertEqual(payload['target']['typeURI'], 'service/compute/servers') self.assertEqual(payload['action'], 'delete') self.assertEqual(payload['outcome'], 'pending') def test_head(self): url = 'http://admin_host:8774/v2/' + str(uuid.uuid4()) + '/servers' payload = self.get_payload('HEAD', url) self.assertEqual(payload['target']['typeURI'], 'service/compute/servers') self.assertEqual(payload['action'], 'read') self.assertEqual(payload['outcome'], 'pending') def test_post_update(self): url = 'http://admin_host:8774/v2/%s/servers/%s' % (uuid.uuid4().hex, uuid.uuid4().hex) payload = self.get_payload('POST', url) self.assertEqual(payload['target']['typeURI'], 'service/compute/servers/server') self.assertEqual(payload['action'], 'update') self.assertEqual(payload['outcome'], 'pending') def test_post_create(self): url = 'http://admin_host:8774/v2/' + str(uuid.uuid4()) + '/servers' payload = self.get_payload('POST', url) self.assertEqual(payload['target']['typeURI'], 'service/compute/servers') self.assertEqual(payload['action'], 'create') self.assertEqual(payload['outcome'], 'pending') def test_post_action(self): url = 'http://admin_host:8774/v2/%s/servers/action' % uuid.uuid4().hex body = b'{"createImage" : {"name" : "new-image","metadata": ' \ b'{"ImageType": "Gold","ImageVersion": "2.0"}}}' payload = self.get_payload('POST', url, body=body) self.assertEqual(payload['target']['typeURI'], 'service/compute/servers/action') self.assertEqual(payload['action'], 'update/createImage') self.assertEqual(payload['outcome'], 'pending') def test_post_empty_body_action(self): url = 'http://admin_host:8774/v2/%s/servers/action' % uuid.uuid4().hex payload = self.get_payload('POST', url) self.assertEqual(payload['target']['typeURI'], 'service/compute/servers/action') self.assertEqual(payload['action'], 'create') self.assertEqual(payload['outcome'], 'pending') def test_custom_action(self): url = 'http://admin_host:8774/v2/%s/os-hosts/%s/reboot' % ( uuid.uuid4().hex, uuid.uuid4().hex) payload = self.get_payload('GET', url) self.assertEqual(payload['target']['typeURI'], 'service/compute/os-hosts/host/reboot') self.assertEqual(payload['action'], 'start/reboot') self.assertEqual(payload['outcome'], 'pending') def test_custom_action_complex(self): url = 'http://admin_host:8774/v2/%s/os-migrations' % uuid.uuid4().hex payload = self.get_payload('GET', url) self.assertEqual(payload['target']['typeURI'], 'service/compute/os-migrations') self.assertEqual(payload['action'], 'read') payload = self.get_payload('POST', url) self.assertEqual(payload['target']['typeURI'], 'service/compute/os-migrations') self.assertEqual(payload['action'], 'create') def test_response_mod_msg(self): url = 'http://admin_host:8774/v2/' + str(uuid.uuid4()) + '/servers' req = webob.Request.blank(url, environ=self.get_environ_header('GET'), remote_addr='192.168.0.1') req.context = {} middleware = self.create_simple_middleware() middleware._process_request(req) payload = req.environ['cadf_event'].as_dict() middleware._process_response(req, webob.Response()) payload2 = req.environ['cadf_event'].as_dict() self.assertEqual(payload['id'], payload2['id']) self.assertEqual(payload['tags'], payload2['tags']) self.assertEqual(payload2['outcome'], 'success') self.assertEqual(payload2['reason']['reasonType'], 'HTTP') self.assertEqual(payload2['reason']['reasonCode'], '200') self.assertEqual(len(payload2['reporterchain']), 1) self.assertEqual(payload2['reporterchain'][0]['role'], 'modifier') self.assertEqual(payload2['reporterchain'][0]['reporter']['id'], 'target') def test_missing_catalog_endpoint_id(self): env_headers = {'HTTP_X_SERVICE_CATALOG': '''[{"endpoints_links": [], "endpoints": [{"adminURL": "http://admin_host:8774", "region": "RegionOne", "publicURL": "http://public_host:8774", "internalURL": "http://internal_host:8774"}], "type": "compute", "name": "nova"}]''', 'HTTP_X_USER_ID': 'user_id', 'HTTP_X_USER_NAME': 'user_name', 'HTTP_X_AUTH_TOKEN': 'token', 'HTTP_X_PROJECT_ID': 'tenant_id', 'HTTP_X_IDENTITY_STATUS': 'Confirmed', 'REQUEST_METHOD': 'GET'} url = 'http://admin_host:8774/v2/' + str(uuid.uuid4()) + '/servers' payload = self.get_payload('GET', url, environ=env_headers) self.assertEqual(payload['target']['id'], 'nova') def test_endpoint_missing_internal_url(self): env_headers = {'HTTP_X_SERVICE_CATALOG': '''[{"endpoints_links": [], "endpoints": [{"adminURL": "http://admin_host:8774", "region": "RegionOne", "publicURL": "http://public_host:8774"}], "type": "compute", "name": "nova"}]''', 'HTTP_X_USER_ID': 'user_id', 'HTTP_X_USER_NAME': 'user_name', 'HTTP_X_AUTH_TOKEN': 'token', 'HTTP_X_PROJECT_ID': 'tenant_id', 'HTTP_X_IDENTITY_STATUS': 'Confirmed', 'REQUEST_METHOD': 'GET'} url = 'http://admin_host:8774/v2/' + str(uuid.uuid4()) + '/servers' payload = self.get_payload('GET', url, environ=env_headers) self.assertEqual((payload['target']['addresses'][1]['url']), "unknown") def test_endpoint_missing_public_url(self): env_headers = {'HTTP_X_SERVICE_CATALOG': '''[{"endpoints_links": [], "endpoints": [{"adminURL": "http://admin_host:8774", "region": "RegionOne", "internalURL": "http://internal_host:8774"}], "type": "compute", "name": "nova"}]''', 'HTTP_X_USER_ID': 'user_id', 'HTTP_X_USER_NAME': 'user_name', 'HTTP_X_AUTH_TOKEN': 'token', 'HTTP_X_PROJECT_ID': 'tenant_id', 'HTTP_X_IDENTITY_STATUS': 'Confirmed', 'REQUEST_METHOD': 'GET'} url = 'http://admin_host:8774/v2/' + str(uuid.uuid4()) + '/servers' payload = self.get_payload('GET', url, environ=env_headers) self.assertEqual((payload['target']['addresses'][2]['url']), "unknown") def test_endpoint_missing_admin_url(self): env_headers = {'HTTP_X_SERVICE_CATALOG': '''[{"endpoints_links": [], "endpoints": [{"region": "RegionOne", "publicURL": "http://public_host:8774", "internalURL": "http://internal_host:8774"}], "type": "compute", "name": "nova"}]''', 'HTTP_X_USER_ID': 'user_id', 'HTTP_X_USER_NAME': 'user_name', 'HTTP_X_AUTH_TOKEN': 'token', 'HTTP_X_PROJECT_ID': 'tenant_id', 'HTTP_X_IDENTITY_STATUS': 'Confirmed', 'REQUEST_METHOD': 'GET'} url = 'http://public_host:8774/v2/' + str(uuid.uuid4()) + '/servers' payload = self.get_payload('GET', url, environ=env_headers) self.assertEqual((payload['target']['addresses'][0]['url']), "unknown") def test_no_auth_token(self): # Test cases where API requests such as Swift list public containers # which does not require an auth token. In these cases, CADF event # should have the defaults (i.e taxonomy.UNKNOWN) instead of raising # an exception. env_headers = {'HTTP_X_IDENTITY_STATUS': 'Invalid', 'REQUEST_METHOD': 'GET'} path = '/v1/' + str(uuid.uuid4()) url = 'https://23.253.72.207' + path payload = self.get_payload('GET', url, environ=env_headers) self.assertEqual(payload['action'], 'read') self.assertEqual(payload['typeURI'], 'http://schemas.dmtf.org/cloud/audit/1.0/event') self.assertEqual(payload['outcome'], 'pending') self.assertEqual(payload['eventType'], 'activity') self.assertEqual(payload['target']['name'], taxonomy.UNKNOWN) self.assertEqual(payload['target']['id'], taxonomy.UNKNOWN) self.assertEqual(payload['target']['typeURI'], taxonomy.UNKNOWN) self.assertNotIn('addresses', payload['target']) self.assertEqual(payload['initiator']['id'], taxonomy.UNKNOWN) self.assertEqual(payload['initiator']['name'], taxonomy.UNKNOWN) self.assertEqual(payload['initiator']['project_id'], taxonomy.UNKNOWN) self.assertEqual(payload['initiator']['host']['address'], '192.168.0.1') self.assertEqual(payload['initiator']['typeURI'], 'service/security/account/user') self.assertNotEqual(payload['initiator']['credential']['token'], None) self.assertEqual(payload['initiator']['credential']['identity_status'], 'Invalid') self.assertNotIn('reason', payload) self.assertNotIn('reporterchain', payload) self.assertEqual(payload['observer']['id'], 'target') self.assertEqual(path, payload['requestPath']) keystonemiddleware-4.21.0/keystonemiddleware/tests/unit/audit/test_audit_middleware.py0000666000175100017510000002121013225160624031562 0ustar zuulzuul00000000000000# # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. import uuid import fixtures import mock import webob from keystonemiddleware.tests.unit.audit import base class AuditMiddlewareTest(base.BaseAuditMiddlewareTest): def setUp(self): self.notifier = mock.MagicMock() p = 'keystonemiddleware.audit._notifier.create_notifier' f = fixtures.MockPatch(p, return_value=self.notifier) self.notifier_fixture = self.useFixture(f) super(AuditMiddlewareTest, self).setUp() def test_api_request(self): self.create_simple_app().get('/foo/bar', extra_environ=self.get_environ_header()) # Check first notification with only 'request' call_args = self.notifier.notify.call_args_list[0][0] self.assertEqual('audit.http.request', call_args[1]) self.assertEqual('/foo/bar', call_args[2]['requestPath']) self.assertEqual('pending', call_args[2]['outcome']) self.assertNotIn('reason', call_args[2]) self.assertNotIn('reporterchain', call_args[2]) # Check second notification with request + response call_args = self.notifier.notify.call_args_list[1][0] self.assertEqual('audit.http.response', call_args[1]) self.assertEqual('/foo/bar', call_args[2]['requestPath']) self.assertEqual('success', call_args[2]['outcome']) self.assertIn('reason', call_args[2]) self.assertIn('reporterchain', call_args[2]) def test_api_request_failure(self): class CustomException(Exception): pass def cb(req): raise CustomException('It happens!') try: self.create_app(cb).get('/foo/bar', extra_environ=self.get_environ_header()) self.fail('Application exception has not been re-raised') except CustomException: pass # Check first notification with only 'request' call_args = self.notifier.notify.call_args_list[0][0] self.assertEqual('audit.http.request', call_args[1]) self.assertEqual('/foo/bar', call_args[2]['requestPath']) self.assertEqual('pending', call_args[2]['outcome']) self.assertNotIn('reporterchain', call_args[2]) # Check second notification with request + response call_args = self.notifier.notify.call_args_list[1][0] self.assertEqual('audit.http.response', call_args[1]) self.assertEqual('/foo/bar', call_args[2]['requestPath']) self.assertEqual('unknown', call_args[2]['outcome']) self.assertIn('reporterchain', call_args[2]) def test_process_request_fail(self): req = webob.Request.blank('/foo/bar', environ=self.get_environ_header('GET')) req.context = {} self.create_simple_middleware()._process_request(req) self.assertTrue(self.notifier.notify.called) def test_process_response_fail(self): req = webob.Request.blank('/foo/bar', environ=self.get_environ_header('GET')) req.context = {} middleware = self.create_simple_middleware() middleware._process_response(req, webob.response.Response()) self.assertTrue(self.notifier.notify.called) def test_ignore_req_opt(self): app = self.create_simple_app(ignore_req_list='get, PUT') # Check GET/PUT request does not send notification app.get('/skip/foo', extra_environ=self.get_environ_header()) app.put('/skip/foo', extra_environ=self.get_environ_header()) self.assertFalse(self.notifier.notify.called) # Check non-GET/PUT request does send notification app.post('/accept/foo', extra_environ=self.get_environ_header()) self.assertEqual(2, self.notifier.notify.call_count) call_args = self.notifier.notify.call_args_list[0][0] self.assertEqual('audit.http.request', call_args[1]) self.assertEqual('/accept/foo', call_args[2]['requestPath']) call_args = self.notifier.notify.call_args_list[1][0] self.assertEqual('audit.http.response', call_args[1]) self.assertEqual('/accept/foo', call_args[2]['requestPath']) def test_cadf_event_context_scoped(self): self.create_simple_app().get('/foo/bar', extra_environ=self.get_environ_header()) self.assertEqual(2, self.notifier.notify.call_count) first, second = [a[0] for a in self.notifier.notify.call_args_list] # the Context is the first argument. Let's verify it. self.assertIsInstance(first[0], dict) # ensure exact same context is used between request and response self.assertIs(first[0], second[0]) def test_cadf_event_scoped_to_request(self): app = self.create_simple_app() resp = app.get('/foo/bar', extra_environ=self.get_environ_header()) self.assertIsNotNone(resp.request.environ.get('cadf_event')) # ensure exact same event is used between request and response self.assertEqual(self.notifier.calls[0].payload['id'], self.notifier.calls[1].payload['id']) def test_cadf_event_scoped_to_request_on_error(self): middleware = self.create_simple_middleware() req = webob.Request.blank('/foo/bar', environ=self.get_environ_header('GET')) req.context = {} self.notifier.notify.side_effect = Exception('error') middleware(req) self.assertTrue(self.notifier.notify.called) req2 = webob.Request.blank('/foo/bar', environ=self.get_environ_header('GET')) req2.context = {} self.notifier.reset_mock() middleware._process_response(req2, webob.response.Response()) self.assertTrue(self.notifier.notify.called) # ensure event is not the same across requests self.assertNotEqual(req.environ['cadf_event'].id, self.notifier.notify.call_args_list[0][0][2]['id']) def test_project_name_from_oslo_config(self): self.assertEqual(self.PROJECT_NAME, self.create_simple_middleware()._conf.project) def test_project_name_from_local_config(self): project_name = uuid.uuid4().hex middleware = self.create_simple_middleware(project=project_name) self.assertEqual(project_name, middleware._conf.project) def test_no_response(self): middleware = self.create_simple_middleware() url = 'http://admin_host:8774/v2/' + str(uuid.uuid4()) + '/servers' req = webob.Request.blank(url, environ=self.get_environ_header('GET'), remote_addr='192.168.0.1') req.context = {} middleware._process_request(req) payload = req.environ['cadf_event'].as_dict() middleware._process_response(req, None) payload2 = req.environ['cadf_event'].as_dict() self.assertEqual(payload['id'], payload2['id']) self.assertEqual(payload['tags'], payload2['tags']) self.assertEqual(payload2['outcome'], 'unknown') self.assertNotIn('reason', payload2) self.assertEqual(len(payload2['reporterchain']), 1) self.assertEqual(payload2['reporterchain'][0]['role'], 'modifier') self.assertEqual(payload2['reporterchain'][0]['reporter']['id'], 'target') def test_missing_req(self): req = webob.Request.blank('http://admin_host:8774/v2/' + str(uuid.uuid4()) + '/servers', environ=self.get_environ_header('GET')) req.context = {} self.assertNotIn('cadf_event', req.environ) self.create_simple_middleware()._process_response(req, webob.Response()) self.assertIn('cadf_event', req.environ) payload = req.environ['cadf_event'].as_dict() self.assertEqual(payload['outcome'], 'success') self.assertEqual(payload['reason']['reasonType'], 'HTTP') self.assertEqual(payload['reason']['reasonCode'], '200') self.assertEqual(payload['observer']['id'], 'target') keystonemiddleware-4.21.0/keystonemiddleware/tests/unit/audit/test_audit_oslo_messaging.py0000666000175100017510000000772413225160624032474 0ustar zuulzuul00000000000000# Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. import mock from keystonemiddleware.tests.unit.audit import base class AuditNotifierConfigTest(base.BaseAuditMiddlewareTest): def test_conf_middleware_log_and_default_as_messaging(self): self.cfg.config(driver='log', group='audit_middleware_notifications') app = self.create_simple_app() with mock.patch('oslo_messaging.notify._impl_log.LogDriver.notify', side_effect=Exception('error')) as driver: app.get('/foo/bar', extra_environ=self.get_environ_header()) # audit middleware conf has 'log' make sure that driver is invoked # and not the one specified in DEFAULT section self.assertTrue(driver.called) def test_conf_middleware_log_and_oslo_msg_as_messaging(self): self.cfg.config(driver=['messaging'], group='oslo_messaging_notifications') self.cfg.config(driver='log', group='audit_middleware_notifications') app = self.create_simple_app() with mock.patch('oslo_messaging.notify._impl_log.LogDriver.notify', side_effect=Exception('error')) as driver: app.get('/foo/bar', extra_environ=self.get_environ_header()) # audit middleware conf has 'log' make sure that driver is invoked # and not the one specified in oslo_messaging_notifications section self.assertTrue(driver.called) def test_conf_middleware_messaging_and_oslo_msg_as_log(self): self.cfg.config(driver=['log'], group='oslo_messaging_notifications') self.cfg.config(driver='messaging', group='audit_middleware_notifications') app = self.create_simple_app() with mock.patch('oslo_messaging.notify.messaging.MessagingDriver' '.notify', side_effect=Exception('error')) as driver: # audit middleware has 'messaging' make sure that driver is invoked # and not the one specified in oslo_messaging_notifications section app.get('/foo/bar', extra_environ=self.get_environ_header()) self.assertTrue(driver.called) def test_with_no_middleware_notification_conf(self): self.cfg.config(driver=['messaging'], group='oslo_messaging_notifications') self.cfg.config(driver=None, group='audit_middleware_notifications') app = self.create_simple_app() with mock.patch('oslo_messaging.notify.messaging.MessagingDriver' '.notify', side_effect=Exception('error')) as driver: # audit middleware section is not set. So driver needs to be # invoked from oslo_messaging_notifications section. app.get('/foo/bar', extra_environ=self.get_environ_header()) self.assertTrue(driver.called) @mock.patch('oslo_messaging.get_notification_transport') def test_conf_middleware_messaging_and_transport_set(self, m): transport_url = 'rabbit://me:passwd@host:5672/virtual_host' self.cfg.config(driver='messaging', transport_url=transport_url, group='audit_middleware_notifications') self.create_simple_middleware() self.assertTrue(m.called) # make sure first call kwarg 'url' is same as provided transport_url self.assertEqual(transport_url, m.call_args_list[0][1]['url']) keystonemiddleware-4.21.0/keystonemiddleware/tests/unit/test_fixtures.py0000666000175100017510000000530113225160624027025 0ustar zuulzuul00000000000000# Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. import datetime import uuid from oslo_utils import timeutils from keystonemiddleware import fixture from keystonemiddleware.tests.unit.auth_token import test_auth_token_middleware class AuthTokenFixtureTest( test_auth_token_middleware.BaseAuthTokenMiddlewareTest): def setUp(self): self.token_id = uuid.uuid4().hex self.user_id = uuid.uuid4().hex self.username = uuid.uuid4().hex self.project_id = uuid.uuid4().hex self.project_name = uuid.uuid4().hex self.role_list = [uuid.uuid4().hex, uuid.uuid4().hex] super(AuthTokenFixtureTest, self).setUp() self.atm_fixture = self.useFixture(fixture.AuthTokenFixture()) self.atm_fixture.add_token_data(token_id=self.token_id, user_id=self.user_id, user_name=self.username, role_list=self.role_list, project_id=self.project_id, project_name=self.project_name) self.set_middleware() self.middleware._app.expected_env = { 'HTTP_X_USER_ID': self.user_id, 'HTTP_X_USER_NAME': self.username, 'HTTP_X_PROJECT_ID': self.project_id, 'HTTP_X_PROJECT_NAME': self.project_name, 'HTTP_X_ROLES': ','.join(self.role_list)} def test_auth_token_fixture_valid_token(self): resp = self.call_middleware(headers={'X-Auth-Token': self.token_id}) self.assertIn('keystone.token_info', resp.request.environ) def test_auth_token_fixture_invalid_token(self): self.call_middleware( headers={'X-Auth-Token': uuid.uuid4().hex}, expected_status=401) def test_auth_token_fixture_expired_token(self): expired_token_id = uuid.uuid4().hex self.atm_fixture.add_token_data( token_id=expired_token_id, user_id=self.user_id, role_list=self.role_list, expires=(timeutils.utcnow() - datetime.timedelta(seconds=86400))) self.call_middleware( headers={'X-Auth-Token': expired_token_id}, expected_status=401) keystonemiddleware-4.21.0/keystonemiddleware/tests/unit/test_entry_points.py0000666000175100017510000000250213225160624027711 0ustar zuulzuul00000000000000# Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. import stevedore from testtools import matchers from keystonemiddleware.tests.unit import utils class TestPasteDeploymentEntryPoints(utils.BaseTestCase): def test_entry_points(self): expected_factory_names = [ 'audit', 'auth_token', 'ec2_token', 's3_token', ] em = stevedore.ExtensionManager('paste.filter_factory') exp_factories = set(['keystonemiddleware.' + name + ':filter_factory' for name in expected_factory_names]) actual_factories = set(['{0.__module__}:{0.__name__}'.format( extension.plugin) for extension in em]) # Ensure that all factories are defined by their names self.assertThat(actual_factories, matchers.ContainsAll(exp_factories)) keystonemiddleware-4.21.0/keystonemiddleware/tests/unit/auth_token/0000775000175100017510000000000013225161130025674 5ustar zuulzuul00000000000000keystonemiddleware-4.21.0/keystonemiddleware/tests/unit/auth_token/test_connection_pool.py0000666000175100017510000001112113225160624032502 0ustar zuulzuul00000000000000# Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. import time import mock from oslo_cache import _memcache_pool from six.moves import queue import testtools from testtools import matchers from keystonemiddleware.tests.unit import utils class _TestConnectionPool(_memcache_pool.ConnectionPool): destroyed_value = 'destroyed' def _create_connection(self): return mock.MagicMock() def _destroy_connection(self, conn): conn(self.destroyed_value) class TestConnectionPool(utils.TestCase): def setUp(self): super(TestConnectionPool, self).setUp() self.unused_timeout = 10 self.maxsize = 2 self.connection_pool = _TestConnectionPool( maxsize=self.maxsize, unused_timeout=self.unused_timeout) def test_get_context_manager(self): self.assertThat(self.connection_pool.queue, matchers.HasLength(0)) with self.connection_pool.acquire() as conn: self.assertEqual(1, self.connection_pool._acquired) self.assertEqual(0, self.connection_pool._acquired) self.assertThat(self.connection_pool.queue, matchers.HasLength(1)) self.assertEqual(conn, self.connection_pool.queue[0].connection) def test_cleanup_pool(self): self.test_get_context_manager() newtime = time.time() + self.unused_timeout * 2 non_expired_connection = _memcache_pool._PoolItem( ttl=(newtime * 2), connection=mock.MagicMock()) self.connection_pool.queue.append(non_expired_connection) self.assertThat(self.connection_pool.queue, matchers.HasLength(2)) with mock.patch.object(time, 'time', return_value=newtime): conn = self.connection_pool.queue[0].connection with self.connection_pool.acquire(): pass conn.assert_has_calls( [mock.call(self.connection_pool.destroyed_value)]) self.assertThat(self.connection_pool.queue, matchers.HasLength(1)) self.assertEqual(0, non_expired_connection.connection.call_count) def test_acquire_conn_exception_returns_acquired_count(self): class TestException(Exception): pass with mock.patch.object(_TestConnectionPool, '_create_connection', side_effect=TestException): with testtools.ExpectedException(TestException): with self.connection_pool.acquire(): pass self.assertThat(self.connection_pool.queue, matchers.HasLength(0)) self.assertEqual(0, self.connection_pool._acquired) def test_connection_pool_limits_maximum_connections(self): # NOTE(morganfainberg): To ensure we don't lockup tests until the # job limit, explicitly call .get_nowait() and .put_nowait() in this # case. conn1 = self.connection_pool.get_nowait() conn2 = self.connection_pool.get_nowait() # Use a nowait version to raise an Empty exception indicating we would # not get another connection until one is placed back into the queue. self.assertRaises(queue.Empty, self.connection_pool.get_nowait) # Place the connections back into the pool. self.connection_pool.put_nowait(conn1) self.connection_pool.put_nowait(conn2) # Make sure we can get a connection out of the pool again. self.connection_pool.get_nowait() def test_connection_pool_maximum_connection_get_timeout(self): connection_pool = _TestConnectionPool( maxsize=1, unused_timeout=self.unused_timeout, conn_get_timeout=0) def _acquire_connection(): with connection_pool.acquire(): pass # Make sure we've consumed the only available connection from the pool conn = connection_pool.get_nowait() self.assertRaises(_memcache_pool.exception.QueueEmpty, _acquire_connection) # Put the connection back and ensure we can acquire the connection # after it is available. connection_pool.put_nowait(conn) _acquire_connection() keystonemiddleware-4.21.0/keystonemiddleware/tests/unit/auth_token/test_revocations.py0000666000175100017510000001004313225160624031650 0ustar zuulzuul00000000000000# Copyright 2014 IBM Corp. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. import datetime import json import shutil import uuid import mock from keystonemiddleware.auth_token import _exceptions as exc from keystonemiddleware.auth_token import _revocations from keystonemiddleware.auth_token import _signing_dir from keystonemiddleware.tests.unit import utils class RevocationsTests(utils.BaseTestCase): def _setup_revocations(self, revoked_list): directory_name = '/tmp/%s' % uuid.uuid4().hex signing_directory = _signing_dir.SigningDirectory(directory_name) self.addCleanup(shutil.rmtree, directory_name) identity_server = mock.Mock() verify_result_obj = {'revoked': revoked_list} cms_verify = mock.Mock(return_value=json.dumps(verify_result_obj)) revocations = _revocations.Revocations( timeout=datetime.timedelta(1), signing_directory=signing_directory, identity_server=identity_server, cms_verify=cms_verify) return revocations def _check_with_list(self, revoked_list, token_ids): revoked_list = list({'id': r} for r in revoked_list) revocations = self._setup_revocations(revoked_list) revocations.check(token_ids) def test_check_empty_list(self): # When the identity server returns an empty list, a token isn't # revoked. revoked_tokens = [] token_ids = [uuid.uuid4().hex] # No assert because this would raise self._check_with_list(revoked_tokens, token_ids) def test_check_revoked(self): # When the identity server returns a list with a token in it, that # token is revoked. token_id = uuid.uuid4().hex revoked_tokens = [token_id] token_ids = [token_id] self.assertRaises(exc.InvalidToken, self._check_with_list, revoked_tokens, token_ids) def test_check_by_audit_id_revoked(self): # When the audit ID is in the revocation list, InvalidToken is raised. audit_id = uuid.uuid4().hex revoked_list = [{'id': uuid.uuid4().hex, 'audit_id': audit_id}] revocations = self._setup_revocations(revoked_list) self.assertRaises(exc.InvalidToken, revocations.check_by_audit_id, [audit_id]) def test_check_by_audit_id_chain_revoked(self): # When the token's audit chain ID is in the revocation list, # InvalidToken is raised. revoked_audit_id = uuid.uuid4().hex revoked_list = [{'id': uuid.uuid4().hex, 'audit_id': revoked_audit_id}] revocations = self._setup_revocations(revoked_list) token_audit_ids = [uuid.uuid4().hex, revoked_audit_id] self.assertRaises(exc.InvalidToken, revocations.check_by_audit_id, token_audit_ids) def test_check_by_audit_id_not_revoked(self): # When the audit ID is not in the revocation list no exception. revoked_list = [{'id': uuid.uuid4().hex, 'audit_id': uuid.uuid4().hex}] revocations = self._setup_revocations(revoked_list) audit_id = uuid.uuid4().hex revocations.check_by_audit_id([audit_id]) def test_check_by_audit_id_no_audit_ids(self): # Older identity servers don't send audit_ids in the revocation list. # When this happens, check_by_audit_id still works, just doesn't # verify anything. revoked_list = [{'id': uuid.uuid4().hex}] revocations = self._setup_revocations(revoked_list) audit_id = uuid.uuid4().hex revocations.check_by_audit_id([audit_id]) keystonemiddleware-4.21.0/keystonemiddleware/tests/unit/auth_token/test_memcache_crypt.py0000666000175100017510000000641013225160624032302 0ustar zuulzuul00000000000000# Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. import six from keystonemiddleware.auth_token import _memcache_crypt as memcache_crypt from keystonemiddleware.tests.unit import utils class MemcacheCryptPositiveTests(utils.BaseTestCase): def _setup_keys(self, strategy): return memcache_crypt.derive_keys('token', 'secret', strategy) def test_derive_keys(self): keys = self._setup_keys(b'strategy') self.assertEqual(len(keys['ENCRYPTION']), len(keys['CACHE_KEY'])) self.assertEqual(len(keys['CACHE_KEY']), len(keys['MAC'])) self.assertNotEqual(keys['ENCRYPTION'], keys['MAC']) self.assertIn('strategy', keys.keys()) def test_key_strategy_diff(self): k1 = self._setup_keys(b'MAC') k2 = self._setup_keys(b'ENCRYPT') self.assertNotEqual(k1, k2) def test_sign_data(self): keys = self._setup_keys(b'MAC') sig = memcache_crypt.sign_data(keys['MAC'], b'data') self.assertEqual(len(sig), memcache_crypt.DIGEST_LENGTH_B64) def test_encryption(self): keys = self._setup_keys(b'ENCRYPT') # what you put in is what you get out for data in [b'data', b'1234567890123456', b'\x00\xFF' * 13 ] + [six.int2byte(x % 256) * x for x in range(768)]: crypt = memcache_crypt.encrypt_data(keys['ENCRYPTION'], data) decrypt = memcache_crypt.decrypt_data(keys['ENCRYPTION'], crypt) self.assertEqual(data, decrypt) self.assertRaises(memcache_crypt.DecryptError, memcache_crypt.decrypt_data, keys['ENCRYPTION'], crypt[:-1]) def test_protect_wrappers(self): data = b'My Pretty Little Data' for strategy in [b'MAC', b'ENCRYPT']: keys = self._setup_keys(strategy) protected = memcache_crypt.protect_data(keys, data) self.assertNotEqual(protected, data) if strategy == b'ENCRYPT': self.assertNotIn(data, protected) unprotected = memcache_crypt.unprotect_data(keys, protected) self.assertEqual(data, unprotected) self.assertRaises(memcache_crypt.InvalidMacError, memcache_crypt.unprotect_data, keys, protected[:-1]) self.assertIsNone(memcache_crypt.unprotect_data(keys, None)) def test_no_cryptography(self): aes = memcache_crypt.ciphers memcache_crypt.ciphers = None self.assertRaises(memcache_crypt.CryptoUnavailableError, memcache_crypt.encrypt_data, 'token', 'secret', 'data') memcache_crypt.ciphers = aes keystonemiddleware-4.21.0/keystonemiddleware/tests/unit/auth_token/test_base_middleware.py0000666000175100017510000001724413225160624032435 0ustar zuulzuul00000000000000# Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. import datetime import uuid from keystoneauth1 import fixture import mock import testtools import webob from keystonemiddleware import auth_token from keystonemiddleware.auth_token import _request class FakeApp(object): @webob.dec.wsgify def __call__(self, req): return webob.Response() class FetchingMiddleware(auth_token.BaseAuthProtocol): kwargs_to_fetch_token = True def __init__(self, app, token_dict={}, **kwargs): super(FetchingMiddleware, self).__init__(app, **kwargs) self.token_dict = token_dict def fetch_token(self, token, **kwargs): try: return self.token_dict[token] except KeyError: raise auth_token.InvalidToken() class BaseAuthProtocolTests(testtools.TestCase): @mock.patch.multiple(auth_token.BaseAuthProtocol, process_request=mock.DEFAULT, process_response=mock.DEFAULT) def test_process_flow(self, process_request, process_response): m = auth_token.BaseAuthProtocol(FakeApp()) process_request.return_value = None process_response.side_effect = lambda x: x req = webob.Request.blank('/', method='GET') resp = req.get_response(m) self.assertEqual(200, resp.status_code) self.assertEqual(1, process_request.call_count) self.assertIsInstance(process_request.call_args[0][0], _request._AuthTokenRequest) self.assertEqual(1, process_response.call_count) self.assertIsInstance(process_response.call_args[0][0], webob.Response) @classmethod def call(cls, middleware, method='GET', path='/', headers=None): req = webob.Request.blank(path) req.method = method for k, v in (headers or {}).items(): req.headers[k] = v resp = req.get_response(middleware) resp.request = req return resp def test_good_v3_user_token(self): t = fixture.V3Token() t.set_project_scope() role = t.add_role() token_id = uuid.uuid4().hex token_dict = {token_id: t} @webob.dec.wsgify def _do_cb(req): self.assertEqual(token_id, req.headers['X-Auth-Token'].strip()) self.assertEqual('Confirmed', req.headers['X-Identity-Status']) self.assertNotIn('X-Service-Token', req.headers) p = req.environ['keystone.token_auth'] self.assertTrue(p.has_user_token) self.assertFalse(p.has_service_token) self.assertEqual(t.project_id, p.user.project_id) self.assertEqual(t.project_domain_id, p.user.project_domain_id) self.assertEqual(t.user_id, p.user.user_id) self.assertEqual(t.user_domain_id, p.user.user_domain_id) self.assertIn(role['name'], p.user.role_names) return webob.Response() m = FetchingMiddleware(_do_cb, token_dict) self.call(m, headers={'X-Auth-Token': token_id}) # also try with whitespace in the token self.call(m, headers={'X-Auth-Token': token_id + ' '}) self.call(m, headers={'X-Auth-Token': token_id + '\r'}) def test_invalid_user_token(self): token_id = uuid.uuid4().hex @webob.dec.wsgify def _do_cb(req): self.assertEqual('Invalid', req.headers['X-Identity-Status']) self.assertEqual(token_id, req.headers['X-Auth-Token']) return webob.Response() m = FetchingMiddleware(_do_cb) self.call(m, headers={'X-Auth-Token': token_id}) def test_expired_user_token(self): t = fixture.V3Token() t.set_project_scope() t.expires = datetime.datetime.utcnow() - datetime.timedelta(minutes=10) token_id = uuid.uuid4().hex token_dict = {token_id: t} @webob.dec.wsgify def _do_cb(req): self.assertEqual('Invalid', req.headers['X-Identity-Status']) self.assertEqual(token_id, req.headers['X-Auth-Token']) return webob.Response() m = FetchingMiddleware(_do_cb, token_dict=token_dict) self.call(m, headers={'X-Auth-Token': token_id}) def test_good_v3_service_token(self): t = fixture.V3Token() t.set_project_scope() role = t.add_role() token_id = uuid.uuid4().hex token_dict = {token_id: t} @webob.dec.wsgify def _do_cb(req): self.assertEqual(token_id, req.headers['X-Service-Token'].strip()) self.assertEqual('Confirmed', req.headers['X-Service-Identity-Status']) self.assertNotIn('X-Auth-Token', req.headers) p = req.environ['keystone.token_auth'] self.assertFalse(p.has_user_token) self.assertTrue(p.has_service_token) self.assertEqual(t.project_id, p.service.project_id) self.assertEqual(t.project_domain_id, p.service.project_domain_id) self.assertEqual(t.user_id, p.service.user_id) self.assertEqual(t.user_domain_id, p.service.user_domain_id) self.assertIn(role['name'], p.service.role_names) return webob.Response() m = FetchingMiddleware(_do_cb, token_dict) self.call(m, headers={'X-Service-Token': token_id}) # also try with whitespace in the token self.call(m, headers={'X-Service-Token': token_id + ' '}) self.call(m, headers={'X-Service-Token': token_id + '\r'}) def test_invalid_service_token(self): token_id = uuid.uuid4().hex @webob.dec.wsgify def _do_cb(req): self.assertEqual('Invalid', req.headers['X-Service-Identity-Status']) self.assertEqual(token_id, req.headers['X-Service-Token']) return webob.Response() m = FetchingMiddleware(_do_cb) self.call(m, headers={'X-Service-Token': token_id}) def test_expired_service_token(self): t = fixture.V3Token() t.set_project_scope() t.expires = datetime.datetime.utcnow() - datetime.timedelta(minutes=10) token_id = uuid.uuid4().hex token_dict = {token_id: t} @webob.dec.wsgify def _do_cb(req): self.assertEqual('Invalid', req.headers['X-Service-Identity-Status']) self.assertEqual(token_id, req.headers['X-Service-Token']) return webob.Response() m = FetchingMiddleware(_do_cb, token_dict=token_dict) self.call(m, headers={'X-Service-Token': token_id}) def test_base_doesnt_block_project_id(self): # X-Project-Id and X-Domain-Id must pass for tokenless/x509 auth project_id = uuid.uuid4().hex domain_id = uuid.uuid4().hex body = uuid.uuid4().hex @webob.dec.wsgify def _do_cb(req): self.assertEqual(project_id, req.headers['X-Project-Id']) self.assertEqual(domain_id, req.headers['X-Domain-Id']) return webob.Response(body, 200) m = FetchingMiddleware(_do_cb) resp = self.call(m, headers={'X-Project-Id': project_id, 'X-Domain-Id': domain_id}) self.assertEqual(body, resp.text) keystonemiddleware-4.21.0/keystonemiddleware/tests/unit/auth_token/test_config.py0000666000175100017510000001110413225160624030560 0ustar zuulzuul00000000000000# Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. import uuid import mock from oslo_config import cfg from oslotest import createfile from keystonemiddleware.auth_token import _auth from keystonemiddleware.auth_token import _opts from keystonemiddleware.tests.unit.auth_token import base def conf_get(app, *args, **kwargs): return app._conf.get(*args, **kwargs) class TestAuthPluginLocalOsloConfig(base.BaseAuthTokenTestCase): def setUp(self): super(TestAuthPluginLocalOsloConfig, self).setUp() self.project = uuid.uuid4().hex # NOTE(cdent): The options below are selected from those # which are statically registered by auth_token middleware # in the 'keystone_authtoken' group. Additional options, from # plugins, are registered dynamically so must not be used here. self.oslo_options = { 'www_authenticate_uri': uuid.uuid4().hex, 'identity_uri': uuid.uuid4().hex, } self.local_oslo_config = cfg.ConfigOpts() self.local_oslo_config.register_group( cfg.OptGroup(name='keystone_authtoken')) self.local_oslo_config.register_opts(_opts._OPTS, group='keystone_authtoken') self.local_oslo_config.register_opts(_auth.OPTS, group='keystone_authtoken') for option, value in self.oslo_options.items(): self.local_oslo_config.set_override(option, value, 'keystone_authtoken') self.local_oslo_config(args=[], project=self.project) self.file_options = { 'auth_type': 'password', 'www_authenticate_uri': uuid.uuid4().hex, 'password': uuid.uuid4().hex, } content = ("[keystone_authtoken]\n" "auth_type=%(auth_type)s\n" "www_authenticate_uri=%(www_authenticate_uri)s\n" "auth_url=%(www_authenticate_uri)s\n" "password=%(password)s\n" % self.file_options) self.conf_file_fixture = self.useFixture( createfile.CreateFileWithContent(self.project, content)) def _create_app(self, conf, project_version=None): if not project_version: project_version = uuid.uuid4().hex fake_pkg_resources = mock.Mock() fake_pkg_resources.get_distribution().version = project_version body = uuid.uuid4().hex with mock.patch('keystonemiddleware._common.config.pkg_resources', new=fake_pkg_resources): # use_global_conf is poorly named. What it means is # don't use the config created in test setUp. return self.create_simple_middleware(body=body, conf=conf, use_global_conf=True) def test_project_in_local_oslo_configuration(self): conf = {'oslo_config_project': self.project, 'oslo_config_file': self.conf_file_fixture.path} app = self._create_app(conf) for option in self.file_options: self.assertEqual(self.file_options[option], conf_get(app, option), option) def test_passed_oslo_configuration(self): conf = {'oslo_config_config': self.local_oslo_config} app = self._create_app(conf) for option in self.oslo_options: self.assertEqual(self.oslo_options[option], conf_get(app, option)) def test_passed_oslo_configuration_wins(self): """oslo_config_config has precedence over oslo_config_project.""" conf = {'oslo_config_project': self.project, 'oslo_config_config': self.local_oslo_config, 'oslo_config_file': self.conf_file_fixture.path} app = self._create_app(conf) for option in self.oslo_options: self.assertEqual(self.oslo_options[option], conf_get(app, option)) self.assertNotEqual(self.file_options['www_authenticate_uri'], conf_get(app, 'www_authenticate_uri')) keystonemiddleware-4.21.0/keystonemiddleware/tests/unit/auth_token/__init__.py0000666000175100017510000000000013225160624030004 0ustar zuulzuul00000000000000keystonemiddleware-4.21.0/keystonemiddleware/tests/unit/auth_token/test_auth.py0000666000175100017510000000757213225160624030272 0ustar zuulzuul00000000000000# Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. import uuid from keystoneauth1 import fixture from keystoneauth1 import plugin as ksa_plugin from keystoneauth1 import session from oslo_log import log as logging from requests_mock.contrib import fixture as rm_fixture import six from keystonemiddleware.auth_token import _auth from keystonemiddleware.tests.unit import utils class DefaultAuthPluginTests(utils.BaseTestCase): def new_plugin(self, auth_host=None, auth_port=None, auth_protocol=None, auth_admin_prefix=None, admin_user=None, admin_password=None, admin_tenant_name=None, admin_token=None, identity_uri=None, log=None): if not log: log = self.logger return _auth.AuthTokenPlugin( auth_host=auth_host, auth_port=auth_port, auth_protocol=auth_protocol, auth_admin_prefix=auth_admin_prefix, admin_user=admin_user, admin_password=admin_password, admin_tenant_name=admin_tenant_name, admin_token=admin_token, identity_uri=identity_uri, log=log) def setUp(self): super(DefaultAuthPluginTests, self).setUp() self.stream = six.StringIO() self.logger = logging.getLogger(__name__) self.session = session.Session() self.requests_mock = self.useFixture(rm_fixture.Fixture()) def test_auth_uri_from_fragments(self): auth_protocol = 'http' auth_host = 'testhost' auth_port = 8888 auth_admin_prefix = 'admin' expected = '%s://%s:%d/admin' % (auth_protocol, auth_host, auth_port) plugin = self.new_plugin(auth_host=auth_host, auth_protocol=auth_protocol, auth_port=auth_port, auth_admin_prefix=auth_admin_prefix) endpoint = plugin.get_endpoint(self.session, interface=ksa_plugin.AUTH_INTERFACE) self.assertEqual(expected, endpoint) def test_identity_uri_overrides_fragments(self): identity_uri = 'http://testhost:8888/admin' plugin = self.new_plugin(identity_uri=identity_uri, auth_host='anotherhost', auth_port=9999, auth_protocol='ftp') endpoint = plugin.get_endpoint(self.session, interface=ksa_plugin.AUTH_INTERFACE) self.assertEqual(identity_uri, endpoint) def test_with_admin_token(self): token = uuid.uuid4().hex plugin = self.new_plugin(identity_uri='http://testhost:8888/admin', admin_token=token) self.assertEqual(token, plugin.get_token(self.session)) def test_with_user_pass(self): base_uri = 'http://testhost:8888/admin' token = fixture.V2Token() admin_tenant_name = uuid.uuid4().hex self.requests_mock.post(base_uri + '/v2.0/tokens', json=token) plugin = self.new_plugin(identity_uri=base_uri, admin_user=uuid.uuid4().hex, admin_password=uuid.uuid4().hex, admin_tenant_name=admin_tenant_name) self.assertEqual(token.token_id, plugin.get_token(self.session)) keystonemiddleware-4.21.0/keystonemiddleware/tests/unit/auth_token/test_cache.py0000666000175100017510000001214113225160624030360 0ustar zuulzuul00000000000000# Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. import uuid import six from keystonemiddleware.auth_token import _exceptions as exc from keystonemiddleware.tests.unit.auth_token import base from keystonemiddleware.tests.unit import utils MEMCACHED_SERVERS = ['localhost:11211'] MEMCACHED_AVAILABLE = None class TestCacheSetup(base.BaseAuthTokenTestCase): def test_assert_valid_memcache_protection_config(self): # test missing memcache_secret_key conf = { 'memcached_servers': ','.join(MEMCACHED_SERVERS), 'memcache_security_strategy': 'Encrypt' } self.assertRaises(exc.ConfigurationError, self.create_simple_middleware, conf=conf) # test invalue memcache_security_strategy conf = { 'memcached_servers': ','.join(MEMCACHED_SERVERS), 'memcache_security_strategy': 'whatever' } self.assertRaises(exc.ConfigurationError, self.create_simple_middleware, conf=conf) # test missing memcache_secret_key conf = { 'memcached_servers': ','.join(MEMCACHED_SERVERS), 'memcache_security_strategy': 'mac' } self.assertRaises(exc.ConfigurationError, self.create_simple_middleware, conf=conf) conf = { 'memcached_servers': ','.join(MEMCACHED_SERVERS), 'memcache_security_strategy': 'Encrypt', 'memcache_secret_key': '' } self.assertRaises(exc.ConfigurationError, self.create_simple_middleware, conf=conf) conf = { 'memcached_servers': ','.join(MEMCACHED_SERVERS), 'memcache_security_strategy': 'mAc', 'memcache_secret_key': '' } self.assertRaises(exc.ConfigurationError, self.create_simple_middleware, conf=conf) class NoMemcacheAuthToken(base.BaseAuthTokenTestCase): """These tests will not have the memcache module available.""" def setUp(self): super(NoMemcacheAuthToken, self).setUp() self.useFixture(utils.DisableModuleFixture('memcache')) def test_nomemcache(self): conf = { 'admin_token': 'admin_token1', 'auth_host': 'keystone.example.com', 'auth_port': '1234', 'memcached_servers': ','.join(MEMCACHED_SERVERS), 'www_authenticate_uri': 'https://keystone.example.com:1234', } self.create_simple_middleware(conf=conf) class TestLiveMemcache(base.BaseAuthTokenTestCase): def setUp(self): super(TestLiveMemcache, self).setUp() global MEMCACHED_AVAILABLE if MEMCACHED_AVAILABLE is None: try: import memcache c = memcache.Client(MEMCACHED_SERVERS) c.set('ping', 'pong', time=1) MEMCACHED_AVAILABLE = c.get('ping') == 'pong' except ImportError: MEMCACHED_AVAILABLE = False if not MEMCACHED_AVAILABLE: self.skipTest('memcached not available') def test_encrypt_cache_data(self): conf = { 'memcached_servers': ','.join(MEMCACHED_SERVERS), 'memcache_security_strategy': 'encrypt', 'memcache_secret_key': 'mysecret' } token = six.b(uuid.uuid4().hex) data = uuid.uuid4().hex token_cache = self.create_simple_middleware(conf=conf)._token_cache token_cache.initialize({}) token_cache.set(token, data) self.assertEqual(token_cache.get(token), data) def test_sign_cache_data(self): conf = { 'memcached_servers': ','.join(MEMCACHED_SERVERS), 'memcache_security_strategy': 'mac', 'memcache_secret_key': 'mysecret' } token = six.b(uuid.uuid4().hex) data = uuid.uuid4().hex token_cache = self.create_simple_middleware(conf=conf)._token_cache token_cache.initialize({}) token_cache.set(token, data) self.assertEqual(token_cache.get(token), data) def test_no_memcache_protection(self): conf = { 'memcached_servers': ','.join(MEMCACHED_SERVERS), 'memcache_secret_key': 'mysecret' } token = six.b(uuid.uuid4().hex) data = uuid.uuid4().hex token_cache = self.create_simple_middleware(conf=conf)._token_cache token_cache.initialize({}) token_cache.set(token, data) self.assertEqual(token_cache.get(token), data) keystonemiddleware-4.21.0/keystonemiddleware/tests/unit/auth_token/base.py0000666000175100017510000000400613225160624027171 0ustar zuulzuul00000000000000# Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. import fixtures from oslo_config import cfg from oslo_config import fixture as cfg_fixture from oslo_log import log as logging from requests_mock.contrib import fixture as rm_fixture from six.moves import http_client import webob.dec from keystonemiddleware import auth_token from keystonemiddleware.tests.unit import utils class BaseAuthTokenTestCase(utils.MiddlewareTestCase): def setUp(self): super(BaseAuthTokenTestCase, self).setUp() self.requests_mock = self.useFixture(rm_fixture.Fixture()) self.logger = fixtures.FakeLogger(level=logging.DEBUG) self.cfg = self.useFixture(cfg_fixture.Config(conf=cfg.ConfigOpts())) self.cfg.conf(args=[]) def create_middleware(self, cb, conf=None, use_global_conf=False): @webob.dec.wsgify def _do_cb(req): return cb(req) if use_global_conf: opts = conf or {} else: opts = { 'oslo_config_config': self.cfg.conf, } opts.update(conf or {}) return auth_token.AuthProtocol(_do_cb, opts) def call(self, middleware, method='GET', path='/', headers=None, expected_status=http_client.OK): req = webob.Request.blank(path) req.method = method for k, v in (headers or {}).items(): req.headers[k] = v resp = req.get_response(middleware) self.assertEqual(expected_status, resp.status_int) resp.request = req return resp keystonemiddleware-4.21.0/keystonemiddleware/tests/unit/auth_token/test_request.py0000666000175100017510000002331013225160624031005 0ustar zuulzuul00000000000000# Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. import itertools import uuid from keystoneauth1 import access from keystoneauth1 import fixture from keystonemiddleware.auth_token import _request from keystonemiddleware.tests.unit import utils class RequestObjectTests(utils.TestCase): def setUp(self): super(RequestObjectTests, self).setUp() self.request = _request._AuthTokenRequest.blank('/') def test_setting_user_token_valid(self): self.assertNotIn('X-Identity-Status', self.request.headers) self.request.user_token_valid = True self.assertEqual('Confirmed', self.request.headers['X-Identity-Status']) self.assertTrue(self.request.user_token_valid) self.request.user_token_valid = False self.assertEqual('Invalid', self.request.headers['X-Identity-Status']) self.assertFalse(self.request.user_token_valid) def test_setting_service_token_valid(self): self.assertNotIn('X-Service-Identity-Status', self.request.headers) self.request.service_token_valid = True self.assertEqual('Confirmed', self.request.headers['X-Service-Identity-Status']) self.assertTrue(self.request.service_token_valid) self.request.service_token_valid = False self.assertEqual('Invalid', self.request.headers['X-Service-Identity-Status']) self.assertFalse(self.request.service_token_valid) def test_removing_headers(self): GOOD = ('X-Auth-Token', 'unknownstring', uuid.uuid4().hex) BAD = ('X-Domain-Id', 'X-Domain-Name', 'X-Project-Id', 'X-Project-Name', 'X-Project-Domain-Id', 'X-Project-Domain-Name', 'X-User-Id', 'X-User-Name', 'X-User-Domain-Id', 'X-User-Domain-Name', 'X-Roles', 'X-Identity-Status', 'X-Service-Domain-Id', 'X-Service-Domain-Name', 'X-Service-Project-Id', 'X-Service-Project-Name', 'X-Service-Project-Domain-Id', 'X-Service-Project-Domain-Name', 'X-Service-User-Id', 'X-Service-User-Name', 'X-Service-User-Domain-Id', 'X-Service-User-Domain-Name', 'X-Service-Roles', 'X-Service-Identity-Status', 'X-Service-Catalog', 'X-Role', 'X-User', 'X-Tenant-Id', 'X-Tenant-Name', 'X-Tenant', ) header_vals = {} for header in itertools.chain(GOOD, BAD): v = uuid.uuid4().hex header_vals[header] = v self.request.headers[header] = v self.request.remove_auth_headers() for header in BAD: self.assertNotIn(header, self.request.headers) for header in GOOD: self.assertEqual(header_vals[header], self.request.headers[header]) def _test_v3_headers(self, token, prefix): self.assertEqual(token.domain_id, self.request.headers['X%s-Domain-Id' % prefix]) self.assertEqual(token.domain_name, self.request.headers['X%s-Domain-Name' % prefix]) self.assertEqual(token.project_id, self.request.headers['X%s-Project-Id' % prefix]) self.assertEqual(token.project_name, self.request.headers['X%s-Project-Name' % prefix]) self.assertEqual( token.project_domain_id, self.request.headers['X%s-Project-Domain-Id' % prefix]) self.assertEqual( token.project_domain_name, self.request.headers['X%s-Project-Domain-Name' % prefix]) self.assertEqual(token.user_id, self.request.headers['X%s-User-Id' % prefix]) self.assertEqual(token.user_name, self.request.headers['X%s-User-Name' % prefix]) self.assertEqual( token.user_domain_id, self.request.headers['X%s-User-Domain-Id' % prefix]) self.assertEqual( token.user_domain_name, self.request.headers['X%s-User-Domain-Name' % prefix]) def test_project_scoped_user_headers(self): token = fixture.V3Token() token.set_project_scope() token_id = uuid.uuid4().hex auth_ref = access.create(auth_token=token_id, body=token) self.request.set_user_headers(auth_ref) self._test_v3_headers(token, '') def test_project_scoped_service_headers(self): token = fixture.V3Token() token.set_project_scope() token_id = uuid.uuid4().hex auth_ref = access.create(auth_token=token_id, body=token) self.request.set_service_headers(auth_ref) self._test_v3_headers(token, '-Service') def test_auth_type(self): self.assertIsNone(self.request.auth_type) self.request.environ['AUTH_TYPE'] = 'NeGoTiatE' self.assertEqual('negotiate', self.request.auth_type) def test_user_token(self): token = uuid.uuid4().hex self.assertIsNone(self.request.user_token) self.request.headers['X-Auth-Token'] = token self.assertEqual(token, self.request.user_token) def test_storage_token(self): storage_token = uuid.uuid4().hex user_token = uuid.uuid4().hex self.assertIsNone(self.request.user_token) self.request.headers['X-Storage-Token'] = storage_token self.assertEqual(storage_token, self.request.user_token) self.request.headers['X-Auth-Token'] = user_token self.assertEqual(user_token, self.request.user_token) def test_service_token(self): token = uuid.uuid4().hex self.assertIsNone(self.request.service_token) self.request.headers['X-Service-Token'] = token self.assertEqual(token, self.request.service_token) def test_token_auth(self): plugin = object() self.assertNotIn('keystone.token_auth', self.request.environ) self.request.token_auth = plugin self.assertIs(plugin, self.request.environ['keystone.token_auth']) self.assertIs(plugin, self.request.token_auth) def test_token_info(self): info = fixture.V3Token() self.assertNotIn('keystone.token_info', self.request.environ) self.request.token_info = info self.assertIs(info, self.request.environ['keystone.token_info']) self.assertIs(info, self.request.token_info) def test_token_without_catalog(self): token = fixture.V3Token() auth_ref = access.create(body=token) self.request.set_service_catalog_headers(auth_ref) self.assertNotIn('X-Service-Catalog', self.request.headers) class CatalogConversionTests(utils.TestCase): PUBLIC_URL = 'http://server:5000/v2.0' ADMIN_URL = 'http://admin:35357/v2.0' INTERNAL_URL = 'http://internal:5000/v2.0' REGION_ONE = 'RegionOne' REGION_TWO = 'RegionTwo' REGION_THREE = 'RegionThree' def test_basic_convert(self): token = fixture.V3Token() s = token.add_service(type='identity') s.add_standard_endpoints(public=self.PUBLIC_URL, admin=self.ADMIN_URL, internal=self.INTERNAL_URL, region=self.REGION_ONE) auth_ref = access.create(body=token) catalog_data = auth_ref.service_catalog.catalog catalog = _request._v3_to_v2_catalog(catalog_data) self.assertEqual(1, len(catalog)) service = catalog[0] self.assertEqual(1, len(service['endpoints'])) endpoints = service['endpoints'][0] self.assertEqual('identity', service['type']) self.assertEqual(4, len(endpoints)) self.assertEqual(self.PUBLIC_URL, endpoints['publicURL']) self.assertEqual(self.ADMIN_URL, endpoints['adminURL']) self.assertEqual(self.INTERNAL_URL, endpoints['internalURL']) self.assertEqual(self.REGION_ONE, endpoints['region']) def test_multi_region(self): token = fixture.V3Token() s = token.add_service(type='identity') s.add_endpoint('internal', self.INTERNAL_URL, region=self.REGION_ONE) s.add_endpoint('public', self.PUBLIC_URL, region=self.REGION_TWO) s.add_endpoint('admin', self.ADMIN_URL, region=self.REGION_THREE) auth_ref = access.create(body=token) catalog_data = auth_ref.service_catalog.catalog catalog = _request._v3_to_v2_catalog(catalog_data) self.assertEqual(1, len(catalog)) service = catalog[0] # the 3 regions will come through as 3 separate endpoints expected = [{'internalURL': self.INTERNAL_URL, 'region': self.REGION_ONE}, {'publicURL': self.PUBLIC_URL, 'region': self.REGION_TWO}, {'adminURL': self.ADMIN_URL, 'region': self.REGION_THREE}] self.assertEqual('identity', service['type']) self.assertEqual(3, len(service['endpoints'])) for e in expected: self.assertIn(e, expected) keystonemiddleware-4.21.0/keystonemiddleware/tests/unit/auth_token/test_signing_dir.py0000666000175100017510000001241013225160624031610 0ustar zuulzuul00000000000000# Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. import os import shutil import stat import uuid from keystonemiddleware.auth_token import _signing_dir from keystonemiddleware.tests.unit import utils class SigningDirectoryTests(utils.BaseTestCase): def test_directory_created_when_doesnt_exist(self): # When _SigningDirectory is created, if the directory doesn't exist # it's created with the expected permissions. tmp_name = uuid.uuid4().hex parent_directory = '/tmp/%s' % tmp_name directory_name = '/tmp/%s/%s' % ((tmp_name,) * 2) # Directories are created by __init__. _signing_dir.SigningDirectory(directory_name) self.addCleanup(shutil.rmtree, parent_directory) self.assertTrue(os.path.isdir(directory_name)) self.assertTrue(os.access(directory_name, os.W_OK)) self.assertEqual(os.stat(directory_name).st_uid, os.getuid()) self.assertEqual(stat.S_IMODE(os.stat(directory_name).st_mode), stat.S_IRWXU) def test_use_directory_already_exists(self): # The directory can already exist. tmp_name = uuid.uuid4().hex parent_directory = '/tmp/%s' % tmp_name directory_name = '/tmp/%s/%s' % ((tmp_name,) * 2) os.makedirs(directory_name, stat.S_IRWXU) self.addCleanup(shutil.rmtree, parent_directory) _signing_dir.SigningDirectory(directory_name) def test_write_file(self): # write_file when the file doesn't exist creates the file. signing_directory = _signing_dir.SigningDirectory() file_name = self.getUniqueString() contents = self.getUniqueString() signing_directory.write_file(file_name, contents) self.addCleanup(shutil.rmtree, signing_directory._directory_name) file_path = signing_directory.calc_path(file_name) with open(file_path) as f: actual_contents = f.read() self.assertEqual(contents, actual_contents) def test_replace_file(self): # write_file when the file already exists overwrites it. signing_directory = _signing_dir.SigningDirectory() file_name = self.getUniqueString() orig_contents = self.getUniqueString() signing_directory.write_file(file_name, orig_contents) self.addCleanup(shutil.rmtree, signing_directory._directory_name) new_contents = self.getUniqueString() signing_directory.write_file(file_name, new_contents) file_path = signing_directory.calc_path(file_name) with open(file_path) as f: actual_contents = f.read() self.assertEqual(new_contents, actual_contents) def test_recreate_directory(self): # If the original directory is lost, it gets recreated when a file # is written. signing_directory = _signing_dir.SigningDirectory() original_file_name = self.getUniqueString() original_contents = self.getUniqueString() signing_directory.write_file(original_file_name, original_contents) self.addCleanup(shutil.rmtree, signing_directory._directory_name) # Delete the directory. shutil.rmtree(signing_directory._directory_name) new_file_name = self.getUniqueString() new_contents = self.getUniqueString() signing_directory.write_file(new_file_name, new_contents) actual_contents = signing_directory.read_file(new_file_name) self.assertEqual(new_contents, actual_contents) def test_read_file(self): # Can read a file that was written. signing_directory = _signing_dir.SigningDirectory() file_name = self.getUniqueString() contents = self.getUniqueString() signing_directory.write_file(file_name, contents) self.addCleanup(shutil.rmtree, signing_directory._directory_name) actual_contents = signing_directory.read_file(file_name) self.assertEqual(contents, actual_contents) def test_read_file_doesnt_exist(self): # Show what happens when try to read a file that wasn't written. signing_directory = _signing_dir.SigningDirectory() file_name = self.getUniqueString() self.assertRaises(IOError, signing_directory.read_file, file_name) self.addCleanup(shutil.rmtree, signing_directory._directory_name) def test_calc_path(self): # calc_path returns the actual filename built from the directory name. signing_directory = _signing_dir.SigningDirectory() file_name = self.getUniqueString() actual_path = signing_directory.calc_path(file_name) self.addCleanup(shutil.rmtree, signing_directory._directory_name) expected_path = os.path.join(signing_directory._directory_name, file_name) self.assertEqual(expected_path, actual_path) keystonemiddleware-4.21.0/keystonemiddleware/tests/unit/auth_token/test_user_auth_plugin.py0000666000175100017510000002104113225160624032671 0ustar zuulzuul00000000000000# Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. import uuid from keystoneauth1 import fixture from keystoneauth1 import loading from keystonemiddleware.auth_token import _base from keystonemiddleware.tests.unit.auth_token import base # NOTE(jamielennox): just some sample values that we can use for testing BASE_URI = 'https://keystone.example.com:1234' AUTH_URL = 'https://keystone.auth.com:1234' class BaseUserPluginTests(object): def configure_middleware(self, auth_type, **kwargs): opts = loading.get_auth_plugin_conf_options(auth_type) self.cfg.register_opts(opts, group=_base.AUTHTOKEN_GROUP) # Since these tests cfg.config() themselves rather than waiting for # auth_token to do it on __init__ we need to register the base auth # options (e.g., auth_plugin) loading.register_auth_conf_options(self.cfg.conf, group=_base.AUTHTOKEN_GROUP) self.cfg.config(group=_base.AUTHTOKEN_GROUP, auth_type=auth_type, **kwargs) def assertTokenDataEqual(self, token_id, token, token_data): self.assertEqual(token_id, token_data.auth_token) self.assertEqual(token.user_id, token_data.user_id) try: trust_id = token.trust_id except KeyError: trust_id = None self.assertEqual(trust_id, token_data.trust_id) self.assertEqual(self.get_role_names(token), token_data.role_names) def get_plugin(self, token_id, service_token_id=None): headers = {'X-Auth-Token': token_id} if service_token_id: headers['X-Service-Token'] = service_token_id m = self.create_simple_middleware() resp = self.call(m, headers=headers) return resp.request.environ['keystone.token_auth'] def test_user_information(self): token_id, token = self.get_token() plugin = self.get_plugin(token_id) self.assertTokenDataEqual(token_id, token, plugin.user) self.assertFalse(plugin.has_service_token) self.assertIsNone(plugin.service) def test_with_service_information(self): token_id, token = self.get_token() service_id, service = self.get_token(service=True) plugin = self.get_plugin(token_id, service_id) self.assertTokenDataEqual(token_id, token, plugin.user) self.assertTokenDataEqual(service_id, service, plugin.service) class V2UserPluginTests(BaseUserPluginTests, base.BaseAuthTokenTestCase): def setUp(self): super(V2UserPluginTests, self).setUp() self.service_token = fixture.V2Token() self.service_token.set_scope() s = self.service_token.add_service('identity', name='keystone') s.add_endpoint(public=BASE_URI, admin=BASE_URI, internal=BASE_URI) self.configure_middleware(auth_type='v2password', auth_url='%s/v2.0/' % AUTH_URL, user_id=self.service_token.user_id, password=uuid.uuid4().hex, tenant_id=self.service_token.tenant_id) auth_discovery = fixture.DiscoveryList(href=AUTH_URL, v3=False) self.requests_mock.get(AUTH_URL, json=auth_discovery) base_discovery = fixture.DiscoveryList(href=BASE_URI, v3=False) self.requests_mock.get(BASE_URI, json=base_discovery) url = '%s/v2.0/tokens' % AUTH_URL self.requests_mock.post(url, json=self.service_token) def get_role_names(self, token): return [x['name'] for x in token['access']['user'].get('roles', [])] def get_token(self, service=False): token = fixture.V2Token() token.set_scope() token.add_role() if service: token.add_role('service') request_headers = {'X-Auth-Token': self.service_token.token_id} url = '%s/v2.0/tokens/%s' % (BASE_URI, token.token_id) self.requests_mock.get(url, request_headers=request_headers, json=token) return token.token_id, token def assertTokenDataEqual(self, token_id, token, token_data): super(V2UserPluginTests, self).assertTokenDataEqual(token_id, token, token_data) self.assertEqual(token.tenant_id, token_data.project_id) self.assertIsNone(token_data.user_domain_id) self.assertIsNone(token_data.project_domain_id) def test_trust_scope(self): token_id, token = self.get_token() token.set_trust() plugin = self.get_plugin(token_id) self.assertEqual(token.trust_id, plugin.user.trust_id) self.assertEqual(token.trustee_user_id, plugin.user.trustee_user_id) self.assertIsNone(plugin.user.trustor_user_id) class V3UserPluginTests(BaseUserPluginTests, base.BaseAuthTokenTestCase): def setUp(self): super(V3UserPluginTests, self).setUp() self.service_token_id = uuid.uuid4().hex self.service_token = fixture.V3Token() s = self.service_token.add_service('identity', name='keystone') s.add_standard_endpoints(public=BASE_URI, admin=BASE_URI, internal=BASE_URI) self.configure_middleware(auth_type='v3password', auth_url='%s/v3/' % AUTH_URL, user_id=self.service_token.user_id, password=uuid.uuid4().hex, project_id=self.service_token.project_id) auth_discovery = fixture.DiscoveryList(href=AUTH_URL) self.requests_mock.get(AUTH_URL, json=auth_discovery) base_discovery = fixture.DiscoveryList(href=BASE_URI) self.requests_mock.get(BASE_URI, json=base_discovery) self.requests_mock.post( '%s/v3/auth/tokens' % AUTH_URL, headers={'X-Subject-Token': self.service_token_id}, json=self.service_token) def get_role_names(self, token): return [x['name'] for x in token['token'].get('roles', [])] def get_token(self, project=True, service=False): token_id = uuid.uuid4().hex token = fixture.V3Token() if project: token.set_project_scope() token.add_role() if service: token.add_role('service') request_headers = {'X-Auth-Token': self.service_token_id, 'X-Subject-Token': token_id} headers = {'X-Subject-Token': token_id} self.requests_mock.get('%s/v3/auth/tokens' % BASE_URI, request_headers=request_headers, headers=headers, json=token) return token_id, token def assertTokenDataEqual(self, token_id, token, token_data): super(V3UserPluginTests, self).assertTokenDataEqual(token_id, token, token_data) self.assertEqual(token.user_domain_id, token_data.user_domain_id) self.assertEqual(token.project_id, token_data.project_id) self.assertEqual(token.project_domain_id, token_data.project_domain_id) def test_domain_scope(self): token_id, token = self.get_token(project=False) token.set_domain_scope() plugin = self.get_plugin(token_id) self.assertEqual(token.domain_id, plugin.user.domain_id) self.assertIsNone(plugin.user.project_id) def test_trust_scope(self): token_id, token = self.get_token(project=False) token.set_trust_scope() plugin = self.get_plugin(token_id) self.assertEqual(token.trust_id, plugin.user.trust_id) self.assertEqual(token.trustor_user_id, plugin.user.trustor_user_id) self.assertEqual(token.trustee_user_id, plugin.user.trustee_user_id) keystonemiddleware-4.21.0/keystonemiddleware/tests/unit/auth_token/test_auth_token_middleware.py0000666000175100017510000033164213225160624033665 0ustar zuulzuul00000000000000# Copyright 2012 OpenStack Foundation # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. import datetime import os import shutil import stat import tempfile import time import uuid import fixtures from keystoneauth1 import exceptions as ksa_exceptions from keystoneauth1 import fixture from keystoneauth1 import loading from keystoneauth1 import session from keystoneclient.common import cms from keystoneclient import exceptions as ksc_exceptions import mock import oslo_cache from oslo_log import log as logging from oslo_serialization import jsonutils from oslo_utils import timeutils import pbr.version import six import testresources import testtools from testtools import matchers import webob import webob.dec from keystonemiddleware import auth_token from keystonemiddleware.auth_token import _base from keystonemiddleware.auth_token import _cache from keystonemiddleware.auth_token import _exceptions as ksm_exceptions from keystonemiddleware.auth_token import _revocations from keystonemiddleware.tests.unit.auth_token import base from keystonemiddleware.tests.unit import client_fixtures EXPECTED_V2_DEFAULT_ENV_RESPONSE = { 'HTTP_X_IDENTITY_STATUS': 'Confirmed', 'HTTP_X_TENANT_ID': 'tenant_id1', 'HTTP_X_TENANT_NAME': 'tenant_name1', 'HTTP_X_USER_ID': 'user_id1', 'HTTP_X_USER_NAME': 'user_name1', 'HTTP_X_ROLES': 'role1,role2', 'HTTP_X_IS_ADMIN_PROJECT': 'True', 'HTTP_X_USER': 'user_name1', # deprecated (diablo-compat) 'HTTP_X_TENANT': 'tenant_name1', # deprecated (diablo-compat) 'HTTP_X_ROLE': 'role1,role2', # deprecated (diablo-compat) } EXPECTED_V2_DEFAULT_SERVICE_ENV_RESPONSE = { 'HTTP_X_SERVICE_IDENTITY_STATUS': 'Confirmed', 'HTTP_X_SERVICE_PROJECT_ID': 'service_project_id1', 'HTTP_X_SERVICE_PROJECT_NAME': 'service_project_name1', 'HTTP_X_SERVICE_USER_ID': 'service_user_id1', 'HTTP_X_SERVICE_USER_NAME': 'service_user_name1', 'HTTP_X_SERVICE_ROLES': 'service,service_role2', } EXPECTED_V3_DEFAULT_ENV_ADDITIONS = { 'HTTP_X_PROJECT_DOMAIN_ID': 'domain_id1', 'HTTP_X_PROJECT_DOMAIN_NAME': 'domain_name1', 'HTTP_X_USER_DOMAIN_ID': 'domain_id1', 'HTTP_X_USER_DOMAIN_NAME': 'domain_name1', 'HTTP_X_IS_ADMIN_PROJECT': 'True' } EXPECTED_V3_DEFAULT_SERVICE_ENV_ADDITIONS = { 'HTTP_X_SERVICE_PROJECT_DOMAIN_ID': 'service_domain_id1', 'HTTP_X_SERVICE_PROJECT_DOMAIN_NAME': 'service_domain_name1', 'HTTP_X_SERVICE_USER_DOMAIN_ID': 'service_domain_id1', 'HTTP_X_SERVICE_USER_DOMAIN_NAME': 'service_domain_name1' } BASE_HOST = 'https://keystone.example.com:1234' BASE_URI = '%s/testadmin' % BASE_HOST FAKE_ADMIN_TOKEN_ID = 'admin_token2' FAKE_ADMIN_TOKEN = jsonutils.dumps( {'access': {'token': {'id': FAKE_ADMIN_TOKEN_ID, 'expires': '2022-10-03T16:58:01Z'}}}) VERSION_LIST_v3 = fixture.DiscoveryList(href=BASE_URI) VERSION_LIST_v2 = fixture.DiscoveryList(v3=False, href=BASE_URI) ERROR_TOKEN = '7ae290c2a06244c4b41692eb4e9225f2' def cleanup_revoked_file(filename): try: os.remove(filename) except OSError: pass def strtime(at=None): at = at or timeutils.utcnow() return at.strftime(timeutils.PERFECT_TIME_FORMAT) class TimezoneFixture(fixtures.Fixture): @staticmethod def supported(): # tzset is only supported on Unix. return hasattr(time, 'tzset') def __init__(self, new_tz): super(TimezoneFixture, self).__init__() self.tz = new_tz self.old_tz = os.environ.get('TZ') def setUp(self): super(TimezoneFixture, self).setUp() if not self.supported(): raise NotImplementedError('timezone override is not supported.') os.environ['TZ'] = self.tz time.tzset() self.addCleanup(self.cleanup) def cleanup(self): if self.old_tz is not None: os.environ['TZ'] = self.old_tz elif 'TZ' in os.environ: del os.environ['TZ'] time.tzset() class TimeFixture(fixtures.Fixture): def __init__(self, new_time, normalize=True): super(TimeFixture, self).__init__() if isinstance(new_time, six.string_types): new_time = timeutils.parse_isotime(new_time) if normalize: new_time = timeutils.normalize_time(new_time) self.new_time = new_time def setUp(self): super(TimeFixture, self).setUp() timeutils.set_time_override(self.new_time) self.addCleanup(timeutils.clear_time_override) class FakeApp(object): """This represents a WSGI app protected by the auth_token middleware.""" SUCCESS = b'SUCCESS' FORBIDDEN = b'FORBIDDEN' expected_env = {} def __init__(self, expected_env=None, need_service_token=False): self.expected_env = dict(EXPECTED_V2_DEFAULT_ENV_RESPONSE) if expected_env: self.expected_env.update(expected_env) self.need_service_token = need_service_token @webob.dec.wsgify def __call__(self, req): for k, v in self.expected_env.items(): assert req.environ[k] == v, '%s != %s' % (req.environ[k], v) resp = webob.Response() if (req.environ.get('HTTP_X_IDENTITY_STATUS') == 'Invalid' and req.environ['HTTP_X_SERVICE_IDENTITY_STATUS'] == 'Invalid'): # Simulate delayed auth forbidding access with arbitrary status # code to differentiate checking this code path resp.status = 419 resp.body = FakeApp.FORBIDDEN elif req.environ.get('HTTP_X_SERVICE_IDENTITY_STATUS') == 'Invalid': # Simulate delayed auth forbidding access with arbitrary status # code to differentiate checking this code path resp.status = 420 resp.body = FakeApp.FORBIDDEN elif req.environ['HTTP_X_IDENTITY_STATUS'] == 'Invalid': # Simulate delayed auth forbidding access resp.status = 403 resp.body = FakeApp.FORBIDDEN elif (self.need_service_token is True and req.environ.get('HTTP_X_SERVICE_TOKEN') is None): # Simulate requiring composite auth # Arbitrary value to allow checking this code path resp.status = 418 resp.body = FakeApp.FORBIDDEN else: resp.body = FakeApp.SUCCESS return resp class v3FakeApp(FakeApp): """This represents a v3 WSGI app protected by the auth_token middleware.""" def __init__(self, expected_env=None, need_service_token=False): # with v3 additions, these are for the DEFAULT TOKEN v3_default_env_additions = dict(EXPECTED_V3_DEFAULT_ENV_ADDITIONS) if expected_env: v3_default_env_additions.update(expected_env) super(v3FakeApp, self).__init__(expected_env=v3_default_env_additions, need_service_token=need_service_token) class CompositeBase(object): """Base composite auth object with common service token environment.""" def __init__(self, expected_env=None): comp_expected_env = dict(EXPECTED_V2_DEFAULT_SERVICE_ENV_RESPONSE) if expected_env: comp_expected_env.update(expected_env) super(CompositeBase, self).__init__( expected_env=comp_expected_env, need_service_token=True) class CompositeFakeApp(CompositeBase, FakeApp): """A fake v2 WSGI app protected by composite auth_token middleware.""" def __init__(self, expected_env): super(CompositeFakeApp, self).__init__(expected_env=expected_env) class v3CompositeFakeApp(CompositeBase, v3FakeApp): """A fake v3 WSGI app protected by composite auth_token middleware.""" def __init__(self, expected_env=None): # with v3 additions, these are for the DEFAULT SERVICE TOKEN v3_default_service_env_additions = dict( EXPECTED_V3_DEFAULT_SERVICE_ENV_ADDITIONS) if expected_env: v3_default_service_env_additions.update(expected_env) super(v3CompositeFakeApp, self).__init__( v3_default_service_env_additions) class FakeOsloCache(_cache._FakeClient): """A fake oslo_cache object. The memcache and oslo_cache interfaces are almost the same except we need to return NO_VALUE when not found. """ def get(self, key): return super(FakeOsloCache, self).get(key) or oslo_cache.NO_VALUE class BaseAuthTokenMiddlewareTest(base.BaseAuthTokenTestCase): """Base test class for auth_token middleware. All the tests allow for running with auth_token configured for receiving v2 or v3 tokens, with the choice being made by passing configuration data into setUp(). The base class will, by default, run all the tests expecting v2 token formats. Child classes can override this to specify, for instance, v3 format. """ def setUp(self, expected_env=None, auth_version=None, fake_app=None): super(BaseAuthTokenMiddlewareTest, self).setUp() self.logger = self.useFixture(fixtures.FakeLogger(level=logging.DEBUG)) # the default oslo_cache is null cache, always use an in-mem cache self.useFixture(fixtures.MockPatchObject(auth_token.AuthProtocol, '_create_oslo_cache', return_value=FakeOsloCache())) self.expected_env = expected_env or dict() self.fake_app = fake_app or FakeApp self.middleware = None signing_dir = self._setup_signing_directory() self.conf = { 'identity_uri': 'https://keystone.example.com:1234/testadmin/', 'signing_dir': signing_dir, 'auth_version': auth_version, 'www_authenticate_uri': 'https://keystone.example.com:1234', 'admin_user': uuid.uuid4().hex, } self.auth_version = auth_version self.response_status = None self.response_headers = None def call_middleware(self, **kwargs): return self.call(self.middleware, **kwargs) def _setup_signing_directory(self): directory_name = self.useFixture(fixtures.TempDir()).path # Copy the sample certificate files into the temporary directory. for filename in ['cacert.pem', 'signing_cert.pem', ]: shutil.copy2(os.path.join(client_fixtures.CERTDIR, filename), os.path.join(directory_name, filename)) return directory_name def set_middleware(self, expected_env=None, conf=None): """Configure the class ready to call the auth_token middleware. Set up the various fake items needed to run the middleware. Individual tests that need to further refine these can call this function to override the class defaults. """ if conf: self.conf.update(conf) if expected_env: self.expected_env.update(expected_env) self.middleware = auth_token.AuthProtocol( self.fake_app(self.expected_env), self.conf) self.middleware._revocations._list = jsonutils.dumps( {"revoked": [], "extra": "success"}) def update_expected_env(self, expected_env={}): self.middleware._app.expected_env.update(expected_env) def purge_token_expected_env(self): for key in six.iterkeys(self.token_expected_env): del self.middleware._app.expected_env[key] def purge_service_token_expected_env(self): for key in six.iterkeys(self.service_token_expected_env): del self.middleware._app.expected_env[key] def assertLastPath(self, path): if path: self.assertEqual(BASE_URI + path, self.requests_mock.last_request.url) else: self.assertIsNone(self.requests_mock.last_request) class DiabloAuthTokenMiddlewareTest(BaseAuthTokenMiddlewareTest, testresources.ResourcedTestCase): resources = [('examples', client_fixtures.EXAMPLES_RESOURCE)] """Auth Token middleware should understand Diablo keystone responses.""" def setUp(self): # pre-diablo only had Tenant ID, which was also the Name expected_env = { 'HTTP_X_TENANT_ID': 'tenant_id1', 'HTTP_X_TENANT_NAME': 'tenant_id1', # now deprecated (diablo-compat) 'HTTP_X_TENANT': 'tenant_id1', } super(DiabloAuthTokenMiddlewareTest, self).setUp( expected_env=expected_env) self.requests_mock.get(BASE_URI, json=VERSION_LIST_v2, status_code=300) self.requests_mock.post("%s/v2.0/tokens" % BASE_URI, text=FAKE_ADMIN_TOKEN) self.token_id = self.examples.VALID_DIABLO_TOKEN token_response = self.examples.JSON_TOKEN_RESPONSES[self.token_id] url = "%s/v2.0/tokens/%s" % (BASE_URI, self.token_id) self.requests_mock.get(url, text=token_response) self.set_middleware() def test_valid_diablo_response(self): resp = self.call_middleware(headers={'X-Auth-Token': self.token_id}) self.assertIn('keystone.token_info', resp.request.environ) class CachePoolTest(BaseAuthTokenMiddlewareTest): def test_use_cache_from_env(self): # If `swift.cache` is set in the environment and `cache` is set in the # config then the env cache is used. env = {'swift.cache': 'CACHE_TEST'} conf = { 'cache': 'swift.cache' } self.set_middleware(conf=conf) self.middleware._token_cache.initialize(env) with self.middleware._token_cache._cache_pool.reserve() as cache: self.assertEqual(cache, 'CACHE_TEST') def test_not_use_cache_from_env(self): # If `swift.cache` is set in the environment but `cache` isn't set # initialize the config then the env cache isn't used. self.set_middleware() env = {'swift.cache': 'CACHE_TEST'} self.middleware._token_cache.initialize(env) with self.middleware._token_cache._cache_pool.reserve() as cache: self.assertNotEqual(cache, 'CACHE_TEST') def test_multiple_context_managers_share_single_client(self): self.set_middleware() token_cache = self.middleware._token_cache env = {} token_cache.initialize(env) caches = [] with token_cache._cache_pool.reserve() as cache: caches.append(cache) with token_cache._cache_pool.reserve() as cache: caches.append(cache) self.assertIs(caches[0], caches[1]) self.assertEqual(set(caches), set(token_cache._cache_pool)) def test_nested_context_managers_create_multiple_clients(self): self.set_middleware() env = {} self.middleware._token_cache.initialize(env) token_cache = self.middleware._token_cache with token_cache._cache_pool.reserve() as outer_cache: with token_cache._cache_pool.reserve() as inner_cache: self.assertNotEqual(outer_cache, inner_cache) self.assertEqual( set([inner_cache, outer_cache]), set(token_cache._cache_pool)) class GeneralAuthTokenMiddlewareTest(BaseAuthTokenMiddlewareTest, testresources.ResourcedTestCase): """General Token Behavior tests. These tests are not affected by the token format (see CommonAuthTokenMiddlewareTest). """ resources = [('examples', client_fixtures.EXAMPLES_RESOURCE)] def test_fixed_cache_key_length(self): self.set_middleware() short_string = uuid.uuid4().hex long_string = 8 * uuid.uuid4().hex token_cache = self.middleware._token_cache hashed_short_string_key, context_ = token_cache._get_cache_key( short_string) hashed_long_string_key, context_ = token_cache._get_cache_key( long_string) # The hash keys should always match in length self.assertThat(hashed_short_string_key, matchers.HasLength(len(hashed_long_string_key))) def test_config_revocation_cache_timeout(self): conf = { 'revocation_cache_time': '24', 'www_authenticate_uri': 'https://keystone.example.com:1234', 'admin_user': uuid.uuid4().hex } middleware = auth_token.AuthProtocol(self.fake_app, conf) self.assertEqual(middleware._revocations._cache_timeout, datetime.timedelta(seconds=24)) def test_conf_values_type_convert(self): conf = { 'revocation_cache_time': '24', 'identity_uri': 'https://keystone.example.com:1234', 'include_service_catalog': '0', 'nonexsit_option': '0', } middleware = auth_token.AuthProtocol(self.fake_app, conf) self.assertEqual(datetime.timedelta(seconds=24), middleware._revocations._cache_timeout) self.assertFalse(middleware._include_service_catalog) self.assertEqual('0', middleware._conf.get('nonexsit_option')) def test_deprecated_conf_values(self): servers = 'localhost:11211' conf = { 'memcache_servers': servers } middleware = auth_token.AuthProtocol(self.fake_app, conf) self.assertEqual([servers], middleware._conf.get('memcached_servers')) def test_conf_values_type_convert_with_wrong_key(self): conf = { 'wrong_key': '123' } log = 'The option "wrong_key" in conf is not known to auth_token' auth_token.AuthProtocol(self.fake_app, conf) self.assertThat(self.logger.output, matchers.Contains(log)) def test_conf_values_type_convert_with_wrong_value(self): conf = { 'include_service_catalog': '123', } self.assertRaises(ksm_exceptions.ConfigurationError, auth_token.AuthProtocol, self.fake_app, conf) def test_auth_region_name(self): token = fixture.V3Token() auth_url = 'http://keystone-auth.example.com:5000' east_url = 'http://keystone-east.example.com:5000' west_url = 'http://keystone-west.example.com:5000' auth_versions = fixture.DiscoveryList(href=auth_url) east_versions = fixture.DiscoveryList(href=east_url) west_versions = fixture.DiscoveryList(href=west_url) s = token.add_service('identity') s.add_endpoint(interface='admin', url=east_url, region='east') s.add_endpoint(interface='admin', url=west_url, region='west') self.requests_mock.get(auth_url, json=auth_versions) self.requests_mock.get(east_url, json=east_versions) self.requests_mock.get(west_url, json=west_versions) self.requests_mock.post( '%s/v3/auth/tokens' % auth_url, headers={'X-Subject-Token': uuid.uuid4().hex}, json=token) east_mock = self.requests_mock.get( '%s/v3/auth/tokens' % east_url, headers={'X-Subject-Token': uuid.uuid4().hex}, json=fixture.V3Token()) west_mock = self.requests_mock.get( '%s/v3/auth/tokens' % west_url, headers={'X-Subject-Token': uuid.uuid4().hex}, json=fixture.V3Token()) loading.register_auth_conf_options(self.cfg.conf, group=_base.AUTHTOKEN_GROUP) opts = loading.get_auth_plugin_conf_options('v3password') self.cfg.register_opts(opts, group=_base.AUTHTOKEN_GROUP) self.cfg.config(auth_url=auth_url + '/v3', auth_type='v3password', username='user', password='pass', user_domain_id=uuid.uuid4().hex, group=_base.AUTHTOKEN_GROUP) self.assertEqual(0, east_mock.call_count) self.assertEqual(0, west_mock.call_count) east_app = self.create_simple_middleware(conf=dict(region_name='east')) self.call(east_app, headers={'X-Auth-Token': uuid.uuid4().hex}) self.assertEqual(1, east_mock.call_count) self.assertEqual(0, west_mock.call_count) west_app = self.create_simple_middleware(conf=dict(region_name='west')) self.call(west_app, headers={'X-Auth-Token': uuid.uuid4().hex}) self.assertEqual(1, east_mock.call_count) self.assertEqual(1, west_mock.call_count) class CommonAuthTokenMiddlewareTest(object): """These tests are run once using v2 tokens and again using v3 tokens.""" def test_init_does_not_call_http(self): conf = { 'revocation_cache_time': '1' } self.create_simple_middleware(conf=conf) self.assertLastPath(None) def test_auth_with_no_token_does_not_call_http(self): middleware = self.create_simple_middleware() self.call(middleware, expected_status=401) self.assertLastPath(None) def test_init_by_ipv6Addr_auth_host(self): del self.conf['identity_uri'] conf = { 'auth_host': '2001:2013:1:f101::1', 'auth_port': '1234', 'auth_protocol': 'http', 'www_authenticate_uri': None, 'auth_version': 'v3.0', } middleware = self.create_simple_middleware(conf=conf) self.assertEqual('http://[2001:2013:1:f101::1]:1234', middleware._www_authenticate_uri) def assert_valid_request_200(self, token, with_catalog=True): resp = self.call_middleware(headers={'X-Auth-Token': token}) if with_catalog: self.assertTrue(resp.request.headers.get('X-Service-Catalog')) else: self.assertNotIn('X-Service-Catalog', resp.request.headers) self.assertEqual(FakeApp.SUCCESS, resp.body) self.assertIn('keystone.token_info', resp.request.environ) return resp.request def test_valid_uuid_request(self): for _ in range(2): # Do it twice because first result was cached. token = self.token_dict['uuid_token_default'] self.assert_valid_request_200(token) self.assert_valid_last_url(token) def test_valid_uuid_request_with_auth_fragments(self): del self.conf['identity_uri'] self.conf['auth_protocol'] = 'https' self.conf['auth_host'] = 'keystone.example.com' self.conf['auth_port'] = '1234' self.conf['auth_admin_prefix'] = '/testadmin' self.set_middleware() self.assert_valid_request_200(self.token_dict['uuid_token_default']) self.assert_valid_last_url(self.token_dict['uuid_token_default']) def _test_cache_revoked(self, token, revoked_form=None): # When the token is cached and revoked, 401 is returned. self.middleware._check_revocations_for_cached = True # Token should be cached as ok after this. self.call_middleware(headers={'X-Auth-Token': token}) # Put it in revocation list. self.middleware._revocations._list = self.get_revocation_list_json( token_ids=[revoked_form or token]) self.call_middleware(headers={'X-Auth-Token': token}, expected_status=401) def test_cached_revoked_error(self): # When the token is cached and revocation list retrieval fails, # 503 is returned token = self.token_dict['uuid_token_default'] self.middleware._check_revocations_for_cached = True # Token should be cached as ok after this. resp = self.call_middleware(headers={'X-Auth-Token': token}) self.assertEqual(200, resp.status_int) # Cause the revocation list to be fetched again next time so we can # test the case where that retrieval fails self.middleware._revocations._fetched_time = datetime.datetime.min with mock.patch.object(self.middleware._revocations, '_fetch', side_effect=ksm_exceptions.RevocationListError): self.call_middleware(headers={'X-Auth-Token': token}, expected_status=503) def test_cached_revoked_uuid(self): # When the UUID token is cached and revoked, 401 is returned. self._test_cache_revoked(self.token_dict['uuid_token_default']) def test_valid_signed_request(self): for _ in range(2): # Do it twice because first result was cached. self.assert_valid_request_200( self.token_dict['signed_token_scoped']) # ensure that signed requests do not generate HTTP traffic self.assertLastPath(None) def test_valid_signed_compressed_request(self): self.assert_valid_request_200( self.token_dict['signed_token_scoped_pkiz']) # ensure that signed requests do not generate HTTP traffic self.assertLastPath(None) def test_revoked_token_receives_401(self): self.middleware._revocations._list = ( self.get_revocation_list_json()) token = self.token_dict['revoked_token'] self.call_middleware(headers={'X-Auth-Token': token}, expected_status=401) def test_revoked_token_receives_401_sha256(self): self.conf['hash_algorithms'] = ','.join(['sha256', 'md5']) self.set_middleware() self.middleware._revocations._list = ( self.get_revocation_list_json(mode='sha256')) token = self.token_dict['revoked_token'] self.call_middleware(headers={'X-Auth-Token': token}, expected_status=401) def test_cached_revoked_pki(self): # When the PKI token is cached and revoked, 401 is returned. token = self.token_dict['signed_token_scoped'] revoked_form = cms.cms_hash_token(token) self._test_cache_revoked(token, revoked_form) def test_cached_revoked_pkiz(self): # When the PKIZ token is cached and revoked, 401 is returned. token = self.token_dict['signed_token_scoped_pkiz'] revoked_form = cms.cms_hash_token(token) self._test_cache_revoked(token, revoked_form) def test_revoked_token_receives_401_md5_secondary(self): # When hash_algorithms has 'md5' as the secondary hash and the # revocation list contains the md5 hash for a token, that token is # considered revoked so returns 401. self.conf['hash_algorithms'] = ','.join(['sha256', 'md5']) self.set_middleware() self.middleware._revocations._list = ( self.get_revocation_list_json()) token = self.token_dict['revoked_token'] self.call_middleware(headers={'X-Auth-Token': token}, expected_status=401) def _test_revoked_hashed_token(self, token_name): # If hash_algorithms is set as ['sha256', 'md5'], # and check_revocations_for_cached is True, # and a token is in the cache because it was successfully validated # using the md5 hash, then # if the token is in the revocation list by md5 hash, it'll be # rejected and auth_token returns 401. self.conf['hash_algorithms'] = ','.join(['sha256', 'md5']) self.conf['check_revocations_for_cached'] = 'true' self.set_middleware() token = self.token_dict[token_name] # Put the token in the revocation list. token_hashed = cms.cms_hash_token(token) self.middleware._revocations._list = self.get_revocation_list_json( token_ids=[token_hashed]) # First, request is using the hashed token, is valid so goes in # cache using the given hash. self.call_middleware(headers={'X-Auth-Token': token_hashed}) # This time use the PKI(Z) token # Should find the token in the cache and revocation list. self.call_middleware(headers={'X-Auth-Token': token}, expected_status=401) def test_revoked_hashed_pki_token(self): self._test_revoked_hashed_token('signed_token_scoped') def test_revoked_hashed_pkiz_token(self): self._test_revoked_hashed_token('signed_token_scoped_pkiz') def test_revoked_pki_token_by_audit_id(self): # When the audit ID is in the revocation list, the token is invalid. self.set_middleware() token = self.token_dict['signed_token_scoped'] # Put the token audit ID in the revocation list, # the entry will have a false token ID so the token ID doesn't match. fake_token_id = uuid.uuid4().hex # The audit_id value is in examples/pki/cms/auth_*_token_scoped.json. audit_id = 'SLIXlXQUQZWUi9VJrqdXqA' revocation_list_data = { 'revoked': [ { 'id': fake_token_id, 'audit_id': audit_id }, ] } self.middleware._revocations._list = jsonutils.dumps( revocation_list_data) self.call_middleware(headers={'X-Auth-Token': token}, expected_status=401) def get_revocation_list_json(self, token_ids=None, mode=None): if token_ids is None: key = 'revoked_token_hash' + (('_' + mode) if mode else '') token_ids = [self.token_dict[key]] revocation_list = {'revoked': [{'id': x, 'expires': timeutils.utcnow()} for x in token_ids]} return jsonutils.dumps(revocation_list) def test_is_signed_token_revoked_returns_false(self): # explicitly setting an empty revocation list here to document intent self.middleware._revocations._list = jsonutils.dumps( {"revoked": [], "extra": "success"}) result = self.middleware._revocations._any_revoked( [self.token_dict['revoked_token_hash']]) self.assertFalse(result) def test_is_signed_token_revoked_returns_true(self): self.middleware._revocations._list = ( self.get_revocation_list_json()) result = self.middleware._revocations._any_revoked( [self.token_dict['revoked_token_hash']]) self.assertTrue(result) def test_is_signed_token_revoked_returns_true_sha256(self): self.conf['hash_algorithms'] = ','.join(['sha256', 'md5']) self.set_middleware() self.middleware._revocations._list = ( self.get_revocation_list_json(mode='sha256')) result = self.middleware._revocations._any_revoked( [self.token_dict['revoked_token_hash_sha256']]) self.assertTrue(result) def test_validate_offline_raises_exception_for_revoked_token(self): self.middleware._revocations._list = ( self.get_revocation_list_json()) self.assertRaises(ksm_exceptions.InvalidToken, self.middleware._validate_offline, self.token_dict['revoked_token'], [self.token_dict['revoked_token_hash']]) def test_validate_offline_raises_exception_for_revoked_token_s256(self): self.conf['hash_algorithms'] = ','.join(['sha256', 'md5']) self.set_middleware() self.middleware._revocations._list = ( self.get_revocation_list_json(mode='sha256')) self.assertRaises(ksm_exceptions.InvalidToken, self.middleware._validate_offline, self.token_dict['revoked_token'], [self.token_dict['revoked_token_hash_sha256'], self.token_dict['revoked_token_hash']]) def test_validate_offline_raises_exception_for_revoked_pkiz_token(self): self.middleware._revocations._list = ( self.examples.REVOKED_TOKEN_PKIZ_LIST_JSON) self.assertRaises(ksm_exceptions.InvalidToken, self.middleware._validate_offline, self.token_dict['revoked_token_pkiz'], [self.token_dict['revoked_token_pkiz_hash']]) def test_validate_offline_succeeds_for_unrevoked_token(self): self.middleware._revocations._list = ( self.get_revocation_list_json()) token = self.middleware._validate_offline( self.token_dict['signed_token_scoped'], [self.token_dict['signed_token_scoped_hash']]) self.assertIsInstance(token, dict) def test_verify_signed_compressed_token_succeeds_for_unrevoked_token(self): self.middleware._revocations._list = ( self.get_revocation_list_json()) token = self.middleware._validate_offline( self.token_dict['signed_token_scoped_pkiz'], [self.token_dict['signed_token_scoped_hash']]) self.assertIsInstance(token, dict) def test_validate_offline_token_succeeds_for_unrevoked_token_sha256(self): self.conf['hash_algorithms'] = ','.join(['sha256', 'md5']) self.set_middleware() self.middleware._revocations._list = ( self.get_revocation_list_json(mode='sha256')) token = self.middleware._validate_offline( self.token_dict['signed_token_scoped'], [self.token_dict['signed_token_scoped_hash_sha256'], self.token_dict['signed_token_scoped_hash']]) self.assertIsInstance(token, dict) def test_get_token_revocation_list_fetched_time_returns_min(self): self.middleware._revocations._fetched_time = None # Get rid of the revoked file revoked_path = self.middleware._signing_directory.calc_path( _revocations.Revocations._FILE_NAME) os.remove(revoked_path) self.assertEqual(self.middleware._revocations._fetched_time, datetime.datetime.min) # FIXME(blk-u): move the unit tests into unit/test_auth_token.py def test_get_token_revocation_list_fetched_time_returns_mtime(self): self.middleware._revocations._fetched_time = None revoked_path = self.middleware._signing_directory.calc_path( _revocations.Revocations._FILE_NAME) mtime = os.path.getmtime(revoked_path) fetched_time = datetime.datetime.utcfromtimestamp(mtime) self.assertEqual(fetched_time, self.middleware._revocations._fetched_time) @testtools.skipUnless(TimezoneFixture.supported(), 'TimezoneFixture not supported') def test_get_token_revocation_list_fetched_time_returns_utc(self): with TimezoneFixture('UTC-1'): self.middleware._revocations._list = jsonutils.dumps( self.examples.REVOCATION_LIST) self.middleware._revocations._fetched_time = None fetched_time = self.middleware._revocations._fetched_time self.assertTrue(timeutils.is_soon(fetched_time, 1)) def test_get_token_revocation_list_fetched_time_returns_value(self): expected = self.middleware._revocations._fetched_time self.assertEqual(self.middleware._revocations._fetched_time, expected) def test_get_revocation_list_returns_fetched_list(self): # auth_token uses v2 to fetch this, so don't allow the v3 # tests to override the fake http connection self.middleware._revocations._fetched_time = None # Get rid of the revoked file revoked_path = self.middleware._signing_directory.calc_path( _revocations.Revocations._FILE_NAME) os.remove(revoked_path) self.assertEqual(self.middleware._revocations._list, self.examples.REVOCATION_LIST) def test_get_revocation_list_returns_current_list_from_memory(self): self.assertEqual(self.middleware._revocations._list, self.middleware._revocations._list_prop) def test_get_revocation_list_returns_current_list_from_disk(self): in_memory_list = self.middleware._revocations._list self.middleware._revocations._list_prop = None self.assertEqual(self.middleware._revocations._list, in_memory_list) def test_invalid_revocation_list_raises_error(self): self.requests_mock.get(self.revocation_url, json={}) self.assertRaises(ksm_exceptions.RevocationListError, self.middleware._revocations._fetch) def test_fetch_revocation_list(self): # auth_token uses v2 to fetch this, so don't allow the v3 # tests to override the fake http connection fetched = jsonutils.loads(self.middleware._revocations._fetch()) self.assertEqual(fetched, self.examples.REVOCATION_LIST) def test_request_invalid_uuid_token(self): # remember because we are testing the middleware we stub the connection # to the keystone server, but this is not what gets returned invalid_uri = "%s/v2.0/tokens/invalid-token" % BASE_URI self.requests_mock.get(invalid_uri, status_code=404) resp = self.call_middleware(headers={'X-Auth-Token': 'invalid-token'}, expected_status=401) self.assertEqual("Keystone uri='https://keystone.example.com:1234'", resp.headers['WWW-Authenticate']) def test_request_invalid_signed_token(self): token = self.examples.INVALID_SIGNED_TOKEN resp = self.call_middleware(headers={'X-Auth-Token': token}, expected_status=401) self.assertEqual("Keystone uri='https://keystone.example.com:1234'", resp.headers['WWW-Authenticate']) def test_request_invalid_signed_pkiz_token(self): token = self.examples.INVALID_SIGNED_PKIZ_TOKEN resp = self.call_middleware(headers={'X-Auth-Token': token}, expected_status=401) self.assertEqual("Keystone uri='https://keystone.example.com:1234'", resp.headers['WWW-Authenticate']) def test_request_no_token(self): resp = self.call_middleware(expected_status=401) self.assertEqual("Keystone uri='https://keystone.example.com:1234'", resp.headers['WWW-Authenticate']) def test_request_no_token_http(self): resp = self.call_middleware(method='HEAD', expected_status=401) self.assertEqual("Keystone uri='https://keystone.example.com:1234'", resp.headers['WWW-Authenticate']) def test_request_blank_token(self): resp = self.call_middleware(headers={'X-Auth-Token': ''}, expected_status=401) self.assertEqual("Keystone uri='https://keystone.example.com:1234'", resp.headers['WWW-Authenticate']) def _get_cached_token(self, token, mode='md5'): token_id = cms.cms_hash_token(token, mode=mode) return self.middleware._token_cache.get(token_id) def test_memcache(self): token = self.token_dict['signed_token_scoped'] self.call_middleware(headers={'X-Auth-Token': token}) self.assertIsNotNone(self._get_cached_token(token)) def test_expired(self): token = self.token_dict['signed_token_scoped_expired'] self.call_middleware(headers={'X-Auth-Token': token}, expected_status=401) def test_memcache_set_invalid_uuid(self): invalid_uri = "%s/v2.0/tokens/invalid-token" % BASE_URI self.requests_mock.get(invalid_uri, status_code=404) token = 'invalid-token' self.call_middleware(headers={'X-Auth-Token': token}, expected_status=401) self.assertEqual(auth_token._CACHE_INVALID_INDICATOR, self._get_cached_token(token)) def test_memcache_hit_invalid_token(self): token = 'invalid-token' invalid_uri = '%s/v2.0/tokens/invalid-token' % BASE_URI self.requests_mock.get(invalid_uri, status_code=404) # Call once to cache token's invalid state; verify it cached as such self.call_middleware(headers={'X-Auth-Token': token}, expected_status=401) self.assertEqual(auth_token._CACHE_INVALID_INDICATOR, self._get_cached_token(token)) # Call again for a cache hit; verify it detected as cached and invalid self.call_middleware(headers={'X-Auth-Token': token}, expected_status=401) self.assertIn('Cached token is marked unauthorized', self.logger.output) def test_memcache_set_expired(self, extra_conf={}, extra_environ={}): token_cache_time = 10 conf = { 'token_cache_time': '%s' % token_cache_time, } conf.update(extra_conf) self.set_middleware(conf=conf) token = self.token_dict['signed_token_scoped'] self.call_middleware(headers={'X-Auth-Token': token}) req = webob.Request.blank('/') req.headers['X-Auth-Token'] = token req.environ.update(extra_environ) now = datetime.datetime.utcnow() self.useFixture(TimeFixture(now)) req.get_response(self.middleware) self.assertIsNotNone(self._get_cached_token(token)) timeutils.advance_time_seconds(token_cache_time) self.assertIsNone(self._get_cached_token(token)) def test_swift_memcache_set_expired(self): extra_conf = {'cache': 'swift.cache'} extra_environ = {'swift.cache': _cache._FakeClient()} self.test_memcache_set_expired(extra_conf, extra_environ) def test_http_error_not_cached_token(self): """Test to don't cache token as invalid on network errors. We use UUID tokens since they are the easiest one to reach get_http_connection. """ self.middleware._http_request_max_retries = 0 self.call_middleware(headers={'X-Auth-Token': ERROR_TOKEN}, expected_status=503) self.assertIsNone(self._get_cached_token(ERROR_TOKEN)) self.assert_valid_last_url(ERROR_TOKEN) def test_http_request_max_retries(self): times_retry = 10 conf = {'http_request_max_retries': '%s' % times_retry} self.set_middleware(conf=conf) with mock.patch('time.sleep') as mock_obj: self.call_middleware(headers={'X-Auth-Token': ERROR_TOKEN}, expected_status=503) self.assertEqual(mock_obj.call_count, times_retry) def test_nocatalog(self): conf = { 'include_service_catalog': 'False' } self.set_middleware(conf=conf) self.assert_valid_request_200(self.token_dict['uuid_token_default'], with_catalog=False) def assert_kerberos_bind(self, token, bind_level, use_kerberos=True, success=True): conf = { 'enforce_token_bind': bind_level, 'auth_version': self.auth_version, } self.set_middleware(conf=conf) req = webob.Request.blank('/') req.headers['X-Auth-Token'] = token if use_kerberos: if use_kerberos is True: req.environ['REMOTE_USER'] = self.examples.KERBEROS_BIND else: req.environ['REMOTE_USER'] = use_kerberos req.environ['AUTH_TYPE'] = 'Negotiate' resp = req.get_response(self.middleware) if success: self.assertEqual(200, resp.status_int) self.assertEqual(FakeApp.SUCCESS, resp.body) self.assertIn('keystone.token_info', req.environ) self.assert_valid_last_url(token) else: self.assertEqual(401, resp.status_int) msg = "Keystone uri='https://keystone.example.com:1234'" self.assertEqual(msg, resp.headers['WWW-Authenticate']) def test_uuid_bind_token_disabled_with_kerb_user(self): for use_kerberos in [True, False]: self.assert_kerberos_bind(self.token_dict['uuid_token_bind'], bind_level='disabled', use_kerberos=use_kerberos, success=True) def test_uuid_bind_token_disabled_with_incorrect_ticket(self): self.assert_kerberos_bind(self.token_dict['uuid_token_bind'], bind_level='kerberos', use_kerberos='ronald@MCDONALDS.COM', success=False) def test_uuid_bind_token_permissive_with_kerb_user(self): self.assert_kerberos_bind(self.token_dict['uuid_token_bind'], bind_level='permissive', use_kerberos=True, success=True) def test_uuid_bind_token_permissive_without_kerb_user(self): self.assert_kerberos_bind(self.token_dict['uuid_token_bind'], bind_level='permissive', use_kerberos=False, success=False) def test_uuid_bind_token_permissive_with_unknown_bind(self): token = self.token_dict['uuid_token_unknown_bind'] for use_kerberos in [True, False]: self.assert_kerberos_bind(token, bind_level='permissive', use_kerberos=use_kerberos, success=True) def test_uuid_bind_token_permissive_with_incorrect_ticket(self): self.assert_kerberos_bind(self.token_dict['uuid_token_bind'], bind_level='kerberos', use_kerberos='ronald@MCDONALDS.COM', success=False) def test_uuid_bind_token_strict_with_kerb_user(self): self.assert_kerberos_bind(self.token_dict['uuid_token_bind'], bind_level='strict', use_kerberos=True, success=True) def test_uuid_bind_token_strict_with_kerbout_user(self): self.assert_kerberos_bind(self.token_dict['uuid_token_bind'], bind_level='strict', use_kerberos=False, success=False) def test_uuid_bind_token_strict_with_unknown_bind(self): token = self.token_dict['uuid_token_unknown_bind'] for use_kerberos in [True, False]: self.assert_kerberos_bind(token, bind_level='strict', use_kerberos=use_kerberos, success=False) def test_uuid_bind_token_required_with_kerb_user(self): self.assert_kerberos_bind(self.token_dict['uuid_token_bind'], bind_level='required', use_kerberos=True, success=True) def test_uuid_bind_token_required_without_kerb_user(self): self.assert_kerberos_bind(self.token_dict['uuid_token_bind'], bind_level='required', use_kerberos=False, success=False) def test_uuid_bind_token_required_with_unknown_bind(self): token = self.token_dict['uuid_token_unknown_bind'] for use_kerberos in [True, False]: self.assert_kerberos_bind(token, bind_level='required', use_kerberos=use_kerberos, success=False) def test_uuid_bind_token_required_without_bind(self): for use_kerberos in [True, False]: self.assert_kerberos_bind(self.token_dict['uuid_token_default'], bind_level='required', use_kerberos=use_kerberos, success=False) def test_uuid_bind_token_named_kerberos_with_kerb_user(self): self.assert_kerberos_bind(self.token_dict['uuid_token_bind'], bind_level='kerberos', use_kerberos=True, success=True) def test_uuid_bind_token_named_kerberos_without_kerb_user(self): self.assert_kerberos_bind(self.token_dict['uuid_token_bind'], bind_level='kerberos', use_kerberos=False, success=False) def test_uuid_bind_token_named_kerberos_with_unknown_bind(self): token = self.token_dict['uuid_token_unknown_bind'] for use_kerberos in [True, False]: self.assert_kerberos_bind(token, bind_level='kerberos', use_kerberos=use_kerberos, success=False) def test_uuid_bind_token_named_kerberos_without_bind(self): for use_kerberos in [True, False]: self.assert_kerberos_bind(self.token_dict['uuid_token_default'], bind_level='kerberos', use_kerberos=use_kerberos, success=False) def test_uuid_bind_token_named_kerberos_with_incorrect_ticket(self): self.assert_kerberos_bind(self.token_dict['uuid_token_bind'], bind_level='kerberos', use_kerberos='ronald@MCDONALDS.COM', success=False) def test_uuid_bind_token_with_unknown_named_FOO(self): token = self.token_dict['uuid_token_bind'] for use_kerberos in [True, False]: self.assert_kerberos_bind(token, bind_level='FOO', use_kerberos=use_kerberos, success=False) def test_caching_token_on_verify(self): # When the token is cached it isn't cached again when it's verified. # The token cache has to be initialized with our cache instance. self.middleware._token_cache._env_cache_name = 'cache' cache = _cache._FakeClient() self.middleware._token_cache.initialize(env={'cache': cache}) # Mock cache.set since then the test can verify call_count. orig_cache_set = cache.set cache.set = mock.Mock(side_effect=orig_cache_set) token = self.token_dict['signed_token_scoped'] self.call_middleware(headers={'X-Auth-Token': token}) self.assertThat(1, matchers.Equals(cache.set.call_count)) self.call_middleware(headers={'X-Auth-Token': token}) # Assert that the token wasn't cached again. self.assertThat(1, matchers.Equals(cache.set.call_count)) def test_auth_plugin(self): for service_url in (self.examples.UNVERSIONED_SERVICE_URL, self.examples.SERVICE_URL): self.requests_mock.get(service_url, json=VERSION_LIST_v3, status_code=300) token = self.token_dict['uuid_token_default'] resp = self.call_middleware(headers={'X-Auth-Token': token}) self.assertEqual(FakeApp.SUCCESS, resp.body) token_auth = resp.request.environ['keystone.token_auth'] endpoint_filter = {'service_type': self.examples.SERVICE_TYPE, 'version': 3} url = token_auth.get_endpoint(session.Session(), **endpoint_filter) self.assertEqual('%s/v3' % BASE_URI, url) self.assertTrue(token_auth.has_user_token) self.assertFalse(token_auth.has_service_token) self.assertIsNone(token_auth.service) def test_doesnt_auto_set_content_type(self): # webob will set content_type = 'text/html' by default if nothing is # provided. We don't want our middleware messing with the content type # of the underlying applications. text = uuid.uuid4().hex def _middleware(environ, start_response): start_response(200, []) return text def _start_response(status_code, headerlist, exc_info=None): self.assertIn('200', status_code) # will be '200 OK' self.assertEqual([], headerlist) m = auth_token.AuthProtocol(_middleware, self.conf) env = {'REQUEST_METHOD': 'GET', 'HTTP_X_AUTH_TOKEN': self.token_dict['uuid_token_default']} r = m(env, _start_response) self.assertEqual(text, r) def test_auth_plugin_service_token(self): url = 'http://test.url' text = uuid.uuid4().hex self.requests_mock.get(url, text=text) token = self.token_dict['uuid_token_default'] resp = self.call_middleware(headers={'X-Auth-Token': token}) self.assertEqual(200, resp.status_int) self.assertEqual(FakeApp.SUCCESS, resp.body) s = session.Session(auth=resp.request.environ['keystone.token_auth']) resp = s.get(url) self.assertEqual(text, resp.text) self.assertEqual(200, resp.status_code) headers = self.requests_mock.last_request.headers self.assertEqual(FAKE_ADMIN_TOKEN_ID, headers['X-Service-Token']) def test_service_token_with_valid_service_role_not_required(self): self.conf['service_token_roles'] = ['service'] self.conf['service_token_roles_required'] = False self.set_middleware(conf=self.conf) user_token = self.token_dict['uuid_token_default'] service_token = self.token_dict['uuid_service_token_default'] resp = self.call_middleware(headers={'X-Auth-Token': user_token, 'X-Service-Token': service_token}) self.assertEqual('Confirmed', resp.request.headers['X-Service-Identity-Status']) def test_service_token_with_invalid_service_role_not_required(self): self.conf['service_token_roles'] = [uuid.uuid4().hex] self.conf['service_token_roles_required'] = False self.set_middleware(conf=self.conf) user_token = self.token_dict['uuid_token_default'] service_token = self.token_dict['uuid_service_token_default'] resp = self.call_middleware(headers={'X-Auth-Token': user_token, 'X-Service-Token': service_token}) self.assertEqual('Confirmed', resp.request.headers['X-Service-Identity-Status']) def test_service_token_with_valid_service_role_required(self): self.conf['service_token_roles'] = ['service'] self.conf['service_token_roles_required'] = True self.set_middleware(conf=self.conf) user_token = self.token_dict['uuid_token_default'] service_token = self.token_dict['uuid_service_token_default'] resp = self.call_middleware(headers={'X-Auth-Token': user_token, 'X-Service-Token': service_token}) self.assertEqual('Confirmed', resp.request.headers['X-Service-Identity-Status']) def test_service_token_with_invalid_service_role_required(self): self.conf['service_token_roles'] = [uuid.uuid4().hex] self.conf['service_token_roles_required'] = True self.set_middleware(conf=self.conf) user_token = self.token_dict['uuid_token_default'] service_token = self.token_dict['uuid_service_token_default'] resp = self.call_middleware(headers={'X-Auth-Token': user_token, 'X-Service-Token': service_token}, expected_status=401) self.assertEqual('Invalid', resp.request.headers['X-Service-Identity-Status']) class V2CertDownloadMiddlewareTest(BaseAuthTokenMiddlewareTest, testresources.ResourcedTestCase): resources = [('examples', client_fixtures.EXAMPLES_RESOURCE)] def __init__(self, *args, **kwargs): super(V2CertDownloadMiddlewareTest, self).__init__(*args, **kwargs) self.auth_version = 'v2.0' self.fake_app = None self.ca_path = '/v2.0/certificates/ca' self.signing_path = '/v2.0/certificates/signing' def setUp(self): super(V2CertDownloadMiddlewareTest, self).setUp( auth_version=self.auth_version, fake_app=self.fake_app) self.logger = self.useFixture(fixtures.FakeLogger()) self.base_dir = tempfile.mkdtemp() self.addCleanup(shutil.rmtree, self.base_dir) self.cert_dir = os.path.join(self.base_dir, 'certs') os.makedirs(self.cert_dir, stat.S_IRWXU) conf = { 'signing_dir': self.cert_dir, 'auth_version': self.auth_version, } self.requests_mock.get(BASE_URI, json=VERSION_LIST_v3, status_code=300) self.set_middleware(conf=conf) # Usually we supply a signed_dir with pre-installed certificates, # so invocation of /usr/bin/openssl succeeds. This time we give it # an empty directory, so it fails. def test_request_no_token_dummy(self): cms._ensure_subprocess() self.requests_mock.get('%s%s' % (BASE_URI, self.ca_path), status_code=404) self.requests_mock.get('%s%s' % (BASE_URI, self.signing_path), status_code=404) token = self.middleware._validate_offline( self.examples.SIGNED_TOKEN_SCOPED, [self.examples.SIGNED_TOKEN_SCOPED_HASH]) self.assertIsNone(token) self.assertIn('Fetch certificate config failed', self.logger.output) self.assertIn('fallback to online validation', self.logger.output) def test_fetch_signing_cert(self): data = 'FAKE CERT' url = "%s%s" % (BASE_URI, self.signing_path) self.requests_mock.get(url, text=data) self.middleware._fetch_signing_cert() signing_cert_path = self.middleware._signing_directory.calc_path( self.middleware._SIGNING_CERT_FILE_NAME) with open(signing_cert_path, 'r') as f: self.assertEqual(f.read(), data) self.assertEqual(url, self.requests_mock.last_request.url) def test_fetch_signing_ca(self): data = 'FAKE CA' url = "%s%s" % (BASE_URI, self.ca_path) self.requests_mock.get(url, text=data) self.middleware._fetch_ca_cert() ca_file_path = self.middleware._signing_directory.calc_path( self.middleware._SIGNING_CA_FILE_NAME) with open(ca_file_path, 'r') as f: self.assertEqual(f.read(), data) self.assertEqual(url, self.requests_mock.last_request.url) def test_prefix_trailing_slash(self): del self.conf['identity_uri'] self.conf['auth_protocol'] = 'https' self.conf['auth_host'] = 'keystone.example.com' self.conf['auth_port'] = '1234' self.conf['auth_admin_prefix'] = '/newadmin/' base_url = '%s/newadmin' % BASE_HOST ca_url = "%s%s" % (base_url, self.ca_path) signing_url = "%s%s" % (base_url, self.signing_path) self.requests_mock.get(base_url, json=VERSION_LIST_v3, status_code=300) self.requests_mock.get(ca_url, text='FAKECA') self.requests_mock.get(signing_url, text='FAKECERT') self.set_middleware(conf=self.conf) self.middleware._fetch_ca_cert() self.assertEqual(ca_url, self.requests_mock.last_request.url) self.middleware._fetch_signing_cert() self.assertEqual(signing_url, self.requests_mock.last_request.url) def test_without_prefix(self): del self.conf['identity_uri'] self.conf['auth_protocol'] = 'https' self.conf['auth_host'] = 'keystone.example.com' self.conf['auth_port'] = '1234' self.conf['auth_admin_prefix'] = '' ca_url = "%s%s" % (BASE_HOST, self.ca_path) signing_url = "%s%s" % (BASE_HOST, self.signing_path) self.requests_mock.get(BASE_HOST, json=VERSION_LIST_v3, status_code=300) self.requests_mock.get(ca_url, text='FAKECA') self.requests_mock.get(signing_url, text='FAKECERT') self.set_middleware(conf=self.conf) self.middleware._fetch_ca_cert() self.assertEqual(ca_url, self.requests_mock.last_request.url) self.middleware._fetch_signing_cert() self.assertEqual(signing_url, self.requests_mock.last_request.url) class V3CertDownloadMiddlewareTest(V2CertDownloadMiddlewareTest): def __init__(self, *args, **kwargs): super(V3CertDownloadMiddlewareTest, self).__init__(*args, **kwargs) self.auth_version = 'v3.0' self.fake_app = v3FakeApp self.ca_path = '/v3/OS-SIMPLE-CERT/ca' self.signing_path = '/v3/OS-SIMPLE-CERT/certificates' def network_error_response(request, context): raise ksa_exceptions.ConnectFailure("Network connection refused.") class v2AuthTokenMiddlewareTest(BaseAuthTokenMiddlewareTest, CommonAuthTokenMiddlewareTest, testresources.ResourcedTestCase): """v2 token specific tests. There are some differences between how the auth-token middleware handles v2 and v3 tokens over and above the token formats, namely: - A v3 keystone server will auto scope a token to a user's default project if no scope is specified. A v2 server assumes that the auth-token middleware will do that. - A v2 keystone server may issue a token without a catalog, even with a tenant The tests below were originally part of the generic AuthTokenMiddlewareTest class, but now, since they really are v2 specific, they are included here. """ resources = [('examples', client_fixtures.EXAMPLES_RESOURCE)] def setUp(self): super(v2AuthTokenMiddlewareTest, self).setUp() self.token_dict = { 'uuid_token_default': self.examples.UUID_TOKEN_DEFAULT, 'uuid_token_unscoped': self.examples.UUID_TOKEN_UNSCOPED, 'uuid_token_bind': self.examples.UUID_TOKEN_BIND, 'uuid_token_unknown_bind': self.examples.UUID_TOKEN_UNKNOWN_BIND, 'signed_token_scoped': self.examples.SIGNED_TOKEN_SCOPED, 'signed_token_scoped_pkiz': self.examples.SIGNED_TOKEN_SCOPED_PKIZ, 'signed_token_scoped_hash': self.examples.SIGNED_TOKEN_SCOPED_HASH, 'signed_token_scoped_hash_sha256': self.examples.SIGNED_TOKEN_SCOPED_HASH_SHA256, 'signed_token_scoped_expired': self.examples.SIGNED_TOKEN_SCOPED_EXPIRED, 'revoked_token': self.examples.REVOKED_TOKEN, 'revoked_token_pkiz': self.examples.REVOKED_TOKEN_PKIZ, 'revoked_token_pkiz_hash': self.examples.REVOKED_TOKEN_PKIZ_HASH, 'revoked_token_hash': self.examples.REVOKED_TOKEN_HASH, 'revoked_token_hash_sha256': self.examples.REVOKED_TOKEN_HASH_SHA256, 'uuid_service_token_default': self.examples.UUID_SERVICE_TOKEN_DEFAULT, } self.requests_mock.get(BASE_URI, json=VERSION_LIST_v2, status_code=300) self.requests_mock.post('%s/v2.0/tokens' % BASE_URI, text=FAKE_ADMIN_TOKEN) self.revocation_url = '%s/v2.0/tokens/revoked' % BASE_URI self.requests_mock.get(self.revocation_url, text=self.examples.SIGNED_REVOCATION_LIST) for token in (self.examples.UUID_TOKEN_DEFAULT, self.examples.UUID_TOKEN_UNSCOPED, self.examples.UUID_TOKEN_BIND, self.examples.UUID_TOKEN_UNKNOWN_BIND, self.examples.UUID_TOKEN_NO_SERVICE_CATALOG, self.examples.UUID_SERVICE_TOKEN_DEFAULT, self.examples.SIGNED_TOKEN_SCOPED_KEY, self.examples.SIGNED_TOKEN_SCOPED_PKIZ_KEY,): url = "%s/v2.0/tokens/%s" % (BASE_URI, token) text = self.examples.JSON_TOKEN_RESPONSES[token] self.requests_mock.get(url, text=text) url = '%s/v2.0/tokens/%s' % (BASE_URI, ERROR_TOKEN) self.requests_mock.get(url, text=network_error_response) self.set_middleware() def assert_unscoped_default_tenant_auto_scopes(self, token): """Unscoped v2 requests with a default tenant should ``auto-scope``. The implied scope is the user's tenant ID. """ resp = self.call_middleware(headers={'X-Auth-Token': token}) self.assertEqual(FakeApp.SUCCESS, resp.body) self.assertIn('keystone.token_info', resp.request.environ) def assert_valid_last_url(self, token_id): self.assertLastPath("/v2.0/tokens/%s" % token_id) def test_default_tenant_uuid_token(self): self.assert_unscoped_default_tenant_auto_scopes( self.examples.UUID_TOKEN_DEFAULT) def test_default_tenant_signed_token(self): self.assert_unscoped_default_tenant_auto_scopes( self.examples.SIGNED_TOKEN_SCOPED) def assert_unscoped_token_receives_401(self, token): """Unscoped requests with no default tenant ID should be rejected.""" resp = self.call_middleware(headers={'X-Auth-Token': token}, expected_status=401) self.assertEqual("Keystone uri='https://keystone.example.com:1234'", resp.headers['WWW-Authenticate']) def test_unscoped_uuid_token_receives_401(self): self.assert_unscoped_token_receives_401( self.examples.UUID_TOKEN_UNSCOPED) def test_unscoped_pki_token_receives_401(self): self.assert_unscoped_token_receives_401( self.examples.SIGNED_TOKEN_UNSCOPED) def test_request_prevent_service_catalog_injection(self): token = self.examples.UUID_TOKEN_NO_SERVICE_CATALOG resp = self.call_middleware(headers={'X-Service-Catalog': '[]', 'X-Auth-Token': token}) self.assertFalse(resp.request.headers.get('X-Service-Catalog')) self.assertEqual(FakeApp.SUCCESS, resp.body) def test_user_plugin_token_properties(self): token = self.examples.UUID_TOKEN_DEFAULT token_data = self.examples.TOKEN_RESPONSES[token] service = self.examples.UUID_SERVICE_TOKEN_DEFAULT resp = self.call_middleware(headers={'X-Service-Catalog': '[]', 'X-Auth-Token': token, 'X-Service-Token': service}) self.assertEqual(FakeApp.SUCCESS, resp.body) token_auth = resp.request.environ['keystone.token_auth'] self.assertTrue(token_auth.has_user_token) self.assertTrue(token_auth.has_service_token) self.assertEqual(token_data.user_id, token_auth.user.user_id) self.assertEqual(token_data.tenant_id, token_auth.user.project_id) self.assertThat(token_auth.user.role_names, matchers.HasLength(2)) self.assertIn('role1', token_auth.user.role_names) self.assertIn('role2', token_auth.user.role_names) self.assertIsNone(token_auth.user.trust_id) self.assertIsNone(token_auth.user.user_domain_id) self.assertIsNone(token_auth.user.project_domain_id) self.assertThat(token_auth.service.role_names, matchers.HasLength(2)) self.assertIn('service', token_auth.service.role_names) self.assertIn('service_role2', token_auth.service.role_names) self.assertIsNone(token_auth.service.trust_id) class CrossVersionAuthTokenMiddlewareTest(BaseAuthTokenMiddlewareTest, testresources.ResourcedTestCase): resources = [('examples', client_fixtures.EXAMPLES_RESOURCE)] def test_valid_uuid_request_forced_to_2_0(self): """Test forcing auth_token to use lower api version. By installing the v3 http hander, auth_token will be get a version list that looks like a v3 server - from which it would normally chose v3.0 as the auth version. However, here we specify v2.0 in the configuration - which should force auth_token to use that version instead. """ conf = { 'auth_version': 'v2.0' } self.requests_mock.get(BASE_URI, json=VERSION_LIST_v3, status_code=300) self.requests_mock.post('%s/v2.0/tokens' % BASE_URI, text=FAKE_ADMIN_TOKEN) token = self.examples.UUID_TOKEN_DEFAULT url = "%s/v2.0/tokens/%s" % (BASE_URI, token) text = self.examples.JSON_TOKEN_RESPONSES[token] self.requests_mock.get(url, text=text) self.set_middleware(conf=conf) # This tests will only work is auth_token has chosen to use the # lower, v2, api version self.call_middleware(headers={'X-Auth-Token': token}) self.assertEqual(url, self.requests_mock.last_request.url) class v3AuthTokenMiddlewareTest(BaseAuthTokenMiddlewareTest, CommonAuthTokenMiddlewareTest, testresources.ResourcedTestCase): """Test auth_token middleware with v3 tokens. Re-execute the AuthTokenMiddlewareTest class tests, but with the auth_token middleware configured to expect v3 tokens back from a keystone server. This is done by configuring the AuthTokenMiddlewareTest class via its Setup(), passing in v3 style data that will then be used by the tests themselves. This approach has been used to ensure we really are running the same tests for both v2 and v3 tokens. There a few additional specific test for v3 only: - We allow an unscoped token to be validated (as unscoped), where as for v2 tokens, the auth_token middleware is expected to try and auto-scope it (and fail if there is no default tenant) - Domain scoped tokens Since we don't specify an auth version for auth_token to use, by definition we are thefore implicitely testing that it will use the highest available auth version, i.e. v3.0 """ resources = [('examples', client_fixtures.EXAMPLES_RESOURCE)] def setUp(self): super(v3AuthTokenMiddlewareTest, self).setUp( auth_version='v3.0', fake_app=v3FakeApp) self.token_dict = { 'uuid_token_default': self.examples.v3_UUID_TOKEN_DEFAULT, 'uuid_token_unscoped': self.examples.v3_UUID_TOKEN_UNSCOPED, 'uuid_token_bind': self.examples.v3_UUID_TOKEN_BIND, 'uuid_token_unknown_bind': self.examples.v3_UUID_TOKEN_UNKNOWN_BIND, 'signed_token_scoped': self.examples.SIGNED_v3_TOKEN_SCOPED, 'signed_token_scoped_pkiz': self.examples.SIGNED_v3_TOKEN_SCOPED_PKIZ, 'signed_token_scoped_hash': self.examples.SIGNED_v3_TOKEN_SCOPED_HASH, 'signed_token_scoped_hash_sha256': self.examples.SIGNED_v3_TOKEN_SCOPED_HASH_SHA256, 'signed_token_scoped_expired': self.examples.SIGNED_TOKEN_SCOPED_EXPIRED, 'revoked_token': self.examples.REVOKED_v3_TOKEN, 'revoked_token_pkiz': self.examples.REVOKED_v3_TOKEN_PKIZ, 'revoked_token_hash': self.examples.REVOKED_v3_TOKEN_HASH, 'revoked_token_hash_sha256': self.examples.REVOKED_v3_TOKEN_HASH_SHA256, 'revoked_token_pkiz_hash': self.examples.REVOKED_v3_PKIZ_TOKEN_HASH, 'uuid_service_token_default': self.examples.v3_UUID_SERVICE_TOKEN_DEFAULT, } self.requests_mock.get(BASE_URI, json=VERSION_LIST_v3, status_code=300) # TODO(jamielennox): auth_token middleware uses a v2 admin token # regardless of the auth_version that is set. self.requests_mock.post('%s/v2.0/tokens' % BASE_URI, text=FAKE_ADMIN_TOKEN) self.revocation_url = '%s/v3/auth/tokens/OS-PKI/revoked' % BASE_URI self.requests_mock.get(self.revocation_url, text=self.examples.SIGNED_REVOCATION_LIST) self.requests_mock.get('%s/v3/auth/tokens' % BASE_URI, text=self.token_response, headers={'X-Subject-Token': uuid.uuid4().hex}) self.set_middleware() def token_response(self, request, context): auth_id = request.headers.get('X-Auth-Token') token_id = request.headers.get('X-Subject-Token') self.assertEqual(auth_id, FAKE_ADMIN_TOKEN_ID) if token_id == ERROR_TOKEN: msg = "Network connection refused." raise ksa_exceptions.ConnectFailure(msg) try: response = self.examples.JSON_TOKEN_RESPONSES[token_id] except KeyError: response = "" context.status_code = 404 return response def assert_valid_last_url(self, token_id): self.assertLastPath('/v3/auth/tokens') def test_valid_unscoped_uuid_request(self): # Remove items that won't be in an unscoped token delta_expected_env = { 'HTTP_X_PROJECT_ID': None, 'HTTP_X_PROJECT_NAME': None, 'HTTP_X_PROJECT_DOMAIN_ID': None, 'HTTP_X_PROJECT_DOMAIN_NAME': None, 'HTTP_X_TENANT_ID': None, 'HTTP_X_TENANT_NAME': None, 'HTTP_X_ROLES': '', 'HTTP_X_TENANT': None, 'HTTP_X_ROLE': '', } self.set_middleware(expected_env=delta_expected_env) self.assert_valid_request_200(self.examples.v3_UUID_TOKEN_UNSCOPED, with_catalog=False) self.assertLastPath('/v3/auth/tokens') def test_domain_scoped_uuid_request(self): # Modify items compared to default token for a domain scope delta_expected_env = { 'HTTP_X_DOMAIN_ID': 'domain_id1', 'HTTP_X_DOMAIN_NAME': 'domain_name1', 'HTTP_X_PROJECT_ID': None, 'HTTP_X_PROJECT_NAME': None, 'HTTP_X_PROJECT_DOMAIN_ID': None, 'HTTP_X_PROJECT_DOMAIN_NAME': None, 'HTTP_X_TENANT_ID': None, 'HTTP_X_TENANT_NAME': None, 'HTTP_X_TENANT': None } self.set_middleware(expected_env=delta_expected_env) self.assert_valid_request_200( self.examples.v3_UUID_TOKEN_DOMAIN_SCOPED) self.assertLastPath('/v3/auth/tokens') def test_gives_v2_catalog(self): self.set_middleware() req = self.assert_valid_request_200( self.examples.SIGNED_v3_TOKEN_SCOPED) catalog = jsonutils.loads(req.headers['X-Service-Catalog']) for service in catalog: for endpoint in service['endpoints']: # no point checking everything, just that it's in v2 format self.assertIn('adminURL', endpoint) self.assertIn('publicURL', endpoint) self.assertIn('internalURL', endpoint) def test_fallback_to_online_validation_with_signing_error(self): self.requests_mock.get('%s/v3/OS-SIMPLE-CERT/certificates' % BASE_URI, status_code=404) self.assert_valid_request_200(self.token_dict['signed_token_scoped']) self.assert_valid_request_200( self.token_dict['signed_token_scoped_pkiz']) def test_fallback_to_online_validation_with_ca_error(self): self.requests_mock.get('%s/v3/OS-SIMPLE-CERT/ca' % BASE_URI, status_code=404) self.assert_valid_request_200(self.token_dict['signed_token_scoped']) self.assert_valid_request_200( self.token_dict['signed_token_scoped_pkiz']) def test_fallback_to_online_validation_with_revocation_list_error(self): self.requests_mock.get(self.revocation_url, status_code=404) self.assert_valid_request_200(self.token_dict['signed_token_scoped']) self.assert_valid_request_200( self.token_dict['signed_token_scoped_pkiz']) def test_user_plugin_token_properties(self): token = self.examples.v3_UUID_TOKEN_DEFAULT token_data = self.examples.TOKEN_RESPONSES[token] service = self.examples.v3_UUID_SERVICE_TOKEN_DEFAULT service_data = self.examples.TOKEN_RESPONSES[service] resp = self.call_middleware(headers={'X-Service-Catalog': '[]', 'X-Auth-Token': token, 'X-Service-Token': service}) self.assertEqual(FakeApp.SUCCESS, resp.body) token_auth = resp.request.environ['keystone.token_auth'] self.assertTrue(token_auth.has_user_token) self.assertTrue(token_auth.has_service_token) self.assertEqual(token_data.user_id, token_auth.user.user_id) self.assertEqual(token_data.project_id, token_auth.user.project_id) self.assertEqual(token_data.user_domain_id, token_auth.user.user_domain_id) self.assertEqual(token_data.project_domain_id, token_auth.user.project_domain_id) self.assertThat(token_auth.user.role_names, matchers.HasLength(2)) self.assertIn('role1', token_auth.user.role_names) self.assertIn('role2', token_auth.user.role_names) self.assertIsNone(token_auth.user.trust_id) self.assertEqual(service_data.user_id, token_auth.service.user_id) self.assertEqual(service_data.project_id, token_auth.service.project_id) self.assertEqual(service_data.user_domain_id, token_auth.service.user_domain_id) self.assertEqual(service_data.project_domain_id, token_auth.service.project_domain_id) self.assertThat(token_auth.service.role_names, matchers.HasLength(2)) self.assertIn('service', token_auth.service.role_names) self.assertIn('service_role2', token_auth.service.role_names) self.assertIsNone(token_auth.service.trust_id) def test_expire_stored_in_cache(self): # tests the upgrade path from storing a tuple vs just the data in the # cache. Can be removed in the future. token = 'mytoken' data = 'this_data' self.set_middleware() self.middleware._token_cache.initialize({}) now = datetime.datetime.utcnow() delta = datetime.timedelta(hours=1) expires = strtime(at=(now + delta)) self.middleware._token_cache.set(token, (data, expires)) new_data = self.middleware.fetch_token(token) self.assertEqual(data, new_data) def test_not_is_admin_project(self): token = self.examples.v3_NOT_IS_ADMIN_PROJECT self.set_middleware(expected_env={'HTTP_X_IS_ADMIN_PROJECT': 'False'}) req = self.assert_valid_request_200(token) self.assertIs(False, req.environ['keystone.token_auth'].user.is_admin_project) def test_service_token_with_valid_service_role_not_required(self): s = super(v3AuthTokenMiddlewareTest, self) s.test_service_token_with_valid_service_role_not_required() e = self.requests_mock.request_history[3].qs.get('allow_expired') self.assertEqual(['1'], e) def test_service_token_with_invalid_service_role_not_required(self): s = super(v3AuthTokenMiddlewareTest, self) s.test_service_token_with_invalid_service_role_not_required() e = self.requests_mock.request_history[3].qs.get('allow_expired') self.assertIsNone(e) def test_service_token_with_valid_service_role_required(self): s = super(v3AuthTokenMiddlewareTest, self) s.test_service_token_with_valid_service_role_required() e = self.requests_mock.request_history[3].qs.get('allow_expired') self.assertEqual(['1'], e) def test_service_token_with_invalid_service_role_required(self): s = super(v3AuthTokenMiddlewareTest, self) s.test_service_token_with_invalid_service_role_required() e = self.requests_mock.request_history[3].qs.get('allow_expired') self.assertIsNone(e) class DelayedAuthTests(BaseAuthTokenMiddlewareTest): def test_header_in_401(self): body = uuid.uuid4().hex www_authenticate_uri = 'http://local.test' conf = {'delay_auth_decision': 'True', 'auth_version': 'v3.0', 'www_authenticate_uri': www_authenticate_uri} middleware = self.create_simple_middleware(status='401 Unauthorized', body=body, conf=conf) resp = self.call(middleware, expected_status=401) self.assertEqual(six.b(body), resp.body) self.assertEqual("Keystone uri='%s'" % www_authenticate_uri, resp.headers['WWW-Authenticate']) def test_delayed_auth_values(self): conf = {'www_authenticate_uri': 'http://local.test'} status = '401 Unauthorized' middleware = self.create_simple_middleware(status=status, conf=conf) self.assertFalse(middleware._delay_auth_decision) for v in ('True', '1', 'on', 'yes'): conf = {'delay_auth_decision': v, 'www_authenticate_uri': 'http://local.test'} middleware = self.create_simple_middleware(status=status, conf=conf) self.assertTrue(middleware._delay_auth_decision) for v in ('False', '0', 'no'): conf = {'delay_auth_decision': v, 'www_authenticate_uri': 'http://local.test'} middleware = self.create_simple_middleware(status=status, conf=conf) self.assertFalse(middleware._delay_auth_decision) def test_auth_plugin_with_no_tokens(self): body = uuid.uuid4().hex www_authenticate_uri = 'http://local.test' conf = { 'delay_auth_decision': True, 'www_authenticate_uri': www_authenticate_uri } middleware = self.create_simple_middleware(body=body, conf=conf) resp = self.call(middleware) self.assertEqual(six.b(body), resp.body) token_auth = resp.request.environ['keystone.token_auth'] self.assertFalse(token_auth.has_user_token) self.assertIsNone(token_auth.user) self.assertFalse(token_auth.has_service_token) self.assertIsNone(token_auth.service) class CommonCompositeAuthTests(object): """Test Composite authentication. Test the behaviour of adding a service-token. """ def test_composite_auth_ok(self): token = self.token_dict['uuid_token_default'] service_token = self.token_dict['uuid_service_token_default'] fake_logger = fixtures.FakeLogger(level=logging.DEBUG) self.middleware.logger = self.useFixture(fake_logger) resp = self.call_middleware(headers={'X-Auth-Token': token, 'X-Service-Token': service_token}) self.assertEqual(FakeApp.SUCCESS, resp.body) expected_env = dict(EXPECTED_V2_DEFAULT_ENV_RESPONSE) expected_env.update(EXPECTED_V2_DEFAULT_SERVICE_ENV_RESPONSE) # role list may get reordered, check for string pieces individually self.assertIn('Received request from user: ', fake_logger.output) self.assertIn('user_id %(HTTP_X_USER_ID)s, ' 'project_id %(HTTP_X_TENANT_ID)s, ' 'roles ' % expected_env, fake_logger.output) self.assertIn('service: user_id %(HTTP_X_SERVICE_USER_ID)s, ' 'project_id %(HTTP_X_SERVICE_PROJECT_ID)s, ' 'roles ' % expected_env, fake_logger.output) roles = ','.join([expected_env['HTTP_X_SERVICE_ROLES'], expected_env['HTTP_X_ROLES']]) for r in roles.split(','): self.assertIn(r, fake_logger.output) def test_composite_auth_invalid_service_token(self): token = self.token_dict['uuid_token_default'] service_token = 'invalid-service-token' resp = self.call_middleware(headers={'X-Auth-Token': token, 'X-Service-Token': service_token}, expected_status=401) expected_body = b'The request you have made requires authentication.' self.assertThat(resp.body, matchers.Contains(expected_body)) def test_composite_auth_no_service_token(self): self.purge_service_token_expected_env() req = webob.Request.blank('/') req.headers['X-Auth-Token'] = self.token_dict['uuid_token_default'] # Ensure injection of service headers is not possible for key, value in self.service_token_expected_env.items(): header_key = key[len('HTTP_'):].replace('_', '-') req.headers[header_key] = value # Check arbitrary headers not removed req.headers['X-Foo'] = 'Bar' resp = req.get_response(self.middleware) for key in six.iterkeys(self.service_token_expected_env): header_key = key[len('HTTP_'):].replace('_', '-') self.assertFalse(req.headers.get(header_key)) self.assertEqual('Bar', req.headers.get('X-Foo')) self.assertEqual(418, resp.status_int) self.assertEqual(FakeApp.FORBIDDEN, resp.body) def test_composite_auth_invalid_user_token(self): token = 'invalid-token' service_token = self.token_dict['uuid_service_token_default'] resp = self.call_middleware(headers={'X-Auth-Token': token, 'X-Service-Token': service_token}, expected_status=401) expected_body = b'The request you have made requires authentication.' self.assertThat(resp.body, matchers.Contains(expected_body)) def test_composite_auth_no_user_token(self): service_token = self.token_dict['uuid_service_token_default'] resp = self.call_middleware(headers={'X-Service-Token': service_token}, expected_status=401) expected_body = b'The request you have made requires authentication.' self.assertThat(resp.body, matchers.Contains(expected_body)) def test_composite_auth_delay_ok(self): self.middleware._delay_auth_decision = True token = self.token_dict['uuid_token_default'] service_token = self.token_dict['uuid_service_token_default'] resp = self.call_middleware(headers={'X-Auth-Token': token, 'X-Service-Token': service_token}) self.assertEqual(FakeApp.SUCCESS, resp.body) def test_composite_auth_delay_invalid_service_token(self): self.middleware._delay_auth_decision = True self.purge_service_token_expected_env() expected_env = { 'HTTP_X_SERVICE_IDENTITY_STATUS': 'Invalid', } self.update_expected_env(expected_env) token = self.token_dict['uuid_token_default'] service_token = 'invalid-service-token' resp = self.call_middleware(headers={'X-Auth-Token': token, 'X-Service-Token': service_token}, expected_status=420) self.assertEqual(FakeApp.FORBIDDEN, resp.body) def test_composite_auth_delay_invalid_service_and_user_tokens(self): self.middleware._delay_auth_decision = True self.purge_service_token_expected_env() self.purge_token_expected_env() expected_env = { 'HTTP_X_IDENTITY_STATUS': 'Invalid', 'HTTP_X_SERVICE_IDENTITY_STATUS': 'Invalid', } self.update_expected_env(expected_env) token = 'invalid-token' service_token = 'invalid-service-token' resp = self.call_middleware(headers={'X-Auth-Token': token, 'X-Service-Token': service_token}, expected_status=419) self.assertEqual(FakeApp.FORBIDDEN, resp.body) def test_composite_auth_delay_no_service_token(self): self.middleware._delay_auth_decision = True self.purge_service_token_expected_env() req = webob.Request.blank('/') req.headers['X-Auth-Token'] = self.token_dict['uuid_token_default'] # Ensure injection of service headers is not possible for key, value in self.service_token_expected_env.items(): header_key = key[len('HTTP_'):].replace('_', '-') req.headers[header_key] = value # Check arbitrary headers not removed req.headers['X-Foo'] = 'Bar' resp = req.get_response(self.middleware) for key in six.iterkeys(self.service_token_expected_env): header_key = key[len('HTTP_'):].replace('_', '-') self.assertFalse(req.headers.get(header_key)) self.assertEqual('Bar', req.headers.get('X-Foo')) self.assertEqual(418, resp.status_int) self.assertEqual(FakeApp.FORBIDDEN, resp.body) def test_composite_auth_delay_invalid_user_token(self): self.middleware._delay_auth_decision = True self.purge_token_expected_env() expected_env = { 'HTTP_X_IDENTITY_STATUS': 'Invalid', } self.update_expected_env(expected_env) token = 'invalid-token' service_token = self.token_dict['uuid_service_token_default'] resp = self.call_middleware(headers={'X-Auth-Token': token, 'X-Service-Token': service_token}, expected_status=403) self.assertEqual(FakeApp.FORBIDDEN, resp.body) def test_composite_auth_delay_no_user_token(self): self.middleware._delay_auth_decision = True self.purge_token_expected_env() expected_env = { 'HTTP_X_IDENTITY_STATUS': 'Invalid', } self.update_expected_env(expected_env) service_token = self.token_dict['uuid_service_token_default'] resp = self.call_middleware(headers={'X-Service-Token': service_token}, expected_status=403) self.assertEqual(FakeApp.FORBIDDEN, resp.body) def assert_kerberos_composite_bind(self, user_token, service_token, bind_level): conf = { 'enforce_token_bind': bind_level, 'auth_version': self.auth_version, } self.set_middleware(conf=conf) req = webob.Request.blank('/') req.headers['X-Auth-Token'] = user_token req.headers['X-Service-Token'] = service_token req.environ['REMOTE_USER'] = self.examples.SERVICE_KERBEROS_BIND req.environ['AUTH_TYPE'] = 'Negotiate' resp = req.get_response(self.middleware) self.assertEqual(200, resp.status_int) self.assertEqual(FakeApp.SUCCESS, resp.body) self.assertIn('keystone.token_info', req.environ) def test_composite_auth_with_bind(self): token = self.token_dict['uuid_token_bind'] service_token = self.token_dict['uuid_service_token_bind'] self.assert_kerberos_composite_bind(token, service_token, bind_level='required') class v2CompositeAuthTests(BaseAuthTokenMiddlewareTest, CommonCompositeAuthTests, testresources.ResourcedTestCase): """Test auth_token middleware with v2 token based composite auth. Execute the Composite auth class tests, but with the auth_token middleware configured to expect v2 tokens back from a keystone server. """ resources = [('examples', client_fixtures.EXAMPLES_RESOURCE)] def setUp(self): super(v2CompositeAuthTests, self).setUp( expected_env=EXPECTED_V2_DEFAULT_SERVICE_ENV_RESPONSE, fake_app=CompositeFakeApp) uuid_token_default = self.examples.UUID_TOKEN_DEFAULT uuid_service_token_default = self.examples.UUID_SERVICE_TOKEN_DEFAULT uuid_token_bind = self.examples.UUID_TOKEN_BIND uuid_service_token_bind = self.examples.UUID_SERVICE_TOKEN_BIND self.token_dict = { 'uuid_token_default': uuid_token_default, 'uuid_service_token_default': uuid_service_token_default, 'uuid_token_bind': uuid_token_bind, 'uuid_service_token_bind': uuid_service_token_bind, } self.requests_mock.get(BASE_URI, json=VERSION_LIST_v2, status_code=300) self.requests_mock.post('%s/v2.0/tokens' % BASE_URI, text=FAKE_ADMIN_TOKEN) self.requests_mock.get('%s/v2.0/tokens/revoked' % BASE_URI, text=self.examples.SIGNED_REVOCATION_LIST, status_code=200) for token in (self.examples.UUID_TOKEN_DEFAULT, self.examples.UUID_SERVICE_TOKEN_DEFAULT, self.examples.UUID_TOKEN_BIND, self.examples.UUID_SERVICE_TOKEN_BIND): text = self.examples.JSON_TOKEN_RESPONSES[token] self.requests_mock.get('%s/v2.0/tokens/%s' % (BASE_URI, token), text=text) for invalid_uri in ("%s/v2.0/tokens/invalid-token" % BASE_URI, "%s/v2.0/tokens/invalid-service-token" % BASE_URI): self.requests_mock.get(invalid_uri, text='', status_code=404) self.token_expected_env = dict(EXPECTED_V2_DEFAULT_ENV_RESPONSE) self.service_token_expected_env = dict( EXPECTED_V2_DEFAULT_SERVICE_ENV_RESPONSE) self.set_middleware() class v3CompositeAuthTests(BaseAuthTokenMiddlewareTest, CommonCompositeAuthTests, testresources.ResourcedTestCase): """Test auth_token middleware with v3 token based composite auth. Execute the Composite auth class tests, but with the auth_token middleware configured to expect v3 tokens back from a keystone server. """ resources = [('examples', client_fixtures.EXAMPLES_RESOURCE)] def setUp(self): super(v3CompositeAuthTests, self).setUp( auth_version='v3.0', fake_app=v3CompositeFakeApp) uuid_token_default = self.examples.v3_UUID_TOKEN_DEFAULT uuid_serv_token_default = self.examples.v3_UUID_SERVICE_TOKEN_DEFAULT uuid_token_bind = self.examples.v3_UUID_TOKEN_BIND uuid_service_token_bind = self.examples.v3_UUID_SERVICE_TOKEN_BIND self.token_dict = { 'uuid_token_default': uuid_token_default, 'uuid_service_token_default': uuid_serv_token_default, 'uuid_token_bind': uuid_token_bind, 'uuid_service_token_bind': uuid_service_token_bind, } self.requests_mock.get(BASE_URI, json=VERSION_LIST_v3, status_code=300) # TODO(jamielennox): auth_token middleware uses a v2 admin token # regardless of the auth_version that is set. self.requests_mock.post('%s/v2.0/tokens' % BASE_URI, text=FAKE_ADMIN_TOKEN) self.requests_mock.get('%s/v3/auth/tokens/OS-PKI/revoked' % BASE_URI, text=self.examples.SIGNED_REVOCATION_LIST) self.requests_mock.get('%s/v3/auth/tokens' % BASE_URI, text=self.token_response, headers={'X-Subject-Token': uuid.uuid4().hex}) self.token_expected_env = dict(EXPECTED_V2_DEFAULT_ENV_RESPONSE) self.token_expected_env.update(EXPECTED_V3_DEFAULT_ENV_ADDITIONS) self.service_token_expected_env = dict( EXPECTED_V2_DEFAULT_SERVICE_ENV_RESPONSE) self.service_token_expected_env.update( EXPECTED_V3_DEFAULT_SERVICE_ENV_ADDITIONS) self.set_middleware() def token_response(self, request, context): auth_id = request.headers.get('X-Auth-Token') token_id = request.headers.get('X-Subject-Token') self.assertEqual(auth_id, FAKE_ADMIN_TOKEN_ID) status = 200 response = "" if token_id == ERROR_TOKEN: msg = "Network connection refused." raise ksc_exceptions.ConnectionRefused(msg) try: response = self.examples.JSON_TOKEN_RESPONSES[token_id] except KeyError: status = 404 context.status_code = status return response class OtherTests(BaseAuthTokenMiddlewareTest): def setUp(self): super(OtherTests, self).setUp() self.logger = self.useFixture(fixtures.FakeLogger()) def test_unknown_server_versions(self): versions = fixture.DiscoveryList(v2=False, v3_id='v4', href=BASE_URI) self.set_middleware() self.requests_mock.get(BASE_URI, json=versions, status_code=300) self.call_middleware(headers={'X-Auth-Token': uuid.uuid4().hex}, expected_status=503) self.assertIn('versions [v3.0, v2.0]', self.logger.output) def _assert_auth_version(self, conf_version, identity_server_version): self.set_middleware(conf={'auth_version': conf_version}) identity_server = self.middleware._create_identity_server() self.assertEqual(identity_server_version, identity_server.auth_version) def test_micro_version(self): self._assert_auth_version('v2', (2, 0)) self._assert_auth_version('v2.0', (2, 0)) self._assert_auth_version('v3', (3, 0)) self._assert_auth_version('v3.0', (3, 0)) self._assert_auth_version('v3.1', (3, 0)) self._assert_auth_version('v3.2', (3, 0)) self._assert_auth_version('v3.9', (3, 0)) self._assert_auth_version('v3.3.1', (3, 0)) self._assert_auth_version('v3.3.5', (3, 0)) def test_default_auth_version(self): # VERSION_LIST_v3 contains both v2 and v3 version elements self.requests_mock.get(BASE_URI, json=VERSION_LIST_v3, status_code=300) self._assert_auth_version(None, (3, 0)) # VERSION_LIST_v2 contains only v2 version elements self.requests_mock.get(BASE_URI, json=VERSION_LIST_v2, status_code=300) self._assert_auth_version(None, (2, 0)) def test_unsupported_auth_version(self): # If the requested version isn't supported we will use v2 self._assert_auth_version('v1', (2, 0)) self._assert_auth_version('v10', (2, 0)) class AuthProtocolLoadingTests(BaseAuthTokenMiddlewareTest): AUTH_URL = 'http://auth.url/prefix' DISC_URL = 'http://disc.url/prefix' KEYSTONE_BASE_URL = 'http://keystone.url/prefix' CRUD_URL = 'http://crud.url/prefix' # NOTE(jamielennox): use the /v2.0 prefix here because this is what's most # likely to be in the service catalog and we should be able to ignore it. KEYSTONE_URL = KEYSTONE_BASE_URL + '/v2.0' def setUp(self): super(AuthProtocolLoadingTests, self).setUp() self.project_id = uuid.uuid4().hex # first touch is to discover the available versions at the auth_url self.requests_mock.get(self.AUTH_URL, json=fixture.DiscoveryList(href=self.DISC_URL), status_code=300) # then we do discovery on the URL from the service catalog. In practice # this is mostly the same URL as before but test the full range. self.requests_mock.get(self.KEYSTONE_BASE_URL + '/', json=fixture.DiscoveryList(href=self.CRUD_URL), status_code=300) def good_request(self, app): # admin_token is the token that the service will get back from auth admin_token_id = uuid.uuid4().hex admin_token = fixture.V3Token(project_id=self.project_id) s = admin_token.add_service('identity', name='keystone') s.add_standard_endpoints(admin=self.KEYSTONE_URL) self.requests_mock.post('%s/v3/auth/tokens' % self.AUTH_URL, json=admin_token, headers={'X-Subject-Token': admin_token_id}) # user_token is the data from the user's inputted token user_token_id = uuid.uuid4().hex user_token = fixture.V3Token() user_token.set_project_scope() request_headers = {'X-Subject-Token': user_token_id, 'X-Auth-Token': admin_token_id} self.requests_mock.get('%s/v3/auth/tokens' % self.KEYSTONE_BASE_URL, request_headers=request_headers, json=user_token, headers={'X-Subject-Token': uuid.uuid4().hex}) resp = self.call(app, headers={'X-Auth-Token': user_token_id}) return resp def test_loading_password_plugin(self): # the password options aren't set on config until loading time, but we # need them set so we can override the values for testing, so force it opts = loading.get_auth_plugin_conf_options('password') self.cfg.register_opts(opts, group=_base.AUTHTOKEN_GROUP) project_id = uuid.uuid4().hex # Register the authentication options loading.register_auth_conf_options(self.cfg.conf, group=_base.AUTHTOKEN_GROUP) # configure the authentication options self.cfg.config(auth_type='password', username='testuser', password='testpass', auth_url=self.AUTH_URL, project_id=project_id, user_domain_id='userdomainid', group=_base.AUTHTOKEN_GROUP) body = uuid.uuid4().hex app = self.create_simple_middleware(body=body) resp = self.good_request(app) self.assertEqual(six.b(body), resp.body) @staticmethod def get_plugin(app): return app._identity_server._adapter.auth def test_invalid_plugin_fails_to_initialize(self): loading.register_auth_conf_options(self.cfg.conf, group=_base.AUTHTOKEN_GROUP) self.cfg.config(auth_type=uuid.uuid4().hex, group=_base.AUTHTOKEN_GROUP) self.assertRaises( ksa_exceptions.NoMatchingPlugin, self.create_simple_middleware) def test_plugin_loading_mixed_opts(self): # some options via override and some via conf opts = loading.get_auth_plugin_conf_options('password') self.cfg.register_opts(opts, group=_base.AUTHTOKEN_GROUP) username = 'testuser' password = 'testpass' # Register the authentication options loading.register_auth_conf_options(self.cfg.conf, group=_base.AUTHTOKEN_GROUP) # configure the authentication options self.cfg.config(auth_type='password', auth_url='http://keystone.test:5000', password=password, project_id=self.project_id, user_domain_id='userdomainid', group=_base.AUTHTOKEN_GROUP) conf = {'username': username, 'auth_url': self.AUTH_URL} body = uuid.uuid4().hex app = self.create_simple_middleware(body=body, conf=conf) resp = self.good_request(app) self.assertEqual(six.b(body), resp.body) plugin = self.get_plugin(app) self.assertEqual(self.AUTH_URL, plugin.auth_url) self.assertEqual(username, plugin._username) self.assertEqual(password, plugin._password) self.assertEqual(self.project_id, plugin._project_id) def test_plugin_loading_with_auth_section(self): # some options via override and some via conf section = 'testsection' username = 'testuser' password = 'testpass' loading.register_auth_conf_options(self.cfg.conf, group=section) opts = loading.get_auth_plugin_conf_options('password') self.cfg.register_opts(opts, group=section) # Register the authentication options loading.register_auth_conf_options(self.cfg.conf, group=_base.AUTHTOKEN_GROUP) # configure the authentication options self.cfg.config(auth_section=section, group=_base.AUTHTOKEN_GROUP) self.cfg.config(auth_type='password', auth_url=self.AUTH_URL, password=password, project_id=self.project_id, user_domain_id='userdomainid', group=section) conf = {'username': username} body = uuid.uuid4().hex app = self.create_simple_middleware(body=body, conf=conf) resp = self.good_request(app) self.assertEqual(six.b(body), resp.body) plugin = self.get_plugin(app) self.assertEqual(self.AUTH_URL, plugin.auth_url) self.assertEqual(username, plugin._username) self.assertEqual(password, plugin._password) self.assertEqual(self.project_id, plugin._project_id) class TestAuthPluginUserAgentGeneration(BaseAuthTokenMiddlewareTest): def setUp(self): super(TestAuthPluginUserAgentGeneration, self).setUp() self.auth_url = uuid.uuid4().hex self.project_id = uuid.uuid4().hex self.username = uuid.uuid4().hex self.password = uuid.uuid4().hex self.section = uuid.uuid4().hex self.user_domain_id = uuid.uuid4().hex loading.register_auth_conf_options(self.cfg.conf, group=self.section) opts = loading.get_auth_plugin_conf_options('password') self.cfg.register_opts(opts, group=self.section) # Register the authentication options loading.register_auth_conf_options(self.cfg.conf, group=_base.AUTHTOKEN_GROUP) # configure the authentication options self.cfg.config(auth_section=self.section, group=_base.AUTHTOKEN_GROUP) self.cfg.config(auth_type='password', password=self.password, project_id=self.project_id, user_domain_id=self.user_domain_id, group=self.section) def test_no_project_configured(self): ksm_version = uuid.uuid4().hex conf = {'username': self.username, 'auth_url': self.auth_url} app = self._create_app(conf, '', ksm_version) self._assert_user_agent(app, '', ksm_version) def test_project_in_configuration(self): project = uuid.uuid4().hex project_version = uuid.uuid4().hex ksm_version = uuid.uuid4().hex conf = {'username': self.username, 'auth_url': self.auth_url, 'project': project} app = self._create_app(conf, project_version, ksm_version) project_with_version = '{0}/{1} '.format(project, project_version) self._assert_user_agent(app, project_with_version, ksm_version) def test_project_not_installed_results_in_unknown_version(self): project = uuid.uuid4().hex conf = {'username': self.username, 'auth_url': self.auth_url, 'project': project} v = pbr.version.VersionInfo('keystonemiddleware').version_string() app = self.create_simple_middleware(conf=conf, use_global_conf=True) project_with_version = '{0}/{1} '.format(project, 'unknown') self._assert_user_agent(app, project_with_version, v) def test_project_in_oslo_configuration(self): project = uuid.uuid4().hex project_version = uuid.uuid4().hex ksm_version = uuid.uuid4().hex conf = {'username': self.username, 'auth_url': self.auth_url} with mock.patch.object(self.cfg.conf, 'project', new=project, create=True): app = self._create_app(conf, project_version, ksm_version) project = '{0}/{1} '.format(project, project_version) self._assert_user_agent(app, project, ksm_version) def _create_app(self, conf, project_version, ksm_version): fake_pkg_resources = mock.Mock() fake_pkg_resources.get_distribution().version = project_version fake_version_info = mock.Mock() fake_version_info.version_string.return_value = ksm_version fake_pbr_version = mock.Mock() fake_pbr_version.VersionInfo.return_value = fake_version_info body = uuid.uuid4().hex at_pbr = 'keystonemiddleware._common.config.pbr.version' with mock.patch('keystonemiddleware._common.config.pkg_resources', new=fake_pkg_resources): with mock.patch(at_pbr, new=fake_pbr_version): return self.create_simple_middleware(body=body, conf=conf) def _assert_user_agent(self, app, project, ksm_version): sess = app._identity_server._adapter.session expected_ua = ('{0}keystonemiddleware.auth_token/{1}' .format(project, ksm_version)) self.assertThat(sess.user_agent, matchers.StartsWith(expected_ua)) def load_tests(loader, tests, pattern): return testresources.OptimisingTestSuite(tests) keystonemiddleware-4.21.0/keystonemiddleware/tests/unit/__init__.py0000666000175100017510000000000013225160624025643 0ustar zuulzuul00000000000000keystonemiddleware-4.21.0/keystonemiddleware/tests/unit/test_ec2_token_middleware.py0000666000175100017510000001562313225160624031232 0ustar zuulzuul00000000000000# Copyright 2012 OpenStack Foundation # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. import mock from oslo_serialization import jsonutils import requests import six import webob from keystonemiddleware import ec2_token from keystonemiddleware.tests.unit import utils TOKEN_ID = 'fake-token-id' GOOD_RESPONSE = {'access': {'token': {'id': TOKEN_ID, 'tenant': {'id': 'TENANT_ID'}}}} EMPTY_RESPONSE = {} class FakeResponse(object): reason = "Test Reason" def __init__(self, json, status_code=400): self._json = json self.status_code = status_code def json(self): return self._json class FakeApp(object): """This represents a WSGI app protected by the auth_token middleware.""" def __call__(self, env, start_response): resp = webob.Response() resp.environ = env return resp(env, start_response) class EC2TokenMiddlewareTestBase(utils.TestCase): TEST_PROTOCOL = 'https' TEST_HOST = 'fakehost' TEST_PORT = 35357 TEST_URL = '%s://%s:%d/v2.0/ec2tokens' % (TEST_PROTOCOL, TEST_HOST, TEST_PORT) def setUp(self): super(EC2TokenMiddlewareTestBase, self).setUp() self.middleware = ec2_token.EC2Token(FakeApp(), {}) def _validate_ec2_error(self, response, http_status, ec2_code): self.assertEqual(http_status, response.status_code, 'Expected HTTP status %s' % http_status) error_msg = '%s' % ec2_code if six.PY3: # encode error message like main code error_msg = error_msg.encode() self.assertIn(error_msg, response.body) class EC2TokenMiddlewareTestGood(EC2TokenMiddlewareTestBase): @mock.patch.object( requests, 'request', return_value=FakeResponse(GOOD_RESPONSE, status_code=200)) def test_protocol_old_versions(self, mock_request): req = webob.Request.blank('/test') req.GET['Signature'] = 'test-signature' req.GET['AWSAccessKeyId'] = 'test-key-id' req.body = b'Action=ListUsers&Version=2010-05-08' resp = req.get_response(self.middleware) self.assertEqual(200, resp.status_code) self.assertEqual(TOKEN_ID, req.headers['X-Auth-Token']) mock_request.assert_called_with( 'POST', 'http://localhost:5000/v2.0/ec2tokens', data=mock.ANY, headers={'Content-Type': 'application/json'}, verify=True, cert=None) data = jsonutils.loads(mock_request.call_args[1]['data']) expected_data = { 'ec2Credentials': { 'access': 'test-key-id', 'headers': {'Host': 'localhost:80', 'Content-Length': '35'}, 'host': 'localhost:80', 'verb': 'GET', 'params': {'AWSAccessKeyId': 'test-key-id'}, 'signature': 'test-signature', 'path': '/test', 'body_hash': 'b6359072c78d70ebee1e81adcbab4f01' 'bf2c23245fa365ef83fe8f1f955085e2'}} self.assertDictEqual(expected_data, data) @mock.patch.object( requests, 'request', return_value=FakeResponse(GOOD_RESPONSE, status_code=200)) def test_protocol_v4(self, mock_request): req = webob.Request.blank('/test') auth_str = ( 'AWS4-HMAC-SHA256' ' Credential=test-key-id/20110909/us-east-1/iam/aws4_request,' ' SignedHeaders=content-type;host;x-amz-date,' ' Signature=test-signature') req.headers['Authorization'] = auth_str req.body = b'Action=ListUsers&Version=2010-05-08' resp = req.get_response(self.middleware) self.assertEqual(200, resp.status_code) self.assertEqual(TOKEN_ID, req.headers['X-Auth-Token']) mock_request.assert_called_with( 'POST', 'http://localhost:5000/v2.0/ec2tokens', data=mock.ANY, headers={'Content-Type': 'application/json'}, verify=True, cert=None) data = jsonutils.loads(mock_request.call_args[1]['data']) expected_data = { 'ec2Credentials': { 'access': 'test-key-id', 'headers': {'Host': 'localhost:80', 'Content-Length': '35', 'Authorization': auth_str}, 'host': 'localhost:80', 'verb': 'GET', 'params': {}, 'signature': 'test-signature', 'path': '/test', 'body_hash': 'b6359072c78d70ebee1e81adcbab4f01' 'bf2c23245fa365ef83fe8f1f955085e2'}} self.assertDictEqual(expected_data, data) class EC2TokenMiddlewareTestBad(EC2TokenMiddlewareTestBase): def test_no_signature(self): req = webob.Request.blank('/test') resp = req.get_response(self.middleware) self._validate_ec2_error(resp, 400, 'AuthFailure') def test_no_key_id(self): req = webob.Request.blank('/test') req.GET['Signature'] = 'test-signature' resp = req.get_response(self.middleware) self._validate_ec2_error(resp, 400, 'AuthFailure') @mock.patch.object(requests, 'request', return_value=FakeResponse(EMPTY_RESPONSE)) def test_communication_failure(self, mock_request): req = webob.Request.blank('/test') req.GET['Signature'] = 'test-signature' req.GET['AWSAccessKeyId'] = 'test-key-id' resp = req.get_response(self.middleware) self._validate_ec2_error(resp, 400, 'AuthFailure') mock_request.assert_called_with('POST', mock.ANY, data=mock.ANY, headers=mock.ANY, verify=mock.ANY, cert=mock.ANY) @mock.patch.object(requests, 'request', return_value=FakeResponse(EMPTY_RESPONSE)) def test_no_result_data(self, mock_request): req = webob.Request.blank('/test') req.GET['Signature'] = 'test-signature' req.GET['AWSAccessKeyId'] = 'test-key-id' resp = req.get_response(self.middleware) self._validate_ec2_error(resp, 400, 'AuthFailure') mock_request.assert_called_with('POST', mock.ANY, data=mock.ANY, headers=mock.ANY, verify=mock.ANY, cert=mock.ANY) keystonemiddleware-4.21.0/keystonemiddleware/tests/unit/test_s3_token_middleware.py0000666000175100017510000002372113225160624031104 0ustar zuulzuul00000000000000# Copyright 2012 OpenStack Foundation # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. import fixtures import mock from oslo_serialization import jsonutils import requests from requests_mock.contrib import fixture as rm_fixture import six from six.moves import urllib from testtools import matchers import webob from keystonemiddleware import s3_token from keystonemiddleware.tests.unit import utils GOOD_RESPONSE = {'access': {'token': {'id': 'TOKEN_ID', 'tenant': {'id': 'TENANT_ID'}}}} class FakeApp(object): """This represents a WSGI app protected by the auth_token middleware.""" def __call__(self, env, start_response): resp = webob.Response() resp.environ = env return resp(env, start_response) class S3TokenMiddlewareTestBase(utils.TestCase): TEST_WWW_AUTHENTICATE_URI = 'https://fakehost/identity' TEST_URL = '%s/v2.0/s3tokens' % (TEST_WWW_AUTHENTICATE_URI, ) def setUp(self): super(S3TokenMiddlewareTestBase, self).setUp() self.conf = { 'www_authenticate_uri': self.TEST_WWW_AUTHENTICATE_URI, } self.requests_mock = self.useFixture(rm_fixture.Fixture()) def start_fake_response(self, status, headers): self.response_status = int(status.split(' ', 1)[0]) self.response_headers = dict(headers) class S3TokenMiddlewareTestGood(S3TokenMiddlewareTestBase): def setUp(self): super(S3TokenMiddlewareTestGood, self).setUp() self.middleware = s3_token.S3Token(FakeApp(), self.conf) self.requests_mock.post(self.TEST_URL, status_code=201, json=GOOD_RESPONSE) # Ignore the request and pass to the next middleware in the # pipeline if no path has been specified. def test_no_path_request(self): req = webob.Request.blank('/') self.middleware(req.environ, self.start_fake_response) self.assertEqual(self.response_status, 200) # Ignore the request and pass to the next middleware in the # pipeline if no Authorization header has been specified def test_without_authorization(self): req = webob.Request.blank('/v1/AUTH_cfa/c/o') self.middleware(req.environ, self.start_fake_response) self.assertEqual(self.response_status, 200) def test_without_auth_storage_token(self): req = webob.Request.blank('/v1/AUTH_cfa/c/o') req.headers['Authorization'] = 'badboy' self.middleware(req.environ, self.start_fake_response) self.assertEqual(self.response_status, 200) def test_authorized(self): req = webob.Request.blank('/v1/AUTH_cfa/c/o') req.headers['Authorization'] = 'access:signature' req.headers['X-Storage-Token'] = 'token' req.get_response(self.middleware) self.assertTrue(req.path.startswith('/v1/AUTH_TENANT_ID')) self.assertEqual(req.headers['X-Auth-Token'], 'TOKEN_ID') def test_authorized_http(self): protocol = 'http' host = 'fakehost' port = 35357 self.requests_mock.post( '%s://%s:%s/v2.0/s3tokens' % (protocol, host, port), status_code=201, json=GOOD_RESPONSE) self.middleware = ( s3_token.filter_factory({'auth_protocol': protocol, 'auth_host': host, 'auth_port': port})(FakeApp())) req = webob.Request.blank('/v1/AUTH_cfa/c/o') req.headers['Authorization'] = 'access:signature' req.headers['X-Storage-Token'] = 'token' req.get_response(self.middleware) self.assertTrue(req.path.startswith('/v1/AUTH_TENANT_ID')) self.assertEqual(req.headers['X-Auth-Token'], 'TOKEN_ID') def test_authorization_nova_toconnect(self): req = webob.Request.blank('/v1/AUTH_swiftint/c/o') req.headers['Authorization'] = 'access:FORCED_TENANT_ID:signature' req.headers['X-Storage-Token'] = 'token' req.get_response(self.middleware) path = req.environ['PATH_INFO'] self.assertTrue(path.startswith('/v1/AUTH_FORCED_TENANT_ID')) @mock.patch.object(requests, 'post') def test_insecure(self, MOCK_REQUEST): self.middleware = ( s3_token.filter_factory({'insecure': 'True'})(FakeApp())) text_return_value = jsonutils.dumps(GOOD_RESPONSE) if six.PY3: text_return_value = text_return_value.encode() MOCK_REQUEST.return_value = utils.TestResponse({ 'status_code': 201, 'text': text_return_value}) req = webob.Request.blank('/v1/AUTH_cfa/c/o') req.headers['Authorization'] = 'access:signature' req.headers['X-Storage-Token'] = 'token' req.get_response(self.middleware) self.assertTrue(MOCK_REQUEST.called) mock_args, mock_kwargs = MOCK_REQUEST.call_args self.assertIs(mock_kwargs['verify'], False) def test_insecure_option(self): # insecure is passed as a string. # Some non-secure values. true_values = ['true', 'True', '1', 'yes'] for val in true_values: config = {'insecure': val, 'certfile': 'false_ind'} middleware = s3_token.filter_factory(config)(FakeApp()) self.assertIs(False, middleware._verify) # Some "secure" values, including unexpected value. false_values = ['false', 'False', '0', 'no', 'someweirdvalue'] for val in false_values: config = {'insecure': val, 'certfile': 'false_ind'} middleware = s3_token.filter_factory(config)(FakeApp()) self.assertEqual('false_ind', middleware._verify) # Default is secure. config = {'certfile': 'false_ind'} middleware = s3_token.filter_factory(config)(FakeApp()) self.assertIs('false_ind', middleware._verify) def test_unicode_path(self): url = u'/v1/AUTH_cfa/c/euro\u20ac'.encode('utf8') req = webob.Request.blank(urllib.parse.quote(url)) req.headers['Authorization'] = 'access:signature' req.headers['X-Storage-Token'] = 'token' req.get_response(self.middleware) class S3TokenMiddlewareTestBad(S3TokenMiddlewareTestBase): def setUp(self): super(S3TokenMiddlewareTestBad, self).setUp() self.middleware = s3_token.S3Token(FakeApp(), self.conf) def test_unauthorized_token(self): ret = {"error": {"message": "EC2 access key not found.", "code": 401, "title": "Unauthorized"}} self.requests_mock.post(self.TEST_URL, status_code=403, json=ret) req = webob.Request.blank('/v1/AUTH_cfa/c/o') req.headers['Authorization'] = 'access:signature' req.headers['X-Storage-Token'] = 'token' resp = req.get_response(self.middleware) s3_denied_req = self.middleware._deny_request('AccessDenied') self.assertEqual(resp.body, s3_denied_req.body) self.assertEqual(resp.status_int, s3_denied_req.status_int) def test_bogus_authorization(self): req = webob.Request.blank('/v1/AUTH_cfa/c/o') req.headers['Authorization'] = 'badboy' req.headers['X-Storage-Token'] = 'token' resp = req.get_response(self.middleware) self.assertEqual(resp.status_int, 400) s3_invalid_req = self.middleware._deny_request('InvalidURI') self.assertEqual(resp.body, s3_invalid_req.body) self.assertEqual(resp.status_int, s3_invalid_req.status_int) def test_fail_to_connect_to_keystone(self): with mock.patch.object(self.middleware, '_json_request') as o: s3_invalid_req = self.middleware._deny_request('InvalidURI') o.side_effect = s3_token.ServiceError(s3_invalid_req) req = webob.Request.blank('/v1/AUTH_cfa/c/o') req.headers['Authorization'] = 'access:signature' req.headers['X-Storage-Token'] = 'token' resp = req.get_response(self.middleware) self.assertEqual(resp.body, s3_invalid_req.body) self.assertEqual(resp.status_int, s3_invalid_req.status_int) def test_bad_reply(self): self.requests_mock.post(self.TEST_URL, status_code=201, text="") req = webob.Request.blank('/v1/AUTH_cfa/c/o') req.headers['Authorization'] = 'access:signature' req.headers['X-Storage-Token'] = 'token' resp = req.get_response(self.middleware) s3_invalid_req = self.middleware._deny_request('InvalidURI') self.assertEqual(resp.body, s3_invalid_req.body) self.assertEqual(resp.status_int, s3_invalid_req.status_int) class S3TokenMiddlewareTestDeprecatedOptions(S3TokenMiddlewareTestBase): def setUp(self): super(S3TokenMiddlewareTestDeprecatedOptions, self).setUp() self.conf = { 'auth_uri': self.TEST_WWW_AUTHENTICATE_URI, } self.logger = self.useFixture(fixtures.FakeLogger()) self.middleware = s3_token.S3Token(FakeApp(), self.conf) self.requests_mock.post(self.TEST_URL, status_code=201, json=GOOD_RESPONSE) def test_logs_warning(self): req = webob.Request.blank('/') self.middleware(req.environ, self.start_fake_response) self.assertEqual(self.response_status, 200) log = "Use of the auth_uri option was deprecated in the Queens " \ "release in favor of www_authenticate_uri." self.assertThat(self.logger.output, matchers.Contains(log)) keystonemiddleware-4.21.0/keystonemiddleware/tests/unit/utils.py0000666000175100017510000001330213225160624025255 0ustar zuulzuul00000000000000# Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. import sys import time import uuid import warnings import fixtures import mock from oslo_log import log as logging import oslotest.base as oslotest import requests import webob import webtest class BaseTestCase(oslotest.BaseTestCase): def setUp(self): super(BaseTestCase, self).setUp() # If keystonemiddleware calls any deprecated function this will raise # an exception. warnings.filterwarnings('error', category=DeprecationWarning, module='^keystonemiddleware\\.') self.addCleanup(warnings.resetwarnings) self.logger = self.useFixture(fixtures.FakeLogger(level=logging.DEBUG)) class TestCase(BaseTestCase): TEST_DOMAIN_ID = '1' TEST_DOMAIN_NAME = 'aDomain' TEST_GROUP_ID = uuid.uuid4().hex TEST_ROLE_ID = uuid.uuid4().hex TEST_TENANT_ID = '1' TEST_TENANT_NAME = 'aTenant' TEST_TOKEN = 'aToken' TEST_TRUST_ID = 'aTrust' TEST_USER = 'test' TEST_USER_ID = uuid.uuid4().hex TEST_ROOT_URL = 'http://127.0.0.1:5000/' def setUp(self): super(TestCase, self).setUp() self.time_patcher = mock.patch.object(time, 'time', lambda: 1234) self.time_patcher.start() def tearDown(self): self.time_patcher.stop() super(TestCase, self).tearDown() if tuple(sys.version_info)[0:2] < (2, 7): def assertDictEqual(self, d1, d2, msg=None): # Simple version taken from 2.7 self.assertIsInstance(d1, dict, 'First argument is not a dictionary') self.assertIsInstance(d2, dict, 'Second argument is not a dictionary') if d1 != d2: if msg: self.fail(msg) else: standardMsg = '%r != %r' % (d1, d2) self.fail(standardMsg) TestCase.assertDictEqual = assertDictEqual class MiddlewareTestCase(BaseTestCase): def create_middleware(self, cb, **kwargs): raise NotImplemented("implement this in your tests") def create_simple_middleware(self, status='200 OK', body='', headers=None, **kwargs): def cb(req): resp = webob.Response(body, status) resp.headers.update(headers or {}) return resp return self.create_middleware(cb, **kwargs) def create_app(self, *args, **kwargs): return webtest.TestApp(self.create_middleware(*args, **kwargs)) def create_simple_app(self, *args, **kwargs): return webtest.TestApp(self.create_simple_middleware(*args, **kwargs)) class TestResponse(requests.Response): """Utility class to wrap requests.Response. Class used to wrap requests.Response and provide some convenience to initialize with a dict. """ def __init__(self, data): self._text = None super(TestResponse, self).__init__() if isinstance(data, dict): self.status_code = data.get('status_code', 200) headers = data.get('headers') if headers: self.headers.update(headers) # Fake the text attribute to streamline Response creation # _content is defined by requests.Response self._content = data.get('text') else: self.status_code = data def __eq__(self, other): """Test if the response is equivalent to another response. This works by comparing the attribute dictionaries of both TestResponse instances. """ return self.__dict__ == other.__dict__ @property def text(self): return self.content class DisableModuleFixture(fixtures.Fixture): """A fixture to provide support for unloading/disabling modules.""" def __init__(self, module, *args, **kw): super(DisableModuleFixture, self).__init__(*args, **kw) self.module = module self._finders = [] self._cleared_modules = {} def tearDown(self): super(DisableModuleFixture, self).tearDown() for finder in self._finders: sys.meta_path.remove(finder) sys.modules.update(self._cleared_modules) def clear_module(self): cleared_modules = {} for fullname in list(sys.modules.keys()): if (fullname == self.module or fullname.startswith(self.module + '.')): cleared_modules[fullname] = sys.modules.pop(fullname) return cleared_modules def setUp(self): """Ensure ImportError for the specified module.""" super(DisableModuleFixture, self).setUp() # Clear 'module' references in sys.modules self._cleared_modules.update(self.clear_module()) finder = NoModuleFinder(self.module) self._finders.append(finder) sys.meta_path.insert(0, finder) class NoModuleFinder(object): """Disallow further imports of 'module'.""" def __init__(self, module): self.module = module def find_module(self, fullname, path): if fullname == self.module or fullname.startswith(self.module + '.'): raise ImportError keystonemiddleware-4.21.0/keystonemiddleware/tests/unit/test_opts.py0000666000175100017510000001152413225160624026145 0ustar zuulzuul00000000000000# Copyright (c) 2014 OpenStack Foundation. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. import stevedore from testtools import matchers from keystonemiddleware.auth_token import _opts as new_opts from keystonemiddleware import opts as old_opts from keystonemiddleware.tests.unit import utils class OptsTestCase(utils.TestCase): def test_original_list_all_options(self): result_of_old_opts = old_opts.list_auth_token_opts() self.assertThat(result_of_old_opts, matchers.HasLength(1)) for group in (g for (g, _l) in result_of_old_opts): self.assertEqual('keystone_authtoken', group) # This is the original list that includes deprecated options expected_opt_names = [ 'auth_admin_prefix', 'auth_host', 'auth_port', 'auth_protocol', 'www_authenticate_uri', 'auth_uri', 'identity_uri', 'auth_version', 'delay_auth_decision', 'http_connect_timeout', 'http_request_max_retries', 'admin_token', 'admin_user', 'admin_password', 'admin_tenant_name', 'cache', 'certfile', 'keyfile', 'cafile', 'region_name', 'insecure', 'signing_dir', 'memcached_servers', 'token_cache_time', 'revocation_cache_time', 'memcache_security_strategy', 'memcache_secret_key', 'memcache_use_advanced_pool', 'memcache_pool_dead_retry', 'memcache_pool_maxsize', 'memcache_pool_unused_timeout', 'memcache_pool_conn_get_timeout', 'memcache_pool_socket_timeout', 'include_service_catalog', 'enforce_token_bind', 'check_revocations_for_cached', 'hash_algorithms', 'auth_type', 'auth_section', 'service_token_roles', 'service_token_roles_required', ] opt_names = [o.name for (g, l) in result_of_old_opts for o in l] self.assertThat(opt_names, matchers.HasLength(len(expected_opt_names))) for opt in opt_names: self.assertIn(opt, expected_opt_names) def _test_list_auth_token_opts(self, result): self.assertThat(result, matchers.HasLength(1)) for group in (g for (g, _l) in result): self.assertEqual('keystone_authtoken', group) # This is the sample config generator list WITHOUT deprecations expected_opt_names = [ 'www_authenticate_uri', 'auth_uri', 'auth_version', 'delay_auth_decision', 'http_connect_timeout', 'http_request_max_retries', 'cache', 'certfile', 'keyfile', 'cafile', 'region_name', 'insecure', 'signing_dir', 'memcached_servers', 'token_cache_time', 'revocation_cache_time', 'memcache_security_strategy', 'memcache_secret_key', 'memcache_use_advanced_pool', 'memcache_pool_dead_retry', 'memcache_pool_maxsize', 'memcache_pool_unused_timeout', 'memcache_pool_conn_get_timeout', 'memcache_pool_socket_timeout', 'include_service_catalog', 'enforce_token_bind', 'check_revocations_for_cached', 'hash_algorithms', 'auth_type', 'auth_section', 'service_token_roles', 'service_token_roles_required', ] opt_names = [o.name for (g, l) in result for o in l] self.assertThat(opt_names, matchers.HasLength(len(expected_opt_names))) for opt in opt_names: self.assertIn(opt, expected_opt_names) def test_list_auth_token_opts(self): self._test_list_auth_token_opts(new_opts.list_opts()) def test_entry_point(self): em = stevedore.ExtensionManager('oslo.config.opts', invoke_on_load=True) for extension in em: if extension.name == 'keystonemiddleware.auth_token': break else: self.fail('keystonemiddleware.auth_token not found') self._test_list_auth_token_opts(extension.obj) keystonemiddleware-4.21.0/keystonemiddleware/tests/unit/client_fixtures.py0000666000175100017510000006053513225160624027336 0ustar zuulzuul00000000000000# Copyright 2013 OpenStack Foundation # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. import os import uuid import fixtures from keystoneauth1 import fixture from keystoneclient.common import cms from keystoneclient import utils from oslo_serialization import jsonutils from oslo_utils import timeutils import six import testresources TESTDIR = os.path.dirname(os.path.abspath(__file__)) ROOTDIR = os.path.normpath(os.path.join(TESTDIR, '..', '..', '..')) CERTDIR = os.path.join(ROOTDIR, 'examples', 'pki', 'certs') CMSDIR = os.path.join(ROOTDIR, 'examples', 'pki', 'cms') KEYDIR = os.path.join(ROOTDIR, 'examples', 'pki', 'private') def _hash_signed_token_safe(signed_text, **kwargs): if isinstance(signed_text, six.text_type): signed_text = signed_text.encode('utf-8') return utils.hash_signed_token(signed_text, **kwargs) class Examples(fixtures.Fixture): """Example tokens and certs loaded from the examples directory. To use this class correctly, the module needs to override the test suite class to use testresources.OptimisingTestSuite (otherwise the files will be read on every test). This is done by defining a load_tests function in the module, like this: def load_tests(loader, tests, pattern): return testresources.OptimisingTestSuite(tests) (see http://docs.python.org/2/library/unittest.html#load-tests-protocol ) """ def setUp(self): super(Examples, self).setUp() # The data for several tests are signed using openssl and are stored in # files in the signing subdirectory. In order to keep the values # consistent between the tests and the signed documents, we read them # in for use in the tests. with open(os.path.join(CMSDIR, 'auth_token_scoped.json')) as f: self.TOKEN_SCOPED_DATA = cms.cms_to_token(f.read()) with open(os.path.join(CMSDIR, 'auth_token_scoped.pem')) as f: self.SIGNED_TOKEN_SCOPED = cms.cms_to_token(f.read()) self.SIGNED_TOKEN_SCOPED_HASH = _hash_signed_token_safe( self.SIGNED_TOKEN_SCOPED) self.SIGNED_TOKEN_SCOPED_HASH_SHA256 = _hash_signed_token_safe( self.SIGNED_TOKEN_SCOPED, mode='sha256') with open(os.path.join(CMSDIR, 'auth_token_unscoped.pem')) as f: self.SIGNED_TOKEN_UNSCOPED = cms.cms_to_token(f.read()) with open(os.path.join(CMSDIR, 'auth_v3_token_scoped.pem')) as f: self.SIGNED_v3_TOKEN_SCOPED = cms.cms_to_token(f.read()) self.SIGNED_v3_TOKEN_SCOPED_HASH = _hash_signed_token_safe( self.SIGNED_v3_TOKEN_SCOPED) self.SIGNED_v3_TOKEN_SCOPED_HASH_SHA256 = _hash_signed_token_safe( self.SIGNED_v3_TOKEN_SCOPED, mode='sha256') with open(os.path.join(CMSDIR, 'auth_token_revoked.pem')) as f: self.REVOKED_TOKEN = cms.cms_to_token(f.read()) with open(os.path.join(CMSDIR, 'auth_token_scoped_expired.pem')) as f: self.SIGNED_TOKEN_SCOPED_EXPIRED = cms.cms_to_token(f.read()) with open(os.path.join(CMSDIR, 'auth_v3_token_revoked.pem')) as f: self.REVOKED_v3_TOKEN = cms.cms_to_token(f.read()) with open(os.path.join(CMSDIR, 'auth_token_scoped.pkiz')) as f: self.SIGNED_TOKEN_SCOPED_PKIZ = cms.cms_to_token(f.read()) with open(os.path.join(CMSDIR, 'auth_token_unscoped.pkiz')) as f: self.SIGNED_TOKEN_UNSCOPED_PKIZ = cms.cms_to_token(f.read()) with open(os.path.join(CMSDIR, 'auth_v3_token_scoped.pkiz')) as f: self.SIGNED_v3_TOKEN_SCOPED_PKIZ = cms.cms_to_token(f.read()) with open(os.path.join(CMSDIR, 'auth_token_revoked.pkiz')) as f: self.REVOKED_TOKEN_PKIZ = cms.cms_to_token(f.read()) with open(os.path.join(CMSDIR, 'auth_token_scoped_expired.pkiz')) as f: self.SIGNED_TOKEN_SCOPED_EXPIRED_PKIZ = cms.cms_to_token(f.read()) with open(os.path.join(CMSDIR, 'auth_v3_token_revoked.pkiz')) as f: self.REVOKED_v3_TOKEN_PKIZ = cms.cms_to_token(f.read()) with open(os.path.join(CMSDIR, 'revocation_list.json')) as f: self.REVOCATION_LIST = jsonutils.loads(f.read()) with open(os.path.join(CMSDIR, 'revocation_list.pem')) as f: self.SIGNED_REVOCATION_LIST = jsonutils.dumps({'signed': f.read()}) self.SIGNING_CERT_FILE = os.path.join(CERTDIR, 'signing_cert.pem') with open(self.SIGNING_CERT_FILE) as f: self.SIGNING_CERT = f.read() self.KERBEROS_BIND = 'USER@REALM' self.SERVICE_KERBEROS_BIND = 'SERVICE_USER@SERVICE_REALM' self.SIGNING_KEY_FILE = os.path.join(KEYDIR, 'signing_key.pem') with open(self.SIGNING_KEY_FILE) as f: self.SIGNING_KEY = f.read() self.SIGNING_CA_FILE = os.path.join(CERTDIR, 'cacert.pem') with open(self.SIGNING_CA_FILE) as f: self.SIGNING_CA = f.read() self.UUID_TOKEN_DEFAULT = "ec6c0710ec2f471498484c1b53ab4f9d" self.UUID_TOKEN_NO_SERVICE_CATALOG = '8286720fbe4941e69fa8241723bb02df' self.UUID_TOKEN_UNSCOPED = '731f903721c14827be7b2dc912af7776' self.UUID_TOKEN_BIND = '3fc54048ad64405c98225ce0897af7c5' self.UUID_TOKEN_UNKNOWN_BIND = '8885fdf4d42e4fb9879e6379fa1eaf48' self.VALID_DIABLO_TOKEN = 'b0cf19b55dbb4f20a6ee18e6c6cf1726' self.v3_UUID_TOKEN_DEFAULT = '5603457654b346fdbb93437bfe76f2f1' self.v3_UUID_TOKEN_UNSCOPED = 'd34835fdaec447e695a0a024d84f8d79' self.v3_UUID_TOKEN_DOMAIN_SCOPED = 'e8a7b63aaa4449f38f0c5c05c3581792' self.v3_UUID_TOKEN_BIND = '2f61f73e1c854cbb9534c487f9bd63c2' self.v3_UUID_TOKEN_UNKNOWN_BIND = '7ed9781b62cd4880b8d8c6788ab1d1e2' self.UUID_SERVICE_TOKEN_DEFAULT = 'fe4c0710ec2f492748596c1b53ab124' self.UUID_SERVICE_TOKEN_BIND = '5e43439613d34a13a7e03b2762bd08ab' self.v3_UUID_SERVICE_TOKEN_DEFAULT = 'g431071bbc2f492748596c1b53cb229' self.v3_UUID_SERVICE_TOKEN_BIND = 'be705e4426d0449a89e35ae21c380a05' self.v3_NOT_IS_ADMIN_PROJECT = uuid.uuid4().hex revoked_token = self.REVOKED_TOKEN if isinstance(revoked_token, six.text_type): revoked_token = revoked_token.encode('utf-8') self.REVOKED_TOKEN_HASH = utils.hash_signed_token(revoked_token) self.REVOKED_TOKEN_HASH_SHA256 = utils.hash_signed_token(revoked_token, mode='sha256') self.REVOKED_TOKEN_LIST = ( {'revoked': [{'id': self.REVOKED_TOKEN_HASH, 'expires': timeutils.utcnow()}]}) self.REVOKED_TOKEN_LIST_JSON = jsonutils.dumps(self.REVOKED_TOKEN_LIST) revoked_v3_token = self.REVOKED_v3_TOKEN if isinstance(revoked_v3_token, six.text_type): revoked_v3_token = revoked_v3_token.encode('utf-8') self.REVOKED_v3_TOKEN_HASH = utils.hash_signed_token(revoked_v3_token) hash = utils.hash_signed_token(revoked_v3_token, mode='sha256') self.REVOKED_v3_TOKEN_HASH_SHA256 = hash self.REVOKED_v3_TOKEN_LIST = ( {'revoked': [{'id': self.REVOKED_v3_TOKEN_HASH, 'expires': timeutils.utcnow()}]}) self.REVOKED_v3_TOKEN_LIST_JSON = jsonutils.dumps( self.REVOKED_v3_TOKEN_LIST) revoked_token_pkiz = self.REVOKED_TOKEN_PKIZ if isinstance(revoked_token_pkiz, six.text_type): revoked_token_pkiz = revoked_token_pkiz.encode('utf-8') self.REVOKED_TOKEN_PKIZ_HASH = utils.hash_signed_token( revoked_token_pkiz) revoked_v3_token_pkiz = self.REVOKED_v3_TOKEN_PKIZ if isinstance(revoked_v3_token_pkiz, six.text_type): revoked_v3_token_pkiz = revoked_v3_token_pkiz.encode('utf-8') self.REVOKED_v3_PKIZ_TOKEN_HASH = utils.hash_signed_token( revoked_v3_token_pkiz) self.REVOKED_TOKEN_PKIZ_LIST = ( {'revoked': [{'id': self.REVOKED_TOKEN_PKIZ_HASH, 'expires': timeutils.utcnow()}, {'id': self.REVOKED_v3_PKIZ_TOKEN_HASH, 'expires': timeutils.utcnow()}, ]}) self.REVOKED_TOKEN_PKIZ_LIST_JSON = jsonutils.dumps( self.REVOKED_TOKEN_PKIZ_LIST) self.SIGNED_TOKEN_SCOPED_KEY = cms.cms_hash_token( self.SIGNED_TOKEN_SCOPED) self.SIGNED_TOKEN_UNSCOPED_KEY = cms.cms_hash_token( self.SIGNED_TOKEN_UNSCOPED) self.SIGNED_v3_TOKEN_SCOPED_KEY = cms.cms_hash_token( self.SIGNED_v3_TOKEN_SCOPED) self.SIGNED_TOKEN_SCOPED_PKIZ_KEY = cms.cms_hash_token( self.SIGNED_TOKEN_SCOPED_PKIZ) self.SIGNED_TOKEN_UNSCOPED_PKIZ_KEY = cms.cms_hash_token( self.SIGNED_TOKEN_UNSCOPED_PKIZ) self.SIGNED_v3_TOKEN_SCOPED_PKIZ_KEY = cms.cms_hash_token( self.SIGNED_v3_TOKEN_SCOPED_PKIZ) self.INVALID_SIGNED_TOKEN = ( "MIIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" "BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB" "CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC" "DDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDD" "EEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEE" "FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF" "0000000000000000000000000000000000000000000000000000000000000000" "1111111111111111111111111111111111111111111111111111111111111111" "2222222222222222222222222222222222222222222222222222222222222222" "3333333333333333333333333333333333333333333333333333333333333333" "4444444444444444444444444444444444444444444444444444444444444444" "5555555555555555555555555555555555555555555555555555555555555555" "6666666666666666666666666666666666666666666666666666666666666666" "7777777777777777777777777777777777777777777777777777777777777777" "8888888888888888888888888888888888888888888888888888888888888888" "9999999999999999999999999999999999999999999999999999999999999999" "0000000000000000000000000000000000000000000000000000000000000000") self.INVALID_SIGNED_PKIZ_TOKEN = ( "PKIZ_AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" "BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB" "CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC" "DDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDD" "EEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEE" "FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF" "0000000000000000000000000000000000000000000000000000000000000000" "1111111111111111111111111111111111111111111111111111111111111111" "2222222222222222222222222222222222222222222222222222222222222222" "3333333333333333333333333333333333333333333333333333333333333333" "4444444444444444444444444444444444444444444444444444444444444444" "5555555555555555555555555555555555555555555555555555555555555555" "6666666666666666666666666666666666666666666666666666666666666666" "7777777777777777777777777777777777777777777777777777777777777777" "8888888888888888888888888888888888888888888888888888888888888888" "9999999999999999999999999999999999999999999999999999999999999999" "0000000000000000000000000000000000000000000000000000000000000000") # JSON responses keyed by token ID self.TOKEN_RESPONSES = {} # basic values PROJECT_ID = 'tenant_id1' PROJECT_NAME = 'tenant_name1' USER_ID = 'user_id1' USER_NAME = 'user_name1' DOMAIN_ID = 'domain_id1' DOMAIN_NAME = 'domain_name1' ROLE_NAME1 = 'role1' ROLE_NAME2 = 'role2' SERVICE_PROJECT_ID = 'service_project_id1' SERVICE_PROJECT_NAME = 'service_project_name1' SERVICE_USER_ID = 'service_user_id1' SERVICE_USER_NAME = 'service_user_name1' SERVICE_DOMAIN_ID = 'service_domain_id1' SERVICE_DOMAIN_NAME = 'service_domain_name1' SERVICE_ROLE_NAME1 = 'service' SERVICE_ROLE_NAME2 = 'service_role2' self.SERVICE_TYPE = 'identity' self.UNVERSIONED_SERVICE_URL = 'https://keystone.example.com:1234/' self.SERVICE_URL = self.UNVERSIONED_SERVICE_URL + 'v2.0' # Old Tokens self.TOKEN_RESPONSES[self.VALID_DIABLO_TOKEN] = { 'access': { 'token': { 'id': self.VALID_DIABLO_TOKEN, 'expires': '2020-01-01T00:00:10.000123Z', 'tenantId': PROJECT_ID, }, 'user': { 'id': USER_ID, 'name': USER_NAME, 'roles': [ {'name': ROLE_NAME1}, {'name': ROLE_NAME2}, ], }, }, } # Generated V2 Tokens token = fixture.V2Token(token_id=self.UUID_TOKEN_DEFAULT, tenant_id=PROJECT_ID, tenant_name=PROJECT_NAME, user_id=USER_ID, user_name=USER_NAME) token.add_role(name=ROLE_NAME1) token.add_role(name=ROLE_NAME2) svc = token.add_service(self.SERVICE_TYPE) svc.add_endpoint(public=self.SERVICE_URL) self.TOKEN_RESPONSES[self.UUID_TOKEN_DEFAULT] = token token = fixture.V2Token(token_id=self.UUID_TOKEN_UNSCOPED, user_id=USER_ID, user_name=USER_NAME) self.TOKEN_RESPONSES[self.UUID_TOKEN_UNSCOPED] = token token = fixture.V2Token(token_id='valid-token', tenant_id=PROJECT_ID, tenant_name=PROJECT_NAME, user_id=USER_ID, user_name=USER_NAME) token.add_role(ROLE_NAME1) token.add_role(ROLE_NAME2) self.TOKEN_RESPONSES[self.UUID_TOKEN_NO_SERVICE_CATALOG] = token token = fixture.V2Token(token_id=self.SIGNED_TOKEN_SCOPED_KEY, tenant_id=PROJECT_ID, tenant_name=PROJECT_NAME, user_id=USER_ID, user_name=USER_NAME) token.add_role(ROLE_NAME1) token.add_role(ROLE_NAME2) self.TOKEN_RESPONSES[self.SIGNED_TOKEN_SCOPED_KEY] = token token = fixture.V2Token(token_id=self.SIGNED_TOKEN_UNSCOPED_KEY, user_id=USER_ID, user_name=USER_NAME) self.TOKEN_RESPONSES[self.SIGNED_TOKEN_UNSCOPED_KEY] = token token = fixture.V2Token(token_id=self.UUID_TOKEN_BIND, tenant_id=PROJECT_ID, tenant_name=PROJECT_NAME, user_id=USER_ID, user_name=USER_NAME) token.add_role(ROLE_NAME1) token.add_role(ROLE_NAME2) token['access']['token']['bind'] = {'kerberos': self.KERBEROS_BIND} self.TOKEN_RESPONSES[self.UUID_TOKEN_BIND] = token token = fixture.V2Token(token_id=self.UUID_SERVICE_TOKEN_BIND, tenant_id=SERVICE_PROJECT_ID, tenant_name=SERVICE_PROJECT_NAME, user_id=SERVICE_USER_ID, user_name=SERVICE_USER_NAME) token.add_role(SERVICE_ROLE_NAME1) token.add_role(SERVICE_ROLE_NAME2) token['access']['token']['bind'] = { 'kerberos': self.SERVICE_KERBEROS_BIND} self.TOKEN_RESPONSES[self.UUID_SERVICE_TOKEN_BIND] = token token = fixture.V2Token(token_id=self.UUID_TOKEN_UNKNOWN_BIND, tenant_id=PROJECT_ID, tenant_name=PROJECT_NAME, user_id=USER_ID, user_name=USER_NAME) token.add_role(ROLE_NAME1) token.add_role(ROLE_NAME2) token['access']['token']['bind'] = {'FOO': 'BAR'} self.TOKEN_RESPONSES[self.UUID_TOKEN_UNKNOWN_BIND] = token token = fixture.V2Token(token_id=self.UUID_SERVICE_TOKEN_DEFAULT, tenant_id=SERVICE_PROJECT_ID, tenant_name=SERVICE_PROJECT_NAME, user_id=SERVICE_USER_ID, user_name=SERVICE_USER_NAME) token.add_role(name=SERVICE_ROLE_NAME1) token.add_role(name=SERVICE_ROLE_NAME2) svc = token.add_service(self.SERVICE_TYPE) svc.add_endpoint(public=self.SERVICE_URL) self.TOKEN_RESPONSES[self.UUID_SERVICE_TOKEN_DEFAULT] = token # Generated V3 Tokens token = fixture.V3Token(user_id=USER_ID, user_name=USER_NAME, user_domain_id=DOMAIN_ID, user_domain_name=DOMAIN_NAME, project_id=PROJECT_ID, project_name=PROJECT_NAME, project_domain_id=DOMAIN_ID, project_domain_name=DOMAIN_NAME) token.add_role(id=ROLE_NAME1, name=ROLE_NAME1) token.add_role(id=ROLE_NAME2, name=ROLE_NAME2) svc = token.add_service(self.SERVICE_TYPE) svc.add_endpoint('public', self.SERVICE_URL) self.TOKEN_RESPONSES[self.v3_UUID_TOKEN_DEFAULT] = token token = fixture.V3Token(user_id=USER_ID, user_name=USER_NAME, user_domain_id=DOMAIN_ID, user_domain_name=DOMAIN_NAME) self.TOKEN_RESPONSES[self.v3_UUID_TOKEN_UNSCOPED] = token token = fixture.V3Token(user_id=USER_ID, user_name=USER_NAME, user_domain_id=DOMAIN_ID, user_domain_name=DOMAIN_NAME, domain_id=DOMAIN_ID, domain_name=DOMAIN_NAME) token.add_role(id=ROLE_NAME1, name=ROLE_NAME1) token.add_role(id=ROLE_NAME2, name=ROLE_NAME2) svc = token.add_service(self.SERVICE_TYPE) svc.add_endpoint('public', self.SERVICE_URL) self.TOKEN_RESPONSES[self.v3_UUID_TOKEN_DOMAIN_SCOPED] = token token = fixture.V3Token(user_id=USER_ID, user_name=USER_NAME, user_domain_id=DOMAIN_ID, user_domain_name=DOMAIN_NAME, project_id=PROJECT_ID, project_name=PROJECT_NAME, project_domain_id=DOMAIN_ID, project_domain_name=DOMAIN_NAME) token.add_role(name=ROLE_NAME1) token.add_role(name=ROLE_NAME2) svc = token.add_service(self.SERVICE_TYPE) svc.add_endpoint('public', self.SERVICE_URL) self.TOKEN_RESPONSES[self.SIGNED_v3_TOKEN_SCOPED_KEY] = token token = fixture.V3Token(user_id=USER_ID, user_name=USER_NAME, user_domain_id=DOMAIN_ID, user_domain_name=DOMAIN_NAME, project_id=PROJECT_ID, project_name=PROJECT_NAME, project_domain_id=DOMAIN_ID, project_domain_name=DOMAIN_NAME) token.add_role(name=ROLE_NAME1) token.add_role(name=ROLE_NAME2) svc = token.add_service(self.SERVICE_TYPE) svc.add_endpoint('public', self.SERVICE_URL) token['token']['bind'] = {'kerberos': self.KERBEROS_BIND} self.TOKEN_RESPONSES[self.v3_UUID_TOKEN_BIND] = token token = fixture.V3Token(user_id=SERVICE_USER_ID, user_name=SERVICE_USER_NAME, user_domain_id=SERVICE_DOMAIN_ID, user_domain_name=SERVICE_DOMAIN_NAME, project_id=SERVICE_PROJECT_ID, project_name=SERVICE_PROJECT_NAME, project_domain_id=SERVICE_DOMAIN_ID, project_domain_name=SERVICE_DOMAIN_NAME) token.add_role(name=SERVICE_ROLE_NAME1) token.add_role(name=SERVICE_ROLE_NAME2) svc = token.add_service(self.SERVICE_TYPE) svc.add_endpoint('public', self.SERVICE_URL) token['token']['bind'] = {'kerberos': self.SERVICE_KERBEROS_BIND} self.TOKEN_RESPONSES[self.v3_UUID_SERVICE_TOKEN_BIND] = token token = fixture.V3Token(user_id=USER_ID, user_name=USER_NAME, user_domain_id=DOMAIN_ID, user_domain_name=DOMAIN_NAME, project_id=PROJECT_ID, project_name=PROJECT_NAME, project_domain_id=DOMAIN_ID, project_domain_name=DOMAIN_NAME) token.add_role(name=ROLE_NAME1) token.add_role(name=ROLE_NAME2) svc = token.add_service(self.SERVICE_TYPE) svc.add_endpoint('public', self.SERVICE_URL) token['token']['bind'] = {'FOO': 'BAR'} self.TOKEN_RESPONSES[self.v3_UUID_TOKEN_UNKNOWN_BIND] = token token = fixture.V3Token(user_id=SERVICE_USER_ID, user_name=SERVICE_USER_NAME, user_domain_id=SERVICE_DOMAIN_ID, user_domain_name=SERVICE_DOMAIN_NAME, project_id=SERVICE_PROJECT_ID, project_name=SERVICE_PROJECT_NAME, project_domain_id=SERVICE_DOMAIN_ID, project_domain_name=SERVICE_DOMAIN_NAME) token.add_role(id=SERVICE_ROLE_NAME1, name=SERVICE_ROLE_NAME1) token.add_role(id=SERVICE_ROLE_NAME2, name=SERVICE_ROLE_NAME2) svc = token.add_service(self.SERVICE_TYPE) svc.add_endpoint('public', self.SERVICE_URL) self.TOKEN_RESPONSES[self.v3_UUID_SERVICE_TOKEN_DEFAULT] = token token = fixture.V3Token(user_id=USER_ID, user_name=USER_NAME, user_domain_id=DOMAIN_ID, user_domain_name=DOMAIN_NAME, project_id=PROJECT_ID, project_name=PROJECT_NAME, project_domain_id=DOMAIN_ID, project_domain_name=DOMAIN_NAME, is_admin_project=False) token.add_role(name=ROLE_NAME1) token.add_role(name=ROLE_NAME2) svc = token.add_service(self.SERVICE_TYPE) svc.add_endpoint('public', self.SERVICE_URL) self.TOKEN_RESPONSES[self.v3_NOT_IS_ADMIN_PROJECT] = token # PKIZ tokens generally link to above tokens self.TOKEN_RESPONSES[self.SIGNED_TOKEN_SCOPED_PKIZ_KEY] = ( self.TOKEN_RESPONSES[self.SIGNED_TOKEN_SCOPED_KEY]) self.TOKEN_RESPONSES[self.SIGNED_TOKEN_UNSCOPED_PKIZ_KEY] = ( self.TOKEN_RESPONSES[self.SIGNED_TOKEN_UNSCOPED_KEY]) self.TOKEN_RESPONSES[self.SIGNED_v3_TOKEN_SCOPED_PKIZ_KEY] = ( self.TOKEN_RESPONSES[self.SIGNED_v3_TOKEN_SCOPED_KEY]) self.JSON_TOKEN_RESPONSES = dict([(k, jsonutils.dumps(v)) for k, v in self.TOKEN_RESPONSES.items()]) EXAMPLES_RESOURCE = testresources.FixtureResource(Examples()) keystonemiddleware-4.21.0/keystonemiddleware/audit/0000775000175100017510000000000013225161130022520 5ustar zuulzuul00000000000000keystonemiddleware-4.21.0/keystonemiddleware/audit/_api.py0000666000175100017510000002727413225160624024027 0ustar zuulzuul00000000000000# Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. import collections import re from oslo_log import log as logging from oslo_serialization import jsonutils from pycadf import cadftaxonomy as taxonomy from pycadf import cadftype from pycadf import credential from pycadf import endpoint from pycadf import eventfactory as factory from pycadf import host from pycadf import identifier from pycadf import resource from pycadf import tag import six from six.moves import configparser from six.moves.urllib import parse as urlparse # NOTE(blk-u): Compatibility for Python 2. SafeConfigParser and # SafeConfigParser.readfp are deprecated in Python 3. Remove this when we drop # support for Python 2. if six.PY2: class _ConfigParser(configparser.SafeConfigParser): read_file = configparser.SafeConfigParser.readfp else: _ConfigParser = configparser.ConfigParser Service = collections.namedtuple('Service', ['id', 'name', 'type', 'admin_endp', 'public_endp', 'private_endp']) AuditMap = collections.namedtuple('AuditMap', ['path_kw', 'custom_actions', 'service_endpoints', 'default_target_endpoint_type']) class PycadfAuditApiConfigError(Exception): """Error raised when pyCADF fails to configure correctly.""" pass class ClientResource(resource.Resource): def __init__(self, project_id=None, **kwargs): super(ClientResource, self).__init__(**kwargs) if project_id is not None: self.project_id = project_id class KeystoneCredential(credential.Credential): def __init__(self, identity_status=None, **kwargs): super(KeystoneCredential, self).__init__(**kwargs) if identity_status is not None: self.identity_status = identity_status class OpenStackAuditApi(object): def __init__(self, cfg_file, log=logging.getLogger(__name__)): """Configure to recognize and map known api paths.""" path_kw = {} custom_actions = {} endpoints = {} default_target_endpoint_type = None if cfg_file: try: map_conf = _ConfigParser() map_conf.read_file(open(cfg_file)) try: default_target_endpoint_type = map_conf.get( 'DEFAULT', 'target_endpoint_type') except configparser.NoOptionError: # nosec # Ignore the undefined config option, # default_target_endpoint_type remains None which is valid. pass try: custom_actions = dict(map_conf.items('custom_actions')) except configparser.Error: # nosec # custom_actions remains {} which is valid. pass try: path_kw = dict(map_conf.items('path_keywords')) except configparser.Error: # nosec # path_kw remains {} which is valid. pass try: endpoints = dict(map_conf.items('service_endpoints')) except configparser.Error: # nosec # endpoints remains {} which is valid. pass except configparser.ParsingError as err: raise PycadfAuditApiConfigError( 'Error parsing audit map file: %s' % err) self._log = log self._MAP = AuditMap( path_kw=path_kw, custom_actions=custom_actions, service_endpoints=endpoints, default_target_endpoint_type=default_target_endpoint_type) @staticmethod def _clean_path(value): """Clean path if path has json suffix.""" return value[:-5] if value.endswith('.json') else value def get_action(self, req): """Take a given Request, parse url path to calculate action type. Depending on req.method: if POST: - path ends with 'action', read the body and use as action; - path ends with known custom_action, take action from config; - request ends with known path, assume is create action; - request ends with unknown path, assume is update action. if GET: - request ends with known path, assume is list action; - request ends with unknown path, assume is read action. if PUT, assume update action. if DELETE, assume delete action. if HEAD, assume read action. """ path = req.path[:-1] if req.path.endswith('/') else req.path url_ending = self._clean_path(path[path.rfind('/') + 1:]) method = req.method if url_ending + '/' + method.lower() in self._MAP.custom_actions: action = self._MAP.custom_actions[url_ending + '/' + method.lower()] elif url_ending in self._MAP.custom_actions: action = self._MAP.custom_actions[url_ending] elif method == 'POST': if url_ending == 'action': try: if req.json: body_action = list(req.json.keys())[0] action = taxonomy.ACTION_UPDATE + '/' + body_action else: action = taxonomy.ACTION_CREATE except ValueError: action = taxonomy.ACTION_CREATE elif url_ending not in self._MAP.path_kw: action = taxonomy.ACTION_UPDATE else: action = taxonomy.ACTION_CREATE elif method == 'GET': if url_ending in self._MAP.path_kw: action = taxonomy.ACTION_LIST else: action = taxonomy.ACTION_READ elif method == 'PUT' or method == 'PATCH': action = taxonomy.ACTION_UPDATE elif method == 'DELETE': action = taxonomy.ACTION_DELETE elif method == 'HEAD': action = taxonomy.ACTION_READ else: action = taxonomy.UNKNOWN return action def _get_service_info(self, endp): service = Service( type=self._MAP.service_endpoints.get( endp['type'], taxonomy.UNKNOWN), name=endp['name'], id=endp['endpoints'][0].get('id', endp['name']), admin_endp=endpoint.Endpoint( name='admin', url=endp['endpoints'][0].get('adminURL', taxonomy.UNKNOWN)), private_endp=endpoint.Endpoint( name='private', url=endp['endpoints'][0].get('internalURL', taxonomy.UNKNOWN)), public_endp=endpoint.Endpoint( name='public', url=endp['endpoints'][0].get('publicURL', taxonomy.UNKNOWN))) return service def _build_typeURI(self, req, service_type): """Build typeURI of target. Combines service type and corresponding path for greater detail. """ type_uri = '' prev_key = None for key in re.split('/', req.path): key = self._clean_path(key) if key in self._MAP.path_kw: type_uri += '/' + key elif prev_key in self._MAP.path_kw: type_uri += '/' + self._MAP.path_kw[prev_key] prev_key = key return service_type + type_uri def _build_target(self, req, service): """Build target resource.""" target_typeURI = ( self._build_typeURI(req, service.type) if service.type != taxonomy.UNKNOWN else service.type) target = resource.Resource(typeURI=target_typeURI, id=service.id, name=service.name) if service.admin_endp: target.add_address(service.admin_endp) if service.private_endp: target.add_address(service.private_endp) if service.public_endp: target.add_address(service.public_endp) return target def get_target_resource(self, req): """Retrieve target information. If discovery is enabled, target will attempt to retrieve information from service catalog. If not, the information will be taken from given config file. """ service_info = Service(type=taxonomy.UNKNOWN, name=taxonomy.UNKNOWN, id=taxonomy.UNKNOWN, admin_endp=None, private_endp=None, public_endp=None) catalog = {} try: catalog = jsonutils.loads(req.environ['HTTP_X_SERVICE_CATALOG']) except KeyError: self._log.warning( 'Unable to discover target information because ' 'service catalog is missing. Either the incoming ' 'request does not contain an auth token or auth ' 'token does not contain a service catalog. For ' 'the latter, please make sure the ' '"include_service_catalog" property in ' 'auth_token middleware is set to "True"') default_endpoint = None for endp in catalog: endpoint_urls = endp['endpoints'][0] admin_urlparse = urlparse.urlparse( endpoint_urls.get('adminURL', '')) public_urlparse = urlparse.urlparse( endpoint_urls.get('publicURL', '')) req_url = urlparse.urlparse(req.host_url) if (req_url.netloc == admin_urlparse.netloc or req_url.netloc == public_urlparse.netloc): service_info = self._get_service_info(endp) break elif (self._MAP.default_target_endpoint_type and endp['type'] == self._MAP.default_target_endpoint_type): default_endpoint = endp else: if default_endpoint: service_info = self._get_service_info(default_endpoint) return self._build_target(req, service_info) def _create_event(self, req): correlation_id = identifier.generate_uuid() action = self.get_action(req) initiator = ClientResource( typeURI=taxonomy.ACCOUNT_USER, id=req.environ.get('HTTP_X_USER_ID', taxonomy.UNKNOWN), name=req.environ.get('HTTP_X_USER_NAME', taxonomy.UNKNOWN), host=host.Host(address=req.client_addr, agent=req.user_agent), credential=KeystoneCredential( token=req.environ.get('HTTP_X_AUTH_TOKEN', ''), identity_status=req.environ.get('HTTP_X_IDENTITY_STATUS', taxonomy.UNKNOWN)), project_id=req.environ.get('HTTP_X_PROJECT_ID', taxonomy.UNKNOWN)) target = self.get_target_resource(req) event = factory.EventFactory().new_event( eventType=cadftype.EVENTTYPE_ACTIVITY, outcome=taxonomy.OUTCOME_PENDING, action=action, initiator=initiator, target=target, observer=resource.Resource(id='target')) event.requestPath = req.path_qs event.add_tag(tag.generate_name_value_tag('correlation_id', correlation_id)) return event keystonemiddleware-4.21.0/keystonemiddleware/audit/__init__.py0000666000175100017510000001556413225160624024655 0ustar zuulzuul00000000000000# # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. """ Build open standard audit information based on incoming requests. AuditMiddleware filter should be placed after keystonemiddleware.auth_token in the pipeline so that it can utilise the information the Identity server provides. """ import copy import functools from oslo_config import cfg from oslo_context import context as oslo_context from oslo_log import log as logging from pycadf import cadftaxonomy as taxonomy from pycadf import cadftype from pycadf import reason from pycadf import reporterstep from pycadf import resource from pycadf import timestamp import webob.dec from keystonemiddleware._common import config from keystonemiddleware.audit import _api from keystonemiddleware.audit import _notifier _LOG = None AUDIT_MIDDLEWARE_GROUP = 'audit_middleware_notifications' _AUDIT_OPTS = [ cfg.StrOpt('driver', help='The Driver to handle sending notifications. Possible ' 'values are messaging, messagingv2, routing, log, test, ' 'noop. If not specified, then value from ' 'oslo_messaging_notifications conf section is used.'), cfg.ListOpt('topics', help='List of AMQP topics used for OpenStack notifications. If' ' not specified, then value from ' ' oslo_messaging_notifications conf section is used.'), cfg.StrOpt('transport_url', secret=True, help='A URL representing messaging driver to use for ' 'notification. If not specified, we fall back to the same ' 'configuration used for RPC.'), ] CONF = cfg.CONF CONF.register_opts(_AUDIT_OPTS, group=AUDIT_MIDDLEWARE_GROUP) def _log_and_ignore_error(fn): @functools.wraps(fn) def wrapper(*args, **kwargs): try: return fn(*args, **kwargs) except Exception as e: _LOG.exception('An exception occurred processing ' 'the API call: %s ', e) return wrapper class AuditMiddleware(object): """Create an audit event based on request/response. The audit middleware takes in various configuration options such as the ability to skip audit of certain requests. The full list of options can be discovered here: https://docs.openstack.org/keystonemiddleware/latest/audit.html """ def __init__(self, app, **conf): self._application = app self._conf = config.Config('audit', AUDIT_MIDDLEWARE_GROUP, _list_opts(), conf) global _LOG _LOG = logging.getLogger(conf.get('log_name', __name__)) self._service_name = conf.get('service_name') self._ignore_req_list = [x.upper().strip() for x in conf.get('ignore_req_list', '').split(',')] self._cadf_audit = _api.OpenStackAuditApi(conf.get('audit_map_file'), _LOG) self._notifier = _notifier.create_notifier(self._conf, _LOG) def _create_event(self, req): event = self._cadf_audit._create_event(req) # cache model in request to allow tracking of transistive steps. req.environ['cadf_event'] = event return event @_log_and_ignore_error def _process_request(self, request): self._notifier.notify(request.context, 'audit.http.request', self._create_event(request).as_dict()) @_log_and_ignore_error def _process_response(self, request, response=None): # NOTE(gordc): handle case where error processing request if 'cadf_event' not in request.environ: self._create_event(request) event = request.environ['cadf_event'] if response: if response.status_int >= 200 and response.status_int < 400: result = taxonomy.OUTCOME_SUCCESS else: result = taxonomy.OUTCOME_FAILURE event.reason = reason.Reason( reasonType='HTTP', reasonCode=str(response.status_int)) else: result = taxonomy.UNKNOWN event.outcome = result event.add_reporterstep( reporterstep.Reporterstep( role=cadftype.REPORTER_ROLE_MODIFIER, reporter=resource.Resource(id='target'), reporterTime=timestamp.get_utc_now())) self._notifier.notify(request.context, 'audit.http.response', event.as_dict()) @webob.dec.wsgify def __call__(self, req): if req.method in self._ignore_req_list: return req.get_response(self._application) # Cannot use a RequestClass on wsgify above because the `req` object is # a `WebOb.Request` when this method is called so the RequestClass is # ignored by the wsgify wrapper. req.context = oslo_context.get_admin_context().to_dict() self._process_request(req) try: response = req.get_response(self._application) except Exception: self._process_response(req) raise else: self._process_response(req, response) return response def _list_opts(): """Return a list of oslo_config options available in audit middleware. The returned list includes all oslo_config options which may be registered at runtime by the project. Each element of the list is a tuple. The first element is the name of the group under which the list of elements in the second element will be registered. A group name of None corresponds to the [DEFAULT] group in config files. :returns: a list of (group_name, opts) tuples """ return [(AUDIT_MIDDLEWARE_GROUP, copy.deepcopy(_AUDIT_OPTS))] def filter_factory(global_conf, **local_conf): """Return a WSGI filter app for use with paste.deploy.""" conf = global_conf.copy() conf.update(local_conf) def audit_filter(app): return AuditMiddleware(app, **conf) return audit_filter # NOTE(jamielennox): Maintained here for public API compatibility. Service = _api.Service AuditMap = _api.AuditMap PycadfAuditApiConfigError = _api.PycadfAuditApiConfigError OpenStackAuditApi = _api.OpenStackAuditApi ClientResource = _api.ClientResource KeystoneCredential = _api.KeystoneCredential keystonemiddleware-4.21.0/keystonemiddleware/audit/_notifier.py0000666000175100017510000000333613225160624025066 0ustar zuulzuul00000000000000# Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. import os import sys try: import oslo_messaging except ImportError: oslo_messaging = None class _LogNotifier(object): def __init__(self, log): self._log = log def notify(self, context, event_type, payload): self._log.info('Event type: %(event_type)s, Context: %(context)s, ' 'Payload: %(payload)s', {'context': context, 'event_type': event_type, 'payload': payload}) class _MessagingNotifier(object): def __init__(self, notifier): self._notifier = notifier def notify(self, context, event_type, payload): self._notifier.info(context, event_type, payload) def create_notifier(conf, log): if oslo_messaging: transport = oslo_messaging.get_notification_transport( conf.oslo_conf_obj, url=conf.get('transport_url')) notifier = oslo_messaging.Notifier( transport, os.path.basename(sys.argv[0]), driver=conf.get('driver'), topics=conf.get('topics')) return _MessagingNotifier(notifier) else: return _LogNotifier(log) keystonemiddleware-4.21.0/keystonemiddleware/locale/0000775000175100017510000000000013225161130022651 5ustar zuulzuul00000000000000keystonemiddleware-4.21.0/keystonemiddleware/locale/en_GB/0000775000175100017510000000000013225161130023623 5ustar zuulzuul00000000000000keystonemiddleware-4.21.0/keystonemiddleware/locale/en_GB/LC_MESSAGES/0000775000175100017510000000000013225161130025410 5ustar zuulzuul00000000000000keystonemiddleware-4.21.0/keystonemiddleware/locale/en_GB/LC_MESSAGES/keystonemiddleware.po0000666000175100017510000000520013225160624031655 0ustar zuulzuul00000000000000# Andi Chandler , 2017. #zanata msgid "" msgstr "" "Project-Id-Version: keystonemiddleware 4.17.1.dev30\n" "Report-Msgid-Bugs-To: https://bugs.launchpad.net/openstack-i18n/\n" "POT-Creation-Date: 2017-11-14 20:57+0000\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "PO-Revision-Date: 2017-10-17 07:06+0000\n" "Last-Translator: Andi Chandler \n" "Language-Team: English (United Kingdom)\n" "Language: en-GB\n" "X-Generator: Zanata 3.9.6\n" "Plural-Forms: nplurals=2; plural=(n != 1)\n" msgid "Access key not provided" msgstr "Access key not provided" msgid "Encrypted data appears to be corrupted." msgstr "Encrypted data appears to be corrupted." #, python-format msgid "Error response from keystone: %s" msgstr "Error response from Keystone: %s" msgid "Failed to fetch token data from identity server" msgstr "Failed to fetch token data from identity server" #, python-format msgid "Failed to fetch token revocation list: %d" msgstr "Failed to fetch token revocation list: %d" msgid "Failure parsing response from keystone" msgstr "Failure parsing response from Keystone" msgid "Identity server rejected authorization necessary to fetch token data" msgstr "Identity server rejected authorisation necessary to fetch token data" msgid "" "Implementations of auth_token must set kwargs_to_fetch_token this will be " "the required and assumed in Queens." msgstr "" "Implementations of auth_token must set kwargs_to_fetch_token this will be " "the required and assumed in Queens." msgid "Invalid MAC; data appears to be corrupted." msgstr "Invalid MAC; data appears to be corrupted." msgid "Invalid version asked for in auth_token plugin" msgstr "Invalid version asked for in auth_token plugin" msgid "No compatible apis supported by server" msgstr "No compatible APIs supported by server" msgid "Revocation list improperly formatted." msgstr "Revocation list improperly formatted." msgid "Signature not provided" msgstr "Signature not provided" msgid "The request you have made requires authentication." msgstr "The request you have made requires authentication." msgid "Token authorization failed" msgstr "Token authorisation failed" msgid "Token has been revoked" msgstr "Token has been revoked" msgid "Unable to determine service tenancy." msgstr "Unable to determine service tenancy." msgid "" "memcache_secret_key must be defined when a memcache_security_strategy is " "defined" msgstr "" "memcache_secret_key must be defined when a memcache_security_strategy is " "defined" #, python-format msgid "unable to access signing_dir %s" msgstr "unable to access signing_dir %s" keystonemiddleware-4.21.0/keystonemiddleware/locale/ko_KR/0000775000175100017510000000000013225161130023656 5ustar zuulzuul00000000000000keystonemiddleware-4.21.0/keystonemiddleware/locale/ko_KR/LC_MESSAGES/0000775000175100017510000000000013225161130025443 5ustar zuulzuul00000000000000keystonemiddleware-4.21.0/keystonemiddleware/locale/ko_KR/LC_MESSAGES/keystonemiddleware.po0000666000175100017510000000440213225160624031713 0ustar zuulzuul00000000000000# Ian Y. Choi , 2017. #zanata msgid "" msgstr "" "Project-Id-Version: keystonemiddleware 4.17.1.dev30\n" "Report-Msgid-Bugs-To: https://bugs.launchpad.net/openstack-i18n/\n" "POT-Creation-Date: 2017-11-14 20:57+0000\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "PO-Revision-Date: 2017-01-23 03:52+0000\n" "Last-Translator: Ian Y. Choi \n" "Language-Team: Korean (South Korea)\n" "Language: ko-KR\n" "X-Generator: Zanata 3.9.6\n" "Plural-Forms: nplurals=1; plural=0\n" msgid "Access key not provided" msgstr "Access keyê°€ 없습니다" msgid "Encrypted data appears to be corrupted." msgstr "ì•”í˜¸í™”ëœ ë°ì´í„°ê°€ ì†ìƒëœ 것으로 보입니다." #, python-format msgid "Error response from keystone: %s" msgstr "keystone 오류 ì‘답: %s" msgid "Failed to fetch token data from identity server" msgstr "ì¸ì¦ 서버로부터 í† í° ë°ì´í„°ë¥¼ 가져올 수 없습니다" #, python-format msgid "Failed to fetch token revocation list: %d" msgstr "í† í° revocation 목ë¡ì„ 가져올 수 없습니다: %d" msgid "Failure parsing response from keystone" msgstr "keystone ì‘답 파싱 실패" msgid "Identity server rejected authorization necessary to fetch token data" msgstr "í† í° ë°ì´í„°ë¥¼ 가져오기 위해 í•„ìˆ˜ì¸ ì¸ì¦ì„ ì¸ì¦ 서버ì—서 ê±°ì ˆ" msgid "Invalid MAC; data appears to be corrupted." msgstr "ìž˜ëª»ëœ MAC; ë°ì´í„°ê°€ ì†ìƒëœ 것으로 보입니다." msgid "Invalid version asked for in auth_token plugin" msgstr "auth_token 플러그ì¸ì—서 ìž˜ëª»ëœ ë²„ì „ 요청" msgid "No compatible apis supported by server" msgstr "서버ì—서 ì§€ì›í•˜ëŠ” 호환 apiê°€ 없습니다" msgid "Signature not provided" msgstr "Signatureê°€ 없습니다" msgid "The request you have made requires authentication." msgstr "제공한 ìš”ì²­ì€ ì¸ì¦ì„ 필요로 합니다." msgid "Token authorization failed" msgstr "Token ì¸ì¦ 실패" msgid "Unable to determine service tenancy." msgstr "서비스 tenancy를 ê²°ì •í•  수 없습니다." msgid "" "memcache_secret_key must be defined when a memcache_security_strategy is " "defined" msgstr "" "memcache_security_strategy ê°€ ì •ì˜ë˜ì–´ ìžˆì„ ë•Œ memcache_secret_key 를 ì •ì˜í•´" "야 합니다" keystonemiddleware-4.21.0/keystonemiddleware/exceptions.py0000666000175100017510000000123413225160624024156 0ustar zuulzuul00000000000000# Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. class KeystoneMiddlewareException(Exception): pass class ConfigurationError(KeystoneMiddlewareException): pass keystonemiddleware-4.21.0/keystonemiddleware/opts.py0000666000175100017510000000134413225160624022764 0ustar zuulzuul00000000000000# Copyright (c) 2014 OpenStack Foundation. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. __all__ = ( 'list_auth_token_opts', ) from keystonemiddleware import auth_token def list_auth_token_opts(): return auth_token.list_opts() keystonemiddleware-4.21.0/keystonemiddleware/auth_token/0000775000175100017510000000000013225161130023553 5ustar zuulzuul00000000000000keystonemiddleware-4.21.0/keystonemiddleware/auth_token/_identity.py0000666000175100017510000002231113225160643026126 0ustar zuulzuul00000000000000# Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. import functools from keystoneauth1 import discover from keystoneauth1 import exceptions as ksa_exceptions from keystoneauth1 import plugin from keystoneclient import exceptions as ksc_exceptions from keystoneclient.v2_0 import client as v2_client from keystoneclient.v3 import client as v3_client from six.moves import urllib from keystonemiddleware.auth_token import _auth from keystonemiddleware.auth_token import _exceptions as ksm_exceptions from keystonemiddleware.i18n import _ def _convert_fetch_cert_exception(fetch_cert): @functools.wraps(fetch_cert) def wrapper(self): try: text = fetch_cert(self) except ksa_exceptions.HttpError as e: raise ksc_exceptions.CertificateConfigError(e.details) return text return wrapper class _RequestStrategy(object): AUTH_VERSION = None def __init__(self, adap, include_service_catalog=None): self._include_service_catalog = include_service_catalog def verify_token(self, user_token, allow_expired=False): pass @_convert_fetch_cert_exception def fetch_signing_cert(self): return self._fetch_signing_cert() def _fetch_signing_cert(self): pass @_convert_fetch_cert_exception def fetch_ca_cert(self): return self._fetch_ca_cert() def _fetch_ca_cert(self): pass def fetch_revocation_list(self): pass class _V2RequestStrategy(_RequestStrategy): AUTH_VERSION = (2, 0) def __init__(self, adap, **kwargs): super(_V2RequestStrategy, self).__init__(adap, **kwargs) self._client = v2_client.Client(session=adap) def verify_token(self, token, allow_expired=False): # NOTE(jamielennox): allow_expired is ignored on V2 auth_ref = self._client.tokens.validate_access_info(token) if not auth_ref: msg = _('Failed to fetch token data from identity server') raise ksm_exceptions.InvalidToken(msg) return {'access': auth_ref} def _fetch_signing_cert(self): return self._client.certificates.get_signing_certificate() def _fetch_ca_cert(self): return self._client.certificates.get_ca_certificate() def fetch_revocation_list(self): return self._client.tokens.get_revoked() class _V3RequestStrategy(_RequestStrategy): AUTH_VERSION = (3, 0) def __init__(self, adap, **kwargs): super(_V3RequestStrategy, self).__init__(adap, **kwargs) self._client = v3_client.Client(session=adap) def verify_token(self, token, allow_expired=False): auth_ref = self._client.tokens.validate( token, include_catalog=self._include_service_catalog, allow_expired=allow_expired) if not auth_ref: msg = _('Failed to fetch token data from identity server') raise ksm_exceptions.InvalidToken(msg) return {'token': auth_ref} def _fetch_signing_cert(self): return self._client.simple_cert.get_certificates() def _fetch_ca_cert(self): return self._client.simple_cert.get_ca_certificates() def fetch_revocation_list(self): return self._client.tokens.get_revoked() _REQUEST_STRATEGIES = [_V3RequestStrategy, _V2RequestStrategy] class IdentityServer(object): """Base class for operations on the Identity API server. The auth_token middleware needs to communicate with the Identity API server to validate UUID tokens, fetch the revocation list, signing certificates, etc. This class encapsulates the data and methods to perform these operations. """ def __init__(self, log, adap, include_service_catalog=None, requested_auth_version=None): self._LOG = log self._adapter = adap self._include_service_catalog = include_service_catalog self._requested_auth_version = requested_auth_version # Built on-demand with self._request_strategy. self._request_strategy_obj = None @property def www_authenticate_uri(self): www_authenticate_uri = self._adapter.get_endpoint( interface=plugin.AUTH_INTERFACE) # NOTE(jamielennox): This weird stripping of the prefix hack is # only relevant to the legacy case. We urljoin '/' to get just the # base URI as this is the original behaviour. if isinstance(self._adapter.auth, _auth.AuthTokenPlugin): www_authenticate_uri = urllib.parse.urljoin( www_authenticate_uri, '/').rstrip('/') return www_authenticate_uri @property def auth_version(self): return self._request_strategy.AUTH_VERSION @property def _request_strategy(self): if not self._request_strategy_obj: strategy_class = self._get_strategy_class() self._adapter.version = strategy_class.AUTH_VERSION self._request_strategy_obj = strategy_class( self._adapter, include_service_catalog=self._include_service_catalog) return self._request_strategy_obj def _get_strategy_class(self): if self._requested_auth_version: # A specific version was requested. if discover.version_match(_V3RequestStrategy.AUTH_VERSION, self._requested_auth_version): return _V3RequestStrategy # The version isn't v3 so we don't know what to do. Just assume V2. return _V2RequestStrategy # Specific version was not requested then we fall through to # discovering available versions from the server for klass in _REQUEST_STRATEGIES: if self._adapter.get_endpoint(version=klass.AUTH_VERSION): self._LOG.debug('Auth Token confirmed use of %s apis', self._requested_auth_version) return klass versions = ['v%d.%d' % s.AUTH_VERSION for s in _REQUEST_STRATEGIES] self._LOG.error('No attempted versions [%s] supported by server', ', '.join(versions)) msg = _('No compatible apis supported by server') raise ksm_exceptions.ServiceError(msg) def verify_token(self, user_token, retry=True, allow_expired=False): """Authenticate user token with identity server. :param user_token: user's token id :param retry: flag that forces the middleware to retry user authentication when an indeterminate response is received. Optional. :param allow_expired: Allow retrieving an expired token. :returns: access info received from identity server on success :rtype: :py:class:`keystoneauth1.access.AccessInfo` :raises exc.InvalidToken: if token is rejected :raises exc.ServiceError: if unable to authenticate token """ try: auth_ref = self._request_strategy.verify_token( user_token, allow_expired=allow_expired) except ksa_exceptions.NotFound as e: self._LOG.info('Authorization failed for token') self._LOG.info('Identity response: %s', e.response.text) raise ksm_exceptions.InvalidToken(_('Token authorization failed')) except ksa_exceptions.Unauthorized as e: self._LOG.info('Identity server rejected authorization') self._LOG.warning('Identity response: %s', e.response.text) if retry: self._LOG.info('Retrying validation') return self.verify_token(user_token, False) msg = _('Identity server rejected authorization necessary to ' 'fetch token data') raise ksm_exceptions.ServiceError(msg) except ksa_exceptions.HttpError as e: self._LOG.error( 'Bad response code while validating token: %s', e.http_status) self._LOG.warning('Identity response: %s', e.response.text) msg = _('Failed to fetch token data from identity server') raise ksm_exceptions.ServiceError(msg) else: return auth_ref def fetch_revocation_list(self): try: data = self._request_strategy.fetch_revocation_list() except ksa_exceptions.HttpError as e: msg = _('Failed to fetch token revocation list: %d') raise ksm_exceptions.RevocationListError(msg % e.http_status) if 'signed' not in data: msg = _('Revocation list improperly formatted.') raise ksm_exceptions.RevocationListError(msg) return data['signed'] def fetch_signing_cert(self): return self._request_strategy.fetch_signing_cert() def fetch_ca_cert(self): return self._request_strategy.fetch_ca_cert() keystonemiddleware-4.21.0/keystonemiddleware/auth_token/_revocations.py0000666000175100017510000001075613225160624026642 0ustar zuulzuul00000000000000# Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. import datetime import os from oslo_log import log as logging from oslo_serialization import jsonutils from oslo_utils import timeutils from keystonemiddleware.auth_token import _exceptions as exc from keystonemiddleware.i18n import _ _LOG = logging.getLogger(__name__) class Revocations(object): _FILE_NAME = 'revoked.pem' def __init__(self, timeout, signing_directory, identity_server, cms_verify, log=_LOG): self._cache_timeout = timeout self._signing_directory = signing_directory self._identity_server = identity_server self._cms_verify = cms_verify self._log = log self._fetched_time_prop = None self._list_prop = None @property def _fetched_time(self): if not self._fetched_time_prop: # If the fetched list has been written to disk, use its # modification time. file_path = self._signing_directory.calc_path(self._FILE_NAME) if os.path.exists(file_path): mtime = os.path.getmtime(file_path) fetched_time = datetime.datetime.utcfromtimestamp(mtime) # Otherwise the list will need to be fetched. else: fetched_time = datetime.datetime.min self._fetched_time_prop = fetched_time return self._fetched_time_prop @_fetched_time.setter def _fetched_time(self, value): self._fetched_time_prop = value def _fetch(self): revocation_list_data = self._identity_server.fetch_revocation_list() return self._cms_verify(revocation_list_data) @property def _list(self): timeout = self._fetched_time + self._cache_timeout list_is_current = timeutils.utcnow() < timeout if list_is_current: # Load the list from disk if required if not self._list_prop: self._list_prop = jsonutils.loads( self._signing_directory.read_file(self._FILE_NAME)) else: self._list = self._fetch() return self._list_prop @_list.setter def _list(self, value): """Save a revocation list to memory and to disk. :param value: A json-encoded revocation list """ self._list_prop = jsonutils.loads(value) self._fetched_time = timeutils.utcnow() self._signing_directory.write_file(self._FILE_NAME, value) def _is_revoked(self, token_id): """Indicate whether the token_id appears in the revocation list.""" revoked_tokens = self._list.get('revoked', None) if not revoked_tokens: return False revoked_ids = (x['id'] for x in revoked_tokens) return token_id in revoked_ids def _any_revoked(self, token_ids): for token_id in token_ids: if self._is_revoked(token_id): return True return False def check(self, token_ids): if self._any_revoked(token_ids): self._log.debug('Token is marked as having been revoked') raise exc.InvalidToken(_('Token has been revoked')) def check_by_audit_id(self, audit_ids): """Check whether the audit_id appears in the revocation list. :raises keystonemiddleware.auth_token._exceptions.InvalidToken: if the audit ID(s) appear in the revocation list. """ revoked_tokens = self._list.get('revoked', None) if not revoked_tokens: # There's no revoked tokens, so nothing to do. return # The audit_id may not be present in the revocation events because # earlier versions of the identity server didn't provide them. revoked_ids = set( x['audit_id'] for x in revoked_tokens if 'audit_id' in x) for audit_id in audit_ids: if audit_id in revoked_ids: self._log.debug( 'Token is marked as having been revoked by audit id') raise exc.InvalidToken(_('Token has been revoked')) keystonemiddleware-4.21.0/keystonemiddleware/auth_token/_exceptions.py0000666000175100017510000000153513225160624026462 0ustar zuulzuul00000000000000# Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. from keystonemiddleware import exceptions ConfigurationError = exceptions.ConfigurationError class InvalidToken(exceptions.KeystoneMiddlewareException): pass class ServiceError(exceptions.KeystoneMiddlewareException): pass class RevocationListError(exceptions.KeystoneMiddlewareException): pass keystonemiddleware-4.21.0/keystonemiddleware/auth_token/_auth.py0000666000175100017510000001767413225160624025255 0ustar zuulzuul00000000000000# Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. from keystoneauth1 import discover from keystoneauth1.identity import v2 from keystoneauth1 import plugin from keystoneauth1 import token_endpoint from oslo_config import cfg from keystonemiddleware.auth_token import _base from keystonemiddleware.i18n import _ class AuthTokenPlugin(plugin.BaseAuthPlugin): def __init__(self, auth_host, auth_port, auth_protocol, auth_admin_prefix, admin_user, admin_password, admin_tenant_name, admin_token, identity_uri, log): log.warning( "Use of the auth_admin_prefix, auth_host, auth_port, " "auth_protocol, identity_uri, admin_token, admin_user, " "admin_password, and admin_tenant_name configuration options was " "deprecated in the Mitaka release in favor of an auth_plugin and " "its related options. This class may be removed in a future " "release.") # NOTE(jamielennox): it does appear here that our default arguments # are backwards. We need to do it this way so that we can handle the # same deprecation strategy for CONF and the conf variable. if not identity_uri: log.warning('Configuring admin URI using auth fragments was ' 'deprecated in the Kilo release, and will be ' 'removed in the Newton release, ' 'use \'identity_uri\ instead.') if ':' in auth_host: # Note(dzyu) it is an IPv6 address, so it needs to be wrapped # with '[]' to generate a valid IPv6 URL, based on # http://www.ietf.org/rfc/rfc2732.txt auth_host = '[%s]' % auth_host identity_uri = '%s://%s:%s' % (auth_protocol, auth_host, auth_port) if auth_admin_prefix: identity_uri = '%s/%s' % (identity_uri, auth_admin_prefix.strip('/')) self._identity_uri = identity_uri.rstrip('/') # FIXME(jamielennox): Yes. This is wrong. We should be determining the # plugin to use based on a combination of discovery and inputs. Much # of this can be changed when we get keystoneclient 0.10. For now this # hardcoded path is EXACTLY the same as the original auth_token did. auth_url = '%s/v2.0' % self._identity_uri if admin_token: log.warning( "The admin_token option in auth_token middleware was " "deprecated in the Kilo release, and will be removed in the " "Newton release, use admin_user and admin_password instead.") self._plugin = token_endpoint.Token(auth_url, admin_token) else: self._plugin = v2.Password(auth_url, username=admin_user, password=admin_password, tenant_name=admin_tenant_name) self._LOG = log self._discover = None def get_token(self, *args, **kwargs): return self._plugin.get_token(*args, **kwargs) def get_endpoint(self, session, interface=None, version=None, **kwargs): """Return an endpoint for the client. There are no required keyword arguments to ``get_endpoint`` as a plugin implementation should use best effort with the information available to determine the endpoint. :param session: The session object that the auth_plugin belongs to. :type session: keystoneauth1.session.Session :param version: The version number required for this endpoint. :type version: tuple or str :param str interface: what visibility the endpoint should have. :returns: The base URL that will be used to talk to the required service or None if not available. :rtype: string """ if interface == plugin.AUTH_INTERFACE: return self._identity_uri if not version: # NOTE(jamielennox): This plugin can only be used within auth_token # and auth_token will always provide version= with requests. return None if not self._discover: self._discover = discover.Discover(session, url=self._identity_uri, authenticated=False) if not self._discover.url_for(version): # NOTE(jamielennox): The requested version is not supported by the # identity server. return None # NOTE(jamielennox): for backwards compatibility here we don't # actually use the URL from discovery we hack it up instead. :( # NOTE(blk-u): Normalizing the version is a workaround for bug 1450272. # This can be removed once that's fixed. Also fix the docstring for the # version parameter to be just "tuple". version = discover.normalize_version_number(version) if discover.version_match((2, 0), version): return '%s/v2.0' % self._identity_uri elif discover.version_match((3, 0), version): return '%s/v3' % self._identity_uri # NOTE(jamielennox): This plugin will only get called from auth_token # middleware. The middleware should never request a version that the # plugin doesn't know how to handle. msg = _('Invalid version asked for in auth_token plugin') raise NotImplementedError(msg) def invalidate(self): return self._plugin.invalidate() OPTS = [ cfg.StrOpt('auth_admin_prefix', default='', help='Prefix to prepend at the beginning of the path. ' 'Deprecated, use identity_uri.'), cfg.StrOpt('auth_host', default='127.0.0.1', help='Host providing the admin Identity API endpoint. ' 'Deprecated, use identity_uri.'), cfg.IntOpt('auth_port', default=35357, help='Port of the admin Identity API endpoint. ' 'Deprecated, use identity_uri.'), cfg.StrOpt('auth_protocol', default='https', choices=('http', 'https'), help='Protocol of the admin Identity API endpoint. ' 'Deprecated, use identity_uri.'), cfg.StrOpt('identity_uri', help='Complete admin Identity API endpoint. This ' 'should specify the unversioned root endpoint ' 'e.g. https://localhost:35357/'), cfg.StrOpt('admin_token', secret=True, help='This option is deprecated and may be removed in ' 'a future release. Single shared secret with the ' 'Keystone configuration used for bootstrapping a ' 'Keystone installation, or otherwise bypassing ' 'the normal authentication process. This option ' 'should not be used, use `admin_user` and ' '`admin_password` instead.'), cfg.StrOpt('admin_user', help='Service username.'), cfg.StrOpt('admin_password', secret=True, help='Service user password.'), cfg.StrOpt('admin_tenant_name', default='admin', help='Service tenant name.'), ] cfg.CONF.register_opts(OPTS, group=_base.AUTHTOKEN_GROUP) keystonemiddleware-4.21.0/keystonemiddleware/auth_token/_signing_dir.py0000666000175100017510000000644013225160624026575 0ustar zuulzuul00000000000000# Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. import os import stat import tempfile from oslo_log import log as logging import six from keystonemiddleware.auth_token import _exceptions as exc from keystonemiddleware.i18n import _ _LOG = logging.getLogger(__name__) class SigningDirectory(object): def __init__(self, directory_name=None, log=None): self._log = log or _LOG self._directory_name = directory_name if self._directory_name: self._log.info( 'Using %s as cache directory for signing certificate', self._directory_name) self._verify_signing_dir() def write_file(self, file_name, new_contents): # In Python2, encoding is slow so the following check avoids it if it # is not absolutely necessary. if isinstance(new_contents, six.text_type): new_contents = new_contents.encode('utf-8') def _atomic_write(): with tempfile.NamedTemporaryFile(dir=self._directory_name, delete=False) as f: f.write(new_contents) os.rename(f.name, self.calc_path(file_name)) try: _atomic_write() except (OSError, IOError): self._verify_signing_dir() _atomic_write() def read_file(self, file_name): path = self.calc_path(file_name) open_kwargs = {'encoding': 'utf-8'} if six.PY3 else {} with open(path, 'r', **open_kwargs) as f: return f.read() def calc_path(self, file_name): self._lazy_create_signing_dir() return os.path.join(self._directory_name, file_name) def _lazy_create_signing_dir(self): if self._directory_name is None: self._directory_name = tempfile.mkdtemp(prefix='keystone-signing-') self._log.info( 'Using %s as cache directory for signing certificate', self._directory_name) self._verify_signing_dir() def _verify_signing_dir(self): if os.path.isdir(self._directory_name): if not os.access(self._directory_name, os.W_OK): raise exc.ConfigurationError( _('unable to access signing_dir %s') % self._directory_name) uid = os.getuid() if os.stat(self._directory_name).st_uid != uid: self._log.warning('signing_dir is not owned by %s', uid) current_mode = stat.S_IMODE(os.stat(self._directory_name).st_mode) if current_mode != stat.S_IRWXU: self._log.warning( 'signing_dir mode is %(mode)s instead of %(need)s', {'mode': oct(current_mode), 'need': oct(stat.S_IRWXU)}) else: os.makedirs(self._directory_name, stat.S_IRWXU) keystonemiddleware-4.21.0/keystonemiddleware/auth_token/_opts.py0000666000175100017510000003304613225160624025270 0ustar zuulzuul00000000000000# Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. import copy from keystoneauth1 import loading from oslo_config import cfg from keystonemiddleware.auth_token import _base # NOTE(jamielennox): A number of options below are deprecated however are left # in the list and only mentioned as deprecated in the help string. This is # because we have to provide the same deprecation functionality for arguments # passed in via the conf in __init__ (from paste) and there is no way to test # that the default value was set or not in CONF. # Also if we were to remove the options from the CONF list (as typical CONF # deprecation works) then other projects will not be able to override the # options via CONF. _OPTS = [ cfg.StrOpt('www_authenticate_uri', # FIXME(dolph): should be default='http://127.0.0.1:5000/v2.0/', # or (depending on client support) an unversioned, publicly # accessible identity endpoint (see bug 1207517). Further, we # can eliminate this configuration option in favor of pulling # the endpoint from the service catalog that the service user # receives (there should be an identity endpoint listed there). # This wasn't an option originally when many auth_token # deployments were configured with the "ADMIN" token and # endpoint combination. deprecated_name='auth_uri', help='Complete "public" Identity API endpoint. This endpoint' ' should not be an "admin" endpoint, as it should be accessible' ' by all end users. Unauthenticated clients are redirected to' ' this endpoint to authenticate. Although this endpoint should' ' ideally be unversioned, client support in the wild varies.' ' If you\'re using a versioned v2 endpoint here, then this' ' should *not* be the same endpoint the service user utilizes' ' for validating tokens, because normal end users may not be' ' able to reach that endpoint.'), cfg.StrOpt('auth_uri', deprecated_for_removal=True, deprecated_reason='The auth_uri option is deprecated in favor' ' of www_authenticate_uri and will be removed in the S ' ' release.', deprecated_since='Queens', help='Complete "public" Identity API endpoint. This endpoint' ' should not be an "admin" endpoint, as it should be accessible' ' by all end users. Unauthenticated clients are redirected to' ' this endpoint to authenticate. Although this endpoint should' ' ideally be unversioned, client support in the wild varies.' ' If you\'re using a versioned v2 endpoint here, then this' ' should *not* be the same endpoint the service user utilizes' ' for validating tokens, because normal end users may not be' ' able to reach that endpoint. This option is deprecated in' ' favor of www_authenticate_uri and will be removed in the S' ' release.'), cfg.StrOpt('auth_version', help='API version of the admin Identity API endpoint.'), cfg.BoolOpt('delay_auth_decision', default=False, help='Do not handle authorization requests within the' ' middleware, but delegate the authorization decision to' ' downstream WSGI components.'), cfg.IntOpt('http_connect_timeout', help='Request timeout value for communicating with Identity' ' API server.'), cfg.IntOpt('http_request_max_retries', default=3, help='How many times are we trying to reconnect when' ' communicating with Identity API Server.'), cfg.StrOpt('cache', help='Request environment key where the Swift cache object is' ' stored. When auth_token middleware is deployed with a Swift' ' cache, use this option to have the middleware share a caching' ' backend with swift. Otherwise, use the ``memcached_servers``' ' option instead.'), cfg.StrOpt('certfile', help='Required if identity server requires client certificate'), cfg.StrOpt('keyfile', help='Required if identity server requires client certificate'), cfg.StrOpt('cafile', help='A PEM encoded Certificate Authority to use when ' 'verifying HTTPs connections. Defaults to system CAs.'), cfg.BoolOpt('insecure', default=False, help='Verify HTTPS connections.'), cfg.StrOpt('region_name', help='The region in which the identity server can be found.'), cfg.StrOpt('signing_dir', deprecated_for_removal=True, deprecated_reason='PKI token format is no longer supported.', deprecated_since='Ocata', help='Directory used to cache files related to PKI tokens. This' ' option has been deprecated in the Ocata release and will be' ' removed in the P release.'), cfg.ListOpt('memcached_servers', deprecated_name='memcache_servers', help='Optionally specify a list of memcached server(s) to' ' use for caching. If left undefined, tokens will instead be' ' cached in-process.'), cfg.IntOpt('token_cache_time', default=300, help='In order to prevent excessive effort spent validating' ' tokens, the middleware caches previously-seen tokens for a' ' configurable duration (in seconds). Set to -1 to disable' ' caching completely.'), cfg.IntOpt('revocation_cache_time', default=10, deprecated_for_removal=True, deprecated_reason='PKI token format is no longer supported.', deprecated_since='Ocata', help='Determines the frequency at which the list of revoked' ' tokens is retrieved from the Identity service (in seconds). A' ' high number of revocation events combined with a low cache' ' duration may significantly reduce performance. Only valid' ' for PKI tokens. This option has been deprecated in the Ocata' ' release and will be removed in the P release.'), cfg.StrOpt('memcache_security_strategy', default='None', choices=('None', 'MAC', 'ENCRYPT'), ignore_case=True, help='(Optional) If defined, indicate whether token data' ' should be authenticated or authenticated and encrypted.' ' If MAC, token data is authenticated (with HMAC) in the cache.' ' If ENCRYPT, token data is encrypted and authenticated in the' ' cache. If the value is not one of these options or empty,' ' auth_token will raise an exception on initialization.'), cfg.StrOpt('memcache_secret_key', secret=True, help='(Optional, mandatory if memcache_security_strategy is' ' defined) This string is used for key derivation.'), cfg.IntOpt('memcache_pool_dead_retry', default=5 * 60, help='(Optional) Number of seconds memcached server is' ' considered dead before it is tried again.'), cfg.IntOpt('memcache_pool_maxsize', default=10, help='(Optional) Maximum total number of open connections to' ' every memcached server.'), cfg.IntOpt('memcache_pool_socket_timeout', default=3, help='(Optional) Socket timeout in seconds for communicating ' 'with a memcached server.'), cfg.IntOpt('memcache_pool_unused_timeout', default=60, help='(Optional) Number of seconds a connection to memcached' ' is held unused in the pool before it is closed.'), cfg.IntOpt('memcache_pool_conn_get_timeout', default=10, help='(Optional) Number of seconds that an operation will wait ' 'to get a memcached client connection from the pool.'), cfg.BoolOpt('memcache_use_advanced_pool', default=False, help='(Optional) Use the advanced (eventlet safe) memcached ' 'client pool. The advanced pool will only work under ' 'python 2.x.'), cfg.BoolOpt('include_service_catalog', default=True, help='(Optional) Indicate whether to set the X-Service-Catalog' ' header. If False, middleware will not ask for service' ' catalog on token validation and will not set the' ' X-Service-Catalog header.'), cfg.StrOpt('enforce_token_bind', default='permissive', help='Used to control the use and type of token binding. Can' ' be set to: "disabled" to not check token binding.' ' "permissive" (default) to validate binding information if the' ' bind type is of a form known to the server and ignore it if' ' not. "strict" like "permissive" but if the bind type is' ' unknown the token will be rejected. "required" any form of' ' token binding is needed to be allowed. Finally the name of a' ' binding method that must be present in tokens.'), cfg.BoolOpt('check_revocations_for_cached', default=False, deprecated_for_removal=True, deprecated_reason='PKI token format is no longer supported.', deprecated_since='Ocata', help='If true, the revocation list will be checked for cached' ' tokens. This requires that PKI tokens are configured on the' ' identity server.'), cfg.ListOpt('hash_algorithms', default=['md5'], deprecated_for_removal=True, deprecated_reason='PKI token format is no longer supported.', deprecated_since='Ocata', help='Hash algorithms to use for hashing PKI tokens. This may' ' be a single algorithm or multiple. The algorithms are those' ' supported by Python standard hashlib.new(). The hashes will' ' be tried in the order given, so put the preferred one first' ' for performance. The result of the first hash will be stored' ' in the cache. This will typically be set to multiple values' ' only while migrating from a less secure algorithm to a more' ' secure one. Once all the old tokens are expired this option' ' should be set to a single value for better performance.'), cfg.ListOpt('service_token_roles', default=['service'], help='A choice of roles that must be present in a service' ' token. Service tokens are allowed to request that an expired' ' token can be used and so this check should tightly control' ' that only actual services should be sending this token.' ' Roles here are applied as an ANY check so any role in this' ' list must be present. For backwards compatibility reasons' ' this currently only affects the allow_expired check.'), cfg.BoolOpt('service_token_roles_required', default=False, help='For backwards compatibility reasons we must let valid' ' service tokens pass that don\'t pass the service_token_roles' ' check as valid. Setting this true will become the default' ' in a future release and should be enabled if possible.'), ] CONF = cfg.CONF CONF.register_opts(_OPTS, group=_base.AUTHTOKEN_GROUP) loading.register_auth_conf_options(cfg.CONF, _base.AUTHTOKEN_GROUP) auth_token_opts = [ (_base.AUTHTOKEN_GROUP, _OPTS + loading.get_auth_common_conf_options()), ] __all__ = ( 'list_opts', ) def list_opts(): """Return a list of oslo_config options available in auth_token middleware. The returned list includes the non-deprecated oslo_config options which may be registered at runtime by the project. The purpose of this is to allow tools like the Oslo sample config file generator to discover the options exposed to users by this middleware. Deprecated Options should not show up here so as to not be included in sample configuration. Each element of the list is a tuple. The first element is the name of the group under which the list of elements in the second element will be registered. A group name of None corresponds to the [DEFAULT] group in config files. This function is discoverable via the entry point 'keystonemiddleware.auth_token' under the 'oslo.config.opts' namespace. :returns: a list of (group_name, opts) tuples """ auth_token_opts = (_OPTS + loading.get_auth_common_conf_options()) return [(_base.AUTHTOKEN_GROUP, copy.deepcopy(auth_token_opts))] keystonemiddleware-4.21.0/keystonemiddleware/auth_token/__init__.py0000666000175100017510000012017313225160624025701 0ustar zuulzuul00000000000000# Copyright 2010-2012 OpenStack Foundation # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or # implied. # See the License for the specific language governing permissions and # limitations under the License. r""" Token-based Authentication Middleware. This WSGI component: * Verifies that incoming client requests have valid tokens by validating tokens with the auth service. * Rejects unauthenticated requests unless the auth_token middleware is in ``delay_auth_decision`` mode, which means the final decision is delegated to the downstream WSGI component (usually the OpenStack service). * Collects and forwards identity information based on a valid token such as user name, domain, project, etc. Refer to: https://docs.openstack.org/keystonemiddleware/latest/\ middlewarearchitecture.html Headers ------- The auth_token middleware uses headers sent in by the client on the request and sets headers and environment variables for the downstream WSGI component. Coming in from initial call from client or customer ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ HTTP_X_AUTH_TOKEN The client token being passed in. HTTP_X_SERVICE_TOKEN A service token being passed in. Used for communication between components ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ WWW-Authenticate HTTP header returned to a user indicating which endpoint to use to retrieve a new token. What auth_token adds to the request for use by the OpenStack service ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ When using composite authentication (a user and service token are present) additional service headers relating to the service user will be added. They take the same form as the standard headers but add ``_SERVICE_``. These headers will not exist in the environment if no service token is present. HTTP_X_IDENTITY_STATUS, HTTP_X_SERVICE_IDENTITY_STATUS Will be set to either ``Confirmed`` or ``Invalid``. The underlying service will only see a value of 'Invalid' if the middleware is configured to run in ``delay_auth_decision`` mode. As with all such headers, ``HTTP_X_SERVICE_IDENTITY_STATUS`` will only exist in the environment if a service token is presented. This is different than ``HTTP_X_IDENTITY_STATUS`` which is always set even if no user token is presented. This allows the underlying service to determine if a denial should use ``401 Unauthenticated`` or ``403 Forbidden``. HTTP_X_DOMAIN_ID, HTTP_X_SERVICE_DOMAIN_ID Identity service managed unique identifier, string. Only present if this is a domain-scoped token. HTTP_X_DOMAIN_NAME, HTTP_X_SERVICE_DOMAIN_NAME Unique domain name, string. Only present if this is a domain-scoped token. HTTP_X_PROJECT_ID, HTTP_X_SERVICE_PROJECT_ID Identity service managed unique identifier, string. Only present if this is a project-scoped token. HTTP_X_PROJECT_NAME, HTTP_X_SERVICE_PROJECT_NAME Project name, unique within owning domain, string. Only present if this is a project-scoped token. HTTP_X_PROJECT_DOMAIN_ID, HTTP_X_SERVICE_PROJECT_DOMAIN_ID Identity service managed unique identifier of owning domain of project, string. Only present if this is a project-scoped v3 token. If this variable is set, this indicates that the PROJECT_NAME can only be assumed to be unique within this domain. HTTP_X_PROJECT_DOMAIN_NAME, HTTP_X_SERVICE_PROJECT_DOMAIN_NAME Name of owning domain of project, string. Only present if this is a project-scoped v3 token. If this variable is set, this indicates that the PROJECT_NAME can only be assumed to be unique within this domain. HTTP_X_USER_ID, HTTP_X_SERVICE_USER_ID Identity-service managed unique identifier, string. HTTP_X_USER_NAME, HTTP_X_SERVICE_USER_NAME User identifier, unique within owning domain, string. HTTP_X_USER_DOMAIN_ID, HTTP_X_SERVICE_USER_DOMAIN_ID Identity service managed unique identifier of owning domain of user, string. If this variable is set, this indicates that the USER_NAME can only be assumed to be unique within this domain. HTTP_X_USER_DOMAIN_NAME, HTTP_X_SERVICE_USER_DOMAIN_NAME Name of owning domain of user, string. If this variable is set, this indicates that the USER_NAME can only be assumed to be unique within this domain. HTTP_X_ROLES, HTTP_X_SERVICE_ROLES Comma delimited list of case-sensitive role names. HTTP_X_IS_ADMIN_PROJECT The string value 'True' or 'False' representing whether the user's token is scoped to the admin project. As historically there was no admin project this will default to True for tokens without this information to be backwards with existing policy files. HTTP_X_SERVICE_CATALOG service catalog (optional, JSON string). For compatibility reasons this catalog will always be in the V2 catalog format even if it is a v3 token. .. note:: This is an exception in that it contains 'SERVICE' but relates to a user token, not a service token. The existing user's catalog can be very large; it was decided not to present a catalog relating to the service token to avoid using more HTTP header space. HTTP_X_TENANT_ID *Deprecated* in favor of HTTP_X_PROJECT_ID. Identity service managed unique identifier, string. For v3 tokens, this will be set to the same value as HTTP_X_PROJECT_ID. HTTP_X_TENANT_NAME *Deprecated* in favor of HTTP_X_PROJECT_NAME. Project identifier, unique within owning domain, string. For v3 tokens, this will be set to the same value as HTTP_X_PROJECT_NAME. HTTP_X_TENANT *Deprecated* in favor of HTTP_X_TENANT_ID and HTTP_X_TENANT_NAME. Identity server-assigned unique identifier, string. For v3 tokens, this will be set to the same value as HTTP_X_PROJECT_ID. HTTP_X_USER *Deprecated* in favor of HTTP_X_USER_ID and HTTP_X_USER_NAME. User name, unique within owning domain, string. HTTP_X_ROLE *Deprecated* in favor of HTTP_X_ROLES. Will contain the same values as HTTP_X_ROLES. Environment Variables ^^^^^^^^^^^^^^^^^^^^^ These variables are set in the request environment for use by the downstream WSGI component. keystone.token_info Information about the token discovered in the process of validation. This may include extended information returned by the token validation call, as well as basic information about the project and user. keystone.token_auth A keystoneauth1 auth plugin that may be used with a :py:class:`keystoneauth1.session.Session`. This plugin will load the authentication data provided to auth_token middleware. Configuration ------------- auth_token middleware configuration can be in the main application's configuration file, e.g. in ``nova.conf``: .. code-block:: ini [keystone_authtoken] auth_plugin = password auth_url = http://keystone:35357/ username = nova user_domain_id = default password = whyarewestillusingpasswords project_name = service project_domain_id = default Configuration can also be in the ``api-paste.ini`` file with the same options, but this is discouraged. Swift ----- When deploy auth_token middleware with Swift, user may elect to use Swift memcache instead of the local auth_token memcache. Swift memcache is passed in from the request environment and it's identified by the ``swift.cache`` key. However it could be different, depending on deployment. To use Swift memcache, you must set the ``cache`` option to the environment key where the Swift cache object is stored. """ import binascii import copy import datetime import warnings from keystoneauth1 import access from keystoneauth1 import adapter from keystoneauth1 import discover from keystoneauth1 import exceptions as ksa_exceptions from keystoneauth1 import loading from keystoneauth1.loading import session as session_loading from keystoneclient.common import cms from keystoneclient import exceptions as ksc_exceptions import oslo_cache from oslo_config import cfg from oslo_log import log as logging from oslo_serialization import jsonutils import webob.dec from keystonemiddleware._common import config from keystonemiddleware.auth_token import _auth from keystonemiddleware.auth_token import _base from keystonemiddleware.auth_token import _cache from keystonemiddleware.auth_token import _exceptions as ksm_exceptions from keystonemiddleware.auth_token import _identity from keystonemiddleware.auth_token import _opts from keystonemiddleware.auth_token import _request from keystonemiddleware.auth_token import _revocations from keystonemiddleware.auth_token import _signing_dir from keystonemiddleware.auth_token import _user_plugin from keystonemiddleware.i18n import _ _LOG = logging.getLogger(__name__) _CACHE_INVALID_INDICATOR = 'invalid' oslo_cache.configure(cfg.CONF) AUTH_TOKEN_OPTS = [ (_base.AUTHTOKEN_GROUP, _opts._OPTS + _auth.OPTS + loading.get_auth_common_conf_options()) ] def list_opts(): """Return a list of oslo_config options available in auth_token middleware. The returned list includes all oslo_config options which may be registered at runtime by the project. Each element of the list is a tuple. The first element is the name of the group under which the list of elements in the second element will be registered. A group name of None corresponds to the [DEFAULT] group in config files. NOTE: This function is no longer used for oslo_config sample generation. Some services rely on this function for listing ALL (including deprecated) options and registering them into their own config objects which we do not want for sample config files. See: :py:func:`keystonemiddleware.auth_token._opts.list_opts` for sample config files. :returns: a list of (group_name, opts) tuples """ return [(g, copy.deepcopy(o)) for g, o in AUTH_TOKEN_OPTS] class _BIND_MODE(object): DISABLED = 'disabled' PERMISSIVE = 'permissive' STRICT = 'strict' REQUIRED = 'required' KERBEROS = 'kerberos' def _uncompress_pkiz(token): # TypeError If the signed_text is not zlib compressed binascii.Error if # signed_text has incorrect base64 padding (py34) try: return cms.pkiz_uncompress(token) except (TypeError, binascii.Error): raise ksm_exceptions.InvalidToken(token) class BaseAuthProtocol(object): """A base class for AuthProtocol token checking implementations. :param Callable app: The next application to call after middleware. :param logging.Logger log: The logging object to use for output. By default it will use a logger in the keystonemiddleware.auth_token namespace. :param str enforce_token_bind: The style of token binding enforcement to perform. """ # NOTE(jamielennox): Default to True and remove in Queens. This is a # compatibility flag to allow passing **kwargs to fetch_token(). This # is basically to allow compatibility with keystone's override. We will # assume all subclasses are ok with this being True in the Queens release. kwargs_to_fetch_token = False def __init__(self, app, log=_LOG, enforce_token_bind=_BIND_MODE.PERMISSIVE, service_token_roles=None, service_token_roles_required=False): self.log = log self._app = app self._enforce_token_bind = enforce_token_bind self._service_token_roles = set(service_token_roles or []) self._service_token_roles_required = service_token_roles_required self._service_token_warning_emitted = False @webob.dec.wsgify(RequestClass=_request._AuthTokenRequest) def __call__(self, req): """Handle incoming request.""" response = self.process_request(req) if response: return response response = req.get_response(self._app) return self.process_response(response) def process_request(self, request): """Process request. If this method returns a value then that value will be used as the response. The next application down the stack will not be executed and process_response will not be called. Otherwise, the next application down the stack will be executed and process_response will be called with the generated response. By default this method does not return a value. :param request: Incoming request :type request: _request.AuthTokenRequest """ user_auth_ref = None serv_auth_ref = None allow_expired = False if request.service_token: self.log.debug('Authenticating service token') try: _, serv_auth_ref = self._do_fetch_token(request.service_token) self._validate_token(serv_auth_ref) self._confirm_token_bind(serv_auth_ref, request) except ksm_exceptions.InvalidToken: self.log.info('Invalid service token') request.service_token_valid = False else: # FIXME(jamielennox): The new behaviour for service tokens is # that they have to pass the policy check to be allowed. # Previously any token was accepted here. For now we will # continue to mark service tokens as valid if they are valid # but we will only allow service role tokens to do # allow_expired. In future we should reject any token that # isn't a service token here. role_names = set(serv_auth_ref.role_names) check = self._service_token_roles.intersection(role_names) role_check_passed = bool(check) # if service_token_role_required then the service token is only # valid if the roles check out. Otherwise at this point it is # true because keystone has already validated it. if self._service_token_roles_required: request.service_token_valid = role_check_passed else: if not self._service_token_warning_emitted: self.log.warning('A valid token was submitted as ' 'a service token, but it was not ' 'a valid service token. This is ' 'incorrect but backwards ' 'compatible behaviour. This will ' 'be removed in future releases.') # prevent log spam on every single request self._service_token_warning_emitted = True request.service_token_valid = True # allow_expired always requires passing the role check. allow_expired = role_check_passed if request.user_token: self.log.debug('Authenticating user token') try: data, user_auth_ref = self._do_fetch_token( request.user_token, allow_expired=allow_expired) self._validate_token(user_auth_ref, allow_expired=allow_expired) if not request.service_token: self._confirm_token_bind(user_auth_ref, request) except ksm_exceptions.InvalidToken: self.log.info('Invalid user token') request.user_token_valid = False else: request.user_token_valid = True request.token_info = data request.token_auth = _user_plugin.UserAuthPlugin(user_auth_ref, serv_auth_ref) def _validate_token(self, auth_ref, allow_expired=False): """Perform the validation steps on the token. :param auth_ref: The token data :type auth_ref: keystoneauth1.access.AccessInfo :raises exc.InvalidToken: if token is rejected """ # 0 seconds of validity means it is invalid right now if (not allow_expired) and auth_ref.will_expire_soon(stale_duration=0): raise ksm_exceptions.InvalidToken(_('Token authorization failed')) def _do_fetch_token(self, token, **kwargs): """Helper method to fetch a token and convert it into an AccessInfo.""" # NOTE(edmondsw): strip the token to remove any whitespace that may # have been passed along in the header per bug 1689468 token = token.strip() if self.kwargs_to_fetch_token: data = self.fetch_token(token, **kwargs) else: m = _('Implementations of auth_token must set ' 'kwargs_to_fetch_token this will be the required and ' 'assumed in Queens.') warnings.warn(m) data = self.fetch_token(token) try: return data, access.create(body=data, auth_token=token) except Exception: self.log.warning('Invalid token contents.', exc_info=True) raise ksm_exceptions.InvalidToken(_('Token authorization failed')) def fetch_token(self, token, **kwargs): """Fetch the token data based on the value in the header. Retrieve the data associated with the token value that was in the header. This can be from PKI, contacting the identity server or whatever is required. :param str token: The token present in the request header. :param dict kwargs: Additional keyword arguments may be passed through here to support new features. If an implementation is not aware of how to use these arguments it should ignore them. :raises exc.InvalidToken: if token is invalid. :returns: The token data :rtype: dict """ raise NotImplementedError() def process_response(self, response): """Do whatever you'd like to the response. By default the response is returned unmodified. :param response: Response object :type response: ._request._AuthTokenResponse """ return response def _invalid_user_token(self, msg=False): # NOTE(jamielennox): use False as the default so that None is valid if msg is False: msg = _('Token authorization failed') raise ksm_exceptions.InvalidToken(msg) def _confirm_token_bind(self, auth_ref, req): if self._enforce_token_bind == _BIND_MODE.DISABLED: return # permissive and strict modes don't require there to be a bind permissive = self._enforce_token_bind in (_BIND_MODE.PERMISSIVE, _BIND_MODE.STRICT) if not auth_ref.bind: if permissive: # no bind provided and none required return else: self.log.info('No bind information present in token.') self._invalid_user_token() # get the named mode if bind_mode is not one of the predefined if permissive or self._enforce_token_bind == _BIND_MODE.REQUIRED: name = None else: name = self._enforce_token_bind if name and name not in auth_ref.bind: self.log.info('Named bind mode %s not in bind information', name) self._invalid_user_token() for bind_type, identifier in auth_ref.bind.items(): if bind_type == _BIND_MODE.KERBEROS: if req.auth_type != 'negotiate': self.log.info('Kerberos credentials required and ' 'not present.') self._invalid_user_token() if req.remote_user != identifier: self.log.info('Kerberos credentials do not match ' 'those in bind.') self._invalid_user_token() self.log.debug('Kerberos bind authentication successful.') elif self._enforce_token_bind == _BIND_MODE.PERMISSIVE: self.log.debug('Ignoring Unknown bind for permissive mode: ' '%(bind_type)s: %(identifier)s.', {'bind_type': bind_type, 'identifier': identifier}) else: self.log.info( 'Couldn`t verify unknown bind: %(bind_type)s: ' '%(identifier)s.', {'bind_type': bind_type, 'identifier': identifier}) self._invalid_user_token() class AuthProtocol(BaseAuthProtocol): """Middleware that handles authenticating client calls.""" _SIGNING_CERT_FILE_NAME = 'signing_cert.pem' _SIGNING_CA_FILE_NAME = 'cacert.pem' kwargs_to_fetch_token = True def __init__(self, app, conf): log = logging.getLogger(conf.get('log_name', __name__)) log.info('Starting Keystone auth_token middleware') self._conf = config.Config('auth_token', _base.AUTHTOKEN_GROUP, list_opts(), conf) if self._conf.oslo_conf_obj != cfg.CONF: oslo_cache.configure(self._conf.oslo_conf_obj) token_roles_required = self._conf.get('service_token_roles_required') if not token_roles_required: log.warning('AuthToken middleware is set with ' 'keystone_authtoken.service_token_roles_required ' 'set to False. This is backwards compatible but ' 'deprecated behaviour. Please set this to True.') super(AuthProtocol, self).__init__( app, log=log, enforce_token_bind=self._conf.get('enforce_token_bind'), service_token_roles=self._conf.get('service_token_roles'), service_token_roles_required=token_roles_required) # delay_auth_decision means we still allow unauthenticated requests # through and we let the downstream service make the final decision self._delay_auth_decision = self._conf.get('delay_auth_decision') self._include_service_catalog = self._conf.get( 'include_service_catalog') self._hash_algorithms = self._conf.get('hash_algorithms') self._auth = self._create_auth_plugin() self._session = self._create_session() self._identity_server = self._create_identity_server() self._www_authenticate_uri = self._conf.get('www_authenticate_uri') if not self._www_authenticate_uri: self._www_authenticate_uri = self._conf.get('auth_uri') if not self._www_authenticate_uri: self.log.warning( 'Configuring www_authenticate_uri to point to the public ' 'identity endpoint is required; clients may not be able to ' 'authenticate against an admin endpoint') # FIXME(dolph): drop support for this fallback behavior as # documented in bug 1207517. self._www_authenticate_uri = \ self._identity_server.www_authenticate_uri self._signing_directory = _signing_dir.SigningDirectory( directory_name=self._conf.get('signing_dir'), log=self.log) self._token_cache = self._token_cache_factory() revocation_cache_timeout = datetime.timedelta( seconds=self._conf.get('revocation_cache_time')) self._revocations = _revocations.Revocations(revocation_cache_timeout, self._signing_directory, self._identity_server, self._cms_verify, self.log) self._check_revocations_for_cached = self._conf.get( 'check_revocations_for_cached') def process_request(self, request): """Process request. Evaluate the headers in a request and attempt to authenticate the request. If authenticated then additional headers are added to the request for use by applications. If not authenticated the request will be rejected or marked unauthenticated depending on configuration. """ request.remove_auth_headers() self._token_cache.initialize(request.environ) resp = super(AuthProtocol, self).process_request(request) if resp: return resp if not request.user_token: # if no user token is present then that's an invalid request request.user_token_valid = False # NOTE(jamielennox): The service status is allowed to be missing if a # service token is not passed. If the service status is missing that's # a valid request. We should find a better way to expose this from the # request object. user_status = request.user_token and request.user_token_valid service_status = request.headers.get('X-Service-Identity-Status', 'Confirmed') if not (user_status and service_status == 'Confirmed'): if self._delay_auth_decision: self.log.debug('Deferring reject downstream') else: self.log.info('Rejecting request') message = _('The request you have made requires ' 'authentication.') body = {'error': { 'code': 401, 'title': 'Unauthorized', 'message': message, }} raise webob.exc.HTTPUnauthorized( body=jsonutils.dumps(body), headers=self._reject_auth_headers, charset='UTF-8', content_type='application/json') if request.user_token_valid: request.set_user_headers(request.token_auth.user) if self._include_service_catalog: request.set_service_catalog_headers(request.token_auth.user) if request.token_auth: request.token_auth._auth = self._auth request.token_auth._session = self._session if request.service_token and request.service_token_valid: request.set_service_headers(request.token_auth.service) if self.log.isEnabledFor(logging.DEBUG): self.log.debug('Received request from %s', request.token_auth._log_format) def process_response(self, response): """Process Response. Add ``WWW-Authenticate`` headers to requests that failed with ``401 Unauthenticated`` so users know where to authenticate for future requests. """ if response.status_int == 401: response.headers.extend(self._reject_auth_headers) return response @property def _reject_auth_headers(self): header_val = 'Keystone uri=\'%s\'' % self._www_authenticate_uri return [('WWW-Authenticate', header_val)] def _token_hashes(self, token): """Generate a list of hashes that the current token may be cached as. With PKI tokens we have multiple hashing algorithms that we test with revocations. This generates that whole list. The first element of this list is the preferred algorithm and is what new cache values should be saved as. :param str token: The token being presented by a user. :returns: list of str token hashes. """ if cms.is_asn1_token(token) or cms.is_pkiz(token): return list(cms.cms_hash_token(token, mode=algo) for algo in self._hash_algorithms) else: return [token] def _cache_get_hashes(self, token_hashes): """Check if the token is cached already. Functions takes a list of hashes that might be in the cache and matches the first one that is present. If nothing is found in the cache it returns None. :returns: token data if found else None. """ for token in token_hashes: cached = self._token_cache.get(token) if cached: return cached def fetch_token(self, token, allow_expired=False): """Retrieve a token from either a PKI bundle or the identity server. :param str token: token id :raises exc.InvalidToken: if token is rejected """ data = None token_hashes = None try: token_hashes = self._token_hashes(token) cached = self._cache_get_hashes(token_hashes) if cached: if cached == _CACHE_INVALID_INDICATOR: self.log.debug('Cached token is marked unauthorized') raise ksm_exceptions.InvalidToken() if self._check_revocations_for_cached: # A token might have been revoked, regardless of initial # mechanism used to validate it, and needs to be checked. self._revocations.check(token_hashes) # NOTE(jamielennox): Cached values used to be stored as a tuple # of data and expiry time. They no longer are but we have to # allow some time to transition the old format so if it's a # tuple just use the data. if len(cached) == 2: cached = cached[0] data = cached else: data = self._validate_offline(token, token_hashes) if not data: data = self._identity_server.verify_token( token, allow_expired=allow_expired) self._token_cache.set(token_hashes[0], data) except (ksa_exceptions.ConnectFailure, ksa_exceptions.RequestTimeout, ksm_exceptions.RevocationListError, ksm_exceptions.ServiceError) as e: self.log.critical('Unable to validate token: %s', e) raise webob.exc.HTTPServiceUnavailable() except ksm_exceptions.InvalidToken: self.log.debug('Token validation failure.', exc_info=True) if token_hashes: self._token_cache.set(token_hashes[0], _CACHE_INVALID_INDICATOR) self.log.warning('Authorization failed for token') raise return data def _validate_offline(self, token, token_hashes): if cms.is_pkiz(token): token_data = _uncompress_pkiz(token) inform = cms.PKIZ_CMS_FORM elif cms.is_asn1_token(token): token_data = cms.token_to_cms(token) inform = cms.PKI_ASN1_FORM else: # Can't do offline validation for this type of token. return try: self._revocations.check(token_hashes) verified = self._cms_verify(token_data, inform) except ksc_exceptions.CertificateConfigError: self.log.warning('Fetch certificate config failed, ' 'fallback to online validation.') except ksm_exceptions.RevocationListError: self.log.warning('Fetch revocation list failed, ' 'fallback to online validation.') else: self.log.warning('auth_token middleware received a PKI/Z token. ' 'This form of token is deprecated and has been ' 'removed from keystone server and will be ' 'removed from auth_token middleware in the Rocky ' 'release. Please contact your administrator ' 'about upgrading keystone and the token format.') data = jsonutils.loads(verified) audit_ids = None if 'access' in data: # It's a v2 token. audit_ids = data['access']['token'].get('audit_ids') else: # It's a v3 token audit_ids = data['token'].get('audit_ids') if audit_ids: self._revocations.check_by_audit_id(audit_ids) return data def _validate_token(self, auth_ref, **kwargs): super(AuthProtocol, self)._validate_token(auth_ref, **kwargs) if auth_ref.version == 'v2.0' and not auth_ref.project_id: msg = _('Unable to determine service tenancy.') raise ksm_exceptions.InvalidToken(msg) def _cms_verify(self, data, inform=cms.PKI_ASN1_FORM): """Verify the signature of the provided data's IAW CMS syntax. If either of the certificate files might be missing, fetch them and retry. """ def verify(): try: signing_cert_path = self._signing_directory.calc_path( self._SIGNING_CERT_FILE_NAME) signing_ca_path = self._signing_directory.calc_path( self._SIGNING_CA_FILE_NAME) return cms.cms_verify(data, signing_cert_path, signing_ca_path, inform=inform).decode('utf-8') except (ksc_exceptions.CMSError, cms.subprocess.CalledProcessError) as err: self.log.warning('Verify error: %s', err) msg = _('Token authorization failed') raise ksm_exceptions.InvalidToken(msg) try: return verify() except ksc_exceptions.CertificateConfigError: # the certs might be missing; unconditionally fetch to avoid racing self._fetch_signing_cert() self._fetch_ca_cert() try: # retry with certs in place return verify() except ksc_exceptions.CertificateConfigError as err: # if this is still occurring, something else is wrong and we # need err.output to identify the problem self.log.error('CMS Verify output: %s', err.output) raise def _fetch_signing_cert(self): self._signing_directory.write_file( self._SIGNING_CERT_FILE_NAME, self._identity_server.fetch_signing_cert()) def _fetch_ca_cert(self): self._signing_directory.write_file( self._SIGNING_CA_FILE_NAME, self._identity_server.fetch_ca_cert()) def _create_auth_plugin(self): # NOTE(jamielennox): Ideally this would use load_from_conf_options # however that is not possible because we have to support the override # pattern we use in _conf.get. This function therefore does a manual # version of load_from_conf_options with the fallback plugin inline. group = self._conf.get('auth_section') or _base.AUTHTOKEN_GROUP # NOTE(jamielennox): auth_plugin was deprecated to auth_type. _conf.get # doesn't handle that deprecation in the case of conf dict options so # we have to manually check the value plugin_name = (self._conf.get('auth_type', group=group) or self._conf.paste_overrides.get('auth_plugin')) if not plugin_name: return _auth.AuthTokenPlugin( log=self.log, auth_admin_prefix=self._conf.get('auth_admin_prefix', group=group), auth_host=self._conf.get('auth_host', group=group), auth_port=self._conf.get('auth_port', group=group), auth_protocol=self._conf.get('auth_protocol', group=group), identity_uri=self._conf.get('identity_uri', group=group), admin_token=self._conf.get('admin_token', group=group), admin_user=self._conf.get('admin_user', group=group), admin_password=self._conf.get('admin_password', group=group), admin_tenant_name=self._conf.get('admin_tenant_name', group=group) ) # Plugin option registration is normally done as part of the load_from # function rather than the register function so copy here. plugin_loader = loading.get_plugin_loader(plugin_name) plugin_opts = loading.get_auth_plugin_conf_options(plugin_loader) self._conf.oslo_conf_obj.register_opts(plugin_opts, group=group) getter = lambda opt: self._conf.get(opt.dest, group=group) return plugin_loader.load_from_options_getter(getter) def _create_session(self, **kwargs): # NOTE(jamielennox): Loading Session here should be exactly the # same as calling Session.load_from_conf_options(CONF, GROUP) # however we can't do that because we have to use _conf.get to # support the paste.ini options. kwargs.setdefault('cert', self._conf.get('certfile')) kwargs.setdefault('key', self._conf.get('keyfile')) kwargs.setdefault('cacert', self._conf.get('cafile')) kwargs.setdefault('insecure', self._conf.get('insecure')) kwargs.setdefault('timeout', self._conf.get('http_connect_timeout')) kwargs.setdefault('user_agent', self._conf.user_agent) return session_loading.Session().load_from_options(**kwargs) def _create_identity_server(self): adap = adapter.Adapter( self._session, auth=self._auth, service_type='identity', interface='admin', region_name=self._conf.get('region_name'), connect_retries=self._conf.get('http_request_max_retries')) auth_version = self._conf.get('auth_version') if auth_version is not None: auth_version = discover.normalize_version_number(auth_version) return _identity.IdentityServer( self.log, adap, include_service_catalog=self._include_service_catalog, requested_auth_version=auth_version) def _create_oslo_cache(self): # having this as a function makes test mocking easier region = oslo_cache.create_region() oslo_cache.configure_cache_region(self._conf.oslo_conf_obj, region) return region def _token_cache_factory(self): security_strategy = self._conf.get('memcache_security_strategy') cache_kwargs = dict( cache_time=int(self._conf.get('token_cache_time')), env_cache_name=self._conf.get('cache'), memcached_servers=self._conf.get('memcached_servers'), use_advanced_pool=self._conf.get('memcache_use_advanced_pool'), dead_retry=self._conf.get('memcache_pool_dead_retry'), maxsize=self._conf.get('memcache_pool_maxsize'), unused_timeout=self._conf.get('memcache_pool_unused_timeout'), conn_get_timeout=self._conf.get('memcache_pool_conn_get_timeout'), socket_timeout=self._conf.get('memcache_pool_socket_timeout'), ) if security_strategy.lower() != 'none': secret_key = self._conf.get('memcache_secret_key') return _cache.SecureTokenCache(self.log, security_strategy, secret_key, **cache_kwargs) else: return _cache.TokenCache(self.log, **cache_kwargs) def filter_factory(global_conf, **local_conf): """Return a WSGI filter app for use with paste.deploy.""" conf = global_conf.copy() conf.update(local_conf) def auth_filter(app): return AuthProtocol(app, conf) return auth_filter def app_factory(global_conf, **local_conf): conf = global_conf.copy() conf.update(local_conf) return AuthProtocol(None, conf) # NOTE(jamielennox): Maintained here for public API compatibility. InvalidToken = ksm_exceptions.InvalidToken ServiceError = ksm_exceptions.ServiceError ConfigurationError = ksm_exceptions.ConfigurationError RevocationListError = ksm_exceptions.RevocationListError keystonemiddleware-4.21.0/keystonemiddleware/auth_token/_user_plugin.py0000666000175100017510000000557213225160624026642 0ustar zuulzuul00000000000000# Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. from keystoneauth1.identity import base as base_identity def _log_format(auth_ref): roles = ','.join(auth_ref.role_names) return 'user_id %s, project_id %s, roles %s' % (auth_ref.user_id, auth_ref.project_id, roles) class UserAuthPlugin(base_identity.BaseIdentityPlugin): """The incoming authentication credentials. A plugin that represents the incoming user credentials. This can be consumed by applications. This object is not expected to be constructed directly by users. It is created and passed by auth_token middleware and then can be used as the authentication plugin when communicating via a session. """ def __init__(self, user_auth_ref, serv_auth_ref, session=None, auth=None): super(UserAuthPlugin, self).__init__(reauthenticate=False) self.user = user_auth_ref self.service = serv_auth_ref # NOTE(jamielennox): adding a service token requires the original # session and auth plugin from auth_token self._session = session self._auth = auth @property def has_user_token(self): """Did this authentication request contained a user auth token.""" return self.user is not None @property def has_service_token(self): """Did this authentication request contained a service token.""" return self.service is not None def get_auth_ref(self, session, **kwargs): # NOTE(jamielennox): We will always use the auth_ref that was # calculated by the middleware. reauthenticate=False in __init__ should # ensure that this function is only called on the first access. return self.user @property def _log_format(self): msg = [] if self.has_user_token: msg.append('user: %s' % _log_format(self.user)) if self.has_service_token: msg.append('service: %s' % _log_format(self.service)) return ' '.join(msg) def get_headers(self, session, **kwargs): headers = super(UserAuthPlugin, self).get_headers(session, **kwargs) if headers is not None and self._session: token = self._session.get_token(auth=self._auth) if token: headers['X-Service-Token'] = token return headers keystonemiddleware-4.21.0/keystonemiddleware/auth_token/_base.py0000666000175100017510000000111113225160624025201 0ustar zuulzuul00000000000000# Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. AUTHTOKEN_GROUP = 'keystone_authtoken' keystonemiddleware-4.21.0/keystonemiddleware/auth_token/_request.py0000666000175100017510000002075513225160624025776 0ustar zuulzuul00000000000000# Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. import itertools from oslo_serialization import jsonutils import webob def _v3_to_v2_catalog(catalog): """Convert a catalog to v2 format. X_SERVICE_CATALOG must be specified in v2 format. If you get a token that is in v3 convert it. """ v2_services = [] for v3_service in catalog: # first copy over the entries we allow for the service v2_service = {'type': v3_service['type']} try: v2_service['name'] = v3_service['name'] except KeyError: # nosec # v3 service doesn't have a name, so v2_service doesn't either. pass # now convert the endpoints. Because in v3 we specify region per # URL not per group we have to collect all the entries of the same # region together before adding it to the new service. regions = {} for v3_endpoint in v3_service.get('endpoints', []): region_name = v3_endpoint.get('region') try: region = regions[region_name] except KeyError: region = {'region': region_name} if region_name else {} regions[region_name] = region interface_name = v3_endpoint['interface'].lower() + 'URL' region[interface_name] = v3_endpoint['url'] v2_service['endpoints'] = list(regions.values()) v2_services.append(v2_service) return v2_services def _is_admin_project(auth_ref): """Return an appropriate header value for X-Is-Admin-Project. Headers must be strings so we can't simply pass a boolean value through so return a True or False string to signal the admin project. """ return 'True' if auth_ref.is_admin_project else 'False' # NOTE(jamielennox): this should probably be moved into its own file, but at # the moment there's no real logic here so just keep it locally. class _AuthTokenResponse(webob.Response): default_content_type = None # prevents webob assigning a content type class _AuthTokenRequest(webob.Request): ResponseClass = _AuthTokenResponse _HEADER_TEMPLATE = { 'X%s-Domain-Id': 'domain_id', 'X%s-Domain-Name': 'domain_name', 'X%s-Project-Id': 'project_id', 'X%s-Project-Name': 'project_name', 'X%s-Project-Domain-Id': 'project_domain_id', 'X%s-Project-Domain-Name': 'project_domain_name', 'X%s-User-Id': 'user_id', 'X%s-User-Name': 'username', 'X%s-User-Domain-Id': 'user_domain_id', 'X%s-User-Domain-Name': 'user_domain_name', } _ROLES_TEMPLATE = 'X%s-Roles' _USER_HEADER_PREFIX = '' _SERVICE_HEADER_PREFIX = '-Service' _USER_STATUS_HEADER = 'X-Identity-Status' _SERVICE_STATUS_HEADER = 'X-Service-Identity-Status' _ADMIN_PROJECT_HEADER = 'X-Is-Admin-Project' _SERVICE_CATALOG_HEADER = 'X-Service-Catalog' _TOKEN_AUTH = 'keystone.token_auth' _TOKEN_INFO = 'keystone.token_info' _CONFIRMED = 'Confirmed' _INVALID = 'Invalid' # header names that have been deprecated in favour of something else. _DEPRECATED_HEADER_MAP = { 'X-Role': 'X-Roles', 'X-User': 'X-User-Name', 'X-Tenant-Id': 'X-Project-Id', 'X-Tenant-Name': 'X-Project-Name', 'X-Tenant': 'X-Project-Name', } def _confirmed(cls, value): return cls._CONFIRMED if value else cls._INVALID @property def user_token_valid(self): """User token is marked as valid. :returns: True if the X-Identity-Status header is set to Confirmed. :rtype: bool """ return self.headers[self._USER_STATUS_HEADER] == self._CONFIRMED @user_token_valid.setter def user_token_valid(self, value): self.headers[self._USER_STATUS_HEADER] = self._confirmed(value) @property def user_token(self): return self.headers.get('X-Auth-Token', self.headers.get('X-Storage-Token')) @property def service_token_valid(self): """Service token is marked as valid. :returns: True if the X-Service-Identity-Status header is set to Confirmed. :rtype: bool """ return self.headers[self._SERVICE_STATUS_HEADER] == self._CONFIRMED @service_token_valid.setter def service_token_valid(self, value): self.headers[self._SERVICE_STATUS_HEADER] = self._confirmed(value) @property def service_token(self): return self.headers.get('X-Service-Token') def _set_auth_headers(self, auth_ref, prefix): names = ','.join(auth_ref.role_names) self.headers[self._ROLES_TEMPLATE % prefix] = names for header_tmplt, attr in self._HEADER_TEMPLATE.items(): self.headers[header_tmplt % prefix] = getattr(auth_ref, attr) def set_user_headers(self, auth_ref): """Convert token object into headers. Build headers that represent authenticated user - see main doc info at start of __init__ file for details of headers to be defined """ self._set_auth_headers(auth_ref, self._USER_HEADER_PREFIX) self.headers[self._ADMIN_PROJECT_HEADER] = _is_admin_project(auth_ref) for k, v in self._DEPRECATED_HEADER_MAP.items(): self.headers[k] = self.headers[v] def set_service_catalog_headers(self, auth_ref): """Convert service catalog from token object into headers. Build headers that represent the catalog - see main doc info at start of __init__ file for details of headers to be defined :param auth_ref: The token data :type auth_ref: keystoneauth.access.AccessInfo """ if not auth_ref.has_service_catalog(): self.headers.pop(self._SERVICE_CATALOG_HEADER, None) return catalog = auth_ref.service_catalog.catalog if auth_ref.version == 'v3': catalog = _v3_to_v2_catalog(catalog) c = jsonutils.dumps(catalog) self.headers[self._SERVICE_CATALOG_HEADER] = c def set_service_headers(self, auth_ref): """Convert token object into service headers. Build headers that represent authenticated user - see main doc info at start of __init__ file for details of headers to be defined """ self._set_auth_headers(auth_ref, self._SERVICE_HEADER_PREFIX) def _all_auth_headers(self): """All the authentication headers that can be set on the request.""" yield self._SERVICE_CATALOG_HEADER yield self._USER_STATUS_HEADER yield self._SERVICE_STATUS_HEADER yield self._ADMIN_PROJECT_HEADER for header in self._DEPRECATED_HEADER_MAP: yield header prefixes = (self._USER_HEADER_PREFIX, self._SERVICE_HEADER_PREFIX) for tmpl, prefix in itertools.product(self._HEADER_TEMPLATE, prefixes): yield tmpl % prefix for prefix in prefixes: yield self._ROLES_TEMPLATE % prefix def remove_auth_headers(self): """Remove headers so a user can't fake authentication.""" for header in self._all_auth_headers(): self.headers.pop(header, None) @property def auth_type(self): """The authentication type that was performed by the web server. The returned string value is always lower case. :returns: The AUTH_TYPE environ string or None if not present. :rtype: str or None """ try: auth_type = self.environ['AUTH_TYPE'] except KeyError: return None else: return auth_type.lower() @property def token_auth(self): """The auth plugin that will be associated with this request.""" return self.environ.get(self._TOKEN_AUTH) @token_auth.setter def token_auth(self, v): self.environ[self._TOKEN_AUTH] = v @property def token_info(self): """The raw token dictionary retrieved by the middleware.""" return self.environ.get(self._TOKEN_INFO) @token_info.setter def token_info(self, v): self.environ[self._TOKEN_INFO] = v keystonemiddleware-4.21.0/keystonemiddleware/auth_token/_cache.py0000666000175100017510000002675113225160624025353 0ustar zuulzuul00000000000000# Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. import contextlib import hashlib from oslo_serialization import jsonutils from oslo_utils import timeutils import six from keystonemiddleware.auth_token import _exceptions as exc from keystonemiddleware.auth_token import _memcache_crypt as memcache_crypt from keystonemiddleware.i18n import _ def _hash_key(key): """Turn a set of arguments into a SHA256 hash. Using a known-length cache key is important to ensure that memcache maximum key length is not exceeded causing failures to validate. """ if isinstance(key, six.text_type): # NOTE(morganfainberg): Ensure we are always working with a bytes # type required for the hasher. In python 2.7 it is possible to # get a text_type (unicode). In python 3.4 all strings are # text_type and not bytes by default. This encode coerces the # text_type to the appropriate bytes values. key = key.encode('utf-8') return hashlib.sha256(key).hexdigest() class _EnvCachePool(object): """A cache pool that has been passed through ENV variables.""" def __init__(self, cache): self._environment_cache = cache @contextlib.contextmanager def reserve(self): """Context manager to manage a pooled cache reference.""" yield self._environment_cache class _CachePool(list): """A lazy pool of cache references.""" def __init__(self, memcached_servers, log): self._memcached_servers = memcached_servers if not self._memcached_servers: log.warning( "Using the in-process token cache is deprecated as of the " "4.2.0 release and may be removed in the 5.0.0 release or " "the 'O' development cycle. The in-process cache causes " "inconsistent results and high memory usage. When the feature " "is removed the auth_token middleware will not cache tokens " "by default which may result in performance issues. It is " "recommended to use memcache for the auth_token token cache " "by setting the memcached_servers option.") @contextlib.contextmanager def reserve(self): """Context manager to manage a pooled cache reference.""" try: c = self.pop() except IndexError: # the pool is empty, so we need to create a new client if self._memcached_servers: import memcache c = memcache.Client(self._memcached_servers, debug=0) else: c = _FakeClient() try: yield c finally: self.append(c) class _MemcacheClientPool(object): """An advanced memcached client pool that is eventlet safe.""" def __init__(self, memcache_servers, **kwargs): # NOTE(sileht): This will import python-memcached, we don't want # it as hard dependency, so lazy load it. from oslo_cache import _memcache_pool self._pool = _memcache_pool.MemcacheClientPool(memcache_servers, **kwargs) @contextlib.contextmanager def reserve(self): with self._pool.get() as client: yield client class TokenCache(object): """Encapsulates the auth_token token cache functionality. auth_token caches tokens that it's seen so that when a token is re-used the middleware doesn't have to do a more expensive operation (like going to the identity server) to validate the token. initialize() must be called before calling the other methods. Store data in the cache store. Check if a token is in the cache and retrieve it using get(). """ _CACHE_KEY_TEMPLATE = 'tokens/%s' def __init__(self, log, cache_time=None, env_cache_name=None, memcached_servers=None, use_advanced_pool=False, **kwargs): self._LOG = log self._cache_time = cache_time self._env_cache_name = env_cache_name self._memcached_servers = memcached_servers self._use_advanced_pool = use_advanced_pool self._memcache_pool_options = kwargs self._cache_pool = None self._initialized = False def _get_cache_pool(self, cache): if cache: return _EnvCachePool(cache) elif self._use_advanced_pool and self._memcached_servers: return _MemcacheClientPool(self._memcached_servers, **self._memcache_pool_options) else: return _CachePool(self._memcached_servers, self._LOG) def initialize(self, env): if self._initialized: return self._cache_pool = self._get_cache_pool(env.get(self._env_cache_name)) self._initialized = True def _get_cache_key(self, token_id): """Get a unique key for this token id. Turn the token_id into something that can uniquely identify that token in a key value store. As this is generally the first function called in a key lookup this function also returns a context object. This context object is not modified or used by the Cache object but is passed back on subsequent functions so that decryption or other data can be shared throughout a cache lookup. :param str token_id: The unique token id. :returns: A tuple of a string key and an implementation specific context object """ # NOTE(jamielennox): in the basic implementation there is no need for # a context so just pass None as it will only get passed back later. unused_context = None return self._CACHE_KEY_TEMPLATE % _hash_key(token_id), unused_context def _deserialize(self, data, context): """Deserialize data from the cache back into python objects. Take data retrieved from the cache and return an appropriate python dictionary. :param str data: The data retrieved from the cache. :param object context: The context that was returned from _get_cache_key. :returns: The python object that was saved. """ # memory cache will handle deserialization for us return data def _serialize(self, data, context): """Serialize data so that it can be saved to the cache. Take python objects and serialize them so that they can be saved into the cache. :param object data: The data to be cached. :param object context: The context that was returned from _get_cache_key. :returns: The python object that was saved. """ # memory cache will handle serialization for us return data def get(self, token_id): """Return token information from cache. If token is invalid raise exc.InvalidToken return token only if fresh (not expired). """ if not token_id: # Nothing to do return key, context = self._get_cache_key(token_id) with self._cache_pool.reserve() as cache: serialized = cache.get(key) if serialized is None: return None if isinstance(serialized, six.text_type): serialized = serialized.encode('utf8') data = self._deserialize(serialized, context) if not isinstance(data, six.string_types): data = data.decode('utf-8') return jsonutils.loads(data) def set(self, token_id, data): """Store value into memcache.""" data = jsonutils.dumps(data) if isinstance(data, six.text_type): data = data.encode('utf-8') cache_key, context = self._get_cache_key(token_id) data_to_store = self._serialize(data, context) with self._cache_pool.reserve() as cache: cache.set(cache_key, data_to_store, time=self._cache_time) class SecureTokenCache(TokenCache): """A token cache that stores tokens encrypted. A more secure version of TokenCache that will encrypt tokens before caching them. """ def __init__(self, log, security_strategy, secret_key, **kwargs): super(SecureTokenCache, self).__init__(log, **kwargs) if not secret_key: msg = _('memcache_secret_key must be defined when a ' 'memcache_security_strategy is defined') raise exc.ConfigurationError(msg) if isinstance(security_strategy, six.string_types): security_strategy = security_strategy.encode('utf-8') if isinstance(secret_key, six.string_types): secret_key = secret_key.encode('utf-8') self._security_strategy = security_strategy self._secret_key = secret_key def _get_cache_key(self, token_id): context = memcache_crypt.derive_keys(token_id, self._secret_key, self._security_strategy) key = self._CACHE_KEY_TEMPLATE % memcache_crypt.get_cache_key(context) return key, context def _deserialize(self, data, context): try: # unprotect_data will return None if raw_cached is None return memcache_crypt.unprotect_data(context, data) except Exception: msg = 'Failed to decrypt/verify cache data' self._LOG.exception(msg) # this should have the same effect as data not # found in cache return None def _serialize(self, data, context): return memcache_crypt.protect_data(context, data) class _FakeClient(object): """Replicates a tiny subset of memcached client interface.""" def __init__(self, *args, **kwargs): # Ignores the passed in args self.cache = {} def get(self, key): """Retrieve the value for a key or None. This expunges expired keys during each get. """ now = timeutils.utcnow_ts() for k in list(self.cache): (timeout, _value) = self.cache[k] if timeout and now >= timeout: del self.cache[k] return self.cache.get(key, (0, None))[1] def set(self, key, value, time=0, min_compress_len=0): """Set the value for a key.""" timeout = 0 if time != 0: timeout = timeutils.utcnow_ts() + time self.cache[key] = (timeout, value) return True def add(self, key, value, time=0, min_compress_len=0): """Set the value for a key if it doesn't exist.""" if self.get(key) is not None: return False return self.set(key, value, time, min_compress_len) def incr(self, key, delta=1): """Increment the value for a key.""" value = self.get(key) if value is None: return None new_value = int(value) + delta self.cache[key] = (self.cache[key][0], str(new_value)) return new_value def delete(self, key, time=0): """Delete the value associated with a key.""" if key in self.cache: del self.cache[key] keystonemiddleware-4.21.0/keystonemiddleware/auth_token/_memcache_crypt.py0000666000175100017510000001555013225160624027266 0ustar zuulzuul00000000000000# Copyright 2010-2013 OpenStack Foundation # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or # implied. # See the License for the specific language governing permissions and # limitations under the License. """ Utilities for memcache encryption and integrity check. Data should be serialized before entering these functions. Encryption has a dependency on the cryptography module. If cryptography is not available, CryptoUnavailableError will be raised. This module will not be called unless signing or encryption is enabled in the config. It will always validate signatures, and will decrypt data if encryption is enabled. It is not valid to mix protection modes. """ import base64 import functools import hashlib import hmac import math import os import six from keystonemiddleware.i18n import _ from oslo_utils import secretutils try: from cryptography.hazmat import backends as crypto_backends from cryptography.hazmat.primitives import ciphers from cryptography.hazmat.primitives.ciphers import algorithms from cryptography.hazmat.primitives.ciphers import modes from cryptography.hazmat.primitives import padding except ImportError: ciphers = None HASH_FUNCTION = hashlib.sha384 DIGEST_LENGTH = HASH_FUNCTION().digest_size DIGEST_SPLIT = DIGEST_LENGTH // 3 DIGEST_LENGTH_B64 = 4 * int(math.ceil(DIGEST_LENGTH / 3.0)) class InvalidMacError(Exception): """raise when unable to verify MACed data. This usually indicates that data had been expectedly modified in memcache. """ pass class DecryptError(Exception): """raise when unable to decrypt encrypted data.""" pass class CryptoUnavailableError(Exception): """raise when Python Crypto module is not available.""" pass def assert_crypto_availability(f): """Ensure cryptography module is available.""" @functools.wraps(f) def wrapper(*args, **kwds): if ciphers is None: raise CryptoUnavailableError() return f(*args, **kwds) return wrapper def derive_keys(token, secret, strategy): """Derive keys for MAC and ENCRYPTION from the user-provided secret. The resulting keys should be passed to the protect and unprotect functions. As suggested by NIST Special Publication 800-108, this uses the first 128 bits from the sha384 KDF for the obscured cache key value, the second 128 bits for the message authentication key and the remaining 128 bits for the encryption key. This approach is faster than computing a separate hmac as the KDF for each desired key. """ if not isinstance(secret, six.binary_type): secret = secret.encode() if not isinstance(token, six.binary_type): token = token.encode() if not isinstance(strategy, six.binary_type): strategy = strategy.encode() digest = hmac.new(secret, token + strategy, HASH_FUNCTION).digest() return {'CACHE_KEY': digest[:DIGEST_SPLIT], 'MAC': digest[DIGEST_SPLIT: 2 * DIGEST_SPLIT], 'ENCRYPTION': digest[2 * DIGEST_SPLIT:], 'strategy': strategy} def sign_data(key, data): """Sign the data using the defined function and the derived key.""" if not isinstance(key, six.binary_type): key = key.encode() if not isinstance(data, six.binary_type): data = data.encode() mac = hmac.new(key, data, HASH_FUNCTION).digest() return base64.b64encode(mac) @assert_crypto_availability def encrypt_data(key, data): """Encrypt the data with the given secret key. Padding is n bytes of the value n, where 1 <= n <= blocksize. """ iv = os.urandom(16) cipher = ciphers.Cipher( algorithms.AES(key), modes.CBC(iv), backend=crypto_backends.default_backend()) # AES algorithm uses block size of 16 bytes = 128 bits, defined in # algorithms.AES.block_size. Previously, we manually padded this using # six.int2byte(padding) * padding. Using ``cryptography``, we will # analogously use hazmat.primitives.padding to pad it to # the 128-bit block size. padder = padding.PKCS7(algorithms.AES.block_size).padder() padded_data = padder.update(data) + padder.finalize() encryptor = cipher.encryptor() return iv + encryptor.update(padded_data) + encryptor.finalize() def decrypt_data(key, data): """Decrypt the data with the given secret key.""" iv = data[:16] cipher = ciphers.Cipher( algorithms.AES(key), modes.CBC(iv), backend=crypto_backends.default_backend()) try: decryptor = cipher.decryptor() result = decryptor.update(data[16:]) + decryptor.finalize() except Exception: raise DecryptError(_('Encrypted data appears to be corrupted.')) # Strip the last n padding bytes where n is the last value in # the plaintext unpadder = padding.PKCS7(algorithms.AES.block_size).unpadder() return unpadder.update(result) + unpadder.finalize() def protect_data(keys, data): """Serialize data given a dict of keys. Given keys and serialized data, returns an appropriately protected string suitable for storage in the cache. """ if keys['strategy'] == b'ENCRYPT': data = encrypt_data(keys['ENCRYPTION'], data) encoded_data = base64.b64encode(data) signature = sign_data(keys['MAC'], encoded_data) return signature + encoded_data def unprotect_data(keys, signed_data): """De-serialize data given a dict of keys. Given keys and cached string data, verifies the signature, decrypts if necessary, and returns the original serialized data. """ # cache backends return None when no data is found. We don't mind # that this particular special value is unsigned. if signed_data is None: return None # First we calculate the signature provided_mac = signed_data[:DIGEST_LENGTH_B64] calculated_mac = sign_data( keys['MAC'], signed_data[DIGEST_LENGTH_B64:]) # Then verify that it matches the provided value if not secretutils.constant_time_compare(provided_mac, calculated_mac): raise InvalidMacError(_('Invalid MAC; data appears to be corrupted.')) data = base64.b64decode(signed_data[DIGEST_LENGTH_B64:]) # then if necessary decrypt the data if keys['strategy'] == b'ENCRYPT': data = decrypt_data(keys['ENCRYPTION'], data) return data def get_cache_key(keys): """Return a cache key. Given keys generated by derive_keys(), returns a base64 encoded value suitable for use as a cache key in memcached. """ return base64.b64encode(keys['CACHE_KEY']) keystonemiddleware-4.21.0/keystonemiddleware/__init__.py0000666000175100017510000000000013225160624023522 0ustar zuulzuul00000000000000keystonemiddleware-4.21.0/keystonemiddleware/echo/0000775000175100017510000000000013225161130022330 5ustar zuulzuul00000000000000keystonemiddleware-4.21.0/keystonemiddleware/echo/__main__.py0000666000175100017510000000025013225160624024430 0ustar zuulzuul00000000000000from keystonemiddleware.echo import service try: service.EchoService() except KeyboardInterrupt: # nosec # The user wants this application to exit. pass keystonemiddleware-4.21.0/keystonemiddleware/echo/__init__.py0000666000175100017510000000000013225160624024440 0ustar zuulzuul00000000000000keystonemiddleware-4.21.0/keystonemiddleware/echo/service.py0000666000175100017510000000327113225160624024356 0ustar zuulzuul00000000000000# Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or # implied. # See the License for the specific language governing permissions and # limitations under the License. """ Run the echo service. The echo service can be run on port 8000 by executing the following:: $ python -m keystonemiddleware.echo When the ``auth_token`` module authenticates a request, the echo service will respond with all the environment variables presented to it by this module. """ from wsgiref import simple_server from oslo_serialization import jsonutils from keystonemiddleware import auth_token def echo_app(environ, start_response): """A WSGI application that echoes the CGI environment back to the user.""" start_response('200 OK', [('Content-Type', 'application/json')]) environment = dict((k, v) for k, v in environ.items() if k.startswith('HTTP_X_')) yield jsonutils.dumps(environment) class EchoService(object): """Runs an instance of the echo app on init.""" def __init__(self): # hardcode any non-default configuration here conf = {'auth_protocol': 'http', 'admin_token': 'ADMIN'} app = auth_token.AuthProtocol(echo_app, conf) server = simple_server.make_server('', 8000, app) print('Serving on port 8000 (Ctrl+C to end)...') server.serve_forever() keystonemiddleware-4.21.0/keystonemiddleware/i18n.py0000666000175100017510000000154213225160624022556 0ustar zuulzuul00000000000000# Copyright 2014 IBM Corp. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. """oslo.i18n integration module. See https://docs.openstack.org/oslo.i18n/latest/user/usage.html . """ import oslo_i18n as i18n _translators = i18n.TranslatorFactory(domain='keystonemiddleware') # The primary translation function using the well-known name "_" _ = _translators.primary keystonemiddleware-4.21.0/keystonemiddleware/s3_token.py0000666000175100017510000002247413225160624023533 0ustar zuulzuul00000000000000# Copyright 2012 OpenStack Foundation # Copyright 2010 United States Government as represented by the # Administrator of the National Aeronautics and Space Administration. # Copyright 2011,2012 Akira YOSHIYAMA # All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. # This source code is based ./auth_token.py and ./ec2_token.py. # See them for their copyright. """ S3 Token Middleware. This WSGI component: * Gets a request from the swift3 middleware with an S3 Authorization access key. * Validates s3 token in Keystone. * Transforms the account name to AUTH_%(tenant_name). """ import webob from oslo_log import log as logging from oslo_serialization import jsonutils from oslo_utils import strutils import requests import six PROTOCOL_NAME = 'S3 Token Authentication' class ServiceError(Exception): pass class S3Token(object): """Middleware that handles S3 authentication.""" def __init__(self, app, conf): """Common initialization code.""" self._app = app self._logger = logging.getLogger(conf.get('log_name', __name__)) self._logger.debug('Starting the %s component', PROTOCOL_NAME) self._reseller_prefix = conf.get('reseller_prefix', 'AUTH_') # where to find the auth service (we use this to validate tokens) self._request_uri = conf.get('www_authenticate_uri') auth_uri = conf.get('auth_uri') if not self._request_uri and auth_uri: self._logger.warning( "Use of the auth_uri option was deprecated " "in the Queens release in favor of www_authenticate_uri. This " "option will be removed in the S release.") self._request_uri = auth_uri if not self._request_uri: self._logger.warning( "Use of the auth_host, auth_port, and auth_protocol " "configuration options was deprecated in the Newton release " "in favor of www_authenticate_uri. These options will be " "removed in the S release.") auth_host = conf.get('auth_host') auth_port = int(conf.get('auth_port', 35357)) auth_protocol = conf.get('auth_protocol', 'https') self._request_uri = '%s://%s:%s' % (auth_protocol, auth_host, auth_port) # SSL insecure = strutils.bool_from_string(conf.get('insecure', False)) cert_file = conf.get('certfile') key_file = conf.get('keyfile') if insecure: self._verify = False elif cert_file and key_file: self._verify = (cert_file, key_file) elif cert_file: self._verify = cert_file else: self._verify = None def _deny_request(self, code): error_table = { 'AccessDenied': (401, 'Access denied'), 'InvalidURI': (400, 'Could not parse the specified URI'), } resp = webob.Response(content_type='text/xml') resp.status = error_table[code][0] error_msg = ('\r\n' '\r\n %s\r\n ' '%s\r\n\r\n' % (code, error_table[code][1])) if six.PY3: error_msg = error_msg.encode() resp.body = error_msg return resp def _json_request(self, creds_json): headers = {'Content-Type': 'application/json'} try: response = requests.post('%s/v2.0/s3tokens' % self._request_uri, headers=headers, data=creds_json, verify=self._verify) except requests.exceptions.RequestException as e: self._logger.info('HTTP connection exception: %s', e) resp = self._deny_request('InvalidURI') raise ServiceError(resp) if response.status_code < 200 or response.status_code >= 300: self._logger.debug('Keystone reply error: status=%s reason=%s', response.status_code, response.reason) resp = self._deny_request('AccessDenied') raise ServiceError(resp) return response def __call__(self, environ, start_response): """Handle incoming request. authenticate and send downstream.""" req = webob.Request(environ) self._logger.debug('Calling S3Token middleware.') try: parts = strutils.split_path(req.path, 1, 4, True) version, account, container, obj = parts except ValueError: msg = 'Not a path query, skipping.' self._logger.debug(msg) return self._app(environ, start_response) # Read request signature and access id. if 'Authorization' not in req.headers: msg = 'No Authorization header. skipping.' self._logger.debug(msg) return self._app(environ, start_response) token = req.headers.get('X-Auth-Token', req.headers.get('X-Storage-Token')) if not token: msg = 'You did not specify an auth or a storage token. skipping.' self._logger.debug(msg) return self._app(environ, start_response) auth_header = req.headers['Authorization'] try: access, signature = auth_header.split(' ')[-1].rsplit(':', 1) except ValueError: msg = 'You have an invalid Authorization header: %s' self._logger.debug(msg, auth_header) return self._deny_request('InvalidURI')(environ, start_response) # NOTE(chmou): This is to handle the special case with nova # when we have the option s3_affix_tenant. We will force it to # connect to another account than the one # authenticated. Before people start getting worried about # security, I should point that we are connecting with # username/token specified by the user but instead of # connecting to its own account we will force it to go to an # another account. In a normal scenario if that user don't # have the reseller right it will just fail but since the # reseller account can connect to every account it is allowed # by the swift_auth middleware. force_tenant = None if ':' in access: access, force_tenant = access.split(':') # Authenticate request. creds = {'credentials': {'access': access, 'token': token, 'signature': signature}} creds_json = jsonutils.dumps(creds) self._logger.debug('Connecting to Keystone sending this JSON: %s', creds_json) # NOTE(vish): We could save a call to keystone by having # keystone return token, tenant, user, and roles # from this call. # # NOTE(chmou): We still have the same problem we would need to # change token_auth to detect if we already # identified and not doing a second query and just # pass it through to swiftauth in this case. try: resp = self._json_request(creds_json) except ServiceError as e: resp = e.args[0] msg = 'Received error, exiting middleware with error: %s' self._logger.debug(msg, resp.status_code) return resp(environ, start_response) self._logger.debug('Keystone Reply: Status: %d, Output: %s', resp.status_code, resp.content) try: identity_info = resp.json() token_id = str(identity_info['access']['token']['id']) tenant = identity_info['access']['token']['tenant'] except (ValueError, KeyError): error = 'Error on keystone reply: %d %s' self._logger.debug(error, resp.status_code, resp.content) return self._deny_request('InvalidURI')(environ, start_response) req.headers['X-Auth-Token'] = token_id tenant_to_connect = force_tenant or tenant['id'] if six.PY2 and isinstance(tenant_to_connect, six.text_type): tenant_to_connect = tenant_to_connect.encode('utf-8') self._logger.debug('Connecting with tenant: %s', tenant_to_connect) new_tenant_name = '%s%s' % (self._reseller_prefix, tenant_to_connect) environ['PATH_INFO'] = environ['PATH_INFO'].replace(account, new_tenant_name) return self._app(environ, start_response) def filter_factory(global_conf, **local_conf): """Return a WSGI filter app for use with paste.deploy.""" conf = global_conf.copy() conf.update(local_conf) def auth_filter(app): return S3Token(app, conf) return auth_filter keystonemiddleware-4.21.0/keystonemiddleware/ec2_token.py0000666000175100017510000002003613225160624023647 0ustar zuulzuul00000000000000# Copyright 2012 OpenStack Foundation # Copyright 2010 United States Government as represented by the # Administrator of the National Aeronautics and Space Administration. # All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. """Starting point for routing EC2 requests.""" import hashlib from oslo_config import cfg from oslo_log import log as logging from oslo_serialization import jsonutils import requests import six import webob.dec from keystonemiddleware.i18n import _ keystone_ec2_opts = [ cfg.StrOpt('url', default='http://localhost:5000/v2.0/ec2tokens', help='URL to get token from ec2 request.'), cfg.StrOpt('keyfile', help='Required if EC2 server requires client certificate.'), cfg.StrOpt('certfile', help='Client certificate key filename. Required if EC2 server ' 'requires client certificate.'), cfg.StrOpt('cafile', help='A PEM encoded certificate authority to use when ' 'verifying HTTPS connections. Defaults to the system ' 'CAs.'), cfg.BoolOpt('insecure', default=False, help='Disable SSL certificate verification.'), ] CONF = cfg.CONF CONF.register_opts(keystone_ec2_opts, group='keystone_ec2_token') PROTOCOL_NAME = 'EC2 Token Authentication' class EC2Token(object): """Authenticate an EC2 request with keystone and convert to token.""" def __init__(self, application, conf): super(EC2Token, self).__init__() self._application = application self._logger = logging.getLogger(conf.get('log_name', __name__)) self._logger.debug('Starting the %s component', PROTOCOL_NAME) def _ec2_error_response(self, code, message): """Helper to construct an EC2 compatible error message.""" self._logger.debug('EC2 error response: %(code)s: %(message)s', {'code': code, 'message': message}) resp = webob.Response() resp.status = 400 resp.headers['Content-Type'] = 'text/xml' error_msg = str('\n' '%s' '%s' % (code, message)) if six.PY3: error_msg = error_msg.encode() resp.body = error_msg return resp def _get_signature(self, req): """Extract the signature from the request. This can be a get/post variable or for version 4 also in a header called 'Authorization'. - params['Signature'] == version 0,1,2,3 - params['X-Amz-Signature'] == version 4 - header 'Authorization' == version 4 """ sig = req.params.get('Signature') or req.params.get('X-Amz-Signature') if sig is None and 'Authorization' in req.headers: auth_str = req.headers['Authorization'] sig = auth_str.partition("Signature=")[2].split(',')[0] return sig def _get_access(self, req): """Extract the access key identifier. For version 0/1/2/3 this is passed as the AccessKeyId parameter, for version 4 it is either an X-Amz-Credential parameter or a Credential= field in the 'Authorization' header string. """ access = req.params.get('AWSAccessKeyId') if access is None: cred_param = req.params.get('X-Amz-Credential') if cred_param: access = cred_param.split("/")[0] if access is None and 'Authorization' in req.headers: auth_str = req.headers['Authorization'] cred_str = auth_str.partition("Credential=")[2].split(',')[0] access = cred_str.split("/")[0] return access @webob.dec.wsgify() def __call__(self, req): # NOTE(alevine): We need to calculate the hash here because # subsequent access to request modifies the req.body so the hash # calculation will yield invalid results. body_hash = hashlib.sha256(req.body).hexdigest() signature = self._get_signature(req) if not signature: msg = _("Signature not provided") return self._ec2_error_response("AuthFailure", msg) access = self._get_access(req) if not access: msg = _("Access key not provided") return self._ec2_error_response("AuthFailure", msg) if 'X-Amz-Signature' in req.params or 'Authorization' in req.headers: auth_params = {} else: # Make a copy of args for authentication and signature verification auth_params = dict(req.params) # Not part of authentication args auth_params.pop('Signature', None) headers = req.headers if six.PY3: # NOTE(andrey-mp): jsonutils dumps it as list of keys without # conversion instead real dict headers = {k: headers[k] for k in headers} cred_dict = { 'access': access, 'signature': signature, 'host': req.host, 'verb': req.method, 'path': req.path, 'params': auth_params, 'headers': headers, 'body_hash': body_hash } if "ec2" in CONF.keystone_ec2_token.url: creds = {'ec2Credentials': cred_dict} else: creds = {'auth': {'OS-KSEC2:ec2Credentials': cred_dict}} creds_json = jsonutils.dumps(creds) headers = {'Content-Type': 'application/json'} verify = not CONF.keystone_ec2_token.insecure if verify and CONF.keystone_ec2_token.cafile: verify = CONF.keystone_ec2_token.cafile cert = None if (CONF.keystone_ec2_token.certfile and CONF.keystone_ec2_token.keyfile): cert = (CONF.keystone_ec2_certfile, CONF.keystone_ec2_token.keyfile) elif CONF.keystone_ec2_token.certfile: cert = CONF.keystone_ec2_token.certfile response = requests.request('POST', CONF.keystone_ec2_token.url, data=creds_json, headers=headers, verify=verify, cert=cert) # NOTE(vish): We could save a call to keystone by # having keystone return token, tenant, # user, and roles from this call. status_code = response.status_code if status_code != 200: msg = _('Error response from keystone: %s') % response.reason self._logger.debug(msg) return self._ec2_error_response("AuthFailure", msg) result = response.json() try: if 'token' in result: # NOTE(andrey-mp): response from keystone v3 token_id = response.headers['x-subject-token'] else: token_id = result['access']['token']['id'] except (AttributeError, KeyError): msg = _("Failure parsing response from keystone") self._logger.exception(msg) return self._ec2_error_response("AuthFailure", msg) # Authenticated! req.headers['X-Auth-Token'] = token_id return self._application def filter_factory(global_conf, **local_conf): """Return a WSGI filter app for use with paste.deploy.""" conf = global_conf.copy() conf.update(local_conf) def auth_filter(app): return EC2Token(app, conf) return auth_filter def app_factory(global_conf, **local_conf): conf = global_conf.copy() conf.update(local_conf) return EC2Token(None, conf) keystonemiddleware-4.21.0/keystonemiddleware/fixture.py0000666000175100017510000000722113225160624023465 0ustar zuulzuul00000000000000# Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. import uuid import fixtures from keystoneauth1 import fixture as client_fixtures from oslo_log import log as logging from oslo_utils import timeutils from keystonemiddleware import auth_token from keystonemiddleware.auth_token import _exceptions _LOG = logging.getLogger(__name__) class AuthTokenFixture(fixtures.Fixture): """Overrides what keystonemiddleware will return to the app behind it.""" def setUp(self): super(AuthTokenFixture, self).setUp() # Ensure that the initialized token data is cleaned up self._token_data = {} self.addCleanup(self._token_data.clear) _LOG.info('Using Testing AuthTokenFixture...') self.useFixture(fixtures.MockPatchObject( auth_token.AuthProtocol, 'fetch_token', self.fetch_token)) @property def tokens(self): return self._token_data.keys() def add_token_data(self, token_id=None, expires=None, user_id=None, user_name=None, user_domain_id=None, user_domain_name=None, project_id=None, project_name=None, project_domain_id=None, project_domain_name=None, role_list=None, is_v2=False): """Add token data to the auth_token fixture.""" if not token_id: token_id = uuid.uuid4().hex if not role_list: role_list = [] if is_v2: token = client_fixtures.V2Token( token_id=token_id, expires=expires, tenant_id=project_id, tenant_name=project_name, user_id=user_id, user_name=user_name) else: token = client_fixtures.V3Token( expires=expires, user_id=user_id, user_name=user_name, user_domain_id=user_domain_id, project_id=project_id, project_name=project_name, project_domain_id=project_domain_id, user_domain_name=user_domain_name, project_domain_name=project_domain_name) for role in role_list: token.add_role(name=role) self.add_token(token, token_id=token_id) def add_token(self, token_data, token_id=None): """Add an existing token to the middleware. :param token_data: token data to add to the fixture :type token_data: dict :param token_id: the token ID to add this token as :type token_id: str :returns: The token_id that the token was added as. :rtype: str """ if not token_id: token_id = uuid.uuid4().hex self._token_data[token_id] = token_data return token_id def fetch_token(self, token, **kwargs): """Low level replacement of fetch_token for AuthProtocol.""" token_data = self._token_data.get(token, {}) if token_data: self._assert_token_not_expired(token_data.expires) return token_data raise _exceptions.InvalidToken() def _assert_token_not_expired(self, token_expires): if timeutils.utcnow() > timeutils.normalize_time(token_expires): raise _exceptions.InvalidToken() keystonemiddleware-4.21.0/babel.cfg0000666000175100017510000000002213225160624017244 0ustar zuulzuul00000000000000[python: **.py] keystonemiddleware-4.21.0/setup.py0000666000175100017510000000200613225160624017234 0ustar zuulzuul00000000000000# Copyright (c) 2013 Hewlett-Packard Development Company, L.P. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or # implied. # See the License for the specific language governing permissions and # limitations under the License. # THIS FILE IS MANAGED BY THE GLOBAL REQUIREMENTS REPO - DO NOT EDIT import setuptools # In python < 2.7.4, a lazy loading of package `pbr` will break # setuptools if some other modules registered functions in `atexit`. # solution from: http://bugs.python.org/issue15881#msg170215 try: import multiprocessing # noqa except ImportError: pass setuptools.setup( setup_requires=['pbr>=2.0.0'], pbr=True)