Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 19 additions & 1 deletion README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -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 = ""
Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The GLOBUS_AUTH_ENABLED config option was removed. Not including a Client ID or Secret does effectively the same thing.


EZID_SERVER = "https://ezid.cdlib.org"
EZID_SCHEME = "ark:/"
Expand All @@ -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"]

63 changes: 32 additions & 31 deletions minid_server/api/utils.py
Original file line number Diff line number Diff line change
@@ -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'])
Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Originally this was set to username, which isn't what we want. Sometimes usernames match up with emails, but not always. One example is Globus IDs, which could have a user with the id of malcomreynolds@globusid.org and email of malcomr@globus.org.

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):
Expand Down
32 changes: 22 additions & 10 deletions minid_server/api/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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()
Expand Down Expand Up @@ -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:
Expand Down Expand Up @@ -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:
Expand Down
33 changes: 33 additions & 0 deletions minid_server/config.py
Original file line number Diff line number Diff line change
@@ -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 = ""