Giter Club home page Giter Club logo

pyoidc's Introduction

image

image

image

image

image

image

image

A Python OpenID Connect implementation

This is a complete implementation of OpenID Connect as specified in the OpenID Connect Core specification. And as a side effect, a complete implementation of OAuth2.0 too.

Please see the CHANGELOG.md to review the latest changes.

Documentation

The documentation is graciously hosted by Read the Docs. Unfortunately, the documentation has been largely left unmaintained and there are various issues. However, the maintainers are trying to remedy this lately with some new momentum. Please help us by submitting pull requests if you can help improve the documentation.

Examples

Unfortunately, the current examples included in this repository are unmaintained and there are many issues. We're currently in the process of creating a working canonical example implementation, however, until that time, the current examples largely do not work. Please help us by submitting pull requests that may bring these examples back into a working condition if you get something working locally.

Acknowledgements

Cudos to Vladislav Mladenov and Christian Mainka both at Horst Görtz Institute for IT-Security, Ruhr-University Bochum, Germany for helping me making the implementation more secure.

Maintainers Needed

If you're interested in helping maintain and improve this package, we're looking for you! We're working on the project on a best effort basis but we still maintain a good flow of reviewing each others pull requests and driving discussions on what should be done.

Please contact one of the current maintainers @rohe, @tpazderka or @schlenk.

Contribute

Fork the repository, clone your copy and install pipenv.

Then just run:

$ make install

Next, running the tests:

$ make test

This will not affect your system level Python installation. Please review our issues to see what needs working on. Do not hesitate to ask questions if something is unclear. We mark easy issues as newcomer-friendly, so they are a good place to start if you want to contribute.

Windows

If you happen to work in a Windows environment, the above will not work out of the box due to the lack of a GNU Make on Windows. In addition one of the dependencies for ldap_authn is not available as a prebuilt wheel from pypi, so use these slightly modified instructions.

With pipenv in your path you run:

pipenv install --dev -e .[develop,testing,docs,quality]

Next you can run the tests:

pipenv run pytest tests

pyoidc's People

Contributors

bjmc avatar claweyenuk avatar dajiaji avatar dallerbarn avatar danielquinn avatar decentral1se avatar denali-lph avatar dependabot[bot] avatar eliasp avatar ermakoves avatar infohash avatar jgehrcke avatar joostd avatar jricher avatar justkiddingcode avatar jvazquez avatar lwm avatar maennel avatar mblomdahl avatar pkoffdeff avatar rectalogic avatar rohe avatar russel1237 avatar schlenk avatar selfissued avatar serac avatar teh avatar tpazderka avatar vjalili avatar zandbelt avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

pyoidc's Issues

Consider using longer, more descriptive variable names, especially in examples

Obviously brevity is frequently a virtue in programming, but as an outsider trying to grok this codebase, I am finding the prevalence of opaque two- and three-letter variable names to be a significant obstacle.

Some selected examples include acr, ac, cdb, and OAS. Of course I can go and track down where each terse variable is defined and figure out what they are. But especially in the examples, it would be much easier for newcomers to get their bearings if the variable names were long and explicit, and gave the reader some notion of what they are at a glance.

AttributeError is_jwe

When using the "code" response-type, the library logicaly tries to get a token. However it fails with:

File "C:\Users\Marc-Antoine\venv\oidc\lib\site-packages\oic\oic\__init__.py" in do_access_token_request
  588.                                                      authn_method, **kwargs)
File "C:\Users\Marc-Antoine\venv\oidc\lib\site-packages\oic\oauth2\__init__.py" in do_access_token_request
  617.                                        http_args=http_args, **kwargs)
File "C:\Users\Marc-Antoine\venv\oidc\lib\site-packages\oic\oauth2\__init__.py" in request_and_return
  551.                                            **kwargs)
File "C:\Users\Marc-Antoine\venv\oidc\lib\site-packages\oic\oauth2\__init__.py" in parse_request_response
  519.                                            state, **kwargs)
File "C:\Users\Marc-Antoine\venv\oidc\lib\site-packages\oic\oauth2\__init__.py" in parse_response
  445.                 verf = resp.verify(**kwargs)
File "C:\Users\Marc-Antoine\venv\oidc\lib\site-packages\oic\oic\message.py" in verify
  288.             idt = IdToken().from_jwt(str(self["id_token"]), **args)
File "C:\Users\Marc-Antoine\venv\oidc\lib\site-packages\oic\oauth2\message.py" in from_jwt
  482.         _jw = jwe.factory(txt)
File "C:\Users\Marc-Antoine\venv\oidc\lib\site-packages\jwkest\jwe.py" in factory
  698.     if _jw.is_jwe(p[0]):
File "C:\Users\Marc-Antoine\venv\oidc\lib\site-packages\jwkest\jws.py" in __getattr__
  283.             raise AttributeError(item)

Exception Type: AttributeError at /openid/authz_cb/
Exception Value: is_jwe

When looking at jwe.py line 695 (factory method), it seems a JWE is created, then immediately used with is_jwe. But I indeed cannot see (I may be blind, sorry if that's the case) such a method on JWE - only on JWEnc which is not a parent of JWE. As I must say this token business is way over my head, I would be hard pressed to propose a fix that does not introduce security issues or regressions...

I also suppose this is due to my using the recent version 1.0.0 of pyjwkest - but as the requirements.txt uses >=0.6.1, pip automatically downloaded that version. I may have opened the bug in your other project, but since this where I found the issue (and where you might want to require > 1.0.1) I put i here... feel free to change it.
(moreover, I should add that I could not downgrade if I wanted - version 0.6.1 uses cryptlib, which does not really have working Windows bindings anymore and my current project must run on that platform as well as RHEL)

Context: on Windows 8.1/2012R2, Inside a Django 1.8.1 project (hence the weird stacktrace format). Python x32 on x64 OS. Inside a virtualenv. Only modification to your library: from jwkest.ecc import NISTEllipticCurve (instead of from cryptlib) inside keyio.

I feel I should also mention this just in case, as Microsoft is known for less than ideal norm implémentations; I use an Azure AD OP.

 "azuread": {
        "srv_discovery_url": "https://sts.windows.net/my guid/",
        "behaviour": BEHAVIOUR,
        "client_registration": {
             "client_id": "taupesecret",
             "client_secret": "aussi taupe secret",
             "redirect_uris": ["http://localhost:8000/openid/authz_cb/"],
         }
    },

Would you be as kind as to help me debug this?
Thanks a lot.

NotAllowedValue: access_denied

I'm using the op1 and rp2 example servers to test out the pyoidc library. During setup, I forgot to properly configure the keys, so my OP failed when asked to issue an access token and responded with the error message '{"error_description": "Could not sign/encrypt id_token", "error": "access_denied"}'

However, on the RP side, the error I got was: NotAllowedValue: access_denied

It seems to be that the access token request code expects a TokenErrorResponse, but 'access_denied' is not a valid value for the error parameter of that response class. I'm not sure what the right approach is here. The RFC is a bit confusing for me... it mentions an error response for token requests in several places. At least one also includes "access_denied" as a valid error value, the other one doesn't.

So I'm not sure what to suggest. Maybe the c_allowed_values list of TokenErrorReponse should include 'access_denied' , or the endpoint code should raise an error with a valid "error" value.

Support Client Configuration Endpoint

http://openid.net/specs/openid-connect-registration-1_0.html#ClientConfigurationEndpoint

oic.oic.Provider:registration_endpoint works great, and the ClientRegistrationResponse includes the 'registration_client_uri'. However, if I issue GET requests to that URI, I usually get a 400 Bad Request response code. It looks like this is because the GET request is treated the same as a (new) Client Registration Request.

@rohe Is this "Client Read Request" functionality something intentionally omitted? Or would you welcome contributions to implement it?

oic.utils.sdb.py

Roland,

On the commit 2f22b2f the class SessionDB on the mentioned file had a new required argument on the constructor, "base_url"

When I try to run this on op1, it is failing now, because the base_url is missing.
Would "https://localhost" be ok to have the op1 server running again ?

edit doc/examples/rp.rst

edit doc/examples/rp.rst
Change OP port in the RP example
"As a UID enter username@localhost:8666"
"As a UID enter username@localhost:8093"

sp_conf does not exist

Roland,

Sorry, but... where is the stripped down version of config.py that you normally use? (previous issue)

In op2 example I see two config files: config.py.simple and config.py.full and when I run python server.py -p 8092 -d config, return this:

Traceback (most recent call last):
  File "server.py", line 478, in <module>
    userinfo=config.USERINFO)
  File "/home/herchila/Projects/virtualenv/pyoidc/local/lib/python2.7/site-packages/oic-0.7.6-py2.7.egg/oic/utils/authn/saml.py", line 75, in __init__
    self.sp_conf = importlib.import_module(spconf)
  File "/usr/lib/python2.7/importlib/__init__.py", line 37, in import_module
    __import__(name)
ImportError: No module named sp_conf

Cannot print provider config

The script

from oic.oic.consumer import Consumer
db={}
config={}
c=Consumer(db, config)
c.allow['issuer_mismatch'] = True
print c.provider_config('https://accounts.google.com')

fails with

Traceback (most recent call last):
  File "c.py", line 6, in <module>
    print c.provider_config('https://accounts.google.com')
  File "/usr/local/lib/python2.7/site-packages/oic/oauth2/message.py", line 520, in __str__
    return self.to_urlencoded()
  File "/usr/local/lib/python2.7/site-packages/oic/oauth2/message.py", line 119, in to_urlencoded
    raise MissingRequiredAttribute("%s" % attribute, self)
oic.oauth2.message.MissingRequiredAttribute: Missing required attribute 'id_token_signing_alg_values_supported'

I think either __str__shouldn't use to_urlencoded, or the config should be defined as a lax message (untested whether this would actually help).

KeyJar throws generic Exception if dance is done w/o SSl

For testing purposes, I am doing the Implicit Flow w/o the intermediary browser, instead copy/pasting responses query strings into an interactive session. On top of that, none of the RP or OP are using SSL as of now, which is to be added later in the implementation process.

As this is highly insecure, I expected the lib to throw an error – as it does:

/oic/utils/keyio.pyc in get(self, use, key_type, issuer, kid, **kwargs)
    418                         _keys = []
    419         else:
--> 420             _keys = self.issuer_keys[issuer]
    421 
    422         lst = []

Exception AttributeError: "'NoneType' object has no attribute 'path'" in <function _remove at 0x10063daa0> ignored

But due to this quite cryptic Exception I am unsure if this is related to the lack of SSL keys in the KeyJar. Or something else.

Content-Type headers should set charset=utf-8 parameter

PyOIDC sets a Content-Type header on the Response object, and it (sensibly) defaults to encoding responses as utf-8 in several places.

However, it does not include a charset=utf-8 parameter in the Content-Type header, so clients won't necessarily know that responses are utf-8 encoded.

As an improvement, I think any time PyOIDC is returning a Response() object with a utf-8 encoded body, it should also include an explicit charset=utf-8 parameter in the Content-Type header.

Passing of client_id from request to cdb causes oicServer:ERROR String or Integer object expected for key, unicode found

oicServer:ERROR String or Integer object expected for key, unicode found
Traceback (most recent call last):
File "./server.py", line 354, in application
return callback(environ, start_response, logger)
File "./server.py", line 113, in token
logger=logger)
File "/usr/local/lib/python2.7/dist-packages/oic-0.7.7-py2.7.egg/oic/utils/http_util.py", line 358, in wsgi_wrapper
args = func(*_kwargs)
File "/usr/local/lib/python2.7/dist-packages/oic-0.7.7-py2.7.egg/oic/oic/provider.py", line 943, in token_endpoint
return self._access_token_endpoint(req, *_kwargs)
File "/usr/local/lib/python2.7/dist-packages/oic-0.7.7-py2.7.egg/oic/oic/provider.py", line 819, in _access_token_endpoint
client_info = self.cdb[req["client_id"]]
File "/usr/local/lib/python2.7/dist-packages/oic-0.7.7-py2.7.egg/oic/utils/shelve_wrapper.py", line 37, in getitem
return db.getitem(key)
File "/usr/lib/python2.7/shelve.py", line 121, in getitem
f = StringIO(self.dict[key])
File "/usr/lib/python2.7/bsddb/init.py", line 270, in getitem
return _DeadlockWrap(lambda: self.db[key]) # self.db[key]
File "/usr/lib/python2.7/bsddb/dbutils.py", line 68, in DeadlockWrap
return function(__args, *__kwargs)
File "/usr/lib/python2.7/bsddb/init.py", line 270, in
return _DeadlockWrap(lambda: self.db[key]) # self.db[key]
TypeError: String or Integer object expected for key, unicode found

Error on logout

On web browser : {"error_description": null, "error": "Not allowed!"}

Bad response_type can lead to XSS attack

https://github.com/rohe/pyoidc/blob/master/src/oic/oic/provider.py#L1837

Because that line includes the user input in the BadRequest response, and does not sanitize the HTML before returning, this can be used to execute arbitrary JS

Example Request
https://my-oidc-provider.com/authorization/?other_valid_parts&response_type=<script>alert("hi")%3B<%2Fscript>

You probably should at least cgi.escape in here https://github.com/rohe/pyoidc/blob/master/src/oic/utils/http_util.py#L52

Google userinfo request failure

I am working with the rp3.py example, trying to get it working with a google email address; I uncommented the google portion of the conf.py, and edited in my client_id and secret. The return_uri is set to /authz_cb, as the rp3.py file does not seem to have a '/google' path case in its 'application' function.

The login process completes the round-trip to and from google's server, but the login process fails at the do_user_info_request, with a 401 error and 'invalid_token' and 'Invalid Credentials' in the json body.

Should rndstr() use SystemRandom instead of random?

The rndstr() function is used in various places to generate nonces, session ids, etc. Internally, it relies on the Python random module that uses a deterministic pseudorandom number generator.

The documentation for random includes a vague but stern warning:

Warning The pseudo-random generators of this module should not be used for security purposes. Use os.urandom() or SystemRandom if you require a cryptographically secure pseudo-random number generator.

Would it be better for rndstr() to use random = SystemRandom()? I'm not enough of an expert to say whether a CSPRNG is strictly necessary for all the places where rndstr is used, but maybe better safe than sorry?

Example for prompt=none

I'd like to write a program that uses the pyoidc from a native application to validate credentials using prompt=none. Is there an example or test case for this that I could look at?

oic.utils.SessionDB is not conducive to a shared DB with multiple SessionDB instances

It is common in my world to deploy multiple processes of an application server (like an oidc server) all sharing a database cluster. In this way, if one process or host crashes, the other instances can continue to serve requests.

I use oic.utils.sdb with a db object that reads/writes to a Redis instance on the network. I then run 2+ servers, each with a SessionDB instance that can read/write from that Redis.

However, I noticed that SessionDB().gets_sids_for_sub() does not return the same values on all hosts. It appears this is because this and a few other SessionDB methods make use of an in-memory .uid2sid cache that is only updated by writes to that one SessionDB instance. Writes to the underlying .db don't update that cache.

Here is a test suite that fails, illustrating that:

import unittest

from oic.oic.message import AuthorizationRequest
from oic.utils.sdb import AuthnEvent

from oic.utils.sdb import SessionDB


def create_auth_request():
    areq = AuthorizationRequest(response_type="code", client_id="client1",
                                redirect_uri="http://example.com/authz",
                                scope=["openid"], state="state000")
    return areq


def setup_session(session_db, uid):
    aevent = AuthnEvent(uid, 'salt')
    areq = create_auth_request()
    sid = session_db.create_authz_session(aevent=aevent,
                                          areq=areq)
    session_db.do_sub(sid, 'client_salt')
    return sid


class TestSessionDB(unittest.TestCase):

    def setUp(self):
        self.db = dict()
        self.sdb = SessionDB('https://1.com', db=self.db)

    def test_exists(self):
        self.assertTrue(bool(self.sdb))

    def test_get_sids_from_uid(self):
        # This passes
        uid = 'uid1'
        sid = setup_session(self.sdb, uid)
        # Now try to look it up
        sids_by_sub = self.sdb.get_sids_from_uid(uid)
        self.assertEqual(len(sids_by_sub), 1)
        self.assertIn(sid, sids_by_sub)

    def test_distributed_get_sids_from_uid(self):
        # This fails
        # Like the former test, but with multiple sdb instances
        # Sharing one db
        uid = 'uiduid'
        db = self.db
        # First
        sdb1 = SessionDB('https://1.com', db=db)
        sid1 = setup_session(sdb1, uid)
        # Second
        sdb2 = SessionDB('https://2.com', db=db)
        sid2 = setup_session(sdb2, uid)
        # Find both
        # From sdb1
        sdb1sids = sdb1.get_sids_from_uid(uid)
        sdb2sids = sdb2.get_sids_from_uid(uid)
        self.assertEqual(set(sdb1sids), set(sdb2sids))


if __name__ == "__main__":
    unittest.main()

I believe this can be fixed without too much effort, via

I'll be implementing a Subclass of SessionDB that tries to implement it, and will share my progress. But I wanted to share my intentions early in case others have solved this problem or thought of a better way.

howto/rp.rst outdated

The howto claims that I can instantiate a Consumer simply with Consumer(). This appears no longer to be true, as the database and the config parameters are mandatory.

Pycrypto version error

Could you update you setup script with Pycrypto version >= 2.7a1

with the stable release (2.6.1) i get the following error :
File "/usr/lib64/python2.6/site-packages/Crypto/Signature/PKCS1_v1_5.py", line 211, in EMSA_PKCS1_V1_5_ENCODE
digestAlgo = DerSequence([hash.oid, DerNull().encode()])
AttributeError: oid

Registration expiration

When a new registration is returned from the registration_endpoint, it contains a non-zero value in client_secret_expires_at. This indicates that after this time, the secret should not be valid but it is not true. The validity of secret is not checked anywhere during the processing of the requests.
According to specifications, this should return 0 in client_secret_expires_at.

But I think it would be more useful to add the checking of the client_secret validity to the code as well as a configuration option for the validity itself.

Q/FR: LDAP Support

Just a question and potential feature request: Can pyoidc be used an as OpenID layer around an LDAP server, like OpenID-LDAP.org's (outdated) implementation?

Compatibility with Python3?

Was trying to get use of pyoidc with Python 3.4 and ran into issues with jwt library that uses itertools.izip (not available in Python3 any more).
My questions are:

  • Are there any plans to get pyoidc compatible with Python 3?
  • Probably pic package at pypi site should be marked as uncompatible with Python 3 for now (it has no compatibility info so far )?

example configs up-to-date

Hi,
I'm currently trying to get the OP2/RP3 example up and running.
After a while, I got the servers now starting without errors ;) However, after trying to log in for the first time, I get errors again.

I'm intentionally not specifying which errors, since I think the provided configurations (conf.py, config.py and sp_conf.py) and documented steps are not up to date anymore.

Can you please:

  • confirm that example configs are not in sync anymore with the code
  • if so, update the examples in order to make it easy for people to get into your code and get it up and running (also thinking about future contributors)

Thanks,
-Manuel

Cleanup install_requires in setup.py

It seems that setup.py has also also documentation and example requirements in its install_requires.

It would be very nice, if install_requires would only have the real library requirements and additional requirements for running examples and building documentation would be in their own extras_requires' lists.

sp_conf

Roland,

Sorry, could you provide an example of sp_conf.py for op2 ?
This is related with issue #43

Traceback (most recent call last):
  File "server.py", line 479, in <module>
    "%s/authorization" % config.issuer, userinfo=config.USERINFO)
  File "/home/jvazquez/env/forkpyoidc/local/lib/python2.7/site-packages/oic/utils/authn/saml.py", line 72, in __init__
    self.sp_conf = importlib.import_module(spconf)
  File "/usr/lib/python2.7/importlib/__init__.py", line 37, in import_module
    __import__(name)
ImportError: No module named sp_conf

Examples or documentation for integrating a PyOIDC Provider with an existing web framework?

Thank you for all your hard work putting together this library. Looking at the examples, it seems like they are designed to be launched as full-fledged WSGI applications in their own right, with their own authentication backend.

Do you have any suggestions on the best approach for adding PyOIDC Provider support to an existing Python MVC application (something like Flask, Django, or Pyramid)? That application framework would be handling things like routing, authentication, rendering HTTP responses, and setting cookies.

It seems like PyOIDC has a well thought-out modular design, but I'm not sure where to begin in order to work with it at higher level of abstraction, and integrate it into an existing application. Do I still need to create a Provider instance? And then have my view layer proxy requests through to it? It looks like the SessionDB and ClientDB are designed to be wrappers around a persistence layer? If I want to use an existing database, what interface does it need to present to those classes?

Thanks for any advice you (or anyone else) can offer. If I can get a working prototype going, I'd be happy to contribute an example or perhaps some documentation.

OP2 Add saml2 to your dependencies

On op2 I get the error :

Traceback (most recent call last):
File "server.py", line 20, in
from oic.utils.userinfo.aa_info import AaUserInfo
File ".../oic-0.5.0beta-py2.6.egg/oic/utils/userinfo/aa_info.py", line 4, in
from saml2 import saml, samlp

Error on OP/verify (using command line)

I try to get tokens from openid connect provider (op2) using command line interface (with curl)

curl -iSsL --data login=diana --data password=krall https://myop:8092/verify

I’m stuck to this error :
File ".../oic/oic/message.py", line 384, in verify
raise MissingRequiredAttribute("response_type missing")
oic.oauth2.message.MissingRequiredAttribute: Missing required attribute 'response_type missing'

What is the correct usage for standalone CLI test?

Any simple tutorial to start with ?

Hello everybody,

I am in the first steps to test pyoidc, I haven't use any single-sign-on method before and I would like to play with it.

Maybe we can begin using the wiki provided within github and publish some howtos.

Let's start with the simple one:

  1. A simple webpage that authenticate a user using a pyoid service.
  2. Let the pyoidc service works with just a fixed array of [(username,password,someinfo)] with passwords in plain text where the system will just validate the user and return the username and someinfo fields to the webpage of point 1. At this point what happen if the user authentication is not successful ? It will stay asking for password or just return to the webpage of point 1 ?
  3. The webpage will print if the user was successfully authenticated and will print it's username and someinfo.

What do you think ?

Next could be:

  1. Changing the fixed array and use an existing source like ldap or a relational database.

Then.

  1. Integrate pyoidc within wsgi in order to use a high grade web server like nginx with uwsgi.

Then.

  1. Remap some fields in order to provide fake information in case of websites that require too much private information.

Does anyone have some literature to read about this topic in a learning by example philosophy ?

Thanks.

UserInfoLDAP Enhancements

The UserInfoLDAP component could benefit from a couple enhancements:

  1. Proper support for StartTLS.
  2. Ability to configure the mapping of LDAP attribute names to OpenID Connect claims.

Should be auth_time decimal or float?

Being pyoidc OpenID Connect server, we have received several messages from RP's that auth_time returned from pyoidc is invalid. RP's cannot handle float numbers and expect decimal. There is a request for Google client to be more liberal and accept float - googleapis/google-oauth-java-client#119, however I've received the same info from people behind https://demo.c2id.com/oidc-client/ (don't know if they are using the same or different library). Maybe it's better to be more conservative on server side and provide decimal instead of float

Spec only says it's number:

auth_time
Time when the End-User authentication occurred. Its value is a JSON number representing the number of seconds from 1970-01-01T0:0:0Z as measured in UTC until the date/time. When a max_age request is made or when auth_time is requested as an Essential Claim, then this Claim is REQUIRED; otherwise, its inclusion is OPTIONAL. (The auth_time Claim semantically corresponds to the OpenID 2.0 PAPE [OpenID.PAPE] auth_time response parameter.)

Creating a Djano app from Pyoidc

Hi Rohe,

There are no Django apps which provide open-id-connect features yet. There are a few beginner django projects for openid connect, but I think Pyoidc is far ahead as a python implementation. I was wondering if I could use pyoidc interfaces on top of django-oauth-toolkit (which is a wonderful oauth2 provider for django apps) to create a well supported Django app for open-id connect.

I have a fair understanding of Django. If I want to create a django app (on top of django-oauth-toolkit), could you give me a few pointers on how to go about it? The basic crux of the question is, is it possible that I use Pyoidc libraries to generate id token (JWT) and insert it in the response of oauth2 provider flows? Thanks!

RP Tutorial typos

Here are some more issues with the tutorial:

In Implicit Flow, replace "seession" with "session", and "request_args" with "args".

Adding a Client Authorization flow after Authentication

Hi again. I am implementing an OP using pyoidc. I have Authentication and client registration working, which is sufficient for use cases where the end-user implicitly trusts the Client that is requesting Authorization to access some scopes on the end-user's bhealf.

Now I would like to work toward enabling second and third-party Clients that the end-user does not implicitly trust. After authentication with username/password, the end-user should see a form essentially asking "Do you authorize Client xyz to access scopes 'x y z' on your behalf?". If the user chooses 'yes' (or a subset of the requested scopes), then the Authorization flow will continue. If the user does not authorize the Client, then the OP should redirect back to the Client's redirect_uri with an 'access_denied' indication (per http://tools.ietf.org/html/rfc6749#section-4.1.2.1). This is similar to how Twitter and Facebook's authorization flows work.

@rohe (or anyone else), do you know of any open-source examples of implementing this step in pyoidc? I can't find any in the existing examples.

If not, that's alright. Perhaps you can share some advice on the best way of implementing it? I can't quite tell if this authorizing-the-client step should be implemented with:

  • A UserAuthnMethod to be used in conjunction with my UsernamePasswordUserAuthnMethod via setup_multi_auth
    • This is what I researched yesterday, but I think it may not be appropriate to consider this client authorization step part of multi_auth(entication). It's not really authentication. It looks like mose uses of multi_auth is for two-factor authentication, not authentication followed by authorization.
  • A custom AuthzHandling object. I can't find any comments indicating what concerns this object is resonsible for. What is an AuthzHandling?
    • This base AuthzHandling class uses an in-memory permdb that is never written to. The custom one would read from a networked database. But what about writes?
      • Writes would come from a new endpoint, form, and form handler that I'd implement.
      • Instead of returning None when permdb[uid] is None in my AuthzHandling, I'd throw an exception indicating that the end-user has never authorized the ClientID in the AuthorizaitonRequest. My Provider class's authz_part2 would catch this, and return a Response object that redirected a user to the form I mention above.
      • The user fills out the form and submits it. On submit, the scopes that the user authorizes the client to use will be written to... where?
        • Should this be a new logical database of ClientAuthorization(uid, client_id, authorized_scopes) ?
        • Or is this the intended purpose of the 'scope' property on objects in the Session DB? Right now it looks like that is always set to the full set of scopes in the AuthorizationRequest, without an obvious way to let the end-user authorize that set. Or perhaps 'access_token_scope'?

Thank you for your advice. I'll be pursuing the second option right now.

[oidexample op2]

If you add new dependencies, could you please document your dependencies?
I currently work on OP2/RP2 examples and since your last commit, saml2 is needed.
Which example OP/RP should I use?

Unicode Cookies Break CherryPy

It appears that b1953a1 and related commits create unicode headers, which appears to break cherrypy on Python 2.7:

WSGI response header key u'Set-Cookie' is not of type str.
['Traceback (most recent call last):\n', '  File "/Users/marvin/Documents/Develop/pyoidc-op/src/server.py", line 374, in application\n    return callback(environ, start_response, logger)\n', '  File "/Users/marvin/Code/pyoidc/src/oic/oauth2/provider.py", line 66, in __call__\n    return self.func(*args, **kwargs)\n', '  File "/Users/marvin/Documents/Develop/pyoidc-op/src/server.py", line 172, in authorization\n    logger=logger)\n', '  File "/Users/marvin/Code/pyoidc/src/oic/utils/http_util.py", line 380, in wsgi_wrapper\n    return resp(environ, start_response)\n', '  File "/Users/marvin/Code/pyoidc/src/oic/utils/http_util.py", line 114, in __call__\n    start_response(self.status, self.headers)\n', '  File "/Library/Python/2.7/site-packages/cherrypy/wsgiserver/wsgiserver2.py", line 2173, in start_response\n    raise TypeError("WSGI response header key %r is not of type str." % k)\n', "TypeError: WSGI response header key u'Set-Cookie' is not of type str.\n"]
AssertionError('WSGI start_response called a second time with no exc_info.',)
Traceback (most recent call last):
  File "/Library/Python/2.7/site-packages/cherrypy/wsgiserver/wsgiserver2.py", line 1302, in communicate
    req.respond()
  File "/Library/Python/2.7/site-packages/cherrypy/wsgiserver/wsgiserver2.py", line 831, in respond
    self.server.gateway(self).respond()
  File "/Library/Python/2.7/site-packages/cherrypy/wsgiserver/wsgiserver2.py", line 2135, in respond
    response = self.req.server.wsgi_app(self.env, self.start_response)
  File "/Users/marvin/Documents/Develop/pyoidc-op/src/server.py", line 381, in application
    return resp(environ, start_response)
  File "/Users/marvin/Code/pyoidc/src/oic/utils/http_util.py", line 49, in __call__
    start_response(self.status, self.headers)
  File "/Library/Python/2.7/site-packages/cherrypy/wsgiserver/wsgiserver2.py", line 2157, in start_response
    raise AssertionError("WSGI start_response called a second "
AssertionError: WSGI start_response called a second time with no exc_info

The double invocation of start_response is an artifact of the error handling (which may be a separate bug), but the root cause is the type of cookie header/values. The cherrypy code reference that enforces str type is here:

https://bitbucket.org/cherrypy/cherrypy/src/ea07b29deabd28d5a10b764a1a452c876692d028/cherrypy/wsgiserver/wsgiserver2.py?at=default&fileviewer=file-view-default#wsgiserver2.py-2283

HTTP protocol headers are defined to contain exclusively ASCII characters [1], so the cherrypy check may be correct from a strict viewpoint (at least for python 2.7 where unicode data is a separate type).

[1] https://www.w3.org/Protocols/rfc2616/rfc2616-sec2.html#sec2

Token Endpoint should respond with nocache headers

Per OAuth2:

The authorization server MUST include the HTTP "Cache-Control"
   response header field [RFC2616] with a value of "no-store" in any
   response containing tokens, credentials, or other sensitive
   information, as well as the "Pragma" response header field [RFC2616]
   with a value of "no-cache".`

And OIDC:

All Token Responses that contain tokens, secrets, or other sensitive information MUST include the following HTTP response header fields and values:
Header Name Header Value
Cache-Control   no-store
Pragma  no-cache

But oic.oic.provider:Provider.token_endpoint does not set these headers. While I'm using 0.7.6, it also looks like the latest master branch is the same in this regard.

In my Provider, subclass, I created a decorator for token_endpoint to enforce this. Others may find it useful.

# HTTP headers required with successful token responses, per OAuth2 Spec
# https://tools.ietf.org/html/rfc6749#section-5.1
OAUTH2_NOCACHE_HEADERS = (
    ('Pragma', 'no-cache'),
    ('Cache-Control', 'no-store'),
)


def nocache_if_successful_token_response(token_endpoint):
    """
    Decorate Provider.token_endpoint to ensure compliance with
    https://tools.ietf.org/html/rfc6749#section-5.1
    by adding nocache response headers if needed
    """
    def wrapped(*args, **kwargs):
        response = token_endpoint(*args, **kwargs)
        return _ensure_token_response_nocache(response) or response
    return wrapped


def _ensure_token_response_nocache(response,
                                   required_headers=OAUTH2_NOCACHE_HEADERS):
    """
    Ensure a given token response is has no cache headers that it is required
    to have per the OAuth2 Spec. This mutates the provided response. :(

    The authorization server MUST include the HTTP "Cache-Control"
    response header field [RFC2616] with a value of "no-store" in any
    response containing tokens, credentials, or other sensitive
    information, as well as the "Pragma" response header field [RFC2616]
    with a value of "no-cache".

    :param response:
    :type response: oic.utils.http_util:Response
    :rtype: oic.utils.http_util:Response
    """
    successful = ( response.status == '200 OK' )
    contains_sensitive_info = ( '"access_token"' in response.message )
    if not (successful and contains_sensitive_info):
        # Doesn't need cache headers
        return response
    existing_headers = (h for h,v in response.headers)
    for header, value in OAUTH2_NOCACHE_HEADERS:
        # Only set headers if they weren't already set
        if header not in existing_headers:
            response.headers.append((header, value))
    return response

from oic.oic.provider import Provider
class MyProvider(Provider):
    @nocache_if_successful_token_response
    def token_endpoint(self, *args, **kwargs):
        response = super(MyProvider, self).token_endpoint(*args, **kwargs)
        return response

I'm not in a position to make a tested PR right away, but will strive to upstream some things like this once my subclass is polished a bit.

parse_qs from future is not handling unicode

Passing unicode string with characters outside range(128) causes deserialize() to fail.

It looks like an issue with parse_qs imported from future.backports.urllib.parse. When the function is imported from urlparse (Python 2) or urllib.parse (Python 3), the conversion goes without any issues.

parse_cookie() triggering exception on Python 2.7.10

I'm getting an exception when parse_cookie() tries to read a cookie set by PyOIDC. I've included an example below to reproduce the error.

I believe the problem has something to do with the way the backports module handles strings in Python 2.7. In the debugger, if I try SimpleCookie(unicode(kaka)), then the exception is not triggered.

import sys

from oic.utils.http_util import parse_cookie

print sys.version
#2.7.10 (default, Oct 14 2015, 16:09:02)
# [GCC 5.2.1 20151010]

kaka = 'pyoidc=bjmc::1463043535::upm|1463043535|18a201305fa15a96ce4048e1fbb03f7715f86499'
seed = ''
name = 'pyoidc'

result = parse_cookie(name, seed, kaka)

# /home/bjmc/Sandbox/project/.env/lib/python2.7/site-packages/oic/utils/http_util.pyc in parse_cookie(name, seed, kaka)
#     278         return None
#     279
# --> 280     cookie_obj = SimpleCookie(kaka)
#     281     morsel = cookie_obj.get(name)
#     282
#
# /home/bjmc/Sandbox/project/.env/lib/python2.7/site-packages/future/backports/http/cookies.pyc in __init__(self, input)
#     490     def __init__(self, input=None):
#     491         if input:
# --> 492             self.load(input)
#     493
#     494     def __set(self, key, real_value, coded_value):
#
# /home/bjmc/Sandbox/project/.env/lib/python2.7/site-packages/future/backports/http/cookies.pyc in load(self, rawdata)
#     543         else:
#     544             # self.update() wouldn't call our custom __setitem__
# --> 545             for key, value in rawdata.items():
#     546                 self[key] = value
#     547         return
#
# AttributeError: 'str' object has no attribute 'items'

Missing json file in op1

Roland, would it be possible to attach to this ticket an example for the file claims_client.json (oidc_example/op1)
When you run start.sh on the oidc_example/op1 it fails partially because it can't find that file

(open_id_connect)jvazquez@alphatauri:~/sandbox/pyoidc/oidc_example/op1$ sh start.sh 
(open_id_connect)jvazquez@alphatauri:~/sandbox/pyoidc/oidc_example/op1$ Traceback (most recent call last):
  File "./claims_provider.py", line 237, in 
    cdb = json.loads(open("claims_client.json").read())
IOError: [Errno 2] No such file or directory: 'claims_client.json'

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.