├── .bumpversion.cfg ├── .gitattributes ├── .gitignore ├── .travis.yml ├── CHANGES-2.2.0 ├── Changelog.md ├── LICENSE ├── MANIFEST.in ├── Makefile ├── NEWS ├── README.md ├── admin ├── builddiscover.py ├── fixperms ├── gettlds.py ├── makechangelog ├── makedoc ├── pythonsource └── tagrelease ├── background-associations.txt ├── contrib ├── associate.py ├── openid-parse └── upgrade-store-1.1-to-2.0 ├── examples ├── README ├── __init__.py ├── consumer.py ├── discover ├── djopenid │ ├── README │ ├── __init__.py │ ├── consumer │ │ ├── __init__.py │ │ ├── models.py │ │ ├── tests.py │ │ ├── urls.py │ │ └── views.py │ ├── manage.py │ ├── server │ │ ├── __init__.py │ │ ├── models.py │ │ ├── tests.py │ │ ├── urls.py │ │ └── views.py │ ├── settings.py │ ├── templates │ │ ├── consumer │ │ │ ├── index.html │ │ │ └── request_form.html │ │ ├── index.html │ │ ├── server │ │ │ ├── endpoint.html │ │ │ ├── idPage.html │ │ │ ├── index.html │ │ │ ├── pape_request_info.html │ │ │ └── trust.html │ │ └── xrds.xml │ ├── urls.py │ └── util.py └── server.py ├── openid ├── __init__.py ├── association.py ├── constants.py ├── consumer │ ├── __init__.py │ ├── consumer.py │ └── discover.py ├── cryptutil.py ├── dh.py ├── extension.py ├── extensions │ ├── __init__.py │ ├── ax.py │ ├── draft │ │ ├── __init__.py │ │ ├── pape2.py │ │ └── pape5.py │ ├── pape.py │ └── sreg.py ├── fetchers.py ├── kvform.py ├── message.py ├── oidutil.py ├── server │ ├── __init__.py │ ├── server.py │ └── trustroot.py ├── sreg.py ├── store │ ├── __init__.py │ ├── filestore.py │ ├── interface.py │ ├── memstore.py │ ├── nonce.py │ └── sqlstore.py ├── test │ ├── __init__.py │ ├── data │ │ ├── accept.txt │ │ ├── example-xrds.xml │ │ ├── openid-1.2-consumer-sqlitestore.db │ │ ├── test1-discover.txt │ │ ├── test_discover │ │ │ ├── openid.html │ │ │ ├── openid2.html │ │ │ ├── openid2_xrds.xml │ │ │ ├── openid2_xrds_no_local_id.xml │ │ │ ├── openid_1_and_2.html │ │ │ ├── openid_1_and_2_xrds.xml │ │ │ ├── openid_1_and_2_xrds_bad_delegate.xml │ │ │ ├── openid_and_yadis.html │ │ │ ├── openid_no_delegate.html │ │ │ ├── unicode.html │ │ │ ├── unicode2.html │ │ │ ├── unicode3.html │ │ │ ├── yadis_0entries.xml │ │ │ ├── yadis_2_bad_local_id.xml │ │ │ ├── yadis_2entries_delegate.xml │ │ │ ├── yadis_2entries_idp.xml │ │ │ ├── yadis_another_delegate.xml │ │ │ ├── yadis_idp.xml │ │ │ ├── yadis_idp_delegate.xml │ │ │ └── yadis_no_delegate.xml │ │ ├── test_etxrd │ │ │ ├── README │ │ │ ├── delegated-20060809-r1.xrds │ │ │ ├── delegated-20060809-r2.xrds │ │ │ ├── delegated-20060809.xrds │ │ │ ├── no-xrd.xml │ │ │ ├── not-xrds.xml │ │ │ ├── prefixsometimes.xrds │ │ │ ├── ref.xrds │ │ │ ├── sometimesprefix.xrds │ │ │ ├── spoof1.xrds │ │ │ ├── spoof2.xrds │ │ │ ├── spoof3.xrds │ │ │ ├── status222.xrds │ │ │ ├── subsegments.xrds │ │ │ └── valid-populated-xrds.xml │ │ └── trustroot.txt │ ├── discoverdata.py │ ├── n2b64 │ ├── test_accept.py │ ├── test_association.py │ ├── test_association_response.py │ ├── test_auth_request.py │ ├── test_ax.py │ ├── test_consumer.py │ ├── test_cryptutil.py │ ├── test_dh.py │ ├── test_discover.py │ ├── test_etxrd.py │ ├── test_extension.py │ ├── test_fetchers.py │ ├── test_htmldiscover.py │ ├── test_kvform.py │ ├── test_message.py │ ├── test_negotiation.py │ ├── test_nonce.py │ ├── test_oidutil.py │ ├── test_openidyadis.py │ ├── test_pape.py │ ├── test_pape_draft2.py │ ├── test_pape_draft5.py │ ├── test_parsehtml.py │ ├── test_rpverify.py │ ├── test_server.py │ ├── test_services.py │ ├── test_sreg.py │ ├── test_storetest.py │ ├── test_symbol.py │ ├── test_trustroot.py │ ├── test_urinorm.py │ ├── test_verifydisco.py │ ├── test_xri.py │ ├── test_xrires.py │ ├── test_yadis_discover.py │ └── utils.py ├── urinorm.py └── yadis │ ├── __init__.py │ ├── accept.py │ ├── constants.py │ ├── discover.py │ ├── etxrd.py │ ├── filters.py │ ├── manager.py │ ├── parsehtml.py │ ├── services.py │ ├── xri.py │ └── xrires.py ├── setup.cfg ├── setup.py └── tox.ini /.bumpversion.cfg: -------------------------------------------------------------------------------- 1 | [bumpversion] 2 | current_version = 3.2 3 | commit = True 4 | tag = True 5 | tag_name = {new_version} 6 | parse = (?P\d+)\.(?P\d+)(?P.*) 7 | serialize = 8 | {major}.{minor}{rc} 9 | {major}.{minor} 10 | 11 | [bumpversion:part:rc] 12 | optional_value = final 13 | values = 14 | rc1 15 | rc2 16 | rc3 17 | rc4 18 | rc5 19 | final 20 | 21 | [bumpversion:file:openid/__init__.py] 22 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | /Makefile whitespace=space-before-tab,indent-with-non-tab,tabwidth=4 2 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.pyc 2 | __pycache__ 3 | # Distribution 4 | /dist 5 | /*.egg-info 6 | # Tests 7 | /.tox 8 | /.eggs 9 | /sstore 10 | # Coverage 11 | /.coverage* 12 | /htmlcov 13 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: python 2 | # Enable python 3.7 3 | dist: xenial 4 | 5 | sudo: false 6 | 7 | python: 8 | - "2.7" 9 | - "3.5" 10 | - "3.6" 11 | - "3.7" 12 | - "3.8" 13 | - "pypy" 14 | 15 | addons: 16 | apt: 17 | packages: 18 | # Dependencies for pycurl compilation 19 | - libcurl4-openssl-dev 20 | - libssl-dev 21 | 22 | install: 23 | - pip install tox-travis 24 | script: 25 | - tox 26 | after_success: 27 | - coverage combine 28 | - coverage report 29 | - pip install codecov 30 | - codecov 31 | -------------------------------------------------------------------------------- /CHANGES-2.2.0: -------------------------------------------------------------------------------- 1 | 2 | * API changes 3 | * SQLStore implementations no longer create or use a 'settings' 4 | table 5 | * SRegResponse.fromSuccessResponse returns None when no signed 6 | arguments were found 7 | * Added functions to generate request/response HTML forms with 8 | auto-submission javascript 9 | * Consumer (relying party) API: AuthRequest.htmlMarkup 10 | * Server API: server.OpenIDResponse.toHTML 11 | * PAPE (Provider Authentication Policy Extension) module 12 | * Updated extension for specification draft 2 13 | * Request.fromSuccessResponse returns None if PAPE response 14 | arguments were not signed 15 | 16 | * New features 17 | * Demo server now supports OP-driven identifier selection 18 | * Demo consumer now has a "stateless" option 19 | * Fetchers now only read/request first megabyte of response 20 | 21 | * Bug fixes 22 | * NOT NULL constraints were added to SQLStore tables where 23 | appropriate 24 | * message.fromPostArgs: use query.items() instead of iteritems(), 25 | fixes #161 (Affects Django users) 26 | * check_authentication requests: copy entire response, not just 27 | signed fields. Fixes missing namespace in check_authentication 28 | requests 29 | * Consumer._verifyDiscoveryResults: fall back to OpenID 1.0 type if 30 | 1.1 endpoint cannot be found; fixes discovery verification bug for 31 | certain OpenID 1 identifiers 32 | * SQLStore._execSQL: convert unicode arguments to str to avoid 33 | postgresql api bug with unicode objects (Thanks to Marek Kuziel.) 34 | * MySQLStore: Use ENGINE instead of TYPE when creating tables 35 | * server.OpenIDResponse.toFormMarkup: Use return_to from the 36 | request, not the response fields (Not all responses (i.e. cancel, 37 | setup_needed) include a return_to field.) 38 | * server.AssociationRequest.answer: include session_type in 39 | no-encryption assoc responses 40 | * OpenIDServiceEndpoint.getDisplayIdentifier: Don't include the 41 | fragment in display identifiers. 42 | -------------------------------------------------------------------------------- /Changelog.md: -------------------------------------------------------------------------------- 1 | # Changelog # 2 | 3 | ## 3.2 ## 4 | * Add support for python 3.8. 5 | * Drop support for python 3.4. 6 | * Fix false positive redirect error in consumer verification. 7 | * Do not percent escape sub delimiters in path in URI normalization. Thanks Colin Watson for report. 8 | * Fix tests and static code checks. Thanks Colin Watson. 9 | 10 | ## 3.1 ## 11 | * Convert data values for extensions to text. 12 | * Fixes in Python 2/3 support. 13 | * Fix examples. 14 | * Add support for python 3.7 15 | * Fix static code checks 16 | * Use bumpversion 17 | 18 | ## 3.0 ## 19 | 20 | * Support Python3. 21 | * Change most of the API to the text strings. UTF-8 encoded byte string should be compatible. 22 | * Authentication methods based on SHA-256 are now preferred over SHA-1. 23 | * Use `cryptography` library for cryptography tasks. 24 | * Add new base64-based API for `DiffieHellman` class. 25 | * Refactor script to negotiate association with an OpenID server. 26 | * Decrease log levels on repetitive logs. 27 | * Default fetcher is picked from more options. 28 | * Remove `openid.consumer.html_parse` module. 29 | * Remove `hmacSha*`, `randomString`, `randrange` and `sha*` functions from `openid.cryptutil`. 30 | * A lot of refactoring and clean up. 31 | 32 | ### Deprecation ### 33 | * Binary strings are deprecated, unless explicitely allowed. 34 | * `hash_func` is deprecated in favor of `algorithm` in `DiffieHellmanSHA*ServerSession` and `DiffieHellmanSHA*ConsumerSession`. 35 | * `DiffieHellmanSHA*ServerSession.consumer_pubkey` is deprecated in favor of `consumer_public_key`. 36 | * Functions `longToBinary` and `binaryToLong` deprecated in favor of `int_to_bytes` and `bytes_to_int`, respectively. 37 | * Old `DiffieHellman` API is deprecated. 38 | 39 | ## 2.3.0 ## 40 | 41 | * Prevent timing attacks on signature comparison. Thanks to Carl Howells. 42 | * Prevent XXE attacks. 43 | * Fix unicode errors. Thanks to Kai Lautaportti. 44 | * Drop support for python versions < 2.7. 45 | * Use logging module. Thanks to Attila-Mihaly Balazs. 46 | * Allow signatory, encoder and decoder to be set for Server. Thanks to julio. 47 | * Fix URL limit to server responses. Thanks to Rodrigo Primo. 48 | * Fix several protocol errors. 49 | * Add utility method to AX store extension. 50 | * Fix curl detection. Thanks to Sergey Shepelev. 51 | * Use setuptools. Thanks to Tres Seaver. 52 | * Refactor `Message` class creation. 53 | * Add `RequestsFetcher`. Thanks to Lennonka. 54 | * Updated examples. 55 | * Add tox for testing. Thanks to Marc Abramowitz. 56 | * Refactor tests. 57 | * Clean code and add static checks. 58 | 59 | ### Deprecation ### 60 | * `Message.setOpenIDNamespace()` method. 61 | * `UndefinedOpenIDNamespace` exception. 62 | * `OpenIDRequest.namespace` attribute. 63 | * `openid.extensions.draft` packages, namely its `pape2` and `pape5` modules. 64 | -------------------------------------------------------------------------------- /MANIFEST.in: -------------------------------------------------------------------------------- 1 | include *.md 2 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | .PHONY: all test test-openid test-djopenid coverage isort 2 | 3 | SOURCES = openid setup.py admin contrib 4 | 5 | # Run tox by default 6 | all: 7 | tox 8 | 9 | test-openid: 10 | python -m unittest discover --start=openid 11 | 12 | # Run tests for djopenid example 13 | test-djopenid: 14 | DJANGO_SETTINGS_MODULE="djopenid.settings" python -m unittest discover --start=examples 15 | 16 | test: test-openid test-djopenid 17 | 18 | coverage: 19 | python -m coverage erase 20 | -rm -r htmlcov 21 | PYTHONPATH="examples" DJANGO_SETTINGS_MODULE="djopenid.settings" python -m coverage run --branch --source="." openid/test/__init__.py discover 22 | python -m coverage html --directory=htmlcov 23 | 24 | isort: 25 | isort --recursive ${SOURCES} 26 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # python-openid2 # 2 | 3 | [![Build Status](https://travis-ci.org/ziima/python-openid.svg?branch=master)](https://travis-ci.org/ziima/python-openid) 4 | [![codecov](https://codecov.io/gh/ziima/python-openid/branch/master/graph/badge.svg)](https://codecov.io/gh/ziima/python-openid) 5 | [![PyPI](https://img.shields.io/pypi/v/python-openid2.svg)](https://pypi.org/pypi/python-openid2/) 6 | [![PyPI - Python Version](https://img.shields.io/pypi/pyversions/python-openid2.svg)](https://pypi.org/pypi/python-openid2/) 7 | 8 | Python OpenID library - OpenID support for servers and consumers. 9 | 10 | This is a set of Python packages to support use of the OpenID decentralized identity system in your application. 11 | Want to enable single sign-on for your web site? 12 | Use the `openid.consumer package`. 13 | Want to run your own OpenID server? 14 | Check out `openid.server`. 15 | Includes example code and support for a variety of storage back-ends. 16 | 17 | ## REQUIREMENTS ## 18 | 19 | - Python 2.7, >=3.5 20 | - lxml 21 | - six 22 | - cryptography 23 | 24 | 25 | ## INSTALLATION ## 26 | 27 | To install the base library, just run the following command: 28 | 29 | pip install python-openid2 30 | 31 | 32 | ## GETTING STARTED ## 33 | 34 | The examples directory includes an example server and consumer 35 | implementation. See the README file in that directory for more 36 | information on running the examples. 37 | 38 | Library documentation is available in html form in the doc directory. 39 | 40 | 41 | ## LOGGING ## 42 | 43 | This library offers a logging hook that will record unexpected 44 | conditions that occur in library code. If a condition is recoverable, 45 | the library will recover and issue a log message. If it is not 46 | recoverable, the library will raise an exception. See the 47 | documentation for the openid.oidutil module for more on the logging 48 | hook. 49 | 50 | 51 | ## DOCUMENTATION ## 52 | 53 | The documentation in this library is in Epydoc format, which is 54 | detailed at: 55 | 56 | http://epydoc.sourceforge.net/ 57 | 58 | 59 | ## CONTACT ## 60 | 61 | Send bug reports, suggestions, comments, and questions to 62 | https://github.com/ziima/python-openid/issues/new 63 | 64 | If you have a bugfix or feature you'd like to contribute, don't 65 | hesitate to send it to us on GitHub. 66 | -------------------------------------------------------------------------------- /admin/builddiscover.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | from __future__ import unicode_literals 3 | 4 | import os.path 5 | 6 | from six.moves.urllib.parse import urljoin 7 | 8 | from openid.test import discoverdata 9 | 10 | manifest_header = """\ 11 | # This file contains test cases for doing YADIS identity URL and 12 | # service discovery. For each case, there are three URLs. The first 13 | # URL is the user input. The second is the identity URL and the third 14 | # is the URL from which the XRDS document should be read. 15 | # 16 | # The file format is as follows: 17 | # User URL Identity URL XRDS URL 18 | # 19 | # blank lines and lines starting with # should be ignored. 20 | # 21 | # To use this test: 22 | # 23 | # 1. Run your discovery routine on the User URL. 24 | # 25 | # 2. Compare the identity URL returned by the discovery routine to the 26 | # identity URL on that line of the file. It must be an EXACT match. 27 | # 28 | # 3. Do a regular HTTP GET on the XRDS URL. Compare the content that 29 | # was returned by your discovery routine with the content returned 30 | # from that URL. It should also be an exact match. 31 | 32 | """ 33 | 34 | 35 | def buildDiscover(base_url, out_dir): 36 | """Convert all files in a directory to apache mod_asis files in 37 | another directory.""" 38 | test_data = discoverdata.readTests(discoverdata.default_test_file) 39 | 40 | def writeTestFile(test_name): 41 | template = test_data[test_name] 42 | 43 | data = discoverdata.fillTemplate( 44 | test_name, template, base_url, discoverdata.example_xrds) 45 | 46 | out_file_name = os.path.join(out_dir, test_name) 47 | out_file = open(out_file_name, 'w') 48 | out_file.write(data) 49 | 50 | manifest = [manifest_header] 51 | for success, input_name, id_name, result_name in discoverdata.testlist: 52 | if not success: 53 | continue 54 | writeTestFile(input_name) 55 | 56 | input_url = urljoin(base_url, input_name) 57 | id_url = urljoin(base_url, id_name) 58 | result_url = urljoin(base_url, result_name) 59 | 60 | manifest.append('\t'.join((input_url, id_url, result_url))) 61 | manifest.append('\n') 62 | 63 | manifest_file_name = os.path.join(out_dir, 'manifest.txt') 64 | manifest_file = open(manifest_file_name, 'w') 65 | for chunk in manifest: 66 | manifest_file.write(chunk) 67 | manifest_file.close() 68 | 69 | 70 | if __name__ == '__main__': 71 | import sys 72 | buildDiscover(*sys.argv[1:]) 73 | -------------------------------------------------------------------------------- /admin/fixperms: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | cat - < 60: 42 | print(output_line + line_suffix) 43 | output_line = line_prefix + tld 44 | else: 45 | output_line = new_output_line 46 | prefix = separator 47 | 48 | print(output_line + suffix) 49 | -------------------------------------------------------------------------------- /admin/makechangelog: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | darcs changes --from-tag '^release-' --summary > CHANGELOG 3 | -------------------------------------------------------------------------------- /admin/makedoc: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | rm -rf doc/* 4 | epydoc --html --output doc/ --name "Python-OpenID" --top openid \ 5 | --url "http://openidenabled.com/python-openid/" --inheritance listed \ 6 | --no-private -v \ 7 | $( find openid -name '*.py' -and -not -path 'openid/test*' ) 8 | -------------------------------------------------------------------------------- /admin/pythonsource: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | find ./openid/ \( \( -name _darcs -o -name .svn -o -name _trial_temp -o -name test -o -name admin -o -name examples \) -type d -prune -a -false \) -o \( ! -iname \*~ -a -type f \) -a \( ! -iname \*.pyc -a -type f \) -a -name \*.py 3 | -------------------------------------------------------------------------------- /admin/tagrelease: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | VERSION="$1" 3 | VERSION_PATTERN='^[0-9]\+\.[0-9]\+\.[0-9]\+\(-[a-z0-9-]\+\)\?$' 4 | 5 | echo "$VERSION" | grep -e "$VERSION_PATTERN" 2>&1 >/dev/null || { 6 | echo "$VERSION" 7 | echo "Malformed version number. Expected X.X.X or X.X.X-name." 1>&2 8 | exit 1 9 | } 10 | 11 | cd $(dirname $(dirname $(readlink --canonicalize "$0"))) 12 | ./admin/setversion "$VERSION" 13 | darcs record -m 'Set version number to '"$VERSION" && darcs tag "release-$VERSION" 14 | -------------------------------------------------------------------------------- /background-associations.txt: -------------------------------------------------------------------------------- 1 | Background association requests 2 | ############################### 3 | 4 | This document describes how to make signing in with OpenID faster for 5 | users of your application by never making the users wait for an 6 | association to be made, but using associations when they're 7 | available. Most OpenID libraries and applications attempt to make 8 | associations during the discovery phase of the OpenID authentication 9 | request. Because association requests may have to do Diffie-Hellman 10 | key exchange, which is time consuming. Even if Diffie-Hellman key 11 | exchange is not used, the user still needs to wait for the association 12 | request. 13 | 14 | Setting up your application to make associations in the background 15 | ================================================================== 16 | 17 | When making associations background, there are two components that 18 | need access to the OpenID association store: the consumer application 19 | and the background association fetcher. The consumer needs to be set 20 | up to record the server URL for any request for which an association 21 | does not exist or is expired instead of making a new association. The 22 | background fetcher looks at the server URL queue and makes 23 | associations for any server URLs that need them. After the 24 | associations are made, the consumer will use them until they expire 25 | again. While associations are expired or missing, the consumer will 26 | use stateless mode to complete authentications with the servers that 27 | need associations. 28 | 29 | The OpenID server endpoint URL queue 30 | ----------------------------------------------------------------- 31 | 32 | You will have to set up a conduit between the consumer and the 33 | background association fetcher so that the background association 34 | fetcher knows what servers need associations. The background 35 | association fetcher will not fetch associations for servers that 36 | already have them, so the queue does not have to be very smart. It 37 | could be as simple as a file to which the server URLs are 38 | appended. Either way, the queue needs to be write-able by the consumer 39 | and readable by the background fetcher. 40 | 41 | Configuring the consumer 42 | ----------------------------------------------------------------- 43 | 44 | Create a subclass of ``GenericConsumer`` that overrides 45 | ``_negotiateAssociation`` so that it just records the server URL that 46 | needs an association:: 47 | 48 | from openid.consumer.consumer import GenericConsumer, Consumer 49 | 50 | class LazyAssociationConsumer(GenericConsumer): 51 | needs_assoc_file = None 52 | 53 | def _negotiateAssociation(self, endpoint): 54 | # Do whatever you need to do here to send the server_url to 55 | # the queue. This example just appends it to a file. 56 | self.needs_assoc_file.write(endpoint.server_url + '\n') 57 | self.needs_assoc_file.flush() 58 | 59 | You could also store the whole endpoint object. When you instantiate 60 | the consumer, pass this generic consumer class to the controlling 61 | consumer:: 62 | 63 | return Consumer(session, store, consumer_class=LazyAssociationConsumer) 64 | 65 | The background association fetcher 66 | ----------------------------------------------------------------- 67 | 68 | The background association fetcher is just a script that should be 69 | added to ``cron`` or triggered periodically. If you are ambitious, you 70 | could make the background fetcher listen for inserts into the queue. 71 | 72 | The background fetcher needs to do something similar to the following:: 73 | 74 | def refresh(consumer, endpoint): 75 | if consumer.store.getAssociation(endpoint.server_url): 76 | logging.info("We don't need to associate with %r", endpoint.server_url) 77 | return 78 | 79 | logging.info("Associating with %r", endpoint.server_url) 80 | now = time.time() 81 | assoc = consumer._negotiateAssociation(endpoint) 82 | if assoc: 83 | elapsed = time.time() - now 84 | logging.info('(%0.2f seconds) Associated with %r', elapsed, 85 | endpoint.server_url) 86 | consumer.store.storeAssociation(endpoint.server_url, assoc) 87 | else: 88 | logging.error('Failed to make an association with %r', 89 | endpoint.server_url) 90 | 91 | The code in this example logs the amount of time that the association 92 | request took. This is time that the user would have been waiting. The 93 | ``consumer`` in this example is a standard consumer, not the 94 | ``LazyAssociationConsumer`` that was defined in the section 95 | above. This is important, because the lazy consumer above will not 96 | actually make any associations. 97 | -------------------------------------------------------------------------------- /contrib/openid-parse: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | """Grab URLs from the clipboard, interpret the queries as OpenID, and print. 3 | 4 | In addition to URLs, I also scan for queries as they appear in httpd log files, 5 | with a pattern like 'GET /foo?bar=baz HTTP'. 6 | 7 | Requires the 'xsel' program to get the contents of the clipboard. 8 | """ 9 | from __future__ import unicode_literals 10 | 11 | import re 12 | import subprocess 13 | import sys 14 | from pprint import pformat 15 | 16 | import six 17 | from six.moves.urllib.parse import parse_qs, urlsplit, urlunsplit 18 | 19 | from openid import message 20 | 21 | OPENID_SORT_ORDER = ['mode', 'identity', 'claimed_id'] 22 | 23 | 24 | class NoQuery(Exception): 25 | def __init__(self, url): 26 | self.url = url 27 | 28 | def __str__(self): 29 | return "No query in url %s" % (self.url,) 30 | 31 | 32 | def getClipboard(): 33 | xsel = subprocess.Popen(["xsel", "-o", "-b"], stdout=subprocess.PIPE) 34 | output = xsel.communicate()[0] 35 | return output 36 | 37 | 38 | def main(): 39 | source = getClipboard() 40 | urls = find_urls(source) 41 | 42 | errors = [] 43 | output = [] 44 | queries = [] 45 | 46 | queries.extend(queriesFromPostdata(source)) 47 | 48 | for url in urls: 49 | try: 50 | queries.append(queryFromURL(url)) 51 | except NoQuery as err: 52 | errors.append(err) 53 | 54 | queries.extend(queriesFromLogs(source)) 55 | 56 | for where, query in queries: 57 | output.append('at %s:\n%s' % (where, openidFromQuery(query))) 58 | 59 | if output: 60 | print('\n\n'.join(output)) 61 | elif errors: 62 | for err in errors: 63 | print(err) 64 | 65 | 66 | def queryFromURL(url): 67 | split_url = urlsplit(url) 68 | query = parse_qs(split_url[3]) 69 | 70 | if not query: 71 | raise NoQuery(url) 72 | 73 | url_without_query = urlunsplit(split_url[:3] + (None, None)) 74 | 75 | return (url_without_query, query) 76 | 77 | 78 | def openidFromQuery(query): 79 | try: 80 | msg = message.Message.fromPostArgs(unlistify(query)) 81 | s = formatOpenIDMessage(msg) 82 | except Exception as err: 83 | # XXX - side effect. 84 | sys.stderr.write(six.text_type(err)) 85 | s = pformat(query) 86 | 87 | return s 88 | 89 | 90 | def formatOpenIDMessage(msg): 91 | value_lists = {} 92 | for (ns_uri, ns_key), value in msg.args.items(): 93 | l = value_lists.setdefault(ns_uri, {}) 94 | l[ns_key] = value 95 | 96 | output = [] 97 | 98 | for ns_uri, values in value_lists.items(): 99 | ns_output = [] 100 | 101 | alias = msg.namespaces.getAlias(ns_uri) 102 | if alias is message.NULL_NAMESPACE: 103 | alias = 'openid' 104 | ns_output.append(" %s <%s>" % (alias, ns_uri)) 105 | 106 | for key in OPENID_SORT_ORDER: 107 | try: 108 | ns_output.append(" %s = %s" % (key, values.pop(key))) 109 | except KeyError: 110 | pass 111 | 112 | values = sorted(values.items()) 113 | 114 | for k, v in values: 115 | ns_output.append(" %s = %s" % (k, v)) 116 | 117 | output.append('\n'.join(ns_output)) 118 | 119 | return '\n\n'.join(output) 120 | 121 | 122 | def unlistify(d): 123 | return dict((i[0], i[1][0]) for i in d.items()) 124 | 125 | 126 | def queriesFromLogs(s): 127 | qre = re.compile(r'GET (/.*)?\?(.+) HTTP') 128 | 129 | return [(match.group(1), parse_qs(match.group(2))) 130 | for match in qre.finditer(s)] 131 | 132 | 133 | def queriesFromPostdata(s): 134 | # This looks for query data in a line that starts POSTDATA=. 135 | # Tamperdata outputs such lines. If there's a 'Host=' in that block, 136 | # use that too, but don't require it. 137 | qre = re.compile(r'(?:^Host=(?P.+?)$.*?)?^POSTDATA=(?P.*)$', 138 | re.DOTALL | re.MULTILINE) 139 | return [(match.group('host') or 'POSTDATA', 140 | parse_qs(match.group('query'))) for match in qre.finditer(s)] 141 | 142 | 143 | def find_urls(s): 144 | # Regular expression borrowed from urlscan 145 | # by Daniel Burrows , GPL. 146 | urlinternalpattern = r'[{}a-zA-Z/\-_0-9%?&.=:;+,#~]' 147 | urltrailingpattern = r'[{}a-zA-Z/\-_0-9%&=+#]' 148 | httpurlpattern = r'(?:https?://' + urlinternalpattern + r'*' + urltrailingpattern + r')' 149 | # Used to guess that blah.blah.blah.TLD is a URL. 150 | tlds = ['biz', 'com', 'edu', 'info', 'org'] 151 | guessedurlpattern = r'(?:[a-zA-Z0-9_\-%]+(?:\.[a-zA-Z0-9_\-%]+)*\.(?:' + '|'.join(tlds) + '))' 152 | urlre = re.compile(r'(?:<(?:URL:)?)?(' + httpurlpattern + '|' + guessedurlpattern + 153 | '|(?:mailto:[a-zA-Z0-9\-_]*@[0-9a-zA-Z_\-.]*[0-9a-zA-Z_\-]))>?') 154 | 155 | return [match.group(1) for match in urlre.finditer(s)] 156 | 157 | 158 | if __name__ == '__main__': 159 | main() 160 | -------------------------------------------------------------------------------- /examples/README: -------------------------------------------------------------------------------- 1 | Python OpenID library example code 2 | ================================== 3 | 4 | The examples directory contains working code illustrating the use of 5 | the library for performing OpenID authentication, both as a consumer 6 | and a server. There are two kinds of examples, one that can run 7 | without any external dependencies, and one that uses the Django Web 8 | framework. The examples do not illustrate how to use all of the 9 | features of the library, but they should be a good starting point to 10 | see how to use this library with your code. 11 | 12 | Both the Django libraries and the BaseHTTPServer examples require that 13 | the OpenID library is installed or that it has been added to Python's 14 | search path (PYTHONPATH environment variable or sys.path). 15 | 16 | The Django example is probably a good place to start reading the 17 | code. There is little that is Django-specific about the OpenID logic 18 | in the example, and it should be easy to port to any framework. To run 19 | the django examples, see the README file in the djopenid subdirectory. 20 | 21 | The other examples use Python's built-in BaseHTTPServer and have a 22 | good deal of ad-hoc dispatching and rendering code mixed in 23 | 24 | Using the BaseHTTPServer examples 25 | ================================= 26 | 27 | This directory contains a working server and consumer that use this 28 | OpenID library. They are both written using python's standard 29 | BaseHTTPServer. 30 | 31 | 32 | To run the example system: 33 | 34 | 1. Make sure you've installed the library, as explained in the 35 | installation instructions. 36 | 37 | 2. Start the consumer server: 38 | 39 | python consumer.py --port 8001 40 | 41 | 42 | 3. In another terminal, start the identity server: 43 | 44 | python server.py --port 8000 45 | 46 | (Hit Ctrl-C in either server's window to stop that server.) 47 | 48 | 49 | 4. Open your web broswer, and go to the consumer server: 50 | 51 | http://localhost:8001/ 52 | 53 | Note that all pages the consumer server shows will have "Python OpenID 54 | Consumer Example" across the top. 55 | 56 | 57 | 5. Enter an identity url managed by the sample identity server: 58 | 59 | http://localhost:8000/id/bob 60 | 61 | 62 | 6. The browser will be redirected to the sample server, which will be 63 | requesting that you log in to proceed. Enter the username for the 64 | identity URL into the login box: 65 | 66 | bob 67 | 68 | Note that all pages the identity server shows will have "Python 69 | OpenID Server Example" across the top. 70 | 71 | 72 | 7. After you log in as bob, the server example will ask you if you 73 | want to allow http://localhost:8001/ to know your identity. Say 74 | yes. 75 | 76 | 77 | 8. You should end up back on the consumer site, at a page indicating 78 | you've logged in successfully. 79 | 80 | 81 | That's a basic OpenID login procedure. You can continue through it, 82 | playing with variations to see how they work. The python code is 83 | intended to be a straightforward example of how to use the python 84 | OpenID library to function as either an identity server or consumer. 85 | 86 | Getting help 87 | ============ 88 | 89 | Please send bug reports, patches, and other feedback to 90 | 91 | http://openid.net/developers/dev-mailing-lists/ 92 | -------------------------------------------------------------------------------- /examples/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/openid/python-openid/afa6adacbe1a41d8f614c8bce2264dfbe9e76489/examples/__init__.py -------------------------------------------------------------------------------- /examples/discover: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | from __future__ import unicode_literals 3 | 4 | from openid.consumer.discover import DiscoveryFailure, discover 5 | from openid.fetchers import HTTPFetchingError 6 | 7 | names = [["server_url", "Server URL "], 8 | ["local_id", "Local ID "], 9 | ["canonicalID", "Canonical ID"], 10 | ] 11 | 12 | 13 | def show_services(user_input, normalized, services): 14 | print(" Claimed identifier:", normalized) 15 | if services: 16 | print(" Discovered OpenID services:") 17 | for n, service in enumerate(services): 18 | print(" %s." % (n,)) 19 | for attr, name in names: 20 | val = getattr(service, attr, None) 21 | if val is not None: 22 | print(" %s: %s" % (name, val)) 23 | 24 | print(" Type URIs:") 25 | for type_uri in service.type_uris: 26 | print(" *", type_uri) 27 | 28 | print() 29 | 30 | else: 31 | print(" No OpenID services found") 32 | print() 33 | 34 | 35 | if __name__ == "__main__": 36 | import sys 37 | 38 | for user_input in sys.argv[1:]: 39 | print("=" * 50) 40 | print("Running discovery on", user_input) 41 | try: 42 | normalized, services = discover(user_input) 43 | except DiscoveryFailure as why: 44 | print("Discovery failed:", why) 45 | print() 46 | except HTTPFetchingError as why: 47 | print("HTTP request failed:", why) 48 | print() 49 | else: 50 | show_services(user_input, normalized, services) 51 | -------------------------------------------------------------------------------- /examples/djopenid/README: -------------------------------------------------------------------------------- 1 | 2 | DJANGO EXAMPLE PACKAGE 3 | ====================== 4 | 5 | This package implements an example consumer and server for the Django 6 | Python web framework. You can get Django (and learn more about it) at 7 | 8 | http://www.djangoproject.com/ 9 | 10 | SETUP 11 | ===== 12 | 13 | 1. Install the OpenID library, version 2.0.0 or later. 14 | 15 | 2. Install Django. 16 | 17 | If you find that the examples doesn't run on newer versions of 18 | Django, please let us know! 19 | 20 | 3. Modify djopenid/settings.py appropriately; you may wish to change 21 | the database type or path, although the default settings should be 22 | sufficient for most systems. 23 | 24 | 4. In examples/djopenid/ run: 25 | 26 | python manage.py migrate 27 | 28 | 5. To run the example consumer or server, run 29 | 30 | python manage.py runserver [PORT] 31 | 32 | where PORT is the port number on which to listen. 33 | 34 | Note that if you want to try both the consumer and server at the 35 | same time, run the command twice with two different values for 36 | PORT. 37 | 38 | 6. Point your web browser at the server at 39 | 40 | http://localhost:PORT/ 41 | 42 | to begin. 43 | 44 | ABOUT THE CODE 45 | ============== 46 | 47 | The example server and consumer code provided in this package are 48 | intended to be instructional in the use of this OpenID library. While 49 | it is not recommended to use the example code in production, the code 50 | should be sufficient to explain the general use of the library. 51 | 52 | If you aren't familiar with the Django web framework, you can quickly 53 | start looking at the important code by looking in the 'views' modules: 54 | 55 | djopenid.consumer.views 56 | djopenid.server.views 57 | 58 | Each view is a python callable that responds to an HTTP request. 59 | Regardless of whether you use a framework, your application should 60 | look similar to these example applications. 61 | 62 | CONTACT 63 | ======= 64 | 65 | Please send bug reports, patches, and other feedback to 66 | 67 | http://openid.net/developers/dev-mailing-lists/ 68 | -------------------------------------------------------------------------------- /examples/djopenid/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/openid/python-openid/afa6adacbe1a41d8f614c8bce2264dfbe9e76489/examples/djopenid/__init__.py -------------------------------------------------------------------------------- /examples/djopenid/consumer/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/openid/python-openid/afa6adacbe1a41d8f614c8bce2264dfbe9e76489/examples/djopenid/consumer/__init__.py -------------------------------------------------------------------------------- /examples/djopenid/consumer/models.py: -------------------------------------------------------------------------------- 1 | """Required module for Django application.""" 2 | from __future__ import unicode_literals 3 | -------------------------------------------------------------------------------- /examples/djopenid/consumer/tests.py: -------------------------------------------------------------------------------- 1 | """Test the consumer.""" 2 | from __future__ import unicode_literals 3 | 4 | import django 5 | from django.test import TestCase 6 | from openid.fetchers import setDefaultFetcher, HTTPResponse 7 | from openid.yadis.constants import YADIS_CONTENT_TYPE 8 | 9 | # Allow django tests to run through discover 10 | django.setup() 11 | 12 | 13 | EXAMPLE_XRDS = b''' 14 | 15 | 16 | 17 | http://specs.openid.net/auth/2.0/server 18 | http://example.com/ 19 | 20 | 21 | ''' 22 | 23 | 24 | class FakeFetcher(object): 25 | """Fake fetcher for tests.""" 26 | 27 | def __init__(self): 28 | self.response = None 29 | 30 | def fetch(self, *args, **kwargs): 31 | return self.response 32 | 33 | 34 | class TestStartOpenID(TestCase): 35 | """Test 'startOpenID' view.""" 36 | 37 | def setUp(self): 38 | self.fetcher = FakeFetcher() 39 | setDefaultFetcher(self.fetcher) 40 | 41 | def tearDown(self): 42 | setDefaultFetcher(None) 43 | 44 | def test_get(self): 45 | response = self.client.get('/consumer/') 46 | self.assertContains(response, ' example consumer ') 47 | 48 | def test_post(self): 49 | self.fetcher.response = HTTPResponse('http://example.com/', 200, {'content-type': YADIS_CONTENT_TYPE}, 50 | EXAMPLE_XRDS) 51 | 52 | response = self.client.post('/consumer/', {'openid_identifier': 'http://example.com/'}) 53 | 54 | # Renders a POST form 55 | self.assertContains(response, 'http://example.com/') 56 | self.assertContains(response, 'openid.identity') 57 | -------------------------------------------------------------------------------- /examples/djopenid/consumer/urls.py: -------------------------------------------------------------------------------- 1 | """Consumer URLs.""" 2 | from __future__ import unicode_literals 3 | 4 | from django.conf.urls import url 5 | 6 | from djopenid.consumer.views import finishOpenID, rpXRDS, startOpenID 7 | 8 | urlpatterns = [ 9 | url(r'^$', startOpenID, name='index'), 10 | url(r'^finish/$', finishOpenID, name='return_to'), 11 | url(r'^xrds/$', rpXRDS, name='xrds'), 12 | ] 13 | -------------------------------------------------------------------------------- /examples/djopenid/manage.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | from __future__ import unicode_literals 3 | 4 | import os 5 | import sys 6 | 7 | if __name__ == "__main__": 8 | os.environ.setdefault("DJANGO_SETTINGS_MODULE", "djopenid.settings") 9 | try: 10 | from django.core.management import execute_from_command_line 11 | except ImportError: 12 | # The above import may fail for some other reason. Ensure that the 13 | # issue is really that Django is missing to avoid masking other 14 | # exceptions on Python 2. 15 | try: 16 | import django # noqa: F401 17 | except ImportError: 18 | raise ImportError( 19 | "Couldn't import Django. Are you sure it's installed and " 20 | "available on your PYTHONPATH environment variable? Did you " 21 | "forget to activate a virtual environment?" 22 | ) 23 | raise 24 | execute_from_command_line(sys.argv) 25 | -------------------------------------------------------------------------------- /examples/djopenid/server/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/openid/python-openid/afa6adacbe1a41d8f614c8bce2264dfbe9e76489/examples/djopenid/server/__init__.py -------------------------------------------------------------------------------- /examples/djopenid/server/models.py: -------------------------------------------------------------------------------- 1 | """Required module for Django application.""" 2 | from __future__ import unicode_literals 3 | -------------------------------------------------------------------------------- /examples/djopenid/server/tests.py: -------------------------------------------------------------------------------- 1 | from __future__ import unicode_literals 2 | 3 | import django 4 | from django.http import HttpRequest 5 | from django.test.testcases import TestCase 6 | from django.urls import reverse 7 | from openid.message import Message 8 | from openid.server.server import HTTP_REDIRECT, CheckIDRequest 9 | from openid.yadis.constants import YADIS_CONTENT_TYPE 10 | from openid.yadis.services import applyFilter 11 | from six.moves.urllib.parse import urljoin 12 | 13 | from .. import util 14 | from ..server import views 15 | 16 | # Allow django tests to run through discover 17 | django.setup() 18 | 19 | 20 | def dummyRequest(): 21 | request = HttpRequest() 22 | request.session = {} 23 | request.META['HTTP_HOST'] = 'example.cz' 24 | request.META['SERVER_PROTOCOL'] = 'HTTP' 25 | return request 26 | 27 | 28 | class TestProcessTrustResult(TestCase): 29 | def setUp(self): 30 | self.request = dummyRequest() 31 | 32 | id_url = urljoin('http://example.cz/', reverse('server:local_id')) 33 | 34 | # Set up the OpenID request we're responding to. 35 | op_endpoint = 'http://127.0.0.1:8080/endpoint' 36 | message = Message.fromPostArgs({ 37 | 'openid.mode': 'checkid_setup', 38 | 'openid.identity': id_url, 39 | 'openid.return_to': 'http://127.0.0.1/%s' % (self.id(),), 40 | 'openid.sreg.required': 'postcode', 41 | }) 42 | self.openid_request = CheckIDRequest.fromMessage(message, op_endpoint) 43 | 44 | views.setRequest(self.request, self.openid_request) 45 | 46 | def test_allow(self): 47 | self.request.POST['allow'] = 'Yes' 48 | 49 | response = views.processTrustResult(self.request) 50 | 51 | self.assertEqual(response.status_code, HTTP_REDIRECT) 52 | finalURL = response['location'] 53 | self.assertIn('openid.mode=id_res', finalURL) 54 | self.assertIn('openid.identity=', finalURL) 55 | self.assertIn('openid.sreg.postcode=12345', finalURL) 56 | 57 | def test_cancel(self): 58 | self.request.POST['cancel'] = 'Yes' 59 | 60 | response = views.processTrustResult(self.request) 61 | 62 | self.assertEqual(response.status_code, HTTP_REDIRECT) 63 | finalURL = response['location'] 64 | self.assertIn('openid.mode=cancel', finalURL) 65 | self.assertNotIn('openid.identity=', finalURL) 66 | self.assertNotIn('openid.sreg.postcode=12345', finalURL) 67 | 68 | 69 | class TestShowDecidePage(TestCase): 70 | def test_unreachableRealm(self): 71 | self.request = dummyRequest() 72 | 73 | id_url = urljoin('http://example.cz/', reverse('server:local_id')) 74 | 75 | # Set up the OpenID request we're responding to. 76 | op_endpoint = 'http://127.0.0.1:8080/endpoint' 77 | message = Message.fromPostArgs({ 78 | 'openid.mode': 'checkid_setup', 79 | 'openid.identity': id_url, 80 | 'openid.return_to': 'http://unreachable.invalid/%s' % (self.id(),), 81 | 'openid.sreg.required': 'postcode', 82 | }) 83 | self.openid_request = CheckIDRequest.fromMessage(message, op_endpoint) 84 | 85 | views.setRequest(self.request, self.openid_request) 86 | 87 | response = views.showDecidePage(self.request, self.openid_request) 88 | self.assertContains(response, 'trust_root_valid is Unreachable') 89 | 90 | 91 | class TestGenericXRDS(TestCase): 92 | def test_genericRender(self): 93 | """Render an XRDS document with a single type URI and a single endpoint URL 94 | Parse it to see that it matches.""" 95 | request = dummyRequest() 96 | 97 | type_uris = ['A_TYPE'] 98 | endpoint_url = 'A_URL' 99 | response = util.renderXRDS(request, type_uris, [endpoint_url]) 100 | 101 | requested_url = 'http://requested.invalid/' 102 | (endpoint,) = applyFilter(requested_url, response.content) 103 | 104 | self.assertEqual(response['Content-Type'], YADIS_CONTENT_TYPE) 105 | self.assertEqual(endpoint.type_uris, type_uris) 106 | self.assertEqual(endpoint.uri, endpoint_url) 107 | -------------------------------------------------------------------------------- /examples/djopenid/server/urls.py: -------------------------------------------------------------------------------- 1 | """Server URLs.""" 2 | from __future__ import unicode_literals 3 | 4 | from django.conf.urls import url 5 | from django.views.generic import TemplateView 6 | 7 | from djopenid.server.views import endpoint, idPage, idpXrds, processTrustResult, server 8 | 9 | urlpatterns = [ 10 | url(r'^$', server, name='index'), 11 | url(r'^xrds/$', idpXrds, name='xrds'), 12 | url(r'^user/$', idPage, name='local_id'), 13 | url(r'^endpoint/$', endpoint, name='endpoint'), 14 | url(r'^trust/$', TemplateView.as_view(template_name='server/trust.html'), name='confirmation'), 15 | url(r'^processTrustResult/$', processTrustResult, name='process-confirmation'), 16 | ] 17 | -------------------------------------------------------------------------------- /examples/djopenid/settings.py: -------------------------------------------------------------------------------- 1 | """Example Django settings for djopenid project.""" 2 | from __future__ import unicode_literals 3 | 4 | import os 5 | import sys 6 | import warnings 7 | 8 | try: 9 | import openid 10 | except ImportError as e: 11 | warnings.warn("Could not import OpenID library. Please consult the djopenid README.") 12 | sys.exit(1) 13 | else: 14 | del openid 15 | 16 | DEBUG = True 17 | ALLOWED_HOSTS = ['*'] 18 | 19 | DATABASES = { 20 | 'default': { 21 | 'ENGINE': 'django.db.backends.sqlite3', # Add 'postgresql_psycopg2', 'mysql', 'sqlite3' or 'oracle'. 22 | 'NAME': ':memory:', 23 | } 24 | } 25 | 26 | SECRET_KEY = 'u^bw6lmsa6fah0$^lz-ct$)y7x7#ag92-z+y45-8!(jk0lkavy' 27 | SESSION_ENGINE = 'django.contrib.sessions.backends.file' 28 | SESSION_SERIALIZER = 'django.contrib.sessions.serializers.PickleSerializer' 29 | 30 | TEMPLATES = [ 31 | { 32 | 'BACKEND': 'django.template.backends.django.DjangoTemplates', 33 | 'DIRS': [os.path.abspath(os.path.join(os.path.dirname(__file__), 'templates'))], 34 | 'APP_DIRS': True, 35 | } 36 | ] 37 | 38 | MIDDLEWARE = ( 39 | 'django.contrib.sessions.middleware.SessionMiddleware', 40 | 'django.middleware.common.CommonMiddleware', 41 | ) 42 | 43 | ROOT_URLCONF = 'djopenid.urls' 44 | 45 | INSTALLED_APPS = ( 46 | 'django.contrib.sessions', 47 | 'djopenid.consumer', 48 | 'djopenid.server', 49 | ) 50 | -------------------------------------------------------------------------------- /examples/djopenid/templates/consumer/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | Django OpenID Example Consumer 4 | 29 | 30 | 31 | 32 |
33 | 34 |

35 | This is an example consumer built for the Django framework. Enter 36 | an OpenID in the box below. 37 |

38 | 39 | {% if error %} 40 |
{{ error|escape }}
41 | {% endif %} 42 | 43 | {% if url %} 44 |
45 | OpenID authentication succeeded; you authenticated as 46 | {{ url|escape }}. 47 | 48 |

49 | {% if sreg %} 50 | Simple Registration data returned: 51 | 52 |

    53 | {% for pair in sreg %} 54 |
  • {{ pair.0 }}: {{ pair.1 }}
  • 55 | {% endfor %} 56 |
57 | {% else %} 58 | The server returned no Simple Registration data. 59 | {% endif %} 60 | 61 | {% if ax %} 62 | Attribute Exchange data returned: 63 | 64 |
    65 | {% for pair in ax %} 66 |
  • {{ pair.0 }}: {{ pair.1|join:", " }}
  • 67 | {% endfor %} 68 |
69 | {% else %} 70 | The server returned no Attribute Exchange data. 71 | {% endif %} 72 | 73 | {% if pape %} 74 | An authentication policy response contained these policies: 75 | 76 |
    77 | {% for uri in pape.auth_policies %} 78 |
  • {{ uri }}
  • 79 | {% endfor %} 80 |
81 | {% else %} 82 | The server returned no authentication policy data (PAPE). 83 | {% endif %} 84 |

85 |
86 | {% endif %} 87 | 88 | {% if message %} 89 |
90 | {{ message|escape }} 91 |
92 | {% endif %} 93 | 94 | {% if failure_reason %} 95 |
96 | {{ failure_reason|escape }} 97 |
98 | {% endif %} 99 | 100 |
101 | 102 | 103 |

104 | Request these authentication policies 105 | (PAPE): 106 | 107 | 108 | {% for pair in pape_policies %} 109 | 110 | 111 | 116 | 117 | {% endfor %} 118 |
112 | 115 |
119 |

120 | 121 | 122 |
123 | 124 |
125 | 126 | 127 | 128 | -------------------------------------------------------------------------------- /examples/djopenid/templates/consumer/request_form.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | {{ html|safe }} 4 | 5 | 6 | -------------------------------------------------------------------------------- /examples/djopenid/templates/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | Python OpenID Example 4 | 5 | 6 |

7 | This is a Django package which implements both an OpenID server 8 | and an OpenID consumer. These examples are provided with the 9 | OpenID library so you can learn how to use it in your own 10 | applications. 11 |

12 | 13 |

14 | To begin, click one of these links: 15 |

16 | 17 | 21 | 22 |

23 | Note: If you want to test the example consumer 24 | using the example server, you must start a separate server process 25 | for each application. 26 |

27 | 28 | 29 | -------------------------------------------------------------------------------- /examples/djopenid/templates/server/endpoint.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | This is an OpenID server endpoint. Your browser should never 5 | actually request this page. 6 | 7 | {% if error %} 8 | The OpenID server has encountered an error: 9 |

10 | {{ error|escape }} 11 |

12 | {% endif %} 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /examples/djopenid/templates/server/idPage.html: -------------------------------------------------------------------------------- 1 | {% extends "server/index.html" %} 2 | 3 | {% block head %} 4 | 6 | 7 | 8 | {% endblock %} 9 | 10 | {% block body %} 11 |

12 | This is the identity page for the OpenID that this server serves. 13 |

14 | {% endblock %} 15 | -------------------------------------------------------------------------------- /examples/djopenid/templates/server/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | Django OpenID Example Server 4 | 29 | {% block head %} 30 | 31 | {% endblock %} 32 | 33 | 34 | 35 | {% block body %} 36 |
37 | 38 |

39 | This is an example server built for the Django framework. It only 40 | authenticates one OpenID, which is also served by this 41 | application. The OpenID it serves is 42 | 43 |

44 | {{ local_id }}
45 |     
46 |

47 | 48 |
49 | {% endblock %} 50 | 51 | 52 | -------------------------------------------------------------------------------- /examples/djopenid/templates/server/pape_request_info.html: -------------------------------------------------------------------------------- 1 | {% if pape_request %} 2 | {% if pape_request.preferred_auth_policies %} 3 | The relying party requested the following PAPE policies be in effect: 4 | 5 |
    6 | {% for uri in pape_request.preferred_auth_policies %} 7 |
  • {{ uri }}
  • 8 | {% endfor %} 9 |
10 | {% endif %} 11 | 12 | {% if pape_request.preferred_auth_level_types %} 13 | The relying party requested the following authentication level types: 14 | 15 |
    16 | {% for uri in pape_request.preferred_auth_level_types %} 17 |
  • {{ uri }}
  • 18 | {% endfor %} 19 |
20 | {% endif %} 21 | {% endif %} 22 | -------------------------------------------------------------------------------- /examples/djopenid/templates/server/trust.html: -------------------------------------------------------------------------------- 1 | {% extends "server/index.html" %} 2 | 3 | {% block body %} 4 | 5 | {% ifequal trust_root_valid "Valid" %} 6 | 7 |

The site {{ trust_root|escape }} has requested verification 8 | of your OpenID.

9 | 10 | {% include "server/pape_request_info.html" %} 11 | {% endifequal %} 12 | {% ifequal trust_root_valid "Invalid" %} 13 |
14 |

This request claims to be from {{ trust_root|escape }} but I have 15 | determined that it is a pack of lies. Beware, if you release 16 | information to them, they are likely to do unconscionable things with it, 17 | being the lying liars that they are.

18 |

Please tell the real {{ trust_root|escape }} that someone is 19 | trying to abuse your trust in their good name.

20 |
21 | {% endifequal %} 22 | {% ifequal trust_root_valid "Unreachable" %} 23 |

The site {{ trust_root|escape }} has requested verification 24 | of your OpenID. I have failed to reach it and thus cannot vouch for its 25 | authenticity. Perhaps it is on your local network.

26 | {% endifequal %} 27 | {% ifequal trust_root_valid "DISCOVERY_FAILED" %} 28 |

The site {{ trust_root|escape }} has requested verification 29 | of your OpenID. However, {{ trust_root|escape }} does not 30 | implement OpenID 2.0's relying party verification mechanism. Please use 31 | extra caution in deciding whether to release information to this party, 32 | and ask {{ trust_root|escape }} to implement relying party 33 | verification for your future transactions.

34 | 35 | {% include "server/pape_request_info.html" %} 36 | {% endifequal %} 37 | 38 | 39 | 40 |
41 |
43 | Verify your identity to the relying party? 44 | 45 |
46 | 47 | 48 |
49 |
50 | 51 | {% endblock %} 52 | -------------------------------------------------------------------------------- /examples/djopenid/templates/xrds.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 7 | 8 | {% for type_uri in type_uris %} 9 | {{ type_uri|escape }} 10 | {% endfor %} 11 | {% for endpoint_url in endpoint_urls %} 12 | {{ endpoint_url }} 13 | {% endfor %} 14 | 15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /examples/djopenid/urls.py: -------------------------------------------------------------------------------- 1 | """Djopenid URLs.""" 2 | from __future__ import unicode_literals 3 | 4 | from django.conf.urls import include, url 5 | from django.views.generic import TemplateView 6 | 7 | urlpatterns = [ 8 | url('^$', TemplateView.as_view(template_name='index.html'), name='index'), 9 | url('^consumer/', include(('djopenid.consumer.urls', 'consumer'))), 10 | url('^server/', include(('djopenid.server.urls', 'server'))), 11 | ] 12 | -------------------------------------------------------------------------------- /examples/djopenid/util.py: -------------------------------------------------------------------------------- 1 | """ 2 | Utility code for the Django example consumer and server. 3 | """ 4 | from __future__ import unicode_literals 5 | 6 | import six 7 | from django.conf import settings 8 | from django.core.exceptions import ImproperlyConfigured 9 | from django.db import connection 10 | from django.shortcuts import render 11 | from openid.store import sqlstore 12 | from openid.store.filestore import FileOpenIDStore 13 | from openid.yadis.constants import YADIS_CONTENT_TYPE 14 | 15 | 16 | def getOpenIDStore(filestore_path, table_prefix): 17 | """ 18 | Returns an OpenID association store object based on the database 19 | engine chosen for this Django application. 20 | 21 | * If no database engine is chosen, a filesystem-based store will 22 | be used whose path is filestore_path. 23 | 24 | * If a database engine is chosen, a store object for that database 25 | type will be returned. 26 | 27 | * If the chosen engine is not supported by the OpenID library, 28 | raise ImproperlyConfigured. 29 | 30 | * If a database store is used, this will create the tables 31 | necessary to use it. The table names will be prefixed with 32 | table_prefix. DO NOT use the same table prefix for both an 33 | OpenID consumer and an OpenID server in the same database. 34 | 35 | The result of this function should be passed to the Consumer 36 | constructor as the store parameter. 37 | """ 38 | if not settings.DATABASES.get('default', {'ENGINE': None}).get('ENGINE'): 39 | return FileOpenIDStore(filestore_path) 40 | 41 | # Possible side-effect: create a database connection if one isn't 42 | # already open. 43 | connection.cursor() 44 | 45 | # Create table names to specify for SQL-backed stores. 46 | tablenames = { 47 | 'associations_table': table_prefix + 'openid_associations', 48 | 'nonces_table': table_prefix + 'openid_nonces', 49 | } 50 | 51 | types = { 52 | 'django.db.backends.postgresql': sqlstore.PostgreSQLStore, 53 | 'django.db.backends.mysql': sqlstore.MySQLStore, 54 | 'django.db.backends.sqlite3': sqlstore.SQLiteStore, 55 | } 56 | 57 | engine = settings.DATABASES.get('default', {'ENGINE': None}).get('ENGINE') 58 | try: 59 | s = types[engine](connection.connection, **tablenames) 60 | except KeyError: 61 | raise ImproperlyConfigured("Database engine %s not supported by OpenID library" % engine) 62 | 63 | try: 64 | s.createTables() 65 | except Exception: 66 | # XXX This is not the Right Way to do this, but because the 67 | # underlying database implementation might differ in behavior 68 | # at this point, we can't reliably catch the right 69 | # exception(s) here. Ideally, the SQL store in the OpenID 70 | # library would catch exceptions that it expects and fail 71 | # silently, but that could be bad, too. More ideally, the SQL 72 | # store would not attempt to create tables it knows already 73 | # exists. 74 | pass 75 | 76 | return s 77 | 78 | 79 | def normalDict(request_data): 80 | """ 81 | Converts a django request MutliValueDict (e.g., request.GET, 82 | request.POST) into a standard python dict whose values are the 83 | first value from each of the MultiValueDict's value lists. This 84 | avoids the OpenID library's refusal to deal with dicts whose 85 | values are lists, because in OpenID, each key in the query arg set 86 | can have at most one value. 87 | """ 88 | return dict((k, v) for k, v in six.iteritems(request_data)) 89 | 90 | 91 | def renderXRDS(request, type_uris, endpoint_urls): 92 | """Render an XRDS page with the specified type URIs and endpoint 93 | URLs in one service block, and return a response with the 94 | appropriate content-type. 95 | """ 96 | context = {'type_uris': type_uris, 'endpoint_urls': endpoint_urls} 97 | return render(request, 'xrds.xml', context, content_type=YADIS_CONTENT_TYPE) 98 | -------------------------------------------------------------------------------- /openid/__init__.py: -------------------------------------------------------------------------------- 1 | """ 2 | This package is an implementation of the OpenID specification in 3 | Python. It contains code for both server and consumer 4 | implementations. For information on implementing an OpenID consumer, 5 | see the C{L{openid.consumer.consumer}} module. For information on 6 | implementing an OpenID server, see the C{L{openid.server.server}} 7 | module. 8 | 9 | @contact: U{http://openid.net/developers/dev-mailing-lists/ 10 | 127: 48 | array = bytearray([0]) + array 49 | return bytes(array) 50 | 51 | 52 | def int_to_bytes(value): 53 | """ 54 | Convert integer to byte string. 55 | 56 | @type value: Union[six.integer_types] 57 | @rtype: six.binary_type 58 | """ 59 | hex_value = '{:x}'.format(value) 60 | if len(hex_value) % 2: 61 | hex_value = '0' + hex_value 62 | array = bytearray.fromhex(hex_value) 63 | # The output must be `btwoc` compatible 64 | return fix_btwoc(array) 65 | 66 | 67 | # Deprecated versions of bytes <--> int conversions 68 | def longToBinary(value): 69 | warnings.warn("Function longToBinary is deprecated in favor of int_to_bytes.", DeprecationWarning) 70 | return int_to_bytes(value) 71 | 72 | 73 | def binaryToLong(s): 74 | warnings.warn("Function binaryToLong is deprecated in favor of bytes_to_int.", DeprecationWarning) 75 | return bytes_to_int(s) 76 | 77 | 78 | def longToBase64(value): 79 | return toBase64(int_to_bytes(value)) 80 | 81 | 82 | def base64ToLong(s): 83 | return bytes_to_int(fromBase64(s)) 84 | -------------------------------------------------------------------------------- /openid/extension.py: -------------------------------------------------------------------------------- 1 | from __future__ import unicode_literals 2 | 3 | import warnings 4 | 5 | from openid import message as message_module 6 | 7 | 8 | class Extension(object): 9 | """An interface for OpenID extensions. 10 | 11 | @ivar ns_uri: The namespace to which to add the arguments for this 12 | extension 13 | """ 14 | ns_uri = None 15 | ns_alias = None 16 | 17 | def getExtensionArgs(self): 18 | """Get the string arguments that should be added to an OpenID 19 | message for this extension. 20 | 21 | @returns: A dictionary of completely non-namespaced arguments 22 | to be added. For example, if the extension's alias is 23 | 'uncle', and this method returns {'meat':'Hot Rats'}, the 24 | final message will contain {'openid.uncle.meat':'Hot Rats'} 25 | """ 26 | raise NotImplementedError 27 | 28 | def toMessage(self, message=None): 29 | """Add the arguments from this extension to the provided 30 | message, or create a new message containing only those 31 | arguments. 32 | 33 | @returns: The message with the extension arguments added 34 | """ 35 | if message is None: 36 | warnings.warn('Passing None to Extension.toMessage is deprecated. ' 37 | 'Creating a message assuming you want OpenID 2.', 38 | DeprecationWarning, stacklevel=2) 39 | message = message_module.Message(message_module.OPENID2_NS) 40 | 41 | implicit = message.isOpenID1() 42 | 43 | try: 44 | message.namespaces.addAlias(self.ns_uri, self.ns_alias, 45 | implicit=implicit) 46 | except KeyError: 47 | if message.namespaces.getAlias(self.ns_uri) != self.ns_alias: 48 | raise 49 | 50 | message.updateArgs(self.ns_uri, self.getExtensionArgs()) 51 | return message 52 | -------------------------------------------------------------------------------- /openid/extensions/__init__.py: -------------------------------------------------------------------------------- 1 | """OpenID Extension modules.""" 2 | from __future__ import unicode_literals 3 | 4 | __all__ = ['ax', 'pape', 'sreg'] 5 | -------------------------------------------------------------------------------- /openid/extensions/draft/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/openid/python-openid/afa6adacbe1a41d8f614c8bce2264dfbe9e76489/openid/extensions/draft/__init__.py -------------------------------------------------------------------------------- /openid/extensions/draft/pape5.py: -------------------------------------------------------------------------------- 1 | """An implementation of the OpenID Provider Authentication Policy 2 | Extension 1.0, Draft 5 3 | 4 | @see: http://openid.net/developers/specs/ 5 | 6 | @since: 2.1.0 7 | """ 8 | from __future__ import unicode_literals 9 | 10 | import warnings 11 | 12 | from openid.extensions.pape import (AUTH_MULTI_FACTOR, AUTH_MULTI_FACTOR_PHYSICAL, AUTH_PHISHING_RESISTANT, LEVELS_JISA, 13 | LEVELS_NIST, Request, Response, ns_uri) 14 | 15 | __all__ = [ 16 | 'Request', 17 | 'Response', 18 | 'ns_uri', 19 | 'AUTH_PHISHING_RESISTANT', 20 | 'AUTH_MULTI_FACTOR', 21 | 'AUTH_MULTI_FACTOR_PHYSICAL', 22 | 'LEVELS_NIST', 23 | 'LEVELS_JISA', 24 | ] 25 | 26 | warnings.warn("Module 'openid.extensions.draft.pape5' is deprecated in favor of 'openid.extensions.pape'.", 27 | DeprecationWarning) 28 | -------------------------------------------------------------------------------- /openid/kvform.py: -------------------------------------------------------------------------------- 1 | """Utilities for key-value format conversions.""" 2 | from __future__ import unicode_literals 3 | 4 | import logging 5 | 6 | import six 7 | 8 | from .oidutil import string_to_text 9 | 10 | __all__ = ['seqToKV', 'kvToSeq', 'dictToKV', 'kvToDict'] 11 | 12 | 13 | _LOGGER = logging.getLogger(__name__) 14 | 15 | 16 | class KVFormError(ValueError): 17 | pass 18 | 19 | 20 | def seqToKV(seq, strict=False): 21 | """Represent a sequence of pairs of strings as newline-terminated 22 | key:value pairs. The pairs are generated in the order given. 23 | 24 | @param seq: The pairs 25 | @type seq: List[Tuple[six.text_type, six.text_type]], binary_type values are deprecated. 26 | 27 | @return: A string representation of the sequence 28 | @rtype: six.text_type 29 | """ 30 | def err(msg): 31 | formatted = 'seqToKV warning: %s: %r' % (msg, seq) 32 | if strict: 33 | raise KVFormError(formatted) 34 | else: 35 | _LOGGER.debug(formatted) 36 | 37 | lines = [] 38 | for k, v in seq: 39 | if not isinstance(k, (six.text_type, six.binary_type)): 40 | err('Converting key to text: %r' % k) 41 | k = six.text_type(k) 42 | if not isinstance(v, (six.text_type, six.binary_type)): 43 | err('Converting value to text: %r' % v) 44 | v = six.text_type(v) 45 | 46 | k = string_to_text(k, "Binary values for keys are deprecated. Use text input instead.") 47 | v = string_to_text(v, "Binary values for values are deprecated. Use text input instead.") 48 | 49 | if '\n' in k: 50 | raise KVFormError( 51 | 'Invalid input for seqToKV: key contains newline: %r' % (k,)) 52 | 53 | if ':' in k: 54 | raise KVFormError( 55 | 'Invalid input for seqToKV: key contains colon: %r' % (k,)) 56 | 57 | if k.strip() != k: 58 | err('Key has whitespace at beginning or end: %r' % (k,)) 59 | 60 | if '\n' in v: 61 | raise KVFormError( 62 | 'Invalid input for seqToKV: value contains newline: %r' % (v,)) 63 | 64 | if v.strip() != v: 65 | err('Value has whitespace at beginning or end: %r' % (v,)) 66 | 67 | lines.append(k + ':' + v + '\n') 68 | 69 | return ''.join(lines) 70 | 71 | 72 | def kvToSeq(data, strict=False): 73 | """ 74 | Parse newline-terminated key:value pair string into a sequence. 75 | 76 | After one parse, seqToKV and kvToSeq are inverses, with no warnings:: 77 | 78 | seq = kvToSeq(s) 79 | seqToKV(kvToSeq(seq)) == seq 80 | 81 | @type data: six.text_type, six.binary_type is deprecated 82 | 83 | @rtype: List[Tuple[six.text_type, six.text_type]] 84 | """ 85 | def err(msg): 86 | formatted = 'kvToSeq warning: %s: %r' % (msg, data) 87 | if strict: 88 | raise KVFormError(formatted) 89 | else: 90 | _LOGGER.debug(formatted) 91 | 92 | data = string_to_text(data, "Binary values for data are deprecated. Use text input instead.") 93 | 94 | lines = data.split('\n') 95 | if lines[-1]: 96 | err('Does not end in a newline') 97 | else: 98 | del lines[-1] 99 | 100 | pairs = [] 101 | line_num = 0 102 | for line in lines: 103 | line_num += 1 104 | 105 | # Ignore blank lines 106 | if not line.strip(): 107 | continue 108 | 109 | pair = line.split(':', 1) 110 | if len(pair) == 2: 111 | k, v = pair 112 | k_s = k.strip() 113 | if k_s != k: 114 | fmt = ('In line %d, ignoring leading or trailing ' 115 | 'whitespace in key %r') 116 | err(fmt % (line_num, k)) 117 | 118 | if not k_s: 119 | err('In line %d, got empty key' % (line_num,)) 120 | 121 | v_s = v.strip() 122 | if v_s != v: 123 | fmt = ('In line %d, ignoring leading or trailing ' 124 | 'whitespace in value %r') 125 | err(fmt % (line_num, v)) 126 | 127 | pairs.append((k_s, v_s)) 128 | else: 129 | err('Line %d does not contain a colon' % line_num) 130 | 131 | return pairs 132 | 133 | 134 | def dictToKV(d): 135 | seq = sorted(d.items()) 136 | return seqToKV(seq) 137 | 138 | 139 | def kvToDict(s): 140 | return dict(kvToSeq(s)) 141 | -------------------------------------------------------------------------------- /openid/oidutil.py: -------------------------------------------------------------------------------- 1 | """This module contains general utility code that is used throughout 2 | the library. 3 | 4 | For users of this library, the C{L{log}} function is probably the most 5 | interesting. 6 | """ 7 | from __future__ import unicode_literals 8 | 9 | import binascii 10 | import logging 11 | import warnings 12 | 13 | import six 14 | from six.moves.urllib.parse import urlencode 15 | 16 | __all__ = ['log', 'appendArgs', 'toBase64', 'fromBase64', 'autoSubmitHTML'] 17 | 18 | 19 | def autoSubmitHTML(form, title='OpenID transaction in progress'): 20 | return """ 21 | 22 | 23 | %s 24 | 25 | 26 | %s 27 | 33 | 34 | 35 | """ % (title, form) 36 | 37 | 38 | def log(message, level=0): 39 | """Handle a log message from the OpenID library. 40 | 41 | This is a legacy function which redirects to logging.error. 42 | The logging module should be used instead of this 43 | 44 | @param message: A string containing a debugging message from the 45 | OpenID library 46 | @type message: six.text_type, six.binary_type is deprecated 47 | 48 | @param level: The severity of the log message. This parameter is 49 | currently unused, but in the future, the library may indicate 50 | more important information with a higher level value. 51 | @type level: int or None 52 | 53 | @returns: Nothing. 54 | """ 55 | message = string_to_text(message, "Binary values for log are deprecated. Use text input instead.") 56 | 57 | logging.error("This is a legacy log message, please use the logging module. Message: %s", message) 58 | 59 | 60 | def appendArgs(url, args): 61 | """Append query arguments to a HTTP(s) URL. If the URL already has 62 | query arguemtns, these arguments will be added, and the existing 63 | arguments will be preserved. Duplicate arguments will not be 64 | detected or collapsed (both will appear in the output). 65 | 66 | @param url: The url to which the arguments will be appended 67 | @type url: six.text_type, six.binary_type is deprecated 68 | 69 | @param args: The query arguments to add to the URL. If a 70 | dictionary is passed, the items will be sorted before 71 | appending them to the URL. If a sequence of pairs is passed, 72 | the order of the sequence will be preserved. 73 | @type args: Union[Dict[six.text_type, six.text_type], List[Tuple[six.text_type, six.text_type]]], 74 | six.binary_type is deprecated 75 | 76 | @returns: The URL with the parameters added 77 | @rtype: six.text_type 78 | """ 79 | url = string_to_text(url, "Binary values for appendArgs are deprecated. Use text input instead.") 80 | 81 | if hasattr(args, 'items'): 82 | args = sorted(args.items()) 83 | else: 84 | args = list(args) 85 | 86 | if len(args) == 0: 87 | return url 88 | 89 | if '?' in url: 90 | sep = '&' 91 | else: 92 | sep = '?' 93 | 94 | i = 0 95 | for k, v in args: 96 | k = string_to_text(k, "Binary values for appendArgs are deprecated. Use text input instead.") 97 | v = string_to_text(v, "Binary values for appendArgs are deprecated. Use text input instead.") 98 | args[i] = (k.encode('utf-8'), v.encode('utf-8')) 99 | i += 1 100 | 101 | encoded_args = urlencode(args) 102 | # `urlencode` returns `str` in both py27 and py3+. We need to convert it to six.text_type. 103 | if not isinstance(encoded_args, six.text_type): 104 | encoded_args = encoded_args.decode('utf-8') 105 | return '%s%s%s' % (url, sep, encoded_args) 106 | 107 | 108 | def toBase64(s): 109 | """Return string s as base64, omitting newlines. 110 | 111 | @type s: six.binary_type 112 | @rtype six.text_type 113 | """ 114 | return binascii.b2a_base64(s)[:-1].decode('utf-8') 115 | 116 | 117 | def fromBase64(s): 118 | """Return binary data from base64 encoded string. 119 | 120 | @type s: six.text_type, six.binary_type deprecated. 121 | @rtype six.binary_type 122 | """ 123 | s = string_to_text(s, "Binary values for s are deprecated. Use text input instead.") 124 | try: 125 | return binascii.a2b_base64(s) 126 | except binascii.Error as why: 127 | # Convert to a common exception type 128 | raise ValueError(six.text_type(why)) 129 | 130 | 131 | class Symbol(object): 132 | """This class implements an object that compares equal to others 133 | of the same type that have the same name. These are distict from 134 | string objects. 135 | """ 136 | 137 | def __init__(self, name): 138 | self.name = name 139 | 140 | def __eq__(self, other): 141 | return type(self) == type(other) and self.name == other.name 142 | 143 | def __ne__(self, other): 144 | return not (self == other) 145 | 146 | def __hash__(self): 147 | return hash((self.__class__, self.name)) 148 | 149 | def __repr__(self): 150 | return '' % (self.name,) 151 | 152 | 153 | def string_to_text(value, deprecate_msg): 154 | """ 155 | Return input string coverted to text string. 156 | 157 | If input is text, it is returned as is. 158 | If input is binary, it is decoded using UTF-8 to text. 159 | """ 160 | assert isinstance(value, (six.text_type, six.binary_type)) 161 | if isinstance(value, six.binary_type): 162 | warnings.warn(deprecate_msg, DeprecationWarning) 163 | value = value.decode('utf-8') 164 | return value 165 | 166 | 167 | def force_text(value): 168 | """ 169 | Return a text object representing value in UTF-8 encoding. 170 | """ 171 | if isinstance(value, six.text_type): 172 | # It's already a text, just return it. 173 | return value 174 | elif isinstance(value, bytes): 175 | # It's a byte string, decode it. 176 | return value.decode('utf-8') 177 | else: 178 | # It's not a string, convert it. 179 | return six.text_type(value) 180 | -------------------------------------------------------------------------------- /openid/server/__init__.py: -------------------------------------------------------------------------------- 1 | """ 2 | This package contains the portions of the library used only when 3 | implementing an OpenID server. See L{openid.server.server}. 4 | """ 5 | from __future__ import unicode_literals 6 | 7 | __all__ = ['server', 'trustroot'] 8 | -------------------------------------------------------------------------------- /openid/sreg.py: -------------------------------------------------------------------------------- 1 | """moved to L{openid.extensions.sreg}""" 2 | from __future__ import unicode_literals 3 | 4 | import warnings 5 | 6 | from openid.extensions.sreg import SRegRequest, SRegResponse, data_fields, ns_uri, ns_uri_1_0, ns_uri_1_1, supportsSReg 7 | 8 | warnings.warn("openid.sreg has moved to openid.extensions.sreg", 9 | DeprecationWarning) 10 | 11 | __all__ = ['SRegRequest', 'SRegResponse', 'data_fields', 'ns_uri', 'ns_uri_1_0', 'ns_uri_1_1', 'supportsSReg'] 12 | -------------------------------------------------------------------------------- /openid/store/__init__.py: -------------------------------------------------------------------------------- 1 | """ 2 | This package contains the modules related to this library's use of 3 | persistent storage. 4 | 5 | @sort: interface, filestore, sqlstore, memstore 6 | """ 7 | from __future__ import unicode_literals 8 | 9 | __all__ = ['interface', 'filestore', 'sqlstore', 'memstore', 'nonce'] 10 | -------------------------------------------------------------------------------- /openid/store/memstore.py: -------------------------------------------------------------------------------- 1 | """A simple store using only in-process memory.""" 2 | from __future__ import unicode_literals 3 | 4 | import copy 5 | import time 6 | 7 | import six 8 | 9 | from openid.store import nonce 10 | 11 | 12 | class ServerAssocs(object): 13 | def __init__(self): 14 | self.assocs = {} 15 | 16 | def set(self, assoc): 17 | self.assocs[assoc.handle] = assoc 18 | 19 | def get(self, handle): 20 | return self.assocs.get(handle) 21 | 22 | def remove(self, handle): 23 | try: 24 | del self.assocs[handle] 25 | except KeyError: 26 | return False 27 | else: 28 | return True 29 | 30 | def best(self): 31 | """Returns association with the oldest issued date. 32 | 33 | or None if there are no associations. 34 | """ 35 | best = None 36 | for assoc in self.assocs.values(): 37 | if best is None or best.issued < assoc.issued: 38 | best = assoc 39 | return best 40 | 41 | def cleanup(self): 42 | """Remove expired associations. 43 | 44 | @return: tuple of (removed associations, remaining associations) 45 | """ 46 | remove = [] 47 | for handle, assoc in six.iteritems(self.assocs): 48 | if assoc.getExpiresIn() == 0: 49 | remove.append(handle) 50 | for handle in remove: 51 | del self.assocs[handle] 52 | return len(remove), len(self.assocs) 53 | 54 | 55 | class MemoryStore(object): 56 | """In-process memory store. 57 | 58 | Use for single long-running processes. No persistence supplied. 59 | """ 60 | 61 | def __init__(self): 62 | self.server_assocs = {} 63 | self.nonces = {} 64 | 65 | def _getServerAssocs(self, server_url): 66 | try: 67 | return self.server_assocs[server_url] 68 | except KeyError: 69 | assocs = self.server_assocs[server_url] = ServerAssocs() 70 | return assocs 71 | 72 | def storeAssociation(self, server_url, assoc): 73 | assocs = self._getServerAssocs(server_url) 74 | assocs.set(copy.deepcopy(assoc)) 75 | 76 | def getAssociation(self, server_url, handle=None): 77 | assocs = self._getServerAssocs(server_url) 78 | if handle is None: 79 | return assocs.best() 80 | else: 81 | return assocs.get(handle) 82 | 83 | def removeAssociation(self, server_url, handle): 84 | assocs = self._getServerAssocs(server_url) 85 | return assocs.remove(handle) 86 | 87 | def useNonce(self, server_url, timestamp, salt): 88 | if abs(timestamp - time.time()) > nonce.SKEW: 89 | return False 90 | 91 | anonce = (six.text_type(server_url), int(timestamp), six.text_type(salt)) 92 | if anonce in self.nonces: 93 | return False 94 | else: 95 | self.nonces[anonce] = None 96 | return True 97 | 98 | def cleanupNonces(self): 99 | now = time.time() 100 | expired = [] 101 | for anonce in self.nonces: 102 | if abs(anonce[1] - now) > nonce.SKEW: 103 | # removing items while iterating over the set could be bad. 104 | expired.append(anonce) 105 | 106 | for anonce in expired: 107 | del self.nonces[anonce] 108 | return len(expired) 109 | 110 | def cleanupAssociations(self): 111 | remove_urls = [] 112 | removed_assocs = 0 113 | for server_url, assocs in six.iteritems(self.server_assocs): 114 | removed, remaining = assocs.cleanup() 115 | removed_assocs += removed 116 | if not remaining: 117 | remove_urls.append(server_url) 118 | 119 | # Remove entries from server_assocs that had none remaining. 120 | for server_url in remove_urls: 121 | del self.server_assocs[server_url] 122 | return removed_assocs 123 | 124 | def __eq__(self, other): 125 | return ((self.server_assocs == other.server_assocs) and (self.nonces == other.nonces)) 126 | 127 | def __ne__(self, other): 128 | return not (self == other) 129 | -------------------------------------------------------------------------------- /openid/store/nonce.py: -------------------------------------------------------------------------------- 1 | from __future__ import unicode_literals 2 | 3 | import itertools 4 | import random 5 | import string 6 | from calendar import timegm 7 | from time import gmtime, strftime, strptime, time 8 | 9 | from openid.oidutil import string_to_text 10 | 11 | __all__ = [ 12 | 'split', 13 | 'mkNonce', 14 | 'checkTimestamp', 15 | ] 16 | 17 | 18 | NONCE_CHARS = string.ascii_letters + string.digits 19 | 20 | # Keep nonces for five hours (allow five hours for the combination of 21 | # request time and clock skew). This is probably way more than is 22 | # necessary, but there is not much overhead in storing nonces. 23 | SKEW = 60 * 60 * 5 24 | 25 | time_fmt = '%Y-%m-%dT%H:%M:%SZ' 26 | time_str_len = len('0000-00-00T00:00:00Z') 27 | 28 | 29 | def split(nonce_string): 30 | """Extract a timestamp from the given nonce string 31 | 32 | @param nonce_string: the nonce from which to extract the timestamp 33 | @type nonce_string: six.text_type, six.binary_type is deprecated 34 | 35 | @returns: A pair of a Unix timestamp and the salt characters 36 | @returntype: (int, six.text_type) 37 | 38 | @raises ValueError: if the nonce does not start with a correctly 39 | formatted time string 40 | """ 41 | nonce_string = string_to_text(nonce_string, 42 | "Binary values for nonce_string are deprecated. Use text input instead.") 43 | 44 | timestamp_str = nonce_string[:time_str_len] 45 | timestamp = timegm(strptime(timestamp_str, time_fmt)) 46 | if timestamp < 0: 47 | raise ValueError('time out of range') 48 | return timestamp, nonce_string[time_str_len:] 49 | 50 | 51 | def checkTimestamp(nonce_string, allowed_skew=SKEW, now=None): 52 | """Is the timestamp that is part of the specified nonce string 53 | within the allowed clock-skew of the current time? 54 | 55 | @param nonce_string: The nonce that is being checked 56 | @type nonce_string: six.text_type, six.binary_type is deprecated 57 | 58 | @param allowed_skew: How many seconds should be allowed for 59 | completing the request, allowing for clock skew. 60 | @type allowed_skew: int 61 | 62 | @param now: The current time, as a Unix timestamp 63 | @type now: int 64 | 65 | @returntype: bool 66 | @returns: Whether the timestamp is correctly formatted and within 67 | the allowed skew of the current time. 68 | """ 69 | try: 70 | stamp, _ = split(nonce_string) 71 | except ValueError: 72 | return False 73 | else: 74 | if now is None: 75 | now = time() 76 | 77 | # Time after which we should not use the nonce 78 | past = now - allowed_skew 79 | 80 | # Time that is too far in the future for us to allow 81 | future = now + allowed_skew 82 | 83 | # the stamp is not too far in the future and is not too far in 84 | # the past 85 | return past <= stamp <= future 86 | 87 | 88 | def make_nonce_salt(length=6): 89 | """ 90 | Generate and return a nonce salt. 91 | 92 | @param length: Length of the generated string. 93 | @type length: int 94 | @rtype: six.text_type 95 | """ 96 | sys_random = random.SystemRandom() 97 | random_chars = itertools.starmap(sys_random.choice, itertools.repeat((NONCE_CHARS, ), length)) 98 | return ''.join(random_chars) 99 | 100 | 101 | def mkNonce(when=None): 102 | """Generate a nonce with the current timestamp 103 | 104 | @param when: Unix timestamp representing the issue time of the 105 | nonce. Defaults to the current time. 106 | @type when: int 107 | 108 | @returntype: six.text_type 109 | @returns: A string that should be usable as a one-way nonce 110 | 111 | @see: time 112 | """ 113 | if when is None: 114 | t = gmtime() 115 | else: 116 | t = gmtime(when) 117 | 118 | time_str = strftime(time_fmt, t) 119 | return time_str + make_nonce_salt() 120 | -------------------------------------------------------------------------------- /openid/test/__init__.py: -------------------------------------------------------------------------------- 1 | """Openid library tests.""" 2 | from __future__ import unicode_literals 3 | 4 | import unittest 5 | 6 | 7 | # Utility code to allow run unittest under coverage called as module. 8 | def _run_unittest(): 9 | unittest.main() 10 | 11 | 12 | if __name__ == '__main__': 13 | _run_unittest() 14 | -------------------------------------------------------------------------------- /openid/test/data/accept.txt: -------------------------------------------------------------------------------- 1 | # Accept: [Accept: header value from RFC2616, 2 | # http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html] 3 | # Available: [whitespace-separated content types] 4 | # Expected: [Accept-header like list, containing the available content 5 | # types with their q-values] 6 | 7 | Accept: */* 8 | Available: text/plain 9 | Expected: text/plain; q=1.0 10 | 11 | Accept: */* 12 | Available: text/plain, text/html 13 | Expected: text/plain; q=1.0, text/html; q=1.0 14 | 15 | # The order matters 16 | Accept: */* 17 | Available: text/html, text/plain 18 | Expected: text/html; q=1.0, text/plain; q=1.0 19 | 20 | Accept: text/*, */*; q=0.9 21 | Available: text/plain, image/jpeg 22 | Expected: text/plain; q=1.0, image/jpeg; q=0.9 23 | 24 | Accept: text/*, */*; q=0.9 25 | Available: image/jpeg, text/plain 26 | Expected: text/plain; q=1.0, image/jpeg; q=0.9 27 | 28 | # wildcard subtypes still reject differing main types 29 | Accept: text/* 30 | Available: image/jpeg, text/plain 31 | Expected: text/plain; q=1.0 32 | 33 | Accept: text/html 34 | Available: text/html 35 | Expected: text/html; q=1.0 36 | 37 | Accept: text/html, text/* 38 | Available: text/html 39 | Expected: text/html; q=1.0 40 | 41 | Accept: text/html, text/* 42 | Available: text/plain, text/html 43 | Expected: text/plain; q=1.0, text/html; q=1.0 44 | 45 | Accept: text/html, text/*; q=0.9 46 | Available: text/plain, text/html 47 | Expected: text/html; q=1.0, text/plain; q=0.9 48 | 49 | # If a more specific type has a higher q-value, then the higher value wins 50 | Accept: text/*; q=0.9, text/html 51 | Available: text/plain, text/html 52 | Expected: text/html; q=1.0, text/plain; q=0.9 53 | 54 | Accept: */*, text/*; q=0.9, text/html; q=0.1 55 | Available: text/plain, text/html, image/monkeys 56 | Expected: image/monkeys; q=1.0, text/plain; q=0.9, text/html; q=0.1 57 | 58 | Accept: text/*, text/html; q=0 59 | Available: text/html 60 | Expected: 61 | 62 | Accept: text/*, text/html; q=0 63 | Available: text/html, text/plain 64 | Expected: text/plain; q=1.0 65 | 66 | Accept: text/html 67 | Available: text/plain 68 | Expected: 69 | 70 | Accept: application/xrds+xml, text/html; q=0.9 71 | Available: application/xrds+xml, text/html 72 | Expected: application/xrds+xml; q=1.0, text/html; q=0.9 73 | 74 | Accept: application/xrds+xml, */*; q=0.9 75 | Available: application/xrds+xml, text/html 76 | Expected: application/xrds+xml; q=1.0, text/html; q=0.9 77 | 78 | Accept: application/xrds+xml, application/xhtml+xml; q=0.9, text/html; q=0.8, text/xml; q=0.7 79 | Available: application/xrds+xml, text/html 80 | Expected: application/xrds+xml; q=1.0, text/html; q=0.8 81 | 82 | # See http://www.rfc-editor.org/rfc/rfc3023.txt, section A.13 83 | Accept: application/xrds 84 | Available: application/xrds+xml 85 | Expected: 86 | 87 | Accept: application/xrds+xml 88 | Available: application/xrds 89 | Expected: 90 | 91 | Accept: application/xml 92 | Available: application/xrds+xml 93 | Expected: 94 | 95 | Available: application/xrds+xml 96 | Accept: application/xml 97 | Expected: 98 | 99 | 100 | 101 | ################################################# 102 | # The tests below this line are documentation of how this library 103 | # works. If the implementation changes, it's acceptable to change the 104 | # test to reflect that. These are specified so that we can make sure 105 | # that the current implementation actually works the way that we 106 | # expect it to given these inputs. 107 | 108 | Accept: text/html;level=1 109 | Available: text/html 110 | Expected: text/html; q=1.0 111 | 112 | Accept: text/html; level=1, text/html; level=9; q=0.1 113 | Available: text/html 114 | Expected: text/html; q=1.0 115 | 116 | Accept: text/html; level=9; q=0.1, text/html; level=1 117 | Available: text/html 118 | Expected: text/html; q=1.0 119 | -------------------------------------------------------------------------------- /openid/test/data/example-xrds.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 6 | 7 | 8 | 9 | http://example.com/ 10 | http://www.openidenabled.com/ 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /openid/test/data/openid-1.2-consumer-sqlitestore.db: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/openid/python-openid/afa6adacbe1a41d8f614c8bce2264dfbe9e76489/openid/test/data/openid-1.2-consumer-sqlitestore.db -------------------------------------------------------------------------------- /openid/test/data/test1-discover.txt: -------------------------------------------------------------------------------- 1 | equiv 2 | Status: 200 OK 3 | Content-Type: text/html 4 | 5 | 6 | 7 | 8 | Joe Schmoe's Homepage 9 | 10 | 11 |

Joe Schmoe's Homepage

12 |

Blah blah blah blah blah blah blah

13 | 14 | 15 | 16 | header 17 | Status: 200 OK 18 | Content-Type: text/html 19 | YADIS_HEADER: URL_BASE/xrds 20 | 21 | 22 | 23 | Joe Schmoe's Homepage 24 | 25 | 26 |

Joe Schmoe's Homepage

27 |

Blah blah blah blah blah blah blah

28 | 29 | 30 | xrds 31 | Status: 200 OK 32 | Content-Type: application/xrds+xml 33 | 34 | 35 | 36 | xrds_ctparam 37 | Status: 200 OK 38 | Content-Type: application/xrds+xml; charset=UTF8 39 | 40 | 41 | 42 | xrds_ctcase 43 | Status: 200 OK 44 | Content-Type: appliCATION/XRDS+xml 45 | 46 | 47 | 48 | xrds_html 49 | Status: 200 OK 50 | Content-Type: text/html 51 | 52 | 53 | 54 | redir_equiv 55 | Status: 302 Found 56 | Content-Type: text/plain 57 | Location: URL_BASE/equiv 58 | 59 | You are presently being redirected. 60 | 61 | redir_header 62 | Status: 302 Found 63 | Content-Type: text/plain 64 | Location: URL_BASE/header 65 | 66 | You are presently being redirected. 67 | 68 | redir_xrds 69 | Status: 302 Found 70 | Content-Type: application/xrds+xml 71 | Location: URL_BASE/xrds 72 | 73 | 74 | 75 | redir_xrds_html 76 | Status: 302 Found 77 | Content-Type: text/plain 78 | Location: URL_BASE/xrds_html 79 | 80 | You are presently being redirected. 81 | 82 | redir_redir_equiv 83 | Status: 302 Found 84 | Content-Type: text/plain 85 | Location: URL_BASE/redir_equiv 86 | 87 | You are presently being redirected. 88 | 89 | lowercase_header 90 | Status: 200 OK 91 | Content-Type: text/html 92 | x-xrds-location: URL_BASE/xrds 93 | 94 | 95 | 96 | Joe Schmoe's Homepage 97 | 98 | 99 |

Joe Schmoe's Homepage

100 |

Blah blah blah blah blah blah blah

101 | 102 | 103 | 404_server_response 104 | Status: 404 Not Found 105 | 106 | EEk! 107 | 108 | 500_server_response 109 | Status: 500 Server error 110 | 111 | EEk! 112 | 113 | 201_server_response 114 | Status: 201 Created 115 | 116 | EEk! 117 | 118 | 404_with_header 119 | Status: 404 Not Found 120 | YADIS_HEADER: URL_BASE/xrds 121 | 122 | EEk! 123 | 124 | 404_with_meta 125 | Status: 404 Not Found 126 | Content-Type: text/html 127 | 128 | 129 | 130 | 131 | Joe Schmoe's Homepage 132 | 133 | 134 |

Joe Schmoe's Homepage

135 |

Blah blah blah blah blah blah blah

136 | 137 | 138 | -------------------------------------------------------------------------------- /openid/test/data/test_discover/openid.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Identity Page for Smoker 5 | 6 | 7 | 8 | 9 |

foo

10 | 11 | 12 | -------------------------------------------------------------------------------- /openid/test/data/test_discover/openid2.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Identity Page for Smoker 5 | 6 | 7 | 8 | 9 |

foo

10 | 11 | 12 | -------------------------------------------------------------------------------- /openid/test/data/test_discover/openid2_xrds.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 7 | http://specs.openid.net/auth/2.0/signon 8 | http://www.myopenid.com/server 9 | http://smoker.myopenid.com/ 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /openid/test/data/test_discover/openid2_xrds_no_local_id.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 7 | http://specs.openid.net/auth/2.0/signon 8 | http://www.myopenid.com/server 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /openid/test/data/test_discover/openid_1_and_2.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Identity Page for Smoker 5 | 6 | 7 | 8 | 9 |

foo

10 | 11 | 12 | -------------------------------------------------------------------------------- /openid/test/data/test_discover/openid_1_and_2_xrds.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 8 | 9 | http://specs.openid.net/auth/2.0/signon 10 | http://openid.net/signon/1.1 11 | http://www.myopenid.com/server 12 | http://smoker.myopenid.com/ 13 | http://smoker.myopenid.com/ 14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /openid/test/data/test_discover/openid_1_and_2_xrds_bad_delegate.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 8 | 9 | http://specs.openid.net/auth/2.0/signon 10 | http://openid.net/signon/1.0 11 | http://openid.net/signon/1.1 12 | http://www.myopenid.com/server 13 | http://smoker.myopenid.com/ 14 | http://localid.mismatch.invalid/ 15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /openid/test/data/test_discover/openid_and_yadis.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Identity Page for Smoker 5 | 6 | 7 | 8 | 9 | 10 |

foo

11 | 12 | 13 | -------------------------------------------------------------------------------- /openid/test/data/test_discover/openid_no_delegate.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Identity Page for Smoker 5 | 6 | 7 | 8 |

foo

9 | 10 | 11 | -------------------------------------------------------------------------------- /openid/test/data/test_discover/unicode.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Title with param that needs decoding 5 | 6 | 7 |

This page can be properly decoded and everything will will be fine

8 | 9 | 10 | -------------------------------------------------------------------------------- /openid/test/data/test_discover/unicode2.html: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/openid/python-openid/afa6adacbe1a41d8f614c8bce2264dfbe9e76489/openid/test/data/test_discover/unicode2.html -------------------------------------------------------------------------------- /openid/test/data/test_discover/unicode3.html: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/openid/python-openid/afa6adacbe1a41d8f614c8bce2264dfbe9e76489/openid/test/data/test_discover/unicode3.html -------------------------------------------------------------------------------- /openid/test/data/test_discover/yadis_0entries.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 8 | http://is-not-openid.unittest/ 9 | http://noffing.unittest./ 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /openid/test/data/test_discover/yadis_2_bad_local_id.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 8 | 9 | http://specs.openid.net/auth/2.0/signon 10 | http://www.myopenid.com/server 11 | http://smoker.myopenid.com/ 12 | http://localid.mismatch.invalid/ 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /openid/test/data/test_discover/yadis_2entries_delegate.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | =!1000 8 | 9 | 10 | http://openid.net/signon/1.0 11 | http://www.myopenid.com/server 12 | http://smoker.myopenid.com/ 13 | 14 | 15 | 16 | http://openid.net/signon/1.0 17 | http://www.livejournal.com/openid/server.bml 18 | http://frank.livejournal.com/ 19 | 20 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /openid/test/data/test_discover/yadis_2entries_idp.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | =!1000 8 | 9 | 10 | http://specs.openid.net/auth/2.0/signon 11 | http://www.myopenid.com/server 12 | http://smoker.myopenid.com/ 13 | 14 | 15 | 16 | http://specs.openid.net/auth/2.0/server 17 | http://www.livejournal.com/openid/server.bml 18 | 19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /openid/test/data/test_discover/yadis_another_delegate.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 8 | 9 | http://openid.net/signon/1.0 10 | http://vroom.unittest/server 11 | http://smoker.myopenid.com/ 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /openid/test/data/test_discover/yadis_idp.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 8 | http://specs.openid.net/auth/2.0/server 9 | http://www.myopenid.com/server 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /openid/test/data/test_discover/yadis_idp_delegate.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 8 | http://specs.openid.net/auth/2.0/server 9 | http://www.myopenid.com/server 10 | http://smoker.myopenid.com/ 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /openid/test/data/test_discover/yadis_no_delegate.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 7 | http://openid.net/signon/1.0 8 | http://www.myopenid.com/server 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /openid/test/data/test_etxrd/README: -------------------------------------------------------------------------------- 1 | delegated-20060809.xrds - results from proxy.xri.net, determined by 2 | Drummond and Kevin to be incorrect. 3 | delegated-20060809-r1.xrds - Drummond's 1st correction 4 | delegated-20060809-r2.xrds - Drummond's 2nd correction 5 | 6 | spoofs: keturn's (=!E4)'s attempts to log in with Drummond's i-number (=!D2) 7 | spoof1.xrds 8 | spoof2.xrds 9 | spoof3.xrds - attempt to steal @!C0!D2 by having "at least one" CanonicalID 10 | match the $res service ProviderID. 11 | 12 | ref.xrds - resolving @ootao*test.ref, which refers to a neustar XRI. 13 | -------------------------------------------------------------------------------- /openid/test/data/test_etxrd/delegated-20060809-r1.xrds: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | *ootao 5 | 6 | 2006-08-09T22:07:13.000Z 7 | xri://@ 8 | !5BAD.2AA.3C72.AF46 9 | @!5BAD.2AA.3C72.AF46 10 | 11 | xri://$res*auth*($v*2.0) 12 | xri://!!1003 13 | application/xrds+xml;trust=none 14 | http://resolve.ezibroker.net/resolve/@ootao/ 15 | 16 | 17 | http://openid.net/signon/1.0 18 | 19 | https://linksafe.ezibroker.net/server/ 20 | 21 | 22 | 23 | *test1 24 | SUCCESS 25 | xri://!!1003 26 | !0000.0000.3B9A.CA01 27 | @!5BAD.2AA.3C72.AF46!0000.0000.3B9A.CA01 28 | 29 | http://openid.net/signon/1.0 30 | 31 | https://linksafe.ezibroker.net/server/ 32 | 33 | 34 | 35 | -------------------------------------------------------------------------------- /openid/test/data/test_etxrd/delegated-20060809-r2.xrds: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | *ootao 5 | 6 | 2006-08-09T22:07:13.000Z 7 | xri://@ 8 | !5BAD.2AA.3C72.AF46 9 | @!5BAD.2AA.3C72.AF46 10 | 11 | xri://$res*auth*($v*2.0) 12 | xri://@!5BAD.2AA.3C72.AF46 13 | application/xrds+xml;trust=none 14 | http://resolve.ezibroker.net/resolve/@ootao/ 15 | 16 | 17 | http://openid.net/signon/1.0 18 | 19 | https://linksafe.ezibroker.net/server/ 20 | 21 | 22 | 23 | *test1 24 | SUCCESS 25 | xri://@!5BAD.2AA.3C72.AF46 26 | !0000.0000.3B9A.CA01 27 | @!5BAD.2AA.3C72.AF46!0000.0000.3B9A.CA01 28 | 29 | http://openid.net/signon/1.0 30 | 31 | https://linksafe.ezibroker.net/server/ 32 | 33 | 34 | 35 | -------------------------------------------------------------------------------- /openid/test/data/test_etxrd/delegated-20060809.xrds: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | *ootao 5 | 6 | 2006-08-09T22:07:13.000Z 7 | xri://@ 8 | !5BAD.2AA.3C72.AF46 9 | @!5BAD.2AA.3C72.AF46 10 | 11 | xri://$res*auth*($v*2.0) 12 | 13 | application/xrds+xml;trust=none 14 | http://resolve.ezibroker.net/resolve/@ootao/ 15 | 16 | 17 | http://openid.net/signon/1.0 18 | 19 | https://linksafe.ezibroker.net/server/ 20 | 21 | 22 | 23 | *test1 24 | SUCCESS 25 | xri://!!1003 26 | !0000.0000.3B9A.CA01 27 | @!5BAD.2AA.3C72.AF46!0000.0000.3B9A.CA01 28 | 29 | http://openid.net/signon/1.0 30 | 31 | https://linksafe.ezibroker.net/server/ 32 | 33 | 34 | 35 | -------------------------------------------------------------------------------- /openid/test/data/test_etxrd/no-xrd.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | -------------------------------------------------------------------------------- /openid/test/data/test_etxrd/not-xrds.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /openid/test/data/test_etxrd/prefixsometimes.xrds: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | *ootao 5 | 6 | 2006-08-09T22:07:13.000Z 7 | xri://@ 8 | !5BAD.2AA.3C72.AF46 9 | @!5BAD.2AA.3C72.AF46 10 | 11 | xri://$res*auth*($v*2.0) 12 | xri://@!5BAD.2AA.3C72.AF46 13 | application/xrds+xml;trust=none 14 | http://resolve.ezibroker.net/resolve/@ootao/ 15 | 16 | 17 | http://openid.net/signon/1.0 18 | 19 | https://linksafe.ezibroker.net/server/ 20 | 21 | 22 | 23 | *test1 24 | SUCCESS 25 | xri://@!5BAD.2AA.3C72.AF46 26 | !0000.0000.3B9A.CA01 27 | xri://@!5BAD.2AA.3C72.AF46!0000.0000.3B9A.CA01 28 | 29 | http://openid.net/signon/1.0 30 | 31 | https://linksafe.ezibroker.net/server/ 32 | 33 | 34 | 35 | -------------------------------------------------------------------------------- /openid/test/data/test_etxrd/ref.xrds: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | *ootao 5 | 6 | 2006-08-15T18:56:09.000Z 7 | xri://@ 8 | !5BAD.2AA.3C72.AF46 9 | @!5BAD.2AA.3C72.AF46 10 | 11 | xri://$res*auth*($v*2.0) 12 | 13 | application/xrds+xml;trust=none 14 | http://resolve.ezibroker.net/resolve/@ootao/ 15 | 16 | 17 | http://openid.net/signon/1.0 18 | 19 | https://linksafe.ezibroker.net/server/ 20 | 21 | 22 | 23 | *test.ref 24 | SUCCESS 25 | xri://!!1003 26 | !0000.0000.3B9A.CA03 27 | @!5BAD.2AA.3C72.AF46!0000.0000.3B9A.CA03 28 | @!BAE.A650.823B.2475 29 | 30 | http://openid.net/signon/1.0 31 | 32 | https://linksafe.ezibroker.net/server/ 33 | 34 | 35 | 36 | 37 | !BAE.A650.823B.2475 38 | 39 | 2006-08-15T18:56:10.000Z 40 | xri://@ 41 | !BAE.A650.823B.2475 42 | @!BAE.A650.823B.2475 43 | 44 | (+wdnc) 45 | 46 | (+wdnc) 47 | http://www.tcpacompliance.us 48 | 49 | 50 | xri://$res*auth*($v*2.0) 51 | 52 | application/xrds+xml;trust=none 53 | http://dev.dready.org/cgi-bin/xri 54 | 55 | 56 | (+i-name) 57 | 58 | (+i-name) 59 | http://www.inames.net 60 | 61 | 62 | xri://+i-service*(+contact)*($v*1.0) 63 | 64 | xri://!!1001 65 | (+contact) 66 | 67 | text/html 68 | 69 | http://www.neustar.biz 70 | 71 | 72 | 73 | 74 | !BAE.A650.823B.2475 75 | 76 | 2006-08-15T18:56:10.000Z 77 | xri://@ 78 | !BAE.A650.823B.2475 79 | @!BAE.A650.823B.2475 80 | 81 | (+wdnc) 82 | 83 | (+wdnc) 84 | http://www.tcpacompliance.us 85 | 86 | 87 | xri://$res*auth*($v*2.0) 88 | 89 | application/xrds+xml;trust=none 90 | http://dev.dready.org/cgi-bin/xri 91 | 92 | 93 | (+i-name) 94 | 95 | (+i-name) 96 | http://www.inames.net 97 | 98 | 99 | xri://+i-service*(+contact)*($v*1.0) 100 | 101 | xri://!!1001 102 | (+contact) 103 | 104 | text/html 105 | 106 | http://www.neustar.biz 107 | 108 | 109 | -------------------------------------------------------------------------------- /openid/test/data/test_etxrd/sometimesprefix.xrds: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | *ootao 5 | 6 | 2006-08-09T22:07:13.000Z 7 | xri://@ 8 | !5BAD.2AA.3C72.AF46 9 | xri://@!5BAD.2AA.3C72.AF46 10 | 11 | xri://$res*auth*($v*2.0) 12 | xri://@!5BAD.2AA.3C72.AF46 13 | application/xrds+xml;trust=none 14 | http://resolve.ezibroker.net/resolve/@ootao/ 15 | 16 | 17 | http://openid.net/signon/1.0 18 | 19 | https://linksafe.ezibroker.net/server/ 20 | 21 | 22 | 23 | *test1 24 | SUCCESS 25 | xri://@!5BAD.2AA.3C72.AF46 26 | !0000.0000.3B9A.CA01 27 | @!5BAD.2AA.3C72.AF46!0000.0000.3B9A.CA01 28 | 29 | http://openid.net/signon/1.0 30 | 31 | https://linksafe.ezibroker.net/server/ 32 | 33 | 34 | 35 | -------------------------------------------------------------------------------- /openid/test/data/test_etxrd/spoof1.xrds: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | *keturn 5 | xri://= 6 | !E4 7 | =!E4 8 | 9 | 10 | xri://$res*auth*($v*2.0) 11 | http://keturn.example.com/resolve/ 12 | =!E4 13 | 14 | 15 | 16 | *isDrummond 17 | =!E4 18 | !D2 19 | =!D2 20 | 21 | http://openid.net/signon/1.0 22 | http://keturn.example.com/openid 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /openid/test/data/test_etxrd/spoof2.xrds: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | *keturn 5 | xri://= 6 | !E4 7 | =!E4 8 | 9 | 10 | xri://$res*auth*($v*2.0) 11 | http://keturn.example.com/resolve/ 12 | xri://= 13 | 14 | 15 | 16 | *isDrummond 17 | xri://= 18 | !D2 19 | =!D2 20 | 21 | http://openid.net/signon/1.0 22 | http://keturn.example.com/openid 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /openid/test/data/test_etxrd/spoof3.xrds: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | *keturn 5 | xri://@ 6 | @E4 7 | @!E4 8 | 9 | 10 | xri://$res*auth*($v*2.0) 11 | http://keturn.example.com/resolve/ 12 | @!E4 13 | 14 | 15 | 16 | *is 17 | @!E4 18 | !D2 19 | =!C0 20 | =!E4!01 21 | 22 | xri://$res*auth*($v*2.0) 23 | http://keturn.example.com/resolve/ 24 | @!C0 25 | 26 | 27 | 28 | *drummond 29 | @!C0 30 | !D2 31 | @!C0!D2 32 | 33 | http://openid.net/signon/1.0 34 | http://keturn.example.com/openid 35 | 36 | 37 | 38 | -------------------------------------------------------------------------------- /openid/test/data/test_etxrd/status222.xrds: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | *x 5 | The subsegment does not exist 6 | 2006-08-18T00:02:35.000Z 7 | xri://= 8 | 9 | -------------------------------------------------------------------------------- /openid/test/data/test_etxrd/subsegments.xrds: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | *nishitani 5 | 6 | 2007-12-25T11:33:39.000Z 7 | xri://= 8 | !E117.EF2F.454B.C707 9 | =!E117.EF2F.454B.C707 10 | 11 | http://openid.net/signon/1.0 12 | xri://!!1003!103 13 | https://linksafe.ezibroker.net/server/ 14 | 15 | 16 | xri://$res*auth*($v*2.0) 17 | xri://!!1003!103 18 | application/xrds+xml;trust=none 19 | http://resolve.ezibroker.net/resolve/=nishitani/ 20 | 21 | 22 | xri://+i-service*(+forwarding)*($v*1.0) 23 | 24 | xri://!!1003!103 25 | (+index) 26 | 27 | http://linksafe-forward.ezibroker.net/forwarding/ 28 | 29 | 30 | 31 | *masaki 32 | SUCCESS 33 | xri://!!1003 34 | !0000.0000.3B9A.CA01 35 | =!E117.EF2F.454B.C707!0000.0000.3B9A.CA01 36 | 37 | http://openid.net/signon/1.0 38 | xri://!!1003!103 39 | https://linksafe.ezibroker.net/server/ 40 | 41 | 42 | xri://+i-service*(+contact)*($v*1.0) 43 | 44 | xri://!!1003!103 45 | (+contact) 46 | 47 | http://linksafe-contact.ezibroker.net/contact/ 48 | 49 | 50 | xri://+i-service*(+forwarding)*($v*1.0) 51 | 52 | xri://!!1003!103 53 | (+index) 54 | 55 | http://linksafe-forward.ezibroker.net/forwarding/ 56 | 57 | 58 | 59 | -------------------------------------------------------------------------------- /openid/test/data/test_etxrd/valid-populated-xrds.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | 9 | 10 | http://openid.net/signon/1.0 11 | http://www.myopenid.com/server 12 | http://josh.myopenid.com/ 13 | 14 | 15 | 16 | http://lid.netmesh.org/sso/2.0b5 17 | http://lid.netmesh.org/2.0b5 18 | http://mylid.net/josh 19 | 20 | 21 | 22 | http://openid.net/signon/1.0 23 | http://www.livejournal.com/openid/server.bml 24 | http://www.livejournal.com/users/nedthealpaca/ 25 | 26 | 27 | 28 | http://typekey.com/services/1.0 29 | joshhoyt 30 | 31 | 32 | 33 | http://openid.net/signon/1.0 34 | http://www.schtuff.com/openid 35 | http://users.schtuff.com/josh 36 | 37 | 38 | 39 | 40 | -------------------------------------------------------------------------------- /openid/test/discoverdata.py: -------------------------------------------------------------------------------- 1 | """Module to make discovery data test cases available""" 2 | from __future__ import unicode_literals 3 | 4 | import os.path 5 | 6 | from six.moves.urllib.parse import urljoin 7 | 8 | from openid.yadis.constants import YADIS_HEADER_NAME 9 | from openid.yadis.discover import DiscoveryFailure, DiscoveryResult 10 | 11 | tests_dir = os.path.dirname(__file__) 12 | data_path = os.path.join(tests_dir, 'data') 13 | 14 | testlist = [ 15 | # success, input_name, id_name, result_name 16 | (True, "equiv", "equiv", "xrds"), 17 | (True, "header", "header", "xrds"), 18 | (True, "lowercase_header", "lowercase_header", "xrds"), 19 | (True, "xrds", "xrds", "xrds"), 20 | (True, "xrds_ctparam", "xrds_ctparam", "xrds_ctparam"), 21 | (True, "xrds_ctcase", "xrds_ctcase", "xrds_ctcase"), 22 | (False, "xrds_html", "xrds_html", "xrds_html"), 23 | (True, "redir_equiv", "equiv", "xrds"), 24 | (True, "redir_header", "header", "xrds"), 25 | (True, "redir_xrds", "xrds", "xrds"), 26 | (False, "redir_xrds_html", "xrds_html", "xrds_html"), 27 | (True, "redir_redir_equiv", "equiv", "xrds"), 28 | (False, "404_server_response", None, None), 29 | (False, "404_with_header", None, None), 30 | (False, "404_with_meta", None, None), 31 | (False, "201_server_response", None, None), 32 | (False, "500_server_response", None, None), 33 | ] 34 | 35 | 36 | def getDataName(*components): 37 | sanitized = [] 38 | for part in components: 39 | if part in ['.', '..']: 40 | raise ValueError 41 | elif part: 42 | sanitized.append(part) 43 | 44 | if not sanitized: 45 | raise ValueError 46 | 47 | return os.path.join(data_path, *sanitized) 48 | 49 | 50 | def getExampleXRDS(): 51 | filename = getDataName('example-xrds.xml') 52 | return open(filename).read() 53 | 54 | 55 | example_xrds = getExampleXRDS() 56 | default_test_file = getDataName('test1-discover.txt') 57 | 58 | discover_tests = {} 59 | 60 | 61 | def readTests(filename): 62 | with open(filename) as data_file: 63 | data = data_file.read() 64 | tests = {} 65 | for case in data.split('\f\n'): 66 | (name, content) = case.split('\n', 1) 67 | tests[name] = content 68 | return tests 69 | 70 | 71 | def getData(filename, name): 72 | global discover_tests 73 | try: 74 | file_tests = discover_tests[filename] 75 | except KeyError: 76 | file_tests = discover_tests[filename] = readTests(filename) 77 | return file_tests[name] 78 | 79 | 80 | def fillTemplate(test_name, template, base_url, example_xrds): 81 | mapping = [ 82 | ('URL_BASE/', base_url), 83 | ('', example_xrds), 84 | ('YADIS_HEADER', YADIS_HEADER_NAME), 85 | ('NAME', test_name), 86 | ] 87 | 88 | for k, v in mapping: 89 | template = template.replace(k, v) 90 | 91 | return template 92 | 93 | 94 | def generateSample(test_name, base_url, 95 | example_xrds=example_xrds, 96 | filename=default_test_file): 97 | try: 98 | template = getData(filename, test_name) 99 | except IOError as why: 100 | import errno 101 | if why[0] == errno.ENOENT: 102 | raise KeyError(filename) 103 | else: 104 | raise 105 | 106 | return fillTemplate(test_name, template, base_url, example_xrds) 107 | 108 | 109 | def generateResult(base_url, input_name, id_name, result_name, success): 110 | input_url = urljoin(base_url, input_name) 111 | 112 | # If the name is None then we expect the protocol to fail, which 113 | # we represent by None 114 | if id_name is None: 115 | assert result_name is None 116 | return input_url, DiscoveryFailure 117 | 118 | result = generateSample(result_name, base_url) 119 | headers, content = result.split('\n\n', 1) 120 | header_lines = headers.split('\n') 121 | for header_line in header_lines: 122 | if header_line.startswith('Content-Type:'): 123 | _, ctype = header_line.split(':', 1) 124 | ctype = ctype.strip() 125 | break 126 | else: 127 | ctype = None 128 | 129 | id_url = urljoin(base_url, id_name) 130 | 131 | result = DiscoveryResult(input_url) 132 | result.normalized_uri = id_url 133 | if success: 134 | result.xrds_uri = urljoin(base_url, result_name) 135 | result.content_type = ctype 136 | result.response_text = content.encode('utf-8') 137 | return input_url, result 138 | -------------------------------------------------------------------------------- /openid/test/test_accept.py: -------------------------------------------------------------------------------- 1 | """Test `openid.yadis.accept` module.""" 2 | from __future__ import unicode_literals 3 | 4 | import os.path 5 | import unittest 6 | 7 | from openid.yadis import accept 8 | 9 | 10 | def getTestData(): 11 | """Read the test data off of disk 12 | 13 | () -> [(int, six.text_type)] 14 | """ 15 | filename = os.path.join(os.path.dirname(__file__), 'data', 'accept.txt') 16 | with open(filename, 'rb') as data_file: 17 | content = data_file.read().decode('utf-8') 18 | lines = enumerate(content.splitlines(), start=1) 19 | return lines 20 | 21 | 22 | def chunk(lines): 23 | """Return groups of lines separated by whitespace or comments 24 | 25 | [(int, six.text_type)] -> [[(int, six.text_type)]] 26 | """ 27 | chunks = [] 28 | chunk = [] 29 | for lineno, line in lines: 30 | stripped = line.strip() 31 | if not stripped or stripped[0] == '#': 32 | if chunk: 33 | chunks.append(chunk) 34 | chunk = [] 35 | else: 36 | chunk.append((lineno, stripped)) 37 | 38 | if chunk: 39 | chunks.append(chunk) 40 | 41 | return chunks 42 | 43 | 44 | def parseLines(chunk): 45 | """Take the given chunk of lines and turn it into a test data dictionary 46 | 47 | [(int, six.text_type)] -> {six.text_type:(int, six.text_type)} 48 | """ 49 | items = {} 50 | for (lineno, line) in chunk: 51 | header, data = line.split(':', 1) 52 | header = header.lower() 53 | items[header] = (lineno, data.strip()) 54 | 55 | return items 56 | 57 | 58 | def parseAvailable(available_text): 59 | """Parse an Available: line's data 60 | 61 | six.text_type -> [six.text_type] 62 | """ 63 | return [s.strip() for s in available_text.split(',')] 64 | 65 | 66 | def parseExpected(expected_text): 67 | """Parse an Expected: line's data 68 | 69 | six.text_type -> [(six.text_type, float)] 70 | """ 71 | expected = [] 72 | if expected_text: 73 | for chunk in expected_text.split(','): 74 | chunk = chunk.strip() 75 | mtype, qstuff = chunk.split(';') 76 | mtype = mtype.strip() 77 | assert '/' in mtype 78 | qstuff = qstuff.strip() 79 | q, qstr = qstuff.split('=') 80 | assert q == 'q' 81 | qval = float(qstr) 82 | expected.append((mtype, qval)) 83 | 84 | return expected 85 | 86 | 87 | class MatchAcceptTest(unittest.TestCase): 88 | 89 | def runTest(self): 90 | lines = getTestData() 91 | chunks = chunk(lines) 92 | data_sets = [parseLines(line) for line in chunks] 93 | for data in data_sets: 94 | lnos = [] 95 | lno, accept_header = data['accept'] 96 | lnos.append(lno) 97 | lno, avail_data = data['available'] 98 | lnos.append(lno) 99 | try: 100 | available = parseAvailable(avail_data) 101 | except Exception: 102 | print('On line', lno) 103 | raise 104 | 105 | lno, exp_data = data['expected'] 106 | lnos.append(lno) 107 | try: 108 | expected = parseExpected(exp_data) 109 | except Exception: 110 | print('On line', lno) 111 | raise 112 | 113 | accepted = accept.parseAcceptHeader(accept_header) 114 | actual = accept.matchTypes(accepted, available) 115 | self.assertEqual(actual, expected) 116 | -------------------------------------------------------------------------------- /openid/test/test_cryptutil.py: -------------------------------------------------------------------------------- 1 | """Test `openid.cryptutil` module.""" 2 | from __future__ import unicode_literals 3 | 4 | import os.path 5 | import random 6 | import sys 7 | import unittest 8 | import warnings 9 | 10 | import six 11 | 12 | from openid import cryptutil 13 | 14 | 15 | # Most of the purpose of this test is to make sure that cryptutil can 16 | # find a good source of randomness on this machine. 17 | class TestLongBinary(unittest.TestCase): 18 | """Test `longToBinary` and `binaryToLong` functions.""" 19 | 20 | def test_binaryLongConvert(self): 21 | MAX = sys.maxsize 22 | with warnings.catch_warnings(): 23 | warnings.simplefilter('ignore', category=DeprecationWarning) 24 | for iteration in range(500): 25 | n = 0 26 | for i in range(10): 27 | n += random.randrange(MAX) 28 | 29 | s = cryptutil.longToBinary(n) 30 | assert isinstance(s, six.binary_type) 31 | n_prime = cryptutil.binaryToLong(s) 32 | assert n == n_prime, (n, n_prime) 33 | 34 | cases = [ 35 | (b'\x00', 0), 36 | (b'\x01', 1), 37 | (b'\x7F', 127), 38 | (b'\x00\xFF', 255), 39 | (b'\x00\x80', 128), 40 | (b'\x00\x81', 129), 41 | (b'\x00\x80\x00', 32768), 42 | (b'OpenID is cool', 1611215304203901150134421257416556) 43 | ] 44 | 45 | with warnings.catch_warnings(): 46 | warnings.simplefilter('ignore', category=DeprecationWarning) 47 | for s, n in cases: 48 | n_prime = cryptutil.binaryToLong(s) 49 | s_prime = cryptutil.longToBinary(n) 50 | assert n == n_prime, (s, n, n_prime) 51 | assert s == s_prime, (n, s, s_prime) 52 | 53 | 54 | class TestFixBtwoc(unittest.TestCase): 55 | """Test `fix_btwoc` function.""" 56 | 57 | cases = ( 58 | (b'\x00', b'\x00'), 59 | (b'\x01', b'\x01'), 60 | (b'\x7F', b'\x7F'), 61 | (b'\x80', b'\x00\x80'), 62 | (b'\xFF', b'\x00\xFF'), 63 | ) 64 | 65 | def test_bytes(self): 66 | for value, output in self.cases: 67 | self.assertEqual(cryptutil.fix_btwoc(value), output) 68 | 69 | def test_bytearray(self): 70 | for value, output in self.cases: 71 | self.assertEqual(cryptutil.fix_btwoc(bytearray(value)), output) 72 | 73 | 74 | class TestBytesIntConversion(unittest.TestCase): 75 | """Test bytes <-> int conversions.""" 76 | 77 | # Examples from http://openid.net/specs/openid-authentication-2_0.html#btwoc 78 | cases = [ 79 | (b'\x00', 0), 80 | (b'\x01', 1), 81 | (b'\x7F', 127), 82 | (b'\x00\xFF', 255), 83 | (b'\x00\x80', 128), 84 | (b'\x00\x81', 129), 85 | (b'\x00\x80\x00', 32768), 86 | (b'OpenID is cool', 1611215304203901150134421257416556) 87 | ] 88 | 89 | def test_conversions(self): 90 | for string, number in self.cases: 91 | self.assertEqual(cryptutil.bytes_to_int(string), number) 92 | self.assertEqual(cryptutil.int_to_bytes(number), string) 93 | 94 | 95 | class TestLongToBase64(unittest.TestCase): 96 | """Test `longToBase64` function.""" 97 | 98 | def test_longToBase64(self): 99 | f = open(os.path.join(os.path.dirname(__file__), 'n2b64')) 100 | try: 101 | for line in f: 102 | parts = line.strip().split(' ') 103 | assert parts[0] == cryptutil.longToBase64(int(parts[1])) 104 | finally: 105 | f.close() 106 | 107 | 108 | class TestBase64ToLong(unittest.TestCase): 109 | """Test `Base64ToLong` function.""" 110 | 111 | def test_base64ToLong(self): 112 | f = open(os.path.join(os.path.dirname(__file__), 'n2b64')) 113 | try: 114 | for line in f: 115 | parts = line.strip().split(' ') 116 | assert int(parts[1]) == cryptutil.base64ToLong(parts[0]) 117 | finally: 118 | f.close() 119 | -------------------------------------------------------------------------------- /openid/test/test_extension.py: -------------------------------------------------------------------------------- 1 | from __future__ import unicode_literals 2 | 3 | import unittest 4 | 5 | from openid import extension, message 6 | 7 | 8 | class DummyExtension(extension.Extension): 9 | ns_uri = 'http://an.extension/' 10 | ns_alias = 'dummy' 11 | 12 | def getExtensionArgs(self): 13 | return {} 14 | 15 | 16 | class ToMessageTest(unittest.TestCase): 17 | def test_OpenID1(self): 18 | oid1_msg = message.Message(message.OPENID1_NS) 19 | ext = DummyExtension() 20 | ext.toMessage(oid1_msg) 21 | namespaces = oid1_msg.namespaces 22 | self.assertTrue(namespaces.isImplicit(DummyExtension.ns_uri)) 23 | self.assertEqual(DummyExtension.ns_uri, namespaces.getNamespaceURI(DummyExtension.ns_alias)) 24 | self.assertEqual(DummyExtension.ns_alias, namespaces.getAlias(DummyExtension.ns_uri)) 25 | 26 | def test_OpenID2(self): 27 | oid2_msg = message.Message(message.OPENID2_NS) 28 | ext = DummyExtension() 29 | ext.toMessage(oid2_msg) 30 | namespaces = oid2_msg.namespaces 31 | self.assertFalse(namespaces.isImplicit(DummyExtension.ns_uri)) 32 | self.assertEqual(DummyExtension.ns_uri, namespaces.getNamespaceURI(DummyExtension.ns_alias)) 33 | self.assertEqual(DummyExtension.ns_alias, namespaces.getAlias(DummyExtension.ns_uri)) 34 | -------------------------------------------------------------------------------- /openid/test/test_htmldiscover.py: -------------------------------------------------------------------------------- 1 | from __future__ import unicode_literals 2 | 3 | import unittest 4 | 5 | from openid.consumer.discover import OpenIDServiceEndpoint 6 | 7 | 8 | class TestFromHTML(unittest.TestCase): 9 | """Test `OpenIDServiceEndpoint.fromHTML`.""" 10 | 11 | def test_empty(self): 12 | self.assertEqual(OpenIDServiceEndpoint.fromHTML('http://example.url/', ''), []) 13 | 14 | def test_invalid_html(self): 15 | self.assertEqual(OpenIDServiceEndpoint.fromHTML('http://example.url/', "http://not.in.a.link.tag/"), []) 16 | 17 | def test_no_op_url(self): 18 | html = '' 19 | self.assertEqual(OpenIDServiceEndpoint.fromHTML('http://example.url/', html), []) 20 | -------------------------------------------------------------------------------- /openid/test/test_kvform.py: -------------------------------------------------------------------------------- 1 | """Tests for `openid.kvform` module.""" 2 | from __future__ import unicode_literals 3 | 4 | import unittest 5 | 6 | import six 7 | from testfixtures import LogCapture 8 | 9 | from openid import kvform 10 | 11 | 12 | class KVDictTest(unittest.TestCase): 13 | 14 | def runTest(self): 15 | for kv_data, result, expected_warnings in kvdict_cases: 16 | # Convert KVForm to dict 17 | with LogCapture() as logbook: 18 | d = kvform.kvToDict(kv_data) 19 | 20 | # make sure it parses to expected dict 21 | self.assertEqual(d, result) 22 | 23 | # Check to make sure we got the expected number of warnings 24 | self.assertEqual(len(logbook.records), expected_warnings) 25 | 26 | # Convert back to KVForm and round-trip back to dict to make 27 | # sure that *** dict -> kv -> dict is identity. *** 28 | kv = kvform.dictToKV(d) 29 | d2 = kvform.kvToDict(kv) 30 | self.assertEqual(d, d2) 31 | 32 | 33 | class KVSeqTest(unittest.TestCase): 34 | 35 | def cleanSeq(self, seq): 36 | """Create a new sequence by stripping whitespace from start 37 | and end of each value of each pair""" 38 | clean = [] 39 | for k, v in seq: 40 | clean.append((k.strip(), v.strip())) 41 | return clean 42 | 43 | def runTest(self): 44 | for kv_data, result, expected_warnings in kvseq_cases: 45 | # seq serializes to expected kvform 46 | with LogCapture() as logbook: 47 | actual = kvform.seqToKV(kv_data) 48 | self.assertEqual(actual, result) 49 | self.assertIsInstance(actual, six.text_type) 50 | 51 | # Parse back to sequence. Expected to be unchanged, except 52 | # stripping whitespace from start and end of values 53 | # (i. e. ordering, case, and internal whitespace is preserved) 54 | seq = kvform.kvToSeq(actual) 55 | clean_seq = self.cleanSeq(seq) 56 | 57 | self.assertEqual(seq, clean_seq) 58 | self.assertEqual(len(logbook.records), expected_warnings, 59 | "Invalid warnings for {}: {}".format(kv_data, [r.getMessage() for r in logbook.records])) 60 | 61 | 62 | kvdict_cases = [ 63 | # (kvform, parsed dictionary, expected warnings) 64 | ('', {}, 0), 65 | ('college:harvey mudd\n', {'college': 'harvey mudd'}, 0), 66 | ('city:claremont\nstate:CA\n', {'city': 'claremont', 'state': 'CA'}, 0), 67 | ('is_valid:true\ninvalidate_handle:{HMAC-SHA1:2398410938412093}\n', 68 | {'is_valid': 'true', 'invalidate_handle': '{HMAC-SHA1:2398410938412093}'}, 0), 69 | 70 | # Warnings from lines with no colon: 71 | ('x\n', {}, 1), 72 | ('x\nx\n', {}, 2), 73 | ('East is least\n', {}, 1), 74 | 75 | # But not from blank lines (because LJ generates them) 76 | ('x\n\n', {}, 1), 77 | 78 | # Warning from empty key 79 | (':\n', {'': ''}, 1), 80 | (':missing key\n', {'': 'missing key'}, 1), 81 | 82 | # Warnings from leading or trailing whitespace in key or value 83 | (' street:foothill blvd\n', {'street': 'foothill blvd'}, 1), 84 | ('major: computer science\n', {'major': 'computer science'}, 1), 85 | (' dorm : east \n', {'dorm': 'east'}, 2), 86 | 87 | # Warnings from missing trailing newline 88 | ('e^(i*pi)+1:0', {'e^(i*pi)+1': '0'}, 1), 89 | ('east:west\nnorth:south', {'east': 'west', 'north': 'south'}, 1), 90 | ] 91 | 92 | kvseq_cases = [ 93 | ([], '', 0), 94 | 95 | # Make sure that we handle unicode characters 96 | ([('\u03bbx', 'x')], '\u03bbx:x\n', 0), 97 | 98 | # If it's a UTF-8 str, make sure that it's equivalent to the same 99 | # string, decoded. 100 | ([(b'\xce\xbbx', b'x')], '\u03bbx:x\n', 0), 101 | 102 | ([('openid', 'useful'), ('a', 'b')], 'openid:useful\na:b\n', 0), 103 | 104 | # Warnings about leading whitespace 105 | ([(' openid', 'useful'), ('a', 'b')], ' openid:useful\na:b\n', 1), 106 | 107 | # Warnings about leading and trailing whitespace 108 | ([(' openid ', ' useful '), 109 | (' a ', ' b ')], ' openid : useful \n a : b \n', 4), 110 | 111 | # warnings about leading and trailing whitespace, but not about 112 | # internal whitespace. 113 | ([(' open id ', ' use ful '), 114 | (' a ', ' b ')], ' open id : use ful \n a : b \n', 4), 115 | 116 | ([('foo', 'bar')], 'foo:bar\n', 0), 117 | ] 118 | 119 | kvexc_cases = [ 120 | [('openid', 'use\nful')], 121 | [('open\nid', 'useful')], 122 | [('open\nid', 'use\nful')], 123 | [('open:id', 'useful')], 124 | [('foo', 'bar'), ('ba\n d', 'seed')], 125 | [('foo', 'bar'), ('bad:', 'seed')], 126 | ] 127 | 128 | 129 | class KVExcTest(unittest.TestCase): 130 | 131 | def runTest(self): 132 | for kv_data in kvexc_cases: 133 | self.assertRaises(ValueError, kvform.seqToKV, kv_data) 134 | 135 | 136 | class GeneralTest(unittest.TestCase): 137 | kvform = '' 138 | 139 | def test_convert(self): 140 | with LogCapture() as logbook: 141 | result = kvform.seqToKV([(1, 1)]) 142 | self.assertEqual(result, '1:1\n') 143 | self.assertEqual(len(logbook.records), 2) 144 | -------------------------------------------------------------------------------- /openid/test/test_nonce.py: -------------------------------------------------------------------------------- 1 | from __future__ import unicode_literals 2 | 3 | import re 4 | import unittest 5 | 6 | import six 7 | 8 | from openid.store.nonce import checkTimestamp, make_nonce_salt, mkNonce, split as splitNonce 9 | 10 | nonce_re = re.compile(r'\A\d{4}-\d\d-\d\dT\d\d:\d\d:\d\dZ') 11 | 12 | 13 | class TestMakeNonceSalt(unittest.TestCase): 14 | """Test `make_nonce_salt` function.""" 15 | 16 | def test_default(self): 17 | salt = make_nonce_salt() 18 | self.assertIsInstance(salt, six.text_type) 19 | self.assertEqual(len(salt), 6) 20 | 21 | def test_custom_length(self): 22 | salt = make_nonce_salt(32) 23 | self.assertIsInstance(salt, six.text_type) 24 | self.assertEqual(len(salt), 32) 25 | 26 | 27 | class NonceTest(unittest.TestCase): 28 | def test_mkNonce(self): 29 | nonce = mkNonce() 30 | self.assertIsNotNone(nonce_re.match(nonce)) 31 | self.assertEqual(len(nonce), 26) 32 | 33 | def test_mkNonce_when(self): 34 | nonce = mkNonce(0) 35 | self.assertIsNotNone(nonce_re.match(nonce)) 36 | self.assertTrue(nonce.startswith('1970-01-01T00:00:00Z')) 37 | self.assertEqual(len(nonce), 26) 38 | 39 | def test_splitNonce(self): 40 | s = '1970-01-01T00:00:00Z' 41 | expected_t = 0 42 | expected_salt = '' 43 | actual_t, actual_salt = splitNonce(s) 44 | self.assertEqual(actual_t, expected_t) 45 | self.assertEqual(actual_salt, expected_salt) 46 | 47 | def test_mkSplit(self): 48 | t = 42 49 | nonce_str = mkNonce(t) 50 | self.assertIsNotNone(nonce_re.match(nonce_str)) 51 | et, salt = splitNonce(nonce_str) 52 | self.assertEqual(len(salt), 6) 53 | self.assertEqual(et, t) 54 | 55 | 56 | class BadSplitTest(unittest.TestCase): 57 | cases = [ 58 | '', 59 | '1970-01-01T00:00:00+1:00', 60 | '1969-01-01T00:00:00Z', 61 | '1970-00-01T00:00:00Z', 62 | '1970.01-01T00:00:00Z', 63 | 'Thu Sep 7 13:29:31 PDT 2006', 64 | 'monkeys', 65 | ] 66 | 67 | def test(self): 68 | for nonce_str in self.cases: 69 | self.assertRaises(ValueError, splitNonce, nonce_str) 70 | 71 | 72 | class CheckTimestampTest(unittest.TestCase): 73 | cases = [ 74 | # exact, no allowed skew 75 | ('1970-01-01T00:00:00Z', 0, 0, True), 76 | 77 | # exact, large skew 78 | ('1970-01-01T00:00:00Z', 1000, 0, True), 79 | 80 | # no allowed skew, one second old 81 | ('1970-01-01T00:00:00Z', 0, 1, False), 82 | 83 | # many seconds old, outside of skew 84 | ('1970-01-01T00:00:00Z', 10, 50, False), 85 | 86 | # one second old, one second skew allowed 87 | ('1970-01-01T00:00:00Z', 1, 1, True), 88 | 89 | # One second in the future, one second skew allowed 90 | ('1970-01-01T00:00:02Z', 1, 1, True), 91 | 92 | # two seconds in the future, one second skew allowed 93 | ('1970-01-01T00:00:02Z', 1, 0, False), 94 | 95 | # malformed nonce string 96 | ('monkeys', 0, 0, False), 97 | ] 98 | 99 | def test(self): 100 | for nonce_string, allowed_skew, now, expected in self.cases: 101 | actual = checkTimestamp(nonce_string, allowed_skew, now) 102 | self.assertEqual(bool(actual), bool(expected)) 103 | -------------------------------------------------------------------------------- /openid/test/test_openidyadis.py: -------------------------------------------------------------------------------- 1 | from __future__ import unicode_literals 2 | 3 | import unittest 4 | 5 | import six 6 | 7 | from openid.consumer.discover import OPENID_1_0_TYPE, OPENID_1_1_TYPE, OpenIDServiceEndpoint 8 | from openid.yadis.services import applyFilter 9 | 10 | XRDS_BOILERPLATE = '''\ 11 | 12 | 15 | 16 | %s\ 17 | 18 | 19 | ''' 20 | 21 | 22 | def mkXRDS(services): 23 | xrds = XRDS_BOILERPLATE % services 24 | return xrds.encode('utf-8') 25 | 26 | 27 | def mkService(uris=None, type_uris=None, local_id=None, dent=' '): 28 | chunks = [dent, '\n'] 29 | dent2 = dent + ' ' 30 | if type_uris: 31 | for type_uri in type_uris: 32 | chunks.extend([dent2 + '', type_uri, '\n']) 33 | 34 | if uris: 35 | for uri in uris: 36 | if isinstance(uri, tuple): 37 | uri, prio = uri 38 | else: 39 | prio = None 40 | 41 | chunks.extend([dent2, '', uri, '\n']) 45 | 46 | if local_id: 47 | chunks.extend( 48 | [dent2, '', local_id, '\n']) 49 | 50 | chunks.extend([dent, '\n']) 51 | 52 | return ''.join(chunks) 53 | 54 | 55 | # Different sets of server URLs for use in the URI tag 56 | server_url_options = [ 57 | [], # This case should not generate an endpoint object 58 | ['http://server.url/'], 59 | ['https://server.url/'], 60 | ['https://server.url/', 'http://server.url/'], 61 | ['https://server.url/', 62 | 'http://server.url/', 63 | 'http://example.server.url/'], 64 | ] 65 | 66 | # Used for generating test data 67 | 68 | 69 | def subsets(lst): 70 | """Generate all non-empty sublists of a list""" 71 | subsets_list = [[]] 72 | for x in lst: 73 | subsets_list += [[x] + t for t in subsets_list] 74 | return subsets_list 75 | 76 | 77 | # A couple of example extension type URIs. These are not at all 78 | # official, but are just here for testing. 79 | ext_types = [ 80 | 'http://janrain.com/extension/blah', 81 | 'http://openid.net/sreg/1.0', 82 | ] 83 | 84 | # All valid combinations of Type tags that should produce an OpenID endpoint 85 | type_uri_options = [ 86 | exts + ts 87 | 88 | # All non-empty sublists of the valid OpenID type URIs 89 | for ts in subsets([OPENID_1_0_TYPE, OPENID_1_1_TYPE]) 90 | if ts 91 | 92 | # All combinations of extension types (including empty extenstion list) 93 | for exts in subsets(ext_types) 94 | ] 95 | 96 | # Range of valid Delegate tag values for generating test data 97 | local_id_options = [ 98 | None, 99 | 'http://vanity.domain/', 100 | 'https://somewhere/yadis/', 101 | ] 102 | 103 | # All combinations of valid URIs, Type URIs and Delegate tags 104 | data = [ 105 | (uris, type_uris, local_id) 106 | for uris in server_url_options 107 | for type_uris in type_uri_options 108 | for local_id in local_id_options 109 | ] 110 | 111 | 112 | class OpenIDYadisTest(unittest.TestCase): 113 | 114 | yadis_url = 'http://unit.test/' 115 | 116 | def shortDescription(self): 117 | # XXX: 118 | return 'Successful OpenID Yadis parsing case' 119 | 120 | def make_xrds(self, uris, type_uris, local_id): 121 | # Create an XRDS document to parse 122 | services = mkService(uris=uris, 123 | type_uris=type_uris, 124 | local_id=local_id) 125 | return mkXRDS(services) 126 | 127 | def runTest(self): 128 | for uris, type_uris, local_id in data: 129 | # Parse into endpoint objects that we will check 130 | endpoints = applyFilter(self.yadis_url, self.make_xrds(uris, type_uris, local_id), OpenIDServiceEndpoint) 131 | 132 | # make sure there are the same number of endpoints as 133 | # URIs. This assumes that the type_uris contains at least one 134 | # OpenID type. 135 | self.assertEqual(len(endpoints), len(uris)) 136 | 137 | # So that we can check equality on the endpoint types 138 | type_uris = sorted(type_uris) 139 | 140 | seen_uris = [] 141 | for endpoint in endpoints: 142 | seen_uris.append(endpoint.server_url) 143 | 144 | # All endpoints will have same yadis_url 145 | self.assertEqual(endpoint.claimed_id, self.yadis_url) 146 | 147 | # and local_id 148 | self.assertEqual(endpoint.local_id, local_id) 149 | 150 | # and types 151 | actual_types = sorted(endpoint.type_uris) 152 | self.assertEqual(type_uris, actual_types) 153 | 154 | # So that they will compare equal, because we don't care what 155 | # order they are in 156 | seen_uris.sort() 157 | uris = sorted(uris) 158 | 159 | # Make sure we saw all URIs, and saw each one once 160 | self.assertEqual(seen_uris, uris) 161 | -------------------------------------------------------------------------------- /openid/test/test_pape_draft5.py: -------------------------------------------------------------------------------- 1 | from __future__ import unicode_literals 2 | 3 | import unittest 4 | import warnings 5 | 6 | from testfixtures import ShouldWarn 7 | 8 | from openid.extensions import pape 9 | 10 | 11 | class PapeImportTestCase(unittest.TestCase): 12 | def test_version(self): 13 | warning_msg = "Module 'openid.extensions.draft.pape5' is deprecated in favor of 'openid.extensions.pape'." 14 | with ShouldWarn(DeprecationWarning(warning_msg)): 15 | warnings.simplefilter('always') 16 | from openid.extensions.draft import pape5 17 | self.assertEqual(pape.Request, pape5.Request) 18 | self.assertEqual(pape.Response, pape5.Response) 19 | -------------------------------------------------------------------------------- /openid/test/test_parsehtml.py: -------------------------------------------------------------------------------- 1 | """Tests for `openid.yadis.parsehtml` module.""" 2 | from __future__ import unicode_literals 3 | 4 | import unittest 5 | 6 | from mock import sentinel 7 | from six import StringIO 8 | 9 | from openid.yadis.parsehtml import MetaNotFound, findHTMLMeta, xpath_lower_case 10 | 11 | 12 | class TestXpathLowerCase(unittest.TestCase): 13 | """Test `xpath_lower_case` function.""" 14 | 15 | def test_lower_case(self): 16 | self.assertEqual(xpath_lower_case(sentinel.context, ['CaMeLcAsE']), ['camelcase']) 17 | 18 | 19 | class TestFindHTMLMeta(unittest.TestCase): 20 | """Test `findHTMLMeta` function.""" 21 | 22 | def test_html(self): 23 | buff = StringIO('') 24 | self.assertEqual(findHTMLMeta(buff), 'found') 25 | 26 | def test_xhtml(self): 27 | buff = StringIO('') 28 | self.assertEqual(findHTMLMeta(buff), 'found') 29 | 30 | def test_case_insensitive_header_name(self): 31 | buff = StringIO('') 32 | self.assertEqual(findHTMLMeta(buff), 'found') 33 | 34 | def test_missing_end_tags(self): 35 | buff = StringIO('') 36 | self.assertEqual(findHTMLMeta(buff), 'found') 37 | 38 | def test_missing_html_header(self): 39 | buff = StringIO('') 40 | self.assertEqual(findHTMLMeta(buff), 'found') 41 | 42 | def test_missing_head_tag(self): 43 | buff = StringIO('') 44 | self.assertEqual(findHTMLMeta(buff), 'found') 45 | 46 | def test_top_level_bogus(self): 47 | buff = StringIO('') 48 | self.assertEqual(findHTMLMeta(buff), 'found') 49 | 50 | def test_missing_html_tag(self): 51 | buff = StringIO('') 52 | self.assertEqual(findHTMLMeta(buff), 'found') 53 | 54 | def test_javascript_in_head(self): 55 | buff = StringIO('' 56 | '') 57 | self.assertEqual(findHTMLMeta(buff), 'found') 58 | 59 | def test_multiple_headers(self): 60 | buff = StringIO('' 61 | '' 62 | '') 63 | self.assertEqual(findHTMLMeta(buff), 'found') 64 | 65 | def test_standard_entity(self): 66 | buff = StringIO('') 67 | self.assertEqual(findHTMLMeta(buff), '&') 68 | 69 | def test_hex_entity(self): 70 | buff = StringIO('') 71 | self.assertEqual(findHTMLMeta(buff), 'found') 72 | 73 | def test_decimal_entity(self): 74 | buff = StringIO('') 75 | self.assertEqual(findHTMLMeta(buff), 'found') 76 | 77 | def test_empty_string(self): 78 | buff = StringIO('') 79 | self.assertEqual(findHTMLMeta(buff), '') 80 | 81 | def test_empty_input(self): 82 | buff = StringIO('') 83 | self.assertRaises(MetaNotFound, findHTMLMeta, buff) 84 | 85 | def test_invalid_html(self): 86 | buff = StringIO('') 87 | self.assertRaises(MetaNotFound, findHTMLMeta, buff) 88 | 89 | def test_meta_in_body(self): 90 | buff = StringIO('') 91 | self.assertRaises(MetaNotFound, findHTMLMeta, buff) 92 | 93 | def test_no_content(self): 94 | buff = StringIO('') 95 | self.assertRaises(MetaNotFound, findHTMLMeta, buff) 96 | 97 | def test_commented_header(self): 98 | buff = StringIO('' 99 | '' 100 | '') 101 | self.assertRaises(MetaNotFound, findHTMLMeta, buff) 102 | 103 | def test_no_yadis_header(self): 104 | buff = StringIO("A boring document" 105 | "

A boring document

There's really nothing interesting about this

" 106 | "") 107 | self.assertRaises(MetaNotFound, findHTMLMeta, buff) 108 | 109 | def test_unclosed_tag(self): 110 | # script tag not closed 111 | buff = StringIO('