Metadata-Version: 2.1
Name: oidcat
Version: 0.5.2
Summary: easy oidc client & server
Home-page: UNKNOWN
Author: Bea Steers
Author-email: bea.steers@gmail.com
License: MIT License
Description: # oidcat
        
        Simple OIDC.
        
        It's all so confusing, so I wrote a (small) wrapper package around `requests` and `flask-oidc` that provides a (slightly) easier interface to protect and connect to private resources.
        
        I don't have time to wait for PRs to merge in `flask_oidc`, but maybe I'll get some of this merged eventually!
        
        ## Install
        
        ```bash
        pip install oidcat
        ```
        
        ## Usage
        
        ### Client
        This is a `requests.Session` object that will handle tokens entirely for you. No need to refresh tokens, no need to manually log back in when both your access and refresh tokens expire.
        
        ```python
        import os
        import oidcat
        
        # basic login:
        sess = oidcat.Session('auth.myapp.com', os.getenv('USERNAME'), os.getenv('PASSWORD'))
        
        # that's it! all future requests will use the token
        # and it will automatically refresh so effectively, it'll never expire!
        out = sess.get('https://api.myapp.com/view').json()
        ```
        
        
        ### Resource Server
        
        Here's an example resource server.
        
        >NOTE: Technically you can do this without creating a client (and omit them in `with_well_known_secrets_file`) and it will use the `admin-cli` client.
        
        ```python
        import os
        import flask
        import oidcat.server
        
        
        app = flask.Flask(__name__)
        app.config.update(
            # Create the client configuration (makes request to well known url)
            OIDC_CLIENT_SECRETS=oidcat.util.with_well_known_secrets_file(
                'auth.myapp.com', 'myclient', 'supersecret'),
        
            # or:
            # Create keycloak client configuration (doesn't need request)
            # OIDC_CLIENT_SECRETS=oidcat.util.with_keycloak_secrets_file(
            #     'auth.myapp.com', 'myclient', 'supersecret', 'myrealm'),
        )
        
        import sqlitedict
        oidc = oidcat.server.OpenIDConnect(app, credentials_store=sqlitedict.SqliteDict('creds.db', autocommit=True))
        # or equivalently:
        oidc = oidcat.server.OpenIDConnect(app, 'creds.db')
        
        
        # various forms of protecting endpoints
        
        @app.route('/')
        @oidc.require_login
        def index():
            '''This will redirect you to a login screen.'''5
            return flask.jsonify({'message': 'Welcome!'})
        
        
        # question - what exactly is the difference between these?
        
        @app.route('/edit')
        @oidc.accept_token(role='editor')  # client/realm role
        def edit():
            '''This will give a 402 if you don't pass `access_token`.'''
            return flask.jsonify({'message': 'you did something!'})
        
        @app.route('/edit')
        @oidc.accept_token(role='editor', realm=False)  # client role
        def edit():
            '''This will give a 402 if you don't pass `access_token`.'''
            return flask.jsonify({'message': 'you did something!'})
        
        
        @app.route('/view')
        @oidc.accept_token(scopes_required=['reader'])  # client scopes
        def view():
            '''This will give a 402 if you don't pass `access_token`.'''
            return flask.jsonify({'message': 'something interesting!'})
        
        
        @app.route('/ultimatepower')
        @oidc.accept_token(role='admin', client=None)  # realm role
        def ultimatepower():
            '''This will give a 402 if you don't pass `access_token`.'''
            return flask.jsonify({'message': 'mwahahah!'})
        
        
        if __name__=='__main__':
            app.run(host='0.0.0.0', port=PORT, debug=True)
        
        ```
        
        ### Changes
         - `Session`
            - add `Access` abstraction which encapsulates the access and refresh tokens, well known, and login/logout logic.
            - the access token is automatically added to requests using the Bearer token method.
                - to disable this on a per-request basis, pass `token=False` to your request method (e.g. `sess.get(..., token=False)`)
                - to disable this for all requests on the object, you can do `Session(..., require_token=False)`
                - and to re-enable it on a per-request basis: `sess.get(..., token=False)`
            - add a `login`/`logout` method (which is a convenience wrapper for the Access object)
         - `token`
            - add a token class which encapsulates the token, token data, and expiration logic
            - a token's truthiness can be used to determine if it needs to be refreshed
            - add token checking function `has_role`
         - `server`:
             - `accept_token` takes additional parameters:
                - `role (str, list)`: roles to check for in the token
                - `client (str, bool, default=True)`: see `has_role`
                - `checks (list of callables)`: you can pass arbitrary
        
             - `has_role` checks for keycloak roles in the token. right now we just support Keycloak compatible token formats
                 - `*roles (tuple[str])`: the roles to compare against
                 - `client_id (str, bool, default=True)`: if a string, it will check for roles in that
                         client_id. If True, it will check in the current client. If False/None, it
                         will check for realm roles.
        
             - `util.with_keycloak_secrets_file`: generate the client secrets file and return the path to it. See usage above.
                - this also handles all of the additional urls (token introspection, etc) from the base url.
Platform: UNKNOWN
Description-Content-Type: text/markdown
Provides-Extra: server
Provides-Extra: cli
