Page tree

Versions Compared

Key

  • This line was added.
  • This line was removed.
  • Formatting was changed.

...

MethodDescriptionParameterExample
Verify Access TokenAllows a client to verify an access token not directly requested from the oAuth2 server.
Column
width250px
access_tokentoken to verify
Column
width410px

Request:
http://workflowmgr:8003/oauth/tokeninfo
    ?access_token=OWLwWDpL2QDWKAHN8qWC7eBwqjKjs9

Response:
{
    "id": 42,
    "scope": ["read"]
}

 

The following examples show concrete use-cases where the session login and oAuth2 mechanisms are used within the Flarecast infrastructure.

...

By the above section we described the workflows of a session login and oAuth2 authentification. The concrete implementation is given by two Flask-addons which are build on the top of our web servers used within the Flarecast infrastructure.

 Python Package DescriptionURL / Documentation
flask-login  
flask-oauthlib 

 

,

which file contains what? parametrization

(0.4.0)User session management support for Flask.https://flask-login.readthedocs.io/en/latest/
flask-oauthlib (0.9.3)oAuth1/oAuth2 client and provider support for Flask.
Build with the oauthlib.
https://flask-oauthlib.readthedocs.io/en/latest/

The integration of the two packages can be found within the python-base image. Hereby, the following files are involved:

  1. flask_login:

    File

    DescriptionHighlights
    /flarecast/service/flarecast_service.pyWrapper class for the Connexion framework (including Flask & Swagger).
    Code Block
    languagepy
    collapsetrue
    # Initialize flask_login
    login_manager = flask_login.LoginManager()
    login_manager.init_app(app.app)
    # set login view which is used for unauthorized
    # users which access protected ressources
    login_manager.login_view = "/login"
    
    # callback used for reloading a user based on
    # the request's session information
    @login_manager.user_loader
    def load_user(user_id):
        with AdminDAO() as ctx:
            return ctx.get_user_by_id(user_id)
        return None
    /flarecast/service/models.pyModule which holds all models required by flask_login and flask_oauthlib.
    Code Block
    languagepy
    collapsetrue
    # class which holds all user related information
    class User(UserMixin):
        def __init__(
            self,
            id, name, password_hash,
            is_authenticated=False, is_active=False, is_anonymous=True
        ):
            self.id = id
            self.name = name
            self.password_hash = password_hash
            self.authenticated = is_authenticated
            self.active = is_active
            self.anonymous = is_anonymous
    
        def set_password_hash(self, password):
            self.password_hash = md5(password+self.name).hexdigest()
    
        def is_correct_password(self, password):
            return self.password_hash == md5(password + self.name).hexdigest()
    
        # ...
    /flarecast/service/users/views.pyBlueprint of the user login and logout pages.
    Code Block
    languagepy
    collapsetrue
    @blueprint.route('/login', methods=['GET', 'POST'])
    def login():
        if request.method == 'POST':
            form = LoginForm(request.form)
            with AdminDAO() as ctx:
                # load user from database
                user = ctx.get_user_by_name(form.username.data)
            if (
                user is not None and
                user.is_correct_password(form.password.data)
            ):
                # set flag as user was authenticated
                user.set_authenticated(True)
                with AdminDAO() as ctx:
                   ctx.update_user_is_authenticated(
                        user.id, user.is_authenticated
                    )
                # create session cookies with user and session IDs
                login_user(user)
                return redirect(redirect_url('ui'))
        return render_template('login.html', form=form)
    /flarecast/service/admin_dao.pyThe data access object (DAO) which mapps requests and database cursers to database queries and python objects.
    Code Block
    languagepy
    collapsetrue
    # ...
  2. flask_oauthlib

    File

    DescriptionHighlights
    /flarecast/service/flarecast_service.pyWrapper class for the Connexion framework (including Flask & Swagger).
    Code Block
    languagepy
    collapsetrue
    # Initialize provider from flask_oauthlib
    oauth = FlarecastProvider()
    oauth.init_app(app.app)
    
    app.app.config.update(
        # set default error page
        OAUTH2_PROVIDER_ERROR_ENDPOINT='/error',
        # set expire time in seconds for access tokens (86400s = 1d)
        OAUTH2_PROVIDER_TOKEN_EXPIRES_IN=86400
    )
    /flarecast/service/models.pyModule which holds all models required by flask_login and flask_oauthlib.
    Code Block
    languagepy
    collapsetrue
    # ...
    /flarecast/service/oauth/views.pyBlueprint for authorize, access, revoke and validate access tokens. Each route is wrapped by a function provided by flask_oauthlib which implements the authorization flow.
    Code Block
    languagepy
    collapsetrue
    # authorization route where the end-user grant's an access token for a client
    @blueprint.route('/oauth/authorize', methods=['GET', 'POST'])
    @flask_login.login_required  # route req. session login
    @oauth.authorize_handler
    def authorize(*args, **kwargs):
        if request.method == 'GET':
            with AdminDAO() as ctx:
                client = ctx.get_client_by_id(int(kwargs.get('client_id')))
            return render_template('confirm.html', **kwargs)
        confirm = request.form.get('confirm', 'no')
        return confirm == 'yes'
    
    # request route for access tokens
    @blueprint.route('/oauth/token', methods=['POST'])
    @oauth.token_handler
    def access_token():
        return {'version': '1.0.0'}
    
    # route for revoke access tokens
    @blueprint.route('/oauth/revoke', methods=['POST'])
    @oauth.revoke_handler
    def revoke_token():
        pass
    
    # route for verifying access tokens
    @blueprint.route('/oauth/tokeninfo')
    def get_tokeninfo():
        if "access_token" in request.args:
            with AdminDAO() as ctx:
                bearer_token = ctx.get_bearer_token_by_access_token(
                    request.args.get("access_token")
                )
                if bearer_token is not None:
                    return jsonify(
                        {'uid': bearer_token.id, 'scope': bearer_token.scopes}
                    )
        return 'No such token', 401
    /flarecast/service/admin_dao.pyThe data access object (DAO) which mapps requests and database cursers to database queries and python objects.
    Code Block
    languagepy
    collapsetrue
    # ...

A last missing fragment is the database schema which persistantly stores the users, clients and tokens refered above:

Image Added

The central entity is the user which, by assumption, owns ressources he grants access to. A client can then define reliable clients and administrate there grant as well as bearer (access) tokens. This workflow however is not defined by the above database schema nor oAuth2. Flarecast do not provide corresponding functionalities for managing users at the moment, so each user has to be manually added to the database. Furthermore, a fine-grained user management system may add some user roles which could then be mapped to specific scopes. An example may look like:

User RoleResource ScopeExample
ReaderreadAccess to protected routes for querying configurations of prediction algorithms.
WriterwriteAccess to protected routes for adding prediction data to existing predictions.
Moderatorread, writeFull (read and write) access to the prediction service.
Administratorread, write, executeFull access to the workflow management service, including protected routes for running and stopping Docker containers.


login_manager = flask_login.LoginManager()
        login_manager.init_app(app.app)
        login_manager.login_view = "users.login"

        @login_manager.user_loader
        def load_user(user_id):
            with AdminDAO() as ctx:
                return ctx.get_user_by_id(user_id)
            return None