def can_modify_members(user: str, user_groups: List[str], group_info: Optional[Any]) -> bool: # No users can modify members on restricted groups if group_info and group_info.restricted: return False if can_admin_all(user, user_groups): return True if is_in_group( user, user_groups, [ *config.get("groups.can_admin_restricted", []), *config.get("dynamic_config.groups.can_admin_restricted", []), ], ): return True if is_in_group( user, user_groups, [ *config.get("groups.can_modify_members", []), *config.get("dynamic_config.groups.can_modify_members", []), ], ): return True return False
def can_edit_attributes( user: str, user_groups: List[str], group_info: Optional[Any] ) -> bool: if can_admin_all(user, user_groups): return True if is_in_group(user, user_groups, config.get("groups.can_admin_restricted", [])): return True if is_in_group(user, user_groups, config.get("groups.can_edit_attributes", [])): return True return False
def can_admin_policies(user: str, user_groups: List[str]) -> bool: if can_admin_all(user, user_groups): return True if is_in_group(user, user_groups, config.get("groups.can_admin_policies", [])): return True return False
def can_admin_all(user: str, user_groups: List[str]): application_admin = config.get("application_admin") if application_admin: if user == application_admin or application_admin in user_groups: return True if is_in_group(user, user_groups, config.get("groups.can_admin", [])): return True return False
def can_edit_dynamic_config( user: str, user_groups: List[str], ) -> bool: if can_admin_all(user, user_groups): return True if is_in_group(user, user_groups, config.get("groups.can_edit_config", [])): return True return False
def can_edit_sensitive_attributes(user: str, user_groups: List[str], group_info: Optional[Any]) -> bool: if can_admin_all(user, user_groups): return True if is_in_group( user, user_groups, [ *config.get("groups.can_edit_sensitive_attributes", []), *config.get("dynamic_config.groups.can_edit_sensitive_attributes", []), ], ): return True return False
def can_delete_roles( user: str, user_groups: List[str], ) -> bool: if can_admin_all(user, user_groups): return True if is_in_group( user, user_groups, [ *config.get("groups.can_delete_roles", []), *config.get("dynamic_config.groups.can_delete_roles", []), ], ): return True return False
def can_delete_iam_principals( user: str, user_groups: List[str], ) -> bool: if can_admin_all(user, user_groups): return True if is_in_group( user, user_groups, [ # TODO: Officially deprecate groups.can_delete_roles config key *config.get("groups.can_delete_roles", []), # TODO: Officially deprecate dynamic_config.groups.can_delete_roles config key *config.get("dynamic_config.groups.can_delete_roles", []), *config.get("groups.can_delete_iam_principals", []), *config.get("dynamic_config.groups.can_delete_iam_principals", []), ], ): return True return False
async def get(self): """ Provide information about site configuration for the frontend :return: """ is_contractor = config.config_plugin().is_contractor(self.user) site_config = { "consoleme_logo": await get_random_security_logo(), "google_tracking_uri": config.get("google_analytics.tracking_url"), "documentation_url": config.get("documentation_page"), "support_contact": config.get("support_contact"), "support_chat_url": config.get("support_chat_url"), "security_logo": config.get("security_logo.image"), "security_url": config.get("security_logo.url"), } user_profile = { "site_config": site_config, "user": self.user, "can_logout": config.get("auth.set_auth_cookie"), "is_contractor": is_contractor, "employee_photo_url": config.config_plugin().get_employee_photo_url(self.user), "employee_info_url": config.config_plugin().get_employee_info_url(self.user), "authorization": { "can_edit_policies": can_admin_policies(self.user, self.groups), "can_create_roles": can_create_roles(self.user, self.groups), "can_delete_roles": can_delete_roles(self.user, self.groups), }, "pages": { "header": { "custom_header_message_title": config.get("headers.custom_header_message.title", ""), "custom_header_message_text": config.get("headers.custom_header_message.text", ""), }, "groups": { "enabled": config.get("headers.group_access.enabled", False) }, "users": { "enabled": config.get("headers.group_access.enabled", False) }, "policies": { "enabled": config.get("headers.policies.enabled", True) and not is_contractor }, "self_service": { "enabled": config.get("enable_self_service", True) and not is_contractor }, "api_health": { "enabled": is_in_group( self.user, self.groups, config.get("groups.can_edit_health_alert", []), ) }, "audit": { "enabled": is_in_group(self.user, self.groups, config.get("groups.can_audit", [])) }, "config": { "enabled": can_edit_dynamic_config(self.user, self.groups) }, }, "accounts": await get_account_id_to_name_mapping(), } self.set_header("Content-Type", "application/json") self.write(user_profile)
async def get(self): """ Provide information about site configuration for the frontend :return: """ is_contractor = config.config_plugin().is_contractor(self.user) site_config = { "consoleme_logo": await get_random_security_logo(), "google_analytics": { "tracking_id": config.get("google_analytics.tracking_id"), "options": config.get("google_analytics.options", {}), }, "documentation_url": config.get( "documentation_page", "https://hawkins.gitbook.io/consoleme/" ), "support_contact": config.get("support_contact"), "support_chat_url": config.get( "support_chat_url", "https://discord.com/invite/nQVpNGGkYu" ), "security_logo": config.get("security_logo.image"), "security_url": config.get("security_logo.url"), # If site_config.landing_url is set, users will be redirected to the landing URL after authenticating # on the frontend. "landing_url": config.get("site_config.landing_url"), "temp_policy_support": config.get("policies.temp_policy_support"), "notifications": { "enabled": config.get("site_config.notifications.enabled"), "request_interval": config.get( "site_config.notifications.request_interval", 60 ), }, "cloudtrail_denies_policy_generation": config.get( "celery.cache_cloudtrail_denies.enabled", False ), } custom_page_header: Dict[str, str] = await get_custom_page_header( self.user, self.groups ) user_profile = { "site_config": site_config, "user": self.user, "can_logout": config.get("auth.set_auth_cookie", False), "is_contractor": is_contractor, "employee_photo_url": config.config_plugin().get_employee_photo_url( self.user ), "employee_info_url": config.config_plugin().get_employee_info_url( self.user ), "authorization": { "can_edit_policies": can_admin_policies(self.user, self.groups), "can_create_roles": can_create_roles(self.user, self.groups), "can_delete_iam_principals": can_delete_iam_principals( self.user, self.groups ), }, "pages": { "header": { "custom_header_message_title": custom_page_header.get( "custom_header_message_title", "" ), "custom_header_message_text": custom_page_header.get( "custom_header_message_text", "" ), "custom_header_message_route": custom_page_header.get( "custom_header_message_route", "" ), }, "groups": { "enabled": config.get("headers.group_access.enabled", False) }, "users": {"enabled": config.get("headers.group_access.enabled", False)}, "policies": { "enabled": config.get("headers.policies.enabled", True) and not is_contractor }, "self_service": { "enabled": config.get("enable_self_service", True) and not is_contractor }, "api_health": { "enabled": is_in_group( self.user, self.groups, config.get("groups.can_edit_health_alert", []), ) }, "audit": { "enabled": is_in_group( self.user, self.groups, config.get("groups.can_audit", []) ) }, "config": {"enabled": can_edit_dynamic_config(self.user, self.groups)}, }, "accounts": await get_account_id_to_name_mapping(), } self.set_header("Content-Type", "application/json") self.write(user_profile)
async def put(self): """ Allows an "authorized user" (Any user the notification is intended for) to mark the notification as read/unread or hidden/unhidden for themselves or all other notification recipients :return: """ change = ConsoleMeNotificationUpdateRequest.parse_raw( self.request.body) errors = [] for untrusted_notification in change.notifications: notification = await fetch_notification( untrusted_notification.predictable_id) if not notification: errors.append("Unable to find matching notification") continue authorized = is_in_group(self.user, self.groups, notification.users_or_groups) if not authorized: errors.append( f"Unauthorized because user is not associated with notification: {notification.predictable_id}" ) continue if (change.action == ConsoleMeNotificationUpdateAction. toggle_read_for_current_user): if self.user in notification.read_by_users: # Mark as unread notification.read_by_users.remove(self.user) else: # Mark as read notification.read_by_users.append(self.user) elif (change.action == ConsoleMeNotificationUpdateAction.toggle_read_for_all_users): # Mark or unmark notification as `read_by_all`. If unmarked, # ConsoleMe will fall back to `notification.read_by_user` to determine if # a given user has read the notification notification.read_by_all = not notification.read_by_all elif (change.action == ConsoleMeNotificationUpdateAction. toggle_hidden_for_current_user): if self.user in notification.hidden_for_users: # Unmark as hidden notification.hidden_for_users.remove(self.user) else: # Mark as hidden notification.hidden_for_users.append(self.user) elif (change.action == ConsoleMeNotificationUpdateAction. toggle_hidden_for_all_users): # Mark or unmark as "Hidden for all users". If unmarked, falls back to `hidden_for_users.read_by_user` # to determine whether to show the notification to a given user notification.hidden_for_all = not notification.hidden_for_all else: raise Exception("Unknown or unsupported change action.") await write_notification(notification) try: # Retrieve and return updated notifications for user notification_response: GetNotificationsForUserResponse = ( await get_notifications_for_user(self.user, self.groups, force_refresh=True)) notifications: List[ ConsoleMeUserNotification] = notification_response.notifications response = WebResponse( status="success", status_code=200, data={ "unreadNotificationCount": notification_response.unread_count, "notifications": notifications, }, ) self.write(response.json()) except Exception as e: sentry_sdk.capture_exception() self.set_status(500) response = WebResponse(status=Status2.error, status_code=500, errors=[str(e)], data=[]) self.write(response.json()) return