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
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)
# 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):
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)
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)
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):
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,
# 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)
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')
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)