Skip to content

Commit

Permalink
Merge pull request #792 from lsst-sqre/u/neophile
Browse files Browse the repository at this point in the history
[neophile] Update dependencies
  • Loading branch information
neophile-square[bot] committed Jul 7, 2023
2 parents bb334b5 + 579d41a commit 790ccf8
Show file tree
Hide file tree
Showing 11 changed files with 89 additions and 66 deletions.
4 changes: 2 additions & 2 deletions .pre-commit-config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ repos:
- id: trailing-whitespace

- repo: https://github.com/astral-sh/ruff-pre-commit
rev: v0.0.272
rev: v0.0.277
hooks:
- id: ruff
args: [--fix, --exit-non-zero-on-fix]
Expand All @@ -27,7 +27,7 @@ repos:
args: [-l, '79', -t, py311]

- repo: https://github.com/pre-commit/mirrors-eslint
rev: v8.43.0
rev: v8.44.0
hooks:
- id: eslint
additional_dependencies:
Expand Down
62 changes: 45 additions & 17 deletions src/gafaelfawr/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,13 @@
from typing import Any, Self

import yaml
from pydantic import AnyHttpUrl, IPvAnyNetwork, root_validator, validator
from pydantic import (
AnyHttpUrl,
Field,
IPvAnyNetwork,
root_validator,
validator,
)
from safir.logging import LogLevel, Profile, configure_logging
from safir.pydantic import CamelCaseModel, validate_exactly_one_of

Expand Down Expand Up @@ -76,8 +82,11 @@ class OIDCSettings(CamelCaseModel):
login_url: AnyHttpUrl
"""URL to which to send the user to initiate authentication."""

login_params: dict[str, str] = {}
"""Additional parameters to the login URL."""
login_params: dict[str, str] = Field(
{},
title="Additional login parameters",
description="Additional parameters to the login URL",
)

redirect_url: AnyHttpUrl
"""Return URL to which the authentication provider should send the user.
Expand All @@ -96,12 +105,15 @@ class OIDCSettings(CamelCaseModel):
URL so that they can register.
"""

scopes: list[str] = []
"""Scopes to request from the authentication provider.
The ``openid`` scope will always be added and does not need to be
specified.
"""
scopes: list[str] = Field(
[],
title="Scopes to request",
description=(
"Scopes to request from the authentication provider. The"
" `openid` scope will always be added and does not need to be"
" specified."
),
)

issuer: str
"""Expected issuer of the ID token."""
Expand Down Expand Up @@ -283,8 +295,13 @@ class QuotaGrantSettings(CamelCaseModel):
overall quota configuration.
"""

api: dict[str, int] = {}
"""Mapping of service names to quota of requests per 15 minutes."""
api: dict[str, int] = Field(
{},
title="Service quotas",
description=(
"Mapping of service names to quota of requests per 15 minutes"
),
)

notebook: NotebookQuotaSettings | None = None
"""Quota settings for the Notebook Aspect."""
Expand All @@ -296,8 +313,11 @@ class QuotaSettings(CamelCaseModel):
default: QuotaGrantSettings
"""Default quotas for all users."""

groups: dict[str, QuotaGrantSettings] = {}
"""Additional quota grants by group name."""
groups: dict[str, QuotaGrantSettings] = Field(
{},
title="Quota grants by group",
description="Additional quota grants by group name",
)


class Settings(CamelCaseModel):
Expand Down Expand Up @@ -391,11 +411,19 @@ class Settings(CamelCaseModel):
initial_admins: list[str]
"""Initial token administrators to configure when initializing database."""

known_scopes: dict[str, str] = {}
"""Known scopes (the keys) and their descriptions (the values)."""
known_scopes: dict[str, str] = Field(
{},
title="Known scopes",
description=(
"Known scopes (the keys) and their descriptions (the values)"
),
)

group_mapping: dict[str, list[str]] = {}
"""Mappings of scopes to lists of groups that provide them."""
group_mapping: dict[str, list[str]] = Field(
{},
title="Scope to group mapping",
description="Mappings of scopes to lists of groups that provide them",
)

@validator("initial_admins", each_item=True)
def _validate_initial_admins(cls, v: str) -> str:
Expand Down
3 changes: 1 addition & 2 deletions src/gafaelfawr/handlers/oidc.py
Original file line number Diff line number Diff line change
Expand Up @@ -120,13 +120,12 @@ async def get_login(
if error:
e = InvalidRequestError(error)
context.logger.warning("%s", e.message, error=str(e))
return_url = build_return_url(
return build_return_url(
parsed_redirect_uri,
state=state,
error=e.error,
error_description=str(e),
)
return return_url

# Get an authorization code and return it.
code = await oidc_service.issue_code(
Expand Down
6 changes: 5 additions & 1 deletion src/gafaelfawr/models/admin.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@

from __future__ import annotations

from typing import ClassVar

from pydantic import BaseModel

__all__ = ["Admin"]
Expand All @@ -15,4 +17,6 @@ class Admin(BaseModel):

class Config:
orm_mode = True
schema_extra = {"example": {"username": "adminuser"}}
schema_extra: ClassVar[dict[str, dict[str, str]]] = {
"example": {"username": "adminuser"}
}
7 changes: 5 additions & 2 deletions src/gafaelfawr/models/history.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,11 @@

from __future__ import annotations

from collections.abc import Callable
from dataclasses import dataclass
from datetime import UTC, datetime
from enum import Enum
from typing import Any, Generic, Self, TypeVar
from typing import Any, ClassVar, Generic, Self, TypeVar
from urllib.parse import parse_qs, urlencode

from pydantic import BaseModel, Field, validator
Expand Down Expand Up @@ -353,7 +354,9 @@ class TokenChangeHistoryEntry(BaseModel):
)

class Config:
json_encoders = {datetime: lambda v: int(v.timestamp())}
json_encoders: ClassVar[dict[type, Callable]] = {
datetime: lambda v: int(v.timestamp())
}
orm_mode = True

_normalize_scopes = validator(
Expand Down
7 changes: 5 additions & 2 deletions src/gafaelfawr/models/oidc.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,9 @@

from __future__ import annotations

from collections.abc import Callable
from datetime import datetime
from typing import Any, Self
from typing import Any, ClassVar, Self

from pydantic import BaseModel, Field, validator
from safir.datetime import current_datetime
Expand Down Expand Up @@ -113,7 +114,9 @@ class OIDCAuthorization(BaseModel):
)

class Config:
json_encoders = {datetime: lambda v: int(v.timestamp())}
json_encoders: ClassVar[dict[type, Callable]] = {
datetime: lambda v: int(v.timestamp())
}

_normalize_created_at = validator(
"created_at", allow_reuse=True, pre=True
Expand Down
11 changes: 8 additions & 3 deletions src/gafaelfawr/models/token.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,10 @@

from __future__ import annotations

from collections.abc import Callable
from datetime import datetime
from enum import Enum
from typing import Any, Self
from typing import Any, ClassVar, Self

from pydantic import BaseModel, Field, validator
from safir.datetime import current_datetime
Expand Down Expand Up @@ -304,7 +305,9 @@ class TokenInfo(TokenBase):

class Config:
orm_mode = True
json_encoders = {datetime: lambda v: int(v.timestamp())}
json_encoders: ClassVar[dict[type, Callable]] = {
datetime: lambda v: int(v.timestamp())
}

_normalize_created = validator(
"created", "last_used", "expires", allow_reuse=True, pre=True
Expand Down Expand Up @@ -407,7 +410,9 @@ class TokenData(TokenBase, TokenUserInfo):
token: Token = Field(..., title="Associated token")

class Config:
json_encoders = {datetime: lambda v: int(v.timestamp())}
json_encoders: ClassVar[dict[type, Callable]] = {
datetime: lambda v: int(v.timestamp())
}

@classmethod
def bootstrap_token(cls) -> Self:
Expand Down
2 changes: 1 addition & 1 deletion src/gafaelfawr/providers/github.py
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ class GitHubProvider(Provider):
_USER_URL = "https://api.github.com/user"
"""URL from which to request user metadata."""

_SCOPES = ["read:org", "read:user", "user:email"]
_SCOPES = ("read:org", "read:user", "user:email")
"""Access scopes to request from GitHub."""

def __init__(
Expand Down
5 changes: 1 addition & 4 deletions src/gafaelfawr/storage/token.py
Original file line number Diff line number Diff line change
Expand Up @@ -523,10 +523,7 @@ async def list(self) -> list[str]:
list of str
The tokens found in Redis (by looking for valid keys).
"""
keys = []
async for key in self._storage.scan("*"):
keys.append(key)
return keys
return [k async for k in self._storage.scan("*")]

async def store_data(self, data: TokenData) -> None:
"""Store the data for a token.
Expand Down
45 changes: 14 additions & 31 deletions tests/handlers/auth_load_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,13 +26,8 @@ async def test_notebook(client: AsyncClient, factory: Factory) -> None:
)
await set_session_cookie(client, data.token)

request_awaits = []
for _ in range(100):
request_awaits.append(
client.get(
"/auth", params={"scope": "exec:test", "notebook": "true"}
)
)
params = {"scope": "exec:test", "notebook": "true"}
request_awaits = [client.get("/auth", params=params) for _ in range(100)]
responses = await asyncio.gather(*request_awaits)
assert responses[0].status_code == 200
token = Token.from_str(responses[0].headers["X-Auth-Request-Token"])
Expand All @@ -48,37 +43,25 @@ async def test_internal(client: AsyncClient, factory: Factory) -> None:
)
await set_session_cookie(client, data.token)

request_awaits = []
for _ in range(100):
request_awaits.append(
client.get(
"/auth",
params={
"scope": "exec:test",
"delegate_to": "a-service",
"delegate_scope": "read:all",
},
)
)
params = {
"scope": "exec:test",
"delegate_to": "a-service",
"delegate_scope": "read:all",
}
request_awaits = [client.get("/auth", params=params) for _ in range(100)]
responses = await asyncio.gather(*request_awaits)
assert responses[0].status_code == 200
token = Token.from_str(responses[0].headers["X-Auth-Request-Token"])
for r in responses:
assert r.status_code == 200
assert Token.from_str(r.headers["X-Auth-Request-Token"]) == token

request_awaits = []
for _ in range(100):
request_awaits.append(
client.get(
"/auth",
params={
"scope": "exec:test",
"delegate_to": "a-service",
"delegate_scope": "exec:test",
},
)
)
params = {
"scope": "exec:test",
"delegate_to": "a-service",
"delegate_scope": "exec:test",
}
request_awaits = [client.get("/auth", params=params) for _ in range(100)]
responses = await asyncio.gather(*request_awaits)
assert responses[0].status_code == 200
new_token = Token.from_str(responses[0].headers["X-Auth-Request-Token"])
Expand Down
3 changes: 2 additions & 1 deletion tests/support/ldap.py
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,8 @@ async def search(
entries = self._entries[base][key]
results = []
for entry in entries:
results.append({a: entry[a] for a in attrlist if a in entry})
attributes = {a: entry[a] for a in attrlist if a in entry}
results.append(attributes)
return results

@asynccontextmanager
Expand Down

0 comments on commit 790ccf8

Please sign in to comment.