Skip to content

Commit

Permalink
Login page without UI enabled (conda-incubator#1039)
Browse files Browse the repository at this point in the history
Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
  • Loading branch information
soapy1 and pre-commit-ci[bot] authored Jan 14, 2025
1 parent c79cffd commit 0fc39be
Show file tree
Hide file tree
Showing 5 changed files with 148 additions and 12 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -4,16 +4,44 @@
license that can be found in the LICENSE file.
-->

{% extends 'base.html' %}
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">

{% block title %}Login{% endblock %}
<!-- Bootstrap CSS -->
<link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.5.2/css/bootstrap.min.css" integrity="sha384-JcKb8q3iqJ61gNV9KGb8thSsNjpSL0n8PARn9HuZOnIxN0hoP+VmmDGMN5t9UJ0Z" crossorigin="anonymous">

{% block content %}
<title>Login</title>
</head>
<body>
<!-- jQuery first, then Popper.js, then Bootstrap JS -->
<script src="https://code.jquery.com/jquery-3.5.1.slim.min.js" integrity="sha384-DfXdz2htPH0lsSSs5nCTpuj/zy4C+OGpamoFVy38MVBnE+IbbVYUew+OrCXaRkfj" crossorigin="anonymous"></script>
<script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/umd/popper.min.js" integrity="sha384-9/reFTGAW83EW2RDu2S0VKaIzap3H66lZH81PoYlFhbGU+6BZp6G7niu735Sk7lN" crossorigin="anonymous"></script>
<script src="https://stackpath.bootstrapcdn.com/bootstrap/4.5.2/js/bootstrap.min.js" integrity="sha384-B4gt1jrGC7Jh4AgTPSdUtOBvfO8shuf57BaghqFfPlYxofvL8/KUEfYiJOMMV+rV" crossorigin="anonymous"></script>

{{ banner | default("") | safe }}
<!-- icons -->
<script type="module" src="https://unpkg.com/[email protected]/dist/ionicons/ionicons.esm.js"></script>
<script nomodule src="https://unpkg.com/[email protected]/dist/ionicons/ionicons.js"></script>

{% block login %}
{{ login_html | safe }}
{% endblock %}
<!-- Initialize popovers globally -->
<script>
$(function () { $('[data-toggle="popover"]').popover() });
</script>

{% endblock %}
<div class="container">
<div class="row" id="message"></div>

{% block content %}

{{ banner | default("") | safe }}

{% block login %}
{{ login_html | safe }}
{% endblock %}

{% endblock %}
</div>
</body>
</html>
6 changes: 2 additions & 4 deletions conda-store-server/conda_store_server/server/auth.py
Original file line number Diff line number Diff line change
Expand Up @@ -550,9 +550,7 @@ async def post_login_method(
)

request.session["next"] = next or request.session.get("next")
redirect_url = request.session.pop("next") or str(
request.url_for("ui_list_environments")
)
redirect_url = request.session.pop("next") or str(request.base_url)
response = await self._post_login_method_response(redirect_url)
response.set_cookie(
self.cookie_name,
Expand All @@ -568,7 +566,7 @@ async def post_login_method(
return response

def post_logout_method(self, request: Request, next: Optional[str] = None):
redirect_url = next or request.url_for("ui_list_environments")
redirect_url = next or request.base_url
response = RedirectResponse(redirect_url, status_code=303)
response.set_cookie(self.cookie_name, "", domain=self.cookie_domain, expires=0)
return response
Expand Down
75 changes: 75 additions & 0 deletions conda-store-server/tests/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,43 @@ def celery_config(tmp_path, conda_store):
return config


@pytest.fixture
def conda_store_api_config(tmp_path):
"""A conda store configuration fixture.
sys.path is manipulated so that only the name of the called program
(e.g. `pytest`) is present. This prevents traitlets from parsing any
additional pytest args as configuration settings to be applied to
the conda-store-server.
"""
from traitlets.config import Config

filename = tmp_path / ".conda-store" / "database.sqlite"

store_directory = tmp_path / ".conda-store" / "state"
store_directory.mkdir(parents=True)

storage.LocalStorage.storage_path = str(tmp_path / ".conda-store" / "storage")

original_sys_argv = list(sys.argv)
sys.argv = [sys.argv[0]]

with utils.chdir(tmp_path):
yield Config(
CondaStore=dict(
storage_class=storage.LocalStorage,
store_directory=str(store_directory),
database_url=f"sqlite:///{filename}?check_same_thread=False",
),
CondaStoreServer=dict(
enable_ui=False,
enable_api=True,
),
)

sys.argv = list(original_sys_argv)


@pytest.fixture
def conda_store_config(tmp_path):
"""A conda store configuration fixture.
Expand Down Expand Up @@ -80,6 +117,36 @@ def conda_store_config(tmp_path):
sys.argv = list(original_sys_argv)


@pytest.fixture
def conda_store_api_server(conda_store_api_config):
_conda_store_server = server_app.CondaStoreServer(config=conda_store_api_config)
_conda_store_server.initialize()

_conda_store = _conda_store_server.conda_store

pathlib.Path(_conda_store.config.store_directory).mkdir(exist_ok=True)

dbutil.upgrade(_conda_store.config.database_url)

with _conda_store.session_factory() as db:
_conda_store.ensure_settings(db)
_conda_store.configuration(db).update_storage_metrics(
db, _conda_store.config.store_directory
)

_conda_store.celery_app

# must import tasks after a celery app has been initialized
# ensure that models are created
from celery.backends.database.session import ResultModelBase

import conda_store_server._internal.worker.tasks # noqa

ResultModelBase.metadata.create_all(db.get_bind())

return _conda_store_server


@pytest.fixture
def conda_store_server(conda_store_config):
_conda_store_server = server_app.CondaStoreServer(config=conda_store_config)
Expand Down Expand Up @@ -115,6 +182,14 @@ def testclient(conda_store_server):
return TestClient(conda_store_server.init_fastapi_app())


@pytest.fixture
def testclient_api_server(conda_store_api_server):
client = TestClient(conda_store_api_server.init_fastapi_app())
ui_respones = client.get("/ui/")
assert ui_respones.status_code == 404
return client


@pytest.fixture
def authenticate(testclient):
response = testclient.post(
Expand Down
29 changes: 29 additions & 0 deletions conda-store-server/tests/server/test_auth.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,11 @@
import uuid

import pytest
from fastapi import Request

from conda_store_server._internal.schema import AuthenticationToken, Permissions
from conda_store_server.server.auth import (
Authentication,
AuthenticationBackend,
RBACAuthorizationBackend,
)
Expand Down Expand Up @@ -528,3 +530,30 @@ def test_is_subset_entity_permissions(
new_entity = AuthenticationToken(role_bindings=new_entity_bindings)

assert authorization.is_subset_entity_permissions(entity, new_entity) == value


def test_post_logout_method_default_next():
"""Test to ensure that the default logout redirect is set correctly"""
authentication = Authentication()
test_request = Request(
scope={
"type": "http",
"path": "/logout",
"app_root_path": "mytest.url.com",
"headers": [
("host", "mytest.url.com"),
],
}
)
redirect_response = authentication.post_logout_method(request=test_request)
assert redirect_response.headers["location"] == "mytest.url.com/"


def test_auth_no_ui(testclient_api_server):
"""Test that users can use the /login route if only the api is
enabled (and the ui is disabled).
"""
response = testclient_api_server.post(
"/login/", json={"username": "username", "password": "password"}
)
assert response.status_code == 200
6 changes: 6 additions & 0 deletions tests/test_playwright.py
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,12 @@ def test_integration(page: Page, server_port):
with page.expect_navigation():
page.locator('button:has-text("Sign In")').click()

# Go to the main admin page (authenticated)
page.goto(
f"http://localhost:{server_port}/conda-store/admin/",
wait_until="domcontentloaded",
)

page.screenshot(path="test-results/conda-store-authenticated.png")

# Click [placeholder="Search"]
Expand Down

0 comments on commit 0fc39be

Please sign in to comment.