Skip to content

Commit

Permalink
Merge pull request RedHatInsights#1093 from astrozzc/org_id
Browse files Browse the repository at this point in the history
Remove legacy account related scope
  • Loading branch information
astrozzc authored May 23, 2024
2 parents aa01408 + 63a3d9e commit 5965485
Show file tree
Hide file tree
Showing 38 changed files with 246 additions and 702 deletions.
9 changes: 0 additions & 9 deletions deploy/rbac-clowdapp.yml
Original file line number Diff line number Diff line change
Expand Up @@ -103,8 +103,6 @@ objects:
cpu: ${CELERY_WORKER_CPU_REQUEST}
memory: ${CELERY_WORKER_MEMORY_REQUEST}
env:
- name: AUTHENTICATE_WITH_ORG_ID
value: ${AUTHENTICATE_WITH_ORG_ID}
- name: DJANGO_LOG_LEVEL
value: ${DJANGO_LOG_LEVEL}
- name: DJANGO_DEBUG
Expand Down Expand Up @@ -258,8 +256,6 @@ objects:
cpu: ${CELERY_SCHEDULER_CPU_REQUEST}
memory: ${CELERY_SCHEDULER_MEMORY_REQUEST}
env:
- name: AUTHENTICATE_WITH_ORG_ID
value: ${AUTHENTICATE_WITH_ORG_ID}
- name: DJANGO_LOG_LEVEL
value: ${DJANGO_LOG_LEVEL}
- name: DJANGO_DEBUG
Expand Down Expand Up @@ -464,8 +460,6 @@ objects:
value: ${REDIS_SOCKET_CONNECT_TIMEOUT}
- name: REDIS_SOCKET_TIMEOUT
value: ${REDIS_SOCKET_TIMEOUT}
- name: AUTHENTICATE_WITH_ORG_ID
value: ${AUTHENTICATE_WITH_ORG_ID}
- name: NOTIFICATIONS_ENABLED
value: ${NOTIFICATIONS_ENABLED}
- name: GUNICORN_WORKER_MULTIPLIER
Expand Down Expand Up @@ -3410,9 +3404,6 @@ parameters:
- description: Enable sending out notification events of Red Hat changes
name: NOTIFICATIONS_RH_ENABLED
value: 'False'
- description: Enable RBAC tenancy using Org ID
name: AUTHENTICATE_WITH_ORG_ID
value: 'True'
- name: TENANT_TRANSLATOR_HOST
required: true
- name: TENANT_TRANSLATOR_PORT
Expand Down
1 change: 0 additions & 1 deletion docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,6 @@ services:
- DJANGO_READ_DOT_ENV_FILE=True
- DEVELOPMENT=${DEVELOPMENT-False}
- DJANGO_DEBUG=${DJANGO_DEBUG-True}
- AUTHENTICATE_WITH_ORG_ID=True
- REDIS_HOST=${REDIS_HOST-rbac_redis}
- PRINCIPAL_PROXY_SERVICE_PROTOCOL=${PRINCIPAL_PROXY_SERVICE_PROTOCOL-https}
- PRINCIPAL_PROXY_SERVICE_PORT=${PRINCIPAL_PROXY_SERVICE_PORT-443}
Expand Down
4 changes: 3 additions & 1 deletion docs/source/specs/openapi.json
Original file line number Diff line number Diff line change
Expand Up @@ -2994,9 +2994,10 @@
"type": "string",
"enum": [
"account",
"org_id",
"principal"
],
"default": "account"
"default": "org_id"
}
},
"NameMatchCriteria": {
Expand Down Expand Up @@ -3692,6 +3693,7 @@
"CrossAccountRequestIn": {
"required": [
"target_account",
"target_org",
"start_date",
"end_date",
"roles"
Expand Down
6 changes: 1 addition & 5 deletions rbac/api/cross_access/access_control.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,6 @@
# along with this program. If not, see <https://www.gnu.org/licenses/>.
#
"""Defines the Admin Access Permissions class."""
from django.conf import settings
from django.urls import reverse
from management.utils import validate_and_get_key
from rest_framework import permissions
Expand All @@ -42,10 +41,7 @@ def has_permission(self, request, view):
return True

# For list
if settings.AUTHENTICATE_WITH_ORG_ID:
query_by = validate_and_get_key(request.query_params, QUERY_BY_KEY, VALID_QUERY_BY_KEY, ORG_ID)
else:
query_by = validate_and_get_key(request.query_params, QUERY_BY_KEY, VALID_QUERY_BY_KEY, ACCOUNT)
query_by = validate_and_get_key(request.query_params, QUERY_BY_KEY, VALID_QUERY_BY_KEY, ORG_ID)
if query_by == ACCOUNT or query_by == ORG_ID:
return request.user.admin
elif query_by == USER_ID:
Expand Down
20 changes: 7 additions & 13 deletions rbac/api/cross_access/util.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,13 +18,11 @@
"""Handler for cross-account request clean up."""
import logging

from django.conf import settings
from django.db.models import Q
from django.utils import timezone
from management.models import Principal

from api.models import CrossAccountRequest, Tenant
from api.serializers import create_tenant_name

logger = logging.getLogger(__name__) # pylint: disable=invalid-name

Expand All @@ -45,25 +43,21 @@ def check_cross_request_expiry():
logger.info("Completed clean up of %d cross-account requests, %d expired.", len(cars), len(expired_cars))


def create_cross_principal(user_id, target_account=None, target_org=None):
def create_cross_principal(user_id, target_org=None):
"""Create a cross account principal in the target account."""
# Principal would have the pattern acctxxx-123456.
if settings.AUTHENTICATE_WITH_ORG_ID:
principal_name = get_cross_principal_name(target_org, user_id)
associate_tenant = Tenant.objects.get(org_id=target_org)
else:
principal_name = get_cross_principal_name(target_account, user_id)
tenant_name = create_tenant_name(target_account)
associate_tenant = Tenant.objects.get(tenant_name=tenant_name)
principal_name = get_cross_principal_name(target_org, user_id)
associate_tenant = Tenant.objects.get(org_id=target_org)

# Create the principal in public schema
cross_account_principal = create_principal_with_tenant(principal_name, associate_tenant)

return cross_account_principal


def get_cross_principal_name(target_account, user_id):
"""Get cross-account principal string from account and UID."""
return f"{target_account}-{user_id}"
def get_cross_principal_name(target_org, user_id):
"""Get cross-account principal string from org_id and UID."""
return f"{target_org}-{user_id}"


def create_principal_with_tenant(principal_name, associate_tenant):
Expand Down
120 changes: 36 additions & 84 deletions rbac/api/cross_access/view.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,6 @@
#

"""View for cross access request."""
from django.conf import settings
from django.db.models import Q
from django.utils import timezone
from django_filters import rest_framework as filters
Expand All @@ -34,18 +33,13 @@
from api.cross_access.serializer import CrossAccountRequestDetailSerializer, CrossAccountRequestSerializer
from api.cross_access.util import create_cross_principal
from api.models import CrossAccountRequest, Tenant
from api.serializers import create_tenant_name

QUERY_BY_KEY = "query_by"
ACCOUNT = "target_account"
ORG_ID = "target_org"
USER_ID = "user_id"
if settings.AUTHENTICATE_WITH_ORG_ID:
PARAMS_FOR_CREATION = ["target_org", "start_date", "end_date", "roles"]
VALID_QUERY_BY_KEY = [ORG_ID, USER_ID]
else:
PARAMS_FOR_CREATION = ["target_org", "target_account", "start_date", "end_date", "roles"]
VALID_QUERY_BY_KEY = [ACCOUNT, USER_ID]
PARAMS_FOR_CREATION = ["target_org", "start_date", "end_date", "roles"]
VALID_QUERY_BY_KEY = [ORG_ID, USER_ID]

VALID_PATCH_FIELDS = ["start_date", "end_date", "roles", "status"]

PROXY = PrincipalProxy()
Expand Down Expand Up @@ -86,7 +80,7 @@ def status_filter(self, queryset, field, values):

class Meta:
model = CrossAccountRequest
fields = ["account", "approved_only", "status"]
fields = ["account", "org_id", "approved_only", "status"]


class CrossAccountRequestViewSet(
Expand All @@ -112,12 +106,8 @@ def get_queryset(self):
if self.request.method in ["PATCH", "PUT"]:
return CrossAccountRequest.objects.all()

if settings.AUTHENTICATE_WITH_ORG_ID:
if validate_and_get_key(self.request.query_params, QUERY_BY_KEY, VALID_QUERY_BY_KEY, ORG_ID) == ORG_ID:
return CrossAccountRequest.objects.filter(target_org=self.request.user.org_id)
else:
if validate_and_get_key(self.request.query_params, QUERY_BY_KEY, VALID_QUERY_BY_KEY, ACCOUNT) == ACCOUNT:
return CrossAccountRequest.objects.filter(target_account=self.request.user.account)
if validate_and_get_key(self.request.query_params, QUERY_BY_KEY, VALID_QUERY_BY_KEY, ORG_ID) == ORG_ID:
return CrossAccountRequest.objects.filter(target_org=self.request.user.org_id)

return CrossAccountRequest.objects.filter(user_id=self.request.user.user_id)

Expand All @@ -144,12 +134,8 @@ def list(self, request, *args, **kwargs):

result = super().list(request=request, args=args, kwargs=kwargs)
# The approver's view requires requester's info such as first name, last name, email address.
if settings.AUTHENTICATE_WITH_ORG_ID:
if validate_and_get_key(self.request.query_params, QUERY_BY_KEY, VALID_QUERY_BY_KEY, ORG_ID) == ORG_ID:
return self.replace_user_id_with_info(result)
else:
if validate_and_get_key(self.request.query_params, QUERY_BY_KEY, VALID_QUERY_BY_KEY, ACCOUNT) == ACCOUNT:
return self.replace_user_id_with_info(result)
if validate_and_get_key(self.request.query_params, QUERY_BY_KEY, VALID_QUERY_BY_KEY, ORG_ID) == ORG_ID:
return self.replace_user_id_with_info(result)
return result

def partial_update(self, request, *args, **kwargs):
Expand All @@ -175,10 +161,8 @@ def update(self, request, *args, **kwargs):

current = self.get_object()
self.check_update_permission(request, current)
if settings.AUTHENTICATE_WITH_ORG_ID:
request.data["target_org"] = current.target_org
else:
request.data["target_account"] = current.target_account

request.data["target_org"] = current.target_org

self.validate_and_format_input(request.data)

Expand All @@ -188,44 +172,28 @@ def retrieve(self, request, *args, **kwargs):
"""Retrieve cross account requests by request_id."""
result = super().retrieve(request=request, args=args, kwargs=kwargs)

if settings.AUTHENTICATE_WITH_ORG_ID:
if validate_and_get_key(self.request.query_params, QUERY_BY_KEY, VALID_QUERY_BY_KEY, ORG_ID) == ORG_ID:
user_id = result.data.pop("user_id")
principal = PROXY.request_filtered_principals(
[user_id], account=None, org_id=None, options={"query_by": "user_id", "return_id": True}
).get("data")[0]

# Replace the user_id with user's info
result.data.update(
{
"first_name": principal["first_name"],
"last_name": principal["last_name"],
"email": principal["email"],
}
)
else:
if validate_and_get_key(self.request.query_params, QUERY_BY_KEY, VALID_QUERY_BY_KEY, ACCOUNT) == ACCOUNT:
user_id = result.data.pop("user_id")
principal = PROXY.request_filtered_principals(
[user_id], account=None, org_id=None, options={"query_by": "user_id", "return_id": True}
).get("data")[0]

# Replace the user_id with user's info
result.data.update(
{
"first_name": principal["first_name"],
"last_name": principal["last_name"],
"email": principal["email"],
}
)
if validate_and_get_key(self.request.query_params, QUERY_BY_KEY, VALID_QUERY_BY_KEY, ORG_ID) == ORG_ID:
user_id = result.data.pop("user_id")
principal = PROXY.request_filtered_principals(
[user_id], org_id=None, options={"query_by": "user_id", "return_id": True}
).get("data")[0]

# Replace the user_id with user's info
result.data.update(
{
"first_name": principal["first_name"],
"last_name": principal["last_name"],
"email": principal["email"],
}
)
return result

def replace_user_id_with_info(self, result):
"""Replace user id with user's info."""
# Get principals through user_ids from BOP
user_ids = [element["user_id"] for element in result.data["data"]]
bop_resp = PROXY.request_filtered_principals(
user_ids, account=None, org_id=None, options={"query_by": "user_id", "return_id": True}
user_ids, org_id=None, options={"query_by": "user_id", "return_id": True}
)

# Make a mapping: user_id => principal
Expand Down Expand Up @@ -257,30 +225,16 @@ def validate_and_format_input(self, request_data):
if not request_data.__contains__(field):
self.throw_validation_error("cross-account-request", f"Field {field} must be specified.")

if settings.AUTHENTICATE_WITH_ORG_ID:
target_org = request_data.get("target_org")
if target_org == self.request.user.org_id:
self.throw_validation_error(
"cross-account-request", "Creating a cross access request for your own org id is not allowed."
)
target_org = request_data.get("target_org")
if target_org == self.request.user.org_id:
self.throw_validation_error(
"cross-account-request", "Creating a cross access request for your own org id is not allowed."
)

try:
Tenant.objects.get(org_id=target_org)
except Tenant.DoesNotExist:
raise self.throw_validation_error("cross-account-request", f"Org ID '{target_org}' does not exist.")
else:
target_account = request_data.get("target_account")
if target_account == self.request.user.account:
self.throw_validation_error(
"cross-account-request", "Creating a cross access request for your own account is not allowed."
)
try:
tenant_name = create_tenant_name(target_account)
Tenant.objects.get(tenant_name=tenant_name)
except Tenant.DoesNotExist:
raise self.throw_validation_error(
"cross-account-request", f"Account '{target_account}' does not exist."
)
try:
Tenant.objects.get(org_id=target_org)
except Tenant.DoesNotExist:
raise self.throw_validation_error("cross-account-request", f"Org ID '{target_org}' does not exist.")

request_data["roles"] = self.format_roles(request_data.get("roles"))
request_data["user_id"] = self.request.user.user_id
Expand Down Expand Up @@ -309,15 +263,13 @@ def update_status(self, car, status):
"""Update the status of a cross-account-request."""
car.status = status
if status == "approved":
create_cross_principal(car.user_id, target_account=car.target_account, target_org=car.target_org)
create_cross_principal(car.user_id, target_org=car.target_org)

car.save()

def check_patch_permission(self, request, update_obj):
"""Check if user has right to patch cross access request."""
if (settings.AUTHENTICATE_WITH_ORG_ID and request.user.org_id == update_obj.target_org) or (
not settings.AUTHENTICATE_WITH_ORG_ID and request.user.account == update_obj.target_account
):
if request.user.org_id == update_obj.target_org:
"""For approvers updating requests coming to them, only org admins
may update status from pending/approved/denied to approved/denied.
"""
Expand Down
6 changes: 1 addition & 5 deletions rbac/api/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,6 @@
# along with this program. If not, see <https://www.gnu.org/licenses/>.
#
"""API models for import organization."""
from django.conf import settings
from django.db import models
from django.db.models import Q

Expand Down Expand Up @@ -46,10 +45,7 @@ class Tenant(models.Model):

def __str__(self):
"""Get string representation of Tenant."""
if settings.AUTHENTICATE_WITH_ORG_ID:
return f"Tenant ({self.org_id})"
else:
return f"Tenant ({self.tenant_name})"
return f"Tenant ({self.org_id})"


class TenantAwareModel(models.Model):
Expand Down
2 changes: 0 additions & 2 deletions rbac/internal/integration/chrome_handlers.py
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,5 @@ def build_chrome_message(event_type, uuid, org_id):

def send_chrome_message(event_type, uuid, org_id):
"""Build and send chrome message."""
if not settings.AUTHENTICATE_WITH_ORG_ID:
return
chrome_message = build_chrome_message(event_type, uuid, org_id)
chrome_producer.send_kafka_message(chrome_topic, chrome_message)
9 changes: 3 additions & 6 deletions rbac/internal/integration/sync_handlers.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,19 +33,16 @@
message_template = json.load(template)


def build_sync_message(event_type, payload, account_id=None, org_id=None):
def build_sync_message(event_type, payload):
"""Create message based on template."""
message = message_template
if settings.AUTHENTICATE_WITH_ORG_ID:
message["org_id"] = org_id
message["event_type"] = event_type
message["timestamp"] = datetime.now().isoformat()
message["account_id"] = account_id
message["events"][0]["payload"] = payload
return message


def send_sync_message(event_type, payload, account_id=None, org_id=None):
def send_sync_message(event_type, payload):
"""Build and send external service sync message."""
sync_message = build_sync_message(event_type, payload, account_id, org_id)
sync_message = build_sync_message(event_type, payload)
sync_producer.send_kafka_message(sync_topic, sync_message)
Loading

0 comments on commit 5965485

Please sign in to comment.