def test_get_access_for_user_data_limited(self, mock_get): """Test handling of user request where access returns data.""" rbac = RbacService() mock_user = Mock() mock_user.identity_header = {'encoded': 'dGVzdCBoZWFkZXIgZGF0YQ=='} access = rbac.get_access_for_user(mock_user) expected = { 'provider': { 'write': [], 'read': [] }, 'rate': { 'write': [], 'read': [] }, 'aws.account': { 'read': ['123456'] }, 'openshift.cluster': { 'read': [] }, 'openshift.node': { 'read': [] }, 'openshift.project': { 'read': [] } } self.assertEqual(access, expected) mock_get.assert_called()
def test_200_text(self, mock_get): """Test handling of request with 200 response and non-json error.""" rbac = RbacService() url = '{}://{}:{}{}'.format(rbac.protocol, rbac.host, rbac.port, rbac.path) access = rbac._request_user_access(url, headers={}) # pylint: disable=protected-access self.assertEqual(access, []) mock_get.assert_called()
def test_get_access_for_user_data_limited(self, mock_get): """Test handling of user request where access returns data.""" rbac = RbacService() mock_user = Mock() mock_user.identity_header = {"encoded": "dGVzdCBoZWFkZXIgZGF0YQ=="} access = rbac.get_access_for_user(mock_user) expected = { "provider": { "write": [], "read": [] }, "rate": { "write": [], "read": [] }, "aws.account": { "read": ["123456"] }, "azure.subscription_guid": { "read": [] }, "openshift.cluster": { "read": [] }, "openshift.node": { "read": [] }, "openshift.project": { "read": [] }, } self.assertEqual(access, expected) mock_get.assert_called()
def test_200_exception(self, mock_get): """Test handling of request with 200 response and raises a json error.""" rbac = RbacService() url = f"{rbac.protocol}://{rbac.host}:{rbac.port}{rbac.path}" access = rbac._request_user_access(url, headers={}) # pylint: disable=protected-access self.assertEqual(access, []) mock_get.assert_called()
def test_200_text(self, mock_get): """Test handling of request with 200 response and non-json error.""" rbac = RbacService() url = f"{rbac.protocol}://{rbac.host}:{rbac.port}{rbac.path}" access = rbac._request_user_access(url, headers={}) self.assertEqual(access, []) mock_get.assert_called()
def test_200_all_results(self, mock_get): """Test handling of request with 200 response with no next link.""" rbac = RbacService() url = '{}://{}:{}{}'.format(rbac.protocol, rbac.host, rbac.port, rbac.path) access = rbac._request_user_access(url, headers={}) # pylint: disable=protected-access self.assertEqual(access, [LIMITED_AWS_ACCESS]) mock_get.assert_called()
def test_200_results_next(self, mock_get): """Test handling of request with 200 response with next link.""" rbac = RbacService() url = f"{rbac.protocol}://{rbac.host}:{rbac.port}{rbac.path}" access = rbac._request_user_access(url, headers={}) # pylint: disable=protected-access self.assertEqual(access, [LIMITED_AWS_ACCESS, LIMITED_AWS_ACCESS]) mock_get.assert_called()
def test_get_access_for_user_none(self, mock_get): """Test handling of user request where no access returns None.""" rbac = RbacService() mock_user = Mock() mock_user.identity_header = {'encoded': 'dGVzdCBoZWFkZXIgZGF0YQ=='} access = rbac.get_access_for_user(mock_user) self.assertIsNone(access) mock_get.assert_called()
def test_non_200_error_except(self, mock_get): """Test handling of request with non-200 response and non-json error.""" rbac = RbacService() url = f"{rbac.protocol}://{rbac.host}:{rbac.port}{rbac.path}" logging.disable(logging.NOTSET) with self.assertLogs(logger="koku.rbac", level=logging.WARNING): access = rbac._request_user_access(url, headers={}) # pylint: disable=protected-access self.assertEqual(access, []) mock_get.assert_called()
def test_get_except(self, mock_get): """Test handling of request with ConnectionError.""" before = REGISTRY.get_sample_value("rbac_connection_errors_total") rbac = RbacService() url = f"{rbac.protocol}://{rbac.host}:{rbac.port}{rbac.path}" with self.assertRaises(RbacConnectionError): rbac._request_user_access(url, headers={}) # pylint: disable=protected-access after = REGISTRY.get_sample_value("rbac_connection_errors_total") self.assertEqual(1, after - before) mock_get.assert_called()
def status(request): """Provide the server status information. @api {get} /cost-management/v1/status/ Request server status @apiName GetStatus @apiGroup Status @apiVersion 1.0.0 @apiDescription Request server status. @apiSuccess {Number} api_version The version of the API. @apiSuccess {String} commit The commit hash of the code base. @apiSuccess {Object} modules The code modules found on the server. @apiSuccess {Object} platform_info The server platform information. @apiSuccess {String} python_version The version of python on the server. @apiSuccess {String} server_address The address of the server. @apiSuccessExample {json} Success-Response: HTTP/1.1 200 OK { "api_version": 1, "commit": "178d2ea", "server_address": "127.0.0.1:8000", "platform_info": { "system": "Darwin", "node": "node-1.example.com", "release": "17.5.0", "version": "Darwin Kernel Version 17.5.0", "machine": "x86_64", "processor": "i386" }, "python_version": "3.6.1", "modules": { "coverage": "4.5.1", "coverage.version": "4.5.1", "coverage.xmlreport": "4.5.1", "cryptography": "2.0.3", "ctypes": "1.1.0", "ctypes.macholib": "1.0", "decimal": "1.70", "django": "1.11.5", "django.utils.six": "1.10.0", "django_filters": "1.0.4", "http.server": "0.6" } } """ status_info = Status() serializer = StatusSerializer(status_info) server_info = serializer.data server_info['server_address'] = request.META.get('HTTP_HOST', 'localhost') server_info['rbac_cache_ttl'] = RbacService().get_cache_ttl() return Response(server_info)
class IdentityHeaderMiddleware(MiddlewareMixin): # pylint: disable=R0903 """A subclass of RemoteUserMiddleware. Processes the provided identity found on the request. """ header = RH_IDENTITY_HEADER rbac = RbacService() @staticmethod def _create_customer(account): """Create a customer. Args: account (str): The account identifier Returns: (Customer) The created customer """ try: with transaction.atomic(): schema_name = create_schema_name(account) customer = Customer(account_id=account, schema_name=schema_name) customer.save() tenant = Tenant(schema_name=schema_name) tenant.save() UNIQUE_ACCOUNT_COUNTER.inc() LOG.info("Created new customer from account_id %s.", account) except IntegrityError: customer = Customer.objects.filter(account_id=account).get() return customer @staticmethod def _create_user(username, email, customer, request): """Create a user for a customer. Args: username (str): The username email (str): The email for the user customer (Customer): The customer the user is associated with request (object): The incoming request Returns: (User) The created user """ new_user = None try: with transaction.atomic(): user_data = {"username": username, "email": email} context = {"request": request} serializer = UserSerializer(data=user_data, context=context) if serializer.is_valid(raise_exception=True): new_user = serializer.save() UNIQUE_USER_COUNTER.labels(account=customer.account_id, user=username).inc() LOG.info("Created new user %s for customer(account_id %s).", username, customer.account_id) except (IntegrityError, ValidationError): new_user = User.objects.get(username=username) return new_user def _get_access(self, user): """Obtain access for given user from RBAC service.""" access = None if user.admin: return access access = self.rbac.get_access_for_user(user) return access # pylint: disable=R0914, R1710 def process_request(self, request): # noqa: C901 """Process request for csrf checks. Args: request (object): The request object """ connection.set_schema_to_public() if is_no_auth(request): request.user = User("", "") return try: rh_auth_header, json_rh_auth = extract_header(request, self.header) except (KeyError, JSONDecodeError): LOG.warning("Could not obtain identity on request.") return except binascii.Error as error: LOG.error("Error decoding authentication header: %s", str(error)) raise PermissionDenied() is_openshift = json_rh_auth.get("entitlements", {}).get("openshift", {}).get("is_entitled", False) if not is_openshift: raise PermissionDenied() account = json_rh_auth.get("identity", {}).get("account_number") user = json_rh_auth.get("identity", {}).get("user", {}) username = user.get("username") email = user.get("email") is_admin = user.get("is_org_admin") if username and email and account: # Get request ID req_id = request.META.get("HTTP_X_RH_INSIGHTS_REQUEST_ID") # Check for customer creation & user creation query_string = "" if request.META["QUERY_STRING"]: query_string = "?{}".format(request.META["QUERY_STRING"]) stmt = (f"{request.method}: {request.path}{query_string}" f" -- ACCOUNT: {account} USER: {username}" f" ORG_ADMIN: {is_admin} REQ_ID: {req_id}") LOG.info(stmt) try: customer = Customer.objects.filter(account_id=account).get() except Customer.DoesNotExist: customer = IdentityHeaderMiddleware._create_customer(account) except OperationalError as err: LOG.error("IdentityHeaderMiddleware exception: %s", err) DB_CONNECTION_ERRORS_COUNTER.inc() return HttpResponseFailedDependency({ "source": "Database", "exception": err }) try: user = User.objects.get(username=username) except User.DoesNotExist: user = IdentityHeaderMiddleware._create_user( username, email, customer, request) user.identity_header = { "encoded": rh_auth_header, "decoded": json_rh_auth } user.admin = is_admin user.req_id = req_id cache = caches["rbac"] user_access = cache.get(user.uuid) if not user_access: try: user_access = self._get_access(user) except RbacConnectionError as err: return HttpResponseFailedDependency({ "source": "Rbac", "exception": err }) cache.set(user.uuid, user_access, self.rbac.cache_ttl) user.access = user_access request.user = user def process_response(self, request, response): # pylint: disable=no-self-use """Process response for identity middleware. Args: request (object): The request object response (object): The response object """ context = "" query_string = "" is_admin = False customer = None username = None req_id = None if request.META.get("QUERY_STRING"): query_string = "?{}".format(request.META["QUERY_STRING"]) if hasattr(request, "user") and request.user and request.user.customer: is_admin = request.user.admin customer = request.user.customer.account_id username = request.user.username req_id = request.user.req_id if customer: context = f" -- ACCOUNT: {customer} USER: {username}" f" ORG_ADMIN: {is_admin} REQ_ID: {req_id}" stmt = f"{request.method}: {request.path}{query_string}" f" {response.status_code}{context}" LOG.info(stmt) return response
def test_get_cache_ttl(self): """Test to get the cache ttl value.""" rbac = RbacService() self.assertEqual(rbac.get_cache_ttl(), 5)
def rbac_cache_ttl(self): """Get the RBAC cache ttl.""" return RbacService().get_cache_ttl()
def test_500_error_json(self, mock_get): """Test handling of request with 500 response and json error.""" rbac = RbacService() url = f"{rbac.protocol}://{rbac.host}:{rbac.port}{rbac.path}" with self.assertRaises(RbacConnectionError): rbac._request_user_access(url, headers={}) # pylint: disable=protected-access
class IdentityHeaderMiddleware(MiddlewareMixin): # pylint: disable=R0903 """A subclass of RemoteUserMiddleware. Processes the provided identity found on the request. """ header = RH_IDENTITY_HEADER rbac = RbacService() @staticmethod def _create_customer(account): """Create a customer. Args: account (str): The account identifier Returns: (Customer) The created customer """ try: with transaction.atomic(): schema_name = create_schema_name(account) customer = Customer(account_id=account, schema_name=schema_name) customer.save() tenant = Tenant(schema_name=schema_name) tenant.save() unique_account_counter.inc() logger.info('Created new customer from account_id %s.', account) except IntegrityError: customer = Customer.objects.filter(account_id=account).get() return customer @staticmethod def _create_user(username, email, customer, request): """Create a user for a customer. Args: username (str): The username email (str): The email for the user customer (Customer): The customer the user is associated with request (object): The incoming request Returns: (User) The created user """ new_user = None try: with transaction.atomic(): user_data = {'username': username, 'email': email} context = {'request': request} serializer = UserSerializer(data=user_data, context=context) if serializer.is_valid(raise_exception=True): new_user = serializer.save() unique_user_counter.labels(account=customer.account_id, user=username).inc() logger.info('Created new user %s for customer(account_id %s).', username, customer.account_id) except (IntegrityError, ValidationError): new_user = User.objects.get(username=username) return new_user def _get_access(self, user): """Obtain access for given user from RBAC service.""" access = None if user.admin: return access access = self.rbac.get_access_for_user(user) return access # pylint: disable=too-many-locals def process_request(self, request): # noqa: C901 """Process request for csrf checks. Args: request (object): The request object """ if is_no_auth(request): request.user = User('', '') return try: rh_auth_header, json_rh_auth = extract_header(request, self.header) username = json_rh_auth.get('identity', {}).get('user', {}).get('username') email = json_rh_auth.get('identity', {}).get('user', {}).get('email') account = json_rh_auth.get('identity', {}).get('account_number') is_admin = json_rh_auth.get('identity', {}).get('user', {}).get('is_org_admin') is_cost_management = json_rh_auth.get('entitlements', {}).get( 'cost_management', {}).get('is_entitled', False) is_hybrid_cloud = json_rh_auth.get('entitlements', {}).get( 'hybrid_cloud', {}).get('is_entitled', False) if not is_hybrid_cloud and not is_cost_management: raise PermissionDenied() except (KeyError, JSONDecodeError): logger.warning('Could not obtain identity on request.') return if (username and email and account): # Check for customer creation & user creation query_string = '' if request.META['QUERY_STRING']: query_string = '?{}'.format(request.META['QUERY_STRING']) stmt = (f'API: {request.path}{query_string}' f' -- ACCOUNT: {account} USER: {username}') logger.info(stmt) try: customer = Customer.objects.filter(account_id=account).get() except Customer.DoesNotExist: customer = IdentityHeaderMiddleware._create_customer(account) try: user = User.objects.get(username=username) except User.DoesNotExist: user = IdentityHeaderMiddleware._create_user( username, email, customer, request) user.identity_header = { 'encoded': rh_auth_header, 'decoded': json_rh_auth } user.admin = is_admin cache = caches['rbac'] user_access = cache.get(user.uuid) if not user_access: user_access = self._get_access(user) cache.set(user.uuid, user_access, self.rbac.cache_ttl) user.access = user_access request.user = user
class IdentityHeaderMiddleware(MiddlewareMixin): """A subclass of RemoteUserMiddleware. Processes the provided identity found on the request. """ header = RH_IDENTITY_HEADER rbac = RbacService() customer_cache = TTLCache(maxsize=MAX_CACHE_SIZE, ttl=TIME_TO_CACHE) @staticmethod def create_customer(account): """Create a customer. Args: account (str): The account identifier Returns: (Customer) The created customer """ try: with transaction.atomic(): schema_name = create_schema_name(account) customer = Customer(account_id=account, schema_name=schema_name) customer.save() tenant = Tenant(schema_name=schema_name) tenant.save() UNIQUE_ACCOUNT_COUNTER.inc() LOG.info("Created new customer from account_id %s.", account) except IntegrityError: customer = Customer.objects.filter(account_id=account).get() return customer @staticmethod def create_user(username, email, customer, request): """Create a user for a customer. Args: username (str): The username email (str): The email for the user customer (Customer): The customer the user is associated with request (object): The incoming request Returns: (User) The created user """ new_user = None try: with transaction.atomic(): user_data = {"username": username, "email": email} context = {"request": request, "customer": customer} serializer = UserSerializer(data=user_data, context=context) if serializer.is_valid(raise_exception=True): new_user = serializer.save() UNIQUE_USER_COUNTER.labels(account=customer.account_id, user=username).inc() LOG.info("Created new user %s for customer(account_id %s).", username, customer.account_id) except (IntegrityError, ValidationError): new_user = User.objects.get(username=username) return new_user def _get_access(self, user): """Obtain access for given user from RBAC service.""" access = None if user.admin: return access access = self.rbac.get_access_for_user(user) return access def process_request(self, request): # noqa: C901 """Process request for csrf checks. Args: request (object): The request object """ connection.set_schema_to_public() if is_no_auth(request): request.user = User("", "") return try: rh_auth_header, json_rh_auth = extract_header(request, self.header) except (KeyError, JSONDecodeError): LOG.warning("Could not obtain identity on request.") return except binascii.Error as error: LOG.error("Error decoding authentication header: %s", str(error)) raise PermissionDenied() is_cost_management = json_rh_auth.get("entitlements", {}).get( "cost_management", {}).get("is_entitled", False) skip_entitlement = is_no_entitled(request) if not skip_entitlement and not is_cost_management: LOG.warning("User is not entitled for Cost Management.") raise PermissionDenied() account = json_rh_auth.get("identity", {}).get("account_number") user = json_rh_auth.get("identity", {}).get("user", {}) username = user.get("username") email = user.get("email") is_admin = user.get("is_org_admin") req_id = None if username and email and account: # Get request ID req_id = request.META.get("HTTP_X_RH_INSIGHTS_REQUEST_ID") # Check for customer creation & user creation query_string = "" if request.META["QUERY_STRING"]: query_string = "?{}".format(request.META["QUERY_STRING"]) stmt = { "method": request.method, "path": request.path + query_string, "request_id": req_id, "account": account, "username": username, "is_admin": is_admin, } LOG.info(stmt) try: if account not in IdentityHeaderMiddleware.customer_cache: IdentityHeaderMiddleware.customer_cache[ account] = Customer.objects.filter( account_id=account).get() LOG.debug(f"Customer added to cache: {account}") customer = IdentityHeaderMiddleware.customer_cache[account] except Customer.DoesNotExist: customer = IdentityHeaderMiddleware.create_customer(account) except OperationalError as err: LOG.error("IdentityHeaderMiddleware exception: %s", err) DB_CONNECTION_ERRORS_COUNTER.inc() return HttpResponseFailedDependency({ "source": "Database", "exception": err }) try: if username not in USER_CACHE: user = User.objects.get(username=username) USER_CACHE[username] = user LOG.debug(f"User added to cache: {username}") else: user = USER_CACHE[username] except User.DoesNotExist: user = IdentityHeaderMiddleware.create_user( username, email, customer, request) user.identity_header = { "encoded": rh_auth_header, "decoded": json_rh_auth } user.admin = is_admin user.req_id = req_id cache = caches["rbac"] user_access = cache.get(user.uuid) if not user_access: if settings.DEVELOPMENT and request.user.req_id == "DEVELOPMENT": # passthrough for DEVELOPMENT_IDENTITY env var. LOG.warning( "DEVELOPMENT is Enabled. Bypassing access lookup for user: %s", json_rh_auth) user_access = request.user.access else: try: user_access = self._get_access(user) except RbacConnectionError as err: return HttpResponseFailedDependency({ "source": "Rbac", "exception": err }) cache.set(user.uuid, user_access, self.rbac.cache_ttl) user.access = user_access request.user = user def process_response(self, request, response): """Process response for identity middleware. Args: request (object): The request object response (object): The response object """ query_string = "" is_admin = False account = None username = None req_id = None if request.META.get("QUERY_STRING"): query_string = "?{}".format(request.META["QUERY_STRING"]) if hasattr(request, "user") and request.user and request.user.customer: is_admin = request.user.admin account = request.user.customer.account_id username = request.user.username req_id = request.user.req_id stmt = { "method": request.method, "path": request.path + query_string, "status": response.status_code, "request_id": req_id, "account": account, "username": username, "is_admin": is_admin, } LOG.info(stmt) return response
class IdentityHeaderMiddleware(MiddlewareMixin): # pylint: disable=R0903 """A subclass of RemoteUserMiddleware. Processes the provided identity found on the request. """ header = RH_IDENTITY_HEADER rbac = RbacService() @staticmethod def _create_customer(account): """Create a customer. Args: account (str): The account identifier Returns: (Customer) The created customer """ try: with transaction.atomic(): schema_name = create_schema_name(account) customer = Customer(account_id=account, schema_name=schema_name) customer.save() tenant = Tenant(schema_name=schema_name) tenant.save() UNIQUE_ACCOUNT_COUNTER.inc() LOG.info('Created new customer from account_id %s.', account) except IntegrityError: customer = Customer.objects.filter(account_id=account).get() return customer @staticmethod def _create_user(username, email, customer, request): """Create a user for a customer. Args: username (str): The username email (str): The email for the user customer (Customer): The customer the user is associated with request (object): The incoming request Returns: (User) The created user """ new_user = None try: with transaction.atomic(): user_data = {'username': username, 'email': email} context = {'request': request} serializer = UserSerializer(data=user_data, context=context) if serializer.is_valid(raise_exception=True): new_user = serializer.save() UNIQUE_USER_COUNTER.labels(account=customer.account_id, user=username).inc() LOG.info( 'Created new user %s for customer(account_id %s).', username, customer.account_id, ) except (IntegrityError, ValidationError): new_user = User.objects.get(username=username) return new_user def _get_access(self, user): """Obtain access for given user from RBAC service.""" access = None if user.admin: return access access = self.rbac.get_access_for_user(user) return access # pylint: disable=R0914, R1710 def process_request(self, request): # noqa: C901 """Process request for csrf checks. Args: request (object): The request object """ if is_no_auth(request): request.user = User('', '') return try: rh_auth_header, json_rh_auth = extract_header(request, self.header) except (KeyError, JSONDecodeError): LOG.warning('Could not obtain identity on request.') return except binascii.Error as error: LOG.error('Error decoding authentication header: %s', str(error)) raise PermissionDenied() is_openshift = (json_rh_auth.get('entitlements', {}).get('openshift', {}).get('is_entitled', False)) if not is_openshift: raise PermissionDenied() account = json_rh_auth.get('identity', {}).get('account_number') user = json_rh_auth.get('identity', {}).get('user', {}) username = user.get('username') email = user.get('email') is_admin = user.get('is_org_admin') if username and email and account: # Check for customer creation & user creation query_string = '' if request.META['QUERY_STRING']: query_string = '?{}'.format(request.META['QUERY_STRING']) stmt = (f'API: {request.path}{query_string}' f' -- ACCOUNT: {account} USER: {username}') LOG.info(stmt) try: customer = Customer.objects.filter(account_id=account).get() except Customer.DoesNotExist: customer = IdentityHeaderMiddleware._create_customer(account) except OperationalError as err: LOG.error('IdentityHeaderMiddleware exception: %s', err) DB_CONNECTION_ERRORS_COUNTER.inc() return HttpResponseFailedDependency({ 'source': 'Database', 'exception': err }) try: user = User.objects.get(username=username) except User.DoesNotExist: user = IdentityHeaderMiddleware._create_user( username, email, customer, request) user.identity_header = { 'encoded': rh_auth_header, 'decoded': json_rh_auth } user.admin = is_admin cache = caches['rbac'] user_access = cache.get(user.uuid) if not user_access: try: user_access = self._get_access(user) except RbacConnectionError as err: return HttpResponseFailedDependency({ 'source': 'Rbac', 'exception': err }) cache.set(user.uuid, user_access, self.rbac.cache_ttl) user.access = user_access request.user = user