Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: Patch dummylogin vulnerability #695

Draft
wants to merge 3 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .github/workflows/lint.yml
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ jobs:
- uses: mamba-org/setup-micromamba@v1
with:
environment-file: environment.yml
create-args: python>=3.8
- name: Add micromamba to GITHUB_PATH
run: echo "${HOME}/micromamba-bin" >> "$GITHUB_PATH"
- run: ln -s "${CONDA_PREFIX}" .venv # Necessary for pyright.
Expand Down
2 changes: 1 addition & 1 deletion .pre-commit-config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -27,4 +27,4 @@ repos:
- types-toml
- types-ujson
- types-aiofiles
args: [--show-error-codes, --implicit-optional]
args: [--show-error-codes, --implicit-optional]
2 changes: 1 addition & 1 deletion dev_config.toml
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@

[github]
# Register the app here: https://github.com/settings/applications/new
# Use a callback URL like `http://localhost:8000/auth/github/authorize`
Expand All @@ -21,4 +22,3 @@ admins = ["dummy:alice"]

[profiling]
enable_sampling = false

6 changes: 6 additions & 0 deletions docs/source/deploying/configuration.rst
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,12 @@ Quetz can also create a channel for a newly connected user:
:redirect_http_to_https: Enforces that all incoming requests must be `https`. Any incoming requests to `http` will be redirected to the secure scheme instead. Defaults to `false`.
:package_unpack_threads: Number of parallel threads used for unpacking. Defaults to `1`.

.. code::

[general]
:dev: Whether to enable development features. Do not enable this in production.
Fills the database with test data and allows http access.

``session`` section
^^^^^^^^^^^^^^^^^^^

Expand Down
2 changes: 1 addition & 1 deletion docs/source/deploying/database.rst
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ You can then create a database in PostgreSQL for quetz tables:
Deploying Quetz with PostgreSQL backend
"""""""""""""""""""""""""""""""""""""""

Then in your configuration file (such as `dev_config.toml`) replace the `[sqlalchemy]` section with:
Then in your configuration file (such as `config.toml`) replace the `[sqlalchemy]` section with:

.. code::

Expand Down
1 change: 1 addition & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@ venv = ".venv"
venvPath= "."

[tool.mypy]
python_version = "3.8"
ignore_missing_imports = true
plugins = [
"sqlmypy"
Expand Down
28 changes: 5 additions & 23 deletions quetz/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -412,15 +412,6 @@ def create(
bool,
typer.Option(help="Skip the creation if deployment already exists."),
] = False,
dev: Annotated[
bool,
typer.Option(
help=(
"Enable/disable dev mode "
"(fills the database with test data and allows http access)"
),
),
] = False,
):
"""Create a new Quetz deployment."""

Expand Down Expand Up @@ -462,6 +453,7 @@ def create(
)
raise typer.Abort()

config = None
if not config_file.exists() and not create_conf and not copy_conf:
try:
# If no config file is provided or created, try to get config
Expand Down Expand Up @@ -489,8 +481,7 @@ def create(
shutil.copyfile(copy_conf, config_file)

if not config_file.exists() and create_conf:
https = "false" if dev else "true"
conf = create_config(https=https)
conf = create_config(https=str(config.is_dev() if config else True).lower())
with open(config_file, "w") as f:
f.write(conf)

Expand All @@ -502,7 +493,7 @@ def create(
with working_directory(db_path):
db = get_session(config.sqlalchemy_database_url)
_run_migrations(config.sqlalchemy_database_url)
if dev:
if config.is_dev():
_fill_test_database(db)
_set_user_roles(db, config)

Expand Down Expand Up @@ -556,7 +547,6 @@ def start(
To be started, a deployment has to be already created.
At this time, only Uvicorn is supported as manager.
"""

logger.info(f"deploying quetz from directory {path}")

if path:
Expand Down Expand Up @@ -591,6 +581,7 @@ def start(
import quetz

quetz_src = os.path.dirname(quetz.__file__)

uvicorn.run(
"quetz.main:app",
reload=reload,
Expand Down Expand Up @@ -630,15 +621,6 @@ def run(
bool,
typer.Option(help="Skip the creation if deployment already exists."),
] = False,
dev: Annotated[
bool,
typer.Option(
help=(
"Enable/disable dev mode "
"(fills the database with test data and allows http access)"
),
),
] = False,
port: Annotated[int, typer.Option(help="The port to bind")] = 8000,
host: Annotated[
str, typer.Option(help="The network interface to bind")
Expand All @@ -665,7 +647,7 @@ def run(
It performs sequentially create and start operations."""

abs_path = os.path.abspath(path)
create(abs_path, copy_conf, create_conf, delete, skip_if_exists, dev)
create(abs_path, copy_conf, create_conf, delete, skip_if_exists)
start(abs_path, port, host, proxy_headers, log_level, reload)


Expand Down
16 changes: 14 additions & 2 deletions quetz/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
import logging.config
import os
from distutils.util import strtobool
from pathlib import Path
from secrets import token_bytes
from typing import Any, Dict, Iterable, List, NamedTuple, Optional, Type, Union

Expand Down Expand Up @@ -59,6 +60,7 @@ class Config:
ConfigSection(
"general",
[
ConfigEntry("dev", bool, required=True, default=False),
ConfigEntry("package_unpack_threads", int, 1),
ConfigEntry("frontend_dir", str, default=""),
ConfigEntry("redirect_http_to_https", bool, False),
Expand Down Expand Up @@ -243,7 +245,7 @@ def __new__(cls, deployment_config: str = None):
return cls._instances[None]

try:
path = os.path.abspath(cls.find_file(deployment_config))
path = str(Path(cls.find_file(deployment_config)).absolute().resolve())
except TypeError:
# if not config path exists, set it to empty string.
path = ""
Expand Down Expand Up @@ -287,7 +289,6 @@ def init(self, path: str) -> None:
deployment_config : str, optional
The configuration stored at deployment level
"""

self.config: Dict[str, Any] = {}

# only try to get config from config file if it exists.
Expand Down Expand Up @@ -407,6 +408,7 @@ def _get_environ_config(self) -> Dict[str, Any]:
for key, value in os.environ.items()
if key.startswith(_env_prefix)
}

for var, value in quetz_var.items():
split_key = var.split("_")
config_key = split_key[1].lower()
Expand Down Expand Up @@ -443,6 +445,16 @@ def _get_environ_config(self) -> Dict[str, Any]:

return config

def is_dev(self) -> bool:
"""Return whether the application is in development mode.

Returns
-------
bool
Whether the application is in development mode
"""
return str(self.config.get("general", {}).get("dev", False)).lower() == "true"

def get_package_store(self) -> pkgstores.PackageStore:
"""Return the appropriate package store as set in the config.

Expand Down
3 changes: 3 additions & 0 deletions quetz/config.toml
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
[general]
dev = true

[github]
# Register the app here: https://github.com/settings/applications/new
client_id = "{}"
Expand Down
2 changes: 1 addition & 1 deletion quetz/deps.py
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,7 @@ def get_rules(
request: Request,
session: dict = Depends(get_session),
db: Session = Depends(get_db),
):
) -> authorization.Rules:
return authorization.Rules(request.headers.get("x-api-key"), session, db)


Expand Down
30 changes: 16 additions & 14 deletions quetz/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -270,22 +270,24 @@ async def route_logout(request):
return RedirectResponse("/")


@api_router.get("/dummylogin/{username}", tags=["dev"])
def dummy_login(
username: str, dao: Dao = Depends(get_dao), session=Depends(get_session)
):
user = dao.get_user_by_username(username)
if user is None:
raise HTTPException(
status_code=status.HTTP_404_NOT_FOUND,
detail=f"User '{username}' not found",
)
if config.is_dev():

@api_router.get("/dummylogin/{username}", tags=["dev"])
def dummy_login(
username: str, dao: Dao = Depends(get_dao), session=Depends(get_session)
):
user = dao.get_user_by_username(username)
if user is None:
raise HTTPException(
status_code=status.HTTP_404_NOT_FOUND,
detail=f"User '{username}' not found",
)

logout(session)
session["user_id"] = str(uuid.UUID(bytes=user.id))
logout(session)
session["user_id"] = str(uuid.UUID(bytes=user.id))

session["identity_provider"] = "dummy"
return RedirectResponse("/")
session["identity_provider"] = "dummy"
return RedirectResponse("/")


@api_router.get("/me", response_model=rest_models.Profile, tags=["users"])
Expand Down
3 changes: 3 additions & 0 deletions quetz/testing/fixtures.py
Original file line number Diff line number Diff line change
Expand Up @@ -168,6 +168,9 @@ def config_base(database_url, plugins, config_auth):
return f"""
{config_auth}

[general]
dev = true

[sqlalchemy]
database_url = "{database_url}"

Expand Down
6 changes: 1 addition & 5 deletions quetz/tests/test_cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -124,11 +124,7 @@ def get_db(_):
return db

with mock.patch("quetz.cli.get_session", get_db):
cli.create(
Path(config_dir) / "new-deployment",
copy_conf="config.toml",
dev=True,
)
cli.create(Path(config_dir) / "new-deployment", copy_conf="config.toml")

user = db.query(User).filter(User.username == "alice").one_or_none()

Expand Down
Loading