def test_login(self): payload = { 'email': '*****@*****.**', 'password': '******' } response = self.client.post('/auth/login', data=json.dumps(payload), content_type='application/json') self.assertEqual(response.status_code, 200) data = json.loads(response.data.decode('utf-8')) self.assertIn('token', data) with self.app.test_request_context(): jwt = Jwt.parse(data['token']) self.assertEqual(jwt.issuer, 'http://localhost/') self.assertEqual(jwt.name, 'Bender Bending Rodríguez') self.assertEqual(jwt.preferred_username, '*****@*****.**') self.assertEqual(jwt.email, '*****@*****.**') self.assertEqual(jwt.provider, 'ldap') self.assertEqual(jwt.orgs, []) self.assertEqual(jwt.groups, ['ship_crew']) self.assertEqual(jwt.roles, ['user']) self.assertEqual(jwt.scopes, ['read', 'write']) self.assertEqual(jwt.email_verified, True) self.assertEqual(jwt.picture, None) self.assertEqual(jwt.customers, [])
def test_login_with_no_domain(self): payload = { 'username': '******', 'password': '******' } response = self.client.post('/auth/login', data=json.dumps(payload), content_type='application/json') self.assertEqual(response.status_code, 200) data = json.loads(response.data.decode('utf-8')) self.assertIn('token', data) with self.app.test_request_context(): jwt = Jwt.parse(data['token']) self.assertEqual(jwt.issuer, 'http://localhost/') self.assertEqual(jwt.name, 'Hubert J. Farnsworth') self.assertEqual(jwt.preferred_username, '*****@*****.**') self.assertEqual(jwt.email, '*****@*****.**') self.assertEqual(jwt.provider, 'ldap') self.assertEqual(jwt.orgs, []) self.assertEqual(jwt.groups, ['admin_staff']) self.assertEqual(jwt.roles, ['admin']) self.assertEqual(jwt.scopes, ['admin', 'read', 'write']) self.assertEqual(jwt.email_verified, True) self.assertEqual(jwt.picture, None) self.assertEqual(jwt.customers, [])
def test_login_with_ldap_domain(self): payload = {'username': '******', 'password': '******'} response = self.client.post('/auth/login', data=json.dumps(payload), content_type='application/json') self.assertEqual(response.status_code, 200) data = json.loads(response.data.decode('utf-8')) self.assertIn('token', data) with self.app.test_request_context(): jwt = Jwt.parse(data['token']) self.assertEqual(jwt.issuer, 'http://localhost/') self.assertEqual(jwt.name, 'Turanga Leela') self.assertEqual(jwt.preferred_username, '*****@*****.**') self.assertEqual(jwt.email, '*****@*****.**') self.assertEqual(jwt.provider, 'ldap') self.assertEqual(jwt.orgs, []) self.assertEqual(jwt.groups, ['cn=ship_crew,ou=people,dc=planetexpress,dc=com']) self.assertEqual(jwt.roles, ['user']) self.assertEqual(jwt.scopes, ['read', 'write']) self.assertEqual(jwt.email_verified, True) self.assertEqual(jwt.picture, None) self.assertEqual(jwt.customers, [])
def userinfo(): auth_header = request.headers.get('Authorization', '') m = re.match(r'Bearer (\S+)', auth_header) token = m.group(1) if m else None if token: return jsonify(Jwt.parse(token).serialize) else: raise ApiError('Missing authorization Bearer token', 401)
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 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_users(self): # add customer mapping payload = {'customer': 'Bonaparte Industries', 'match': 'bonaparte.fr'} response = self.client.post('/customer', data=json.dumps(payload), content_type='application/json', headers=self.headers) self.assertEqual(response.status_code, 201) payload = { 'name': 'Napoleon Bonaparte', 'email': '*****@*****.**', 'password': '******', 'text': 'added to circle of trust' } # create user response = self.client.post('/auth/signup', data=json.dumps(payload), content_type='application/json', headers=self.headers) self.assertEqual(response.status_code, 200) data = json.loads(response.data.decode('utf-8')) self.assertIsNotNone(data, 'Failed to create user') with self.app.test_request_context(): jwt = Jwt.parse(data['token']) user_id = jwt.subject # get user response = self.client.get('/users', headers=self.headers) self.assertEqual(response.status_code, 200) data = json.loads(response.data.decode('utf-8')) self.assertIn(user_id, [u['id'] for u in data['users']]) # create duplicate user response = self.client.post('/auth/signup', data=json.dumps(payload), content_type='application/json', headers=self.headers) self.assertEqual(response.status_code, 409) # delete user response = self.client.delete('/user/' + user_id, headers=self.headers) self.assertEqual(response.status_code, 200)
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 is_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) customer = get_customer(id_token.email, groups=[domain]) token = create_token(id_token.subject, profile['name'], id_token.email, provider='google', customer=customer, orgs=[domain], email=id_token.email, email_verified=id_token.email_verified) return jsonify(token=token.tokenize)
def test_users(self): # add customer mapping payload = { 'customer': 'Bonaparte Industries', 'match': 'bonaparte.fr' } response = self.client.post('/customer', data=json.dumps(payload), content_type='application/json', headers=self.headers) self.assertEqual(response.status_code, 201) payload = { 'name': 'Napoleon Bonaparte', 'email': '*****@*****.**', 'password': '******', 'text': 'added to circle of trust' } # create user response = self.client.post('/auth/signup', data=json.dumps(payload), content_type='application/json', headers=self.headers) self.assertEqual(response.status_code, 200) data = json.loads(response.data.decode('utf-8')) self.assertIsNotNone(data, 'Failed to create user') with self.app.test_request_context(): jwt = Jwt.parse(data['token']) user_id = jwt.subject # get user response = self.client.get('/users', headers=self.headers) self.assertEqual(response.status_code, 200) data = json.loads(response.data.decode('utf-8')) self.assertIn(user_id, [u['id'] for u in data['users']]) # create duplicate user response = self.client.post('/auth/signup', data=json.dumps(payload), content_type='application/json', headers=self.headers) self.assertEqual(response.status_code, 409) # delete user response = self.client.delete('/user/' + user_id, headers=self.headers) self.assertEqual(response.status_code, 200)
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 test_edit_user(self): # add customer mapping payload = { 'customer': 'Manor Farm', 'match': 'manorfarm.ru' } response = self.client.post('/customer', data=json.dumps(payload), content_type='application/json', headers=self.headers) self.assertEqual(response.status_code, 201) payload = { 'name': 'Snowball', 'email': '*****@*****.**', 'password': '******', 'text': 'Can you not understand that liberty is worth more than ribbons?', 'attributes': {'two-legs': 'bad', 'hasFourLegs': True, 'isEvil': False} } # create user response = self.client.post('/auth/signup', data=json.dumps(payload), content_type='application/json') self.assertEqual(response.status_code, 200) data = json.loads(response.data.decode('utf-8')) with self.app.test_request_context(): jwt = Jwt.parse(data['token']) user_id = jwt.subject # get user response = self.client.get('/user/' + user_id, headers=self.headers) self.assertEqual(response.status_code, 200) data = json.loads(response.data.decode('utf-8')) self.assertEqual(data['status'], 'ok') self.assertEqual(data['user']['name'], 'Snowball') self.assertEqual(data['user']['email'], '*****@*****.**') self.assertEqual(data['user']['text'], 'Can you not understand that liberty is worth more than ribbons?') # FIXME: attribute keys with None (null) values aren't deleted in postgres # change user details update = { 'name': 'Squealer', 'text': 'Four legs good, two legs bad.', 'attributes': {'four-legs': 'good', 'isEvil': True} } response = self.client.put('/user/' + user_id, data=json.dumps(update), headers=self.headers) self.assertEqual(response.status_code, 200) data = json.loads(response.data.decode('utf-8')) self.assertEqual(data['status'], 'ok') # check updates worked and didn't change anything else response = self.client.get('/user/' + user_id, headers=self.headers) self.assertEqual(response.status_code, 200) data = json.loads(response.data.decode('utf-8')) self.assertEqual(data['user']['name'], 'Squealer') self.assertEqual(data['user']['email'], '*****@*****.**') self.assertEqual(data['user']['text'], 'Four legs good, two legs bad.') self.assertEqual(data['user']['attributes'], { 'four-legs': 'good', 'two-legs': 'bad', 'hasFourLegs': True, 'isEvil': True }) # just update attributes update = { 'attributes': {'four-legs': 'double good', 'isEvil': False, 'hasFourLegs': None} } response = self.client.put('/user/' + user_id + '/attributes', data=json.dumps(update), headers=self.headers) self.assertEqual(response.status_code, 200) data = json.loads(response.data.decode('utf-8')) self.assertEqual(data['status'], 'ok') # check updates worked and didn't change anything else response = self.client.get('/user/' + user_id, headers=self.headers) self.assertEqual(response.status_code, 200) data = json.loads(response.data.decode('utf-8')) self.assertEqual(data['user']['name'], 'Squealer') self.assertEqual(data['user']['email'], '*****@*****.**') self.assertEqual(data['user']['text'], 'Four legs good, two legs bad.') self.assertEqual(data['user']['attributes'], { 'four-legs': 'double good', 'two-legs': 'bad', 'isEvil': False })
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 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)