Example #1
0
async def api_add_user_to_group_or_raise(group_name, member_name, actor):
    try:
        group_info = await auth.get_group_info(group_name, members=False)
    except Exception:
        raise NoGroupsException("Unable to retrieve the specified group")

    actor_groups = await auth.get_groups(actor)
    can_add_remove_members = can_modify_members(actor, actor_groups, group_info)

    if not can_add_remove_members:
        raise UnauthorizedToAccess("Unauthorized to modify members of this group.")

    try:
        await add_user_to_group(member_name, group_name, actor)
    except HttpError as e:
        # Inconsistent GG API error - ignore failure for user already existing
        if e.resp.reason == "duplicate":
            pass
    except UserAlreadyAMemberOfGroupException:
        pass
    except BulkAddPrevented:
        dynamo_handler = UserDynamoHandler(actor)
        dynamo_handler.add_request(
            member_name,
            group_name,
            f"{actor} requesting on behalf of {member_name} from a bulk operation",
            updated_by=actor,
        )
        return "REQUESTED"

    return "ADDED"
Example #2
0
    async def authorization_flow(self,
                                 user: str = None,
                                 console_only: bool = True,
                                 refresh_cache: bool = False) -> None:
        """Perform high level authorization flow."""
        self.eligible_roles = []
        self.eligible_accounts = []
        self.request_uuid = str(uuid.uuid4())
        refresh_cache = (self.request.arguments.get(
            "refresh_cache", [False])[0] or refresh_cache)
        attempt_sso_authn = await self.attempt_sso_authn()

        refreshed_user_roles_from_cache = False

        if not refresh_cache and config.get(
                "dynamic_config.role_cache.always_refresh_roles_cache", False):
            refresh_cache = True

        self.red = await RedisHandler().redis()
        self.ip = self.get_request_ip()
        self.user = user
        self.groups = None
        self.user_role_name = None
        self.auth_cookie_expiration = 0

        log_data = {
            "function": "Basehandler.authorization_flow",
            "ip": self.ip,
            "request_path": self.request.uri,
            "user-agent": self.request.headers.get("User-Agent"),
            "request_id": self.request_uuid,
            "message": "Incoming request",
        }

        log.debug(log_data)

        # Check to see if user has a valid auth cookie
        if config.get("auth_cookie_name", "consoleme_auth"):
            auth_cookie = self.get_cookie(
                config.get("auth_cookie_name", "consoleme_auth"))

            # Validate auth cookie and use it to retrieve group information
            if auth_cookie:
                res = await validate_and_return_jwt_token(auth_cookie)
                if res and isinstance(res, dict):
                    self.user = res.get("user")
                    self.groups = res.get("groups")
                    self.auth_cookie_expiration = res.get("exp")

        if not self.user:
            # Check for development mode and a configuration override that specify the user and their groups.
            if config.get("development") and config.get(
                    "_development_user_override"):
                self.user = config.get("_development_user_override")
            if config.get("development") and config.get(
                    "_development_groups_override"):
                self.groups = config.get("_development_groups_override")

        if not self.user:
            # SAML flow. If user has a JWT signed by ConsoleMe, and SAML is enabled in configuration, user will go
            # through this flow.

            if config.get("auth.get_user_by_saml",
                          False) and attempt_sso_authn:
                res = await authenticate_user_by_saml(self)
                if not res:
                    if (self.request.uri != "/saml/acs"
                            and not self.request.uri.startswith("/auth?")):
                        raise SilentException(
                            "Unable to authenticate the user by SAML. "
                            "Redirecting to authentication endpoint")
                    return

        if not self.user:
            if config.get("auth.get_user_by_oidc",
                          False) and attempt_sso_authn:
                res = await authenticate_user_by_oidc(self)
                if not res:
                    raise SilentException(
                        "Unable to authenticate the user by OIDC. "
                        "Redirecting to authentication endpoint")
                if res and isinstance(res, dict):
                    self.user = res.get("user")
                    self.groups = res.get("groups")

        if not self.user:
            if config.get("auth.get_user_by_aws_alb_auth", False):
                res = await authenticate_user_by_alb_auth(self)
                if not res:
                    raise Exception(
                        "Unable to authenticate the user by ALB Auth")
                if res and isinstance(res, dict):
                    self.user = res.get("user")
                    self.groups = res.get("groups")

        if not self.user:
            # Username/Password authn flow
            if config.get("auth.get_user_by_password", False):
                after_redirect_uri = self.request.arguments.get(
                    "redirect_url", [""])[0]
                if after_redirect_uri and isinstance(after_redirect_uri,
                                                     bytes):
                    after_redirect_uri = after_redirect_uri.decode("utf-8")
                self.set_status(403)
                self.write({
                    "type":
                    "redirect",
                    "redirect_url":
                    f"/login?redirect_after_auth={after_redirect_uri}",
                    "reason":
                    "unauthenticated",
                    "message":
                    "User is not authenticated. Redirect to authenticate",
                })
                await self.finish()
                raise SilentException(
                    "Redirecting user to authenticate by username/password.")

        if not self.user:
            try:
                # Get user. Config options can specify getting username from headers or
                # OIDC, but custom plugins are also allowed to override this.
                self.user = await auth.get_user(headers=self.request.headers)
                if not self.user:
                    raise NoUserException(
                        f"User not detected. Headers: {self.request.headers}")
                log_data["user"] = self.user
            except NoUserException:
                self.clear()
                self.set_status(403)

                stats.count(
                    "Basehandler.authorization_flow.no_user_detected",
                    tags={
                        "request_path": self.request.uri,
                        "ip": self.ip,
                        "user_agent": self.request.headers.get("User-Agent"),
                    },
                )
                log_data["message"] = "No user detected. Check configuration."
                log.error(log_data)
                await self.finish(log_data["message"])
                raise

        self.contractor = config.config_plugin().is_contractor(self.user)

        if config.get("auth.cache_user_info_server_side",
                      True) and not refresh_cache:
            try:
                cache_r = self.red.get(
                    f"USER-{self.user}-CONSOLE-{console_only}")
            except redis.exceptions.ConnectionError:
                cache_r = None
            if cache_r:
                log_data["message"] = "Loading from cache"
                log.debug(log_data)
                cache = json.loads(cache_r)
                self.groups = cache.get("groups")
                self.eligible_roles = cache.get("eligible_roles")
                self.eligible_accounts = cache.get("eligible_accounts")
                self.user_role_name = cache.get("user_role_name")
                refreshed_user_roles_from_cache = True

        try:
            if not self.groups:
                self.groups = await auth.get_groups(
                    self.user, headers=self.request.headers)
            if not self.groups:
                raise NoGroupsException(
                    f"Groups not detected. Headers: {self.request.headers}")

        except NoGroupsException:
            stats.count("Basehandler.authorization_flow.no_groups_detected")
            log_data["message"] = "No groups detected. Check configuration."
            log.error(log_data)

        # Set Per-User Role Name (This logic is not used in OSS deployment)
        if (config.get("user_roles.opt_in_group")
                and config.get("user_roles.opt_in_group") in self.groups):
            # Get or create user_role_name attribute
            self.user_role_name = await auth.get_or_create_user_role_name(
                self.user)

        self.eligible_roles = await group_mapping.get_eligible_roles(
            self.user,
            self.groups,
            self.user_role_name,
            console_only=console_only)

        if not self.eligible_roles:
            log_data[
                "message"] = "No eligible roles detected for user. But letting them continue"
            log.error(log_data)
        log_data["eligible_roles"] = len(self.eligible_roles)

        if not self.eligible_accounts:
            try:
                self.eligible_accounts = await group_mapping.get_eligible_accounts(
                    self.eligible_roles)
                log_data["eligible_accounts"] = len(self.eligible_accounts)
                log_data["message"] = "Successfully authorized user."
                log.debug(log_data)
            except Exception:
                stats.count("Basehandler.authorization_flow.exception")
                log.error(log_data, exc_info=True)
                raise
        if (config.get("auth.cache_user_info_server_side", True)
                and self.groups
                # Only set role cache if we didn't retrieve user's existing roles from cache
                and not refreshed_user_roles_from_cache):
            try:
                self.red.setex(
                    f"USER-{self.user}-CONSOLE-{console_only}",
                    config.get("dynamic_config.role_cache.cache_expiration",
                               60),
                    json.dumps({
                        "groups": self.groups,
                        "eligible_roles": self.eligible_roles,
                        "eligible_accounts": self.eligible_accounts,
                        "user_role_name": self.user_role_name,
                    }),
                )
            except redis.exceptions.ConnectionError:
                pass
        if (config.get("auth.set_auth_cookie")
                and config.get("auth_cookie_name", "consoleme_auth")
                and not self.get_cookie(
                    config.get("auth_cookie_name", "consoleme_auth"))):
            expiration = datetime.utcnow().replace(
                tzinfo=pytz.UTC) + timedelta(
                    minutes=config.get("jwt.expiration_minutes", 60))

            encoded_cookie = await generate_jwt_token(self.user,
                                                      self.groups,
                                                      exp=expiration)
            self.set_cookie(
                config.get("auth_cookie_name", "consoleme_auth"),
                encoded_cookie,
                expires=expiration,
                secure=config.get(
                    "auth.cookie.secure",
                    "https://" in config.get("url"),
                ),
                httponly=config.get("auth.cookie.httponly", True),
                samesite=config.get("auth.cookie.samesite", True),
            )
        if self.tracer:
            await self.tracer.set_additional_tags({"USER": self.user})