diff --git a/README.rst b/README.rst index 34ae452..4090acd 100644 --- a/README.rst +++ b/README.rst @@ -45,7 +45,9 @@ Here is an example configuration file that uses a local SQLite database and the HOSTNAME = "http://localhost:5000/minid" PORT = 5000 LANDING_PAGE = "http://localhost:5000/minid/landingpage" - GLOBUS_AUTH_ENABLED = False + + GLOBUS_CLIENT_ID = "a5cdede9-567f-4ae5-aba5-cb997d08693a" + GLOBUS_CLIENT_SECRET = "" EZID_SERVER = "https://ezid.cdlib.org" EZID_SCHEME = "ark:/" @@ -63,3 +65,19 @@ Here is an example configuration file that uses a local SQLite database and the AWS_ACCESS_KEY_ID = "" AWS_SECRET_ACCESS_KEY = "" AWS_EMAIL_SENDER = "" + + +Globus Auth +----------- + +The Minid Server needs to register two items: + +* A Globus App at https://developers.globus.org/ +* A Resource Server Scope at https://docs.globus.org/api/auth/reference/#create_scope + +Clients can then add the following scope to their login flow: + +.. code-block:: python + + scopes = ["https://auth.globus.org/scopes/a5cdede9-567f-4ae5-aba5-cb997d08693a/minid"] + diff --git a/minid_server/api/utils.py b/minid_server/api/utils.py index 9374aff..f5ba792 100644 --- a/minid_server/api/utils.py +++ b/minid_server/api/utils.py @@ -1,39 +1,40 @@ +from app import app +try: + import globus_sdk + GLOBUS_AUTH_ENABLED = bool(app.config.get('GLOBUS_CLIENT_ID') and + app.config.get('GLOBUS_CLIENT_SECRET')) +except ImportError: + GLOBUS_AUTH_ENABLED = False -def validate_globus_user(email, authorization_header): - try: - type, code = authorization_header.split() - if str(type) != 'Bearer': - raise AuthorizationException('Only Bearer tokens are supported ' - 'for Globus Auth', - user=email, type='InvalidToken') - import globus_sdk # noqa - ac = globus_sdk.AuthClient( - authorizer=globus_sdk.AccessTokenAuthorizer(code)) +def validate_globus_user(email, authorization_header): - try: - idents = ac.get_identities(email).get('identities') - if not idents: - raise AuthorizationException('User needs to link their email ' - '%s to their Globus identity: ' - 'globus.org/app/account' % email, - user=email, - type='InvalidIdentity') - except globus_sdk.exc.AuthAPIError: - raise AuthorizationException('Expired or invalid Globus Auth ' - 'code.', user=email, - type='AuthorizationFailed') - except (ValueError, AttributeError): - raise AuthorizationException('Invalid Globus Authorization Header.', + scheme, token = authorization_header.split() + if str(scheme) != 'Bearer': + raise AuthorizationException('Only Bearer token scheme is supported ' + 'for Globus Auth', + user=email, type='InvalidToken') + client = globus_sdk.ConfidentialAppAuthClient( + app.config.get('GLOBUS_CLIENT_ID'), + app.config.get('GLOBUS_CLIENT_SECRET') + ) + info = client.oauth2_token_introspect(token, 'identity_set') + if info.data.get('active') is False: + raise AuthorizationException('Expired or invalid Globus Auth ' + 'code.', user=email, + type='AuthorizationFailed') + ids = client.get_identities(ids=info.data['identity_set']) + linked_emails = [str(u_id['email']) + for u_id in ids['identities']] + if email not in linked_emails: + # We're assuming the user just needs to link their id. It's + # possible they're an impersonator and we should raise an error + raise AuthorizationException('User needs to link their email ' + '%s to their Globus identity: ' + 'globus.org/app/account' % email, user=email, - type='InvalidHeader') - except ImportError: - print('Please install Globus: "pip install globus_sdk"') - raise AuthorizationException('Server is misconfigured to use ' - 'Globus Auth, please notify ' - 'the administrator. Sorry.', user=email, - code=500, type='ServerError') + type='InvalidIdentity') class AuthorizationException(Exception): diff --git a/minid_server/api/views.py b/minid_server/api/views.py index 47ad974..e78a22c 100644 --- a/minid_server/api/views.py +++ b/minid_server/api/views.py @@ -4,7 +4,8 @@ import datetime from providers import EZIDClient from app import app, db, minid_email -from api.utils import AuthorizationException, validate_globus_user +from api.utils import (AuthorizationException, validate_globus_user, + GLOBUS_AUTH_ENABLED) # When using the test capabilities we append TEST to the checksum to avoid # colusion with the real namespace @@ -42,15 +43,26 @@ def create_ark(creator, title, created, test): def find_user(email, code): - - user = Miniduser.query.filter_by(email=email, code=code).first() - if user: - return user - else: - print('User %s with code %s does not exist.' % (email, code)) + """ + Authorize a user in order by: + 1. Check for matching email+code in database + 2. Valid Globus Auth header on the current request + If both fail, a 403 AuthorizationException is raised. + :param email: A users email + :param code: A minid code + :return: A user object + """ + + if code: + user = Miniduser.query.filter_by(email=email, code=code).first() + if user: + return user + else: + print('User %s with code %s does not exist.' % (email, code)) globus_auth_header = request.headers.get('Authorization') - if app.config['GLOBUS_AUTH_ENABLED'] and globus_auth_header: + if GLOBUS_AUTH_ENABLED and globus_auth_header: + print('Auth header found and Globus Auth is Enabled') validate_globus_user(email, globus_auth_header) user = Miniduser.query.filter_by(email=email).first() @@ -249,7 +261,7 @@ def create_entity(): content_key = request.json["content_key"] if "checksum_function" in request.json: - checksum_function = request.json["checksum_function"] + checksum_function = request.json["checksum_function"] test = False if "test" in request.json: @@ -312,7 +324,7 @@ def register_user(): orcid = request.json.get("orcid") globus_auth_header = request.headers.get('Authorization') - if globus_auth_header and not app.config['GLOBUS_AUTH_ENABLED']: + if globus_auth_header and not GLOBUS_AUTH_ENABLED: print('User tried to register with Globus, but it is not enabled.') abort(400) elif globus_auth_header: diff --git a/minid_server/config.py b/minid_server/config.py new file mode 100644 index 0000000..82107c0 --- /dev/null +++ b/minid_server/config.py @@ -0,0 +1,33 @@ +class Config(object): + """Don't store your secrets here! Instead, create a file called + 'local_config.py' which will override settings below and isn't + tracked by git to avoid accidental VC checkins. + """ + DEBUG = True + TESTING = True + + SQLALCHEMY_DATABASE_URI = "sqlite:////tmp/minid.db" + SQLALCHEMY_TRACK_MODIFICATIONS = True + + HOSTNAME = "http://localhost:5000/minid" + PORT = 5000 + LANDING_PAGE = "http://localhost:5000/minid/landingpage" + + GLOBUS_CLIENT_ID = "a5cdede9-567f-4ae5-aba5-cb997d08693a" + GLOBUS_CLIENT_SECRET = "" + + EZID_SERVER = "https://ezid.cdlib.org" + EZID_SCHEME = "ark:/" + EZID_SHOULDER = "99999/fk4" + EZID_USERNAME = "apitest" + EZID_PASSWORD = "apitest" + + TEST_EZID_SERVER = "https://ezid.cdlib.org" + TEST_EZID_SCHEME = "ark:/" + TEST_EZID_SHOULDER = "99999/fk4" + TEST_EZID_USERNAME = "apitest" + TEST_EZID_PASSWORD = "apitest" + + AWS_ACCESS_KEY_ID = "" + AWS_SECRET_ACCESS_KEY = "" +