def create_perm(): try: perm = Permission.parse(request.json) except ValueError as e: raise ApiError(str(e), 400) if perm.match in ['admin', 'user']: raise ApiError('{} role already exists'.format(perm.match), 409) for want_scope in perm.scopes: if not Permission.is_in_scope(want_scope, have_scopes=g.scopes): raise ApiError("Requested scope '{}' not in existing scopes: {}".format( want_scope, ','.join(g.scopes)), 403) try: perm = perm.create() except Exception as e: raise ApiError(str(e), 500) admin_audit_trail.send(current_app._get_current_object(), event='permission-created', message='', user=g.login, customers=g.customers, scopes=g.scopes, resource_id=perm.id, type='permission', request=request) if perm: return jsonify(status='ok', id=perm.id, permission=perm.serialize), 201 else: raise ApiError('create API key failed', 500)
def list_perms(): perms = Permission.find_all() # add system-defined roles 'admin' and 'user' admin_perm = Permission( match='admin', scopes=[Scope.admin] ) perms.append(admin_perm) user_perm = Permission( match='user', scopes=current_app.config['USER_DEFAULT_SCOPES'] ) perms.append(user_perm) if perms: return jsonify( status='ok', permissions=[perm.serialize for perm in perms], total=len(perms) ) else: return jsonify( status='ok', message='not found', permissions=[], total=0 )
def list_perms(): query = qb.from_params(request.args) perms = Permission.find_all(query) admin_perm = Permission(match=DEFAULT_ADMIN_ROLE, scopes=[Scope.admin]) user_perm = Permission(match='user', scopes=current_app.config['USER_DEFAULT_SCOPES']) guest_perm = Permission(match='guest', scopes=current_app.config['GUEST_DEFAULT_SCOPES']) # add system-defined roles 'admin', 'user' and 'guest if 'scopes' in request.args: want_scopes = request.args.getlist('scopes') if set(admin_perm.scopes) & set(want_scopes): perms.append(admin_perm) if set(user_perm.scopes) & set(want_scopes): perms.append(user_perm) if set(guest_perm.scopes) & set(want_scopes): perms.append(guest_perm) else: perms.append(admin_perm) perms.append(user_perm) perms.append(guest_perm) if perms: return jsonify(status='ok', permissions=[perm.serialize for perm in perms], total=len(perms)) else: return jsonify(status='ok', message='not found', permissions=[], total=0)
def update_user(user_id): if not request.json: raise ApiError('nothing to change', 400) user = User.find_by_id(user_id) if not user: raise ApiError('not found', 404) if request.json.get('email'): user_by_email = User.find_by_email(request.json['email']) if user_by_email and user_by_email.id != user.id: raise ApiError('user with that email already exists', 409) if request.json.get('roles'): want_scopes = Permission.lookup(login='', roles=request.json['roles']) for want_scope in want_scopes: if not Permission.is_in_scope(want_scope, have_scopes=g.scopes): raise ApiError("Requested scope '{}' not in existing scopes: {}".format( want_scope, ','.join(g.scopes)), 403) admin_audit_trail.send(current_app._get_current_object(), event='user-updated', message='', user=g.login, customers=g.customers, scopes=g.scopes, resource_id=user.id, type='user', request=request) if user.update(**request.json): return jsonify(status='ok') else: raise ApiError('failed to update user', 500)
def test_system_roles(self): login = '******' roles = ['no-ops', 'team'] with self.app.test_request_context(): scopes = Permission.lookup(login, roles) self.assertEqual(scopes, [Scope.admin, Scope.read, Scope.write]) login = '******' roles = ['web', 'ops'] with self.app.test_request_context(): scopes = Permission.lookup(login, roles) self.assertEqual(scopes, [Scope.admin, Scope.read, Scope.write]) login = '******' roles = ['dev', 'engineer'] with self.app.test_request_context(): scopes = Permission.lookup(login, roles) self.assertEqual(scopes, [Scope.read, Scope.write]) login = '******' roles = ['guest'] with self.app.test_request_context(): scopes = Permission.lookup(login, roles) self.assertEqual(scopes, [Scope.read_alerts])
def update_user(user_id): if not request.json: raise ApiError('nothing to change', 400) user = User.find_by_id(user_id) if not user: raise ApiError('not found', 404) if request.json.get('email'): user_by_email = User.find_by_email(request.json['email']) if user_by_email and user_by_email.id != user.id: raise ApiError('user with that email already exists', 409) if request.json.get('roles'): want_scopes = Permission.lookup(login='', roles=request.json['roles']) for want_scope in want_scopes: if not Permission.is_in_scope(want_scope, have_scopes=g.scopes): raise ApiError("Requested scope '{}' not in existing scopes: {}".format( want_scope, ','.join(g.scopes)), 403) updated = user.update(**request.json) admin_audit_trail.send(current_app._get_current_object(), event='user-updated', message='', user=g.login, customers=g.customers, scopes=g.scopes, resource_id=user.id, type='user', request=request) if updated: return jsonify(status='ok', user=updated.serialize) else: raise ApiError('failed to update user', 500)
def create_perm(): try: perm = Permission.parse(request.json) except ValueError as e: raise ApiError(str(e), 400) if perm.match in ['admin', 'user']: raise ApiError('{} role already exists'.format(perm.match), 409) for want_scope in perm.scopes: if not Permission.is_in_scope(want_scope, have_scopes=g.scopes): raise ApiError("Requested scope '{}' not in existing scopes: {}".format( want_scope, ','.join(g.scopes)), 403) try: perm = perm.create() except Exception as e: raise ApiError(str(e), 500) admin_audit_trail.send(current_app._get_current_object(), event='permission-created', message='', user=g.user, customers=g.customers, scopes=g.scopes, resource_id=perm.id, type='permission', request=request) if perm: return jsonify(status='ok', id=perm.id, permission=perm.serialize), 201 else: raise ApiError('create API key failed', 500)
def list_perms(): query = qb.perms.from_params(request.args) total = Permission.count(query) perms: list[Permission] = [] admin_perm = Permission(match=current_app.config['DEFAULT_ADMIN_ROLE'], scopes=ADMIN_SCOPES) user_perm = Permission(match=current_app.config['DEFAULT_USER_ROLE'], scopes=current_app.config['USER_DEFAULT_SCOPES']) guest_perm = Permission(match=current_app.config['DEFAULT_GUEST_ROLE'], scopes=current_app.config['GUEST_DEFAULT_SCOPES']) # add system-defined roles 'admin', 'user' and 'guest if 'scopes' in request.args: want_scopes = request.args.getlist('scopes') if set(admin_perm.scopes) & set(want_scopes): perms.append(admin_perm) total += 1 if set(user_perm.scopes) & set(want_scopes): perms.append(user_perm) total += 1 if set(guest_perm.scopes) & set(want_scopes): perms.append(guest_perm) total += 1 else: perms.append(admin_perm) perms.append(user_perm) perms.append(guest_perm) total += 3 paging = Page.from_params(request.args, total) perms.extend( Permission.find_all(query, page=paging.page, page_size=paging.page_size)) if perms: return jsonify(status='ok', page=paging.page, pageSize=paging.page_size, pages=paging.pages, more=paging.has_more, permissions=[perm.serialize for perm in perms], total=total) else: return jsonify(status='ok', page=paging.page, pageSize=paging.page_size, pages=paging.pages, more=paging.has_more, message='not found', permissions=[], total=0)
def wrapped(*args, **kwargs): auth_header = request.headers.get('Authorization', '') m = re.match(r'Key (\S+)', auth_header) param = m.group(1) if m else request.args.get('api-key', None) if param: key = ApiKey.verify_key(param) if not key: raise ApiError("API key parameter '%s' is invalid" % param, 401) g.user = key.user g.customer = key.customer g.scopes = key.scopes if not Permission.is_in_scope(scope, g.scopes): raise ApiError('Missing required scope: %s' % scope, 403) else: return f(*args, **kwargs) auth_header = request.headers.get('Authorization', '') m = re.match(r'Bearer (\S+)', auth_header) token = m.group(1) if m else None if token: try: jwt = Jwt.parse(token) except DecodeError: raise ApiError('Token is invalid', 401) except ExpiredSignature: raise ApiError('Token has expired', 401) except InvalidAudience: raise ApiError('Invalid audience', 401) g.user = jwt.preferred_username g.customer = jwt.customer g.scopes = jwt.scopes if not Permission.is_in_scope(scope, g.scopes): raise ApiError("Missing required scope: %s" % scope, 403) else: return f(*args, **kwargs) if not current_app.config['AUTH_REQUIRED']: g.user = None g.customer = None g.scopes = [] return f(*args, **kwargs) # Google App Engine Cron Service if request.headers.get('X-Appengine-Cron', False) and request.headers.get('X-Forwarded-For', '') == '0.1.0.1': return f(*args, **kwargs) raise ApiError('Missing authorization API Key or Bearer Token', 401)
def test_system_roles(self): login = '******' roles = ['admin'] with self.app.test_request_context(): scopes = Permission.lookup(login, roles) self.assertEqual(scopes, [Scope.admin, Scope.read, Scope.write]) login = '******' roles = ['user'] with self.app.test_request_context(): scopes = Permission.lookup(login, roles) self.assertEqual(scopes, [Scope.read, Scope.write])
def create_user(): if current_app.config['AUTH_PROVIDER'] != 'basic': raise ApiError( 'must use {} login flow to create new user'.format( current_app.config['AUTH_PROVIDER']), 400) try: user = User.parse(request.json) except Exception as e: raise ApiError(str(e), 400) # check allowed domain if not_authorized('ALLOWED_EMAIL_DOMAINS', groups=[user.domain]): raise ApiError('unauthorized domain', 403) if User.find_by_username(username=user.email): raise ApiError('user with that email already exists', 409) want_scopes = Permission.lookup(login=user.email, roles=user.roles) for want_scope in want_scopes: if not Permission.is_in_scope(want_scope, have_scopes=g.scopes): raise ApiError( "Requested scope '{}' not in existing scopes: {}".format( want_scope, ','.join(g.scopes)), 403) try: user = user.create() except Exception as e: ApiError(str(e), 500) # if email verification is enforced, send confirmation email if current_app.config['EMAIL_VERIFICATION'] and not user.email_verified: user.send_confirmation() admin_audit_trail.send(current_app._get_current_object(), event='user-created', message='', user=g.login, customers=g.customers, scopes=g.scopes, resource_id=user.id, type='user', request=request) if user: return jsonify(status='ok', id=user.id, user=user.serialize), 201 else: raise ApiError('create user failed', 500)
def signup(): if not current_app.config['SIGNUP_ENABLED']: raise ApiError('user signup is disabled', 403) try: user = User.parse(request.json) except Exception as e: raise ApiError(str(e), 400) # set sign-up defaults user.roles = ['user'] user.email_verified = False # check allowed domain if not_authorized('ALLOWED_EMAIL_DOMAINS', groups=[user.domain]): raise ApiError('unauthorized domain', 403) if User.find_by_email(email=user.email): raise ApiError('username already exists', 409) try: user = user.create() except Exception as e: ApiError(str(e), 500) # if email verification is enforced, deny login and send email if current_app.config['EMAIL_VERIFICATION'] and not user.email_verified: user.send_confirmation() raise ApiError('email not verified', 403) # check user is active & update last login if user.status != 'active': raise ApiError('user not active', 403) user.update_last_login() # assign customers customers = get_customers(user.email, groups=[user.domain]) auth_audit_trail.send(current_app._get_current_object(), event='basic-auth-signup', message='user signup using BasicAuth', user=user.email, customers=customers, scopes=Permission.lookup(user.email, groups=user.roles), resource_id=user.id, type='user', request=request) # generate token token = create_token(user_id=user.id, name=user.name, login=user.email, provider='basic', customers=customers, roles=user.roles, email=user.email, email_verified=user.email_verified) return jsonify(token=token.tokenize)
def create_key(): try: key = ApiKey.parse(request.json) except ValueError as e: raise ApiError(str(e), 400) if 'admin' in g.scopes or 'admin:keys' in g.scopes: key.user = key.user or g.user key.customer = key.customer or g.get('customer', None) else: key.user = g.user key.customer = g.get('customer', None) if not key.user: raise ApiError( "An API key must be associated with a 'user'. Retry with user credentials.", 400) for want_scope in key.scopes: if not Permission.is_in_scope(want_scope, g.scopes): raise ApiError( "Requested scope '%s' not in existing scopes: %s" % (want_scope, ','.join(g.scopes)), 403) try: key = key.create() except Exception as e: raise ApiError(str(e), 500) if key: return jsonify(status="ok", key=key.key, data=key.serialize), 201 else: raise ApiError("create API key failed", 500)
def get_perm(perm_id): perm = Permission.find_by_id(perm_id) if perm: return jsonify(status='ok', total=1, permission=perm.serialize) else: raise ApiError('not found', 404)
def create_token(user_id: str, name: str, login: str, provider: str, customers: List[str], orgs: List[str] = None, groups: List[str] = None, roles: List[str] = None, email: str = None, email_verified: bool = None) -> 'Jwt': now = datetime.utcnow() scopes = Permission.lookup(login, groups=(roles or []) + (groups or []) + (orgs or [])) return Jwt(iss=request.url_root, typ='Bearer', sub=user_id, aud=current_app.config.get('OAUTH2_CLIENT_ID', None) or request.url_root, exp=(now + timedelta(days=current_app.config['TOKEN_EXPIRE_DAYS'])), nbf=now, iat=now, jti=str(uuid4()), name=name, preferred_username=login, orgs=orgs, roles=roles, groups=groups, provider=provider, scopes=scopes, email=email, email_verified=email_verified, customers=customers)
def update_perm(perm_id): if not request.json: raise ApiError('nothing to change', 400) for s in request.json.get('scopes', []): if s not in list(Scope): raise ApiError("'{}' is not a valid Scope".format(s), 400) perm = Permission.find_by_id(perm_id) if not perm: raise ApiError('not found', 404) admin_audit_trail.send(current_app._get_current_object(), event='permission-updated', message='', user=g.login, customers=g.customers, scopes=g.scopes, resource_id=perm.id, type='permission', request=request) if perm.update(**request.json): return jsonify(status='ok') else: raise ApiError('failed to update permission', 500)
def create_key(): try: key = ApiKey.parse(request.json) except ValueError as e: raise ApiError(str(e), 400) if Scope.admin in g.scopes or Scope.admin_keys in g.scopes: key.user = key.user or g.login else: key.user = g.login key.customer = assign_customer(wanted=key.customer, permission=Scope.admin_keys) if not key.user: raise ApiError("An API key must be associated with a 'user'. Retry with user credentials.", 400) for want_scope in key.scopes: if not Permission.is_in_scope(want_scope, have_scopes=g.scopes): raise ApiError("Requested scope '{}' not in existing scopes: {}".format( want_scope, ','.join(g.scopes)), 403) try: key = key.create() except Exception as e: raise ApiError(str(e), 500) write_audit_trail.send(current_app._get_current_object(), event='apikey-created', message='', user=g.login, customers=g.customers, scopes=g.scopes, resource_id=key.id, type='apikey', request=request) if key: return jsonify(status='ok', key=key.key, data=key.serialize), 201 else: raise ApiError('create API key failed', 500)
def update_key(key): if not request.json: raise ApiError('nothing to change', 400) if not current_app.config['AUTH_REQUIRED']: key = ApiKey.find_by_id(key) elif Scope.admin in g.scopes or Scope.admin_keys in g.scopes: key = ApiKey.find_by_id(key) else: key = ApiKey.find_by_id(key, user=g.login) if not key: raise ApiError('not found', 404) update = request.json update['customer'] = assign_customer(wanted=update.get('customer'), permission=Scope.admin_keys) for want_scope in update.get('scopes', []): if not Permission.is_in_scope(want_scope, have_scopes=g.scopes): raise ApiError("Requested scope '{}' not in existing scopes: {}".format( want_scope, ','.join(g.scopes)), 403) admin_audit_trail.send(current_app._get_current_object(), event='apikey-updated', message='', user=g.login, customers=g.customers, scopes=g.scopes, resource_id=key.id, type='apikey', request=request) if key.update(**request.json): return jsonify(status='ok') else: raise ApiError('failed to update API key', 500)
def pingfederate(): access_token_url = current_app.config['PINGFEDERATE_OPENID_ACCESS_TOKEN_URL'] payload = { 'client_id': request.json['clientId'], 'client_secret': current_app.config['OAUTH2_CLIENT_SECRET'], 'redirect_uri': request.json['redirectUri'], 'grant_type': 'authorization_code', 'code': request.json['code'], 'scope': 'openid email', } try: r = requests.post(access_token_url, data=payload) except Exception: raise ApiError('Failed to call sso API over HTTPS', 400) access_token = r.json() encoded = access_token['access_token'] keyfile = open(current_app.config['PINGFEDERATE_PUBKEY_LOCATION'], 'r') keystring = keyfile.read() decoded = jwt.decode(encoded, keystring, algorithms=current_app.config['PINGFEDERATE_TOKEN_ALGORITHM']) login = decoded[current_app.config['PINGFEDERATE_OPENID_PAYLOAD_USERNAME']] email = decoded[current_app.config['PINGFEDERATE_OPENID_PAYLOAD_EMAIL']] customers = get_customers(login, current_app.config['PINGFEDERATE_OPENID_PAYLOAD_GROUP']) auth_audit_trail.send(current_app._get_current_object(), event='pingfederate-login', message='user login via PingFederate', user=email, customers=customers, scopes=Permission.lookup(login, groups=[]), resource_id=login, type='pingfederate', request=request) token = create_token(user_id=login, name=email, login=email, provider='openid', customers=customers) return jsonify(token=token.tokenize)
def create_key(): try: key = ApiKey.parse(request.json) except ValueError as e: raise ApiError(str(e), 400) if 'admin' in g.scopes or 'admin:keys' in g.scopes: key.user = key.user or g.user key.customer = key.customer or g.get('customer', None) else: key.user = g.user key.customer = g.get('customer', None) if not key.user: raise ApiError("Must set 'user' to create API key", 400) for want_scope in key.scopes: if not Permission.is_in_scope(want_scope, g.scopes): raise ApiError("Requested scope '%s' not in existing scopes: %s" % (want_scope, ','.join(g.scopes)), 403) try: key = key.create() except Exception as e: raise ApiError(str(e), 500) if key: return jsonify(status="ok", key=key.key, data=key.serialize), 201 else: raise ApiError("create API key failed", 500)
def pingfederate(): access_token_url = current_app.config['PINGFEDERATE_OPENID_ACCESS_TOKEN_URL'] data = { 'client_id': request.json['clientId'], 'client_secret': current_app.config['OAUTH2_CLIENT_SECRET'], 'redirect_uri': request.json['redirectUri'], 'grant_type': 'authorization_code', 'code': request.json['code'], 'scope': 'openid email', } r = requests.post(access_token_url, data) access_token = r.json() encoded = access_token['access_token'] keyfile = open(current_app.config['PINGFEDERATE_PUBKEY_LOCATION'], 'r') keystring = keyfile.read() decoded = jwt.decode(encoded, keystring, algorithms=current_app.config['PINGFEDERATE_TOKEN_ALGORITHM']) login = decoded[current_app.config['PINGFEDERATE_OPENID_PAYLOAD_USERNAME']] email = decoded[current_app.config['PINGFEDERATE_OPENID_PAYLOAD_EMAIL']] scopes = Permission.lookup(login, roles=[]) customers = get_customers(login, current_app.config['PINGFEDERATE_OPENID_PAYLOAD_GROUP']) auth_audit_trail.send(current_app._get_current_object(), event='pingfederate-login', message='user login via PingFederate', user=email, customers=customers, scopes=scopes, resource_id=login, type='pingfederate', request=request) token = create_token(user_id=login, name=email, login=email, provider='openid', customers=customers, scopes=scopes) return jsonify(token=token.tokenize)
def github(): if current_app.config['GITHUB_URL']: access_token_url = current_app.config[ 'GITHUB_URL'] + '/login/oauth/access_token' github_api_url = current_app.config['GITHUB_URL'] + '/api/v3' else: access_token_url = 'https://github.com/login/oauth/access_token' github_api_url = 'https://api.github.com' client_lookup = dict( zip(current_app.config['OAUTH2_CLIENT_ID'].split(','), current_app.config['OAUTH2_CLIENT_SECRET'].split(','))) client_secret = client_lookup.get(request.json['clientId'], None) params = { 'client_id': request.json['clientId'], 'redirect_uri': request.json['redirectUri'], 'client_secret': client_secret, 'code': request.json['code'] } headers = {'Accept': 'application/json'} r = requests.get(access_token_url, headers=headers, params=params) access_token = r.json() r = requests.get(github_api_url + '/user', params=access_token) profile = r.json() r = requests.get( github_api_url + '/user/orgs', params=access_token) # list public and private Github orgs organizations = [o['login'] for o in r.json()] login = profile['login'] if not_authorized('ALLOWED_GITHUB_ORGS', organizations): raise ApiError('User %s is not authorized' % login, 403) customers = get_customers(login, organizations) auth_audit_trail.send(current_app._get_current_object(), event='github-login', message='user login via GitHub', user=login, customers=customers, scopes=Permission.lookup(login, groups=organizations), resource_id=profile['id'], type='github', request=request) token = create_token(user_id=profile['id'], name=profile.get('name', '@' + login), login=login, provider='github', customers=customers, orgs=organizations, email=profile.get('email', None), email_verified=True if 'email' in profile else False) return jsonify(token=token.tokenize)
def gitlab(): access_token_url = current_app.config['GITLAB_URL'] + '/oauth/token' tokeninfo_url = current_app.config['GITLAB_URL'] + '/oauth/token/info' userinfo_url = current_app.config['GITLAB_URL'] + '/oauth/userinfo' gitlab_api_url = current_app.config['GITLAB_URL'] + '/api/v4' payload = { 'client_id': request.json['clientId'], 'client_secret': current_app.config['OAUTH2_CLIENT_SECRET'], 'redirect_uri': request.json['redirectUri'], 'grant_type': 'authorization_code', 'code': request.json['code'], } try: r = requests.post(access_token_url, data=payload) except Exception: raise ApiError('Failed to call GitLab API over HTTPS', 400) token = r.json() headers = {'Authorization': 'Bearer ' + token['access_token']} r = requests.get(tokeninfo_url, headers=headers) scopes = r.json().get('scopes', []) current_app.logger.info('GitLab scopes: {}'.format(scopes)) if 'openid' in scopes: r = requests.post(userinfo_url, headers=headers) profile = r.json() user_id = profile['sub'] login = profile['nickname'] groups = profile.get('groups', []) email_verified = profile.get('email_verified', False) else: r = requests.get(gitlab_api_url + '/user', headers=headers) profile = r.json() user_id = profile['id'] login = profile['username'] r = requests.get(gitlab_api_url + '/groups', headers=headers) groups = [g['full_path'] for g in r.json()] email_verified = True if profile.get('email', None) else False if not_authorized('ALLOWED_GITLAB_GROUPS', groups): raise ApiError('User %s is not authorized' % login, 403) customers = get_customers(login, groups) auth_audit_trail.send(current_app._get_current_object(), event='gitlab-login', message='user login via GitLab', user=login, customers=customers, scopes=Permission.lookup(login, groups=groups), resource_id=user_id, type='gitlab', request=request) token = create_token(user_id=user_id, name=profile.get('name', '@' + login), login=login, provider='gitlab', customers=customers, groups=groups, email=profile.get('email', None), email_verified=email_verified) return jsonify(token=token.tokenize)
def delete_perm(perm_id): perm = Permission.find_by_id(perm_id) if not perm: raise ApiError("not found", 404) if perm.delete(): return jsonify(status="ok") else: raise ApiError("failed to delete permission", 500)
def create_perm(): try: perm = Permission.parse(request.json) except ValueError as e: raise ApiError(str(e), 400) for want_scope in perm.scopes: if not Permission.is_in_scope(want_scope, g.scopes): raise ApiError("Requested scope '%s' not in existing scopes: %s" % (want_scope, ','.join(g.scopes)), 403) try: perm = perm.create() except Exception as e: raise ApiError(str(e), 500) if perm: return jsonify(status="ok", id=perm.id, permission=perm.serialize), 201 else: raise ApiError("create API key failed", 500)
def delete_perm(perm_id): perm = Permission.find_by_id(perm_id) if not perm: raise ApiError('not found', 404) if perm.delete(): return jsonify(status='ok') else: raise ApiError('failed to delete permission', 500)
def google(): access_token_url = 'https://accounts.google.com/o/oauth2/token' people_api_url = 'https://www.googleapis.com/plus/v1/people/me/openIdConnect' payload = { 'client_id': request.json['clientId'], 'client_secret': current_app.config['OAUTH2_CLIENT_SECRET'], 'redirect_uri': request.json['redirectUri'], 'grant_type': 'authorization_code', 'code': request.json['code'], } r = requests.post(access_token_url, data=payload) token = r.json() id_token = Jwt.parse(token['id_token'], key='', verify=False, algorithm='RS256') domain = id_token.email.split('@')[1] if not_authorized('ALLOWED_EMAIL_DOMAINS', groups=[domain]): raise ApiError('User %s is not authorized' % id_token.email, 403) # Get Google+ profile for Full name headers = {'Authorization': 'Bearer ' + token['access_token']} r = requests.get(people_api_url, headers=headers) profile = r.json() if not profile: raise ApiError('Google+ API is not enabled for this Client ID', 400) customers = get_customers(id_token.email, groups=[domain]) name = profile.get('name', id_token.email.split('@')[0]) auth_audit_trail.send(current_app._get_current_object(), event='google-login', message='user login via Google', user=id_token.email, customers=customers, scopes=Permission.lookup(id_token.email, groups=[domain]), resource_id=id_token.subject, type='google', request=request) token = create_token(user_id=id_token.subject, name=name, login=id_token.email, provider='google', customers=customers, orgs=[domain], email=id_token.email, email_verified=id_token.email_verified) return jsonify(token=token.tokenize)
def login(): # lookup user from username/email try: username = request.json.get('username', None) or request.json['email'] password = request.json['password'] except KeyError: raise ApiError("must supply 'username' and 'password'", 401) user = User.check_credentials(username, password) if not user: raise ApiError('Invalid username or password', 401) # if email verification is enforced, deny login and send email if current_app.config['EMAIL_VERIFICATION'] and not user.email_verified: user.send_confirmation() raise ApiError('email not verified', 403) # check allowed domain if not_authorized('ALLOWED_EMAIL_DOMAINS', groups=[user.domain]): raise ApiError('unauthorized domain', 403) # check user is active & update last login if user.status != 'active': raise ApiError('User {} not active'.format(user.login), 403) user.update_last_login() groups = [g.name for g in user.get_groups()] scopes = Permission.lookup(login=user.login, roles=user.roles + groups) customers = get_customers(login=user.login, groups=[user.domain] + groups) auth_audit_trail.send(current_app._get_current_object(), event='basic-auth-login', message='user login via BasicAuth', user=user.login, customers=customers, scopes=scopes, roles=user.roles, groups=groups, resource_id=user.id, type='user', request=request) # generate token token = create_token(user_id=user.id, name=user.name, login=user.login, provider='basic', customers=customers, scopes=scopes, roles=user.roles, groups=groups, email=user.email, email_verified=user.email_verified) return jsonify(token=token.tokenize)
def list_perms(): perms = Permission.find_all() if perms: return jsonify(status='ok', permissions=[perm.serialize for perm in perms], total=len(perms)) else: return jsonify(status='ok', message='not found', permissions=[], total=0)
def saml_response_from_idp(): def _make_response(resp_obj, resp_code): if 'usePostMessage' in request.form.get('RelayState', '') and 'text/html' in request.headers.get('Accept', ''): origins = current_app.config.get('CORS_ORIGINS', []) response = make_response( '''<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Authenticating...</title> <script type="application/javascript"> var origins = {origins}; // in case when API and WebUI are on the same origin if (origins.indexOf(window.location.origin) < 0) origins.push(window.location.origin); // only one will succeed origins.forEach(origin => window.opener.postMessage({msg_data}, origin)); window.close(); </script> </head> <body></body> </html>'''.format(msg_data=json.dumps(resp_obj), origins=json.dumps(origins)), resp_code ) response.headers['Content-Type'] = 'text/html' return response else: return jsonify(**resp_obj), resp_code authn_response = saml_client().parse_authn_request_response( request.form['SAMLResponse'], saml2.entity.BINDING_HTTP_POST ) identity = authn_response.get_identity() email = identity['emailAddress'][0] domain = email.split('@')[1] name = (current_app.config.get('SAML2_USER_NAME_FORMAT', '{givenName} {surname}')).format( **dict(map(lambda x: (x[0], x[1][0]), identity.items()))) groups = identity.get('groups', []) if not_authorized('ALLOWED_SAML2_GROUPS', groups): return _make_response({'status': 'error', 'message': 'User {} is not authorized'.format(email)}, 403) scopes = Permission.lookup(login=email, roles=groups) customers = get_customers(login=email, groups=[domain] + groups) auth_audit_trail.send(current_app._get_current_object(), event='saml2-login', message='user login via SAML2', user=email, customers=customers, scopes=scopes, resource_id=email, type='saml2', request=request) token = create_token(user_id=email, name=name, login=email, provider='saml2', customers=customers, scopes=scopes, groups=groups) return _make_response({'status': 'ok', 'token': token.tokenize}, 200)
def azure(): if not current_app.config['AZURE_TENANT']: raise ApiError( 'Must define AZURE_TENANT setting in server configuration.', 503) discovery_doc_url = 'https://login.microsoftonline.com/{}/.well-known/openid-configuration'.format( current_app.config['AZURE_TENANT']) r = requests.get(discovery_doc_url) token_endpoint = r.json()['token_endpoint'] data = { 'code': request.json['code'], 'client_id': request.json['clientId'], 'client_secret': current_app.config['OAUTH2_CLIENT_SECRET'], 'redirect_uri': request.json['redirectUri'], 'grant_type': 'authorization_code' } r = requests.post(token_endpoint, data) token = r.json() id_token = jwt.decode(token['id_token'], verify=False) subject = id_token['sub'] name = id_token['name'] email = id_token['email'] domain = email.split('@')[1] if not_authorized('ALLOWED_EMAIL_DOMAINS', groups=[domain]): raise ApiError('User %s is not authorized' % email, 403) customers = get_customers(email, groups=[domain]) auth_audit_trail.send(current_app._get_current_object(), event='azure-login', message='user login via Azure', user=email, customers=customers, scopes=Permission.lookup(email, groups=[domain]), resource_id=subject, type='azure', request=request) token = create_token(user_id=subject, name=name, login=email, provider='azure', customers=customers, orgs=[domain], email=email) return jsonify(token=token.tokenize)
def delete_perm(perm_id): perm = Permission.find_by_id(perm_id) if not perm: raise ApiError('not found', 404) admin_audit_trail.send(current_app._get_current_object(), event='permission-deleted', message='', user=g.user, customers=g.customers, scopes=g.scopes, resource_id=perm.id, type='permission', request=request) if perm.delete(): return jsonify(status='ok') else: raise ApiError('failed to delete permission', 500)
def create_user(): if current_app.config['AUTH_PROVIDER'] != 'basic': raise ApiError( 'must use {} login flow to create new user'.format(current_app.config['AUTH_PROVIDER']), 400) try: user = User.parse(request.json) except Exception as e: raise ApiError(str(e), 400) # check allowed domain if not_authorized('ALLOWED_EMAIL_DOMAINS', groups=[user.domain]): raise ApiError('unauthorized domain', 403) if User.find_by_username(username=user.email): raise ApiError('user with that email already exists', 409) want_scopes = Permission.lookup(login=user.email, roles=user.roles) for want_scope in want_scopes: if not Permission.is_in_scope(want_scope, have_scopes=g.scopes): raise ApiError("Requested scope '{}' not in existing scopes: {}".format( want_scope, ','.join(g.scopes)), 403) try: user = user.create() except Exception as e: ApiError(str(e), 500) # if email verification is enforced, send confirmation email if current_app.config['EMAIL_VERIFICATION'] and not user.email_verified: user.send_confirmation() admin_audit_trail.send(current_app._get_current_object(), event='user-created', message='', user=g.login, customers=g.customers, scopes=g.scopes, resource_id=user.id, type='user', request=request) if user: return jsonify(status='ok', id=user.id, user=user.serialize), 201 else: raise ApiError('create user failed', 500)
def delete_perm(perm_id): perm = Permission.find_by_id(perm_id) if not perm: raise ApiError('not found', 404) admin_audit_trail.send(current_app._get_current_object(), event='permission-deleted', message='', user=g.login, customers=g.customers, scopes=g.scopes, resource_id=perm.id, type='permission', request=request) if perm.delete(): return jsonify(status='ok') else: raise ApiError('failed to delete permission', 500)
def saml_response_from_idp(): def _make_response(resp_obj, resp_code): if 'usePostMessage' in request.form.get('RelayState', '') and 'text/html' in request.headers.get('Accept', ''): origins = current_app.config.get('CORS_ORIGINS', []) response = make_response( '''<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Authenticating...</title> <script type="application/javascript"> var origins = {origins}; // in case when API and WebUI are on the same origin if (origins.indexOf(window.location.origin) < 0) origins.push(window.location.origin); // only one will succeed origins.forEach(origin => window.opener.postMessage({msg_data}, origin)); window.close(); </script> </head> <body></body> </html>'''.format(msg_data=json.dumps(resp_obj), origins=json.dumps(origins)), resp_code ) response.headers['Content-Type'] = 'text/html' return response else: return jsonify(**resp_obj), resp_code authn_response = saml_client().parse_authn_request_response( request.form['SAMLResponse'], saml2.entity.BINDING_HTTP_POST ) identity = authn_response.get_identity() email = identity['emailAddress'][0] domain = email.split('@')[1] name = (current_app.config.get('SAML2_USER_NAME_FORMAT', '{givenName} {surname}')).format( **dict(map(lambda x: (x[0], x[1][0]), identity.items()))) groups = identity.get('groups', []) if not_authorized('ALLOWED_SAML2_GROUPS', groups): return _make_response({'status': 'error', 'message': 'User {} is not authorized'.format(email)}, 403) customers = get_customers(email, groups=[domain]) auth_audit_trail.send(current_app._get_current_object(), event='saml2-login', message='user login via SAML2', user=email, customers=customers, scopes=Permission.lookup(email, groups=groups), resource_id=email, type='saml2', request=request) token = create_token(user_id=email, name=name, login=email, provider='saml2', customers=customers, groups=groups) return _make_response({'status': 'ok', 'token': token.tokenize}, 200)
def signup(): if not current_app.config['SIGNUP_ENABLED']: raise ApiError('user signup is disabled', 403) try: user = User.parse(request.json) except Exception as e: raise ApiError(str(e), 400) # set sign-up defaults user.roles = ['user'] user.email_verified = False # check allowed domain if not_authorized('ALLOWED_EMAIL_DOMAINS', groups=[user.domain]): raise ApiError('unauthorized domain', 403) if User.find_by_username(username=user.email): raise ApiError('user with that email already exists', 409) try: user = user.create() except Exception as e: ApiError(str(e), 500) # if email verification is enforced, deny login and send email if current_app.config['EMAIL_VERIFICATION'] and not user.email_verified: user.send_confirmation() raise ApiError('email not verified', 403) # check user is active & update last login if user.status != 'active': raise ApiError('User {} not active'.format(user.login), 403) user.update_last_login() groups = [g.name for g in user.get_groups()] scopes = Permission.lookup(login=user.login, roles=user.roles + groups) customers = get_customers(login=user.login, groups=[user.domain] + groups) auth_audit_trail.send(current_app._get_current_object(), event='basic-auth-signup', message='user signup using BasicAuth', user=user.login, customers=customers, scopes=scopes, resource_id=user.id, type='user', request=request) # generate token token = create_token(user_id=user.id, name=user.name, login=user.login, provider='basic', customers=customers, scopes=scopes, roles=user.roles, groups=groups, email=user.email, email_verified=user.email_verified) return jsonify(token=token.tokenize)
def list_perms(): perms = Permission.find_all() if perms: return jsonify( status="ok", permissions=[perm.serialize for perm in perms], total=len(perms) ) else: return jsonify( status="ok", message="not found", permissions=[], total=0 )
def github(): if current_app.config['GITHUB_URL'] == 'https://github.com': access_token_url = 'https://github.com/login/oauth/access_token' github_api_url = 'https://api.github.com' else: access_token_url = current_app.config['GITHUB_URL'] + '/login/oauth/access_token' github_api_url = current_app.config['GITHUB_URL'] + '/api/v3' client_lookup = dict(zip( current_app.config['OAUTH2_CLIENT_ID'].split(','), current_app.config['OAUTH2_CLIENT_SECRET'].split(',') )) client_secret = client_lookup.get(request.json['clientId'], None) params = { 'client_id': request.json['clientId'], 'redirect_uri': request.json['redirectUri'], 'client_secret': client_secret, 'code': request.json['code'] } headers = {'Accept': 'application/json'} r = requests.get(access_token_url, headers=headers, params=params) access_token = r.json() r = requests.get(github_api_url + '/user', params=access_token) profile = r.json() r = requests.get(github_api_url + '/user/orgs', params=access_token) # list public and private Github orgs organizations = [o['login'] for o in r.json()] login = profile['login'] if not_authorized('ALLOWED_GITHUB_ORGS', organizations): raise ApiError('User %s is not authorized' % login, 403) scopes = Permission.lookup(login, roles=organizations) customers = get_customers(login, groups=organizations) auth_audit_trail.send(current_app._get_current_object(), event='github-login', message='user login via GitHub', user=login, customers=customers, scopes=scopes, resource_id=profile['id'], type='github', request=request) token = create_token(user_id=profile['id'], name=profile.get('name', '@' + login), login=login, provider='github', customers=customers, scopes=scopes, orgs=organizations, email=profile.get('email', None), email_verified=True if 'email' in profile else False) return jsonify(token=token.tokenize)
def saml_response_from_idp(): origin = request.form['RelayState'] authn_response = saml_client().parse_authn_request_response( xmlstr=request.form['SAMLResponse'], binding=saml2.entity.BINDING_HTTP_POST ) identity = authn_response.get_identity() subject = authn_response.get_subject() name = current_app.config['SAML2_USER_NAME_FORMAT'].format(**dict(map(lambda x: (x[0], x[1][0]), identity.items()))) login = subject.text email = identity[current_app.config['SAML2_EMAIL_ATTRIBUTE']][0] # Create user if not yet there user = User.find_by_username(username=email) if not user: user = User(name=name, login=login, password='', email=email, roles=[], text='SAML2 user', email_verified=True) try: user = user.create() except Exception as e: ApiError(str(e), 500) if user.status != 'active': raise ApiError('User {} is not active'.format(email), 403) groups = identity.get('groups', []) if not_authorized('ALLOWED_SAML2_GROUPS', groups) or not_authorized('ALLOWED_EMAIL_DOMAINS', groups=[user.domain]): message = {'status': 'error', 'message': 'User {} is not authorized'.format(email)} return render_template('auth/saml2.html', message=message, origin=origin), 403 user.update_last_login() scopes = Permission.lookup(login=user.email, roles=user.roles + groups) customers = get_customers(login=user.email, groups=[user.domain] + groups) auth_audit_trail.send(current_app._get_current_object(), event='saml2-login', message='user login via SAML2', user=user.email, customers=customers, scopes=scopes, resource_id=user.id, type='user', request=request) token = create_token(user_id=user.id, name=user.name, login=user.email, provider='saml2', customers=customers, scopes=scopes, roles=user.roles, email=user.email, email_verified=user.email_verified) message = {'status': 'ok', 'token': token.tokenize} return render_template('auth/saml2.html', message=message, origin=origin), 200
def create_key(): try: key = ApiKey.parse(request.json) except ValueError as e: raise ApiError(str(e), 400) if Scope.admin in g.scopes or Scope.admin_keys in g.scopes: key.user = key.user or g.login else: key.user = g.login key.customer = assign_customer(wanted=key.customer, permission=Scope.admin_keys) if not key.user: raise ApiError( "An API key must be associated with a 'user'. Retry with user credentials.", 400) for want_scope in key.scopes: if not Permission.is_in_scope(want_scope, have_scopes=g.scopes): raise ApiError( "Requested scope '{}' not in existing scopes: {}".format( want_scope, ','.join(g.scopes)), 403) try: key = key.create() except Exception as e: raise ApiError(str(e), 500) write_audit_trail.send(current_app._get_current_object(), event='apikey-created', message='', user=g.login, customers=g.customers, scopes=g.scopes, resource_id=key.id, type='apikey', request=request) if key: return jsonify(status='ok', key=key.key, data=key.serialize), 201 else: raise ApiError('create API key failed', 500)
def keycloak(): if not current_app.config['KEYCLOAK_URL']: raise ApiError('Must define KEYCLOAK_URL setting in server configuration.', 503) access_token_url = '{}/auth/realms/{}/protocol/openid-connect/token'.format( current_app.config['KEYCLOAK_URL'], current_app.config['KEYCLOAK_REALM']) payload = { 'client_id': request.json['clientId'], 'client_secret': current_app.config['OAUTH2_CLIENT_SECRET'], 'redirect_uri': request.json['redirectUri'], 'grant_type': 'authorization_code', 'code': request.json['code'], } try: r = requests.post(access_token_url, data=payload) except Exception: raise ApiError('Failed to call Keycloak API over HTTPS', 400) access_token = r.json() headers = {'Authorization': '{} {}'.format(access_token['token_type'], access_token['access_token'])} r = requests.get('{}/auth/realms/{}/protocol/openid-connect/userinfo'.format( current_app.config['KEYCLOAK_URL'], current_app.config['KEYCLOAK_REALM']), headers=headers) profile = r.json() roles = profile.get('roles', ['user']) login = profile['preferred_username'] if not_authorized('ALLOWED_KEYCLOAK_ROLES', roles): raise ApiError('User %s is not authorized' % login, 403) customers = get_customers(login, groups=roles) auth_audit_trail.send(current_app._get_current_object(), event='keycloak-login', message='user login via Keycloak', user=login, customers=customers, scopes=Permission.lookup(login, groups=roles), resource_id=profile['sub'], type='keycloak', request=request) token = create_token(user_id=profile['sub'], name=profile['name'], login=login, provider='keycloak', customers=customers, roles=roles) return jsonify(token=token.tokenize)
def create_token(user_id, name, login, provider, customer, orgs=None, groups=None, roles=None, email=None, email_verified=None): now = datetime.utcnow() scopes = Permission.lookup(login, groups=(roles or []) + (groups or []) + (orgs or [])) return Jwt( iss=request.url_root, sub=user_id, aud=current_app.config.get('OAUTH2_CLIENT_ID', None) or request.url_root, exp=(now + timedelta(days=current_app.config['TOKEN_EXPIRE_DAYS'])), nbf=now, iat=now, jti=str(uuid4()), name=name, preferred_username=login, orgs=orgs, roles=roles, groups=groups, provider=provider, scopes=scopes, email=email, email_verified=email_verified, customer=customer )
def login(): # lookup user from username/email try: username = request.json.get('username', None) or request.json['email'] password = request.json['password'] except KeyError: raise ApiError("must supply 'username' and 'password'", 401) user = User.check_credentials(username, password) if not user: raise ApiError('Invalid username or password', 401) # if email verification is enforced, deny login and send email if current_app.config['EMAIL_VERIFICATION'] and not user.email_verified: user.send_confirmation() raise ApiError('email not verified', 403) # check allowed domain if not_authorized('ALLOWED_EMAIL_DOMAINS', groups=[user.domain]): raise ApiError('unauthorized domain', 403) # check user is active & update last login if user.status != 'active': raise ApiError('User {} not active'.format(user.login), 403) user.update_last_login() groups = [g.name for g in user.get_groups()] scopes = Permission.lookup(login=user.login, roles=user.roles + groups) customers = get_customers(login=user.login, groups=[user.domain] + groups) auth_audit_trail.send(current_app._get_current_object(), event='basic-auth-login', message='user login via BasicAuth', user=user.login, customers=customers, scopes=scopes, resource_id=user.id, type='user', request=request) # generate token token = create_token(user_id=user.id, name=user.name, login=user.login, provider='basic', customers=customers, scopes=scopes, roles=user.roles, groups=groups, email=user.email, email_verified=user.email_verified) return jsonify(token=token.tokenize)
def update_key(key): if not request.json: raise ApiError('nothing to change', 400) if not current_app.config['AUTH_REQUIRED']: key = ApiKey.find_by_id(key) elif Scope.admin in g.scopes or Scope.admin_keys in g.scopes: key = ApiKey.find_by_id(key) else: key = ApiKey.find_by_id(key, user=g.login) if not key: raise ApiError('not found', 404) update = request.json update['customer'] = assign_customer(wanted=update.get('customer'), permission=Scope.admin_keys) for want_scope in update.get('scopes', []): if not Permission.is_in_scope(want_scope, have_scopes=g.scopes): raise ApiError( "Requested scope '{}' not in existing scopes: {}".format( want_scope, ','.join(g.scopes)), 403) admin_audit_trail.send(current_app._get_current_object(), event='apikey-updated', message='', user=g.login, customers=g.customers, scopes=g.scopes, resource_id=key.id, type='apikey', request=request) updated = key.update(**request.json) if updated: return jsonify(status='ok', key=updated.serialize) else: raise ApiError('failed to update API key', 500)
def list_perms(): query = qb.from_params(request.args) perms = Permission.find_all(query) admin_perm = Permission( match='admin', scopes=[Scope.admin] ) user_perm = Permission( match='user', scopes=current_app.config['USER_DEFAULT_SCOPES'] ) # add system-defined roles 'admin' and 'user' if 'scopes' in request.args: want_scopes = request.args.getlist('scopes') if set(admin_perm.scopes) & set(want_scopes): perms.append(admin_perm) if set(user_perm.scopes) & set(want_scopes): perms.append(user_perm) else: perms.append(admin_perm) perms.append(user_perm) if perms: return jsonify( status='ok', permissions=[perm.serialize for perm in perms], total=len(perms) ) else: return jsonify( status='ok', message='not found', permissions=[], total=0 )
def login(): # Retrieve required fields from client request try: email = request.json.get('username', None) or request.json['email'] password = request.json['password'] except KeyError: raise ApiError("must supply 'username' and 'password'", 401) username, domain = email.split('@') # Validate LDAP domain if domain not in current_app.config['LDAP_DOMAINS']: raise ApiError('unauthorized domain', 403) userdn = current_app.config['LDAP_DOMAINS'][domain] % username # Attempt LDAP AUTH try: trace_level = 2 if current_app.debug else 0 ldap_connection = ldap.initialize(current_app.config['LDAP_URL'], trace_level=trace_level) ldap_connection.simple_bind_s(userdn, password) except ldap.INVALID_CREDENTIALS: raise ApiError('invalid username or password', 401) except Exception as e: raise ApiError(str(e), 500) # Create user if not yet there user = User.find_by_username(username=email) if not user: user = User(name=username, login=email, password='', email=email, roles=[], text='LDAP user', email_verified=True) try: user = user.create() except Exception as e: ApiError(str(e), 500) # Assign customers & update last login time groups = list() try: groups_filters = current_app.config.get('LDAP_DOMAINS_GROUP', {}) base_dns = current_app.config.get('LDAP_DOMAINS_BASEDN', {}) if domain in groups_filters and domain in base_dns: resultID = ldap_connection.search( base_dns[domain], ldap.SCOPE_SUBTREE, groups_filters[domain].format(username=username, email=email, userdn=userdn), ['cn'] ) resultTypes, results = ldap_connection.result(resultID) for _dn, attributes in results: groups.append(attributes['cn'][0].decode('utf-8')) except ldap.LDAPError as e: raise ApiError(str(e), 500) # Check user is active if user.status != 'active': raise ApiError('User {} not active'.format(email), 403) user.update_last_login() scopes = Permission.lookup(login=user.email, roles=user.roles) customers = get_customers(login=user.email, groups=[user.domain] + groups) auth_audit_trail.send(current_app._get_current_object(), event='basic-ldap-login', message='user login via LDAP', user=user.email, customers=customers, scopes=scopes, resource_id=user.id, type='user', request=request) # Generate token token = create_token(user_id=user.id, name=user.name, login=user.email, provider='ldap', customers=customers, scopes=scopes, roles=user.roles, email=user.email, email_verified=user.email_verified) return jsonify(token=token.tokenize)
def openid(): oidc_configuration, jwt_key_set = get_oidc_configuration(current_app) token_endpoint = oidc_configuration['token_endpoint'] userinfo_endpoint = oidc_configuration['userinfo_endpoint'] data = { 'grant_type': 'authorization_code', 'code': request.json['code'], 'redirect_uri': request.json['redirectUri'], 'client_id': request.json['clientId'], 'client_secret': current_app.config['OAUTH2_CLIENT_SECRET'], } r = requests.post(token_endpoint, data) token = r.json() try: if current_app.config['OIDC_VERIFY_TOKEN']: jwt_header = jwt.get_unverified_header(token['id_token']) public_key = jwt_key_set[jwt_header['kid']] id_token = jwt.decode( token['id_token'], key=public_key, algorithms=jwt_header['alg'] ) else: id_token = jwt.decode( token['id_token'], verify=False ) except Exception: current_app.logger.warning('No ID token in OpenID Connect token response.') id_token = {} try: headers = {'Authorization': '{} {}'.format(token.get('token_type', 'Bearer'), token['access_token'])} r = requests.get(userinfo_endpoint, headers=headers) userinfo = r.json() except Exception: raise ApiError('No access token in OpenID Connect token response.') subject = userinfo['sub'] name = userinfo.get('name') or id_token.get('name') nickname = userinfo.get('nickname') email = userinfo.get('email') or id_token.get('email') email_verified = userinfo.get('email_verified', id_token.get('email_verified', bool(email))) picture = userinfo.get('picture') or id_token.get('picture') role_claim = current_app.config['OIDC_ROLE_CLAIM'] group_claim = current_app.config['OIDC_GROUP_CLAIM'] custom_claims = { role_claim: userinfo.get(role_claim) or id_token.get(role_claim, []), group_claim: userinfo.get(group_claim) or id_token.get(group_claim, []), } login = userinfo.get('preferred_username', nickname or email) if not login: raise ApiError("Must support one of the following OpenID claims: 'preferred_username', 'nickname' or 'email'", 400) user = User.find_by_id(id=subject) if not user: user = User(id=subject, name=name, login=login, password='', email=email, roles=[], text='', email_verified=email_verified) user.create() else: user.update(login=login, email=email) roles = custom_claims[role_claim] or user.roles groups = custom_claims[group_claim] if user.status != 'active': raise ApiError('User {} is not active'.format(login), 403) if not_authorized('ALLOWED_OIDC_ROLES', roles) and not_authorized('ALLOWED_EMAIL_DOMAINS', groups=[user.domain]): raise ApiError('User {} is not authorized'.format(login), 403) user.update_last_login() scopes = Permission.lookup(login, roles) customers = get_customers(login, groups=[user.domain] + groups) auth_audit_trail.send(current_app._get_current_object(), event='openid-login', message='user login via OpenID Connect', user=login, customers=customers, scopes=scopes, resource_id=subject, type='user', request=request) token = create_token(user_id=subject, name=name, login=login, provider='openid', customers=customers, scopes=scopes, email=email, email_verified=email_verified, picture=picture, **custom_claims) return jsonify(token=token.tokenize)
def wrapped(*args, **kwargs): # API Key (Authorization: Key <key>) if 'Authorization' in request.headers: auth_header = request.headers['Authorization'] m = re.match(r'Key (\S+)', auth_header) key = m.group(1) if m else None # API Key (X-API-Key: <key>) elif 'X-API-Key' in request.headers: key = request.headers['X-API-Key'] # API Key (/foo?api-key=<key>) else: key = request.args.get('api-key', None) if key: key_info = ApiKey.verify_key(key) if not key_info: raise ApiError("API key parameter '%s' is invalid" % key, 401) g.user_id = None g.login = key_info.user g.customers = [key_info.customer] if key_info.customer else [] g.scopes = key_info.scopes # type: List[Scope] if not Permission.is_in_scope(scope, have_scopes=g.scopes): raise ApiError('Missing required scope: %s' % scope.value, 403) else: return f(*args, **kwargs) # Bearer Token auth_header = request.headers.get('Authorization', '') m = re.match(r'Bearer (\S+)', auth_header) token = m.group(1) if m else None if token: try: jwt = Jwt.parse(token) except DecodeError: raise ApiError('Token is invalid', 401) except ExpiredSignature: raise ApiError('Token has expired', 401) except InvalidAudience: raise ApiError('Invalid audience', 401) g.user_id = jwt.subject g.login = jwt.preferred_username g.customers = jwt.customers g.scopes = jwt.scopes # type: List[Scope] if not Permission.is_in_scope(scope, have_scopes=g.scopes): raise ApiError('Missing required scope: %s' % scope.value, 403) else: return f(*args, **kwargs) # Basic Auth auth_header = request.headers.get('Authorization', '') m = re.match(r'Basic (\S+)', auth_header) credentials = m.group(1) if m else None if credentials: try: username, password = base64.b64decode(credentials).decode('utf-8').split(':') except Exception as e: raise BasicAuthError('Invalid credentials', 400, errors=[str(e)]) user = User.check_credentials(username, password) if not user: raise BasicAuthError('Authorization required', 401) if current_app.config['EMAIL_VERIFICATION'] and not user.email_verified: raise BasicAuthError('email not verified', 401) if not_authorized('ALLOWED_EMAIL_DOMAINS', groups=[user.domain]): raise BasicAuthError('Unauthorized domain', 403) g.user_id = user.id g.login = user.email g.customers = get_customers(user.email, groups=[user.domain]) g.scopes = Permission.lookup(user.email, roles=user.roles) # type: List[Scope] if not Permission.is_in_scope(scope, have_scopes=g.scopes): raise BasicAuthError('Missing required scope: %s' % scope.value, 403) else: return f(*args, **kwargs) if not current_app.config['AUTH_REQUIRED']: g.user_id = None g.login = None g.customers = [] g.scopes = [] # type: List[Scope] return f(*args, **kwargs) # Google App Engine Cron Service if request.headers.get('X-Appengine-Cron', False) and request.headers.get('X-Forwarded-For', '') == '0.1.0.1': return f(*args, **kwargs) raise ApiError('Missing authorization API Key or Bearer Token', 401)