├── src └── oic │ ├── py.typed │ ├── extension │ ├── __init__.py │ ├── single.py │ ├── heart.py │ └── sts.py │ ├── utils │ ├── authn │ │ ├── __init__.py │ │ ├── ldap_member.py │ │ ├── client_saml.py │ │ └── javascript_login.py │ ├── claims.py │ ├── __init__.py │ ├── userinfo │ │ ├── __init__.py │ │ └── aa_info.py │ ├── shelve_wrapper.py │ ├── template_render.py │ ├── restrict.py │ ├── sanitize.py │ └── authz.py │ ├── oauth2 │ └── exception.py │ ├── __init__.py │ └── exception.py ├── tests ├── __init__.py ├── utils │ └── __init__.py ├── jwks │ ├── jwks_fault.json │ ├── jwks0.json │ ├── jwks1.json │ ├── jwks_uk.json │ └── jwks_spo.json ├── data │ ├── keys │ │ ├── jwk_enc.json │ │ ├── rsa.pub │ │ ├── jwk.json │ │ ├── rsa.key │ │ ├── cert.pem │ │ └── cert.key │ └── templates │ │ ├── root.mako │ │ └── login.mako ├── rsa_enc.pub ├── rsa_enc ├── not_yet_test_x_device_flow.py ├── test_stateless.py ├── test_authn_user.py ├── conftest.py ├── test_claims.py ├── test_client_management.py ├── test_jwt.py ├── test_shelve_wrapper.py └── test_aes.py ├── MANIFEST.in ├── oauth_example ├── rp │ ├── __init__.py │ ├── static │ │ ├── robots.txt │ │ └── style.css │ ├── htdocs │ │ ├── logout_done.mako │ │ └── as_choice.mako │ ├── templates │ │ └── root.mako │ └── conf.py └── as │ ├── static │ └── robots.txt │ ├── __init__.py │ ├── tre.py │ ├── templates │ └── root.mako │ ├── certs │ ├── server.csr │ ├── server.crt │ └── server.key │ ├── htdocs │ └── login.mako │ ├── authn_setup.py │ ├── keys │ └── key.pem │ └── tre.jwks ├── oidc_example ├── rp2 │ ├── start.sh │ ├── static │ │ ├── robots.txt │ │ ├── style.css │ │ └── bootstrap │ │ │ └── fonts │ │ │ ├── glyphicons-halflings-regular.eot │ │ │ ├── glyphicons-halflings-regular.ttf │ │ │ └── glyphicons-halflings-regular.woff │ ├── conf.py.example │ └── htdocs │ │ ├── post_logout.mako │ │ ├── opbyuid.mako │ │ └── acrvalue.mako ├── simple_op │ ├── src │ │ ├── provider │ │ │ ├── server │ │ │ │ └── __init__.py │ │ │ ├── __init__.py │ │ │ └── authn │ │ │ │ ├── util.py │ │ │ │ ├── templates │ │ │ │ ├── yubico_otp.jinja2 │ │ │ │ ├── mail_two_factor.jinja2 │ │ │ │ └── user_pass.jinja2 │ │ │ │ ├── __init__.py │ │ │ │ ├── user_pass.py │ │ │ │ └── yubikey.py │ │ └── run.py │ ├── yubikeys.json │ ├── passwd.json │ ├── requirements.txt │ ├── users.json │ ├── settings.yaml.example │ └── certs │ │ └── localhost.crt ├── op2 │ ├── requirements.txt │ ├── start.sh │ ├── .gitignore │ ├── capabilities_session_mgmt.json │ ├── static │ │ └── bootstrap │ │ │ └── fonts │ │ │ ├── glyphicons-halflings-regular.eot │ │ │ ├── glyphicons-halflings-regular.ttf │ │ │ └── glyphicons-halflings-regular.woff │ ├── constraints.txt │ ├── htdocs │ │ ├── unauthorized.mako │ │ ├── form_response.mako │ │ ├── javascript_login.mako │ │ ├── login_se.mako │ │ ├── login.mako │ │ └── op_session_iframe.html │ ├── certs │ │ ├── server.crt │ │ └── server.key │ ├── sp_cert │ │ ├── localhost.crt │ │ └── localhost.key │ ├── client_mgr.py │ ├── templates │ │ ├── base.mako │ │ └── root.mako │ ├── cp_keys │ │ ├── cert.pem │ │ └── key.pem │ ├── README.md │ └── config_simple.py ├── rp3 │ ├── run.sh │ ├── static │ │ ├── robots.txt │ │ ├── style.css │ │ └── bootstrap │ │ │ └── fonts │ │ │ ├── glyphicons-halflings-regular.eot │ │ │ ├── glyphicons-halflings-regular.ttf │ │ │ └── glyphicons-halflings-regular.woff │ ├── run_heart.sh │ ├── certs │ │ ├── server.crt │ │ └── server.key │ ├── htdocs │ │ ├── rp_session_iframe.mako │ │ ├── opresult_repost.mako │ │ ├── operror.mako │ │ └── opchoice.mako │ ├── conf_heart.py │ ├── conf_heart.py.ex │ ├── README │ └── conf.py.example ├── op1 │ ├── claims_client.json │ ├── start.sh │ ├── cp_config.json │ ├── create_jwk_from_cert.py │ └── certs │ │ ├── server.crt │ │ └── server.key ├── simple_rp │ ├── requirements.txt │ ├── src │ │ └── htdocs │ │ │ ├── success_page.html │ │ │ ├── index.html │ │ │ └── repost_fragment.html │ ├── settings.yaml.example │ └── certs │ │ └── localhost.crt ├── op3 │ ├── start.sh │ ├── static │ │ ├── bootstrap │ │ │ └── fonts │ │ │ │ ├── glyphicons-halflings-regular.eot │ │ │ │ ├── glyphicons-halflings-regular.ttf │ │ │ │ └── glyphicons-halflings-regular.woff │ │ └── jwks.json │ ├── README.md │ ├── cryptography_keys │ │ ├── key.pem.pub │ │ └── key.pem │ ├── htdocs │ │ ├── unauthorized.mako │ │ ├── form_response.mako │ │ ├── javascript_login.mako │ │ ├── cp_keys │ │ │ ├── cert.pem │ │ │ └── key.pem │ │ ├── login_se.mako │ │ ├── login.mako │ │ └── op_session_iframe.html │ ├── certification │ │ ├── server.crt │ │ └── server.key │ └── Templates │ │ ├── base.mako │ │ └── root.mako └── README.md ├── docker ├── op_test │ ├── assigned_ports.json │ ├── https%3A%2F%2Fop%3A4433 │ │ └── default │ ├── config.py │ ├── run.sh │ ├── tt_config.py │ ├── my_jwks_60003.json │ ├── Dockerfile │ └── cert.pem ├── oidc_op │ └── scripts │ │ └── make_test_site.py ├── rp_test │ ├── run.sh │ ├── Dockerfile │ └── cert.pem ├── op │ ├── apache-ssl.conf │ ├── run.sh │ ├── Dockerfile │ └── cert.pem ├── docker-compose.yml └── integration_tests │ ├── run.sh │ ├── Makefile │ └── Dockerfile ├── doc ├── _static │ └── ViewmeonGitHub.png ├── contrib │ ├── settings.rst │ ├── documentation.rst │ ├── install.rst │ └── testing.rst ├── examples │ ├── docker.rst │ └── tls.rst ├── index.rst └── conf.py ├── .readthedocs.yaml ├── .coveragerc ├── .isort.cfg ├── .github ├── PULL_REQUEST_TEMPLATE.md └── workflows │ ├── python-test.yml │ └── codeql-analysis.yml ├── setup.cfg ├── script └── webfinger.py ├── appveyor.yml ├── pylama.ini ├── LICENSE.txt ├── mypy.ini ├── tox.ini ├── runOpRp.sh ├── .gitignore └── Makefile /src/oic/py.typed: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /tests/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /tests/utils/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /MANIFEST.in: -------------------------------------------------------------------------------- 1 | include src/oic/py.typed 2 | -------------------------------------------------------------------------------- /oauth_example/rp/__init__.py: -------------------------------------------------------------------------------- 1 | __author__ = "roland" 2 | -------------------------------------------------------------------------------- /oidc_example/rp2/start.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | ./rp2.py & 3 | -------------------------------------------------------------------------------- /oidc_example/simple_op/src/provider/server/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/oic/extension/__init__.py: -------------------------------------------------------------------------------- 1 | __author__ = "roland" 2 | -------------------------------------------------------------------------------- /src/oic/utils/authn/__init__.py: -------------------------------------------------------------------------------- 1 | __author__ = "rolandh" 2 | -------------------------------------------------------------------------------- /oauth_example/as/static/robots.txt: -------------------------------------------------------------------------------- 1 | User-agent: * 2 | Disallow: / -------------------------------------------------------------------------------- /oauth_example/rp/static/robots.txt: -------------------------------------------------------------------------------- 1 | User-agent: * 2 | Disallow: / -------------------------------------------------------------------------------- /oidc_example/op2/requirements.txt: -------------------------------------------------------------------------------- 1 | CherryPy<=8.9.1 2 | oic 3 | -------------------------------------------------------------------------------- /oidc_example/rp2/static/robots.txt: -------------------------------------------------------------------------------- 1 | User-agent: * 2 | Disallow: / 3 | -------------------------------------------------------------------------------- /oidc_example/rp3/run.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | ./rp3.py -p 8088 -k conf & -------------------------------------------------------------------------------- /oidc_example/rp3/static/robots.txt: -------------------------------------------------------------------------------- 1 | User-agent: * 2 | Disallow: / 3 | -------------------------------------------------------------------------------- /oidc_example/simple_op/yubikeys.json: -------------------------------------------------------------------------------- 1 | { 2 | "abcdefghijkl": "diana" 3 | } -------------------------------------------------------------------------------- /docker/op_test/assigned_ports.json: -------------------------------------------------------------------------------- 1 | {"https://op:4433][default": 60003} 2 | -------------------------------------------------------------------------------- /oidc_example/op1/claims_client.json: -------------------------------------------------------------------------------- 1 | {"client_1":{"client_secret": "hemlig"}} -------------------------------------------------------------------------------- /oidc_example/rp3/run_heart.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | ./rp3.py -p 8088 -k conf_heart & -------------------------------------------------------------------------------- /oidc_example/simple_op/src/provider/__init__.py: -------------------------------------------------------------------------------- 1 | __author__ = 'regu0004' 2 | -------------------------------------------------------------------------------- /oidc_example/simple_rp/requirements.txt: -------------------------------------------------------------------------------- 1 | cherrypy==3.2.4 2 | pyaml==15.03.1 3 | -------------------------------------------------------------------------------- /oauth_example/as/__init__.py: -------------------------------------------------------------------------------- 1 | # A basic example of a OAuth2 Authorization Server 2 | -------------------------------------------------------------------------------- /oidc_example/op1/start.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | ./oc_server.py -p 8092 -d oc_config & 3 | -------------------------------------------------------------------------------- /oidc_example/op2/start.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | ./server.py -p 8040 -t -d config_simple.py 3 | -------------------------------------------------------------------------------- /oauth_example/rp/static/style.css: -------------------------------------------------------------------------------- 1 | body { 2 | min-height: 2000px; 3 | padding-top: 70px; 4 | } -------------------------------------------------------------------------------- /oidc_example/rp2/static/style.css: -------------------------------------------------------------------------------- 1 | body { 2 | min-height: 2000px; 3 | padding-top: 70px; 4 | } -------------------------------------------------------------------------------- /oidc_example/rp3/static/style.css: -------------------------------------------------------------------------------- 1 | body { 2 | min-height: 2000px; 3 | padding-top: 70px; 4 | } -------------------------------------------------------------------------------- /doc/_static/ViewmeonGitHub.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/infohash/pyoidc/master/doc/_static/ViewmeonGitHub.png -------------------------------------------------------------------------------- /oidc_example/simple_op/passwd.json: -------------------------------------------------------------------------------- 1 | { 2 | "diana": "krall", 3 | "babs": "howes", 4 | "upper": "crust" 5 | } -------------------------------------------------------------------------------- /oauth_example/rp/htdocs/logout_done.mako: -------------------------------------------------------------------------------- 1 | <%inherit file="root.mako" /> 2 | <%def name="title()">Log out done 3 | -------------------------------------------------------------------------------- /oidc_example/simple_op/requirements.txt: -------------------------------------------------------------------------------- 1 | cherrypy==3.2.4 2 | pyaml==15.03.1 3 | Jinja2==2.11.3 4 | yubico-client==1.9.1 5 | -------------------------------------------------------------------------------- /oidc_example/op3/start.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | ./server.py -c config -d & 3 | 4 | # -c: configuration file 5 | # -d: debug 6 | 7 | -------------------------------------------------------------------------------- /oidc_example/simple_op/src/run.py: -------------------------------------------------------------------------------- 1 | from provider.server.server import main 2 | 3 | if __name__ == "__main__": 4 | main() 5 | -------------------------------------------------------------------------------- /.readthedocs.yaml: -------------------------------------------------------------------------------- 1 | version: 2 2 | 3 | python: 4 | install: 5 | - path: . 6 | extra_requirements: 7 | - docs 8 | -------------------------------------------------------------------------------- /.coveragerc: -------------------------------------------------------------------------------- 1 | [run] 2 | branch = True 3 | 4 | [report] 5 | exclude_lines = 6 | pragma: no cover 7 | raise NotImplementedError 8 | -------------------------------------------------------------------------------- /doc/contrib/settings.rst: -------------------------------------------------------------------------------- 1 | Settings 2 | ======== 3 | 4 | .. automodule:: oic.utils.settings 5 | :members: 6 | :show-inheritance: 7 | -------------------------------------------------------------------------------- /.isort.cfg: -------------------------------------------------------------------------------- 1 | [settings] 2 | force_single_line = 1 3 | known_first_party = oic 4 | known_third_party = jwkest,pytest 5 | default_section = THIRDPARTY 6 | -------------------------------------------------------------------------------- /oidc_example/op2/.gitignore: -------------------------------------------------------------------------------- 1 | venv 2 | client_db* 3 | config.py 4 | sp_cert/* 5 | !sp_cert/localhost.crt 6 | !sp_cert/localhost.key 7 | static/jwks.json 8 | -------------------------------------------------------------------------------- /oidc_example/op2/capabilities_session_mgmt.json: -------------------------------------------------------------------------------- 1 | { 2 | "check_session_iframe": "https://localhost:8080/check_session", 3 | "subject_types_supported": ["public", "pairwise"] 4 | } -------------------------------------------------------------------------------- /oidc_example/op2/static/bootstrap/fonts/glyphicons-halflings-regular.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/infohash/pyoidc/master/oidc_example/op2/static/bootstrap/fonts/glyphicons-halflings-regular.eot -------------------------------------------------------------------------------- /oidc_example/op2/static/bootstrap/fonts/glyphicons-halflings-regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/infohash/pyoidc/master/oidc_example/op2/static/bootstrap/fonts/glyphicons-halflings-regular.ttf -------------------------------------------------------------------------------- /oidc_example/op3/static/bootstrap/fonts/glyphicons-halflings-regular.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/infohash/pyoidc/master/oidc_example/op3/static/bootstrap/fonts/glyphicons-halflings-regular.eot -------------------------------------------------------------------------------- /oidc_example/op3/static/bootstrap/fonts/glyphicons-halflings-regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/infohash/pyoidc/master/oidc_example/op3/static/bootstrap/fonts/glyphicons-halflings-regular.ttf -------------------------------------------------------------------------------- /oidc_example/rp2/static/bootstrap/fonts/glyphicons-halflings-regular.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/infohash/pyoidc/master/oidc_example/rp2/static/bootstrap/fonts/glyphicons-halflings-regular.eot -------------------------------------------------------------------------------- /oidc_example/rp2/static/bootstrap/fonts/glyphicons-halflings-regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/infohash/pyoidc/master/oidc_example/rp2/static/bootstrap/fonts/glyphicons-halflings-regular.ttf -------------------------------------------------------------------------------- /oidc_example/rp3/static/bootstrap/fonts/glyphicons-halflings-regular.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/infohash/pyoidc/master/oidc_example/rp3/static/bootstrap/fonts/glyphicons-halflings-regular.eot -------------------------------------------------------------------------------- /oidc_example/rp3/static/bootstrap/fonts/glyphicons-halflings-regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/infohash/pyoidc/master/oidc_example/rp3/static/bootstrap/fonts/glyphicons-halflings-regular.ttf -------------------------------------------------------------------------------- /oidc_example/op2/static/bootstrap/fonts/glyphicons-halflings-regular.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/infohash/pyoidc/master/oidc_example/op2/static/bootstrap/fonts/glyphicons-halflings-regular.woff -------------------------------------------------------------------------------- /oidc_example/op3/static/bootstrap/fonts/glyphicons-halflings-regular.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/infohash/pyoidc/master/oidc_example/op3/static/bootstrap/fonts/glyphicons-halflings-regular.woff -------------------------------------------------------------------------------- /oidc_example/rp2/static/bootstrap/fonts/glyphicons-halflings-regular.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/infohash/pyoidc/master/oidc_example/rp2/static/bootstrap/fonts/glyphicons-halflings-regular.woff -------------------------------------------------------------------------------- /oidc_example/rp3/static/bootstrap/fonts/glyphicons-halflings-regular.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/infohash/pyoidc/master/oidc_example/rp3/static/bootstrap/fonts/glyphicons-halflings-regular.woff -------------------------------------------------------------------------------- /tests/jwks/jwks_fault.json: -------------------------------------------------------------------------------- 1 | { 2 | "keys": [ 3 | { 4 | "n": 1194457323795445980561452000539327328778638467, 5 | "e": 65537, 6 | "kty": "RSA", 7 | "kid": "rsa1" 8 | } 9 | ] 10 | } -------------------------------------------------------------------------------- /.github/PULL_REQUEST_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | - [ ] Any changes relevant to users are recorded in the `CHANGELOG.md`. 2 | - [ ] The documentation has been updated, if necessary. 3 | - [ ] New code is annotated. 4 | - [ ] Changes are covered by tests. 5 | --- 6 | -------------------------------------------------------------------------------- /oidc_example/op3/README.md: -------------------------------------------------------------------------------- 1 | # OIDCProvider 2 | An example of OpenID Connect Provider based on pyoidc. 3 | 4 | This example is a simplified version of pyoidc "op2" example available at: 5 | https://github.com/CZ-NIC/pyoidc/tree/master/oidc_example/op2 6 | -------------------------------------------------------------------------------- /tests/data/keys/jwk_enc.json: -------------------------------------------------------------------------------- 1 | { 2 | "keys": [ 3 | { 4 | "kty": "oct", 5 | "k": "AyM1SysPpbyDfgZld3umj1qzKObwVMkoqQ-EstJQLr_T-1qS0gZH75aKtMN3Yj0iPS4hcgUuTwjAzZr1Z9CAow", 6 | "kid": "HMAC key used in JWS spec Appendix A.1 example"} 7 | ] 8 | } 9 | -------------------------------------------------------------------------------- /setup.cfg: -------------------------------------------------------------------------------- 1 | [pydocstyle] 2 | convention = google 3 | add-ignore = 4 | D1, # Ignore missing docstrings 5 | D212, # Multiline docstring not on first line 6 | D413, # No blank line after last section 7 | add-select = 8 | D213 # Multiline docstring should start on first line 9 | -------------------------------------------------------------------------------- /tests/rsa_enc.pub: -------------------------------------------------------------------------------- 1 | -----BEGIN PUBLIC KEY----- 2 | MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCIc+q8Y+q4qjw/SMMRzwyfJ7Iv 3 | IQ7Q3WSE+x2Z61bwh4luI65HzARW3X9NkD8kHAh/Vxaz/wS86FXALTGygoKQ8ETO 4 | C7WNz/4g/z9VHowcZPO/jwdTzfo+2nK3Xg9qSAWF6x5jnSaFsuvcotcrxpke9gVC 5 | q+MkEDDzeKOAiHJnNwIDAQAB 6 | -----END PUBLIC KEY----- -------------------------------------------------------------------------------- /script/webfinger.py: -------------------------------------------------------------------------------- 1 | import sys 2 | 3 | from oic.oauth2 import PBase 4 | from oic.utils.webfinger import OIC_ISSUER 5 | from oic.utils.webfinger import WebFinger 6 | 7 | __author__ = 'roland' 8 | 9 | wf = WebFinger(OIC_ISSUER) 10 | wf.httpd = PBase() 11 | print (wf.discovery_query(sys.argv[1])) 12 | -------------------------------------------------------------------------------- /appveyor.yml: -------------------------------------------------------------------------------- 1 | image: 2 | - Visual Studio 2019 3 | 4 | environment: 5 | matrix: 6 | - TOXENV: py37 7 | - TOXENV: py38 8 | - TOXENV: py39 9 | - TOXENV: py310 10 | 11 | build: off 12 | 13 | install: 14 | - py -m pip install --upgrade pip tox 15 | 16 | test_script: 17 | - py -m tox 18 | -------------------------------------------------------------------------------- /oidc_example/op1/cp_config.json: -------------------------------------------------------------------------------- 1 | { 2 | "baseurl":"https://localhost:8093", 3 | "issuer": "https://www.kodtest.se/rolandsCP", 4 | "keys": { 5 | "rsa": { 6 | "key":"cp_keys/key.pem", 7 | "jwk": "cp_keys/pub.jwk", 8 | "cert": "cp_keys/cert.pem" 9 | }} 10 | } -------------------------------------------------------------------------------- /tests/data/keys/rsa.pub: -------------------------------------------------------------------------------- 1 | -----BEGIN PUBLIC KEY----- 2 | MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQD6vqn19W/VB215DBADRakfPmCt 3 | FBf8/+YyhGqixWIwDiEl/L6Lw5HKZCUPVgrC0ADhJfvAbn4fte5MWBCTkqgepKL3 4 | BySMA0LMaBF12pbHlPSUbmQGBJmTX4NNXuUel6TbPYJAU2Nh5Nan0Mb7Bmb8QpFv 5 | S0Hw7qZRW8y2eIttfwIDAQAB 6 | -----END PUBLIC KEY----- 7 | -------------------------------------------------------------------------------- /docker/op_test/https%3A%2F%2Fop%3A4433/default: -------------------------------------------------------------------------------- 1 | {"tool": {"issuer": "https://op:4433", "tag": "default", "webfinger_email": "acct:foobar@op:4433", "webfinger_url": "https://op:4433/foobar", "acr_values": ["session urn:mace:incommon:iap:bronze"], "insecure": true, "login_hint": "bob@example.com", "profile": "CT.T.T.T.ens.+"}} -------------------------------------------------------------------------------- /pylama.ini: -------------------------------------------------------------------------------- 1 | [pylama] 2 | linters = pyflakes,eradicate,pycodestyle,mccabe 3 | # D203/D204 and D212/D213 are mutually exclusive, pick one 4 | # E203 is not PEP8 compliant in pycodestyle 5 | ignore = D203,D212,E203,C901 6 | 7 | [pylama:pycodestyle] 8 | max_line_length = 120 9 | 10 | [pylama:mccabe] 11 | complexity = 30 12 | -------------------------------------------------------------------------------- /src/oic/extension/single.py: -------------------------------------------------------------------------------- 1 | class SingleService(object): 2 | def __init__(self, host): 3 | self.host = host 4 | self.endpoints = {} # type: ignore 5 | 6 | 7 | class SingleClient(object): 8 | def __init__(self, host): 9 | self.host = host 10 | self.requests = {} # type: ignore 11 | -------------------------------------------------------------------------------- /tests/jwks/jwks0.json: -------------------------------------------------------------------------------- 1 | { 2 | "keys": [ 3 | { 4 | "kty": "RSA", 5 | "e": "AQAB", 6 | "kid": "abc", 7 | "n": "wf-wiusGhA-gleZYQAOPQlNUIucPiqXdPVyieDqQbXXOPBe3nuggtVzeq7pVFH1dZz4dY2Q2LA5DaegvP8kRvoSB_87ds3dy3Rfym_GUSc5B0l1TgEobcyaep8jguRoHto6GWHfCfKqoUYZq4N8vh4LLMQwLR6zi6Jtu82nB5k8" 8 | } 9 | ] 10 | } -------------------------------------------------------------------------------- /oauth_example/as/tre.py: -------------------------------------------------------------------------------- 1 | from oic.utils.keyio import build_keyjar 2 | from oic.utils.keyio import dump_jwks 3 | 4 | __author__ = "roland" 5 | 6 | key_conf = [{"type": "RSA", "use": ["enc", "sig"]}] 7 | 8 | pub_jwks, keyjar, kdd = build_keyjar(key_conf, "tre%d", None, None) 9 | 10 | dump_jwks(keyjar.issuer_keys[""], "tre.jwks") 11 | -------------------------------------------------------------------------------- /oidc_example/simple_rp/src/htdocs/success_page.html: -------------------------------------------------------------------------------- 1 | 2 | RP Auth Success 3 | 4 | 5 | Authorization code: 6 |
 7 | 	{0}
 8 | 	
 9 | 
10 | 11 | Access token: 12 |
13 | 	{1}
14 | 
15 | 16 | Id token: 17 |
18 | 	{2}
19 | 
20 | 21 | User info: 22 |
23 | 	{3}
24 | 
25 | 26 | 27 | -------------------------------------------------------------------------------- /oidc_example/simple_op/src/provider/authn/util.py: -------------------------------------------------------------------------------- 1 | import json 2 | 3 | 4 | class JSONDictDB(object): 5 | def __init__(self, json_path): 6 | with open(json_path, "r") as f: 7 | self._db = json.load(f) 8 | 9 | def __getitem__(self, item): 10 | return self._db[item] 11 | 12 | def __contains__(self, item): 13 | return item in self._db 14 | -------------------------------------------------------------------------------- /docker/oidc_op/scripts/make_test_site.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | import os 3 | 4 | from oidctest.site_setup import oidc_op_setup 5 | 6 | _distroot = os.path.abspath(os.path.join(os.path.dirname(__file__), "../../..")) 7 | 8 | _root = 'test_site' 9 | 10 | if os.path.isdir(_root) is False: 11 | os.makedirs(_root) 12 | 13 | os.chdir(_root) 14 | 15 | oidc_op_setup(_distroot) 16 | -------------------------------------------------------------------------------- /oidc_example/op2/constraints.txt: -------------------------------------------------------------------------------- 1 | alabaster==0.7.12 2 | asn1crypto==0.24.0 3 | Beaker==1.10.1 4 | certifi==2018.11.29 5 | cffi==1.12.1 6 | chardet==3.0.4 7 | CherryPy==8.9.1 8 | cryptography==2.5 9 | future==0.17.1 10 | idna==2.8 11 | Mako==1.0.7 12 | MarkupSafe==1.1.1 13 | oic==0.15.1 14 | pycparser==2.19 15 | pycryptodomex==3.7.3 16 | pyjwkest==1.4.0 17 | requests==2.21.0 18 | six==1.12.0 19 | urllib3==1.24.1 20 | -------------------------------------------------------------------------------- /oidc_example/simple_rp/src/htdocs/index.html: -------------------------------------------------------------------------------- 1 | 2 | Test RP 3 | 4 |

Example OpenID Connect RP

5 | 6 | Enter UID to select OpenID Connect provider: 7 | 8 |
9 | 10 | 11 |
12 | 13 | 14 | -------------------------------------------------------------------------------- /oidc_example/op3/cryptography_keys/key.pem.pub: -------------------------------------------------------------------------------- 1 | -----BEGIN RSA PUBLIC KEY----- 2 | MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA6LC5Zr1Ng3HN/RF173gS 3 | AtcWo3RbnRc+YtE2BZnmNDVe2cepFmMjYbcum6bonS7JWCTDYd5m0Gt7+yh71YQx 4 | F3bjXAEXGbR6w1405emJuurFgfAnwk8h+o//+DYoF4E9WdbHfi9wRa7tS8pAB2o1 5 | JpsH1r3O7wCSPb/cDRIQZ3R5ORjl+q/PWUFxAjLKe2IAM2rfJulertITnGlxcnzz 6 | y9cnIyYgY+gm17hZFZd1qLpNG10McyzlwxPl6c+7PagS2nx9Mdv4gUvaNwzgkzQQ 7 | h9viJfBggc1o86CEPg8RAOqWjUJ8jHAYJDeRxEFHKGcbOXa2n1ZOBj+FHprIOg60 8 | XwIDAQAB 9 | -----END RSA PUBLIC KEY----- -------------------------------------------------------------------------------- /doc/contrib/documentation.rst: -------------------------------------------------------------------------------- 1 | .. _documentation: 2 | 3 | Documentation 4 | ############# 5 | 6 | PyOIDC uses Sphinx_ for documentation. 7 | 8 | You can install it via Pip_: 9 | 10 | .. _Pip: https://pip.pypa.io/en/stable/installing/ 11 | .. _Sphinx: https://www.sphinx-doc.org/ 12 | 13 | :: 14 | 15 | $ pip install .[docs] 16 | 17 | To build the documentation, run: 18 | 19 | :: 20 | 21 | $ make help 22 | 23 | To have the changes automatically shown while you're hacking, run: 24 | 25 | :: 26 | 27 | $ make livehtml 28 | -------------------------------------------------------------------------------- /docker/rp_test/run.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | trap 'kill ${!}; term_handler' SIGHUP SIGINT SIGQUIT SIGTERM 4 | 5 | term_handler() { 6 | echo "term_handler" 7 | killall python3 8 | exit 143; # 128 + 15 -- SIGTERM 9 | } 10 | 11 | echo -n "Starting config_server.py ... " 12 | python3 server.py -f flows -p 8080 -k -t conf & 13 | if [ $? -eq 0 ] ; then 14 | echo "OK" 15 | else 16 | echo "ERROR" 17 | exit -1 18 | fi 19 | 20 | while true 21 | do 22 | tail -f /dev/null & wait ${!} 23 | done 24 | 25 | echo "exited $0" 26 | -------------------------------------------------------------------------------- /docker/op_test/config.py: -------------------------------------------------------------------------------- 1 | import os 2 | 3 | BASEDIR = os.path.abspath(os.path.dirname(__file__)) 4 | 5 | SERVER_CERT = "certs/cert.pem" 6 | SERVER_KEY = "certs/key.pem" 7 | CA_BUNDLE = None 8 | 9 | VERIFY_SSL = False 10 | 11 | PORT_MIN = 60001 12 | PORT_MAX = 61000 13 | 14 | BASE_URL = 'https://op-test' 15 | 16 | # The variables below are all passed on to the test tool instance 17 | ENT_PATH = 'entities' 18 | ENT_INFO = 'entity_info' 19 | 20 | FLOWDIR = 'flows' 21 | 22 | PATH2PORT = 'path2port.csv' 23 | TEST_SCRIPT = './op_test_tool.py' 24 | -------------------------------------------------------------------------------- /tests/data/keys/jwk.json: -------------------------------------------------------------------------------- 1 | { 2 | "keys": [ 3 | { 4 | "alg": "RS256", 5 | "e": "AQAB", 6 | "kid": "abc", 7 | "kty": "RSA", 8 | "use": "sig", 9 | "n": "pKybs0WaHU_y4cHxWbm8Wzj66HtcyFn7Fh3n-99qTXu5yNa30MRYIYfSDwe9JVc1JUoGw41yq2StdGBJ40HxichjE-Yopfu3B58QlgJvToUbWD4gmTDGgMGxQxtv1En2yedaynQ73sDpIK-12JJDY55pvf-PCiSQ9OjxZLiVGKlClDus44_uv2370b9IN2JiEOF-a7JBqaTEYLPpXaoKWDSnJNonr79tL0T7iuJmO1l705oO3Y0TQ-INLY6jnKG_RpsvyvGNnwP9pMvcP1phKsWZ10ofuuhJGRp8IxQL9RfzT87OvF0RBSO1U73h09YP-corWDsnKIi6TbzRpN5YDw" 10 | } 11 | ] 12 | } -------------------------------------------------------------------------------- /docker/op_test/run.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | trap 'kill ${!}; term_handler' SIGHUP SIGINT SIGQUIT SIGTERM 4 | 5 | term_handler() { 6 | echo "term_handler" 7 | killall python3 8 | exit 143; # 128 + 15 -- SIGTERM 9 | } 10 | 11 | echo -n "Starting config_server.py ... " 12 | python3 config_server.py -t -k -p 60000 -H html -c tt_config config & 13 | if [ $? -eq 0 ] ; then 14 | echo "OK" 15 | else 16 | echo "ERROR" 17 | exit -1 18 | fi 19 | 20 | while true 21 | do 22 | tail -f /dev/null & wait ${!} 23 | done 24 | 25 | echo "exited $0" 26 | -------------------------------------------------------------------------------- /docker/op/apache-ssl.conf: -------------------------------------------------------------------------------- 1 | 2 | Listen 4433 3 | 4 | DocumentRoot /var/www/html 5 | 6 | #LogLevel info ssl:warn 7 | ErrorLog ${APACHE_LOG_DIR}/error.log 8 | CustomLog ${APACHE_LOG_DIR}/access.log combined 9 | 10 | SSLEngine on 11 | SSLCertificateFile /etc/apache2/cert.pem 12 | SSLCertificateKeyFile /etc/apache2/key.pem 13 | 14 | RequestHeader set X-Forwarded-Proto "https" 15 | ProxyPass / http://127.0.0.1:3000/ 16 | ProxyPassReverse / http://127.0.0.1:3000/ 17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /src/oic/utils/claims.py: -------------------------------------------------------------------------------- 1 | __author__ = "rolandh" 2 | 3 | 4 | class ClaimsMode(object): 5 | def __init__(self, user2mode): 6 | self.user2mode = user2mode 7 | 8 | def aggregate(self, uid, info=None): 9 | """ 10 | Determine whether the claims for a user should be aggregated. 11 | 12 | :param uid: user id 13 | :param info: claims 14 | :return: True if the claims should be aggregated, otherwist False 15 | """ 16 | if uid in self.user2mode and self.user2mode[uid] == "aggregate": 17 | return True 18 | 19 | return False 20 | -------------------------------------------------------------------------------- /oidc_example/rp2/conf.py.example: -------------------------------------------------------------------------------- 1 | PORT = 8666 2 | #BASE = "http://lingon.catalogix.se:" + str(PORT) + "/" 3 | #BASE = "http://hashog.umdc.umu.se:" + str(PORT) + "/" 4 | BASE = "http://localhost:" + str(PORT) + "/" 5 | 6 | # If BASE is https these has to be specified 7 | SERVER_KEY = '' 8 | SERVER_CERT = '' 9 | CA_BUNDLE = None 10 | 11 | # information used when registering the client 12 | ME = { 13 | "application_type": "web", 14 | "application_name": "idpproxy", 15 | "contacts": ["ops@example.com"], 16 | } 17 | 18 | # Which scopes to use 19 | SCOPE = ["openid", "profile", "email", "address", "phone"] -------------------------------------------------------------------------------- /oidc_example/simple_op/src/provider/authn/templates/yubico_otp.jinja2: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Please login 7 | 8 | 9 | 10 |

Yubikey OTP (One Time Password)

11 | 12 |
13 | 14 | 15 |

16 | 17 | 18 |

19 | 20 | 21 |
22 | 23 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | Copyright (C) 2017 Roland Hedberg, Sweden 2 | 3 | Licensed under the Apache License, Version 2.0 (the "License"); 4 | you may not use this file except in compliance with the License. 5 | You may obtain a copy of the License at 6 | 7 | http://www.apache.org/licenses/LICENSE-2.0 8 | 9 | Unless required by applicable law or agreed to in writing, software 10 | distributed under the License is distributed on an "AS IS" BASIS, 11 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | See the License for the specific language governing permissions and 13 | limitations under the License. 14 | -------------------------------------------------------------------------------- /oidc_example/op1/create_jwk_from_cert.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | from oic.oauth2 import PBase 4 | from oic.utils.keystore import KeyStore 5 | from oic.utils.keystore import x509_rsa_loads 6 | 7 | __author__ = 'rohe0002' 8 | 9 | def main(x509_file, out="keys.jwk"): 10 | pb = PBase() 11 | ks = KeyStore(pb.http_request) 12 | 13 | key = x509_rsa_loads(open(x509_file).read()) 14 | ks.add_key(key, "rsa", "sig") 15 | 16 | f = open(out, "w") 17 | txt = ks.dumps("sig") 18 | f.write(txt) 19 | f.close() 20 | 21 | if __name__ == "__main__": 22 | import sys 23 | main(*sys.argv[1:2]) 24 | -------------------------------------------------------------------------------- /tests/jwks/jwks1.json: -------------------------------------------------------------------------------- 1 | { 2 | "keys": [ 3 | { 4 | "n": "zkpUgEgXICI54blf6iWiD2RbMDCOO1jV0VSff1MFFnujM4othfMsad7H1kRo50YM5S_X9TdvrpdOfpz5aBaKFhT6Ziv0nhtcekq1eRl8mjBlvGKCE5XGk-0LFSDwvqgkJoFYInq7bu0a4JEzKs5AyJY75YlGh879k1Uu2Sv3ZZOunfV1O1Orta-NvS-aG_jN5cstVbCGWE20H0vFVrJKNx0Zf-u-aA-syM4uX7wdWgQ-owoEMHge0GmGgzso2lwOYf_4znanLwEuO3p5aabEaFoKNR4K6GjQcjBcYmDEE4CtfRU9AEmhcD1kleiTB9TjPWkgDmT9MXsGxBHf3AKT5w", 5 | "e": "AQAB", 6 | "kty": "RSA", 7 | "kid": "rsa1" 8 | }, 9 | { 10 | "k": "YTEyZjBlMDgxMGI4YWU4Y2JjZDFiYTFlZTBjYzljNDU3YWM0ZWNiNzhmNmFlYTNkNTY0NzMzYjE", 11 | "kty": "oct" 12 | } 13 | ] 14 | } -------------------------------------------------------------------------------- /tests/jwks/jwks_uk.json: -------------------------------------------------------------------------------- 1 | { 2 | "keys": [ 3 | { 4 | "n": "zkpUgEgXICI54blf6iWiD2RbMDCOO1jV0VSff1MFFnujM4othfMsad7H1kRo50YM5S_X9TdvrpdOfpz5aBaKFhT6Ziv0nhtcekq1eRl8mjBlvGKCE5XGk-0LFSDwvqgkJoFYInq7bu0a4JEzKs5AyJY75YlGh879k1Uu2Sv3ZZOunfV1O1Orta-NvS-aG_jN5cstVbCGWE20H0vFVrJKNx0Zf-u-aA-syM4uX7wdWgQ-owoEMHge0GmGgzso2lwOYf_4znanLwEuO3p5aabEaFoKNR4K6GjQcjBcYmDEE4CtfRU9AEmhcD1kleiTB9TjPWkgDmT9MXsGxBHf3AKT5w", 5 | "e": "AQAB", 6 | "kty": "RSA", 7 | "kid": "rsa1" 8 | }, 9 | { 10 | "k": "YTEyZjBlMDgxMGI4YWU4Y2JjZDFiYTFlZTBjYzljNDU3YWM0ZWNiNzhmNmFlYTNkNTY0NzMzYjE", 11 | "kty": "buz" 12 | } 13 | ] 14 | } -------------------------------------------------------------------------------- /oidc_example/op2/htdocs/unauthorized.mako: -------------------------------------------------------------------------------- 1 | 2 | 3 | <%inherit file="base.mako"/> 4 | 5 | <%block name="title"> 6 | Unauthorized 7 | ${parent.title()} 8 | 9 | 10 | <%block name="headline"> 11 | 12 | 17 | 18 | 19 | <%block name="body"> 20 |
21 |
${message}
22 |
23 | -------------------------------------------------------------------------------- /oidc_example/op3/htdocs/unauthorized.mako: -------------------------------------------------------------------------------- 1 | 2 | 3 | <%inherit file="base.mako"/> 4 | 5 | <%block name="title"> 6 | Unauthorized 7 | ${parent.title()} 8 | 9 | 10 | <%block name="headline"> 11 | 12 | 17 | 18 | 19 | <%block name="body"> 20 |
21 |
${message}
22 |
23 | -------------------------------------------------------------------------------- /oidc_example/op2/htdocs/form_response.mako: -------------------------------------------------------------------------------- 1 | <%! 2 | def inputs(form_args): 3 | """ 4 | Creates list of input elements 5 | """ 6 | element = "" 7 | for name, value in form_args.items(): 8 | element += "" % (name, 9 | value) 10 | return element 11 | %> 12 | 13 | 14 | 15 | Submit This Form 16 | 17 | 18 |
19 | ${inputs(form_args)} 20 |
21 | 22 | 23 | -------------------------------------------------------------------------------- /oidc_example/op3/htdocs/form_response.mako: -------------------------------------------------------------------------------- 1 | <%! 2 | def inputs(form_args): 3 | """ 4 | Creates list of input elements 5 | """ 6 | element = "" 7 | for name, value in form_args.items(): 8 | element += "" % (name, 9 | value) 10 | return element 11 | %> 12 | 13 | 14 | 15 | Submit This Form 16 | 17 | 18 |
19 | ${inputs(form_args)} 20 |
21 | 22 | 23 | -------------------------------------------------------------------------------- /docker/op/run.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | trap 'kill ${!}; term_handler' SIGHUP SIGINT SIGQUIT SIGTERM 4 | 5 | pid=0 6 | 7 | term_handler() { 8 | echo "term_handler" 9 | service ntp stop 10 | if [ $pid -ne 0 ]; then 11 | kill -SIGTERM "$pid" 12 | wait "$pid" 13 | fi 14 | exit 143; # 128 + 15 -- SIGTERM 15 | } 16 | 17 | service ntp start 18 | service apache2 start 19 | 20 | echo -n "Starting node example ... " 21 | node certification/oidc & 22 | pid="$!" 23 | if [ $? -eq 0 ] ; then 24 | echo "OK" 25 | else 26 | echo "ERROR" 27 | exit -1 28 | fi 29 | 30 | while true 31 | do 32 | tail -f /dev/null & wait ${!} 33 | done 34 | 35 | echo "exited $0" 36 | -------------------------------------------------------------------------------- /docker/docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: '3' 2 | services: 3 | op-test: 4 | build: 5 | context: .. 6 | dockerfile: docker/op_test/Dockerfile 7 | ports: 8 | - "60000-60010:60000-60010" 9 | rp-test: 10 | build: 11 | context: .. 12 | dockerfile: docker/rp_test/Dockerfile 13 | ports: 14 | - "8080:8080" 15 | op: 16 | build: 17 | context: .. 18 | dockerfile: docker/op/Dockerfile 19 | ports: 20 | - "4433:4433" 21 | environment: 22 | - ISSUER=https://op:4433 23 | - NODE_ENV=production 24 | - DEBUG=oidc-provider:* 25 | - NODE_TLS_REJECT_UNAUTHORIZED=0 26 | links: 27 | - op-test 28 | -------------------------------------------------------------------------------- /mypy.ini: -------------------------------------------------------------------------------- 1 | [mypy] 2 | check_untyped_defs = True 3 | no_implicit_optional = True 4 | 5 | [mypy-jwkest.*] 6 | ignore_missing_imports = True 7 | 8 | [mypy-saml2.*] 9 | ignore_missing_imports = True 10 | 11 | [mypy-ldap.*] 12 | ignore_missing_imports = True 13 | 14 | [mypy-cryptography.*] 15 | ignore_missing_imports = True 16 | 17 | [mypy-defusedxml.*] 18 | ignore_missing_imports = True 19 | 20 | [mypy-pytest.*] 21 | ignore_missing_imports = True 22 | 23 | [mypy-responses.*] 24 | ignore_missing_imports = True 25 | 26 | [mypy-freezegun.*] 27 | ignore_missing_imports = True 28 | 29 | [mypy-testfixtures.*] 30 | ignore_missing_imports = True 31 | 32 | [mypy-mako.*] 33 | ignore_missing_imports = True 34 | -------------------------------------------------------------------------------- /oauth_example/as/templates/root.mako: -------------------------------------------------------------------------------- 1 | <%def name="pre()" filter="trim"> 2 |
3 |

Login

4 |
5 | 6 | <%def name="post()" filter="trim"> 7 |
8 | 11 |
12 | 13 | ## 15 | 16 | OAuth test 17 | ${self.css()} 18 | 19 | 20 | 21 | ${pre()} 22 | ${next.body()} 23 | ${post()} 24 | 25 | 26 | -------------------------------------------------------------------------------- /oauth_example/as/certs/server.csr: -------------------------------------------------------------------------------- 1 | -----BEGIN CERTIFICATE REQUEST----- 2 | MIIBqDCCARECAQAwUTELMAkGA1UEBhMCU0UxEzARBgNVBAgTClNvbWUtU3RhdGUx 3 | DDAKBgNVBAoTA1VtVTEMMAoGA1UECxMDSVRTMREwDwYDVQQDEwhyb2hlMDAwMjCB 4 | nzANBgkqhkiG9w0BAQEFAAOBjQAwgYkCgYEA5zbNbHIYIkGGJ3RGdRKkYmF4gOor 5 | v5eDuUKTVtuu3VvxrpOWvwnFV+NY0LgqkQSMMyVzodJE3SUuwQTUHPXXY5784vnk 6 | FqzPRx6bHgPxKz7XfwQjEBTafQTMmOeYI8wFIOIHY5i0RWR+gxDbh/D5TXuUqScO 7 | OqR47vSpIbUH+ncCAwEAAaAXMBUGCSqGSIb3DQEJBzEIEwZmb29iYXIwDQYJKoZI 8 | hvcNAQEFBQADgYEAY3aUO7bJxGOmqUtBTTFeDkuJutUk2KQjvycVOkCy9QdbYPyL 9 | vtwJGfNN1cLpdSdiEICtwGg1XfjCZQBRPrXOSBiCfmPkchVlw9QXzluPeIJ7oFnZ 10 | fi9I6u+7D3ZmRVSwZmrRIcj9fb/v6ohAKPVr8520HkGD3mKUkXMyTcXLFjo= 11 | -----END CERTIFICATE REQUEST----- 12 | -------------------------------------------------------------------------------- /oidc_example/simple_op/src/provider/authn/templates/mail_two_factor.jinja2: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Please login 7 | 8 | 9 | 10 |

Second part of authentication

11 | 12 | Check your registered mail {{ mail }}, then enter the received code 13 | below: 14 | 15 |
16 | 17 | 18 |

19 | 20 | 21 |

22 | 23 | 24 |
25 | 26 | -------------------------------------------------------------------------------- /oauth_example/rp/templates/root.mako: -------------------------------------------------------------------------------- 1 | <%def name="pre()" filter="trim"> 2 |
3 |

Login

4 |
5 | 6 | <%def name="post()" filter="trim"> 7 |
8 | 11 |
12 | 13 | ## 15 | 16 | OAuth test 17 | 18 | 19 | 20 | ${pre()} 21 | ## ${comps.dict_to_table(pageargs)} 22 | ##

23 | ${next.body()} 24 | ${post()} 25 | 26 | 27 | -------------------------------------------------------------------------------- /docker/op_test/tt_config.py: -------------------------------------------------------------------------------- 1 | import os 2 | 3 | BASEDIR = os.path.abspath(os.path.dirname(__file__)) 4 | 5 | SERVER_CERT = "certs/cert.pem" 6 | SERVER_KEY = "certs/key.pem" 7 | CA_BUNDLE = None 8 | 9 | VERIFY_SSL = False 10 | 11 | # Make sure BASE starts with https if TLS = True 12 | BASE = 'https://op-test' 13 | 14 | ENT_PATH = 'entities' 15 | ENT_INFO = 'entity_info' 16 | PRE_HTML = 'html/tt' 17 | 18 | KEYS = [ 19 | {"key": "keys/enc.key", "type": "RSA", "use": ["enc"]}, 20 | {"key": "keys/sig.key", "type": "RSA", "use": ["sig"]}, 21 | {"crv": "P-256", "type": "EC", "use": ["sig"]}, 22 | {"crv": "P-256", "type": "EC", "use": ["enc"]} 23 | ] 24 | 25 | SESSION_CHANGE_URL = "{}session_change" 26 | SESSION_UNCHANGE_URL = "{}session_unchange" 27 | SESSION_MAX_CHECKS = 3 28 | -------------------------------------------------------------------------------- /oidc_example/simple_rp/settings.yaml.example: -------------------------------------------------------------------------------- 1 | # parameters for registration with the provider 2 | registration_info: 3 | application_type: web 4 | application_name: "Example RP" 5 | # only has support for using the first redirect_uri and response_type 6 | redirect_uris: 7 | #- "{base}/code_flow" 8 | #- "{base}/implicit_hybrid_flow" 9 | - "{base}:{port}/implicit_hybrid_flow" 10 | response_types: 11 | #- code 12 | - code id_token token 13 | 14 | # parameters for authentication request 15 | behaviour: 16 | scope: 17 | - openid 18 | - profile 19 | acr_values: 20 | - password 21 | - mail_two_factor 22 | - yubikey 23 | 24 | 25 | server: 26 | cert: certs/localhost.crt 27 | key: certs/localhost.key 28 | cert_chain: 29 | verify_ssl: False 30 | -------------------------------------------------------------------------------- /oidc_example/simple_op/src/provider/authn/templates/user_pass.jinja2: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Please login 7 | 8 | 9 | 10 |

{{ page_header }}

11 | 12 |
13 | 14 | 15 |

16 | 17 | 19 |

20 | 21 |

22 | 23 | 24 |

25 | 26 | 27 |
28 | 29 | 30 | -------------------------------------------------------------------------------- /oauth_example/rp/conf.py: -------------------------------------------------------------------------------- 1 | from mako.lookup import TemplateLookup 2 | 3 | PORT = 8666 4 | HOST = "localhost" 5 | 6 | BASE = "http://%s:%d/" % (HOST, PORT) 7 | 8 | # If BASE is https these has to be specified 9 | SERVER_KEY = "" 10 | SERVER_CERT = "" 11 | CA_BUNDLE = None 12 | 13 | SCOPE = [] 14 | 15 | ROOT = "./" 16 | LOOKUP = TemplateLookup( 17 | directories=[ROOT + "templates", ROOT + "htdocs"], 18 | module_directory=ROOT + "modules", 19 | input_encoding="utf-8", 20 | output_encoding="utf-8", 21 | ) 22 | 23 | AS_CONF = { 24 | "AuthzServer@DIG": { 25 | "authorization_endpoint": "https://localhost:8080/authorization", 26 | "token_endpoint": "https://localhost:8080/token", 27 | "client_id": "YWwQiwQNWaeI", 28 | "client_secret": "cdb8c2f40110a5fdefe7e26ea26a0bd51fb3d1b9593d6a054c75abcb", 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /oauth_example/as/certs/server.crt: -------------------------------------------------------------------------------- 1 | -----BEGIN CERTIFICATE----- 2 | MIICGTCCAYICCQCIs8fF+AnxUzANBgkqhkiG9w0BAQUFADBRMQswCQYDVQQGEwJT 3 | RTETMBEGA1UECBMKU29tZS1TdGF0ZTEMMAoGA1UEChMDVW1VMQwwCgYDVQQLEwNJ 4 | VFMxETAPBgNVBAMTCHJvaGUwMDAyMB4XDTExMDkwNzA4MjMwOFoXDTEyMDkwNjA4 5 | MjMwOFowUTELMAkGA1UEBhMCU0UxEzARBgNVBAgTClNvbWUtU3RhdGUxDDAKBgNV 6 | BAoTA1VtVTEMMAoGA1UECxMDSVRTMREwDwYDVQQDEwhyb2hlMDAwMjCBnzANBgkq 7 | hkiG9w0BAQEFAAOBjQAwgYkCgYEA5zbNbHIYIkGGJ3RGdRKkYmF4gOorv5eDuUKT 8 | Vtuu3VvxrpOWvwnFV+NY0LgqkQSMMyVzodJE3SUuwQTUHPXXY5784vnkFqzPRx6b 9 | HgPxKz7XfwQjEBTafQTMmOeYI8wFIOIHY5i0RWR+gxDbh/D5TXuUqScOOqR47vSp 10 | IbUH+ncCAwEAATANBgkqhkiG9w0BAQUFAAOBgQDP9PE53utRqocZpFRxrhL+4vcI 11 | vlQd3XonE7vMJwdl9FUW7QRMon19dpYKQ6LTCOBA4ZCwh2z/Om2X97zogMLKFPcE 12 | LVqxzBVlrzuqAgOErEalYv9pCBzoFHGUHP6kTAUOCsoO9ZdQWgA6YkK2hYRSlLNX 13 | Z0wHIHGaHe5xnFFpyQ== 14 | -----END CERTIFICATE----- 15 | -------------------------------------------------------------------------------- /oidc_example/op1/certs/server.crt: -------------------------------------------------------------------------------- 1 | -----BEGIN CERTIFICATE----- 2 | MIICGTCCAYICCQCIs8fF+AnxUzANBgkqhkiG9w0BAQUFADBRMQswCQYDVQQGEwJT 3 | RTETMBEGA1UECBMKU29tZS1TdGF0ZTEMMAoGA1UEChMDVW1VMQwwCgYDVQQLEwNJ 4 | VFMxETAPBgNVBAMTCHJvaGUwMDAyMB4XDTExMDkwNzA4MjMwOFoXDTEyMDkwNjA4 5 | MjMwOFowUTELMAkGA1UEBhMCU0UxEzARBgNVBAgTClNvbWUtU3RhdGUxDDAKBgNV 6 | BAoTA1VtVTEMMAoGA1UECxMDSVRTMREwDwYDVQQDEwhyb2hlMDAwMjCBnzANBgkq 7 | hkiG9w0BAQEFAAOBjQAwgYkCgYEA5zbNbHIYIkGGJ3RGdRKkYmF4gOorv5eDuUKT 8 | Vtuu3VvxrpOWvwnFV+NY0LgqkQSMMyVzodJE3SUuwQTUHPXXY5784vnkFqzPRx6b 9 | HgPxKz7XfwQjEBTafQTMmOeYI8wFIOIHY5i0RWR+gxDbh/D5TXuUqScOOqR47vSp 10 | IbUH+ncCAwEAATANBgkqhkiG9w0BAQUFAAOBgQDP9PE53utRqocZpFRxrhL+4vcI 11 | vlQd3XonE7vMJwdl9FUW7QRMon19dpYKQ6LTCOBA4ZCwh2z/Om2X97zogMLKFPcE 12 | LVqxzBVlrzuqAgOErEalYv9pCBzoFHGUHP6kTAUOCsoO9ZdQWgA6YkK2hYRSlLNX 13 | Z0wHIHGaHe5xnFFpyQ== 14 | -----END CERTIFICATE----- 15 | -------------------------------------------------------------------------------- /oidc_example/op2/certs/server.crt: -------------------------------------------------------------------------------- 1 | -----BEGIN CERTIFICATE----- 2 | MIICGTCCAYICCQCIs8fF+AnxUzANBgkqhkiG9w0BAQUFADBRMQswCQYDVQQGEwJT 3 | RTETMBEGA1UECBMKU29tZS1TdGF0ZTEMMAoGA1UEChMDVW1VMQwwCgYDVQQLEwNJ 4 | VFMxETAPBgNVBAMTCHJvaGUwMDAyMB4XDTExMDkwNzA4MjMwOFoXDTEyMDkwNjA4 5 | MjMwOFowUTELMAkGA1UEBhMCU0UxEzARBgNVBAgTClNvbWUtU3RhdGUxDDAKBgNV 6 | BAoTA1VtVTEMMAoGA1UECxMDSVRTMREwDwYDVQQDEwhyb2hlMDAwMjCBnzANBgkq 7 | hkiG9w0BAQEFAAOBjQAwgYkCgYEA5zbNbHIYIkGGJ3RGdRKkYmF4gOorv5eDuUKT 8 | Vtuu3VvxrpOWvwnFV+NY0LgqkQSMMyVzodJE3SUuwQTUHPXXY5784vnkFqzPRx6b 9 | HgPxKz7XfwQjEBTafQTMmOeYI8wFIOIHY5i0RWR+gxDbh/D5TXuUqScOOqR47vSp 10 | IbUH+ncCAwEAATANBgkqhkiG9w0BAQUFAAOBgQDP9PE53utRqocZpFRxrhL+4vcI 11 | vlQd3XonE7vMJwdl9FUW7QRMon19dpYKQ6LTCOBA4ZCwh2z/Om2X97zogMLKFPcE 12 | LVqxzBVlrzuqAgOErEalYv9pCBzoFHGUHP6kTAUOCsoO9ZdQWgA6YkK2hYRSlLNX 13 | Z0wHIHGaHe5xnFFpyQ== 14 | -----END CERTIFICATE----- 15 | -------------------------------------------------------------------------------- /oidc_example/rp3/certs/server.crt: -------------------------------------------------------------------------------- 1 | -----BEGIN CERTIFICATE----- 2 | MIICGTCCAYICCQCIs8fF+AnxUzANBgkqhkiG9w0BAQUFADBRMQswCQYDVQQGEwJT 3 | RTETMBEGA1UECBMKU29tZS1TdGF0ZTEMMAoGA1UEChMDVW1VMQwwCgYDVQQLEwNJ 4 | VFMxETAPBgNVBAMTCHJvaGUwMDAyMB4XDTExMDkwNzA4MjMwOFoXDTEyMDkwNjA4 5 | MjMwOFowUTELMAkGA1UEBhMCU0UxEzARBgNVBAgTClNvbWUtU3RhdGUxDDAKBgNV 6 | BAoTA1VtVTEMMAoGA1UECxMDSVRTMREwDwYDVQQDEwhyb2hlMDAwMjCBnzANBgkq 7 | hkiG9w0BAQEFAAOBjQAwgYkCgYEA5zbNbHIYIkGGJ3RGdRKkYmF4gOorv5eDuUKT 8 | Vtuu3VvxrpOWvwnFV+NY0LgqkQSMMyVzodJE3SUuwQTUHPXXY5784vnkFqzPRx6b 9 | HgPxKz7XfwQjEBTafQTMmOeYI8wFIOIHY5i0RWR+gxDbh/D5TXuUqScOOqR47vSp 10 | IbUH+ncCAwEAATANBgkqhkiG9w0BAQUFAAOBgQDP9PE53utRqocZpFRxrhL+4vcI 11 | vlQd3XonE7vMJwdl9FUW7QRMon19dpYKQ6LTCOBA4ZCwh2z/Om2X97zogMLKFPcE 12 | LVqxzBVlrzuqAgOErEalYv9pCBzoFHGUHP6kTAUOCsoO9ZdQWgA6YkK2hYRSlLNX 13 | Z0wHIHGaHe5xnFFpyQ== 14 | -----END CERTIFICATE----- 15 | -------------------------------------------------------------------------------- /.github/workflows/python-test.yml: -------------------------------------------------------------------------------- 1 | name: Run tests and quality control 2 | 3 | on: 4 | push: 5 | branches: [ master ] 6 | pull_request: 7 | branches: [ master ] 8 | schedule: 9 | - cron: '15 6 * * *' 10 | 11 | jobs: 12 | build: 13 | 14 | runs-on: ubuntu-latest 15 | strategy: 16 | max-parallel: 4 17 | matrix: 18 | python: [3.7, 3.8, 3.9, "3.10", "3.11"] 19 | 20 | steps: 21 | - uses: actions/checkout@v3 22 | - name: Set up Python 23 | uses: actions/setup-python@v4 24 | with: 25 | python-version: ${{ matrix.python }} 26 | - name: Install dependencies 27 | run: | 28 | python -m pip install --upgrade pip 29 | pip install tox 30 | - name: Run tests 31 | run: | 32 | tox 33 | - name: Upload Coverage to Codecov 34 | uses: codecov/codecov-action@v3 35 | -------------------------------------------------------------------------------- /oidc_example/op3/certification/server.crt: -------------------------------------------------------------------------------- 1 | -----BEGIN CERTIFICATE----- 2 | MIICGTCCAYICCQCIs8fF+AnxUzANBgkqhkiG9w0BAQUFADBRMQswCQYDVQQGEwJT 3 | RTETMBEGA1UECBMKU29tZS1TdGF0ZTEMMAoGA1UEChMDVW1VMQwwCgYDVQQLEwNJ 4 | VFMxETAPBgNVBAMTCHJvaGUwMDAyMB4XDTExMDkwNzA4MjMwOFoXDTEyMDkwNjA4 5 | MjMwOFowUTELMAkGA1UEBhMCU0UxEzARBgNVBAgTClNvbWUtU3RhdGUxDDAKBgNV 6 | BAoTA1VtVTEMMAoGA1UECxMDSVRTMREwDwYDVQQDEwhyb2hlMDAwMjCBnzANBgkq 7 | hkiG9w0BAQEFAAOBjQAwgYkCgYEA5zbNbHIYIkGGJ3RGdRKkYmF4gOorv5eDuUKT 8 | Vtuu3VvxrpOWvwnFV+NY0LgqkQSMMyVzodJE3SUuwQTUHPXXY5784vnkFqzPRx6b 9 | HgPxKz7XfwQjEBTafQTMmOeYI8wFIOIHY5i0RWR+gxDbh/D5TXuUqScOOqR47vSp 10 | IbUH+ncCAwEAATANBgkqhkiG9w0BAQUFAAOBgQDP9PE53utRqocZpFRxrhL+4vcI 11 | vlQd3XonE7vMJwdl9FUW7QRMon19dpYKQ6LTCOBA4ZCwh2z/Om2X97zogMLKFPcE 12 | LVqxzBVlrzuqAgOErEalYv9pCBzoFHGUHP6kTAUOCsoO9ZdQWgA6YkK2hYRSlLNX 13 | Z0wHIHGaHe5xnFFpyQ== 14 | -----END CERTIFICATE----- 15 | -------------------------------------------------------------------------------- /doc/contrib/install.rst: -------------------------------------------------------------------------------- 1 | .. _install: 2 | 3 | Development Install 4 | ################### 5 | 6 | Firstly, fork_ the project and get a local copy with: 7 | 8 | :: 9 | 10 | $ git clone git@github.com:/pyoidc.git 11 | 12 | .. _fork: https://github.com/CZ-NIC/pyoidc/issues#fork-destination-box 13 | 14 | PyOIDC supports Python 3. 15 | 16 | Installation via a `virtual environment`_ is **highly** recommended. 17 | 18 | .. _virtual environment: http://docs.python-guide.org/en/latest/dev/virtualenvs/ 19 | 20 | Install dependencies (using Pip_) via: 21 | 22 | .. _Pip: pip.pypa.io/en/stable/installing/ 23 | 24 | :: 25 | 26 | $ make install 27 | 28 | .. Note:: The dependencies will require that you compile your Python source code 29 | with byte-compiling. This means avoiding the ``-B`` option and 30 | not setting ``PYTHONDONTWRITEBYTECODE``. 31 | -------------------------------------------------------------------------------- /oidc_example/op2/sp_cert/localhost.crt: -------------------------------------------------------------------------------- 1 | -----BEGIN CERTIFICATE----- 2 | MIICIzCCAYwCAXcwDQYJKoZIhvcNAQELBQAwWjELMAkGA1UEBhMCc2UxCzAJBgNV 3 | BAgTAmFjMQ0wCwYDVQQHEwRVbWVhMQwwCgYDVQQKEwNJVFMxDTALBgNVBAsTBERJ 4 | UkcxEjAQBgNVBAMTCWxvY2FsaG9zdDAeFw0xNDAzMTkxNzE5MzlaFw0yNDAzMTYx 5 | NzE5MzlaMFoxCzAJBgNVBAYTAnNlMQswCQYDVQQIEwJhYzENMAsGA1UEBxMEVW1l 6 | YTEMMAoGA1UEChMDSVRTMQ0wCwYDVQQLEwRESVJHMRIwEAYDVQQDEwlsb2NhbGhv 7 | c3QwgZ8wDQYJKoZIhvcNAQEBBQADgY0AMIGJAoGBALswQbMz6dKL4FEKu+O3GYDf 8 | aniyxcfizsHaiehuh5jufrlGZw/zMMttteTRhpjtTFN2/R68d1kh8pmO+HV7u+53 9 | h+C33I178ZsOquSovGe1AWmzDzp2j6CRdQYdZaMCc4YxtwEXs7570YmnPLKhz8aH 10 | diQRHhtx1cqdPPStkz67AgMBAAEwDQYJKoZIhvcNAQELBQADgYEApQ5znEdhsJBs 11 | +ew81X9pkUKZ/ruu/p2OPfYmKzdFnK6mq9bF5CEIaIkvbSJjpECicmGCNw9ATJXy 12 | lnwFLnfxw6d+YGi4XPuo64NSG00ycnMSTMJ8OR5ForcF89v72BLRDYo6yXeNDw72 13 | 2ul0lPzH54CNQzsm4N/5w5iStnOT1TQ= 14 | -----END CERTIFICATE----- 15 | -------------------------------------------------------------------------------- /oidc_example/op2/client_mgr.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | import json 3 | 4 | from oic.utils.client_management import CDB 5 | 6 | if __name__ == '__main__': 7 | import argparse 8 | 9 | parser = argparse.ArgumentParser() 10 | parser.add_argument('-l', dest='list', action='store_true') 11 | parser.add_argument('-a', dest='add') 12 | parser.add_argument('-d', dest='delete') 13 | parser.add_argument(dest="config") 14 | args = parser.parse_args() 15 | 16 | # Client data base 17 | cdb = CDB(args.config) 18 | 19 | if args.list: 20 | for key, val in cdb.items(): 21 | print('{}:{}'.format(key, val['redirect_uris'])) 22 | 23 | if args.add: 24 | fp = open(args.add) 25 | spec = json.load(fp) 26 | cli_info = cdb.create(**spec) 27 | print(cli_info) 28 | 29 | if args.delete: 30 | del cdb[args.delete] 31 | -------------------------------------------------------------------------------- /src/oic/utils/authn/ldap_member.py: -------------------------------------------------------------------------------- 1 | import logging 2 | 3 | from oic.utils.userinfo.ldap_info import UserInfoLDAP 4 | 5 | __author__ = "haho0032" 6 | 7 | logger = logging.getLogger(__name__) 8 | 9 | 10 | class UserLDAPMemberValidation(UserInfoLDAP): 11 | def __init__(self, verify_attr=None, verify_attr_valid=None, **kwargs): 12 | UserInfoLDAP.__init__(self, **kwargs) 13 | self.verify_attr = verify_attr 14 | self.verify_attr_valid = verify_attr_valid 15 | 16 | def __call__(self, userid, **kwargs): 17 | result = UserInfoLDAP.__call__(self, userid, None, False) 18 | if self.verify_attr in result: 19 | for field in result[self.verify_attr]: 20 | if field in self.verify_attr_valid: 21 | return True 22 | logger.warning(userid + "tries to use the service with the values " + result) 23 | return False 24 | -------------------------------------------------------------------------------- /tox.ini: -------------------------------------------------------------------------------- 1 | [tox] 2 | envlist = py{37,38,39,310,311},docs,quality 3 | skip_missing_interpreters = True 4 | 5 | [testenv] 6 | commands = 7 | py.test --cov-report=xml --cov=oic {posargs:tests} 8 | extras = testing 9 | deps = 10 | pytest-cov 11 | 12 | [testenv:docs] 13 | whitelist_externals = make 14 | extras = docs 15 | commands = sphinx-build -b html doc/ doc/_build/html -W 16 | 17 | [testenv:quality] 18 | ignore_errors = True 19 | deps = twine 20 | extras = 21 | quality 22 | types 23 | commands = 24 | isort --diff --check-only src/ tests/ 25 | pylama src/ tests/ 26 | pydocstyle src 27 | mypy --config-file mypy.ini src/ tests/ 28 | black src/ tests/ --check -t py36 29 | python3 setup.py --quiet sdist 30 | bandit -a file -r src/ oauth_example/ oidc_example/ 31 | twine check dist/* 32 | 33 | [pep8] 34 | max-line-length=100 35 | 36 | [pytest] 37 | addopts = --color=yes 38 | -------------------------------------------------------------------------------- /runOpRp.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # Check if running on mac 4 | if [ "$(uname)" = "Darwin" ]; then 5 | # Check so the boot2docker vm is running 6 | if [ "$(boot2docker status)" != "running" ]; then 7 | boot2docker start 8 | fi 9 | boot2docker shellinit 10 | HOST_IP=$(boot2docker ip) 11 | else 12 | # if running on linux 13 | if [ "$(id -u)" -ne 0 ]; then 14 | sudo="sudo" 15 | fi 16 | HOST_IP=$(ifconfig | grep 'inet addr:'| grep -v '127.0.0.1' | grep -v '172.17' | cut -d: -f2 | awk '{ print $1}' | head -1) 17 | fi 18 | 19 | echo "HOST IP: " "${HOST_IP}" 20 | 21 | ${sudo} docker run -d \ 22 | --name op \ 23 | -p 8092:8092 \ 24 | -e HOST_IP="${HOST_IP}" \ 25 | -i -t \ 26 | itsdirg/pyoidc_example_op 27 | 28 | ${sudo} docker run -d \ 29 | --name rp \ 30 | -p 8666:8666 \ 31 | -e HOST_IP="${HOST_IP}" \ 32 | -i -t \ 33 | itsdirg/pyoidc_example_rp 34 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # IntelliJ project folder 2 | .idea 3 | 4 | # Other dev-env folders and files 5 | .vagrant 6 | .vscode 7 | Vagrantfile 8 | Pipfile 9 | Pipfile.lock 10 | 11 | # setup.py-related folders and files 12 | build 13 | dist 14 | *.egg-info 15 | *.egg 16 | *.eggs 17 | 18 | # Compiled files 19 | *.pyc 20 | *.pyo 21 | 22 | # Logs 23 | *.log 24 | *.log.* 25 | 26 | # Tox testing 27 | .tox 28 | private/ 29 | tmp/ 30 | keys/ 31 | secret/ 32 | pyoidc* 33 | foo.* 34 | 35 | # Testing stuff 36 | .coverage 37 | .cache/ 38 | .pytest_cache 39 | .mypy_cache 40 | 41 | # Dynamically created doc folders 42 | doc/_build 43 | !tests/data/keys 44 | 45 | # Remaining stuff 46 | oidc_example/op1/client_db.db 47 | oidc_example/op1/oc_config.py 48 | oidc_example/rp3/conf.py 49 | oidc_example/rp3/modules/ 50 | oauth_example/rp/modules/ 51 | oauth_example/as/modules/ 52 | oauth_example/as/static/jwks.json 53 | oauth_example/as/client_db.* 54 | update 55 | -------------------------------------------------------------------------------- /tests/rsa_enc: -------------------------------------------------------------------------------- 1 | -----BEGIN RSA PRIVATE KEY----- 2 | MIICXAIBAAKBgQCIc+q8Y+q4qjw/SMMRzwyfJ7IvIQ7Q3WSE+x2Z61bwh4luI65H 3 | zARW3X9NkD8kHAh/Vxaz/wS86FXALTGygoKQ8ETOC7WNz/4g/z9VHowcZPO/jwdT 4 | zfo+2nK3Xg9qSAWF6x5jnSaFsuvcotcrxpke9gVCq+MkEDDzeKOAiHJnNwIDAQAB 5 | AoGANM43HyTDpycqHYt5AiFQTx87k4WFiErFJblQYUpz4K1y/86LGXnYjA03wLp7 6 | 1OuMVktLm+iq2rhGxxI2U1CyWfgWnJHfvZgojjcUHd4fRm5U16fzCKgnl3ZtC0fG 7 | mxmpbq1f+h1nhgK4cNi6s3boz+GfdrdT5MvshRRT8z/zINECQQC41xgat9yRAHO2 8 | y9+fqrnOQlSL/uObm8NqmuKPiqJRsqBqGPYuDyeJlx1I3mxkCL7Q/WqmfjFlqIeB 9 | Zp4BnmyFAkEAvPwBzUFylaKLwUVcZmGXbHzo9G4oauRe6Vj+um7eXinz1D+X5ReJ 10 | BHa6Su+4xHOE9myEMZ6P80F+BnLuepH/iwJAMRbGywm7ZTMGiCx61k+kCvgotgla 11 | b1AdxOkfdFmwJBxZZ/P7JV5W9L6SQ3D2vlZoPt6efVsUSVhJrH8RRYLKdQJBALzG 12 | HiahkYvW6jMMzdeW9GLyAuDmiIj9xbDhrNEdnhIBZgZF37x/XeaPklb4TmAt5Esi 13 | 6omGEdSzPufCNmVJITECQEDD6V9ojDly//wsTTPykgiXmLiETAH9Ff6I6Zp2g5d7 14 | VcNvzfoQ3BegXbYuJzSFanCWNk/2+9GPptlcdMbGjo4= 15 | -----END RSA PRIVATE KEY----- -------------------------------------------------------------------------------- /tests/data/keys/rsa.key: -------------------------------------------------------------------------------- 1 | -----BEGIN RSA PRIVATE KEY----- 2 | MIICXAIBAAKBgQD6vqn19W/VB215DBADRakfPmCtFBf8/+YyhGqixWIwDiEl/L6L 3 | w5HKZCUPVgrC0ADhJfvAbn4fte5MWBCTkqgepKL3BySMA0LMaBF12pbHlPSUbmQG 4 | BJmTX4NNXuUel6TbPYJAU2Nh5Nan0Mb7Bmb8QpFvS0Hw7qZRW8y2eIttfwIDAQAB 5 | AoGBAJVf9FxkRKUB8cOE3h006JWGUY2KROghgn9hxy0ErYO3RyQcN1+HuFh75GAI 6 | gAyiYYO/XwS6TkSR2057wBRJ8ABzcL3+v5g+16Vbh0BjXVE+cv1WGdNGujyzl6ji 7 | jlyF4cb6tXDyqWTLkMAtV20NfO/CGsfii6YEkZb2P90usthRAkEA/oG7a9EvQ7eR 8 | gSEqppzW7KCwidPjnZTr/ROIZQU33nwkIJ0ElTjMNYKP8DerSuixR9skw2ZY8Q8I 9 | 1PTBnocHwwJBAPw3SAQYwxZwQMu1trVPMNOGIbSY4rQlMZGXrCZSu/TnozczFLA8 10 | qNM84g5veyJOzHKmYkIsMG1gwg5VNniG45UCQF6SlLOW0upl70K9sVyiUVcyywcc 11 | Xqty6FJtjLSFQOKC3OXlkwtkRLXpo1UPSq6WUzIxY7LceFZzUMPZg41F/gMCQHNr 12 | POqbBlPzZMOUUZthNP/nhu8lc8Fqr+dnmGElRVxK0JdHKfWInN2mI/DlNV064Dar 13 | S5XqsPKs78EtX7MCT40CQFQZiry8m7ROubOU4+HDG9o1w9zcKXCkmbD9hBCGvTAj 14 | BQNuGE0DtC6FEWTs8bXybLM5yBRq1XiKLdmi5N+3n4g= 15 | -----END RSA PRIVATE KEY----- 16 | -------------------------------------------------------------------------------- /oauth_example/as/certs/server.key: -------------------------------------------------------------------------------- 1 | -----BEGIN RSA PRIVATE KEY----- 2 | MIICXAIBAAKBgQDnNs1schgiQYYndEZ1EqRiYXiA6iu/l4O5QpNW267dW/Guk5a/ 3 | CcVX41jQuCqRBIwzJXOh0kTdJS7BBNQc9ddjnvzi+eQWrM9HHpseA/ErPtd/BCMQ 4 | FNp9BMyY55gjzAUg4gdjmLRFZH6DENuH8PlNe5SpJw46pHju9KkhtQf6dwIDAQAB 5 | AoGAKuSxy1KHQ6OgPaWGhKWGtXGbp17J6usy1qWRK+XpVMt/1IEw0BQB9kII8f+Y 6 | dfq//6UNBJI7kEMbn1dD+nNpF4ncO9QWHE5oqacHgaZOl6+MF3ePy8aXkADhwiel 7 | L7CtZjhwbcjGt5PI6AIcpFfmBAbu5Pf4gidr6bR+MoJGlhECQQDzfMaRqruJkqsz 8 | Z5b9boIr08orx1xPoHTmE5g0ET9+UJy/BBgx7DNv+AQhJ2UC1ZaKcgqwetOwJhQs 9 | u8Cbrct9AkEA8xiQSwqlM7ltpNl6L2VvSxzTd897it+FJElXbD6u80RvzMuo3Xw3 10 | +M+F0kDobM4vsyBuZRw418/yOpnOv8x4AwJATj5WgRDgWwEqysYLGz2bzwGsAg16 11 | eIwThKvfSTwRr0GwXSGvtLs2fFCy4wSJzTNdwPeMv9F4nS5fZVCgQGbE8QJAMZBG 12 | iyZGfH9H/Z5hrRwvTs83xmvFMpFUIgvaCTXWkb7YVJcJfO8AsngNPssBGH4Jd6ob 13 | F/5jEI1TQ+NsJerYZQJBAJdqDlnPQyqek4kdBvwh2hYo9EwOrgOchmruMOeP5lE6 14 | 2TLIyjYC3uVMPJj+ESayVcAMrgj4Enk4qh/WKVeMJ7c= 15 | -----END RSA PRIVATE KEY----- 16 | -------------------------------------------------------------------------------- /oidc_example/op1/certs/server.key: -------------------------------------------------------------------------------- 1 | -----BEGIN RSA PRIVATE KEY----- 2 | MIICXAIBAAKBgQDnNs1schgiQYYndEZ1EqRiYXiA6iu/l4O5QpNW267dW/Guk5a/ 3 | CcVX41jQuCqRBIwzJXOh0kTdJS7BBNQc9ddjnvzi+eQWrM9HHpseA/ErPtd/BCMQ 4 | FNp9BMyY55gjzAUg4gdjmLRFZH6DENuH8PlNe5SpJw46pHju9KkhtQf6dwIDAQAB 5 | AoGAKuSxy1KHQ6OgPaWGhKWGtXGbp17J6usy1qWRK+XpVMt/1IEw0BQB9kII8f+Y 6 | dfq//6UNBJI7kEMbn1dD+nNpF4ncO9QWHE5oqacHgaZOl6+MF3ePy8aXkADhwiel 7 | L7CtZjhwbcjGt5PI6AIcpFfmBAbu5Pf4gidr6bR+MoJGlhECQQDzfMaRqruJkqsz 8 | Z5b9boIr08orx1xPoHTmE5g0ET9+UJy/BBgx7DNv+AQhJ2UC1ZaKcgqwetOwJhQs 9 | u8Cbrct9AkEA8xiQSwqlM7ltpNl6L2VvSxzTd897it+FJElXbD6u80RvzMuo3Xw3 10 | +M+F0kDobM4vsyBuZRw418/yOpnOv8x4AwJATj5WgRDgWwEqysYLGz2bzwGsAg16 11 | eIwThKvfSTwRr0GwXSGvtLs2fFCy4wSJzTNdwPeMv9F4nS5fZVCgQGbE8QJAMZBG 12 | iyZGfH9H/Z5hrRwvTs83xmvFMpFUIgvaCTXWkb7YVJcJfO8AsngNPssBGH4Jd6ob 13 | F/5jEI1TQ+NsJerYZQJBAJdqDlnPQyqek4kdBvwh2hYo9EwOrgOchmruMOeP5lE6 14 | 2TLIyjYC3uVMPJj+ESayVcAMrgj4Enk4qh/WKVeMJ7c= 15 | -----END RSA PRIVATE KEY----- 16 | -------------------------------------------------------------------------------- /oidc_example/op2/certs/server.key: -------------------------------------------------------------------------------- 1 | -----BEGIN RSA PRIVATE KEY----- 2 | MIICXAIBAAKBgQDnNs1schgiQYYndEZ1EqRiYXiA6iu/l4O5QpNW267dW/Guk5a/ 3 | CcVX41jQuCqRBIwzJXOh0kTdJS7BBNQc9ddjnvzi+eQWrM9HHpseA/ErPtd/BCMQ 4 | FNp9BMyY55gjzAUg4gdjmLRFZH6DENuH8PlNe5SpJw46pHju9KkhtQf6dwIDAQAB 5 | AoGAKuSxy1KHQ6OgPaWGhKWGtXGbp17J6usy1qWRK+XpVMt/1IEw0BQB9kII8f+Y 6 | dfq//6UNBJI7kEMbn1dD+nNpF4ncO9QWHE5oqacHgaZOl6+MF3ePy8aXkADhwiel 7 | L7CtZjhwbcjGt5PI6AIcpFfmBAbu5Pf4gidr6bR+MoJGlhECQQDzfMaRqruJkqsz 8 | Z5b9boIr08orx1xPoHTmE5g0ET9+UJy/BBgx7DNv+AQhJ2UC1ZaKcgqwetOwJhQs 9 | u8Cbrct9AkEA8xiQSwqlM7ltpNl6L2VvSxzTd897it+FJElXbD6u80RvzMuo3Xw3 10 | +M+F0kDobM4vsyBuZRw418/yOpnOv8x4AwJATj5WgRDgWwEqysYLGz2bzwGsAg16 11 | eIwThKvfSTwRr0GwXSGvtLs2fFCy4wSJzTNdwPeMv9F4nS5fZVCgQGbE8QJAMZBG 12 | iyZGfH9H/Z5hrRwvTs83xmvFMpFUIgvaCTXWkb7YVJcJfO8AsngNPssBGH4Jd6ob 13 | F/5jEI1TQ+NsJerYZQJBAJdqDlnPQyqek4kdBvwh2hYo9EwOrgOchmruMOeP5lE6 14 | 2TLIyjYC3uVMPJj+ESayVcAMrgj4Enk4qh/WKVeMJ7c= 15 | -----END RSA PRIVATE KEY----- 16 | -------------------------------------------------------------------------------- /oidc_example/rp3/certs/server.key: -------------------------------------------------------------------------------- 1 | -----BEGIN RSA PRIVATE KEY----- 2 | MIICXAIBAAKBgQDnNs1schgiQYYndEZ1EqRiYXiA6iu/l4O5QpNW267dW/Guk5a/ 3 | CcVX41jQuCqRBIwzJXOh0kTdJS7BBNQc9ddjnvzi+eQWrM9HHpseA/ErPtd/BCMQ 4 | FNp9BMyY55gjzAUg4gdjmLRFZH6DENuH8PlNe5SpJw46pHju9KkhtQf6dwIDAQAB 5 | AoGAKuSxy1KHQ6OgPaWGhKWGtXGbp17J6usy1qWRK+XpVMt/1IEw0BQB9kII8f+Y 6 | dfq//6UNBJI7kEMbn1dD+nNpF4ncO9QWHE5oqacHgaZOl6+MF3ePy8aXkADhwiel 7 | L7CtZjhwbcjGt5PI6AIcpFfmBAbu5Pf4gidr6bR+MoJGlhECQQDzfMaRqruJkqsz 8 | Z5b9boIr08orx1xPoHTmE5g0ET9+UJy/BBgx7DNv+AQhJ2UC1ZaKcgqwetOwJhQs 9 | u8Cbrct9AkEA8xiQSwqlM7ltpNl6L2VvSxzTd897it+FJElXbD6u80RvzMuo3Xw3 10 | +M+F0kDobM4vsyBuZRw418/yOpnOv8x4AwJATj5WgRDgWwEqysYLGz2bzwGsAg16 11 | eIwThKvfSTwRr0GwXSGvtLs2fFCy4wSJzTNdwPeMv9F4nS5fZVCgQGbE8QJAMZBG 12 | iyZGfH9H/Z5hrRwvTs83xmvFMpFUIgvaCTXWkb7YVJcJfO8AsngNPssBGH4Jd6ob 13 | F/5jEI1TQ+NsJerYZQJBAJdqDlnPQyqek4kdBvwh2hYo9EwOrgOchmruMOeP5lE6 14 | 2TLIyjYC3uVMPJj+ESayVcAMrgj4Enk4qh/WKVeMJ7c= 15 | -----END RSA PRIVATE KEY----- 16 | -------------------------------------------------------------------------------- /oidc_example/op2/sp_cert/localhost.key: -------------------------------------------------------------------------------- 1 | -----BEGIN RSA PRIVATE KEY----- 2 | MIICXAIBAAKBgQC7MEGzM+nSi+BRCrvjtxmA32p4ssXH4s7B2onoboeY7n65RmcP 3 | 8zDLbbXk0YaY7UxTdv0evHdZIfKZjvh1e7vud4fgt9yNe/GbDqrkqLxntQFpsw86 4 | do+gkXUGHWWjAnOGMbcBF7O+e9GJpzyyoc/Gh3YkER4bcdXKnTz0rZM+uwIDAQAB 5 | AoGAF8uTnn8r6xri4gp2RgVBlbNQ6pT3NWisldH9E/HxBMzUiSLc+RcWDdYdeD/2 6 | VzxbJoUKujyFA2ygVUrqZKzc/8TBc0VAljjKtcoj4w3DshjzYFXa7zJ1b3D0NYoA 7 | FARFHJL45o/SGSjBkMSqAs2jSVp2zebbQwsNHzH8Co7/lHkCQQDfXt1NNjLMG8FA 8 | qRzckIf5ZYLUkKUt+2p2+oFMjSctkwvCcU86yWIJIqpSFoiwXHWieOuNj8tbdwp+ 9 | sFSBvUBdAkEA1ohVKfCUecksOTLBgkbboEbWFY1FEdzTe77QN6UDRcUSuRk2za89 10 | aqQCU0/o3usYqzbq9x8EVO2xPlC/6aRp9wJACm0QIVF9T3+gJpd+LGFmp2Jwl+Fk 11 | hF/u2TzaGiugf4UUkG/fhdvugoSmtIwutySprPq96+6hTFSjHcS3PRMLVQJAF2o/ 12 | 6Vjxy60SX5fTmRWEySSSHz4RCNcSWhJEhZ5EEixlU8CVw7NZv7bw7gjtwM7mHWHc 13 | GWbQQTjEPKnxmF2ylwJBALqeTnvDZyqD3yJcRH5mRlCIZZOo90f9VumXBDyFG5ya 14 | /ozazEN8n18J/Y7RkTl59oYnJn7WeOsYvFSbm3VUiaQ= 15 | -----END RSA PRIVATE KEY----- 16 | -------------------------------------------------------------------------------- /oidc_example/op3/certification/server.key: -------------------------------------------------------------------------------- 1 | -----BEGIN RSA PRIVATE KEY----- 2 | MIICXAIBAAKBgQDnNs1schgiQYYndEZ1EqRiYXiA6iu/l4O5QpNW267dW/Guk5a/ 3 | CcVX41jQuCqRBIwzJXOh0kTdJS7BBNQc9ddjnvzi+eQWrM9HHpseA/ErPtd/BCMQ 4 | FNp9BMyY55gjzAUg4gdjmLRFZH6DENuH8PlNe5SpJw46pHju9KkhtQf6dwIDAQAB 5 | AoGAKuSxy1KHQ6OgPaWGhKWGtXGbp17J6usy1qWRK+XpVMt/1IEw0BQB9kII8f+Y 6 | dfq//6UNBJI7kEMbn1dD+nNpF4ncO9QWHE5oqacHgaZOl6+MF3ePy8aXkADhwiel 7 | L7CtZjhwbcjGt5PI6AIcpFfmBAbu5Pf4gidr6bR+MoJGlhECQQDzfMaRqruJkqsz 8 | Z5b9boIr08orx1xPoHTmE5g0ET9+UJy/BBgx7DNv+AQhJ2UC1ZaKcgqwetOwJhQs 9 | u8Cbrct9AkEA8xiQSwqlM7ltpNl6L2VvSxzTd897it+FJElXbD6u80RvzMuo3Xw3 10 | +M+F0kDobM4vsyBuZRw418/yOpnOv8x4AwJATj5WgRDgWwEqysYLGz2bzwGsAg16 11 | eIwThKvfSTwRr0GwXSGvtLs2fFCy4wSJzTNdwPeMv9F4nS5fZVCgQGbE8QJAMZBG 12 | iyZGfH9H/Z5hrRwvTs83xmvFMpFUIgvaCTXWkb7YVJcJfO8AsngNPssBGH4Jd6ob 13 | F/5jEI1TQ+NsJerYZQJBAJdqDlnPQyqek4kdBvwh2hYo9EwOrgOchmruMOeP5lE6 14 | 2TLIyjYC3uVMPJj+ESayVcAMrgj4Enk4qh/WKVeMJ7c= 15 | -----END RSA PRIVATE KEY----- 16 | -------------------------------------------------------------------------------- /oidc_example/simple_op/src/provider/authn/__init__.py: -------------------------------------------------------------------------------- 1 | import importlib 2 | from oic.utils.authn.user import UserAuthnMethod 3 | 4 | __author__ = 'regu0004' 5 | 6 | class AuthnModule(UserAuthnMethod): 7 | 8 | # override in subclass specifying suitable url endpoint to POST user input 9 | url_endpoint = "/verify" 10 | FAILED_AUTHN = (None, True) 11 | 12 | def __call__(self, *args, **kwargs): 13 | """ 14 | Display user interaction. 15 | :return: instance of oic.utils.http_util.Response 16 | """ 17 | raise NotImplementedError() 18 | 19 | def verify(self, *args, **kwargs): 20 | """ 21 | Callback to verify user input 22 | :return: username of the authenticated user 23 | """ 24 | raise NotImplementedError() 25 | 26 | 27 | def make_cls_from_name(name): 28 | module_name, cls_name = name.rsplit(".", 1) 29 | cls = getattr(importlib.import_module(module_name), cls_name) 30 | return cls 31 | -------------------------------------------------------------------------------- /doc/examples/docker.rst: -------------------------------------------------------------------------------- 1 | Run Example with Docker 2 | ======================= 3 | 4 | So, you just want to see how it works? 5 | 6 | In order to get started, you'll need to install Docker_. Once installed, please 7 | run the `runOpRp.sh`_ script in the root of the repository. 8 | 9 | .. _Docker: https://docs.docker.com/get-docker/ 10 | .. _runOpRp.sh: https://github.com/CZ-NIC/pyoidc/blob/master/runOpRp.sh 11 | 12 | This will set up the following: 13 | 14 | * An OP listening at port ``localhost:8088``. 15 | * An RP listening at port ``localhost:8666``. 16 | 17 | You can check this is the case by running: 18 | 19 | :: 20 | 21 | $ docker ps -a 22 | 23 | Go ahead and visit the RP at ``localhost:8666``. You can now login with the 24 | following UID: 25 | 26 | * ``username@localhost:8093`` 27 | 28 | You will then be re-directed to the OP, where you will be asked to enter your 29 | username and password. Please use the following: 30 | 31 | * ``username: diana`` 32 | * ``password: krall`` 33 | -------------------------------------------------------------------------------- /tests/not_yet_test_x_device_flow.py: -------------------------------------------------------------------------------- 1 | from oic.extension.device_flow import AuthorizationRequest 2 | from oic.extension.device_flow import DeviceFlowClient 3 | from oic.extension.device_flow import DeviceFlowServer 4 | from oic.extension.device_flow import TokenRequest 5 | from oic.oauth2 import Client 6 | from oic.oauth2 import Server 7 | 8 | 9 | def test_device_flow(): 10 | _client = Client() 11 | cli = DeviceFlowClient(_client) 12 | 13 | _server = Server() 14 | srv = DeviceFlowServer(_server) 15 | 16 | # init 17 | req = AuthorizationRequest( 18 | client_id=cli.host.client_id, response_type="device_code" 19 | ) 20 | 21 | resp = srv.device_endpoint(req) 22 | 23 | # Polling 24 | 25 | req2 = TokenRequest( 26 | grant_type="urn:ietf:params:oauth:grant-type:device_code", 27 | device_code=resp["device_dode"], 28 | client_id=cli.host.client_id, 29 | ) 30 | 31 | resp = srv.token_endpoint(req2) 32 | 33 | # Authorization Pending 34 | 35 | # Do Authorization 36 | -------------------------------------------------------------------------------- /tests/test_stateless.py: -------------------------------------------------------------------------------- 1 | from oic.utils.stateless import StateLess 2 | 3 | __author__ = "roland" 4 | 5 | 6 | def _eq(l1, l2): 7 | return set(l1) == set(l2) 8 | 9 | 10 | def test_access_code(): 11 | keys = {"oct": ["symmetric key123"]} # keylength 16 bytes=128 bits 12 | st = StateLess(keys, enc_alg="A128KW", enc_method="A128CBC-HS256") 13 | con = st.create_authz_session("subject", {"redirect_uri": "https://example.com"}) 14 | tok = st.get_token(con) 15 | 16 | _info = st[tok] 17 | assert _eq(_info.keys(), ["typ", "aud", "val", "sub"]) 18 | assert _info["sub"] == "subject" 19 | assert _info["typ"] == "code" 20 | assert _info["aud"] == "https://example.com" 21 | 22 | 23 | def test_update_to_access_token(): 24 | keys = {"oct": ["symmetric key123"]} 25 | st = StateLess(keys, enc_alg="A128KW", enc_method="A128CBC-HS256") 26 | tok = st.create_authz_session("subject", {"redirect_uri": "https://example.com"}) 27 | assert tok["aud"] == "https://example.com" 28 | assert tok["sub"] == "subject" 29 | -------------------------------------------------------------------------------- /doc/index.rst: -------------------------------------------------------------------------------- 1 | A Python OpenID Connect implementation 2 | ====================================== 3 | 4 | This is a complete implementation of OpenID Connect as specified in the `OpenID 5 | Connect Core specification`_. And as a side effect, a complete implementation 6 | of OAuth2.0 too. 7 | 8 | .. _OpenID Connect Core specification: http://openid.net/specs/openid-connect-core-1_0.html 9 | 10 | Getting a copy is simple with Pip_: 11 | 12 | .. _Pip: http://pip.pypa.io/ 13 | 14 | :: 15 | 16 | $ pip install oic 17 | 18 | .. toctree:: 19 | :maxdepth: 1 20 | 21 | examples/op 22 | examples/rp 23 | examples/cookbook 24 | examples/docker 25 | examples/tls 26 | 27 | contrib/install 28 | contrib/settings 29 | contrib/testing 30 | contrib/documentation 31 | 32 | .. raw:: html 33 | 34 | 35 | Fork me on GitHub 36 | 37 | -------------------------------------------------------------------------------- /doc/contrib/testing.rst: -------------------------------------------------------------------------------- 1 | .. _testing: 2 | 3 | Testing 4 | ####### 5 | 6 | Using Pytest 7 | ------------ 8 | 9 | Please make sure you have already reviewed the :ref:`install`. 10 | 11 | PyOIDC uses Pytest_ for testing. 12 | 13 | .. _Pytest: https://doc.pytest.org/ 14 | 15 | You can install it along with the rest of the test dependencies via Pip_: 16 | 17 | .. _Pip: pip.pypa.io/en/stable/installing/ 18 | 19 | :: 20 | 21 | $ pip install .[testing] 22 | 23 | Now, you can run the tests by simply invoking ``py.test``: 24 | 25 | :: 26 | 27 | $ py.test 28 | 29 | Using Tox 30 | --------- 31 | 32 | PyOIDC also uses Tox_ for testing. This is to ensure PyOIDC is supported across 33 | many versions of Python. You can install it via Pip_: 34 | 35 | .. _Tox: https://tox.readthedocs.io/ 36 | .. _Pip: pip.pypa.io/en/stable/installing/ 37 | 38 | :: 39 | 40 | $ pip install tox 41 | 42 | Then, check the available environments with: 43 | 44 | :: 45 | 46 | $ tox -l 47 | 48 | Then run Tox on your chosen environment: 49 | 50 | :: 51 | 52 | $ tox -e py36 53 | -------------------------------------------------------------------------------- /oauth_example/as/htdocs/login.mako: -------------------------------------------------------------------------------- 1 | <%inherit file="root.mako" /> 2 | <%def name="title()">Log in 3 | 4 |
5 | 24 | % if logo_uri: 25 | Client logo 26 | % endif 27 | % if policy_uri: 28 | Client policy 29 | % endif 30 |
31 | -------------------------------------------------------------------------------- /oidc_example/op2/templates/base.mako: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 7 | <%block name="meta"/> 8 | 9 | <%block name="script"/> 10 | 11 | <%block name="css"/> 12 | <%block name="title"/> 13 | 14 | 15 | 16 | <%block name="header"> 17 | 18 |
19 | 20 | <%block name="headline"> 21 | 22 |
23 | 24 | 25 | ${self.body()} 26 | 27 | <%block name="footer"> 28 |
29 |
30 | 31 | 32 | 33 | 34 | -------------------------------------------------------------------------------- /oidc_example/op3/Templates/base.mako: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 7 | <%block name="meta"/> 8 | 9 | <%block name="script"/> 10 | 11 | <%block name="css"/> 12 | <%block name="title"/> 13 | 14 | 15 | 16 | <%block name="header"> 17 | 18 |
19 | 20 | <%block name="headline"> 21 | 22 |
23 | 24 | 25 | ${self.body()} 26 | 27 | <%block name="footer"> 28 |
29 |
30 | 31 | 32 | 33 | 34 | -------------------------------------------------------------------------------- /tests/data/templates/root.mako: -------------------------------------------------------------------------------- 1 | <% self.seen_css = set() %> 2 | <%def name="css_link(path, media='')" filter="trim"> 3 | % if path not in self.seen_css: 4 | 5 | % endif 6 | <% self.seen_css.add(path) %> 7 | 8 | <%def name="css()" filter="trim"> 9 | ${css_link('/css/main.css', 'screen')} 10 | 11 | <%def name="pre()" filter="trim"> 12 | 13 | <%def name="post()" filter="trim"> 14 |
15 | 18 |
19 | 20 | ## 22 | 23 | OpenID Connect provider example 24 | ${self.css()} 25 | 26 | 27 | 28 | ${pre()} 29 | ## ${comps.dict_to_table(pageargs)} 30 | ##

31 | ${next.body()} 32 | ${post()} 33 | 34 | 35 | -------------------------------------------------------------------------------- /docker/op/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM ubuntu:18.04 2 | 3 | ENV DEBIAN_FRONTEND noninteractive 4 | RUN apt-get update && apt-get install -y --no-install-recommends \ 5 | ca-certificates \ 6 | git \ 7 | gnupg \ 8 | ntp \ 9 | wget 10 | RUN wget -q https://deb.nodesource.com/setup_12.x -O - | bash - 11 | RUN apt-get update && apt-get install -y --no-install-recommends \ 12 | apache2 \ 13 | nodejs && apt-get clean && rm -rf /var/lib/apt/lists/* 14 | 15 | ENV SRCDIR /usr/local/src 16 | ENV INSTDIR node-oidc-provider 17 | ENV SUBDIR ${SRCDIR}/${INSTDIR} 18 | 19 | WORKDIR ${SRCDIR} 20 | RUN git clone --depth=1 --branch=v6.17.3 https://github.com/panva/node-oidc-provider.git 21 | WORKDIR ${INSTDIR} 22 | RUN npm install 23 | 24 | COPY docker/op/apache-ssl.conf /etc/apache2/sites-available/ssl.conf 25 | COPY docker/op/cert.pem /etc/apache2/ 26 | COPY docker/op/key.pem /etc/apache2/ 27 | 28 | RUN a2enmod headers && a2enmod ssl && a2enmod proxy && a2enmod proxy_http && a2ensite ssl 29 | 30 | COPY docker/op/run.sh ${SUBDIR}/ 31 | 32 | WORKDIR ${SUBDIR} 33 | ENTRYPOINT ["./run.sh"] 34 | -------------------------------------------------------------------------------- /oidc_example/op2/templates/root.mako: -------------------------------------------------------------------------------- 1 | <% self.seen_css = set() %> 2 | <%def name="css_link(path, media='')" filter="trim"> 3 | % if path not in self.seen_css: 4 | 5 | % endif 6 | <% self.seen_css.add(path) %> 7 | 8 | <%def name="css()" filter="trim"> 9 | ${css_link('/css/main.css', 'screen')} 10 | 11 | <%def name="pre()" filter="trim"> 12 | 13 | <%def name="post()" filter="trim"> 14 |
15 | 18 |
19 | 20 | ## 22 | 23 | OpenID Connect provider example 24 | ${self.css()} 25 | 26 | 27 | 28 | ${pre()} 29 | ## ${comps.dict_to_table(pageargs)} 30 | ##

31 | ${next.body()} 32 | ${post()} 33 | 34 | 35 | -------------------------------------------------------------------------------- /oidc_example/op3/Templates/root.mako: -------------------------------------------------------------------------------- 1 | <% self.seen_css = set() %> 2 | <%def name="css_link(path, media='')" filter="trim"> 3 | % if path not in self.seen_css: 4 | 5 | % endif 6 | <% self.seen_css.add(path) %> 7 | 8 | <%def name="css()" filter="trim"> 9 | ${css_link('/css/main.css', 'screen')} 10 | 11 | <%def name="pre()" filter="trim"> 12 | 13 | <%def name="post()" filter="trim"> 14 |
15 | 18 |
19 | 20 | ## 22 | 23 | OpenID Connect provider example 24 | ${self.css()} 25 | 26 | 27 | 28 | ${pre()} 29 | ## ${comps.dict_to_table(pageargs)} 30 | ##

31 | ${next.body()} 32 | ${post()} 33 | 34 | 35 | -------------------------------------------------------------------------------- /oidc_example/op2/htdocs/javascript_login.mako: -------------------------------------------------------------------------------- 1 | <%inherit file="root.mako" /> 2 | <%def name="title()">Log in 3 | 4 |
5 | 6 |

Log in with Javascript

7 | 8 | 15 | % if logo_uri: 16 | Client logo 17 | % endif 18 | % if policy_uri: 19 | Client policy 20 | % endif 21 |
22 | 23 | 26 | 27 | <%def name="add_js()"> 28 | 33 | 34 | -------------------------------------------------------------------------------- /oidc_example/op3/htdocs/javascript_login.mako: -------------------------------------------------------------------------------- 1 | <%inherit file="root.mako" /> 2 | <%def name="title()">Log in 3 | 4 |
5 | 6 |

Log in with Javascript

7 | 8 | 15 | % if logo_uri: 16 | Client logo 17 | % endif 18 | % if policy_uri: 19 | Client policy 20 | % endif 21 |
22 | 23 | 26 | 27 | <%def name="add_js()"> 28 | 33 | 34 | -------------------------------------------------------------------------------- /tests/data/keys/cert.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN CERTIFICATE----- 2 | MIIC3zCCAcegAwIBAgIBATANBgkqhkiG9w0BAQUFADA0MRgwFgYDVQQDEw9UaGUg 3 | Y29kZSB0ZXN0ZXIxGDAWBgNVBAoTD1VtZWEgVW5pdmVyc2l0eTAeFw0xMjA4MjEx 4 | MTAyMjFaFw0xMzA4MjExMTAyMjFaMDIxCzAJBgNVBAYTAlNFMSMwIQYDVQQDExpP 5 | cGVuSUQgQ29ubmVjdCBUZXN0IFNlcnZlcjCCASIwDQYJKoZIhvcNAQEBBQADggEP 6 | ADCCAQoCggEBANRAq0lVRsRw4wpvoC3RqEzwk3iIE0b4p9HBPTOrPxpA9LWt08ug 7 | wpbvBogZpfj0H0LUsmuRM7/BLlF4RYTQqUft/7zMFI2Cwcx+cmwAfzQUS/DCdTO1 8 | lM5vlkp5dYXWDKJV9cctnMtlJubGiLbmzDjYYVRvoRQLMsvYgg1/hVfGp6zj6Qgb 9 | mCtQdAWgUmnzA7rN0kfsavE+i4E3pcK97KyZukOgNgM3QbApJvlnClud+ApUWzGH 10 | zGiUJQWKZrZ0Lf5PHLVFl9ZP5CACexkeVof++eecAnFSW56HOIjYD7NSsjX5h79k 11 | YhK9pX9AZsaZM1BpzUDWjlagdfw9coLDTAMCAwEAATANBgkqhkiG9w0BAQUFAAOC 12 | AQEAIeLXKI+XukIcWHY67N9NqdWWhBi8HIygbvt6bNOlM9dFIBvc68LQIZSrJd/6 13 | AKs07N+mY9rewYFcORrgI6767Gd/vzu+HhEMpFgJnipPjBq6XuwzLSOPRHIOGMcx 14 | gLmHa/ALDhQq+ma4eghpxjYM4hY8VOXPRLzGE+WqOFc/PTUIxDRT5yk0ct+XlCdC 15 | mCRA9BnJfYG2ABsf2GtfNK2aXMIbCC/n9c/NNkffc2aXGImalj18BZzYW8TWNQt9 16 | Ypx4GMUpDZu8CLaddGHoAxZgar6+BLSwO8FWB5XiFLOH/OEai9o+LZZbSebL4fyl 17 | yST8CMkOK8hBl1tHmEWbknlH5A== 18 | -----END CERTIFICATE----- 19 | -------------------------------------------------------------------------------- /oidc_example/op2/cp_keys/cert.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN CERTIFICATE----- 2 | MIIC3zCCAcegAwIBAgIBATANBgkqhkiG9w0BAQUFADA0MRgwFgYDVQQDEw9UaGUg 3 | Y29kZSB0ZXN0ZXIxGDAWBgNVBAoTD1VtZWEgVW5pdmVyc2l0eTAeFw0xMjA4MjEx 4 | MTAyMjFaFw0xMzA4MjExMTAyMjFaMDIxCzAJBgNVBAYTAlNFMSMwIQYDVQQDExpP 5 | cGVuSUQgQ29ubmVjdCBUZXN0IFNlcnZlcjCCASIwDQYJKoZIhvcNAQEBBQADggEP 6 | ADCCAQoCggEBANRAq0lVRsRw4wpvoC3RqEzwk3iIE0b4p9HBPTOrPxpA9LWt08ug 7 | wpbvBogZpfj0H0LUsmuRM7/BLlF4RYTQqUft/7zMFI2Cwcx+cmwAfzQUS/DCdTO1 8 | lM5vlkp5dYXWDKJV9cctnMtlJubGiLbmzDjYYVRvoRQLMsvYgg1/hVfGp6zj6Qgb 9 | mCtQdAWgUmnzA7rN0kfsavE+i4E3pcK97KyZukOgNgM3QbApJvlnClud+ApUWzGH 10 | zGiUJQWKZrZ0Lf5PHLVFl9ZP5CACexkeVof++eecAnFSW56HOIjYD7NSsjX5h79k 11 | YhK9pX9AZsaZM1BpzUDWjlagdfw9coLDTAMCAwEAATANBgkqhkiG9w0BAQUFAAOC 12 | AQEAIeLXKI+XukIcWHY67N9NqdWWhBi8HIygbvt6bNOlM9dFIBvc68LQIZSrJd/6 13 | AKs07N+mY9rewYFcORrgI6767Gd/vzu+HhEMpFgJnipPjBq6XuwzLSOPRHIOGMcx 14 | gLmHa/ALDhQq+ma4eghpxjYM4hY8VOXPRLzGE+WqOFc/PTUIxDRT5yk0ct+XlCdC 15 | mCRA9BnJfYG2ABsf2GtfNK2aXMIbCC/n9c/NNkffc2aXGImalj18BZzYW8TWNQt9 16 | Ypx4GMUpDZu8CLaddGHoAxZgar6+BLSwO8FWB5XiFLOH/OEai9o+LZZbSebL4fyl 17 | yST8CMkOK8hBl1tHmEWbknlH5A== 18 | -----END CERTIFICATE----- 19 | -------------------------------------------------------------------------------- /oidc_example/op3/static/jwks.json: -------------------------------------------------------------------------------- 1 | {"keys": [{"use": "enc", "n": "6LC5Zr1Ng3HN_RF173gSAtcWo3RbnRc-YtE2BZnmNDVe2cepFmMjYbcum6bonS7JWCTDYd5m0Gt7-yh71YQxF3bjXAEXGbR6w1405emJuurFgfAnwk8h-o__-DYoF4E9WdbHfi9wRa7tS8pAB2o1JpsH1r3O7wCSPb_cDRIQZ3R5ORjl-q_PWUFxAjLKe2IAM2rfJulertITnGlxcnzzy9cnIyYgY-gm17hZFZd1qLpNG10McyzlwxPl6c-7PagS2nx9Mdv4gUvaNwzgkzQQh9viJfBggc1o86CEPg8RAOqWjUJ8jHAYJDeRxEFHKGcbOXa2n1ZOBj-FHprIOg60Xw", "e": "AQAB", "kty": "RSA", "kid": "op0"}, {"use": "sig", "n": "6LC5Zr1Ng3HN_RF173gSAtcWo3RbnRc-YtE2BZnmNDVe2cepFmMjYbcum6bonS7JWCTDYd5m0Gt7-yh71YQxF3bjXAEXGbR6w1405emJuurFgfAnwk8h-o__-DYoF4E9WdbHfi9wRa7tS8pAB2o1JpsH1r3O7wCSPb_cDRIQZ3R5ORjl-q_PWUFxAjLKe2IAM2rfJulertITnGlxcnzzy9cnIyYgY-gm17hZFZd1qLpNG10McyzlwxPl6c-7PagS2nx9Mdv4gUvaNwzgkzQQh9viJfBggc1o86CEPg8RAOqWjUJ8jHAYJDeRxEFHKGcbOXa2n1ZOBj-FHprIOg60Xw", "e": "AQAB", "kty": "RSA", "kid": "op1"}, {"use": "sig", "crv": "P-256", "kty": "EC", "y": "SDi5lkPJ6OTf2uNTNlDuSNwmmsxtY6QQyOS7P5erMPo", "x": "I9NTB0qqgS1B5CkuKoZDTWbk7G4j6TZqBusrAtHNKro", "kid": "op2"}, {"use": "enc", "crv": "P-256", "kty": "EC", "y": "_F7JXiEvUPM8upu4J_q9MXhGAYLJ_CdzKg9REuV9Tbo", "x": "mbJogROQtkoPxauwEhnUfFGTmWWS6ps9GW-REM_5UgE", "kid": "op3"}]} -------------------------------------------------------------------------------- /oidc_example/op3/htdocs/cp_keys/cert.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN CERTIFICATE----- 2 | MIIC3zCCAcegAwIBAgIBATANBgkqhkiG9w0BAQUFADA0MRgwFgYDVQQDEw9UaGUg 3 | Y29kZSB0ZXN0ZXIxGDAWBgNVBAoTD1VtZWEgVW5pdmVyc2l0eTAeFw0xMjA4MjEx 4 | MTAyMjFaFw0xMzA4MjExMTAyMjFaMDIxCzAJBgNVBAYTAlNFMSMwIQYDVQQDExpP 5 | cGVuSUQgQ29ubmVjdCBUZXN0IFNlcnZlcjCCASIwDQYJKoZIhvcNAQEBBQADggEP 6 | ADCCAQoCggEBANRAq0lVRsRw4wpvoC3RqEzwk3iIE0b4p9HBPTOrPxpA9LWt08ug 7 | wpbvBogZpfj0H0LUsmuRM7/BLlF4RYTQqUft/7zMFI2Cwcx+cmwAfzQUS/DCdTO1 8 | lM5vlkp5dYXWDKJV9cctnMtlJubGiLbmzDjYYVRvoRQLMsvYgg1/hVfGp6zj6Qgb 9 | mCtQdAWgUmnzA7rN0kfsavE+i4E3pcK97KyZukOgNgM3QbApJvlnClud+ApUWzGH 10 | zGiUJQWKZrZ0Lf5PHLVFl9ZP5CACexkeVof++eecAnFSW56HOIjYD7NSsjX5h79k 11 | YhK9pX9AZsaZM1BpzUDWjlagdfw9coLDTAMCAwEAATANBgkqhkiG9w0BAQUFAAOC 12 | AQEAIeLXKI+XukIcWHY67N9NqdWWhBi8HIygbvt6bNOlM9dFIBvc68LQIZSrJd/6 13 | AKs07N+mY9rewYFcORrgI6767Gd/vzu+HhEMpFgJnipPjBq6XuwzLSOPRHIOGMcx 14 | gLmHa/ALDhQq+ma4eghpxjYM4hY8VOXPRLzGE+WqOFc/PTUIxDRT5yk0ct+XlCdC 15 | mCRA9BnJfYG2ABsf2GtfNK2aXMIbCC/n9c/NNkffc2aXGImalj18BZzYW8TWNQt9 16 | Ypx4GMUpDZu8CLaddGHoAxZgar6+BLSwO8FWB5XiFLOH/OEai9o+LZZbSebL4fyl 17 | yST8CMkOK8hBl1tHmEWbknlH5A== 18 | -----END CERTIFICATE----- 19 | -------------------------------------------------------------------------------- /tests/test_authn_user.py: -------------------------------------------------------------------------------- 1 | import base64 2 | from urllib.parse import quote_plus 3 | 4 | import pytest 5 | 6 | from oic.exception import ImproperlyConfigured 7 | from oic.utils.authn.user import BasicAuthn 8 | from oic.utils.authn.user import SymKeyAuthn 9 | 10 | 11 | def test_symkeyauthn_improperly_configured(provider): 12 | improper_symkey = "" 13 | with pytest.raises(ImproperlyConfigured) as err: 14 | SymKeyAuthn(srv=provider, ttl=666, symkey=improper_symkey) 15 | expected_msg = "SymKeyAuthn.symkey cannot be an empty value" 16 | assert expected_msg in str(err.value) 17 | 18 | 19 | def test_basic_authn_authenticate_as(): 20 | pwd_database = {"Diana": "Piano player", "NonAscii": "€&+%#@äää"} 21 | ba = BasicAuthn(None, pwd=pwd_database) 22 | 23 | for user, passwd in pwd_database.items(): 24 | credentials = "{}:{}".format(quote_plus(user), quote_plus(passwd)) 25 | 26 | authz = base64.b64encode(credentials.encode("utf-8")).decode("utf-8") 27 | authorization_string = "Basic {}".format(authz) 28 | 29 | uid, when = ba.authenticated_as(authorization=authorization_string) 30 | assert uid == {"uid": user} 31 | -------------------------------------------------------------------------------- /oidc_example/simple_op/users.json: -------------------------------------------------------------------------------- 1 | { 2 | "diana": { 3 | "sub": "dikr0001", 4 | "name": "Diana Krall", 5 | "given_name": "Diana", 6 | "family_name": "Krall", 7 | "nickname": "Dina", 8 | "email": "diana@example.org", 9 | "email_verified": false, 10 | "phone_number": "+46 90 7865000", 11 | "address": { 12 | "street_address": "Umeå Universitet", 13 | "locality": "Umeå", 14 | "postal_code": "SE-90187", 15 | "country": "Sweden" 16 | } 17 | }, 18 | "babs": { 19 | "sub": "babs0001", 20 | "name": "Barbara J Jensen", 21 | "given_name": "Barbara", 22 | "family_name": "Jensen", 23 | "nickname": "babs", 24 | "email": "babs@example.com", 25 | "email_verified": true, 26 | "address": { 27 | "street_address": "100 Universal City Plaza", 28 | "locality": "Hollywood", 29 | "region": "CA", 30 | "postal_code": "91608", 31 | "country": "USA" 32 | } 33 | }, 34 | "upper": { 35 | "sub": "uppe0001", 36 | "name": "Upper Crust", 37 | "given_name": "Upper", 38 | "family_name": "Crust", 39 | "email": "uc@example.com", 40 | "email_verified": true 41 | } 42 | } -------------------------------------------------------------------------------- /oauth_example/rp/htdocs/as_choice.mako: -------------------------------------------------------------------------------- 1 | <%! 2 | def as_choice(as_list): 3 | """ 4 | Creates a dropdown list of authorization servers 5 | """ 6 | element = "" 10 | return element 11 | %> 12 | 13 | 14 | 15 | OAuth2 RP Example 16 | 17 | 18 | 21 | 22 | 23 | 24 |
25 | 26 |
27 |
28 |

Choose the Authorization Server:

29 | ${as_choice(as_list)} 30 |
31 |
32 | 33 |
34 |
35 |
36 | 37 | 38 | -------------------------------------------------------------------------------- /tests/conftest.py: -------------------------------------------------------------------------------- 1 | """Pytest fixtures for testing.""" 2 | from typing import Any 3 | from typing import Dict 4 | 5 | import pytest 6 | 7 | from oic.oic.provider import Provider 8 | from oic.utils.authn.client import verify_client 9 | from oic.utils.authz import AuthzHandling 10 | from oic.utils.sdb import create_session_db 11 | 12 | 13 | @pytest.fixture 14 | def session_db_factory(): 15 | def fac(issuer): 16 | return create_session_db(issuer, secret="supersecret", password="badpassword") 17 | 18 | return fac 19 | 20 | 21 | @pytest.fixture 22 | def session_db(session_db_factory): 23 | return session_db_factory("https://op.example.com") 24 | 25 | 26 | @pytest.fixture 27 | def provider(session_db): 28 | issuer = "https://op.example.com" 29 | client_db: Dict[str, Any] = {} 30 | verification_function = verify_client 31 | authz_handler = AuthzHandling() 32 | symkey = None 33 | user_info_store = None 34 | authn_broker = None 35 | return Provider( 36 | issuer, 37 | session_db, 38 | client_db, 39 | authn_broker, 40 | user_info_store, 41 | authz_handler, 42 | verification_function, 43 | symkey, 44 | ) 45 | -------------------------------------------------------------------------------- /oidc_example/README.md: -------------------------------------------------------------------------------- 1 | # Simple RP and OP examples 2 | 3 | These 2 examples can run on the same machine together following these steps. 4 | 5 | Both servers "use" up a port, and running them on the same machine means they 6 | need their own ports. `simple_rp` supports providing a response url with a port 7 | in it, so that will run on a random port (8000 in the example below) and 8 | `simple_op` runs on port 443 - the standard https port. 9 | 10 | Run the following: 11 | 12 | 1. Install oic from source using `python setup.py install` 13 | 1. Install `requirements.txt` for `simple_op` using `pip install -r requirements.txt` 14 | 1. Install `requirements.txt` for `simple_rp` using `pip install -r requirements.txt` 15 | 1. Start the op server on port 443 in `simple_op` using `python src/run.py settings.yaml.example -p 443` 16 | 1. Start the rp server on port 8000 in `simple_rp` using `python src/rp.py settings.yaml.example -p 8000` 17 | 1. Open the rp server in a browser, 18 | 1. Enter the uid `localhost` to connect to the simple op server 19 | 1. Login using the credentials in `simple_op/passwd.json` (this is referenced in the simple op example settings) 20 | 1. Observe the user info is loaded by the RP server 21 | -------------------------------------------------------------------------------- /src/oic/utils/__init__.py: -------------------------------------------------------------------------------- 1 | import sys 2 | import traceback 3 | 4 | __author__ = "rohe0002" 5 | 6 | 7 | def tobytes(value) -> bytes: 8 | """Convert value to bytes.""" 9 | if isinstance(value, bytes): 10 | return value 11 | else: 12 | if isinstance(value, str): 13 | return value.encode() 14 | else: 15 | return bytes(value) 16 | 17 | 18 | def exception_trace(tag, exc, log=None): 19 | message = traceback.format_exception(*sys.exc_info()) 20 | if log: 21 | log.error("[%s] ExcList: %s", tag, "".join(message)) 22 | log.error("[%s] Exception: %s", tag, exc) 23 | else: 24 | print("[{0}] ExcList: {1}".format(tag, "".join(message)), file=sys.stderr) 25 | print("[{0}] Exception: {1}".format(tag, exc), file=sys.stderr) 26 | 27 | 28 | SORT_ORDER = {"RS": 0, "ES": 1, "HS": 2, "PS": 3, "no": 4} 29 | 30 | 31 | def sort_sign_alg(alg1, alg2): 32 | if SORT_ORDER[alg1[0:2]] < SORT_ORDER[alg2[0:2]]: 33 | return -1 34 | elif SORT_ORDER[alg1[0:2]] > SORT_ORDER[alg2[0:2]]: 35 | return 1 36 | else: 37 | if alg1 < alg2: 38 | return -1 39 | elif alg1 > alg2: 40 | return 1 41 | else: 42 | return 0 43 | -------------------------------------------------------------------------------- /docker/integration_tests/run.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | #TRAVIS and CI variables make the test suite think that it's running in a travis pipeline 4 | #which makes it start puppeteer with the --no-sandbox option, otherwise you get a no usable 5 | #sandbox error from chrome and it won't start 6 | export TRAVIS=TRAVIS 7 | export CI=CI 8 | 9 | export TEST_PORT=60003 10 | export TEST_HOSTNAME=op-test 11 | export TEST_PROTOCOL=https 12 | export TAG=default 13 | export NODE_TLS_REJECT_UNAUTHORIZED=0 14 | 15 | cd /root/oidc-provider-conformance-tests 16 | export ISSUER=https://op:4433 17 | echo "---Running oidc-provider-conformance-tests 'code' and 'id_token'---" 18 | concurrently -- "npm:code" "npm:id_token" || exit 19 | echo "---Running oidc-provider-conformance-tests 'id_token+token' and 'code+id_token'---" 20 | concurrently -- "npm:id_token+token" "npm:code+id_token" || exit 21 | echo "---Running oidc-provider-conformance-tests 'code+id_token+token' and 'code+token'---" 22 | concurrently -- "npm:code+id_token+token" "npm:code+token" || exit 23 | 24 | cd /root/openid-client-conformance-tests 25 | export ISSUER=https://rp-test:8080 26 | export NODE_TLS_REJECT_UNAUTHORIZED=0 27 | echo "---Running openid-client-conformance-tests---" 28 | npm run test || exit 29 | echo "---FINISHED---" 30 | -------------------------------------------------------------------------------- /docker/rp_test/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM ubuntu:18.04 2 | 3 | ENV DEBIAN_FRONTEND noninteractive 4 | ENV SRCDIR /usr/local/src 5 | ENV INSTDIR oidf 6 | ENV SUBDIR ${SRCDIR}/${INSTDIR}/oidc_cp_rplib 7 | 8 | RUN apt-get update && apt-get install -y --no-install-recommends \ 9 | build-essential \ 10 | git \ 11 | libffi-dev \ 12 | libssl-dev \ 13 | python3-dev \ 14 | python3-pip \ 15 | python3-setuptools && apt-get clean && rm -rf /var/lib/apt/lists/* 16 | 17 | RUN python3 -mpip install -U pip setuptools 18 | RUN git clone --depth=1 https://github.com/openid-certification/otest.git ${SRCDIR}/otest 19 | WORKDIR ${SRCDIR}/otest 20 | RUN python3 setup.py install 21 | 22 | RUN git clone --depth=1 https://github.com/openid-certification/oidctest.git ${SRCDIR}/oidctest 23 | WORKDIR ${SRCDIR}/oidctest 24 | RUN python3 setup.py install 25 | 26 | WORKDIR ${SRCDIR} 27 | RUN oidc_setup.py ${SRCDIR}/oidctest ${INSTDIR} 28 | COPY docker/rp_test/cert.pem ${SUBDIR}/certs/ 29 | COPY docker/rp_test/key.pem ${SUBDIR}/certs/ 30 | COPY docker/rp_test/conf.py ${SUBDIR}/ 31 | COPY docker/rp_test/run.sh ${SUBDIR}/ 32 | 33 | RUN mkdir pyoidc 34 | COPY . ${SRCDIR}/pyoidc/ 35 | WORKDIR ${SRCDIR}/pyoidc 36 | RUN python3 setup.py install 37 | 38 | WORKDIR ${SUBDIR} 39 | ENTRYPOINT ["./run.sh"] 40 | -------------------------------------------------------------------------------- /docker/integration_tests/Makefile: -------------------------------------------------------------------------------- 1 | IMAGENAME?=oidctest-local-it 2 | CONTAINERNAME?=oidctest-it 3 | 4 | .PHONY: help 5 | help: 6 | @echo "Usage" 7 | @echo " build : Build the image" 8 | @echo " delete : Deletes the container" 9 | @echo " logs : Shows container logs" 10 | @echo " shell : Starts interactive shell" 11 | @echo " start : Starts the container (you need to run 'make build' once first)" 12 | @echo " status : Shows container status" 13 | @echo " stop : Stops the container" 14 | @echo " " 15 | @echo "Other oidctest containers must be already running when you start this container" 16 | 17 | .PHONY: build 18 | build: 19 | docker build -t "$(IMAGENAME)" . 20 | docker run --net=host --name "$(CONTAINERNAME)" -d "$(IMAGENAME)" 21 | 22 | 23 | .PHONY: start 24 | start: 25 | docker container start "$(CONTAINERNAME)" 26 | docker container logs --follow "$(CONTAINERNAME)" 27 | 28 | .PHONY: stop 29 | stop: 30 | docker stop "$(CONTAINERNAME)" 31 | 32 | .PHONY: delete 33 | delete: 34 | docker rm "$(CONTAINERNAME)" 35 | 36 | .PHONY: logs 37 | logs: 38 | docker container logs --follow "$(CONTAINERNAME)" 39 | 40 | .PHONY: shell 41 | shell: 42 | docker exec -it "$(CONTAINERNAME)" bash 43 | 44 | .PHONY: status 45 | status: 46 | docker ps -f name="$(CONTAINERNAME)" 47 | -------------------------------------------------------------------------------- /docker/op_test/my_jwks_60003.json: -------------------------------------------------------------------------------- 1 | {"keys": [{"kty": "RSA", "use": "enc", "kid": "rJ2OTcGTyd1Gk1GHjX-0exXGpVLYC-f0wnhmApnIfDw", "n": "trHr6obGgLNQiOwgTqcRFl18QjREfixvocvQAM-iIzcWud1g86mJG8IHOmZfh6Q9MRUvToDU-y1b-p44AtFSwi3_j4dh6R99tSe_2J0QjHZ7by6_ujxo_4KwolDbb_ToON-0hgWw7q4nvtvEr0ajpeiVVbIcTjqi13kY8ZmkKN_HjLebqeu4nelsuxVLNwuFaXDwNqtynTNXrgQtOSDqCu7sadyfX5jC6FdnSCL1XRin2gVGbsJ_4QVOy3tfpxAZa5pwt347w-cOrRqrIFOr2oUAC3lW57xHq8XiwN5r4naziztHmsUY13Z20nr6-tgeR9IZayArCy3vzd_-PQHbEw", "e": "AQAB"}, {"kty": "RSA", "use": "sig", "kid": "r3kqK2g4_x6aZ_X_s5wN-HXFcuF4KkMWGXZATRLtdGU", "n": "y_HYxdN31Zkg-BRddSuUbNIvsZazg3f6T1aPKoiGKdhQ0zozho19n03_uDJOOMj9cZZzGQG-vABa9bVPOyuBYDxGNQJraUeMNC1MczeIDLmpSV_JtndrwT-RXsKE74eXTajdmtF-ovZ3to7N78zSgt4sivMwmj1z2LmjKZA6HS2dkUNdDGLg7u0gvQj9kvON-XMyG5gwpZnnlYmPhYLaLTEW6GV0MecjTeoUT50LKSqqdrsc7Cx4L5aIlJBJ34AWu3LlFVvacCJt7opgyR9OiXNdQPr_LalZzFHcJMsF1HUCb7F9L1l9xED67w6bkgzGEUCROhLYF3ItFNfh3Nsj5Q", "e": "AQAB"}, {"kty": "EC", "use": "sig", "kid": "0UxXvqg6eSWklYtiknLooQseuV8KDqo7nueEVK4J1qQ", "crv": "P-256", "x": "iUgt9gbOE0kmUowZ9qka5SqsWx-tDK64NE3AcjJQxo4", "y": "JlHv9cnk51XdlqoaFaFjxUOB7QQus1QTqvq_XwS_RCo"}, {"kty": "EC", "use": "enc", "kid": "lWMyG2VyQWlORh1_-wxg335hYUbxc5TIk0HrKAHoaEw", "crv": "P-256", "x": "aW0Wlzq1XZypiZkyUis7Lq1t1jZob6o-gzXldhkegQQ", "y": "wt2sgmId7ujsPmgQQlnpd1iXro4SGEPWwyR7yK5QYXw"}]} -------------------------------------------------------------------------------- /tests/test_claims.py: -------------------------------------------------------------------------------- 1 | from oic.oic import claims_match 2 | from oic.utils.claims import ClaimsMode 3 | 4 | 5 | def test_claims_for_user(): 6 | user = "foobar" 7 | user2mode = {user: "aggregate"} 8 | claims_mode = ClaimsMode(user2mode) 9 | 10 | assert claims_mode.aggregate(user) 11 | 12 | 13 | def test_claims_for_missing_user(): 14 | claims_mode = ClaimsMode({}) 15 | 16 | assert not claims_mode.aggregate("nobody") 17 | 18 | 19 | def test_non_aggregate_claims(): 20 | user = "foobar" 21 | claims_mode = ClaimsMode({user: "distributed"}) 22 | 23 | assert not claims_mode.aggregate(user) 24 | 25 | 26 | def test_claims_match(): 27 | claims_request = { 28 | "sub": {"value": "248289761001"}, 29 | "auth_time": {"essential": True}, 30 | "acr": { 31 | "essential": True, 32 | "values": ["urn:mace:incommon:iap:silver", "urn:mace:incommon:iap:bronze"], 33 | }, 34 | } 35 | 36 | assert claims_match("248289761001", claims_request["sub"]) 37 | assert claims_match("123456789012", claims_request["sub"]) is False 38 | assert claims_match("123456789", claims_request["auth_time"]) 39 | assert claims_match("urn:mace:incommon:iap:silver", claims_request["acr"]) 40 | assert claims_match("urn:mace:incommon:iap:gold", claims_request["acr"]) is False 41 | -------------------------------------------------------------------------------- /oidc_example/rp3/htdocs/rp_session_iframe.mako: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 42 | 43 | 44 | -------------------------------------------------------------------------------- /oidc_example/op2/htdocs/login_se.mako: -------------------------------------------------------------------------------- 1 | 2 | 3 | Logga in 4 | 5 | 6 | 35 | 36 | 37 | -------------------------------------------------------------------------------- /oidc_example/op3/htdocs/login_se.mako: -------------------------------------------------------------------------------- 1 | 2 | 3 | Logga in 4 | 5 | 6 | 35 | 36 | 37 | -------------------------------------------------------------------------------- /oidc_example/rp3/htdocs/opresult_repost.mako: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | OpenID Certification OP Test 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 17 | 18 | 19 | 20 | 21 | 22 |
23 |
24 |
25 | 26 | 32 |
33 |
34 |
35 | 36 | 37 | -------------------------------------------------------------------------------- /src/oic/oauth2/exception.py: -------------------------------------------------------------------------------- 1 | from oic.exception import PyoidcError 2 | 3 | __author__ = "roland" 4 | 5 | 6 | class HttpError(PyoidcError): 7 | pass 8 | 9 | 10 | class MissingRequiredAttribute(PyoidcError): 11 | pass 12 | 13 | 14 | class VerificationError(PyoidcError): 15 | pass 16 | 17 | 18 | class ResponseError(PyoidcError): 19 | pass 20 | 21 | 22 | class TimeFormatError(PyoidcError): 23 | pass 24 | 25 | 26 | class CapabilitiesMisMatch(PyoidcError): 27 | pass 28 | 29 | 30 | class MissingEndpoint(PyoidcError): 31 | pass 32 | 33 | 34 | class TokenError(PyoidcError): 35 | pass 36 | 37 | 38 | class GrantError(PyoidcError): 39 | pass 40 | 41 | 42 | class ParseError(PyoidcError): 43 | pass 44 | 45 | 46 | class OtherError(PyoidcError): 47 | pass 48 | 49 | 50 | class NoClientInfoReceivedError(PyoidcError): 51 | pass 52 | 53 | 54 | class InvalidRequest(PyoidcError): 55 | pass 56 | 57 | 58 | class NonFatalException(PyoidcError): 59 | """ 60 | Return the response but accompany it by an error message. 61 | 62 | :param resp: A response that the function/method would return on non-error 63 | :param msg: A message describing what error has occurred. 64 | """ 65 | 66 | def __init__(self, resp, msg): 67 | self.resp = resp 68 | self.msg = msg 69 | 70 | 71 | class Unsupported(PyoidcError): 72 | pass 73 | 74 | 75 | class UnsupportedResponseType(Unsupported): 76 | pass 77 | -------------------------------------------------------------------------------- /oidc_example/simple_op/src/provider/authn/user_pass.py: -------------------------------------------------------------------------------- 1 | import json 2 | 3 | from provider.authn import AuthnModule 4 | from provider.authn import make_cls_from_name 5 | 6 | from oic.utils.http_util import Response 7 | 8 | 9 | class UserPass(AuthnModule): 10 | url_endpoint = "/user_pass/verify" 11 | 12 | def __init__(self, db, template_env, template="user_pass.jinja2", **kwargs): 13 | super(UserPass, self).__init__(None) 14 | self.template_env = template_env 15 | self.template = template 16 | 17 | cls = make_cls_from_name(db["class"]) 18 | self.user_db = cls(**db["kwargs"]) 19 | 20 | self.kwargs = kwargs 21 | self.kwargs.setdefault("page_header", "Log in") 22 | self.kwargs.setdefault("user_label", "Username") 23 | self.kwargs.setdefault("passwd_label", "Password") 24 | self.kwargs.setdefault("submit_btn", "Log in") 25 | 26 | def __call__(self, *args, **kwargs): 27 | template = self.template_env.get_template(self.template) 28 | return Response(template.render(action=self.url_endpoint, 29 | state=json.dumps(kwargs), 30 | **self.kwargs)) 31 | 32 | def verify(self, *args, **kwargs): 33 | username = kwargs["username"] 34 | if username in self.user_db and self.user_db[username] == kwargs[ 35 | "password"]: 36 | return username, True 37 | else: 38 | return self.FAILED_AUTHN 39 | -------------------------------------------------------------------------------- /src/oic/utils/userinfo/__init__.py: -------------------------------------------------------------------------------- 1 | import copy 2 | 3 | __author__ = "rolandh" 4 | 5 | 6 | class UserInfo(object): 7 | """Read only interface to a user info store.""" 8 | 9 | def __init__(self, db=None): 10 | self.db = db 11 | 12 | def filter(self, userinfo, user_info_claims=None): 13 | """ 14 | Return only those claims that are asked for. 15 | 16 | It's a best effort task; if essential claims are not present no error is flagged. 17 | 18 | :param userinfo: A dictionary containing the available user info. 19 | :param user_info_claims: A dictionary specifying the asked for claims 20 | :return: A dictionary of filtered claims. 21 | """ 22 | if user_info_claims is None: 23 | return copy.copy(userinfo) 24 | else: 25 | result = {} 26 | missing = [] 27 | optional = [] 28 | for key, restr in user_info_claims.items(): 29 | try: 30 | result[key] = userinfo[key] 31 | except KeyError: 32 | if restr == {"essential": True}: 33 | missing.append(key) 34 | else: 35 | optional.append(key) 36 | return result 37 | 38 | def __call__(self, userid, client_id, user_info_claims=None, **kwargs): 39 | try: 40 | return self.filter(self.db[userid], user_info_claims) 41 | except KeyError: 42 | return {} 43 | -------------------------------------------------------------------------------- /tests/test_client_management.py: -------------------------------------------------------------------------------- 1 | # pylint: disable=redefined-outer-name, missing-docstring 2 | 3 | import json 4 | import tempfile 5 | 6 | import pytest 7 | 8 | from oic.utils.client_management import CDB 9 | 10 | 11 | @pytest.fixture 12 | def cdb(): 13 | file = tempfile.NamedTemporaryFile() # just get a unique filename 14 | file.close() 15 | return CDB(file.name) 16 | 17 | 18 | class TestCDB(object): 19 | def test_create(self, cdb): 20 | info = self._create_new(cdb) 21 | assert info == cdb[info["client_id"]] 22 | 23 | def test_dump(self, cdb): 24 | info = self._create_new(cdb) 25 | 26 | file = tempfile.NamedTemporaryFile(delete=False) 27 | file.close() 28 | cdb.dump(file.name) 29 | 30 | with open(file.name) as f: 31 | from_file = json.load(f) 32 | assert from_file[0] == info # serialized to file properly 33 | 34 | client_id = info["client_id"] 35 | del cdb[client_id] 36 | 37 | with pytest.raises(KeyError): 38 | cdb[client_id] # make sure the client is removed 39 | cdb.load(file.name) 40 | assert cdb[client_id] == info # ensure the all info was restored 41 | 42 | def _create_new(self, client_db): 43 | info = client_db.create( 44 | ["https://example.com/redirect"], 45 | "https://example.com/policy", 46 | "https://example.com/logo", 47 | "https://example.com/jwks", 48 | ) 49 | 50 | return info 51 | -------------------------------------------------------------------------------- /oidc_example/op2/htdocs/login.mako: -------------------------------------------------------------------------------- 1 | <%inherit file="root.mako" /> 2 |
3 |

${title}

4 |
5 | 36 | 37 | <%def name="add_js()"> 38 | 43 | 44 | -------------------------------------------------------------------------------- /oidc_example/op3/htdocs/login.mako: -------------------------------------------------------------------------------- 1 | <%inherit file="root.mako" /> 2 |
3 |

${title}

4 |
5 | 36 | 37 | <%def name="add_js()"> 38 | 43 | 44 | -------------------------------------------------------------------------------- /tests/data/templates/login.mako: -------------------------------------------------------------------------------- 1 | <%inherit file="root.mako" /> 2 |
3 |

${title}

4 |
5 | 36 | 37 | <%def name="add_js()"> 38 | 43 | 44 | -------------------------------------------------------------------------------- /docker/op_test/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM ubuntu:18.04 2 | 3 | ENV DEBIAN_FRONTEND noninteractive 4 | ENV SRCDIR /usr/local/src 5 | ENV INSTDIR oidf 6 | ENV SUBDIR ${SRCDIR}/${INSTDIR}/oidc_op 7 | 8 | RUN apt-get update && apt-get install -y --no-install-recommends \ 9 | build-essential \ 10 | git \ 11 | libffi-dev \ 12 | libssl-dev \ 13 | python3-dev \ 14 | python3-pip \ 15 | python3-setuptools && apt-get clean && rm -rf /var/lib/apt/lists/* 16 | 17 | RUN python3 -mpip install -U pip setuptools 18 | RUN git clone --depth=1 https://github.com/openid-certification/otest.git ${SRCDIR}/otest 19 | WORKDIR ${SRCDIR}/otest 20 | RUN python3 setup.py install 21 | 22 | RUN git clone --depth=1 https://github.com/openid-certification/oidctest.git ${SRCDIR}/oidctest 23 | WORKDIR ${SRCDIR}/oidctest 24 | RUN python3 setup.py install 25 | 26 | WORKDIR ${SRCDIR} 27 | RUN oidc_setup.py ${SRCDIR}/oidctest ${INSTDIR} 28 | COPY docker/op_test/cert.pem ${SUBDIR}/certs/ 29 | COPY docker/op_test/key.pem ${SUBDIR}/certs/ 30 | COPY docker/op_test/config.py ${SUBDIR}/ 31 | COPY docker/op_test/tt_config.py ${SUBDIR}/ 32 | COPY docker/op_test/run.sh ${SUBDIR}/ 33 | 34 | COPY docker/op_test/https%3A%2F%2Fop%3A4433 ${SUBDIR}/entities/https%3A%2F%2Fop%3A4433 35 | COPY docker/op_test/assigned_ports.json ${SUBDIR}/ 36 | COPY docker/op_test/my_jwks_60003.json ${SUBDIR}/static/jwks_60003.json 37 | 38 | RUN mkdir pyoidc 39 | COPY . ${SRCDIR}/pyoidc 40 | WORKDIR ${SRCDIR}/pyoidc 41 | RUN python3 setup.py install 42 | 43 | WORKDIR ${SUBDIR} 44 | ENTRYPOINT ["./run.sh"] 45 | -------------------------------------------------------------------------------- /docker/integration_tests/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM ubuntu:18.04 2 | 3 | ENV DEBIAN_FRONTEND noninteractive 4 | RUN apt-get update && apt-get install -y --no-install-recommends\ 5 | apache2 \ 6 | ca-certificates \ 7 | git \ 8 | gnupg \ 9 | libgconf-2-4 \ 10 | libnss3-tools \ 11 | ntp \ 12 | wget \ 13 | && apt-get clean \ 14 | && rm -rf /var/lib/apt/lists/* 15 | RUN wget -q https://deb.nodesource.com/setup_12.x -O - | bash - 16 | RUN wget -q -O - https://dl-ssl.google.com/linux/linux_signing_key.pub | apt-key add - 17 | RUN echo 'deb [arch=amd64] http://dl.google.com/linux/chrome/deb/ stable main' | tee /etc/apt/sources.list.d/google-chrome.list 18 | RUN apt-get update && apt-get install -y --no-install-recommends \ 19 | google-chrome-stable \ 20 | libxss1 \ 21 | libxtst6 \ 22 | nodejs \ 23 | libgtk2.0-0 libgtk-3-0 libnotify-dev \ 24 | libgconf-2-4 libnss3 libxss1 \ 25 | libasound2 libxtst6 xauth xvfb \ 26 | libgbm-dev \ 27 | && apt-get clean \ 28 | && rm -rf /var/lib/apt/lists/* 29 | 30 | WORKDIR /root 31 | RUN git clone https://github.com/openid-certification/oidc-provider-conformance-tests.git --depth 1 32 | WORKDIR /root/oidc-provider-conformance-tests 33 | RUN npm install --production 34 | WORKDIR /root 35 | RUN git clone https://github.com/openid-certification/openid-client-conformance-tests.git --depth 1 36 | WORKDIR /root/openid-client-conformance-tests 37 | RUN npm install --production 38 | RUN npm install -g concurrently@5 39 | 40 | COPY run.sh /root/run.sh 41 | RUN chmod 755 /root/run.sh 42 | 43 | ENTRYPOINT /root/run.sh 44 | -------------------------------------------------------------------------------- /oidc_example/simple_rp/src/htdocs/repost_fragment.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | pyoidc RP 6 | 7 | 8 | 9 | 10 |
11 | 12 |
13 | 14 | 41 | 42 | 43 | -------------------------------------------------------------------------------- /oidc_example/rp3/conf_heart.py: -------------------------------------------------------------------------------- 1 | # BASE = "https://lingon.ladok.umu.se" 2 | BASE = "https://localhost" 3 | 4 | # If BASE is https these has to be specified 5 | SERVER_CERT = "certs/server.crt" 6 | SERVER_KEY = "certs/server.key" 7 | CA_BUNDLE = None 8 | CERT_CHAIN = None 9 | 10 | VERIFY_SSL = False 11 | 12 | # information used when registering the client, this may be the same for all OPs 13 | 14 | ME = { 15 | "application_type": "web", 16 | "application_name": "idpproxy", 17 | "contacts": ["ops@example.com"], 18 | "redirect_uris": ["{base}authz_cb"], 19 | "post_logout_redirect_uris": ["{base}logout_success"], 20 | "response_types": ["code"], 21 | 'token_endpoint_auth_method': ['private_key_jwt'] 22 | } 23 | 24 | BEHAVIOUR = { 25 | "response_type": "code", 26 | "scope": ["openid", "profile", "email", "address", "phone"], 27 | } 28 | 29 | ACR_VALUES = ["PASSWD"] 30 | 31 | # The keys in this dictionary are the OPs short userfriendly name 32 | # not the issuer (iss) name. 33 | 34 | CLIENTS = { 35 | # The ones that support webfinger, OP discovery and client registration 36 | # This is the default, any client that is not listed here is expected to 37 | # support dynamic discovery and registration. 38 | "": { 39 | "client_info": ME, 40 | "behaviour": BEHAVIOUR 41 | }, 42 | } 43 | 44 | KEY_SPECIFICATION = [ 45 | {"type": "RSA", "key": "keys/pyoidc_enc", "use": ["enc"]}, 46 | {"type": "RSA", "key": "keys/pyoidc_sig", "use": ["sig"]}, 47 | {"type": "EC", "crv": "P-256", "use": ["sig"]}, 48 | {"type": "EC", "crv": "P-256", "use": ["enc"]} 49 | ] 50 | 51 | CLIENT_TYPE = 'OAUTH2' # one of OIDC/OAUTH2 52 | USERINFO = False 53 | -------------------------------------------------------------------------------- /src/oic/__init__.py: -------------------------------------------------------------------------------- 1 | import hashlib 2 | import string 3 | 4 | # Since SystemRandom is not available on all systems 5 | try: 6 | # Python 3.6+, designed for this usecase 7 | from secrets import choice 8 | except ImportError: 9 | import random 10 | 11 | try: 12 | # Python 2.4+ if available on the platform 13 | _sysrand = random.SystemRandom() 14 | choice = _sysrand.choice 15 | except AttributeError: 16 | # Fallback, really bad 17 | import warnings 18 | 19 | choice = random.choice 20 | warnings.warn( 21 | "No good random number generator available on this platform. " 22 | "Security tokens will be weak and guessable.", 23 | RuntimeWarning, 24 | ) 25 | 26 | __author__ = "Roland Hedberg" 27 | __version__ = "1.6.0" 28 | 29 | 30 | OIDCONF_PATTERN = "%s/.well-known/openid-configuration" 31 | CC_METHOD = {"S256": hashlib.sha256, "S384": hashlib.sha384, "S512": hashlib.sha512} 32 | 33 | 34 | def rndstr(size=16): 35 | """ 36 | Return a string of random ascii characters or digits. 37 | 38 | :param size: The length of the string 39 | :return: string 40 | """ 41 | _basech = string.ascii_letters + string.digits 42 | return "".join([choice(_basech) for _ in range(size)]) 43 | 44 | 45 | BASECH = string.ascii_letters + string.digits + "-._~" 46 | 47 | 48 | def unreserved(size=64): 49 | """ 50 | Return a string of random ascii characters, digits and unreserved characters for use as RFC 7636 code verifiers. 51 | 52 | :param size: The length of the string 53 | :return: string 54 | """ 55 | return "".join([choice(BASECH) for _ in range(size)]) 56 | -------------------------------------------------------------------------------- /oidc_example/rp3/conf_heart.py.ex: -------------------------------------------------------------------------------- 1 | BASE = "https://localhost" 2 | #BASE = "https://130.239.200.165" 3 | 4 | # If BASE is https these has to be specified 5 | SERVER_CERT = "certs/server.crt" 6 | SERVER_KEY = "certs/server.key" 7 | CA_BUNDLE = None 8 | CERT_CHAIN = None 9 | 10 | VERIFY_SSL = False 11 | 12 | # information used when registering the client, this may be the same for all OPs 13 | 14 | ME = { 15 | "application_type": "web", 16 | "application_name": "idpproxy", 17 | "contacts": ["ops@example.com"], 18 | "redirect_uris": ["{base}authz_cb/{iss}"], 19 | "post_logout_redirect_uris": ["{base}logout_success/{iss}"], 20 | "response_types": ["code"], 21 | # 'token_endpoint_auth_method': '' 22 | } 23 | 24 | BEHAVIOUR = { 25 | "response_type": "code", 26 | "scope": ["offline_access"], 27 | } 28 | 29 | ACR_VALUES = [] 30 | 31 | # The keys in then CLIENTS dictionary are the OPs short user friendly name 32 | # not the issuer (iss) name. 33 | 34 | CLIENTS = { 35 | # The ones that support web finger, OP discovery and dynamic client 36 | # registration. 37 | # This is the default, any client that is not specifically listed here is 38 | # expected to support dynamic discovery and registration. 39 | "": { 40 | "client_info": ME, 41 | "behaviour": BEHAVIOUR, 42 | 'config': { 43 | 'code_challenge': { 44 | 'length': 64, 45 | 'method': 'S256' 46 | } 47 | } 48 | }, 49 | } 50 | 51 | USERINFO = False 52 | RESOURCE_SERVER = None 53 | CLIENT_TYPE = 'OAuth2' 54 | 55 | KEY_SPECIFICATION = [ 56 | {"type": "RSA", "use": ["enc", "sig"]}, 57 | {"type": "EC", "crv": "P-256", "use": ["enc", "sig"]}, 58 | ] 59 | -------------------------------------------------------------------------------- /tests/test_jwt.py: -------------------------------------------------------------------------------- 1 | import os 2 | 3 | from oic.utils.jwt import JWT 4 | from oic.utils.keyio import build_keyjar 5 | from oic.utils.keyio import keybundle_from_local_file 6 | 7 | __author__ = "roland" 8 | 9 | 10 | BASE_PATH = os.path.abspath(os.path.join(os.path.dirname(__file__), "data/keys")) 11 | 12 | keys = [ 13 | {"type": "RSA", "key": os.path.join(BASE_PATH, "cert.key"), "use": ["enc", "sig"]}, 14 | {"type": "EC", "crv": "P-256", "use": ["sig"]}, 15 | {"type": "EC", "crv": "P-256", "use": ["enc"]}, 16 | ] 17 | jwks, keyjar, kidd = build_keyjar(keys) 18 | issuer = "https://fedop.example.org" 19 | 20 | 21 | def _eq(l1, l2): 22 | return set(l1) == set(l2) 23 | 24 | 25 | def test_jwt_pack(): 26 | _jwt = JWT(keyjar, lifetime=3600, iss=issuer).pack() 27 | 28 | assert _jwt 29 | assert len(_jwt.split(".")) == 3 30 | 31 | 32 | def test_jwt_pack_and_unpack(): 33 | srv = JWT(keyjar, iss=issuer) 34 | _jwt = srv.pack(sub="sub") 35 | 36 | info = srv.unpack(_jwt) 37 | 38 | assert _eq(info.keys(), ["jti", "iat", "exp", "iss", "sub", "kid"]) 39 | 40 | 41 | class TestJWT(object): 42 | """Tests for JWT.""" 43 | 44 | def test_unpack_verify_key(self): 45 | srv = JWT(keyjar, iss=issuer) 46 | _jwt = srv.pack(sub="sub") 47 | # Remove the signing key from keyjar 48 | keyjar.remove_key("", "RSA", "") 49 | # And add it back as verify 50 | kb = keybundle_from_local_file( 51 | os.path.join(BASE_PATH, "cert.key"), "RSA", ["ver"] 52 | ) 53 | # keybundle_from_local_file doesn'assign kid, so assign manually 54 | kb._keys[0].kid = kidd["sig"]["RSA"] 55 | keyjar.add_kb("", kb) 56 | info = srv.unpack(_jwt) 57 | assert info["sub"] == "sub" 58 | -------------------------------------------------------------------------------- /oauth_example/as/authn_setup.py: -------------------------------------------------------------------------------- 1 | from oic.utils.authn.authn_context import AuthnBroker 2 | 3 | __author__ = "roland" 4 | 5 | 6 | def ldap_validation(config): 7 | from oic.utils.authn.ldap_member import UserLDAPMemberValidation 8 | 9 | config["args"].update(config["conf"]) 10 | return UserLDAPMemberValidation(**config["args"]) 11 | 12 | 13 | VALIDATOR = {"LDAP": ldap_validation} 14 | 15 | 16 | def cas_setup(item): 17 | from oic.utils.authn.user_cas import CasAuthnMethod 18 | 19 | try: 20 | v_cnf = item["validator"] 21 | except KeyError: 22 | _func = None 23 | else: 24 | _func = VALIDATOR[v_cnf["type"].upper()](item) 25 | 26 | _cnf = item["config"] 27 | return CasAuthnMethod( 28 | None, _cnf["cas_server"], item["URL"], _cnf["return_to"], _func 29 | ) 30 | 31 | 32 | def userpwd_setup(item): 33 | from oic.utils.authn.user import UsernamePasswordMako 34 | 35 | _conf = item["config"] 36 | return UsernamePasswordMako( 37 | None, "login.mako", _conf["lookup"], _conf["passwd"], _conf["return_to"] 38 | ) 39 | 40 | 41 | AUTH_METHOD = { 42 | "UserPassword": userpwd_setup, 43 | "CAS": cas_setup, 44 | } 45 | 46 | 47 | def authn_setup(config): 48 | broker = AuthnBroker() 49 | 50 | # Which methods to use is defined in the configuration file 51 | for authkey, method_conf in config.AUTHN_METHOD.items(): 52 | try: 53 | func = AUTH_METHOD[authkey] 54 | except KeyError: 55 | pass 56 | else: 57 | broker.add( 58 | method_conf["ACR"], 59 | func(method_conf), 60 | method_conf["WEIGHT"], 61 | method_conf["URL"], 62 | ) 63 | 64 | return broker 65 | -------------------------------------------------------------------------------- /docker/op/cert.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN CERTIFICATE----- 2 | MIIErjCCApagAwIBAgIJAMBDTTEuAAmkMA0GCSqGSIb3DQEBCwUAMA0xCzAJBgNV 3 | BAMMAm9wMB4XDTE4MTAxMjA1MTA1N1oXDTE5MTAxMjA1MTA1N1owDTELMAkGA1UE 4 | AwwCb3AwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQDDitqHIUI0sSBA 5 | BVig2d7z1Yu+1xpfeXxCwiMoaD1HMF+Mbqe1yKVc741TibwFv9WC+Nk/4jGdXfT+ 6 | vt6zUiGb3/RuICEsfFE9nPfGH+/LdjGwguo/KOAckiOg450tPkg/bPpQ0OlJX7Co 7 | nwra4C6VveH53lk5Wrf/jtmQVezgWZCXly9PJ9h3nscxRy1ffvuDC+EOft02d8as 8 | 0jW/84xrP8ELhQoj8w2pUfyv4c/ZZj3vi/EPKM0DjgQWedz02loEwwCA2UVHFSiy 9 | qrEr0tiAOkmrXIgA/jhChaqUs2D7CM2vosUwkHgExU+bg0rmHUJgFDWih8TR29OI 10 | GxXy0T9pdtWPu4f2Ox4A4LSjzqMJwiTw9yHqFwixQSm4czLFq6WsJXx67UBGWL+o 11 | TyCnu0glPbEm3hQS6bjmzWyD8Gb4N3wyUJIu85/IaGRBBUqmlbH0sWn4cr2ax7hl 12 | YZbIdqTxLmcXA4XrjQ5gtAPv1CrWxqwivMnkyEYmuNbRd/ZQ5JaNYAmneADiPt7a 13 | i790HOX3+cAlm0ymDu5mgzOJ//mGZhY4dF75id/d+RX3xSf7FLd0OD4WSxPHjDPa 14 | zC7Dn+LpUzSNgTV4/vHwFIT880xRNIaAB+DS5pA1aLvFGfhzPoKKKns75KZ1Hh+t 15 | iTGL8xXKDfrCID0ECve0YvoCkz8GowIDAQABoxEwDzANBgNVHREEBjAEggJvcDAN 16 | BgkqhkiG9w0BAQsFAAOCAgEAfTU1yJa9xNcwUMdqnmsSyc7WAGlnVAtHaur5CmL2 17 | YzAT27E4oWei3rIyobrX6AMl8lU6fZot2yTxlx5c+e5GkgBWsQD50F6ePiLqaFoH 18 | hRAL03bqd6fczmWkJqi7ZzfA7bL6vYaE7CP/OcYnUP9lwOpXR/9sw6+68FYiUJV/ 19 | iR0qLH+H5uCQlm7S1/VPLbVfnnSc1ZdOnMvWg/9gS0KOCBgZTe0Umx5Md9sk2kCZ 20 | 9HnHR7n9jLivAYzb+BthAGH+scTIVdXvBHU+xsV2vjw6nhdj7M0e5JAuquCjyTVG 21 | W0WtNTY6XnoW59nAfgsLc40HW5ZW40hcqD+Ag1yXnGaWGoYITUIIs3H8DVdRUw7m 22 | C5ktZNht/DRi7PTAMhMLt2IcEKSV2XvfJ5CHHUk+gG0HC19I5HitdeEC2cvWSo+Z 23 | UFTyreBDe6lNfhNKffY+o8IOtjq0BXaBBGTKZ9qXz9DILkxScoF6O4vsNFO4llJq 24 | OGr3lEC4W3ODGeyGdgoihUJjh/3iqqzV8m6wX0mx5Eeu2cd/SCU88u3VWPhqoffN 25 | bEI3GFgIWq8a79z9+BajuFsiYSVqmW9F88mwL2UyVLyxlbmvm5CHAaRcLZN5JC6C 26 | G9pQadRELZ4L/7VHem41xNvQNPikUv/JeTWQq3Y+imTt3FVPX4qmuYdWoznkILVF 27 | BTQ= 28 | -----END CERTIFICATE----- 29 | -------------------------------------------------------------------------------- /tests/data/keys/cert.key: -------------------------------------------------------------------------------- 1 | -----BEGIN RSA PRIVATE KEY----- 2 | MIIEpAIBAAKCAQEA1ECrSVVGxHDjCm+gLdGoTPCTeIgTRvin0cE9M6s/GkD0ta3T 3 | y6DClu8GiBml+PQfQtSya5Ezv8EuUXhFhNCpR+3/vMwUjYLBzH5ybAB/NBRL8MJ1 4 | M7WUzm+WSnl1hdYMolX1xy2cy2Um5saItubMONhhVG+hFAsyy9iCDX+FV8anrOPp 5 | CBuYK1B0BaBSafMDus3SR+xq8T6LgTelwr3srJm6Q6A2AzdBsCkm+WcKW534ClRb 6 | MYfMaJQlBYpmtnQt/k8ctUWX1k/kIAJ7GR5Wh/7555wCcVJbnoc4iNgPs1KyNfmH 7 | v2RiEr2lf0BmxpkzUGnNQNaOVqB1/D1ygsNMAwIDAQABAoIBAQDUMOqMX5Jl5K01 8 | y66I3+avNHtZrkAHXaL4UYVL2FE3f+SklGj+U3L1zXPsMCf7IKL3/wd3/iuL8ibK 9 | D8EALFJvtIFMT4HkjuoL9AWT71M7z2a0BNOCpG9liazoO1DAQeNTjzgsrW7o7/Da 10 | GXSn1UgpNDjpXsfb7+4SWBp8QBYgTok/dx/nZyewZsVtkCsnwrm26Y2k8nhynLm1 11 | nGkQBbRKweQJ5O3PrsrZhRO9OXJ10WK/lYHVqRnu4dam8iP8JHKz5CQN/O7x0HI5 12 | VHHAl0Az4+YZe8wrdNhgtkkOhGxwOtIK5Q3eV8st51YBpWbMiDtQ53ghisWtyNzg 13 | EyphOksxAoGBAO1bTWHsuo6srY4cEJJlJ/C7W237iUM2p9MkeOwcewSXtgY19jAb 14 | IalYzn+yIHLqMyJ1H4cA1sBXBooNvkjVd+7niBbE8du2LyHf4avhNHN2tgKIOjpj 15 | GCsitLEqhyQmUUlYC3z8tQbh99b7fqn2kBDnhdSWPojEx84eXQ71IhMPAoGBAOTs 16 | lQ2iT3cqyore02YOieDwSg7n4flAPPG6cTGGQeOyHpNllTy9U/wyEBrHGGSCkjHI 17 | uDICHtYxyBmL6b0H5IyT4vn1Wxm19ecy8zbuK7Lmwd/iWRfKlGr+YQuez/mHVcCG 18 | YMoQvS6j8WOrQxt2wdOMNatgJeJSz66TaRy2QWfNAoGBAOmTWNJN8MSon15itdgq 19 | 3aQj6/SOfOR866h3ktvfpxu85C62eZ+bg4OwVf4J367WVB3LnovvQmYi/ddrcN8h 20 | 2xVqGV020D+DyFwQgnbvdvtNTg2t24dLryP70k8qZ7UmVAXWM+/6i3bLdmbENUCy 21 | 19Ea1XN/quhSpcFr1e37Q133AoGAD5GLXX8BWoBdf+5BgDpS5CpTTwo0EwhsXKAq 22 | XIzd5EdTzwBkktnpYUhiUf/iR8udd6dH55a/VB/UlPAv+DwWLf1MvWUTSf9W9t8/ 23 | LSgrbqJE4x34oyaSy2f7X5fwWu76RPqekH9s7kQWAYo/KRn9eo6Zg8spKGgrWZsK 24 | 1foLHq0CgYBJRjKEY79aNuKCJZw60QPpXodJ65RJufXPz9MgDdoxUOtno8eYPfep 25 | KWWyhJsQXhMJNUMZGvQXRXaaZ3ZZp1e1q18CLh1TqbInC1ODW3L/ZAWCpT9ihcdA 26 | Owj1RL042er59qut/nivipmB5fn1hTbRDLq9rng0fsNU9XlrETbUfg== 27 | -----END RSA PRIVATE KEY----- 28 | -------------------------------------------------------------------------------- /oauth_example/as/keys/key.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN RSA PRIVATE KEY----- 2 | MIIEpAIBAAKCAQEA1ECrSVVGxHDjCm+gLdGoTPCTeIgTRvin0cE9M6s/GkD0ta3T 3 | y6DClu8GiBml+PQfQtSya5Ezv8EuUXhFhNCpR+3/vMwUjYLBzH5ybAB/NBRL8MJ1 4 | M7WUzm+WSnl1hdYMolX1xy2cy2Um5saItubMONhhVG+hFAsyy9iCDX+FV8anrOPp 5 | CBuYK1B0BaBSafMDus3SR+xq8T6LgTelwr3srJm6Q6A2AzdBsCkm+WcKW534ClRb 6 | MYfMaJQlBYpmtnQt/k8ctUWX1k/kIAJ7GR5Wh/7555wCcVJbnoc4iNgPs1KyNfmH 7 | v2RiEr2lf0BmxpkzUGnNQNaOVqB1/D1ygsNMAwIDAQABAoIBAQDUMOqMX5Jl5K01 8 | y66I3+avNHtZrkAHXaL4UYVL2FE3f+SklGj+U3L1zXPsMCf7IKL3/wd3/iuL8ibK 9 | D8EALFJvtIFMT4HkjuoL9AWT71M7z2a0BNOCpG9liazoO1DAQeNTjzgsrW7o7/Da 10 | GXSn1UgpNDjpXsfb7+4SWBp8QBYgTok/dx/nZyewZsVtkCsnwrm26Y2k8nhynLm1 11 | nGkQBbRKweQJ5O3PrsrZhRO9OXJ10WK/lYHVqRnu4dam8iP8JHKz5CQN/O7x0HI5 12 | VHHAl0Az4+YZe8wrdNhgtkkOhGxwOtIK5Q3eV8st51YBpWbMiDtQ53ghisWtyNzg 13 | EyphOksxAoGBAO1bTWHsuo6srY4cEJJlJ/C7W237iUM2p9MkeOwcewSXtgY19jAb 14 | IalYzn+yIHLqMyJ1H4cA1sBXBooNvkjVd+7niBbE8du2LyHf4avhNHN2tgKIOjpj 15 | GCsitLEqhyQmUUlYC3z8tQbh99b7fqn2kBDnhdSWPojEx84eXQ71IhMPAoGBAOTs 16 | lQ2iT3cqyore02YOieDwSg7n4flAPPG6cTGGQeOyHpNllTy9U/wyEBrHGGSCkjHI 17 | uDICHtYxyBmL6b0H5IyT4vn1Wxm19ecy8zbuK7Lmwd/iWRfKlGr+YQuez/mHVcCG 18 | YMoQvS6j8WOrQxt2wdOMNatgJeJSz66TaRy2QWfNAoGBAOmTWNJN8MSon15itdgq 19 | 3aQj6/SOfOR866h3ktvfpxu85C62eZ+bg4OwVf4J367WVB3LnovvQmYi/ddrcN8h 20 | 2xVqGV020D+DyFwQgnbvdvtNTg2t24dLryP70k8qZ7UmVAXWM+/6i3bLdmbENUCy 21 | 19Ea1XN/quhSpcFr1e37Q133AoGAD5GLXX8BWoBdf+5BgDpS5CpTTwo0EwhsXKAq 22 | XIzd5EdTzwBkktnpYUhiUf/iR8udd6dH55a/VB/UlPAv+DwWLf1MvWUTSf9W9t8/ 23 | LSgrbqJE4x34oyaSy2f7X5fwWu76RPqekH9s7kQWAYo/KRn9eo6Zg8spKGgrWZsK 24 | 1foLHq0CgYBJRjKEY79aNuKCJZw60QPpXodJ65RJufXPz9MgDdoxUOtno8eYPfep 25 | KWWyhJsQXhMJNUMZGvQXRXaaZ3ZZp1e1q18CLh1TqbInC1ODW3L/ZAWCpT9ihcdA 26 | Owj1RL042er59qut/nivipmB5fn1hTbRDLq9rng0fsNU9XlrETbUfg== 27 | -----END RSA PRIVATE KEY----- 28 | -------------------------------------------------------------------------------- /oidc_example/op2/cp_keys/key.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN RSA PRIVATE KEY----- 2 | MIIEpAIBAAKCAQEA1ECrSVVGxHDjCm+gLdGoTPCTeIgTRvin0cE9M6s/GkD0ta3T 3 | y6DClu8GiBml+PQfQtSya5Ezv8EuUXhFhNCpR+3/vMwUjYLBzH5ybAB/NBRL8MJ1 4 | M7WUzm+WSnl1hdYMolX1xy2cy2Um5saItubMONhhVG+hFAsyy9iCDX+FV8anrOPp 5 | CBuYK1B0BaBSafMDus3SR+xq8T6LgTelwr3srJm6Q6A2AzdBsCkm+WcKW534ClRb 6 | MYfMaJQlBYpmtnQt/k8ctUWX1k/kIAJ7GR5Wh/7555wCcVJbnoc4iNgPs1KyNfmH 7 | v2RiEr2lf0BmxpkzUGnNQNaOVqB1/D1ygsNMAwIDAQABAoIBAQDUMOqMX5Jl5K01 8 | y66I3+avNHtZrkAHXaL4UYVL2FE3f+SklGj+U3L1zXPsMCf7IKL3/wd3/iuL8ibK 9 | D8EALFJvtIFMT4HkjuoL9AWT71M7z2a0BNOCpG9liazoO1DAQeNTjzgsrW7o7/Da 10 | GXSn1UgpNDjpXsfb7+4SWBp8QBYgTok/dx/nZyewZsVtkCsnwrm26Y2k8nhynLm1 11 | nGkQBbRKweQJ5O3PrsrZhRO9OXJ10WK/lYHVqRnu4dam8iP8JHKz5CQN/O7x0HI5 12 | VHHAl0Az4+YZe8wrdNhgtkkOhGxwOtIK5Q3eV8st51YBpWbMiDtQ53ghisWtyNzg 13 | EyphOksxAoGBAO1bTWHsuo6srY4cEJJlJ/C7W237iUM2p9MkeOwcewSXtgY19jAb 14 | IalYzn+yIHLqMyJ1H4cA1sBXBooNvkjVd+7niBbE8du2LyHf4avhNHN2tgKIOjpj 15 | GCsitLEqhyQmUUlYC3z8tQbh99b7fqn2kBDnhdSWPojEx84eXQ71IhMPAoGBAOTs 16 | lQ2iT3cqyore02YOieDwSg7n4flAPPG6cTGGQeOyHpNllTy9U/wyEBrHGGSCkjHI 17 | uDICHtYxyBmL6b0H5IyT4vn1Wxm19ecy8zbuK7Lmwd/iWRfKlGr+YQuez/mHVcCG 18 | YMoQvS6j8WOrQxt2wdOMNatgJeJSz66TaRy2QWfNAoGBAOmTWNJN8MSon15itdgq 19 | 3aQj6/SOfOR866h3ktvfpxu85C62eZ+bg4OwVf4J367WVB3LnovvQmYi/ddrcN8h 20 | 2xVqGV020D+DyFwQgnbvdvtNTg2t24dLryP70k8qZ7UmVAXWM+/6i3bLdmbENUCy 21 | 19Ea1XN/quhSpcFr1e37Q133AoGAD5GLXX8BWoBdf+5BgDpS5CpTTwo0EwhsXKAq 22 | XIzd5EdTzwBkktnpYUhiUf/iR8udd6dH55a/VB/UlPAv+DwWLf1MvWUTSf9W9t8/ 23 | LSgrbqJE4x34oyaSy2f7X5fwWu76RPqekH9s7kQWAYo/KRn9eo6Zg8spKGgrWZsK 24 | 1foLHq0CgYBJRjKEY79aNuKCJZw60QPpXodJ65RJufXPz9MgDdoxUOtno8eYPfep 25 | KWWyhJsQXhMJNUMZGvQXRXaaZ3ZZp1e1q18CLh1TqbInC1ODW3L/ZAWCpT9ihcdA 26 | Owj1RL042er59qut/nivipmB5fn1hTbRDLq9rng0fsNU9XlrETbUfg== 27 | -----END RSA PRIVATE KEY----- 28 | -------------------------------------------------------------------------------- /oidc_example/op3/cryptography_keys/key.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN RSA PRIVATE KEY----- 2 | MIIEpAIBAAKCAQEA6LC5Zr1Ng3HN/RF173gSAtcWo3RbnRc+YtE2BZnmNDVe2cep 3 | FmMjYbcum6bonS7JWCTDYd5m0Gt7+yh71YQxF3bjXAEXGbR6w1405emJuurFgfAn 4 | wk8h+o//+DYoF4E9WdbHfi9wRa7tS8pAB2o1JpsH1r3O7wCSPb/cDRIQZ3R5ORjl 5 | +q/PWUFxAjLKe2IAM2rfJulertITnGlxcnzzy9cnIyYgY+gm17hZFZd1qLpNG10M 6 | cyzlwxPl6c+7PagS2nx9Mdv4gUvaNwzgkzQQh9viJfBggc1o86CEPg8RAOqWjUJ8 7 | jHAYJDeRxEFHKGcbOXa2n1ZOBj+FHprIOg60XwIDAQABAoIBAGbZgkFyp/nTCijz 8 | GE1OJavitUtvSXUwim80KOwl37Yw5ulc0pSmIkP1K2E6T7P2M6HFsiSmoIw8DhNw 9 | C/hO00IbVxhaFQNUHjUPhVMgPDQFxGIQLAq7Xnn5PztPeL4W7m6wvAG+5yGt1Y47 10 | 6bw9Shvzx223/OjwODOaA6I1W1HWNmHR3VO2+l7VTvNJ9I80u/PY3nvbRP5t2But 11 | NoepW6XBUnWytQTHbIia+8VpEJ4xQCLFTRSdGCCx/uH2qtCFM74tmEzBSYeQKbra 12 | vcdeMP0NMtz9u1wXpQzcU5xVWoN1lZaXU8X5njsrxTW+A+YRDCY5qyz0mjGCMDFR 13 | 05BgBq0CgYEA8yJ0bjl9uFLBDa8a01g9lDgFrKLA1SbnaTCd7B/K7Yw4XOmyWWzT 14 | NkVk5cIdn5XgMI2p9DuPlLp8VcBxrUeXKqWLenyWwj/O9aYYbtJ3mMG9Ew+z354a 15 | nh2BeyREA3kUFV9T62MAzgGoOLxmeki0Vg9QaNzNZ0TiXRTSgmhaOdUCgYEA9QDK 16 | IcLB2B8u3BDhKlqAi/x74DXwnWr+NH9yXcok17/CfdL65ohlq/8vn9iLIGPm21HD 17 | qElpY8VZ1SN47CbXCpBIYxUOAq1X4z6v2dpmia0wKX9DwGSNq1AjGv/q1XVZWP+L 18 | bwY9iYBtZkzQ6O6gFNYtahU863S1Y1wbl/dde2MCgYByeN5opxjSF+RdFPyEVR9p 19 | EZdaWKaBtPdvRzyTV8SrY0GGPWGDSQGIo9OFeYeJA3+yXICQRRqs48B2f00GUJaT 20 | ta2d1dsFShWfAryuMNmuSklDGDmQJ1hQ6YN0/vYmzHLwPRm90bcnRxB05f85w9Fk 21 | U4GAXbU30e7h9FPIomkYoQKBgQCRdQeMpbEqW9EZTFgyKgsZkSwGgF+7RfNoPaYr 22 | ssDO6oVQ6HqXdbFPPTEMKKbo8wbzfQa5meQdHOTpmqbHJYQUPHcNhEmOI1YDj4sd 23 | vwGTiaQHbwxNZCWL6Qb+l6PXWVVTdlxU7RKJ2RqaWs7JDCs0U7ggvD0v2Vl7hQrI 24 | 3U692wKBgQC65W0277Kk8sj4Gxc/32mkSfnPMCbvxP0qiMt9pracAVTcHfr4ZQKu 25 | qMcPKyVMMYRM1zCc59aLt2L5OUr+WctgRcnnjBRNxLdgcTDxK+FMyzkP9FaUp28M 26 | kbynDyXAdpGxPQ8t18vzjLUjbYWuAZM9+LcgZ6YSV22OjgcdmsH52g== 27 | -----END RSA PRIVATE KEY----- -------------------------------------------------------------------------------- /oidc_example/op3/htdocs/cp_keys/key.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN RSA PRIVATE KEY----- 2 | MIIEpAIBAAKCAQEA1ECrSVVGxHDjCm+gLdGoTPCTeIgTRvin0cE9M6s/GkD0ta3T 3 | y6DClu8GiBml+PQfQtSya5Ezv8EuUXhFhNCpR+3/vMwUjYLBzH5ybAB/NBRL8MJ1 4 | M7WUzm+WSnl1hdYMolX1xy2cy2Um5saItubMONhhVG+hFAsyy9iCDX+FV8anrOPp 5 | CBuYK1B0BaBSafMDus3SR+xq8T6LgTelwr3srJm6Q6A2AzdBsCkm+WcKW534ClRb 6 | MYfMaJQlBYpmtnQt/k8ctUWX1k/kIAJ7GR5Wh/7555wCcVJbnoc4iNgPs1KyNfmH 7 | v2RiEr2lf0BmxpkzUGnNQNaOVqB1/D1ygsNMAwIDAQABAoIBAQDUMOqMX5Jl5K01 8 | y66I3+avNHtZrkAHXaL4UYVL2FE3f+SklGj+U3L1zXPsMCf7IKL3/wd3/iuL8ibK 9 | D8EALFJvtIFMT4HkjuoL9AWT71M7z2a0BNOCpG9liazoO1DAQeNTjzgsrW7o7/Da 10 | GXSn1UgpNDjpXsfb7+4SWBp8QBYgTok/dx/nZyewZsVtkCsnwrm26Y2k8nhynLm1 11 | nGkQBbRKweQJ5O3PrsrZhRO9OXJ10WK/lYHVqRnu4dam8iP8JHKz5CQN/O7x0HI5 12 | VHHAl0Az4+YZe8wrdNhgtkkOhGxwOtIK5Q3eV8st51YBpWbMiDtQ53ghisWtyNzg 13 | EyphOksxAoGBAO1bTWHsuo6srY4cEJJlJ/C7W237iUM2p9MkeOwcewSXtgY19jAb 14 | IalYzn+yIHLqMyJ1H4cA1sBXBooNvkjVd+7niBbE8du2LyHf4avhNHN2tgKIOjpj 15 | GCsitLEqhyQmUUlYC3z8tQbh99b7fqn2kBDnhdSWPojEx84eXQ71IhMPAoGBAOTs 16 | lQ2iT3cqyore02YOieDwSg7n4flAPPG6cTGGQeOyHpNllTy9U/wyEBrHGGSCkjHI 17 | uDICHtYxyBmL6b0H5IyT4vn1Wxm19ecy8zbuK7Lmwd/iWRfKlGr+YQuez/mHVcCG 18 | YMoQvS6j8WOrQxt2wdOMNatgJeJSz66TaRy2QWfNAoGBAOmTWNJN8MSon15itdgq 19 | 3aQj6/SOfOR866h3ktvfpxu85C62eZ+bg4OwVf4J367WVB3LnovvQmYi/ddrcN8h 20 | 2xVqGV020D+DyFwQgnbvdvtNTg2t24dLryP70k8qZ7UmVAXWM+/6i3bLdmbENUCy 21 | 19Ea1XN/quhSpcFr1e37Q133AoGAD5GLXX8BWoBdf+5BgDpS5CpTTwo0EwhsXKAq 22 | XIzd5EdTzwBkktnpYUhiUf/iR8udd6dH55a/VB/UlPAv+DwWLf1MvWUTSf9W9t8/ 23 | LSgrbqJE4x34oyaSy2f7X5fwWu76RPqekH9s7kQWAYo/KRn9eo6Zg8spKGgrWZsK 24 | 1foLHq0CgYBJRjKEY79aNuKCJZw60QPpXodJ65RJufXPz9MgDdoxUOtno8eYPfep 25 | KWWyhJsQXhMJNUMZGvQXRXaaZ3ZZp1e1q18CLh1TqbInC1ODW3L/ZAWCpT9ihcdA 26 | Owj1RL042er59qut/nivipmB5fn1hTbRDLq9rng0fsNU9XlrETbUfg== 27 | -----END RSA PRIVATE KEY----- 28 | -------------------------------------------------------------------------------- /docker/op_test/cert.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN CERTIFICATE----- 2 | MIIEvTCCAqWgAwIBAgIJAMjU19gt2p1PMA0GCSqGSIb3DQEBCwUAMBIxEDAOBgNV 3 | BAMMB29wLXRlc3QwHhcNMTgxMDEyMTM0NTExWhcNMTkxMDEyMTM0NTExWjASMRAw 4 | DgYDVQQDDAdvcC10ZXN0MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEA 5 | 2zDYX4tAjA+9Zo4hZdvAzs7mO+b0Lc7Fj878fv0KTBcl4qifi9nVJ87T41+TSOJw 6 | O+w/J21OoFmPKdygVFYRV6bxeu7TEUTgOlE8hOqFULFnryIt1wYQNMGN5GWF3jcF 7 | eDzWVI+gpnn2EkZHx+ADn+y+zR38CBHyyQbqcIM92wTrttGvEQaDJMCEtP4rP68G 8 | TrC3d7MgMrsESp38SaEm1HylMr5VkQ6e6cILlGiXe5QbHr44eGUmQ63PtfEfJFDT 9 | 7JhllRA+C6POdm9OkUoyfiWSaJTHXAl9bKT1SzTnCZH09bS2RKsYZN7WYOEWlrg0 10 | cK0T4qqkGK6LNDIjkXxwm5tqTy55TGHDdfLBoX/sSGVuq6IcO2NZwz+5KN6AeYgY 11 | KJJrYDcHzbmdbckjYKt6FWc7tD96hmkxVR4zwmZXmUeCeH8UZhwTkA0ZFi3Bs1ZA 12 | rpTIbzvCoFQbuPx1evme9USZzM5LxG6OJGYLpQfmJGkXkUfNHPeiaqb3JdHzXdeU 13 | hbLZLHB+3LIg4GsOLSOFJ75vtOYOETQu0T+Bp+qxFMR76CUzzi9fDZf1B1RyJHRe 14 | JzM1jOLIc8wFCNmJ6o3O+dZmv+E3t52Y5dOBAAb8BwldHeeWy5IjkEx4HhFqjnb8 15 | 2r8X2bELIYnkg887IsxhpM3kah9ClDRGk3rkU0QFmvMCAwEAAaMWMBQwEgYDVR0R 16 | BAswCYIHb3AtdGVzdDANBgkqhkiG9w0BAQsFAAOCAgEALsnFnDrX9vdJb43fwxRz 17 | 8r2qiUPF7Tc/oGKEueMRcykw5k/vtoKqqnC8WVDYsP7Ga6f0KP7bgJxR2dx5IikY 18 | Ajzdd7p/5mKBj507STksNmzESdLGuft3JBdidsYU3G2359uJ2+lHcMgScRWUcX+F 19 | /JKYp336t8RcjWcFSeb/wJrHJIxUOL3rkw619LlW/+T1ZlLxCpV9hBuLmqg+3MbB 20 | 9/k5ixdGpnJAo6g6pCmH5r0der20qumMjnecz0MpUXG60f2ODG00G90jXdIGdPdk 21 | tvp41MMqtKQmWHyQxNVn/Bb0JperKf7ECpZe60k97Pfwn5Ei9jzVnMvsdJSVFHdk 22 | L7/H42u1cbCW+UxJmJkbTpWmlv0DW73ZIFaztddS35uheWXvUWyMuA8yxcc7uyjG 23 | dWvwSNuGX6cm9YOfrAZswd2JCYL0LbII6xxKH1/JxW2ScC6M2zMe7CtAmbuIS3Ca 24 | 4wZZLYZd15MhvwHouvyjWfBBrJM2fRTcURCOag2EsPuzfip3lLDAp3TNW9lbobBl 25 | ZLNPhhd9ErKlnb1PHR/C8FAkOEk5uaF+FYb+yUP/LUjE4ydRqQ5vZEc59CG1mb2R 26 | z1b8xDF54lrm7fQliGSJeuc7qLXibvn8m/dIfnTsQB+446VVXuhHgWgVt8AJKIkP 27 | ngB61uUFVpzUGM6d3Xpqnts= 28 | -----END CERTIFICATE----- 29 | -------------------------------------------------------------------------------- /docker/rp_test/cert.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN CERTIFICATE----- 2 | MIIEvTCCAqWgAwIBAgIJAKA0L6TMBrDXMA0GCSqGSIb3DQEBCwUAMBIxEDAOBgNV 3 | BAMMB3JwLXRlc3QwHhcNMTgxMDEyMTM0NDU4WhcNMTkxMDEyMTM0NDU4WjASMRAw 4 | DgYDVQQDDAdycC10ZXN0MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEA 5 | xCeKAs1ccDCfnVPN0xti7LNI41tX0qw7/RnaJyQcyAZ4LQ/zyHZpsTGVdkuXHwEl 6 | ddCIWXxG2tXDmqpBgBblFSMui560h6n8+iklCIX4J5mEigEmb47SVcxnXDqvEOQs 7 | uvLq8gF7nFhPT/pRaDQEfS3L/UWWK+UpLKhInwRhoo6p0a7ce8bUg3RzZR0Lioyw 8 | ADJqCoDzM5A1vhzQIyxZIqHsa14lth27id5W7BupsidSLX9+XpuN/ZFRS7k4P6hP 9 | Apea2FjzorkYKJos6cOebPMKIK8ynKFajoKwwkuX5zx9jp7BTJMBIC3WFapJE/Px 10 | DUEfTELTrggX57iRDZGPZHWthg4MkUcH6upPsA5qbOotCwUBiexLakF2EIw52+IR 11 | Dp7gnOrF5trHMh5An3nJ06OmpQ6Kzr08i9gPOTKJbkD9XN9I/2TztRuPApXG0xua 12 | 3lkaOe4n1Q4DWNzOtFdxjEfxaFKg+5lTAo/bJLHwXq5HFWaGleIcFx6Bnz8UdS0r 13 | flZbcw9jdvUTr3EYJgbN0v7ihagheVe7C5DJTg4C8bQBtc6ODfZtuwN9qYBz8B7F 14 | bullE/bLJXtz+RBlSJqtnRygLp2oD2zQfYBFaRS2ofmTwpRzG6v6k0NrGV8vz1/x 15 | AwPvDAxRxxlEDY+CZIsTjRWTwb6hnnJkY90QrpjV7a8CAwEAAaMWMBQwEgYDVR0R 16 | BAswCYIHcnAtdGVzdDANBgkqhkiG9w0BAQsFAAOCAgEACdjPx50+OkO05Sy8xAxY 17 | TPj293fJHDRZa5xopNWsAQL/Wyq4vyCFUt1liYWzX3xr+YkMhwZikv3ibxzviR0Y 18 | AZh5FMsQQiKBFGxksFOoXot5LKGIn+NyaOhKPfOl9aRZHe6tTDDKP8rH0uv0OyJI 19 | eAREGIGpQkAtXhOwtKn8DvsEa/0z74+nxPZH0gTyhZhk0tZIYPphn8+HlUAngSnq 20 | yjuNxrNqK85xDu9FF35IKpxivlm+X9/igXikpxosddNcbDDXWqZrm8/2hHdTPiaw 21 | t88hpHnFqInXtvQB0IxPOJoG9eVHyAcBIWZwN4BExzVJuQFDpsgTdILjWyx/14mQ 22 | IjSEVdsCIex702S6O2/GGq1HoaKj2ObM4Ix1r4S/F3xWYYWuIHrtmlSZuRFIn7cq 23 | bTXKqvzVGGgA+KYZrd6R4mChc3l/3Ic4PlZ5AQhTEZm8A2VvynLoJwyeG8ArsH0H 24 | VE3SInQYu1FtME/ZQvzSGXcFhl4j05jenhj6vpfnsljVEIPRby8YigOlH+vBDMjd 25 | gvdCKXjkpLAuGyWTOq+Ub5gzvcSX9+FnxQ8SipaL0ej+F64yehVZDjgQ0B7CTBJM 26 | hfxcbJ9fPGWHfvI0RSW8pWqVzGa1umW/N4nl/3/4T6+eFcW97C6k/SabXemtGJkn 27 | y9upB8dfLu8hdNt/WorV4AA= 28 | -----END CERTIFICATE----- 29 | -------------------------------------------------------------------------------- /src/oic/utils/authn/client_saml.py: -------------------------------------------------------------------------------- 1 | import base64 2 | 3 | from oic.utils.authn.client import CLIENT_AUTHN_METHOD 4 | from oic.utils.authn.client import ClientAuthnMethod 5 | 6 | __author__ = "rolandh" 7 | 8 | SAML2_BEARER_ASSERTION_TYPE = "urn:ietf:params:oauth:client-assertion-type:saml2-bearer" 9 | 10 | try: 11 | from saml2.saml import assertion_from_string 12 | except ImportError: 13 | pass 14 | else: 15 | 16 | class SAML2AuthnMethod(ClientAuthnMethod): 17 | """Authenticating clients using the SAML2 assertion profile.""" 18 | 19 | def construct(self, cis, assertion=None, **kwargs): 20 | """ 21 | Create the HTTP request. 22 | 23 | :param cis: The request 24 | :param assertion: A SAML2 Assertion 25 | :param kwargs: Extra arguments 26 | :return: Constructed HTTP arguments, in this case none 27 | """ 28 | cis["client_assertion"] = base64.urlsafe_b64encode(assertion) 29 | cis["client_assertion_type"] = SAML2_BEARER_ASSERTION_TYPE 30 | 31 | def verify(self, areq, **kwargs): 32 | xmlstr = base64.urlsafe_b64decode(areq["client_assertion"]) 33 | try: 34 | assertion = assertion_from_string(xmlstr) 35 | except Exception: 36 | # FIXME: This should catch specific exceptions thrown by `assertion_from_string` 37 | return False 38 | return self._verify_saml2_assertion(assertion) 39 | 40 | def _verify_saml2_assertion(self, assertion): 41 | subject = assertion.subject 42 | audience = [] 43 | for ar in subject.audience_restiction: 44 | for aud in ar.audience: 45 | audience.append(aud) 46 | 47 | CLIENT_AUTHN_METHOD["saml2_bearer"] = SAML2AuthnMethod 48 | -------------------------------------------------------------------------------- /doc/conf.py: -------------------------------------------------------------------------------- 1 | import alabaster 2 | import os 3 | import sys 4 | 5 | sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), '..', 'src'))) 6 | 7 | extensions = [ 8 | 'sphinx.ext.autodoc', 9 | 'sphinx.ext.napoleon', 10 | 'sphinxcontrib.autodoc_pydantic', 11 | ] 12 | 13 | autoclass_content = 'both' # Merge the __init__ docstring into the class docstring. 14 | autodoc_member_order = 'bysource' # Order by source ordering 15 | autodoc_pydantic_model_show_config = True 16 | autodoc_pydantic_settings_show_json = False 17 | 18 | templates_path = ['_templates'] 19 | 20 | source_suffix = '.rst' 21 | 22 | master_doc = 'index' 23 | 24 | project = u'pyoidc' 25 | 26 | copyright = u'2014, Roland Hedberg' 27 | 28 | version = '0.1' 29 | 30 | release = '0.1' 31 | 32 | exclude_patterns = ['_build'] 33 | 34 | pygments_style = 'sphinx' 35 | 36 | html_theme_path = [alabaster.get_path()] 37 | 38 | html_theme = 'alabaster' 39 | 40 | html_static_path = ['_static'] 41 | 42 | htmlhelp_basename = 'pyoidcdoc' 43 | 44 | html_theme_options = { 45 | 'description': '', 46 | 'github_button': False, 47 | 'github_user': 'its-dirg', 48 | 'github_repo': 'saml2testGui', 49 | 'github_banner': False, 50 | 51 | } 52 | 53 | html_sidebars = { 54 | '**': [ 55 | 'about.html', 56 | 'navigation.html', 57 | 'searchbox.html', 58 | 'donate.html', 59 | ] 60 | } 61 | 62 | man_pages = [ 63 | ('index', 'pyoidc', u'pyoidc Documentation', 64 | [u'Roland Hedberg'], 1) 65 | ] 66 | 67 | latex_elements = {} 68 | 69 | latex_documents = [ 70 | ('index', 'pyoidc.tex', u'pyoidc Documentation', 71 | u'Roland Hedberg', 'manual'), 72 | ] 73 | 74 | texinfo_documents = [ 75 | ('index', 'pyoidc', u'pyoidc Documentation', 76 | u'Roland Hedberg', 'pyoidc', 'One line description of project.', 77 | 'Miscellaneous'), 78 | ] 79 | -------------------------------------------------------------------------------- /src/oic/utils/shelve_wrapper.py: -------------------------------------------------------------------------------- 1 | import shelve # nosec 2 | 3 | __author__ = "danielevertsson" 4 | 5 | 6 | class ShelfWrapper(object): 7 | def __init__(self, filename): 8 | self.filename = filename 9 | 10 | def keys(self): 11 | db = self._reopen_database() 12 | return db.keys() 13 | 14 | def __len__(self): 15 | db = self._reopen_database() 16 | return db.__len__() 17 | 18 | def has_key(self, key): 19 | return key in self 20 | 21 | def __contains__(self, key): 22 | db = self._reopen_database() 23 | return db.__contains__(key) 24 | 25 | def get(self, key, default=None): 26 | db = self._reopen_database() 27 | return db.get(key, default) 28 | 29 | def __getitem__(self, key): 30 | db = self._reopen_database() 31 | return db.__getitem__(key) 32 | 33 | def __setitem__(self, key, value): 34 | db = self._reopen_database() 35 | db.__setitem__(key, value) 36 | 37 | def __delitem__(self, key): 38 | db = self._reopen_database() 39 | db.__delitem__(key) 40 | 41 | def _reopen_database(self): 42 | return shelve.open(self.filename, writeback=True) # nosec 43 | 44 | 45 | def open(filename): 46 | """ 47 | Open a persistent dictionary for reading and writing. 48 | 49 | The filename parameter is the base filename for the underlying 50 | database. As a side-effect, an extension may be added to the 51 | filename and more than one file may be created. The optional flag 52 | parameter has the same interpretation as the flag parameter of 53 | anydbm.open(). The optional protocol parameter specifies the 54 | version of the pickle protocol (0, 1, or 2). 55 | 56 | See the module's __doc__ string for an overview of the interface. 57 | """ 58 | return ShelfWrapper(filename) 59 | -------------------------------------------------------------------------------- /tests/test_shelve_wrapper.py: -------------------------------------------------------------------------------- 1 | # pylint: disable=missing-docstring,no-self-use,redefined-outer-name 2 | import os 3 | 4 | import pytest 5 | 6 | from oic.utils import shelve_wrapper 7 | 8 | __author__ = "mathiashedstrom" 9 | 10 | VALUES = {"key_1": "val_1", "key_2": "val_2", "key_3": "val_3"} 11 | 12 | 13 | def _eq(l1, l2): 14 | return set(l1) == set(l2) 15 | 16 | 17 | @pytest.fixture 18 | def db(tmpdir): 19 | return shelve_wrapper.open(os.path.join(tmpdir.strpath, "test_db_shelve_wrapper")) 20 | 21 | 22 | @pytest.fixture 23 | def populated_db(db): 24 | for k, v in VALUES.items(): 25 | db[k] = v 26 | 27 | return db 28 | 29 | 30 | class TestShelfWrapper(object): 31 | def test_keys(self, populated_db): 32 | assert _eq(populated_db.keys(), VALUES.keys()) 33 | 34 | def test_contains(self, populated_db): 35 | for k in VALUES.keys(): 36 | assert k in populated_db 37 | 38 | assert "NO_KEY" not in populated_db 39 | 40 | def test_get(self, populated_db): 41 | for k, v in VALUES.items(): 42 | assert populated_db.get(k) == v 43 | 44 | assert populated_db.get("NO_KEY") is None 45 | 46 | def test_getitem(self, populated_db): 47 | for k, v in VALUES.items(): 48 | assert populated_db[k] == v 49 | 50 | with pytest.raises(KeyError): 51 | populated_db["NO_KEY"] # pylint: disable=pointless-statement 52 | 53 | def test_delitem(self, populated_db): 54 | key = list(VALUES.keys())[0] 55 | del populated_db[key] 56 | 57 | with pytest.raises(KeyError): 58 | populated_db[key] # pylint: disable=pointless-statement 59 | 60 | def test_len(self, db): 61 | assert len(db) == 0 62 | 63 | length = 4 64 | for i in range(length): 65 | db["key_{}".format(i)] = "foo" 66 | assert len(db) == length 67 | -------------------------------------------------------------------------------- /oidc_example/op2/htdocs/op_session_iframe.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Check session iframe 6 | 7 | 8 | 9 | 10 | 55 | 56 | 57 | -------------------------------------------------------------------------------- /oidc_example/op3/htdocs/op_session_iframe.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Check session iframe 6 | 7 | 8 | 9 | 10 | 55 | 56 | 57 | -------------------------------------------------------------------------------- /src/oic/utils/template_render.py: -------------------------------------------------------------------------------- 1 | """Implementation of basic templating engine.""" 2 | 3 | FORM_POST = """ 4 | 5 | Submit This Form 6 | 7 | 8 |
9 | {html_inputs} 10 |
11 | 12 | """ 13 | 14 | 15 | VERIFY_LOGOUT = """ 16 | 17 | Please verify logout 18 | 19 | 20 |
21 | {html_inputs} 22 | 23 | 24 | 25 | """ 26 | 27 | 28 | def inputs(form_args): 29 | """Create list of input elements.""" 30 | element = [] 31 | for name, value in form_args.items(): 32 | element.append( 33 | ''.format(name, value) 34 | ) 35 | return "\n".join(element) 36 | 37 | 38 | class TemplateException(Exception): 39 | """Custom exception from TemplateEngine.""" 40 | 41 | 42 | def render_template(template_name, context): 43 | """ 44 | Render specified template with the given context. 45 | 46 | Templates are defined as strings in this module. 47 | """ 48 | if "action" not in context: 49 | raise TemplateException("Missing action in context.") 50 | if template_name == "form_post": 51 | context["html_inputs"] = inputs(context.get("inputs", {})) 52 | return FORM_POST.format(**context) 53 | elif template_name == "verify_logout": 54 | form_args = { 55 | "id_token_hint": context.get("id_token_hint", ""), 56 | "post_logout_redirect_uri": context.get("post_logout_redirect_uri", ""), 57 | } 58 | context["html_inputs"] = inputs(form_args) 59 | return VERIFY_LOGOUT.format(**context) 60 | raise TemplateException("Unknown template name.") 61 | -------------------------------------------------------------------------------- /oidc_example/op2/README.md: -------------------------------------------------------------------------------- 1 | OP2 Example 2 | =========== 3 | 4 | Getting Started 5 | --------------- 6 | 7 | git clone https://github.com/CZ-NIC/pyoidc.git 8 | cd pyoidc/oidc_example/op2/ 9 | python3 -m venv venv && . venv/bin/activate 10 | pip install -r requirements.txt -c constraints.txt 11 | 12 | 13 | ### Client Management 14 | 15 | To be able to start up the project you have to create a client in the `client_db` file. 16 | This is done by the CLI application `client_management.py`. 17 | The allowed redirect_uris must be provided, then a client_id and client_secret is generated. See some samples below. 18 | 19 | Add new client by starting client_management tool with -c and answer the upcoming questions: 20 | 21 | python ../../src/oic/utils/client_management.py -c client_db 22 | 23 | List clients in your new `client_db` file: 24 | 25 | python ../../src/oic/utils/client_management.py -l client_db 26 | 27 | Show values for a specific client: 28 | 29 | ../../src/oic/utils/client_management.py -s -i client_db 30 | 31 | Should yield something like... 32 | 33 | { 34 | 'client_secret': 'e4d70473ac2db4adbf9fb765ec56d34076720fa52e8edebdbbc368c2', 35 | 'redirect_uris': [['https://myserver.com/callback', None], ['https://eny5ndkgibofe.x.pipedream.net/', None]], 36 | 'client_salt': 'vULdf5Q8', 37 | 'client_id': '9D8nyeoaIfE8' 38 | } 39 | 40 | To show all command-line options: 41 | 42 | ../../src/oic/utils/client_management.py -h client_db 43 | 44 | 45 | ### Running the Server 46 | 47 | ./server.py -p 8040 config_simple.py 48 | 49 | To explore options for running the server, invoke it with `--help` to get an overview. 50 | Then dig in and read through the source! :) 51 | 52 | 53 | ### Good to Know 54 | 55 | If the `max_age` is not set in the authorization request the `max_age` will be 0 and the authorization cookie will never expire. 56 | -------------------------------------------------------------------------------- /src/oic/utils/restrict.py: -------------------------------------------------------------------------------- 1 | import inspect 2 | import json 3 | import sys 4 | 5 | __author__ = "roland" 6 | 7 | 8 | def single(restriction, cinfo): 9 | for s in restriction: 10 | try: 11 | if len(cinfo[s]) != 1: 12 | return "Too Many {}".format(s) 13 | except KeyError: 14 | pass 15 | return "" 16 | 17 | 18 | def map_grant_type2response_type(restriction, cinfo): 19 | if "grant_types" in cinfo and "response_types" in cinfo: 20 | for g, r in restriction.items(): 21 | if g in cinfo["grant_types"] and r in cinfo["response_types"]: 22 | pass 23 | elif g in cinfo["grant_types"] or r in cinfo["response_types"]: 24 | return "grant_type didn't match response_type" 25 | return "" 26 | 27 | 28 | def map(restriction, cinfo): 29 | for fname, spec in restriction.items(): 30 | func = factory("map_" + fname) 31 | resp = func(spec, cinfo) 32 | if resp: 33 | return resp 34 | return "" 35 | 36 | 37 | def allow(restriction, cinfo): 38 | for param, args in restriction.items(): 39 | try: 40 | _cparam = cinfo[param] 41 | except KeyError: 42 | continue 43 | 44 | if isinstance(_cparam, str): 45 | if _cparam not in args: 46 | return "Not allowed to register with {}={}".format(param, _cparam) 47 | else: 48 | if not set(_cparam).issubset(args): 49 | return "Not allowed to register with {}={}".format( 50 | param, json.dumps(_cparam) 51 | ) 52 | 53 | return "" 54 | 55 | 56 | def assign(restriction, cinfo): 57 | cinfo.update(restriction) 58 | 59 | 60 | def factory(name): 61 | for fname, obj in inspect.getmembers(sys.modules[__name__]): 62 | if inspect.isfunction(obj): 63 | if fname == name: 64 | return obj 65 | 66 | return None 67 | -------------------------------------------------------------------------------- /tests/test_aes.py: -------------------------------------------------------------------------------- 1 | import os 2 | 3 | import pytest 4 | 5 | from oic.utils.aes import AEAD 6 | from oic.utils.aes import AESError 7 | from oic.utils.aes import decrypt 8 | from oic.utils.aes import encrypt 9 | 10 | 11 | def test_encrypt_decrypt(): 12 | key_ = b"1234523451234545" # 16 byte key 13 | # Iff padded the message doesn't have to be multiple of 16 in length 14 | msg_ = "ToBeOrNotTobe W.S." 15 | iv_ = os.urandom(16) 16 | encrypted_msg = encrypt(key_, msg_, iv_) 17 | txt = decrypt(key_, encrypted_msg, iv_) 18 | assert txt == msg_ 19 | 20 | encrypted_msg = encrypt(key_, msg_, 0) 21 | txt = decrypt(key_, encrypted_msg, 0) 22 | assert txt == msg_ 23 | 24 | 25 | @pytest.fixture 26 | def aead_key(): 27 | return os.urandom(32) 28 | 29 | 30 | @pytest.fixture 31 | def aead_iv(): 32 | return os.urandom(16) 33 | 34 | 35 | @pytest.fixture 36 | def cleartext(): 37 | return b"secret sauce" 38 | 39 | 40 | def test_AEAD_good(aead_key, aead_iv, cleartext): 41 | extra = ["some", "extra", "data"] 42 | k = AEAD(aead_key, aead_iv) 43 | for d in extra: 44 | k.add_associated_data(d) 45 | ciphertext, tag = k.encrypt_and_tag(cleartext) 46 | 47 | # get a fresh AEAD object 48 | c = AEAD(aead_key, aead_iv) 49 | for d in extra: 50 | c.add_associated_data(d) 51 | cleartext2 = c.decrypt_and_verify(ciphertext, tag) 52 | assert cleartext2 == cleartext 53 | 54 | 55 | def test_AEAD_bad_aad(aead_key, aead_iv, cleartext): 56 | extra = ["some", "extra", "data"] 57 | k = AEAD(aead_key, aead_iv) 58 | for d in extra: 59 | k.add_associated_data(d) 60 | ciphertext, tag = k.encrypt_and_tag(cleartext) 61 | 62 | # get a fresh AEAD object 63 | c = AEAD(aead_key, aead_iv) 64 | # skip one aad item, MAC is wrong now 65 | for d in extra[:1]: 66 | c.add_associated_data(d) 67 | 68 | with pytest.raises(AESError): 69 | c.decrypt_and_verify(ciphertext, tag) 70 | -------------------------------------------------------------------------------- /oidc_example/simple_op/settings.yaml.example: -------------------------------------------------------------------------------- 1 | provider: 2 | keys: 3 | - type: RSA 4 | key: keys/key.pem 5 | use: [enc, sig] 6 | - type: EC 7 | crv: P-256 8 | use: [sig] 9 | - type: EC 10 | crv: P-256 11 | use: [enc] 12 | 13 | authn: 14 | - acr: password 15 | class: provider.authn.user_pass.UserPass 16 | kwargs: 17 | db: 18 | class: provider.authn.util.JSONDictDB 19 | kwargs: 20 | json_path: passwd.json 21 | template: user_pass.jinja2 22 | page_header: "Testing log in" 23 | submit_btn: "Get me in!" 24 | user_label: "Nickname" 25 | passwd_label: "Secret sauce" 26 | - acr: mail_two_factor 27 | class: provider.authn.two_factor.MailTwoFactor 28 | kwargs: 29 | user_db: 30 | class: provider.authn.util.JSONDictDB 31 | kwargs: 32 | json_path: users.json 33 | passwd_db: 34 | class: provider.authn.util.JSONDictDB 35 | kwargs: 36 | json_path: passwd.json 37 | smtp_server: smtp.example.com 38 | outgoing_sender: sender@example.com 39 | #- acr: yubikey 40 | # class: provider.authn.yubikey.YubicoOTP 41 | # kwargs: 42 | # yubikey_db: # mapping from yubikey public identity to local username 43 | # class: provider.authn.util.JSONDictDB 44 | # kwargs: 45 | # json_path: yubikeys.json 46 | # validation_server: https://example.com/wsapi/2.0/verify 47 | # client_id: 123456789 48 | # secret_key: abcdefghi 49 | 50 | userinfo: 51 | class: provider.authn.util.JSONDictDB 52 | kwargs: 53 | json_path: users.json 54 | 55 | server: 56 | cert: certs/localhost.crt 57 | key: certs/localhost.key 58 | cert_chain: 59 | template_dirs: 60 | - src/provider/authn/templates -------------------------------------------------------------------------------- /src/oic/utils/sanitize.py: -------------------------------------------------------------------------------- 1 | import re 2 | from collections.abc import Mapping 3 | from textwrap import dedent 4 | 5 | SENSITIVE_THINGS = { 6 | "password", 7 | "passwd", 8 | "client_secret", 9 | "code", 10 | "authorization", 11 | "access_token", 12 | "refresh_token", 13 | } 14 | 15 | REPLACEMENT = "" 16 | 17 | SANITIZE_PATTERN = r""" 18 | (?' 33 | """ 34 | 35 | SANITIZE_PATTERN = dedent(SANITIZE_PATTERN.format("|".join(SENSITIVE_THINGS))) 36 | SANITIZE_REGEX = re.compile(SANITIZE_PATTERN, re.VERBOSE | re.IGNORECASE | re.UNICODE) 37 | 38 | 39 | def redacted(key, value): 40 | if key in SENSITIVE_THINGS: 41 | return (key, REPLACEMENT) 42 | return (key, value) 43 | 44 | 45 | def sanitize(potentially_sensitive): 46 | if isinstance(potentially_sensitive, Mapping): 47 | # Makes new dict so we don't modify the original 48 | # Also case-insensitive--possibly important for HTTP headers. 49 | return dict(redacted(k.lower(), v) for k, v in potentially_sensitive.items()) 50 | else: 51 | if not isinstance(potentially_sensitive, str): 52 | potentially_sensitive = str(potentially_sensitive) 53 | return SANITIZE_REGEX.sub(r"\1{}".format(REPLACEMENT), potentially_sensitive) 54 | -------------------------------------------------------------------------------- /oidc_example/rp3/README: -------------------------------------------------------------------------------- 1 | A simple but fully functioning OpenID Connect Relying Party implementation. 2 | 3 | Configuration 4 | ============= 5 | 6 | This is the first thing you have to deal with. conf.py.example contains a 7 | boilerplate for you to modify to your liking. 8 | 9 | BASE and PORT should be obvious, it's where the RP resides. 10 | 11 | SERVER_KEY, SERVER_CERT and CA_BUNDLE are used for TLS/SSL. 12 | 13 | ME is information that is registered with the OP on dynamic client 14 | registration. There are lots more values that you can register, a list can be 15 | found in the standard 16 | http://openid.net/specs/openid-connect-registration-1_0.html#ClientMetadata 17 | redirect_uris is the only one that is required all the rest are optional. 18 | 19 | BEHAVIOUR specifies parameters to the Authentication Request. 20 | If response_types are omitted in ME (which it is in the example) then the 21 | default is that the RP will only use the code response type. 22 | 23 | CLIENTS is a list of known OPs with their behaviour and one entry for 24 | uknown OPs. For unknown OPs it is assumed that discovery and registration can 25 | be performed dynamically. 26 | If everything is done dynamically then there are three stages that are performed: 27 | 28 | 1) Using webfinger to find the publisher of the Provider Information 29 | 2) Asking for the Provider Information 30 | 3) Registering the Client with the OP 31 | 32 | For different reasons OPs can decide not to support one or more of these. 33 | If they don't the the information has to be found in some out-of-band way 34 | and entered into the configuration. 35 | 36 | If (1) is not supported a srv_discovery_url has to be provided. 37 | If (2) is not supported provider_info has to be provided. 38 | If (3) is not supported client_registration has to be provided. 39 | 40 | If (3) is supported you have to provide client_info which is the information 41 | that is going to be registered with the OP. This allows you to register 42 | different information with different OPs. 43 | 44 | -------------------------------------------------------------------------------- /doc/examples/tls.rst: -------------------------------------------------------------------------------- 1 | TLS configuration 2 | ================= 3 | 4 | Both the OP and the RP side make HTTPS based requests 5 | to various endpoints like webfinger, token endpoints and so on. 6 | 7 | So this is used by various classes, a non exhaustive list: 8 | 9 | * :py:class:`oic.oauth2.Client` 10 | * :py:class:`oic.oic.Client` 11 | * :py:class:`oic.oic.Provider` 12 | * :py:class:`oic.oauth2.Provider` 13 | * :py:class:`oic.utils.keyio.KeyJar` 14 | * :py:class:`oic.utils.keyio.KeyBundle` 15 | 16 | Server certificate verification 17 | ------------------------------- 18 | 19 | If you want to use the library you should have a working 20 | TLS certificate verification setup, as OAuth2/OIDC depends 21 | on TLS for some of its security properties. 22 | 23 | If you do nothing and just use all the default settings, certificates 24 | will be verified using the global settings as documented 25 | for the python requests library. 26 | 27 | .. seealso:: 28 | 29 | Requests SSL Cert Verification 30 | http://docs.python-requests.org/en/master/user/advanced/#ssl-cert-verification 31 | 32 | You can customize the setting with the `verify_ssl` option to various classes. 33 | The semantics follow the definition of the `verify` option for requests, see above. 34 | 35 | In short, set `verify_ssl` to: 36 | 37 | ``True`` 38 | Verify against the globally configured CA certificates. 39 | 40 | ``False`` 41 | Do not verify any certificates. Not recommended. 42 | 43 | ``path to a ca bundle`` 44 | Use the given CA bundle for verification. 45 | 46 | ``path to ca directory`` 47 | Use the directory as a source for trusted CA certificates. 48 | 49 | 50 | Client side certificates 51 | ------------------------ 52 | 53 | Some classes allow the configuration of client side TLS certificates 54 | for mutual authentication. You can configure it with the `client_cert` 55 | option, which follows the semantics of the request libraries `cert` 56 | option. 57 | -------------------------------------------------------------------------------- /oauth_example/as/tre.jwks: -------------------------------------------------------------------------------- 1 | {"keys": [{"n": "y9BozfJBWeAJ-tDPUMy4azOItS161t7f-jb3vyVsLlLMc_xX-aRnRk7dYwn7kZi4wVtgWlIlQb6qniIlHBG9u36aflYfXp-VNWDKdTva1X3iOvPIg75ljBOujiB5--r0_X1i_VDV4HVzmClDX7k397m_0-EfrzN3srY9yALB5EGHFckv_C9DGGZJRGjam9ogXarXyYpPwLqrH0_g5LmB6tOshfoi6bRrxC1zCK6YlWlaga2RSHP2W7idO3QREpXGk5njYs47OUK8rNVnNkv_2I2N5YKI_lzJvu5knze2leGlii0AweB_5bFFGwKjefp4F4SsMozRjQJwPawMpfFP2Q", "kty": "RSA", "kid": "tre0", "use": "enc", "d": "DuUHv7iQTOaPUXswkhL_-DpKJ5QaXyO8YmoMICCL6nURGCSCc7iaITHSwoc10WMD6izeGq5YUYNtrxmc334EFl9vIBlK-MHjUtpkY_265Ua_lszUOXKGM8mIGb68WMFyjtLlaNCfQg8l9-JYwuEDkCZM40msudj3oAHAUUKy_kCn85O_PywYsIERVdUbDhwT6U14YXcVNuXo4XfJTIvS227TcFy_5APb0hlm-La_KbmmP--nw6teq-CRKHjl7L_eIOiHGHUWkKedBhKwzoirGRuxH8z3fSHbOK9Zo-1KZS-WGHwna5BCjEYCVnPexCb_pxQYRcVLhONULAOHcF60_Q", "q": "6mc5ojrKNoaV0dppa1hqT5vtF9Ob8rMF_hQgp1oBF2tiQ6MezNY3GzuG-09yeG1gRNWYuGo-PmV7nf1iO9OiHDFcC-mPDox_2tKNkBpMRwI5a0jwegaHvA4yg6F-lWmj9DDLGdqkSZDUETFkxcyfe0bekWigX57RQiKMMglAWs0", "e": "AQAB"}, {"n": "y9BozfJBWeAJ-tDPUMy4azOItS161t7f-jb3vyVsLlLMc_xX-aRnRk7dYwn7kZi4wVtgWlIlQb6qniIlHBG9u36aflYfXp-VNWDKdTva1X3iOvPIg75ljBOujiB5--r0_X1i_VDV4HVzmClDX7k397m_0-EfrzN3srY9yALB5EGHFckv_C9DGGZJRGjam9ogXarXyYpPwLqrH0_g5LmB6tOshfoi6bRrxC1zCK6YlWlaga2RSHP2W7idO3QREpXGk5njYs47OUK8rNVnNkv_2I2N5YKI_lzJvu5knze2leGlii0AweB_5bFFGwKjefp4F4SsMozRjQJwPawMpfFP2Q", "kty": "RSA", "kid": "tre1", "p": "3pew29j1DvXqJkD2u4f9XvKjNwNl9tsbio1MSbchuppdHAyGeA1vqqVVLZE0wYSi5pO-c01j2d0KhhxuuAXBvD1Q9rHB02loXdF5-HW2rs4HOW5jaCaLIkRRVVE3tQCShcc2FgJEdcRx4DJaRQhhym3APKOz0Nq7C5pL3DQEYT0", "use": "sig", "d": "DuUHv7iQTOaPUXswkhL_-DpKJ5QaXyO8YmoMICCL6nURGCSCc7iaITHSwoc10WMD6izeGq5YUYNtrxmc334EFl9vIBlK-MHjUtpkY_265Ua_lszUOXKGM8mIGb68WMFyjtLlaNCfQg8l9-JYwuEDkCZM40msudj3oAHAUUKy_kCn85O_PywYsIERVdUbDhwT6U14YXcVNuXo4XfJTIvS227TcFy_5APb0hlm-La_KbmmP--nw6teq-CRKHjl7L_eIOiHGHUWkKedBhKwzoirGRuxH8z3fSHbOK9Zo-1KZS-WGHwna5BCjEYCVnPexCb_pxQYRcVLhONULAOHcF60_Q", "q": "6mc5ojrKNoaV0dppa1hqT5vtF9Ob8rMF_hQgp1oBF2tiQ6MezNY3GzuG-09yeG1gRNWYuGo-PmV7nf1iO9OiHDFcC-mPDox_2tKNkBpMRwI5a0jwegaHvA4yg6F-lWmj9DDLGdqkSZDUETFkxcyfe0bekWigX57RQiKMMglAWs0", "e": "AQAB"}]} -------------------------------------------------------------------------------- /oidc_example/rp3/htdocs/operror.mako: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | pyoidc RP 6 | 7 | 8 | 9 | 10 | 11 | 12 | 16 | 17 | 18 | 19 | 20 | 38 | 39 |
40 | 41 |
42 |

OP result

43 | 44 |

You have failed to connect to the designated OP with the message:

45 | 46 |

${error}

47 |
48 | 49 |
50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | -------------------------------------------------------------------------------- /oidc_example/rp2/htdocs/post_logout.mako: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | pyoidc RP 7 | 8 | 9 | 10 | 11 | 12 | 13 | 17 | 18 | 19 | 20 | 21 | 38 | 39 |
40 | 41 |
42 |

Log out

43 |

You have now successfully logged out!!

44 |
45 | 46 |
47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | PROJECT_ROOT:=. 2 | 3 | SPHINXOPTS = 4 | SPHINXBUILD = sphinx-build 5 | SPHINXABUILD = sphinx-autobuild 6 | BUILDDIR = doc/_build 7 | DOCDIR = doc/ 8 | OICDIR = src/oic 9 | TESTDIR = tests 10 | OAUTH_EXAMPLE = oauth_example 11 | 12 | 13 | help: 14 | @echo "Please use \`make ' where is one of" 15 | @echo " html to make HTML documentation files" 16 | @echo " livehtml to make HTML documentation files (live reload!)" 17 | @echo " install to install the python dependencies for development" 18 | @echo " test to run the tests" 19 | @echo " isort to sort imports" 20 | @echo " blacken to format the code" 21 | @echo " bandit to run some simple security checkers" 22 | .PHONY: help 23 | 24 | clean: 25 | rm -rf $(BUILDDIR)/* 26 | .PHONY: clean 27 | 28 | ALLSPHINXOPTS=-W 29 | html: 30 | @pipenv run $(SPHINXBUILD) -b html $(DOCDIR) $(BUILDDIR)/html $(ALLSPHINXOPTS) 31 | @echo "Build finished. The HTML pages are in $(BUILDDIR)/html." 32 | .PHONY: html 33 | 34 | livehtml: 35 | @pipenv run $(SPHINXABUILD) -b html $(DOCDIR) $(BUILDDIR)/html $(ALLSPHINXOPTS) 36 | @echo "Build finished. Watching for change ..." 37 | .PHONY: livehtml 38 | 39 | install: 40 | @pipenv install --dev -e .[develop,testing,docs,quality,ldap_authn,examples] 41 | .PHONY: install 42 | 43 | test: 44 | @pipenv run pytest $(TESTDIR) 45 | .PHONY: test 46 | 47 | isort: 48 | @pipenv run isort $(OICDIR) $(TESTDIR) $(OAUTH_EXAMPLE) 49 | 50 | check-isort: 51 | @pipenv run isort --diff --check-only $(OICDIR) $(TESTDIR) $(OAUTH_EXAMPLE) 52 | .PHONY: isort check-isort 53 | 54 | blacken: 55 | @pipenv run black src/ tests/ oauth_example/ 56 | 57 | check-black: 58 | @pipenv run black src/ tests/ oauth_example/ --check 59 | .PHONY: blacken check-black 60 | 61 | bandit: 62 | @pipenv run bandit -a file -r src/ oauth_example/ oidc_example/ 63 | .PHONY: bandit 64 | 65 | check-pylama: 66 | @pipenv run pylama $(OICDIR) $(TESTDIR) $(OAUTH_EXAMPLE) 67 | .PHONY: check-pylama 68 | 69 | release: 70 | @pipenv run python setup.py sdist upload -r pypi 71 | .PHONY: release 72 | -------------------------------------------------------------------------------- /oidc_example/op2/config_simple.py: -------------------------------------------------------------------------------- 1 | keys = [ 2 | {"type": "RSA", "key": "cp_keys/key.pem", "use": ["enc", "sig"]}, 3 | {"type": "EC", "crv": "P-256", "use": ["sig"]}, 4 | {"type": "EC", "crv": "P-256", "use": ["enc"]} 5 | ] 6 | 7 | ISSUER = 'http://localhost' 8 | SERVICE_URL = "{issuer}/verify" 9 | 10 | # Only Username and password. 11 | AUTHENTICATION = { 12 | "UserPassword": {"ACR": "PASSWORD", "WEIGHT": 1, "URL": SERVICE_URL, 13 | "END_POINTS": ["verify"]} 14 | } 15 | 16 | COOKIENAME = 'pyoic' 17 | COOKIETTL = 4 * 60 # 4 hours 18 | SYM_KEY = "SoLittleTime,Got" 19 | 20 | SERVER_CERT = "certs/server.crt" 21 | SERVER_KEY = "certs/server.key" 22 | # CERT_CHAIN="certs/chain.pem" 23 | CERT_CHAIN = None 24 | 25 | # ======= SIMPLE DATABASE ============== 26 | 27 | USERINFO = "SIMPLE" 28 | 29 | USERDB = { 30 | "diana": { 31 | "sub": "dikr0001", 32 | "name": "Diana Krall", 33 | "given_name": "Diana", 34 | "family_name": "Krall", 35 | "nickname": "Dina", 36 | "email": "diana@example.org", 37 | "email_verified": False, 38 | "phone_number": "+46 90 7865000", 39 | "address": { 40 | "street_address": "Umeå Universitet", 41 | "locality": "Umeå", 42 | "postal_code": "SE-90187", 43 | "country": "Sweden" 44 | }, 45 | }, 46 | "babs": { 47 | "sub": "babs0001", 48 | "name": "Barbara J Jensen", 49 | "given_name": "Barbara", 50 | "family_name": "Jensen", 51 | "nickname": "babs", 52 | "email": "babs@example.com", 53 | "email_verified": True, 54 | "address": { 55 | "street_address": "100 Universal City Plaza", 56 | "locality": "Hollywood", 57 | "region": "CA", 58 | "postal_code": "91608", 59 | "country": "USA", 60 | }, 61 | }, 62 | "upper": { 63 | "sub": "uppe0001", 64 | "name": "Upper Crust", 65 | "given_name": "Upper", 66 | "family_name": "Crust", 67 | "email": "uc@example.com", 68 | "email_verified": True, 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /src/oic/exception.py: -------------------------------------------------------------------------------- 1 | __author__ = "rohe0002" 2 | 3 | 4 | class PyoidcError(Exception): 5 | def __init__(self, errmsg, content_type="", *args): 6 | Exception.__init__(self, errmsg, *args) 7 | self.content_type = content_type 8 | 9 | 10 | class MissingAttribute(PyoidcError): 11 | pass 12 | 13 | 14 | class UnsupportedMethod(PyoidcError): 15 | pass 16 | 17 | 18 | class AccessDenied(PyoidcError): 19 | pass 20 | 21 | 22 | class UnknownClient(PyoidcError): 23 | pass 24 | 25 | 26 | class MissingParameter(PyoidcError): 27 | pass 28 | 29 | 30 | class UnknownAssertionType(PyoidcError): 31 | pass 32 | 33 | 34 | class ParameterError(PyoidcError): 35 | pass 36 | 37 | 38 | class URIError(PyoidcError): 39 | pass 40 | 41 | 42 | class InvalidRequest(PyoidcError): 43 | pass 44 | 45 | 46 | class RedirectURIError(PyoidcError): 47 | pass 48 | 49 | 50 | class ParseError(PyoidcError): 51 | pass 52 | 53 | 54 | class FailedAuthentication(PyoidcError): 55 | pass 56 | 57 | 58 | class MissingSession(PyoidcError): 59 | pass 60 | 61 | 62 | class NotForMe(PyoidcError): 63 | pass 64 | 65 | 66 | class UnSupported(Exception): 67 | pass 68 | 69 | 70 | class MessageException(PyoidcError): 71 | pass 72 | 73 | 74 | class AuthzError(PyoidcError): 75 | pass 76 | 77 | 78 | class IssuerMismatch(PyoidcError): 79 | pass 80 | 81 | 82 | class RestrictionError(PyoidcError): 83 | pass 84 | 85 | 86 | class InvalidRedirectUri(Exception): 87 | pass 88 | 89 | 90 | class MissingPage(Exception): 91 | pass 92 | 93 | 94 | class ModificationForbidden(Exception): 95 | pass 96 | 97 | 98 | class RegistrationError(PyoidcError): 99 | pass 100 | 101 | 102 | class CommunicationError(PyoidcError): 103 | pass 104 | 105 | 106 | class RequestError(PyoidcError): 107 | pass 108 | 109 | 110 | class AuthnToOld(PyoidcError): 111 | pass 112 | 113 | 114 | class ImproperlyConfigured(PyoidcError): 115 | pass 116 | 117 | 118 | class SubMismatch(PyoidcError): 119 | pass 120 | -------------------------------------------------------------------------------- /src/oic/utils/authn/javascript_login.py: -------------------------------------------------------------------------------- 1 | from urllib.parse import parse_qs 2 | 3 | from oic.utils.authn.user import UsernamePasswordMako 4 | from oic.utils.authn.user import logger 5 | from oic.utils.http_util import SeeOther 6 | from oic.utils.http_util import Unauthorized 7 | 8 | __author__ = "danielevertsson" 9 | 10 | 11 | class JavascriptFormMako(UsernamePasswordMako): 12 | """ 13 | Do user authentication. 14 | 15 | This is using the normal username password form in a WSGI environment using Mako as template system. 16 | """ 17 | 18 | def verify(self, request, **kwargs): 19 | """ 20 | Verify that the given username and password was correct. 21 | 22 | :param request: Either the query part of a URL a urlencoded body of a HTTP message or a parse such. 23 | :param kwargs: Catch whatever else is sent. 24 | :return: redirect back to where ever the base applications wants the user after authentication. 25 | """ 26 | logger.debug("verify(%s)" % request) 27 | if isinstance(request, str): 28 | _dict = parse_qs(request) 29 | elif isinstance(request, dict): 30 | _dict = request 31 | else: 32 | raise ValueError("Wrong type of input") 33 | 34 | logger.debug("dict: %s" % _dict) 35 | logger.debug("passwd: %s" % self.passwd) 36 | # verify username and password 37 | try: 38 | if _dict["login_parameter"][0] != "logged_in": 39 | raise KeyError() 40 | except KeyError: 41 | return ( 42 | Unauthorized("You are not authorized. Javascript not executed"), 43 | False, 44 | ) 45 | else: 46 | cookie = self.create_cookie("diana", "upm") 47 | try: 48 | _qp = _dict["query"][0] 49 | except KeyError: 50 | _qp = self.get_multi_auth_cookie(kwargs["cookie"]) 51 | try: 52 | return_to = self.generate_return_url(kwargs["return_to"], _qp) 53 | except KeyError: 54 | return_to = self.generate_return_url(self.return_to, _qp) 55 | return SeeOther(return_to, headers=[cookie]), True 56 | -------------------------------------------------------------------------------- /src/oic/utils/authz.py: -------------------------------------------------------------------------------- 1 | import logging 2 | import time 3 | from typing import Any 4 | from typing import Dict 5 | 6 | from oic.utils.authn.user import ToOld 7 | from oic.utils.http_util import CookieDealer 8 | from oic.utils.sanitize import sanitize 9 | 10 | logger = logging.getLogger(__name__) 11 | 12 | 13 | class AuthzHandling(CookieDealer): 14 | """Class that allows an entity to manage authorization.""" 15 | 16 | def __init__(self): 17 | self.permdb: Dict[str, Any] = {} 18 | 19 | def __call__(self, *args, **kwargs): 20 | return "" 21 | 22 | def permissions(self, cookie=None, **kwargs): 23 | if cookie is None: 24 | return None 25 | else: 26 | logger.debug("kwargs: %s" % sanitize(kwargs)) 27 | 28 | val = self.getCookieValue(cookie, self.srv.cookie_name) 29 | if val is None: 30 | return None 31 | else: 32 | uid, _ts, typ = val 33 | 34 | if typ == "uam": # short lived 35 | _now = int(time.time()) 36 | if _now > (int(_ts) + int(self.cookie_ttl * 60)): 37 | logger.debug("Authentication timed out") 38 | raise ToOld( 39 | "%d > (%d + %d)" % (_now, int(_ts), int(self.cookie_ttl * 60)) 40 | ) 41 | else: 42 | if "max_age" in kwargs and kwargs["max_age"]: 43 | _now = int(time.time()) 44 | if _now > (int(_ts) + int(kwargs["max_age"])): 45 | logger.debug("Authentication too old") 46 | raise ToOld( 47 | "%d > (%d + %d)" % (_now, int(_ts), int(kwargs["max_age"])) 48 | ) 49 | 50 | return self.permdb[uid] 51 | 52 | 53 | class UserInfoConsent(AuthzHandling): 54 | def __call__(self, user, userinfo, **kwargs): 55 | pass 56 | 57 | 58 | class Implicit(AuthzHandling): 59 | def __init__(self, permission="implicit"): 60 | AuthzHandling.__init__(self) 61 | self.permission = permission 62 | 63 | def permissions(self, cookie=None, **kwargs): 64 | return self.permission 65 | -------------------------------------------------------------------------------- /oidc_example/simple_op/certs/localhost.crt: -------------------------------------------------------------------------------- 1 | -----BEGIN CERTIFICATE----- 2 | MIIF9DCCA9ygAwIBAgIJAL+NvBwzlQcvMA0GCSqGSIb3DQEBBQUAMFkxCzAJBgNV 3 | BAYTAkFVMRMwEQYDVQQIEwpTb21lLVN0YXRlMSEwHwYDVQQKExhJbnRlcm5ldCBX 4 | aWRnaXRzIFB0eSBMdGQxEjAQBgNVBAMTCWxvY2FsaG9zdDAeFw0xNTA0MjgwODU5 5 | MDlaFw0xODAxMjIwODU5MDlaMFkxCzAJBgNVBAYTAkFVMRMwEQYDVQQIEwpTb21l 6 | LVN0YXRlMSEwHwYDVQQKExhJbnRlcm5ldCBXaWRnaXRzIFB0eSBMdGQxEjAQBgNV 7 | BAMTCWxvY2FsaG9zdDCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAKyu 8 | 7r4+Og3FjB91Dg0/UXx7Nmz+zt37TymW47zmbflv6qxKAhFynunmF9YQhhTz5qdB 9 | h8rktKudlus2axjDX5iJ/Ea10rOnx5JSO0QoAgDpaSwDQm5GSPKy+UtjDf81Q0wU 10 | IYkDkypMaMWS3fSU12Gjy/o2tG/VCGCeB87dl/SnZyicP33zYRnLuf6/LaB41XK8 11 | YSO2jhZ92qElYk76FGwPV//7oQBPYTd5aC94ujkBVozZEW8pTnMWy+D5+JEJAbRk 12 | lJ+PBfxvqA0RBbKQOgRjSCiDiFaiVJcGZ9L5etGCu/YKsRJs/fIplBtHh4hwt53B 13 | 6JcYFVV5zrQBy4d9G0zjtYQYCRzmoHDhzJ137+q8Y7Y8kOB6ZALJp96DCKe7G1D9 14 | JHrrJke7pkyuGjytCRzl9y7ZvsLvEE8Efjlk9DkLF9M8RJ2wYvoWcmiyKWYomDU1 15 | tFCYwHLA83WLcO3GED2AoE9BD8QOZOlo12oLQJ77EOU/BIRB2lzpEumzAGPAUZ0q 16 | /Pt7U1g/MLzUcg8mjRf+hmKFaZf+VN9IqgDElCzyLYQcMLNNY6Q6Y7HsVWaJ3I/L 17 | gUVVzdiQRznXNqmUrNKiMx/+M9c5/3Qz3J2/9UBZusJXr2CyiqS5AZLQqsyWYMgT 18 | RQVutC2mBuD12dCE4agxkG2xwsC8yailgB3yp7K3AgMBAAGjgb4wgbswHQYDVR0O 19 | BBYEFA4H+v5idecQY8LMmigkIQrMQcqNMIGLBgNVHSMEgYMwgYCAFA4H+v5idecQ 20 | Y8LMmigkIQrMQcqNoV2kWzBZMQswCQYDVQQGEwJBVTETMBEGA1UECBMKU29tZS1T 21 | dGF0ZTEhMB8GA1UEChMYSW50ZXJuZXQgV2lkZ2l0cyBQdHkgTHRkMRIwEAYDVQQD 22 | Ewlsb2NhbGhvc3SCCQC/jbwcM5UHLzAMBgNVHRMEBTADAQH/MA0GCSqGSIb3DQEB 23 | BQUAA4ICAQAxp3W4wAbIQw9hytkDA1XNiPpI/emO+pTJOj8lcnlAtC5YOOVvG5P4 24 | VWrB88SUBn+UOHmeJs7jWd6j7ZHN3mb8ZXbyCw3jZ5lORF7TcbATImYazFznwU7M 25 | SNj+6dbcpODEOeGR5oGSJrSE4EwNm9QITMkt6GmjVR9EoKX8cLGe0fkRi8hJWKhh 26 | 7OOC+inq1XamJdMhYojHJLZjQvPN5MPA0LNmqPSO0jjttkteEDnflHy/Rvn2lStd 27 | gIOoW6H124qGx0SI6UgwZpKhw8IQeVvbU1QYsgvjEpfZLfxZbeuUuz0T0Sk8SdwE 28 | Fe24nJHRDuSTI4I/uw956gMT0xgZdUnRCxJ5Grlv15a+ajj5ZblrAsuSWsSY4g54 29 | NdwHHEjnUA3WjielEYTpW4Wc3zn6UQYYdO9KFparYY+lGYjZ05IRioLmTlVbGsU1 30 | STa0+iZE31ZbNR4t69eeFTbRyEyHRd3nVL8o5knFH0/OSpie2cOJWfy1ZHrlPM2B 31 | YnPeqTZmEa0mzpG0Xis+chXVDg2xQzKLmM7rIfXm/PztW0YPcgpLmE/444DIHuyA 32 | rOccE4iHe3agMpaVYqqo84roNVpmYNgrYJ6ZUio50tjfesXWNGzJhmzf1L3mjRFz 33 | bPC8xyQqYapu2Ea5gKp3g6LEgSUgy2qh0EFkb6sAc3P8AoJl1Fg4EQ== 34 | -----END CERTIFICATE----- 35 | -------------------------------------------------------------------------------- /oidc_example/simple_rp/certs/localhost.crt: -------------------------------------------------------------------------------- 1 | -----BEGIN CERTIFICATE----- 2 | MIIF9DCCA9ygAwIBAgIJAIWwMA2fNdmcMA0GCSqGSIb3DQEBBQUAMFkxCzAJBgNV 3 | BAYTAkFVMRMwEQYDVQQIEwpTb21lLVN0YXRlMSEwHwYDVQQKExhJbnRlcm5ldCBX 4 | aWRnaXRzIFB0eSBMdGQxEjAQBgNVBAMTCWxvY2FsaG9zdDAeFw0xNTA0MjgxNDM0 5 | MDFaFw0xODAxMjIxNDM0MDFaMFkxCzAJBgNVBAYTAkFVMRMwEQYDVQQIEwpTb21l 6 | LVN0YXRlMSEwHwYDVQQKExhJbnRlcm5ldCBXaWRnaXRzIFB0eSBMdGQxEjAQBgNV 7 | BAMTCWxvY2FsaG9zdDCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAKxk 8 | 3bmbOZKnWfo4EjAUyB1/Hy9ACLIRaNv1sr95nBYBMn47guRf6oL3hgW1a9agvAee 9 | dGmQPaHFEehGnWxIGdJ7ISkm5umfAD7f7kyW2AfU5a9V85BAMttn0Fr+HJFzjsq1 10 | di/6iKyT8JOlUEd0Y9Jg7rC5Rw768OztO7+JFNVfJUGbQy7ck8Umvbgckl5ortZ+ 11 | ndcCmdVFVzmJWNNiajjWURJHWsD3HJStD/oW6awmoXy+LRGjcJxzLrQNDkGOqOBj 12 | N37g3wdntUZJSvv380BDZKxR/8R1Le8rLZ0aC1cIwWCQwWnJXavxO0FyaZ+ytbrx 13 | WHbV11/qh5HNsNxamWaf2CzU2j+ZV4svQsnwERDzWdYJWfc5iQnp/ZqOlevFOjRy 14 | rXsZ6ufSc+5O0gQy06g2327sB/c6RgXuxBg4XpOT0wShOXugZZdsPjA908hxUklH 15 | zj4lMELfuFnSbeVABNPw44cQ3hcFlftgdkaf7pmDV3uskjMQ7FQCV4HPlEXtY/i/ 16 | 2QxShELUrrnSN1wOW9OAcpULAi+Zh4fxRR7AhOExI1IqOOSYRmpESSYWU0fhruaC 17 | 7n5My5vUUQm9lpwyQCkDjEMVbvMvp3MlFmTd1DAVOlk+wyCPPPc7ZkP6RcCq20kY 18 | tFK4LhTsqUpsHui5nc00co2fZoqufmQ5X8NXpyjfAgMBAAGjgb4wgbswHQYDVR0O 19 | BBYEFNQVa1tVKw5MYz8lOuLKai0kSUnRMIGLBgNVHSMEgYMwgYCAFNQVa1tVKw5M 20 | Yz8lOuLKai0kSUnRoV2kWzBZMQswCQYDVQQGEwJBVTETMBEGA1UECBMKU29tZS1T 21 | dGF0ZTEhMB8GA1UEChMYSW50ZXJuZXQgV2lkZ2l0cyBQdHkgTHRkMRIwEAYDVQQD 22 | Ewlsb2NhbGhvc3SCCQCFsDANnzXZnDAMBgNVHRMEBTADAQH/MA0GCSqGSIb3DQEB 23 | BQUAA4ICAQCCIcreKQVnn2bVM4wnqiKMa3k7Aipkq789LGNgUjhqqbiX1XCE60T7 24 | M6NHEclOMgUds+UYT2rfBoNZUquPrnR0wVYHzEw+9PFWDihb0+HXFDjmZdQ4cExJ 25 | T8pPCflqED4wTyIe6Es6Z/u+AFnhd6D961DgFKH2ip62Aj4m1cXXwqYZvVqqXJzy 26 | qMcFaBi8AmNMeq4sZbgvo6fg+jhMpA3fU7gpfAlYJteEMyUxf+AMDF53i80SHRJN 27 | cgQky9CR5zt1Ha+frH/KWhMO+azeW7jroWgPATpbwiMIDEDV13swPrN8YKOuNzU6 28 | KPkKQdNrMZKtqoAEHoq2sfRwlssIhjjz4D3TmsZwMIh+KXthKozCK0r1jNRC9coy 29 | k8n4s8trynPC6MiVqCfcjlLfiweGRJo6G0B+SV9h56EGhb+KN0qrgQ2Uw+oLQC8r 30 | mRyS1UMFAfgEuQDkAHvEXDhGmB4GV184lDi4N+m2H+0P6e2CJk6COhTfDoe9+MRK 31 | OrgRvyXNUwl/GdbspY/r7qDU1+bMjPsMSvOy/3LnbNAs4JD7qcYyP/AdXbnQyIYE 32 | VOGpqlqGlcRsDENxKNYqW7J19XbSpdOOUaw+mQTgQ3QGp1abQP4EAHeCLrFhhFBl 33 | 5XFyobHokWBc+PEzou+fP1ihuVV5wkJXmSK+BySM5rRq/gHPqaDOCw== 34 | -----END CERTIFICATE----- 35 | -------------------------------------------------------------------------------- /oidc_example/rp3/htdocs/opchoice.mako: -------------------------------------------------------------------------------- 1 | <%! 2 | def op_choice(op_list): 3 | #Creates a dropdown list of OpenID Connect providers 4 | element = "" 8 | return element 9 | %> 10 | 11 | 12 | 13 | 14 | 15 | pyoidc RP 16 | 17 | 18 | 19 | 20 | 21 | 22 | 26 | 27 | 28 | 29 | 30 | 35 | 36 |
37 | 38 |
39 | 40 |

OP by UID

41 | 42 |

Chose the OpenID Connect Provider:

43 | 44 |

From this list

45 | ${op_choice(op_list)} 46 |

OR by providing your unique identifier at the OP.

47 | 48 |

OR by providing an issuer id

49 | 50 | 51 | 52 |
53 | 54 |
55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | -------------------------------------------------------------------------------- /oidc_example/rp3/conf.py.example: -------------------------------------------------------------------------------- 1 | #BASE = "https://lingon.ladok.umu.se" 2 | BASE = "http://localhost" 3 | 4 | # If BASE is https these has to be specified 5 | SERVER_CERT = "certs/server.crt" 6 | SERVER_KEY = "certs/server.key" 7 | CA_BUNDLE = None 8 | 9 | VERIFY_SSL = False 10 | 11 | # information used when registering the client, this may be the same for all OPs 12 | 13 | ME = { 14 | "application_type": "web", 15 | "application_name": "idpproxy", 16 | "contacts": ["ops@example.com"], 17 | "redirect_uris": ["{base}authz_cb"], 18 | "post_logout_redirect_uris": ["{base}logout_success"], 19 | "response_types": ["code"] 20 | } 21 | 22 | BEHAVIOUR = { 23 | "response_type": "code", 24 | "scope": ["openid", "profile", "email", "address", "phone"], 25 | } 26 | 27 | ACR_VALUES = ["SAML"] 28 | 29 | # The keys in this dictionary are the OPs short userfriendly name 30 | # not the issuer (iss) name. 31 | 32 | CLIENTS = { 33 | # The ones that support webfinger, OP discovery and client registration 34 | # This is the default, any client that is not listed here is expected to 35 | # support dynamic discovery and registration. 36 | "": { 37 | "client_info": ME, 38 | "behaviour": BEHAVIOUR 39 | }, 40 | #"oictest": { 41 | # "srv_discovery_url": "https://oictest.umdc.umu.se:8085/", 42 | # "client_info": ME, 43 | # "behaviour": BEHAVIOUR 44 | #}, 45 | # "lingon": { 46 | # "srv_discovery_url": "https://lingon.ladok.umu.se:8092/", 47 | # "client_info": ME, 48 | # "behaviour": BEHAVIOUR, 49 | # "verify_ssl": False 50 | # }, 51 | # Supports OP information lookup but not client registration 52 | # "google": { 53 | # "srv_discovery_url": "https://accounts.google.com/", 54 | # "client_registration": { 55 | # "client_id": "xxxxxxxxx.apps.googleusercontent.com", 56 | # "client_secret": "2222222222", 57 | # "redirect_uris": ["{base}google"], 58 | # }, 59 | # "behaviour": { 60 | # "response_type": "code", 61 | # "scope": ["openid", "profile", "email"] 62 | # }, 63 | # "allow": { 64 | # "issuer_mismatch": True 65 | # }, 66 | # "userinfo_request_method": "GET" 67 | # } 68 | } 69 | 70 | # Which type of client 71 | CLIENT_TYPE = 'OIDC' # one of OIDC/OAUTH2 72 | # Whether an attempt to fetch the userinfo should be made 73 | USERINFO = True 74 | -------------------------------------------------------------------------------- /tests/jwks/jwks_spo.json: -------------------------------------------------------------------------------- 1 | { 2 | "keys": [ 3 | { 4 | "kid": "BfxfnahEtkRBG3Hojc9XGLGht_5rDBj49Wh3sBDVnzRpulMqYwMRmpizA0aSPT1fhCHYivTiaucWUqFu_GwTqA", 5 | "use": "sig", 6 | "alg": "ES256", 7 | "kty": "EC", 8 | "crv": "P-256", 9 | "x": "1XXUXq75gOPZ4bEj1o2Z5XKJWSs6LmL6fAOK3vyMzSc", 10 | "y": "ac1h_DwyuUxhkrD9oKMJ-b_KuiVvvSARIwT-XoEmDXs" 11 | }, 12 | { 13 | "kid": "91pD1H81rXUvrfg9mkngIG-tXjnldykKUVbITDIU1SgJvq91b8clOcJuEHNAq61eIvg8owpEvWcWAtlbV2awyA", 14 | "use": "sig", 15 | "alg": "ES256", 16 | "kty": "EC", 17 | "crv": "P-256", 18 | "x": "2DfQoLpZS2j3hHEcHDkzV8ISx-RdLt6Opy8YZYVm4AQ", 19 | "y": "ycvkFMBIzgsowiaf6500YlG4vaMSK4OF7WVtQpUbEE0" 20 | }, 21 | { 22 | "kid": "0sIEl3MUJiCxrqleEBBF-_bZq5uClE84xp-wpt8oOI-WIeNxBjSR4ak_OTOmLdndB0EfDLtC7X1JrnfZILJkxA", 23 | "use": "sig", 24 | "alg": "RS256", 25 | "kty": "RSA", 26 | "n": "yG9914Q1j63Os4jX5dBQbUfImGq4zsXJD4R59XNjGJlEt5ek6NoiDl0ucJO3_7_R9e5my2ONTSqZhtzFW6MImnIn8idWYzJzO2EhUPCHTvw_2oOGjeYTE2VltIyY_ogIxGwY66G0fVPRRH9tCxnkGOrIvmVgkhCCGkamqeXuWvx9MCHL_gJbZJVwogPSRN_SjA1gDlvsyCdA6__CkgAFcSt1sGgiZ_4cQheKexxf1-7l8R91ZYetz53drk2FS3SfuMZuwMM4KbXt6CifNhzh1Ye-5Tr_ZENXdAvuBRDzfy168xnk9m0JBtvul9GoVIqvCVECB4MPUb7zU6FTIcwRAw", 27 | "e": "AQAB" 28 | }, 29 | { 30 | "kid": "zyDfdEU7pvH0xEROK156ik8G7vLO1MIL9TKyL631kSPtr9tnvs9XOIiq5jafK2hrGr2qqvJdejmoonlGqWWZRA", 31 | "use": "sig", 32 | "alg": "RS256", 33 | "kty": "RSA", 34 | "n": "68be-nJp46VLj4Ci1V36IrVGYqkuBfYNyjQTZD_7yRYcERZebowOnwr3w0DoIQpl8iL2X8OXUo7rUW_LMzLxKx2hEmdJfUn4LL2QqA3KPgjYz8hZJQPG92O14w9IZ-8bdDUgXrg9216H09yq6ZvJrn5Nwvap3MXgECEzsZ6zQLRKdb_R96KFFgCiI3bEiZKvZJRA7hM2ePyTm15D9En_Wzzfn_JLMYgE_DlVpoKR1MsTinfACOlwwdO9U5Dm-5elapovILTyVTgjN75i-wsPU2TqzdHFKA-4hJNiWGrYPiihlAFbA2eUSXuEYFkX43ahoQNpeaf0mc17Jt5kp7pM2w", 35 | "e": "AQAB" 36 | }, 37 | { 38 | "kid": "q-H9y8iuh3BIKZBbK6S0mH_isBlJsk-u6VtZ5rAdBo5fCjjy3LnkrsoK_QWrlKB08j_PcvwpAMfTEDHw5spepw", 39 | "use": "sig", 40 | "alg": "EdDSA", 41 | "kty": "OKP", 42 | "crv": "Ed25519", 43 | "x": "FnbcUAXZ4ySvrmdXK1MrDuiqlqTXvGdAaE4RWZjmFIQ" 44 | }, 45 | { 46 | "kid": "bL33HthM3fWaYkY2_pDzUd7a65FV2R2LHAKCOsye8eNmAPDgRgpHWPYpWFVmeaujUUEXRyDLHN-Up4QH_sFcmw", 47 | "use": "sig", 48 | "alg": "EdDSA", 49 | "kty": "OKP", 50 | "crv": "Ed25519", 51 | "x": "CS01DGXDBPV9cFmd8tgFu3E7eHn1UcP7N1UCgd_JgZo" 52 | } 53 | ] 54 | } 55 | -------------------------------------------------------------------------------- /src/oic/extension/heart.py: -------------------------------------------------------------------------------- 1 | from urllib.parse import urlparse 2 | 3 | from oic.oauth2.message import REQUIRED_LIST_OF_STRINGS 4 | from oic.oauth2.message import SINGLE_REQUIRED_STRING 5 | from oic.oic.message import SINGLE_REQUIRED_INT 6 | from oic.oic.message import JasonWebToken 7 | from oic.utils.keyio import KeyBundle 8 | 9 | __author__ = "roland" 10 | 11 | 12 | class PrivateKeyJWT(JasonWebToken): 13 | c_param = JasonWebToken.c_param.copy() 14 | c_param.update( 15 | { 16 | "aud": SINGLE_REQUIRED_STRING, 17 | "iss": SINGLE_REQUIRED_STRING, 18 | "sub": SINGLE_REQUIRED_STRING, 19 | "aud": SINGLE_REQUIRED_STRING, 20 | "exp": SINGLE_REQUIRED_INT, 21 | "iat": SINGLE_REQUIRED_INT, 22 | "jti": SINGLE_REQUIRED_STRING, 23 | } 24 | ) 25 | 26 | 27 | def verify_url(url): 28 | """ 29 | Verify security of URL. 30 | 31 | Hosted on a website with Transport Layer Security (TLS) protection 32 | (a Hypertext Transfer Protocol – Secure (HTTPS) URI) 33 | Hosted on the local domain of the client (e.g., http://localhost/) 34 | Hosted on a client-specific non-remote-protocol URI scheme (e.g., myapp://) 35 | 36 | :param url: 37 | :return: 38 | """ 39 | if url.startswith("http://localhost"): 40 | return True 41 | else: 42 | p = urlparse(url) 43 | if p.scheme == "http": 44 | return False 45 | 46 | return True 47 | 48 | 49 | class HeartSoftwareStatement(JasonWebToken): 50 | c_param = JasonWebToken.c_param.copy() 51 | c_param.update( 52 | { 53 | "redirect_uris": REQUIRED_LIST_OF_STRINGS, 54 | "grant_types": SINGLE_REQUIRED_STRING, 55 | "jwks_uri": SINGLE_REQUIRED_STRING, 56 | "jwks": SINGLE_REQUIRED_STRING, 57 | "client_name": SINGLE_REQUIRED_STRING, 58 | "client_uri": SINGLE_REQUIRED_STRING, 59 | } 60 | ) 61 | c_allowed_values = {"grant_types": ["authorization_code", "implicit"]} 62 | 63 | def verify(self, **kwargs): 64 | if "jwks" in self: 65 | try: 66 | _keys = self["jwks"]["keys"] 67 | except KeyError: 68 | raise SyntaxError('"keys" parameter missing') 69 | else: 70 | # will raise an exception if syntax error 71 | KeyBundle(_keys) 72 | for param in ["jwks_uri", "client_uri"]: 73 | verify_url(self[param]) 74 | 75 | JasonWebToken.verify(self, **kwargs) 76 | -------------------------------------------------------------------------------- /src/oic/utils/userinfo/aa_info.py: -------------------------------------------------------------------------------- 1 | import importlib 2 | from tempfile import NamedTemporaryFile 3 | 4 | from oic.utils.userinfo import UserInfo 5 | 6 | __author__ = "danielevertsson" 7 | 8 | try: 9 | from saml2.client import Saml2Client 10 | except ImportError: 11 | 12 | class AaUserInfo(UserInfo): 13 | pass 14 | 15 | else: 16 | 17 | class AaUserInfo(UserInfo): # type: ignore 18 | def __init__(self, spconf, url, db=None): 19 | UserInfo.__init__(self, db) 20 | 21 | # Configurations for the SP handler. (pyOpSamlProxy.client.sp.conf) 22 | self.sp_conf = importlib.import_module(spconf) 23 | ntf = NamedTemporaryFile(suffix="pyoidc.py", delete=True) 24 | ntf.write( 25 | b"CONFIG = " 26 | + str(self.sp_conf.CONFIG).replace("%s", url) # type: ignore 27 | ) 28 | ntf.seek(0) 29 | self.sp = Saml2Client(config_file="%s" % ntf.name) 30 | self.samlcache = self.sp_conf.SAML_CACHE # type: ignore 31 | 32 | def __call__(self, userid, client_id, user_info_claims=None, **kwargs): 33 | try: 34 | ava = self.db[userid] 35 | entity_id = self.sp_conf.AA_ENTITY_ID # type: ignore 36 | if entity_id is None: 37 | entity_id = self.samlcache["AA_ENTITYID"] 38 | response = self.sp.do_attribute_query( 39 | entity_id, 40 | ava[self.sp_conf.AA_NAMEID_ATTRIBUTE][0], # type: ignore 41 | nameid_format=self.sp_conf.AA_NAMEID_FORMAT, # type: ignore 42 | attribute=self.sp_conf.AA_REQUEST_ATTRIBUTES, # type: ignore 43 | ) 44 | 45 | response_dict = response.ava.copy() 46 | if self.sp_conf.AA_ATTRIBUTE_SAML_IDP is True: # type: ignore 47 | for key, value in ava.items(): 48 | if ( 49 | self.sp_conf.AA_ATTRIBUTE_SAML_IDP_WHITELIST # type: ignore 50 | is None 51 | or key 52 | in self.sp_conf.AA_ATTRIBUTE_SAML_IDP_WHITELIST # type: ignore 53 | ) and key not in response_dict: 54 | response_dict[key] = value 55 | 56 | return response_dict 57 | except Exception: 58 | return {} 59 | 60 | def filter(self, userinfo, user_info_claims=None): 61 | return userinfo 62 | -------------------------------------------------------------------------------- /.github/workflows/codeql-analysis.yml: -------------------------------------------------------------------------------- 1 | # For most projects, this workflow file will not need changing; you simply need 2 | # to commit it to your repository. 3 | # 4 | # You may wish to alter this file to override the set of languages analyzed, 5 | # or to provide custom queries or build logic. 6 | name: "CodeQL" 7 | 8 | on: 9 | push: 10 | branches: [master] 11 | pull_request: 12 | # The branches below must be a subset of the branches above 13 | branches: [master] 14 | schedule: 15 | - cron: '0 7 * * 0' 16 | 17 | jobs: 18 | analyze: 19 | name: Analyze 20 | runs-on: ubuntu-latest 21 | 22 | strategy: 23 | fail-fast: false 24 | matrix: 25 | # Override automatic language detection by changing the below list 26 | # Supported options are ['csharp', 'cpp', 'go', 'java', 'javascript', 'python'] 27 | language: ['python'] 28 | # Learn more... 29 | # https://docs.github.com/en/github/finding-security-vulnerabilities-and-errors-in-your-code/configuring-code-scanning#overriding-automatic-language-detection 30 | 31 | steps: 32 | - name: Checkout repository 33 | uses: actions/checkout@v2 34 | with: 35 | # We must fetch at least the immediate parents so that if this is 36 | # a pull request then we can checkout the head. 37 | fetch-depth: 2 38 | 39 | # If this run was triggered by a pull request event, then checkout 40 | # the head of the pull request instead of the merge commit. 41 | - run: git checkout HEAD^2 42 | if: ${{ github.event_name == 'pull_request' }} 43 | 44 | # Initializes the CodeQL tools for scanning. 45 | - name: Initialize CodeQL 46 | uses: github/codeql-action/init@v1 47 | with: 48 | languages: ${{ matrix.language }} 49 | # If you wish to specify custom queries, you can do so here or in a config file. 50 | # By default, queries listed here will override any specified in a config file. 51 | # Prefix the list here with "+" to use these queries and those in the config file. 52 | # queries: ./path/to/local/query, your-org/your-repo/queries@main 53 | 54 | # ℹ️ Command-line programs to run using the OS shell. 55 | # 📚 https://git.io/JvXDl 56 | 57 | # ✏️ If the Autobuild fails above, remove it and uncomment the following three lines 58 | # and modify them (or add more) to build your code if your project 59 | # uses a compiled language 60 | 61 | #- run: | 62 | # make bootstrap 63 | # make release 64 | 65 | - name: Perform CodeQL Analysis 66 | uses: github/codeql-action/analyze@v1 67 | -------------------------------------------------------------------------------- /oidc_example/simple_op/src/provider/authn/yubikey.py: -------------------------------------------------------------------------------- 1 | import json 2 | import logging 3 | 4 | from provider.authn import AuthnModule 5 | from provider.authn import make_cls_from_name 6 | from yubico_client import yubico_exceptions 7 | from yubico_client.yubico import Yubico 8 | 9 | from oic.utils.http_util import Response 10 | 11 | logger = logging.getLogger(__name__) 12 | 13 | 14 | class YubicoOTP(AuthnModule): 15 | url_endpoint = "/yubi_otp/verify" 16 | 17 | def __init__(self, yubikey_db, validation_server, client_id, template_env, 18 | secret_key=None, verify_ssl=True, template="yubico_otp.jinja2", 19 | **kwargs): 20 | super(YubicoOTP, self).__init__(None) 21 | self.template_env = template_env 22 | self.template = template 23 | 24 | cls = make_cls_from_name(yubikey_db["class"]) 25 | self.yubikey_db = cls(**yubikey_db["kwargs"]) 26 | 27 | self.client = Yubico(client_id, secret_key, 28 | api_urls=[validation_server], 29 | verify_cert=verify_ssl) 30 | if not verify_ssl: 31 | # patch yubico-client to not find any ca bundle 32 | self.client._get_ca_bundle_path = lambda: None 33 | 34 | def __call__(self, *args, **kwargs): 35 | template = self.template_env.get_template(self.template) 36 | return Response(template.render(action=self.url_endpoint, 37 | state=json.dumps(kwargs))) 38 | 39 | def verify(self, *args, **kwargs): 40 | otp = kwargs["otp"] 41 | try: 42 | status = self.client.verify(otp, return_response=True) 43 | except yubico_exceptions.InvalidClientIdError as e: 44 | logger.error( 45 | "Client with id {} does not exist".format(e.client_id)) 46 | return self.FAILED_AUTHN 47 | except yubico_exceptions.SignatureVerificationError: 48 | logger.error("Signature verification failed") 49 | return self.FAILED_AUTHN 50 | except yubico_exceptions.StatusCodeError as e: 51 | logger.error( 52 | "Negative status code was returned: {}".format( 53 | e.status_code)) 54 | return self.FAILED_AUTHN 55 | 56 | if status: 57 | logger.debug("Success, the provided OTP is valid") 58 | yubikey_public_id = otp[:12] 59 | 60 | return self.yubikey_db[yubikey_public_id], True 61 | else: 62 | logger.error( 63 | "No response from the servers or received other negative status code") 64 | -------------------------------------------------------------------------------- /src/oic/extension/sts.py: -------------------------------------------------------------------------------- 1 | """ 2 | Message types in draft-ietf-oauth-token-exchange-03. 3 | 4 | :copyright: (c) 2016 by Roland Hedberg. 5 | :license: Apache2, see LICENSE for more details. 6 | 7 | """ 8 | import json 9 | 10 | from oic.oauth2.message import OPTIONAL_LIST_OF_SP_SEP_STRINGS 11 | from oic.oauth2.message import OPTIONAL_LIST_OF_STRINGS 12 | from oic.oauth2.message import REQUIRED_LIST_OF_STRINGS 13 | from oic.oauth2.message import SINGLE_OPTIONAL_INT 14 | from oic.oauth2.message import SINGLE_OPTIONAL_STRING 15 | from oic.oauth2.message import SINGLE_REQUIRED_STRING 16 | from oic.oauth2.message import Message 17 | from oic.oauth2.message import ParamDefinition 18 | from oic.oic.message import SINGLE_REQUIRED_INT 19 | from oic.oic.message import msg_ser 20 | 21 | __author__ = "roland" 22 | 23 | 24 | class TokenExchangeRequest(Message): 25 | c_param = { 26 | "grant_type": SINGLE_REQUIRED_STRING, 27 | "resource": SINGLE_OPTIONAL_STRING, 28 | "audience": SINGLE_OPTIONAL_STRING, 29 | "scope": OPTIONAL_LIST_OF_SP_SEP_STRINGS, 30 | "requested_token_type": SINGLE_OPTIONAL_STRING, 31 | "subject_token": SINGLE_REQUIRED_STRING, 32 | "subject_token_type": SINGLE_REQUIRED_STRING, 33 | "actor_token": SINGLE_OPTIONAL_STRING, 34 | "actor_token_type": SINGLE_OPTIONAL_STRING, 35 | "want_composite": SINGLE_OPTIONAL_STRING, 36 | } 37 | 38 | def verify(self, **kwargs): 39 | if "actor_token" in self: 40 | if not "actor_token_type": 41 | return False 42 | 43 | 44 | class TokenExchangeResponse(Message): 45 | c_param = { 46 | "access_token": SINGLE_REQUIRED_STRING, 47 | "issued_token_type": SINGLE_REQUIRED_STRING, 48 | "token_type": SINGLE_REQUIRED_STRING, 49 | "expires_in": SINGLE_OPTIONAL_INT, 50 | "refresh_token": SINGLE_OPTIONAL_STRING, 51 | "scope": OPTIONAL_LIST_OF_SP_SEP_STRINGS, 52 | } 53 | 54 | 55 | def sts_deser(val, sformat="json"): 56 | if sformat == "urlencoded": 57 | sformat = "json" 58 | if sformat in ["dict", "json"]: 59 | if not isinstance(val, str): 60 | val = json.dumps(val) 61 | sformat = "json" 62 | return STS().deserialize(val, sformat) 63 | 64 | 65 | SINGLE_OPTIONAL_STS = ParamDefinition(Message, False, msg_ser, sts_deser, False) 66 | 67 | 68 | class STS(Message): 69 | c_param = { 70 | "aud": REQUIRED_LIST_OF_STRINGS, # Array of strings or string 71 | "iss": SINGLE_REQUIRED_STRING, 72 | "exp": SINGLE_REQUIRED_INT, 73 | "nbf": SINGLE_REQUIRED_INT, 74 | "sub": SINGLE_REQUIRED_STRING, 75 | "act": SINGLE_OPTIONAL_STS, 76 | "scp": OPTIONAL_LIST_OF_STRINGS, 77 | } 78 | -------------------------------------------------------------------------------- /oidc_example/rp2/htdocs/opbyuid.mako: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | pyoidc RP 6 | 7 | 8 | 9 | 10 | 11 | 12 | 16 | 17 | 18 | 19 | 20 | 36 | 37 |
38 | 39 |
40 |

OP by UID

41 |

42 | You can perform a login to an OP's by using your unique identifier at the OP. 43 | A unique identifier is defined as your username@opserver, this may be equal to an e-mail address. 44 | A unique identifier is only equal to an e-mail address if the op server is published at the same 45 | server address as your e-mail provider. 46 |

47 | 52 |
53 | 54 |
55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | -------------------------------------------------------------------------------- /oidc_example/rp2/htdocs/acrvalue.mako: -------------------------------------------------------------------------------- 1 | 2 | 3 | <%! 4 | import re, string 5 | 6 | def createResult(acrvalues): 7 | """ 8 | Creates a drop-down based on the service configuration. 9 | """ 10 | element = "" 11 | for acr in acrvalues: 12 | name = acr 13 | if acr == "PASSWORD": 14 | name = "Username password authentication" 15 | elif acr == "CAS": 16 | name = "CAS authentication" 17 | elif acr == "SAML": 18 | name = "SAML IdP authentication" 19 | element += "
" 20 | element += "" 21 | element += name 22 | element += "" 23 | element += "
" 24 | return element 25 | %> 26 | 27 | 28 | 29 | pyoidc RP 30 | 31 | 32 | 33 | 34 | 35 | 36 | 40 | 41 | 42 | 43 | 44 | 60 | 61 |
62 | 63 |
64 |

Choose authentication method to use:

65 | ${createResult(acrvalues)} 66 |
67 | 68 |
69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | --------------------------------------------------------------------------------