updates
This commit is contained in:
490
Backend/venv/lib/python3.12/site-packages/safety/init/main.py
Normal file
490
Backend/venv/lib/python3.12/site-packages/safety/init/main.py
Normal file
@@ -0,0 +1,490 @@
|
||||
import logging
|
||||
import sys
|
||||
import uuid
|
||||
from rich.prompt import Prompt
|
||||
import typer
|
||||
from rich.console import Console
|
||||
|
||||
from safety.events.utils.emission import emit_firewall_setup_completed
|
||||
from safety.init.render import progressive_print
|
||||
from safety.util import clean_project_id
|
||||
|
||||
from ..tool import configure_system, configure_alias
|
||||
from ..codebase_utils import load_unverified_project_from_config, save_project_info
|
||||
|
||||
from .constants import (
|
||||
MSG_AUTH_PROMPT,
|
||||
MSG_NEED_AUTHENTICATION,
|
||||
MSG_SETUP_INCOMPLETE,
|
||||
MSG_SETUP_PACKAGE_FIREWALL_NOTE_STATUS,
|
||||
MSG_SETUP_PACKAGE_FIREWALL_RESULT,
|
||||
)
|
||||
|
||||
from pathlib import Path
|
||||
from safety_schemas.models import ProjectModel
|
||||
from safety_schemas.models.events.types import ToolType
|
||||
from safety.scan.util import GIT
|
||||
from ..auth.utils import SafetyAuthSession
|
||||
|
||||
from typing import TYPE_CHECKING, Any, Literal, Optional, Tuple
|
||||
from safety.scan.render import (
|
||||
print_wait_project_verification,
|
||||
prompt_project_id,
|
||||
prompt_link_project,
|
||||
)
|
||||
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from ..codebase_utils import UnverifiedProjectModel
|
||||
from ..models import SafetyCLI
|
||||
from .types import FirewallConfigStatus
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
def check_project(
|
||||
ctx: typer.Context,
|
||||
session: SafetyAuthSession,
|
||||
console: Console,
|
||||
unverified_project: "UnverifiedProjectModel",
|
||||
git_origin: Optional[str],
|
||||
ask_project_id: bool = False,
|
||||
) -> dict:
|
||||
"""
|
||||
Check the project against the session and stage, verifying the project if necessary.
|
||||
|
||||
Args:
|
||||
console: The console for output.
|
||||
ctx (typer.Context): The context of the Typer command.
|
||||
session (SafetyAuthSession): The authentication session.
|
||||
unverified_project (UnverifiedProjectModel): The unverified project model.
|
||||
stage (Stage): The current stage.
|
||||
git_origin (Optional[str]): The Git origin URL.
|
||||
ask_project_id (bool): Whether to prompt for the project ID.
|
||||
|
||||
Returns:
|
||||
dict: The result of the project check.
|
||||
"""
|
||||
stage = ctx.obj.auth.stage
|
||||
source = ctx.obj.telemetry.safety_source if ctx.obj.telemetry else None
|
||||
data = {"scan_stage": stage, "safety_source": source}
|
||||
|
||||
PRJ_SLUG_KEY = "project_slug"
|
||||
PRJ_SLUG_SOURCE_KEY = "project_slug_source"
|
||||
PRJ_GIT_ORIGIN_KEY = "git_origin"
|
||||
|
||||
if git_origin:
|
||||
data[PRJ_GIT_ORIGIN_KEY] = git_origin
|
||||
|
||||
if unverified_project.id:
|
||||
data[PRJ_SLUG_KEY] = unverified_project.id
|
||||
|
||||
if unverified_project.created:
|
||||
data[PRJ_SLUG_SOURCE_KEY] = ".safety-project.ini"
|
||||
else:
|
||||
data[PRJ_SLUG_SOURCE_KEY] = "user"
|
||||
elif not git_origin or ask_project_id:
|
||||
fallback_id = unverified_project.project_path.parent.name
|
||||
|
||||
if not fallback_id:
|
||||
# Sometimes the parent directory is empty, so we generate
|
||||
# a random ID
|
||||
fallback_id = str(uuid.uuid4())[:10]
|
||||
|
||||
fallback_id = clean_project_id(fallback_id)
|
||||
|
||||
if ask_project_id:
|
||||
id = prompt_project_id(console, fallback_id)
|
||||
else:
|
||||
id = fallback_id
|
||||
|
||||
unverified_project.id = id
|
||||
|
||||
data[PRJ_SLUG_KEY] = unverified_project.id
|
||||
data[PRJ_SLUG_SOURCE_KEY] = "user"
|
||||
|
||||
status = print_wait_project_verification(
|
||||
console,
|
||||
data[PRJ_SLUG_KEY] if data.get(PRJ_SLUG_KEY, None) else "-",
|
||||
(session.check_project, data),
|
||||
on_error_delay=1,
|
||||
)
|
||||
|
||||
return status
|
||||
|
||||
|
||||
def verify_project(
|
||||
console: Console,
|
||||
ctx: typer.Context,
|
||||
session: SafetyAuthSession,
|
||||
unverified_project: "UnverifiedProjectModel",
|
||||
git_origin: Optional[str],
|
||||
create_if_missing: bool = True,
|
||||
link_behavior: Literal["always", "prompt", "never"] = "prompt",
|
||||
prompt_for_name: bool = False,
|
||||
) -> Tuple[bool, Optional[str]]:
|
||||
"""
|
||||
Verify the project, linking it if necessary and saving the verified project information.
|
||||
|
||||
Args:
|
||||
console: The console for output.
|
||||
ctx (typer.Context): The context of the Typer command.
|
||||
session (SafetyAuthSession): The authentication session.
|
||||
unverified_project (UnverifiedProjectModel): The unverified project model.
|
||||
git_origin (Optional[str]): The Git origin URL.
|
||||
create_if_missing (bool): Whether to create codebase if it doesn't exist. Defaults to True.
|
||||
link_behavior (Literal["always", "prompt", "never"]): How to handle codebase linking.
|
||||
- "always": Link without prompting
|
||||
- "prompt": Ask user before linking (default)
|
||||
- "never": Don't link, return early if codebase exists
|
||||
|
||||
Returns:
|
||||
Tuple[bool, Optional[str]]: (success, status_message)
|
||||
- (True, "created"): New codebase was created and verified
|
||||
- (True, "linked"): Existing codebase was linked
|
||||
- (True, "found"): Codebase found but not linked (link_behavior="never")
|
||||
- (False, "not_found"): Codebase not found and create_if_missing=False
|
||||
- (False, None): Verification failed
|
||||
"""
|
||||
|
||||
# Track if we need to ask for project ID (when user declines linking)
|
||||
ask_for_project_id = False
|
||||
asked_codebase_asked_before = False
|
||||
|
||||
while True:
|
||||
result = check_project(
|
||||
ctx,
|
||||
session,
|
||||
console,
|
||||
unverified_project,
|
||||
git_origin,
|
||||
# Ask for project ID when:
|
||||
# 1. User previously declined linking and we need a new ID
|
||||
# 2. We're in prompt mode and don't have a project ID yet
|
||||
ask_project_id=ask_for_project_id,
|
||||
)
|
||||
|
||||
unverified_slug = result.get("slug")
|
||||
project = result.get("project", None)
|
||||
|
||||
# Handle case where project doesn't exist
|
||||
if not project:
|
||||
if not create_if_missing:
|
||||
return (False, "not_found")
|
||||
# Project will be created - continue to verification
|
||||
project_status = (True, "created")
|
||||
|
||||
if prompt_for_name and not asked_codebase_asked_before:
|
||||
asked_codebase_asked_before = True
|
||||
ask_for_project_id = True
|
||||
continue
|
||||
else:
|
||||
# Project exists - handle based on link_behavior
|
||||
if link_behavior == "never":
|
||||
unverified_project.id = project.get("slug")
|
||||
return (True, "found")
|
||||
elif link_behavior == "always":
|
||||
project_status = (True, "linked")
|
||||
elif link_behavior == "prompt":
|
||||
# Prompt user for confirmation
|
||||
prj_name = project.get("name", None)
|
||||
prj_admin_email = project.get("admin", None)
|
||||
|
||||
should_link = prompt_link_project(
|
||||
prj_name=prj_name, prj_admin_email=prj_admin_email, console=console
|
||||
)
|
||||
|
||||
if should_link:
|
||||
project_status = (True, "linked")
|
||||
else:
|
||||
# User declined linking, ask for new project ID and retry
|
||||
unverified_project.id = None
|
||||
ask_for_project_id = True
|
||||
continue
|
||||
|
||||
# Proceed with project verification
|
||||
verified_prj = print_wait_project_verification(
|
||||
console,
|
||||
unverified_slug, # type: ignore
|
||||
(session.project, {"project_id": unverified_slug}),
|
||||
on_error_delay=1,
|
||||
)
|
||||
|
||||
if (
|
||||
verified_prj
|
||||
and isinstance(verified_prj, dict)
|
||||
and verified_prj.get("slug", None)
|
||||
):
|
||||
save_verified_project(
|
||||
ctx,
|
||||
verified_prj["slug"],
|
||||
verified_prj.get("name", None),
|
||||
unverified_project.project_path,
|
||||
verified_prj.get("url", None),
|
||||
verified_prj.get("organization", None),
|
||||
)
|
||||
return project_status
|
||||
else:
|
||||
# Verification failed
|
||||
return (False, None)
|
||||
|
||||
|
||||
def save_verified_project(
|
||||
ctx: typer.Context,
|
||||
slug: str,
|
||||
name: Optional[str],
|
||||
project_path: Path,
|
||||
url_path: Optional[str],
|
||||
organization: Optional[dict],
|
||||
):
|
||||
"""
|
||||
Save the verified project information to the context and project info file.
|
||||
|
||||
Args:
|
||||
ctx (typer.Context): The context of the Typer command.
|
||||
slug (str): The project slug.
|
||||
name (Optional[str]): The project name.
|
||||
project_path (Path): The project path.
|
||||
url_path (Optional[str]): The project URL path.
|
||||
organization (Optional[str]): The project organization.
|
||||
"""
|
||||
ctx.obj.project = ProjectModel(
|
||||
id=slug, name=name, project_path=project_path, url_path=url_path
|
||||
)
|
||||
|
||||
save_project_info(project=ctx.obj.project, project_path=project_path)
|
||||
|
||||
ctx.obj.org = {}
|
||||
if organization:
|
||||
ctx.obj.org = {
|
||||
"name": organization.get("name"),
|
||||
"slug": organization.get("slug"),
|
||||
}
|
||||
|
||||
|
||||
def create_project(
|
||||
ctx: typer.Context,
|
||||
console: Console,
|
||||
target: Path,
|
||||
unverified_project: Optional["UnverifiedProjectModel"] = None,
|
||||
create_if_missing: bool = True,
|
||||
link_behavior: Literal["always", "prompt", "never"] = "prompt",
|
||||
) -> Tuple[bool, Optional[str]]:
|
||||
"""
|
||||
Loads existing project from the specified target locations or creates a new project.
|
||||
|
||||
Args:
|
||||
ctx: The CLI context
|
||||
console: The console object
|
||||
target (Path): The target location
|
||||
unverified_project (UnverifiedProjectModel): The unverified project model
|
||||
create_if_missing (bool): Whether to create codebase if it doesn't exist
|
||||
link_behavior (Literal["always", "prompt", "never"]): How to handle codebase linking
|
||||
"""
|
||||
# Load .safety-project.ini
|
||||
if not unverified_project:
|
||||
unverified_project = load_unverified_project_from_config(project_root=target)
|
||||
|
||||
session = ctx.obj.auth.client
|
||||
git_data = GIT(root=target).build_git_data()
|
||||
origin = None
|
||||
|
||||
if git_data:
|
||||
origin = git_data.origin
|
||||
|
||||
if ctx.obj.platform_enabled:
|
||||
result = verify_project(
|
||||
console,
|
||||
ctx,
|
||||
session,
|
||||
unverified_project,
|
||||
origin,
|
||||
create_if_missing,
|
||||
link_behavior,
|
||||
)
|
||||
if ctx.obj.project:
|
||||
ctx.obj.project.git = git_data
|
||||
return result
|
||||
else:
|
||||
console.print("Project creation is not supported for your account.")
|
||||
return (False, None)
|
||||
|
||||
|
||||
def launch_auth_if_needed(ctx: typer.Context, console: Console) -> Optional[str]:
|
||||
"""
|
||||
Launch the authentication flow if needed.
|
||||
|
||||
Args:
|
||||
ctx: The CLI context
|
||||
|
||||
Returns:
|
||||
Optional[str]: The organization slug if authentication is successful
|
||||
"""
|
||||
obj: "SafetyCLI" = ctx.obj
|
||||
org_slug = None
|
||||
|
||||
if (
|
||||
not obj.auth
|
||||
or not obj.auth.client
|
||||
or not obj.auth.client.is_using_auth_credentials()
|
||||
):
|
||||
console.print(MSG_NEED_AUTHENTICATION)
|
||||
|
||||
if not console.is_interactive:
|
||||
sys.exit(0)
|
||||
|
||||
auth_choice = Prompt.ask(
|
||||
MSG_AUTH_PROMPT,
|
||||
choices=["r", "l", "R", "L"],
|
||||
default="L",
|
||||
show_choices=False,
|
||||
show_default=True,
|
||||
console=console,
|
||||
).lower()
|
||||
|
||||
from safety.auth.cli import auth_app
|
||||
from safety.cli_util import get_command_for
|
||||
|
||||
login_command = get_command_for(name="login", typer_instance=auth_app)
|
||||
register_command = get_command_for(name="register", typer_instance=auth_app)
|
||||
|
||||
ctx.obj.only_auth_msg = True
|
||||
|
||||
if auth_choice == "r":
|
||||
ctx.invoke(register_command)
|
||||
else:
|
||||
ctx.invoke(login_command)
|
||||
|
||||
try:
|
||||
data = ctx.obj.auth.client.initialize()
|
||||
org_slug = data.get("organization-data", {}).get("slug")
|
||||
except Exception:
|
||||
logger.exception("Unable to load data on the init command")
|
||||
|
||||
return org_slug
|
||||
|
||||
|
||||
def setup_firewall(
|
||||
ctx: Any, status: "FirewallConfigStatus", org_slug: Optional[str], console: Console
|
||||
) -> Tuple[str, bool, bool, "FirewallConfigStatus"]:
|
||||
"""
|
||||
Setup the firewall, this function also handles the output.
|
||||
|
||||
Args:
|
||||
ctx: The CLI context
|
||||
status: The current status of the firewall
|
||||
org_slug: The organization slug
|
||||
console: The console object
|
||||
|
||||
Returns:
|
||||
Tuple[bool, bool, FirewallConfigStatus]: A tuple containing the following:
|
||||
- bool: True if all tools are configured, False otherwise
|
||||
- bool: True if all tools are missing, False otherwise
|
||||
- FirewallConfigStatus: The current status of the firewall
|
||||
"""
|
||||
emoji_check = "[green]:icon_check:[/green]"
|
||||
|
||||
configured_index = configure_system(org_slug)
|
||||
configured_alias = configure_alias()
|
||||
if configured_alias is None:
|
||||
configured_alias = []
|
||||
|
||||
console.line()
|
||||
|
||||
configured = {}
|
||||
if configured_index:
|
||||
configured["index"] = configured_index
|
||||
|
||||
if configured_alias:
|
||||
configured["alias"] = configured_alias
|
||||
|
||||
if any([item[1] for item in configured_index]) or any(
|
||||
[item[1] for item in configured_alias]
|
||||
):
|
||||
for config_type, results in configured.items():
|
||||
for tool_type, path in results:
|
||||
tool_name = tool_type.value
|
||||
index_type = "global"
|
||||
|
||||
tool_config = status[tool_type]
|
||||
is_configured = False
|
||||
|
||||
if path:
|
||||
if config_type == "index":
|
||||
msg = f"Configured {tool_name}’s {index_type} index"
|
||||
else:
|
||||
msg = f"Aliased {tool_name} to safety"
|
||||
|
||||
is_configured = True
|
||||
configured_msg = f"{emoji_check} {msg}"
|
||||
|
||||
path = path.resolve()
|
||||
|
||||
if len(path.parts) > 1:
|
||||
progressive_print([f"{configured_msg} (`{path}`)"])
|
||||
else:
|
||||
progressive_print([configured_msg])
|
||||
else:
|
||||
if config_type == "index":
|
||||
msg = f"{tool_name}’s {index_type} index"
|
||||
else:
|
||||
msg = f"{tool_name} alias"
|
||||
|
||||
prefix_msg = "Failed to configure"
|
||||
emoji = "[red bold]x[/red bold]"
|
||||
|
||||
# If there is a non-compatible global index
|
||||
if tool_type in [ToolType.POETRY]:
|
||||
prefix_msg = "Skipped"
|
||||
msg += " - not supported by poetry"
|
||||
emoji = "[gray bold]-[/gray bold]"
|
||||
# TODO: Set None for now, to avoid mixing
|
||||
# no configured error with skipped.
|
||||
tool_config[config_type] = None
|
||||
else:
|
||||
is_configured = False
|
||||
|
||||
progressive_print([f"{emoji} {prefix_msg} {msg}"])
|
||||
|
||||
if config_obj := tool_config[config_type]:
|
||||
config_obj.is_configured = is_configured
|
||||
|
||||
console.line()
|
||||
else:
|
||||
progressive_print(["[red bold]x[/red bold] Failed to configure system"])
|
||||
|
||||
completed = []
|
||||
missing = []
|
||||
for tool_type, tool_status in status.items():
|
||||
for config_type, config_obj in tool_status.items():
|
||||
if config_obj:
|
||||
if config_obj.is_configured:
|
||||
completed.append(config_obj)
|
||||
else:
|
||||
missing.append(config_obj)
|
||||
|
||||
all_completed = not missing
|
||||
all_missing = not completed
|
||||
|
||||
tools = [tool_type.value.title() for tool_type in status]
|
||||
completed_tools = (
|
||||
", ".join(tools[:-1]) + " and " + tools[-1] if len(tools) > 1 else tools[0]
|
||||
)
|
||||
|
||||
if all_completed:
|
||||
console.print(
|
||||
f"{emoji_check} {completed_tools} {MSG_SETUP_PACKAGE_FIREWALL_RESULT}"
|
||||
)
|
||||
console.print(MSG_SETUP_PACKAGE_FIREWALL_NOTE_STATUS)
|
||||
else:
|
||||
progressive_print([MSG_SETUP_INCOMPLETE])
|
||||
|
||||
console.line()
|
||||
|
||||
emit_firewall_setup_completed(
|
||||
event_bus=ctx.obj.event_bus,
|
||||
ctx=ctx,
|
||||
status=status,
|
||||
)
|
||||
|
||||
return completed_tools, all_completed, all_missing, status
|
||||
Reference in New Issue
Block a user