def test_get_tenant_with_user(self): """Test that the customer tenant is returned.""" mock_request = self.request middleware = IdentityHeaderMiddleware() result = middleware.get_tenant(Tenant, "localhost", mock_request) self.assertEqual(result.schema_name, create_schema_name(mock_request.user.account))
def get_tenant(self, model, hostname, request): """Override the tenant selection logic.""" connections["default"].set_schema_to_public() if request.user.account not in TENANTS: if request.user.system: try: tenant = Tenant.objects.get(schema_name=create_schema_name(request.user.account)) except Tenant.DoesNotExist: raise Http404() else: tenant, created = Tenant.objects.get_or_create(schema_name=create_schema_name(request.user.account)) if created: seed_roles(tenant=tenant, update=False) seed_group(tenant=tenant) TENANTS[request.user.account] = tenant return TENANTS[request.user.account]
def test_create_principal_on_approval(self): """Test that moving a car to approved creates a principal.""" update_data = {"status": "approved"} tenant_schema = create_schema_name(self.request_2.target_account) principal_name = get_cross_principal_name( self.request_2.target_account, self.request_2.user_id) car_uuid = self.request_2.request_id url = reverse("cross-detail", kwargs={"pk": str(car_uuid)}) tenant = Tenant.objects.get(schema_name=tenant_schema) client = APIClient() response = client.patch(url, update_data, format="json", **self.associate_admin_request.META) self.assertEqual(response.status_code, status.HTTP_200_OK) self.assertEqual(response.data.get("status"), update_data.get("status")) with tenant_context(tenant): princ = Principal.objects.get(username__iexact=principal_name) self.assertEqual(princ.username, principal_name) self.assertEqual(princ.tenant, tenant) self.assertTrue(princ.cross_account)
def get_tenant(self, model, hostname, request): """Override the tenant selection logic.""" connections["default"].set_schema_to_public() tenant_schema = create_schema_name(request.user.account) tenant = TENANTS.get_tenant(tenant_schema) if tenant is None: if request.user.system: try: tenant = Tenant.objects.get(schema_name=tenant_schema) except Tenant.DoesNotExist: raise Http404() else: with transaction.atomic(): try: tenant = Tenant.objects.get(schema_name=tenant_schema) except Tenant.DoesNotExist: cursor = transaction.get_connection().cursor() cursor.execute("LOCK TABLE public.api_tenant in SHARE ROW EXCLUSIVE MODE") tenant, created = Tenant.objects.get_or_create(schema_name=tenant_schema) if created: seed_permissions(tenant=tenant) seed_roles(tenant=tenant) seed_group(tenant=tenant) TENANTS.save_tenant(tenant) return tenant
def process_request(self, request): # noqa: C901 """Process request for identity middleware. 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') account = json_rh_auth.get('identity', {}).get('account_number') is_admin = json_rh_auth.get('identity', {}).get('user', {}).get('is_org_admin') except (KeyError, JSONDecodeError) as decode_err: logger.error('Could not obtain identity on request.') raise decode_err except binascii.Error as error: logger.error('Could not decode header: %s.', error) raise error if (username and account): # Get request ID req_id = request.META.get(RH_INSIGHTS_REQUEST_ID) # Check for customer creation & user creation try: schema_name = create_schema_name(account) tenant = Tenant.objects.filter(schema_name=schema_name).get() except Tenant.DoesNotExist: tenant = IdentityHeaderMiddleware._create_tenant(account) try: user = User.objects.get(username__iexact=username) except User.DoesNotExist: user = IdentityHeaderMiddleware._create_user( username, tenant, request) with tenant_context(tenant): try: Principal.objects.get(username__iexact=username) except Principal.DoesNotExist: Principal.objects.create(username=username) logger.info('Created new principal %s for account %s.', username, account) user.identity_header = { 'encoded': rh_auth_header, 'decoded': json_rh_auth } user.admin = is_admin user.account = account user.req_id = req_id if not is_admin: user.access = IdentityHeaderMiddleware._get_access_for_user( username, tenant) request.user = user
def create_principal(self, target_account, user_id): """Create a cross account principal in the target account.""" # Principal would have the pattern acctxxx-123456. principal_name = f"{target_account}-{user_id}" tenant_schema = create_schema_name(target_account) with tenant_context(Tenant.objects.get(schema_name=tenant_schema)): cross_account_principal = Principal.objects.get_or_create( username=principal_name, cross_account=True) return cross_account_principal
def setUp(self): """Set up middleware tests.""" super().setUp() self.user_data = self._create_user_data() self.customer = self._create_customer_data() self.schema_name = create_schema_name(self.customer['account_id']) self.request_context = self._create_request_context( self.customer, self.user_data, create_customer=False) self.request = self.request_context['request'] self.request.path = '/api/v1/providers/' self.request.META['QUERY_STRING'] = ''
def setUp(self): """Set up middleware tests.""" super().setUp() self.user_data = self._create_user_data() self.customer = self._create_customer_data() self.schema_name = create_schema_name(self.customer["account_id"]) self.request_context = self._create_request_context( self.customer, self.user_data, create_customer=False) self.request = self.request_context["request"] self.request.path = "/api/v1/providers/" self.request.META["QUERY_STRING"] = ""
def create_cross_principal(target_account, user_id): """Create a cross account principal in the target account.""" # Principal would have the pattern acctxxx-123456. principal_name = get_cross_principal_name(target_account, user_id) tenant_schema = create_schema_name(target_account) associate_tenant = Tenant.objects.get(schema_name=tenant_schema) cross_account_principal = create_principal_with_tenant(principal_name, tenant_schema, associate_tenant) # Create the principal in public schema create_principal_with_tenant(principal_name, "public", associate_tenant) return cross_account_principal
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: json_rh_auth = extract_header(request, self.header) username = json_rh_auth['identity']['user']['username'] email = json_rh_auth['identity']['user']['email'] account = json_rh_auth['identity']['account_number'] 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']) logger.info(f'API: {request.path}{query_string}' # pylint: disable=W1203 f' -- ACCOUNT: {account} USER: {username}') try: schema_name = create_schema_name(account) tenant = Tenant.objects.filter(schema_name=schema_name).get() except Tenant.DoesNotExist: tenant = IdentityHeaderMiddleware._create_tenant(account) try: user = User.objects.get(username=username) except User.DoesNotExist: user = IdentityHeaderMiddleware._create_user( username, email, tenant, request) request.user = user # Temporarily add principals based on API interaction with tenant_context(tenant): try: Principal.objects.get(username=username) except Principal.DoesNotExist: try: with transaction.atomic(): principal = Principal(username=username, email=email) principal.save() logger.info( 'Created new principal for account_id %s.', account) except IntegrityError: pass
def validate_and_get_input_for_creation(self, request_data): """Validate the create api input.""" target_account = request_data.get("target_account") start_date = request_data.get("start_date") end_date = request_data.get("end_date") roles = request_data.get("roles") if None in [target_account, start_date, end_date, roles]: self.throw_validation_error( "cross-account-create", f"{PARAMS_FOR_CREATION} must be all specified.") try: start_date = datetime.strptime(start_date, "%m/%d/%Y") end_date = datetime.strptime(end_date, "%m/%d/%Y") except ValueError: raise self.throw_validation_error( "cross-account-create", "start_date or end_date does not match format: '%m/%d/%Y'.") if start_date > (datetime.now() + timedelta(60)): raise self.throw_validation_error( "cross-account-create", "Start date must be within 60 days of today.") if end_date - start_date > timedelta(365): raise self.throw_validation_error( "cross-account-create", "Access duration may not be longer than one year.") with tenant_context(Tenant.objects.get(schema_name="public")): for role in roles: try: Role.objects.get(display_name=role) except Role.DoesNotExist: raise self.throw_validation_error( "cross-account-create", f"Role '{role}' does not exist.") try: tenant_schema_name = create_schema_name(target_account) Tenant.objects.get(schema_name=tenant_schema_name) except Tenant.DoesNotExist: raise self.throw_validation_error( "cross-account-create", f"Account '{target_account}' does not exist.") request_data["start_date"] = start_date request_data["end_date"] = end_date request_data["user_id"] = self.request.user.user_id request_data["roles"] = [{"display_name": role} for role in roles]
def setUp(self): """Set up middleware tests.""" super().setUp() self.user_data = self._create_user_data() self.customer = self._create_customer_data() self.schema_name = create_schema_name(self.customer["account_id"]) self.request_context = self._create_request_context( self.customer, self.user_data, create_customer=False) self.request = self.request_context["request"] self.request.path = "/api/v1/providers/" user = User() user.username = self.user_data["username"] user.account = self.customer_data["account_id"] self.request.user = user
def setUp(self): """Set up middleware tests.""" super().setUp() self.user_data = self._create_user_data() self.customer = self._create_customer_data() self.schema_name = create_schema_name(self.customer['account_id']) self.request_context = self._create_request_context( self.customer, self.user_data) request = self.request_context['request'] request.path = '/api/v1/providers/' serializer = UserSerializer(data=self.user_data, context=self.request_context) if serializer.is_valid(raise_exception=True): user = serializer.save() request.user = user
def _create_customer(cls, account, create_tenant=True): """Create a customer. Args: account (str): The account identifier Returns: (Customer) The created customer """ connection.set_schema_to_public() schema_name = create_schema_name(account) tenant = None if create_tenant: tenant = Tenant(schema_name=schema_name) tenant.save() return tenant
def validate_and_format_input(self, request_data): """Validate the create api input.""" for field in PARAMS_FOR_CREATION: if not request_data.__contains__(field): self.throw_validation_error("cross-account-request", f"Field {field} must be specified.") target_account = request_data.get("target_account") try: tenant_schema_name = create_schema_name(target_account) Tenant.objects.get(schema_name=tenant_schema_name) except Tenant.DoesNotExist: raise self.throw_validation_error("cross-account-request", f"Account '{target_account}' does not exist.") with tenant_context(Tenant.objects.get(schema_name="public")): request_data["roles"] = self.format_roles(request_data.get("roles")) request_data["user_id"] = self.request.user.user_id
def validate_and_format_input(self, request_data, partial=False): """Validate the create api input.""" target_account = request_data.get("target_account") if target_account: try: tenant_schema_name = create_schema_name(target_account) Tenant.objects.get(schema_name=tenant_schema_name) except Tenant.DoesNotExist: raise self.throw_validation_error( "cross-account-request", f"Account '{target_account}' does not exist.") request_data["user_id"] = self.request.user.user_id if "roles" in request_data: with tenant_context(Tenant.objects.get(schema_name="public")): request_data["roles"] = self.format_roles( request_data.get("roles"))
def create_cross_principal(target_account, user_id): """Create a cross account principal in the target account.""" # Principal would have the pattern acctxxx-123456. principal_name = get_cross_principal_name(target_account, user_id) tenant_schema = create_schema_name(target_account) tenant = Tenant.objects.get(schema_name=tenant_schema) with tenant_context(tenant): cross_account_principal, _ = Principal.objects.get_or_create( username=principal_name, cross_account=True) # NOTE: after we ensure/enforce all object have a tenant_id FK, we can add tenant=tenant # to the get_or_create. We cannot currently, because records without would fail the GET # and would create duplicate records. This ensures we temporarily do an update if # obj.tenant_id is NULL if not cross_account_principal.tenant: cross_account_principal.tenant = tenant cross_account_principal.save() return cross_account_principal
def _create_tenant(account): """Create a tenant. Args: account (str): The account identifier Returns: (Tenant) The created tenant """ schema_name = create_schema_name(account) try: with transaction.atomic(): tenant = Tenant(schema_name=schema_name) tenant.save() logger.info('Created new tenant from account_id %s.', account) except IntegrityError: tenant = Tenant.objects.filter(schema_name=schema_name).get() return tenant
def process_request(self, request): # pylint: disable=R1710 """Process request for identity middleware. Args: request (object): The request object """ # Get request ID request.req_id = request.META.get(RH_INSIGHTS_REQUEST_ID) if any([request.path.startswith(prefix) for prefix in settings.INTERNAL_API_PATH_PREFIXES]): # This request is for a private API endpoint return if is_no_auth(request): return user = User() try: _, json_rh_auth = extract_header(request, self.header) user.account = json_rh_auth.get("identity", {})["account_number"] user_info = json_rh_auth.get("identity", {}).get("user", {}) user.username = user_info["username"] user.admin = user_info.get("is_org_admin") user.internal = user_info.get("is_internal") user.user_id = user_info.get("user_id") user.system = False if not user.admin: try: schema_name = create_schema_name(user.account) tenant = Tenant.objects.filter(schema_name=schema_name).get() except Tenant.DoesNotExist: request.user = user tenant = self.get_tenant(model=None, hostname=None, request=request) user.access = IdentityHeaderMiddleware._get_access_for_user(user.username, tenant) # Cross account request check internal = json_rh_auth.get("identity", {}).get("internal", {}) if internal != {}: cross_account = internal.get("cross_access", False) if cross_account: if not (user.internal and user_info.get("email").endswith("@redhat.com")): logger.error("Cross accout request permission denied. Requester is not internal user.") return HttpResponseUnauthorizedRequest() user.username = f"{user.account}-{user.user_id}" except (KeyError, JSONDecodeError): request_psk = request.META.get(RH_RBAC_PSK) account = request.META.get(RH_RBAC_ACCOUNT) client_id = request.META.get(RH_RBAC_CLIENT_ID) has_system_auth_headers = request_psk and account and client_id if has_system_auth_headers and validate_psk(request_psk, client_id): user.username = client_id user.account = account user.admin = True user.system = True else: logger.error("Could not obtain identity on request.") return HttpResponseUnauthorizedRequest() except binascii.Error as error: logger.error("Could not decode header: %s.", error) raise error if user.username and user.account: request.user = user super().process_request(request) # We are now in the database context of the tenant assert request.tenant