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 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 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 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 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 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 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_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 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 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 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 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 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 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 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 = key_info.user g.customers = [key_info.customer] if key_info.customer else [] g.scopes = key_info.scopes if not Permission.is_in_scope(scope, g.scopes): raise ApiError('Missing required scope: %s' % scope, 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 = jwt.preferred_username g.customers = jwt.customers 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) # 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 = user.email g.customers = get_customers(user.email, groups=[user.domain]) g.scopes = Permission.lookup(user.email, groups=user.roles) if not Permission.is_in_scope(scope, g.scopes): raise BasicAuthError('Missing required scope: %s' % scope, 403) else: return f(*args, **kwargs) if not current_app.config['AUTH_REQUIRED']: g.user = None g.customers = [] 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 wrapped(*args, **kwargs): # API Key (Authorization: Key <key>) if 'Authorization' in request.headers and request.headers[ 'Authorization'].startswith('Key '): 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, 403) else: return f(*args, **kwargs) # Hawk HMAC Signature (Authorization: Hawk mac=...) if request.headers.get('Authorization', '').startswith('Hawk'): try: receiver = HmacAuth.authenticate(request) except mohawk.exc.HawkFail as e: raise ApiError(str(e), 401) g.user_id = None g.login = receiver.parsed_header.get('id') g.customers = [] g.scopes = ADMIN_SCOPES 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 ExpiredSignatureError: raise ApiError('Token has expired', 401) except InvalidAudienceError: raise ApiError('Invalid audience', 401) g.user_id = jwt.oid or 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, 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, 403) else: return f(*args, **kwargs) # auth not required 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) # auth required for admin/write, but readonly is allowed if current_app.config['AUTH_REQUIRED'] and current_app.config[ 'ALLOW_READONLY']: g.user_id = None g.login = None g.customers = [] g.scopes = current_app.config['READONLY_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 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)
def test_is_in_scope(self): self.assertEqual( Permission.is_in_scope(Scope.read_customers, [Scope.read]), True) self.assertEqual( Permission.is_in_scope(Scope.read_customers, [Scope.write]), True) self.assertEqual( Permission.is_in_scope(Scope.read_customers, [Scope.admin]), True) self.assertEqual( Permission.is_in_scope(Scope.read_heartbeats, [Scope.read_alerts]), False) self.assertEqual( Permission.is_in_scope(Scope.read_heartbeats, [Scope.write_alerts]), False) self.assertEqual( Permission.is_in_scope(Scope.read_heartbeats, [Scope.admin_alerts]), False) self.assertEqual( Permission.is_in_scope(Scope.write_blackouts, [Scope.read]), False) self.assertEqual( Permission.is_in_scope(Scope.write_blackouts, [Scope.read_blackouts, Scope.read]), False) self.assertEqual( Permission.is_in_scope( Scope.write_blackouts, [Scope.read_blackouts, Scope.write_blackouts]), True) self.assertEqual( Permission.is_in_scope(Scope.write_blackouts, [Scope.write_blackouts]), True) self.assertEqual( Permission.is_in_scope(Scope.write_blackouts, [Scope.read_blackouts, Scope.write]), True) self.assertEqual( Permission.is_in_scope(Scope.write_blackouts, [Scope.read_blackouts, Scope.admin]), True) self.assertEqual( Permission.is_in_scope(Scope.write_blackouts, [Scope.read, Scope.write_keys]), False) self.assertEqual( Permission.is_in_scope(Scope.write_blackouts, [Scope.read, Scope.admin_keys]), False) self.assertEqual(Permission.is_in_scope(Scope.admin, [Scope.write]), False) self.assertEqual( Permission.is_in_scope(Scope.admin, [Scope.read, Scope.write, Scope.admin]), True) self.assertEqual( Permission.is_in_scope(Scope.read_heartbeats, [Scope.write]), True)