Refactor database connection handling in LocalOAuthProvider to use async context manager
This commit is contained in:
@@ -8,8 +8,9 @@ import html
|
|||||||
import json
|
import json
|
||||||
import secrets
|
import secrets
|
||||||
import time
|
import time
|
||||||
|
from contextlib import asynccontextmanager
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from typing import Any
|
from typing import Any, AsyncIterator
|
||||||
from urllib.parse import urlencode
|
from urllib.parse import urlencode
|
||||||
|
|
||||||
import aiosqlite
|
import aiosqlite
|
||||||
@@ -110,13 +111,17 @@ class LocalOAuthProvider:
|
|||||||
)
|
)
|
||||||
await db.commit()
|
await db.commit()
|
||||||
|
|
||||||
async def _connect(self) -> aiosqlite.Connection:
|
@asynccontextmanager
|
||||||
|
async def _connect(self) -> AsyncIterator[aiosqlite.Connection]:
|
||||||
db = await aiosqlite.connect(self.database_path)
|
db = await aiosqlite.connect(self.database_path)
|
||||||
db.row_factory = aiosqlite.Row
|
db.row_factory = aiosqlite.Row
|
||||||
return db
|
try:
|
||||||
|
yield db
|
||||||
|
finally:
|
||||||
|
await db.close()
|
||||||
|
|
||||||
async def get_client(self, client_id: str) -> OAuthClientInformationFull | None:
|
async def get_client(self, client_id: str) -> OAuthClientInformationFull | None:
|
||||||
async with await self._connect() as db:
|
async with self._connect() as db:
|
||||||
async with db.execute(
|
async with db.execute(
|
||||||
"SELECT metadata_json FROM oauth_clients WHERE client_id = ?", (client_id,)
|
"SELECT metadata_json FROM oauth_clients WHERE client_id = ?", (client_id,)
|
||||||
) as cursor:
|
) as cursor:
|
||||||
@@ -129,7 +134,7 @@ class LocalOAuthProvider:
|
|||||||
if not client_info.client_id:
|
if not client_info.client_id:
|
||||||
raise ValueError("OAuth client registration did not include a client ID")
|
raise ValueError("OAuth client registration did not include a client ID")
|
||||||
data = client_info.model_dump_json()
|
data = client_info.model_dump_json()
|
||||||
async with await self._connect() as db:
|
async with self._connect() as db:
|
||||||
async with db.execute(
|
async with db.execute(
|
||||||
"SELECT COUNT(*) AS n FROM oauth_clients WHERE client_id != ?",
|
"SELECT COUNT(*) AS n FROM oauth_clients WHERE client_id != ?",
|
||||||
(client_info.client_id,),
|
(client_info.client_id,),
|
||||||
@@ -158,7 +163,7 @@ class LocalOAuthProvider:
|
|||||||
raise AuthorizeError("invalid_target", "This authorization server only issues tokens for this MCP endpoint")
|
raise AuthorizeError("invalid_target", "This authorization server only issues tokens for this MCP endpoint")
|
||||||
|
|
||||||
flow_id = secrets.token_urlsafe(32)
|
flow_id = secrets.token_urlsafe(32)
|
||||||
async with await self._connect() as db:
|
async with self._connect() as db:
|
||||||
now = int(time.time())
|
now = int(time.time())
|
||||||
await db.execute("DELETE FROM oauth_flows WHERE expires_at < ?", (now,))
|
await db.execute("DELETE FROM oauth_flows WHERE expires_at < ?", (now,))
|
||||||
await db.execute("DELETE FROM oauth_codes WHERE expires_at < ?", (now,))
|
await db.execute("DELETE FROM oauth_codes WHERE expires_at < ?", (now,))
|
||||||
@@ -185,7 +190,7 @@ class LocalOAuthProvider:
|
|||||||
return f"{self.issuer_url}/login?{urlencode({'flow': flow_id})}"
|
return f"{self.issuer_url}/login?{urlencode({'flow': flow_id})}"
|
||||||
|
|
||||||
async def get_flow(self, flow_id: str) -> dict[str, Any] | None:
|
async def get_flow(self, flow_id: str) -> dict[str, Any] | None:
|
||||||
async with await self._connect() as db:
|
async with self._connect() as db:
|
||||||
async with db.execute(
|
async with db.execute(
|
||||||
"SELECT * FROM oauth_flows WHERE flow_id = ?", (flow_id,)
|
"SELECT * FROM oauth_flows WHERE flow_id = ?", (flow_id,)
|
||||||
) as cursor:
|
) as cursor:
|
||||||
@@ -202,7 +207,7 @@ class LocalOAuthProvider:
|
|||||||
|
|
||||||
async def login_is_limited(self, client_ip: str) -> bool:
|
async def login_is_limited(self, client_ip: str) -> bool:
|
||||||
cutoff = int(time.time()) - LOGIN_WINDOW_SECONDS
|
cutoff = int(time.time()) - LOGIN_WINDOW_SECONDS
|
||||||
async with await self._connect() as db:
|
async with self._connect() as db:
|
||||||
async with db.execute(
|
async with db.execute(
|
||||||
"SELECT COUNT(*) AS n FROM oauth_login_failures WHERE client_ip = ? AND failed_at >= ?",
|
"SELECT COUNT(*) AS n FROM oauth_login_failures WHERE client_ip = ? AND failed_at >= ?",
|
||||||
(client_ip, cutoff),
|
(client_ip, cutoff),
|
||||||
@@ -213,7 +218,7 @@ class LocalOAuthProvider:
|
|||||||
async def record_login_failure(self, client_ip: str) -> None:
|
async def record_login_failure(self, client_ip: str) -> None:
|
||||||
now = int(time.time())
|
now = int(time.time())
|
||||||
cutoff = now - LOGIN_WINDOW_SECONDS
|
cutoff = now - LOGIN_WINDOW_SECONDS
|
||||||
async with await self._connect() as db:
|
async with self._connect() as db:
|
||||||
await db.execute("DELETE FROM oauth_login_failures WHERE failed_at < ?", (cutoff,))
|
await db.execute("DELETE FROM oauth_login_failures WHERE failed_at < ?", (cutoff,))
|
||||||
await db.execute(
|
await db.execute(
|
||||||
"INSERT INTO oauth_login_failures (client_ip, failed_at) VALUES (?, ?)",
|
"INSERT INTO oauth_login_failures (client_ip, failed_at) VALUES (?, ?)",
|
||||||
@@ -222,7 +227,7 @@ class LocalOAuthProvider:
|
|||||||
await db.commit()
|
await db.commit()
|
||||||
|
|
||||||
async def clear_login_failures(self, client_ip: str) -> None:
|
async def clear_login_failures(self, client_ip: str) -> None:
|
||||||
async with await self._connect() as db:
|
async with self._connect() as db:
|
||||||
await db.execute("DELETE FROM oauth_login_failures WHERE client_ip = ?", (client_ip,))
|
await db.execute("DELETE FROM oauth_login_failures WHERE client_ip = ?", (client_ip,))
|
||||||
await db.commit()
|
await db.commit()
|
||||||
|
|
||||||
@@ -238,7 +243,7 @@ class LocalOAuthProvider:
|
|||||||
|
|
||||||
code_value = secrets.token_urlsafe(48)
|
code_value = secrets.token_urlsafe(48)
|
||||||
now = int(time.time())
|
now = int(time.time())
|
||||||
async with await self._connect() as db:
|
async with self._connect() as db:
|
||||||
await db.execute(
|
await db.execute(
|
||||||
"""INSERT INTO oauth_codes
|
"""INSERT INTO oauth_codes
|
||||||
(code, client_id, redirect_uri, redirect_uri_provided_explicitly,
|
(code, client_id, redirect_uri, redirect_uri_provided_explicitly,
|
||||||
@@ -267,7 +272,7 @@ class LocalOAuthProvider:
|
|||||||
async def load_authorization_code(
|
async def load_authorization_code(
|
||||||
self, client: OAuthClientInformationFull, authorization_code: str
|
self, client: OAuthClientInformationFull, authorization_code: str
|
||||||
) -> AuthorizationCode | None:
|
) -> AuthorizationCode | None:
|
||||||
async with await self._connect() as db:
|
async with self._connect() as db:
|
||||||
async with db.execute(
|
async with db.execute(
|
||||||
"SELECT * FROM oauth_codes WHERE code = ? AND client_id = ?",
|
"SELECT * FROM oauth_codes WHERE code = ? AND client_id = ?",
|
||||||
(authorization_code, client.client_id),
|
(authorization_code, client.client_id),
|
||||||
@@ -295,7 +300,7 @@ class LocalOAuthProvider:
|
|||||||
now = int(time.time())
|
now = int(time.time())
|
||||||
access = secrets.token_urlsafe(48)
|
access = secrets.token_urlsafe(48)
|
||||||
refresh = secrets.token_urlsafe(48)
|
refresh = secrets.token_urlsafe(48)
|
||||||
async with await self._connect() as db:
|
async with self._connect() as db:
|
||||||
await db.execute("BEGIN IMMEDIATE")
|
await db.execute("BEGIN IMMEDIATE")
|
||||||
async with db.execute(
|
async with db.execute(
|
||||||
"SELECT expires_at FROM oauth_codes WHERE code = ? AND client_id = ?",
|
"SELECT expires_at FROM oauth_codes WHERE code = ? AND client_id = ?",
|
||||||
@@ -335,7 +340,7 @@ class LocalOAuthProvider:
|
|||||||
self, client: OAuthClientInformationFull, refresh_token: str
|
self, client: OAuthClientInformationFull, refresh_token: str
|
||||||
) -> RefreshToken | None:
|
) -> RefreshToken | None:
|
||||||
now = int(time.time())
|
now = int(time.time())
|
||||||
async with await self._connect() as db:
|
async with self._connect() as db:
|
||||||
async with db.execute(
|
async with db.execute(
|
||||||
"""SELECT * FROM oauth_tokens
|
"""SELECT * FROM oauth_tokens
|
||||||
WHERE refresh_token = ? AND client_id = ? AND refresh_expires_at >= ?""",
|
WHERE refresh_token = ? AND client_id = ? AND refresh_expires_at >= ?""",
|
||||||
@@ -365,7 +370,7 @@ class LocalOAuthProvider:
|
|||||||
raise TokenError("invalid_scope", "A refresh token cannot grant additional permissions")
|
raise TokenError("invalid_scope", "A refresh token cannot grant additional permissions")
|
||||||
access = secrets.token_urlsafe(48)
|
access = secrets.token_urlsafe(48)
|
||||||
refresh = secrets.token_urlsafe(48)
|
refresh = secrets.token_urlsafe(48)
|
||||||
async with await self._connect() as db:
|
async with self._connect() as db:
|
||||||
await db.execute("BEGIN IMMEDIATE")
|
await db.execute("BEGIN IMMEDIATE")
|
||||||
async with db.execute(
|
async with db.execute(
|
||||||
"""SELECT * FROM oauth_tokens WHERE refresh_token = ? AND client_id = ?
|
"""SELECT * FROM oauth_tokens WHERE refresh_token = ? AND client_id = ?
|
||||||
@@ -406,7 +411,7 @@ class LocalOAuthProvider:
|
|||||||
|
|
||||||
async def load_access_token(self, token: str) -> AccessToken | None:
|
async def load_access_token(self, token: str) -> AccessToken | None:
|
||||||
now = int(time.time())
|
now = int(time.time())
|
||||||
async with await self._connect() as db:
|
async with self._connect() as db:
|
||||||
async with db.execute(
|
async with db.execute(
|
||||||
"SELECT * FROM oauth_tokens WHERE access_token = ? AND access_expires_at >= ?",
|
"SELECT * FROM oauth_tokens WHERE access_token = ? AND access_expires_at >= ?",
|
||||||
(token, now),
|
(token, now),
|
||||||
@@ -425,7 +430,7 @@ class LocalOAuthProvider:
|
|||||||
|
|
||||||
async def revoke_token(self, token: AccessToken | RefreshToken) -> None:
|
async def revoke_token(self, token: AccessToken | RefreshToken) -> None:
|
||||||
column = "access_token" if isinstance(token, AccessToken) else "refresh_token"
|
column = "access_token" if isinstance(token, AccessToken) else "refresh_token"
|
||||||
async with await self._connect() as db:
|
async with self._connect() as db:
|
||||||
await db.execute(f"DELETE FROM oauth_tokens WHERE {column} = ?", (token.token,))
|
await db.execute(f"DELETE FROM oauth_tokens WHERE {column} = ?", (token.token,))
|
||||||
await db.commit()
|
await db.commit()
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user