Skip to content

Commit

Permalink
Merge branch 'main' into fix-ssl-verify-test-check
Browse files Browse the repository at this point in the history
  • Loading branch information
soapy1 authored Jan 23, 2025
2 parents a1a8750 + a7a6390 commit 281147a
Show file tree
Hide file tree
Showing 27 changed files with 822 additions and 357 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
from sqlalchemy.orm import Query

from conda_store_server._internal import conda_utils, orm, schema, utils
from conda_store_server.server import schema as auth_schema


def validate_environment(specification):
Expand Down Expand Up @@ -151,15 +152,15 @@ def _append_pip_packages(specification, packages):

def filter_environments(
query: Query,
role_bindings: schema.RoleBindings,
role_bindings: auth_schema.RoleBindings,
) -> Query:
"""Filter a query containing environments and namespaces by a set of role bindings.
Parameters
----------
query : Query
Query containing both environments and namespaces
role_bindings : schema.RoleBindings
role_bindings : auth_schema.RoleBindings
Role bindings to filter the results by
Returns
Expand All @@ -171,7 +172,7 @@ def filter_environments(
cases = []
for entity_arn, entity_roles in role_bindings.items():
namespace, name = utils.compile_arn_sql_like(
entity_arn, schema.ARN_ALLOWED_REGEX
entity_arn, auth_schema.ARN_ALLOWED_REGEX
)
cases.append(
and_(
Expand Down
3 changes: 2 additions & 1 deletion conda-store-server/conda_store_server/_internal/orm.py
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@
from conda_store_server._internal import conda_utils, schema, utils
from conda_store_server._internal.environment import validate_environment
from conda_store_server.exception import BuildPathError
from conda_store_server.server import schema as auth_schema

logger = logging.getLogger("orm")

Expand Down Expand Up @@ -101,7 +102,7 @@ class NamespaceRoleMapping(Base):

@validates("entity")
def validate_entity(self, key, entity):
if not schema.ARN_ALLOWED_REGEX.match(entity):
if not auth_schema.ARN_ALLOWED_REGEX.match(entity):
raise ValueError(f"invalid entity={entity}")

return entity
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,7 @@

class CondaLock(lock.LockPlugin):
def _conda_command(self, conda_store) -> str:
with conda_store.session_factory() as db:
settings = conda_store.get_settings(db=db)
settings = conda_store.get_settings()
return settings.conda_command

def _conda_flags(self, conda_store) -> str:
Expand Down
55 changes: 1 addition & 54 deletions conda-store-server/conda_store_server/_internal/schema.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,10 @@

import datetime
import enum
import functools
import os
import re
import sys
from typing import Annotated, Any, Dict, List, Optional, TypeAlias, Union
from typing import Annotated, Any, Dict, List, Optional, Union

from conda_lock.lockfile.v1.models import Lockfile
from pydantic import (
Expand All @@ -23,59 +22,7 @@
from conda_store_server._internal import conda_utils
from conda_store_server.exception import CondaStoreError


def _datetime_factory(offset: datetime.timedelta):
"""Utcnow datetime + timezone as string"""
return datetime.datetime.utcnow() + offset


# An ARN is a string which matches namespaces and environments. For example:
# */* matches all environments
# */team matches all environments named 'team' in any namespace
#
# Namespaces and environment names cannot contain "*" ":" "#" " " "/"
ALLOWED_CHARACTERS = "A-Za-z0-9-+_@$&?^~.="
ARN_ALLOWED = f"^([{ALLOWED_CHARACTERS}*]+)/([{ALLOWED_CHARACTERS}*]+)$"
ARN_ALLOWED_REGEX = re.compile(ARN_ALLOWED)


#########################
# Authentication Schema
#########################

RoleBindings: TypeAlias = Dict[
Annotated[str, StringConstraints(pattern=ARN_ALLOWED)], List[str]
]


class Permissions(enum.Enum):
"""Permissions map to conda-store actions"""

ENVIRONMENT_CREATE = "environment:create"
ENVIRONMENT_READ = "environment::read"
ENVIRONMENT_UPDATE = "environment::update"
ENVIRONMENT_DELETE = "environment::delete"
ENVIRONMENT_SOLVE = "environment::solve"
BUILD_CANCEL = "build::cancel"
BUILD_DELETE = "build::delete"
NAMESPACE_CREATE = "namespace::create"
NAMESPACE_READ = "namespace::read"
NAMESPACE_UPDATE = "namespace::update"
NAMESPACE_DELETE = "namespace::delete"
NAMESPACE_ROLE_MAPPING_CREATE = "namespace-role-mapping::create"
NAMESPACE_ROLE_MAPPING_READ = "namespace-role-mapping::read"
NAMESPACE_ROLE_MAPPING_UPDATE = "namespace-role-mapping::update"
NAMESPACE_ROLE_MAPPING_DELETE = "namespace-role-mapping::delete"
SETTING_READ = "setting::read"
SETTING_UPDATE = "setting::update"


class AuthenticationToken(BaseModel):
exp: datetime.datetime = Field(
default_factory=functools.partial(_datetime_factory, datetime.timedelta(days=1))
)
primary_namespace: str = "default"
role_bindings: RoleBindings = {}


##########################
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -388,7 +388,6 @@ def start(self):
dbutil.upgrade(self.conda_store.config.database_url)

with self.conda_store.session_factory() as db:
self.conda_store.ensure_settings(db)
self.conda_store.ensure_namespace(db)
self.conda_store.ensure_conda_channels(db)

Expand Down
93 changes: 45 additions & 48 deletions conda-store-server/conda_store_server/_internal/server/views/api.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,11 +14,12 @@
from conda_store_server import __version__, api
from conda_store_server._internal import orm, schema
from conda_store_server._internal.environment import filter_environments
from conda_store_server._internal.schema import AuthenticationToken, Permissions
from conda_store_server._internal.server import dependencies
from conda_store_server.conda_store import CondaStore
from conda_store_server.exception import CondaStoreError
from conda_store_server.server import schema as auth_schema
from conda_store_server.server.auth import Authentication
from conda_store_server.server.schema import AuthenticationToken, Permissions


class PaginatedArgs(TypedDict):
Expand Down Expand Up @@ -225,14 +226,14 @@ async def api_post_token(
entity=Depends(dependencies.get_entity),
):
if entity is None:
entity = schema.AuthenticationToken(
entity = auth_schema.AuthenticationToken(
exp=datetime.datetime.now(tz=datetime.timezone.utc)
+ datetime.timedelta(days=1),
primary_namespace=conda_store.config.default_namespace,
role_bindings={},
)

new_entity = schema.AuthenticationToken(
new_entity = auth_schema.AuthenticationToken(
exp=expiration or entity.exp,
primary_namespace=primary_namespace or entity.primary_namespace,
role_bindings=role_bindings or auth.authorization.get_entity_bindings(entity),
Expand Down Expand Up @@ -672,7 +673,7 @@ async def api_list_environments(
If specified, filter by environments containing the given package name(s)
artifact : Optional[schema.BuildArtifactType]
If specified, filter by environments with the given BuildArtifactType
jwt : Optional[schema.AuthenticationToken]
jwt : Optional[auth_schema.AuthenticationToken]
If specified, retrieve only the environments accessible to this token; that is,
only return environments that the user has 'admin', 'editor', and 'viewer'
role bindings for.
Expand Down Expand Up @@ -1428,28 +1429,25 @@ async def api_get_settings(
namespace: str = None,
environment_name: str = None,
):
with conda_store.get_db() as db:
if namespace is None:
arn = ""
elif environment_name is None:
arn = namespace
else:
arn = f"{namespace}/{environment_name}"

auth.authorize_request(
request,
arn,
{Permissions.SETTING_READ},
require=True,
)
if namespace is None:
arn = ""
elif environment_name is None:
arn = namespace
else:
arn = f"{namespace}/{environment_name}"

auth.authorize_request(
request,
arn,
{Permissions.SETTING_READ},
require=True,
)

return {
"status": "ok",
"data": conda_store.get_settings(
db, namespace, environment_name
).model_dump(),
"message": None,
}
return {
"status": "ok",
"data": conda_store.get_settings(namespace, environment_name).model_dump(),
"message": None,
}


@router_api.put(
Expand All @@ -1472,28 +1470,27 @@ async def api_put_settings(
namespace: str = None,
environment_name: str = None,
):
with conda_store.get_db() as db:
if namespace is None:
arn = ""
elif environment_name is None:
arn = namespace
else:
arn = f"{namespace}/{environment_name}"
if namespace is None:
arn = ""
elif environment_name is None:
arn = namespace
else:
arn = f"{namespace}/{environment_name}"

auth.authorize_request(
request,
arn,
{Permissions.SETTING_UPDATE},
require=True,
)

auth.authorize_request(
request,
arn,
{Permissions.SETTING_UPDATE},
require=True,
)
try:
conda_store.set_settings(namespace, environment_name, data)
except Exception as e:
raise HTTPException(status_code=400, detail=str(e))

try:
conda_store.set_settings(db, namespace, environment_name, data)
except ValueError as e:
raise HTTPException(status_code=400, detail=str(e.args[0]))

return {
"status": "ok",
"data": None,
"message": f"global setting keys {list(data.keys())} updated",
}
return {
"status": "ok",
"data": None,
"message": f"global setting keys {list(data.keys())} updated",
}
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,8 @@

from conda_store_server import api
from conda_store_server._internal import orm, schema
from conda_store_server._internal.schema import Permissions
from conda_store_server._internal.server import dependencies
from conda_store_server.server.schema import Permissions

router_registry = APIRouter(tags=["registry"])

Expand Down
61 changes: 30 additions & 31 deletions conda-store-server/conda_store_server/_internal/server/views/ui.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,8 @@
from conda_store_server._internal.action.generate_constructor_installer import (
get_installer_platform,
)
from conda_store_server._internal.schema import Permissions
from conda_store_server._internal.server import dependencies
from conda_store_server.server.schema import Permissions

router_ui = APIRouter(tags=["ui"])

Expand Down Expand Up @@ -293,34 +293,33 @@ async def ui_get_setting(
namespace: str = None,
environment_name: str = None,
):
with conda_store.get_db() as db:
if namespace is None:
arn = ""
elif environment_name is None:
arn = namespace
else:
arn = f"{namespace}/{environment_name}"

auth.authorize_request(
request,
arn,
{Permissions.SETTING_READ},
require=True,
)

api_setting_url = str(request.url_for("api_put_settings"))
if namespace is not None:
api_setting_url += f"{namespace}/"
if environment_name is not None:
api_setting_url += f"{environment_name}/"

context = {
"request": request,
"namespace": namespace,
"environment_name": environment_name,
"api_settings_url": api_setting_url,
"settings": conda_store.get_settings(
db, namespace=namespace, environment_name=environment_name
),
}
if namespace is None:
arn = ""
elif environment_name is None:
arn = namespace
else:
arn = f"{namespace}/{environment_name}"

auth.authorize_request(
request,
arn,
{Permissions.SETTING_READ},
require=True,
)

api_setting_url = str(request.url_for("api_put_settings"))
if namespace is not None:
api_setting_url += f"{namespace}/"
if environment_name is not None:
api_setting_url += f"{environment_name}/"

context = {
"request": request,
"namespace": namespace,
"environment_name": environment_name,
"api_settings_url": api_setting_url,
"settings": conda_store.get_settings(
namespace=namespace, environment_name=environment_name
),
}
return templates.TemplateResponse(request, "setting.html", context)
Loading

0 comments on commit 281147a

Please sign in to comment.