Пример #1
0
def create_authorization_server(app, lazy=False):
    query_client = create_query_client_func(db.session, Client)
    save_token = create_save_token_func(db.session, Token)

    if lazy:
        server = AuthorizationServer()
        server.init_app(app, query_client, save_token)
    else:
        server = AuthorizationServer(app, query_client, save_token)

    @app.route('/oauth/authorize', methods=['GET', 'POST'])
    def authorize():
        if request.method == 'GET':
            user_id = request.args.get('user_id')
            if user_id:
                end_user = User.query.get(int(user_id))
            else:
                end_user = None
            try:
                grant = server.get_consent_grant(end_user=end_user)
                return grant.prompt or 'ok'
            except OAuth2Error as error:
                return url_encode(error.get_body())
        user_id = request.form.get('user_id')
        if user_id:
            grant_user = User.query.get(int(user_id))
        else:
            grant_user = None
        return server.create_authorization_response(grant_user=grant_user)

    @app.route('/oauth/token', methods=['GET', 'POST'])
    def issue_token():
        return server.create_token_response()
    return server
Пример #2
0
def config_oauth(app):
    require_oauth = ResourceProtector()
    authorization = AuthorizationServer()
    query_client = create_query_client_func(db.session, OAuth2Client)
    save_token = create_save_token_func(db.session, OAuth2Token)
    authorization.init_app(app,
                           query_client=query_client,
                           save_token=save_token)
    # support all openid grants
    authorization.register_grant(AuthorizationCodeGrant, [
        OpenIDCode(require_nonce=True, **app.config['OAUTH_JWT_CONFIG']),
    ])
    authorization.register_grant(HybridGrant)

    # protect resource
    bearer_cls = create_bearer_token_validator(db.session, OAuth2Token)
    require_oauth.register_token_validator(bearer_cls())

    register_as_extension(app, 'authorization', authorization)
    register_as_extension(app, 'require_oauth', require_oauth)
Пример #3
0
# For the full copyright and license information, please see the
# AUTHORS and LICENSE files distributed with this source code, or
# at https://www.sourcefabric.org/superdesk/license

import time
import logging
from authlib.integrations.flask_oauth2 import AuthorizationServer
from authlib.oauth2.rfc6749 import grants
from authlib.jose import jwt
import superdesk
from .models import query_client, save_token

logger = logging.getLogger(__name__)

authorization = AuthorizationServer(
    query_client=query_client,
    save_token=save_token,
)

bp = superdesk.Blueprint("auth_server", __name__)

TOKEN_ENDPOINT = "/auth_server/token"
shared_secret = None
expiration_delay = 0


@bp.route(TOKEN_ENDPOINT, methods=["POST"])
def issue_token():
    return authorization.create_token_response()


def generate_jwt_token(client, grant_type, user, scope):
Пример #4
0
class HybridGrant(_OpenIDHybridGrant):
    def create_authorization_code(self, client, grant_user, request):
        return create_authorization_code(client, grant_user, request)

    def exists_nonce(self, nonce, request):
        return exists_nonce(nonce, request)

    def get_jwt_config(self):
        return DUMMY_JWT_CONFIG

    def generate_user_info(self, user, scope):
        return generate_user_info(user, scope)


authorization = AuthorizationServer()
require_oauth = ResourceProtector()


def config_oauth(app):
    query_client = create_query_client_func(db.session, OAuth2Client)
    save_token = create_save_token_func(db.session, OAuth2Token)
    authorization.init_app(app,
                           query_client=query_client,
                           save_token=save_token)

    # support all openid grants
    authorization.register_grant(AuthorizationCodeGrant, [
        OpenIDCode(require_nonce=True),
    ])
    authorization.register_grant(ImplicitGrant)
Пример #5
0
        auth_code = OAuth2AuthorizationCode.query.filter_by(
            code=code, client_id=client.client_id).first()
        if auth_code and not auth_code.is_expired():
            return auth_code

    def delete_authorization_code(self, authorization_code):
        db.session.delete(authorization_code)
        db.session.commit()

    def authenticate_user(self, authorization_code):
        return User.query.get(authorization_code.user_id)


def do_not_save(token, request):
    pass


query_client = create_query_client_func(db.session, OAuth2Client)

authorization = AuthorizationServer(
    query_client=query_client,
    save_token=do_not_save,  # Because of JWT
)


def config_oauth(app):
    app.config['OAUTH2_ACCESS_TOKEN_GENERATOR'] = gen_access_token
    authorization.init_app(app)
    authorization.register_grant(AuthorizationCodeGrant,
                                 [CodeChallenge(required=True)])
        if token and token.is_refresh_token_active():
            return token

    def authenticate_user(self, credential):
        return User.query.get(credential.user_id)

    def revoke_old_credential(self, credential):
        credential.revoked = True
        db.session.add(credential)
        db.session.commit()


query_client = create_query_client_func(db.session, OAuth2Client)
save_token = create_save_token_func(db.session, OAuth2Token)
authorization = AuthorizationServer(
    query_client=query_client,
    save_token=save_token,
)
require_oauth = ResourceProtector()

authorization.init_app(app)

# support all grants
# authorization.register_grant(grants.ImplicitGrant)
authorization.register_grant(grants.ClientCredentialsGrant)
authorization.register_grant(AuthorizationCodeGrant,
                             [CodeChallenge(required=True)])
authorization.register_grant(PasswordGrant)
authorization.register_grant(RefreshTokenGrant)

# support revocation
revocation_cls = create_revocation_endpoint(db.session, OAuth2Token)
Пример #7
0
from flask_wtf import FlaskForm
from wtforms import StringField, PasswordField, SubmitField, SelectField, BooleanField, TextAreaField
from wtforms.validators import DataRequired, Email, EqualTo, Length, Optional
from authlib.integrations.flask_oauth2 import AuthorizationServer
from models import db, User, Client, Token, AuthorizationCode
from werkzeug.security import gen_salt
from authlib.integrations.sqla_oauth2 import (
    create_query_client_func,
    create_save_token_func
)
from authlib.oauth2.rfc6749 import grants
import time
from authlib.integrations.flask_oauth2 import ResourceProtector, current_token
from authlib.oauth2.rfc6750 import BearerTokenValidator

server = AuthorizationServer()
blueprint = Blueprint('auth', __name__)

## TODO: Change local session management (session id instead of username)

################################################################################
# User - A person that is using this service
# Client - An app that connects to this service and asks for user data
################################################################################


# OAuth Grant type that involves user giving it's credentials to a client and client loging in on their's behalf
class PasswordGrant(grants.ResourceOwnerPasswordCredentialsGrant):
    def authenticate_user(self, username, password):
        user = User.query.filter_by(username=username).first()
        if user.check_password(password):
Пример #8
0
        auth_code = OAuth2AuthorizationCode.query.filter_by(
            code=code, client_id=client.id).first()
        if auth_code and not auth_code.is_expired():
            return auth_code

    def delete_authorization_code(self, authorization_code):
        db.session.delete(authorization_code)
        db.session.commit()

    def authenticate_user(self, authorization_code):
        return User.query.get(authorization_code.user_id)


authorization = AuthorizationServer(
    app=app,
    query_client=lambda id: OAuth2Client.query.get(id),
    save_token=lambda *args, **kwargs: None,
)


def get_or_create_token(client, grant_type, user=None, **kwargs):
    valid_token = OAuth2Token.query.filter(
        OAuth2Token.client_id == client.id,
        OAuth2Token.user_id == user.id,
        OAuth2Token.revoked_at.is_(None),
    ).one_or_none()

    if not valid_token:
        try:
            valid_token = OAuth2Token(client_id=client.id,
                                      user_id=user.id,
Пример #9
0
# Load configuration--default, then custom. Path to latter set in Dockerfile.
app.config.from_object('default_settings')
app.config.from_envvar('PATH_TO_APP_CONFIG')

# Configure app to work with SQLAlchemy
db.init_app(app)
# Create tables.
# SQLAlchemy object not bound to app, so pass in app as arg.
# Alternatively could push an app context.
db.create_all(app=app)

query_client = create_query_client_func(db.session, Client)
save_token = create_save_token_func(db.session, Token)

server = AuthorizationServer(app,
                             query_client=query_client,
                             save_token=save_token)

# register AuthorizationCodeGrant to grant endpoint
server.register_grant(AuthorizationCodeGrant,
                      [OpenIDCode(require_nonce=False)])


def get_or_create_shib_user():
    if request.remote_user:
        user = User.query.filter(
            User.shib_id == request.remote_user).one_or_none()
        if not user:
            print("User not found in db; creating...")
            new_user = User(shib_id=request.remote_user)
            db.session.add(new_user)
Пример #10
0
def _init_oauth():
    """
    Initialize Afterglow OAuth2 server

    :return: None
    """
    from authlib.oauth2.rfc6749 import ClientMixin
    from authlib.oauth2.rfc6749 import grants
    from authlib.oauth2.rfc7636 import CodeChallenge
    from authlib.integrations.sqla_oauth2 import (OAuth2AuthorizationCodeMixin,
                                                  OAuth2TokenMixin,
                                                  create_save_token_func)
    from authlib.integrations.flask_oauth2 import AuthorizationServer
    from .resources.users import DbUser, db

    global Token, oauth_server

    class OAuth2Client(ClientMixin):
        """
        OAuth2 client definition class
        """
        name = None
        description = None
        client_id = None
        client_secret = None
        redirect_uris = None
        default_scopes = ('email', )
        token_endpoint_auth_method = 'client_secret_basic'
        allowed_grant_types = (
            'authorization_code',
            'refresh_token',
        )

        def __init__(self, **kwargs):
            """
            Initialize OAuth2 client from a dictionary of attributes

            :param kwargs: client attributes; the following are required::
                    - name: client name
                    - client_id: a random string
                    - client_secret: a random string
                    - redirect_uris: a list of redirect uris; the first one
                        is used by default
                    - consent_uri: redirect URI of the user consent page

                Optional attributes::
                    - description: client description
                    - default_scopes: list of default scopes of the client
                    - token_endpoint_auth_method: RFC7591 token endpoint
                        authentication method: "none" (public client),
                        "client_secret_post" (client uses the HTTP POST
                        parameters), or "client_secret_basic" (client uses
                        basic HTTP auth)
                    - allowed_grant_types: list of allowed grant types,
                        including "authorization_code", "implicit",
                        "client_credentials", and "password"
            """
            for name, val in kwargs.items():
                setattr(self, name, val)

            if self.name is None:
                raise ValueError('Missing OAuth client name')
            if self.client_id is None:
                raise ValueError('Missing OAuth client ID')

            if not self.redirect_uris:
                raise ValueError('Missing OAuth redirect URIs')

            if self.token_endpoint_auth_method not in ('none',
                                                       'client_secret_post',
                                                       'client_secret_basic'):
                raise ValueError('Invalid token endpoint auth method')

            if self.token_endpoint_auth_method != 'none' and \
                    self.client_secret is None:
                raise ValueError('Missing OAuth client secret')

            if self.description is None:
                self.description = self.name

        def get_client_id(self) -> str:
            """Return ID of the client"""
            return self.client_id

        def get_default_redirect_uri(self) -> str:
            """Return client default redirect_uri"""
            return self.redirect_uris[0]

        def get_allowed_scope(self, scope: str) -> str:
            """
            Return requested scopes which are supported by this client

            :param scope: requested scope(s), multiple scopes are separated
                by spaces
            """
            if scope is None:
                scope = ''
            return ' '.join(
                {s
                 for s in scope.split() if s in self.default_scopes})

        def check_redirect_uri(self, redirect_uri: str) -> bool:
            """Validate redirect_uri parameter in authorization endpoints

            :param redirect_uri: URL string for redirecting.

            :return: True if valid redirect URI
            """
            return redirect_uri in self.redirect_uris

        def has_client_secret(self) -> bool:
            """Does the client has a secret?"""
            return bool(self.client_secret)

        def check_client_secret(self, client_secret: str) -> bool:
            """Validate client_secret

            :param client_secret: client secret

            :return: True if client secret matches the stored value
            """
            return client_secret == self.client_secret

        def check_token_endpoint_auth_method(self, method: str) -> bool:
            """Validate token endpoint auth method

            :param method: token endpoint auth method

            :return: True if the given token endpoint auth method matches
                the one for the server
            """
            return method == self.token_endpoint_auth_method

        def check_response_type(self, response_type: str) -> bool:
            """Check that the client can handle the given response_type

            :param response_type: requested response_type

            :return: True if a valid response type
            """
            return response_type in ('code', 'token')

        def check_grant_type(self, grant_type: str) -> bool:
            """Check that the client can handle the given grant_type

            :param grant_type: requested grant type

            :return: True if grant type is supported by client
            """
            return grant_type in self.allowed_grant_types

    class OAuth2AuthorizationCode(db.Model, OAuth2AuthorizationCodeMixin):
        __tablename__ = 'oauth_codes'
        __table_args__ = dict(sqlite_autoincrement=True)

        id = db.Column(db.Integer, primary_key=True)
        user_id = db.Column(db.Integer,
                            db.ForeignKey('users.id', ondelete='CASCADE'),
                            nullable=False)

        user = db.relationship('DbUser', uselist=False, backref='oauth_codes')

    class _Token(db.Model, OAuth2TokenMixin):
        """
        Token object; stored in the memory database
        """
        __tablename__ = 'oauth_tokens'
        __table_args__ = dict(sqlite_autoincrement=True)

        id = db.Column(db.Integer, primary_key=True)
        user_id = db.Column(db.Integer,
                            db.ForeignKey('users.id', ondelete='CASCADE'),
                            nullable=False)
        # override Mixin to set default=oauth2
        token_type = db.Column(db.String(40), default='oauth2')
        note = db.Column(db.Text, default='')

        user = db.relationship('DbUser', uselist=False, backref='oauth_tokens')

        @property
        def active(self) -> bool:
            if self.revoked:
                return False
            if not self.expires_in:
                return True
            return self.issued_at + self.expires_in >= time.time()

        def is_refresh_token_active(self):
            if self.revoked:
                return False
            expires_at = self.issued_at + \
                app.config.get('REFRESH_TOKEN_EXPIRES')
            return expires_at >= time.time()

    Token = _Token

    class AuthorizationCodeGrant(grants.AuthorizationCodeGrant):
        def save_authorization_code(self, code, req) -> None:
            """Save authorization_code for later use"""
            try:
                # noinspection PyArgumentList
                code_challenge = request.data.get('code_challenge')
                code_challenge_method = request.data.get(
                    'code_challenge_method')

                # noinspection PyArgumentList
                db.session.add(
                    OAuth2AuthorizationCode(
                        code=code,
                        client_id=req.client.client_id,
                        redirect_uri=req.redirect_uri,
                        scope=req.scope,
                        user_id=req.user.id,
                        code_challenge=code_challenge,
                        code_challenge_method=code_challenge_method,
                    ))
                db.session.commit()
            except Exception:
                db.session.rollback()
                raise

        def query_authorization_code(self, code, client) \
                -> OAuth2AuthorizationCode:
            item = OAuth2AuthorizationCode.query.filter_by(
                code=code, client_id=client.client_id).first()
            if item and not item.is_expired():
                return item

        def delete_authorization_code(self, authorization_code) -> None:
            try:
                db.session.delete(authorization_code)
                db.session.commit()
            except Exception:
                db.session.rollback()
                raise

        def authenticate_user(self, authorization_code) -> DbUser:
            return DbUser.query.get(authorization_code.user_id)

    class RefreshTokenGrant(grants.RefreshTokenGrant):
        def authenticate_refresh_token(self, refresh_token) -> Token:
            token = Token.query \
                .filter_by(refresh_token=refresh_token) \
                .first()
            if token and token.is_refresh_token_active():
                return token

        def authenticate_user(self, credential) -> DbUser:
            return credential.user

        def revoke_old_credential(self, credential) -> None:
            credential.revoked = True
            try:
                db.session.add(credential)
                db.session.commit()
            except Exception:
                db.session.rollback()
                raise

    for client_def in app.config.get('OAUTH_CLIENTS', []):
        oauth_clients[client_def.get('client_id')] = OAuth2Client(**client_def)

    def access_token_generator(*_):
        return secrets.token_hex(20)

    # Configure Afterglow OAuth2 tokens
    app.config['OAUTH2_ACCESS_TOKEN_GENERATOR'] = \
        app.config['OAUTH2_REFRESH_TOKEN_GENERATOR'] = \
        access_token_generator

    oauth_server = AuthorizationServer(
        app,
        query_client=lambda client_id: oauth_clients.get(client_id),
        save_token=create_save_token_func(db.session, Token),
    )
    oauth_server.register_grant(grants.ImplicitGrant)
    oauth_server.register_grant(grants.ClientCredentialsGrant)
    oauth_server.register_grant(AuthorizationCodeGrant,
                                [CodeChallenge(required=True)])
    oauth_server.register_grant(RefreshTokenGrant)

    app.logger.info('Initialized Afterglow OAuth2 Service')
Пример #11
0
        return create_authorization_code(client, grant_user, request)

    def exists_nonce(self, nonce, request):
        return exists_nonce(nonce, request)

    def get_jwt_config(self):
        return DUMMY_JWT_CONFIG

    def generate_user_info(self, user, scope):
        return generate_user_info(user, scope)


query_client = create_query_client_func(db.session, OAuth2Client)
save_token = create_save_token_func(db.session, OAuth2Token)
authorization = AuthorizationServer(
    query_client=query_client,
    save_token=save_token,
)
require_oauth = ResourceProtector()

authorization.init_app(app)

# support all openid grants
authorization.register_grant(ImplicitGrant)
authorization.register_grant(HybridGrant)
authorization.register_grant(AuthorizationCodeGrant,
                             [OpenIDCode(require_nonce=True)])

# support revocation
revocation_cls = create_revocation_endpoint(db.session, OAuth2Token)
authorization.register_endpoint(revocation_cls)