kaappu-sdk 0.1.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Kaappu
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,129 @@
1
+ Metadata-Version: 2.4
2
+ Name: kaappu-sdk
3
+ Version: 0.1.0
4
+ Summary: JWT authentication and permission-based authorization for Python services
5
+ License: MIT
6
+ Requires-Python: >=3.9
7
+ Description-Content-Type: text/markdown
8
+ License-File: LICENSE
9
+ Requires-Dist: PyJWT[crypto]>=2.8.0
10
+ Requires-Dist: requests>=2.31.0
11
+ Provides-Extra: flask
12
+ Requires-Dist: Flask>=2.3.0; extra == "flask"
13
+ Provides-Extra: fastapi
14
+ Requires-Dist: fastapi>=0.100.0; extra == "fastapi"
15
+ Provides-Extra: dev
16
+ Requires-Dist: pytest>=7.0.0; extra == "dev"
17
+ Dynamic: license-file
18
+
19
+ # kaappu-sdk
20
+
21
+ JWT authentication and permission-based authorization for Python services. Works with Flask, FastAPI, or any Python HTTP framework.
22
+
23
+ ## Install
24
+
25
+ ```bash
26
+ pip install kaappu-sdk
27
+
28
+ # With Flask support
29
+ pip install kaappu-sdk[flask]
30
+
31
+ # With FastAPI support
32
+ pip install kaappu-sdk[fastapi]
33
+ ```
34
+
35
+ ## Quick Start
36
+
37
+ ### Permission Checking
38
+
39
+ ```python
40
+ from kaappu import check_permission, check_all_permissions, check_any_permission
41
+
42
+ user_perms = ["users:read", "roles:read", "gateway:*"]
43
+
44
+ check_permission(user_perms, "users:read") # True
45
+ check_permission(user_perms, "users:delete") # False
46
+ check_permission(user_perms, "gateway:view") # True (wildcard)
47
+ check_all_permissions(user_perms, ["users:read", "roles:read"]) # True
48
+ check_any_permission(user_perms, ["users:delete", "roles:read"]) # True
49
+ ```
50
+
51
+ Wildcard support:
52
+ - `*` -- super wildcard, matches any permission
53
+ - `resource:*` -- matches any action on that resource
54
+
55
+ ### Flask Route Protection
56
+
57
+ ```python
58
+ from flask import Flask
59
+ from kaappu.decorators import require_permission
60
+
61
+ app = Flask(__name__)
62
+
63
+ @app.route("/api/users")
64
+ @require_permission("users:read")
65
+ def list_users():
66
+ return {"users": []}
67
+ ```
68
+
69
+ ### Security Context
70
+
71
+ ```python
72
+ from kaappu import SecurityContext
73
+ from kaappu.context import set_context, get_context
74
+
75
+ # Set context (typically in middleware)
76
+ ctx = SecurityContext(
77
+ user_id="u_123",
78
+ account_id="acc_456",
79
+ email="user@example.com",
80
+ permissions=["users:read", "roles:read"],
81
+ )
82
+ set_context(ctx)
83
+
84
+ # Read context anywhere in the same thread
85
+ current = get_context()
86
+ print(current.email)
87
+ ```
88
+
89
+ ### API Client
90
+
91
+ ```python
92
+ from kaappu import KaappuClient
93
+
94
+ client = KaappuClient(
95
+ base_url="https://your-kaappu-instance",
96
+ publishable_key="pk_live_...",
97
+ )
98
+
99
+ # Sign in
100
+ result = client.sign_in("user@example.com", "password")
101
+ access_token = result["accessToken"]
102
+
103
+ # Get current user
104
+ user = client.get_me(access_token)
105
+
106
+ # Refresh token
107
+ new_tokens = client.refresh_token(result["refreshToken"])
108
+ ```
109
+
110
+ ## API
111
+
112
+ | Function / Class | Description |
113
+ |------------------|-------------|
114
+ | `check_permission(perms, required)` | Check a single permission with wildcard support |
115
+ | `check_all_permissions(perms, required)` | Check that ALL permissions are satisfied |
116
+ | `check_any_permission(perms, required)` | Check that ANY permission is satisfied |
117
+ | `SecurityContext` | Dataclass holding user identity and permissions |
118
+ | `set_context(ctx)` / `get_context()` | Thread-local security context storage |
119
+ | `require_permission(perm)` | Flask route decorator for permission enforcement |
120
+ | `KaappuClient` | API client for sign-in, sign-up, refresh, and user fetch |
121
+
122
+ ## Requirements
123
+
124
+ - Python 3.9+
125
+ - PyJWT 2.8+
126
+
127
+ ## License
128
+
129
+ MIT
@@ -0,0 +1,111 @@
1
+ # kaappu-sdk
2
+
3
+ JWT authentication and permission-based authorization for Python services. Works with Flask, FastAPI, or any Python HTTP framework.
4
+
5
+ ## Install
6
+
7
+ ```bash
8
+ pip install kaappu-sdk
9
+
10
+ # With Flask support
11
+ pip install kaappu-sdk[flask]
12
+
13
+ # With FastAPI support
14
+ pip install kaappu-sdk[fastapi]
15
+ ```
16
+
17
+ ## Quick Start
18
+
19
+ ### Permission Checking
20
+
21
+ ```python
22
+ from kaappu import check_permission, check_all_permissions, check_any_permission
23
+
24
+ user_perms = ["users:read", "roles:read", "gateway:*"]
25
+
26
+ check_permission(user_perms, "users:read") # True
27
+ check_permission(user_perms, "users:delete") # False
28
+ check_permission(user_perms, "gateway:view") # True (wildcard)
29
+ check_all_permissions(user_perms, ["users:read", "roles:read"]) # True
30
+ check_any_permission(user_perms, ["users:delete", "roles:read"]) # True
31
+ ```
32
+
33
+ Wildcard support:
34
+ - `*` -- super wildcard, matches any permission
35
+ - `resource:*` -- matches any action on that resource
36
+
37
+ ### Flask Route Protection
38
+
39
+ ```python
40
+ from flask import Flask
41
+ from kaappu.decorators import require_permission
42
+
43
+ app = Flask(__name__)
44
+
45
+ @app.route("/api/users")
46
+ @require_permission("users:read")
47
+ def list_users():
48
+ return {"users": []}
49
+ ```
50
+
51
+ ### Security Context
52
+
53
+ ```python
54
+ from kaappu import SecurityContext
55
+ from kaappu.context import set_context, get_context
56
+
57
+ # Set context (typically in middleware)
58
+ ctx = SecurityContext(
59
+ user_id="u_123",
60
+ account_id="acc_456",
61
+ email="user@example.com",
62
+ permissions=["users:read", "roles:read"],
63
+ )
64
+ set_context(ctx)
65
+
66
+ # Read context anywhere in the same thread
67
+ current = get_context()
68
+ print(current.email)
69
+ ```
70
+
71
+ ### API Client
72
+
73
+ ```python
74
+ from kaappu import KaappuClient
75
+
76
+ client = KaappuClient(
77
+ base_url="https://your-kaappu-instance",
78
+ publishable_key="pk_live_...",
79
+ )
80
+
81
+ # Sign in
82
+ result = client.sign_in("user@example.com", "password")
83
+ access_token = result["accessToken"]
84
+
85
+ # Get current user
86
+ user = client.get_me(access_token)
87
+
88
+ # Refresh token
89
+ new_tokens = client.refresh_token(result["refreshToken"])
90
+ ```
91
+
92
+ ## API
93
+
94
+ | Function / Class | Description |
95
+ |------------------|-------------|
96
+ | `check_permission(perms, required)` | Check a single permission with wildcard support |
97
+ | `check_all_permissions(perms, required)` | Check that ALL permissions are satisfied |
98
+ | `check_any_permission(perms, required)` | Check that ANY permission is satisfied |
99
+ | `SecurityContext` | Dataclass holding user identity and permissions |
100
+ | `set_context(ctx)` / `get_context()` | Thread-local security context storage |
101
+ | `require_permission(perm)` | Flask route decorator for permission enforcement |
102
+ | `KaappuClient` | API client for sign-in, sign-up, refresh, and user fetch |
103
+
104
+ ## Requirements
105
+
106
+ - Python 3.9+
107
+ - PyJWT 2.8+
108
+
109
+ ## License
110
+
111
+ MIT
@@ -0,0 +1,14 @@
1
+ """Kaappu SDK for Python — JWT authentication and permission-based authorization."""
2
+
3
+ from kaappu.permissions import check_permission, check_all_permissions, check_any_permission
4
+ from kaappu.context import SecurityContext
5
+ from kaappu.client import KaappuClient
6
+
7
+ __version__ = "0.1.0"
8
+ __all__ = [
9
+ "check_permission",
10
+ "check_all_permissions",
11
+ "check_any_permission",
12
+ "SecurityContext",
13
+ "KaappuClient",
14
+ ]
@@ -0,0 +1,71 @@
1
+ """API client for Kaappu Identity — sign in, sign up, refresh, verify."""
2
+
3
+ from typing import Any, Dict, Optional
4
+ import requests
5
+
6
+
7
+ class KaappuClient:
8
+ """Framework-agnostic client for Kaappu Identity APIs."""
9
+
10
+ def __init__(self, base_url: str = "http://localhost:9091", publishable_key: str = ""):
11
+ self.base_url = base_url.rstrip("/")
12
+ self.publishable_key = publishable_key
13
+
14
+ def get_tenant_config(self) -> Optional[Dict[str, Any]]:
15
+ """Fetch tenant config (auth methods, branding, bot protection)."""
16
+ try:
17
+ r = requests.get(f"{self.base_url}/api/v1/accounts/config", params={"pk": self.publishable_key})
18
+ return r.json().get("data") if r.ok else None
19
+ except Exception:
20
+ return None
21
+
22
+ def sign_in(self, email: str, password: str, account_id: str = "default") -> Dict[str, Any]:
23
+ """Sign in with email + password. Returns accessToken, refreshToken, user."""
24
+ r = requests.post(f"{self.base_url}/api/v1/idm/auth/sign-in", json={
25
+ "email": email, "password": password, "accountId": account_id,
26
+ })
27
+ data = r.json()
28
+ if not data.get("success"):
29
+ raise Exception(data.get("error", "Sign in failed"))
30
+ return data["data"]
31
+
32
+ def sign_up(self, email: str, password: str, first_name: str = "", last_name: str = "",
33
+ account_id: str = "default") -> Dict[str, Any]:
34
+ """Sign up a new user. Returns accessToken, refreshToken, user."""
35
+ r = requests.post(f"{self.base_url}/api/v1/idm/auth/sign-up", json={
36
+ "email": email, "password": password,
37
+ "firstName": first_name, "lastName": last_name, "accountId": account_id,
38
+ })
39
+ data = r.json()
40
+ if not data.get("success"):
41
+ raise Exception(data.get("error", "Sign up failed"))
42
+ return data["data"]
43
+
44
+ def refresh_token(self, refresh_token: str) -> Optional[Dict[str, Any]]:
45
+ """Refresh an access token."""
46
+ try:
47
+ r = requests.post(f"{self.base_url}/api/v1/idm/auth/refresh", json={
48
+ "refreshToken": refresh_token,
49
+ })
50
+ return r.json().get("data") if r.ok else None
51
+ except Exception:
52
+ return None
53
+
54
+ def get_me(self, access_token: str) -> Optional[Dict[str, Any]]:
55
+ """Get current user profile."""
56
+ try:
57
+ r = requests.get(f"{self.base_url}/api/v1/idm/auth/me", headers={
58
+ "Authorization": f"Bearer {access_token}",
59
+ })
60
+ return r.json().get("data", {}).get("user") if r.ok else None
61
+ except Exception:
62
+ return None
63
+
64
+ def sign_out(self, access_token: str) -> None:
65
+ """Sign out — invalidate session."""
66
+ try:
67
+ requests.post(f"{self.base_url}/api/v1/idm/auth/sign-out", headers={
68
+ "Authorization": f"Bearer {access_token}",
69
+ })
70
+ except Exception:
71
+ pass
@@ -0,0 +1,33 @@
1
+ """Thread-local security context for the authenticated user."""
2
+
3
+ import threading
4
+ from dataclasses import dataclass, field
5
+ from typing import List, Optional
6
+
7
+
8
+ @dataclass
9
+ class SecurityContext:
10
+ """Holds the authenticated user's identity and permissions."""
11
+ user_id: str = ""
12
+ account_id: str = ""
13
+ email: str = ""
14
+ session_id: str = ""
15
+ permissions: List[str] = field(default_factory=list)
16
+
17
+
18
+ _context = threading.local()
19
+
20
+
21
+ def set_context(ctx: SecurityContext) -> None:
22
+ """Set the security context for the current thread."""
23
+ _context.kaappu = ctx
24
+
25
+
26
+ def get_context() -> Optional[SecurityContext]:
27
+ """Get the security context for the current thread."""
28
+ return getattr(_context, "kaappu", None)
29
+
30
+
31
+ def clear_context() -> None:
32
+ """Clear the security context for the current thread."""
33
+ _context.kaappu = None
@@ -0,0 +1,45 @@
1
+ """
2
+ Decorators for Flask and FastAPI route protection.
3
+
4
+ Flask usage:
5
+ @app.route("/api/users")
6
+ @require_permission("users:read")
7
+ def list_users():
8
+ ...
9
+
10
+ FastAPI usage:
11
+ @app.get("/api/users")
12
+ async def list_users(ctx: SecurityContext = Depends(get_kaappu_context)):
13
+ ...
14
+ """
15
+
16
+ from functools import wraps
17
+ from typing import Callable
18
+
19
+ from kaappu.context import get_context
20
+ from kaappu.permissions import check_permission
21
+
22
+
23
+ def require_permission(permission: str) -> Callable:
24
+ """
25
+ Decorator that checks the current SecurityContext for the required permission.
26
+ Returns 403 if the permission check fails.
27
+ Works with Flask routes.
28
+ """
29
+ def decorator(f: Callable) -> Callable:
30
+ @wraps(f)
31
+ def wrapper(*args, **kwargs):
32
+ ctx = get_context()
33
+ if ctx is None or not check_permission(ctx.permissions, permission):
34
+ # Flask import deferred to avoid hard dependency
35
+ try:
36
+ from flask import jsonify
37
+ return jsonify({
38
+ "error": f"Forbidden: Requires {permission} permission",
39
+ "code": "forbidden",
40
+ }), 403
41
+ except ImportError:
42
+ raise PermissionError(f"Requires {permission} permission")
43
+ return f(*args, **kwargs)
44
+ return wrapper
45
+ return decorator
@@ -0,0 +1,37 @@
1
+ """
2
+ Framework-agnostic permission checking with wildcard support.
3
+ Works with Flask, FastAPI, Django, or any Python service.
4
+ """
5
+
6
+ from typing import List, Optional
7
+
8
+
9
+ def check_permission(user_permissions: Optional[List[str]], required: str) -> bool:
10
+ """
11
+ Check if the user's permissions satisfy the required permission.
12
+
13
+ Supports:
14
+ - Exact match: 'users:read' satisfies 'users:read'
15
+ - Super wildcard: '*' satisfies any permission
16
+ - Resource wildcard: 'users:*' satisfies 'users:read', 'users:delete', etc.
17
+ """
18
+ if not required:
19
+ return True
20
+ if not user_permissions:
21
+ return False
22
+
23
+ resource = required.split(":")[0]
24
+ return any(
25
+ p == "*" or p == required or p == f"{resource}:*"
26
+ for p in user_permissions
27
+ )
28
+
29
+
30
+ def check_all_permissions(user_permissions: Optional[List[str]], required: List[str]) -> bool:
31
+ """Check if the user has ALL required permissions."""
32
+ return all(check_permission(user_permissions, r) for r in required)
33
+
34
+
35
+ def check_any_permission(user_permissions: Optional[List[str]], required: List[str]) -> bool:
36
+ """Check if the user has ANY of the required permissions."""
37
+ return any(check_permission(user_permissions, r) for r in required)
@@ -0,0 +1,129 @@
1
+ Metadata-Version: 2.4
2
+ Name: kaappu-sdk
3
+ Version: 0.1.0
4
+ Summary: JWT authentication and permission-based authorization for Python services
5
+ License: MIT
6
+ Requires-Python: >=3.9
7
+ Description-Content-Type: text/markdown
8
+ License-File: LICENSE
9
+ Requires-Dist: PyJWT[crypto]>=2.8.0
10
+ Requires-Dist: requests>=2.31.0
11
+ Provides-Extra: flask
12
+ Requires-Dist: Flask>=2.3.0; extra == "flask"
13
+ Provides-Extra: fastapi
14
+ Requires-Dist: fastapi>=0.100.0; extra == "fastapi"
15
+ Provides-Extra: dev
16
+ Requires-Dist: pytest>=7.0.0; extra == "dev"
17
+ Dynamic: license-file
18
+
19
+ # kaappu-sdk
20
+
21
+ JWT authentication and permission-based authorization for Python services. Works with Flask, FastAPI, or any Python HTTP framework.
22
+
23
+ ## Install
24
+
25
+ ```bash
26
+ pip install kaappu-sdk
27
+
28
+ # With Flask support
29
+ pip install kaappu-sdk[flask]
30
+
31
+ # With FastAPI support
32
+ pip install kaappu-sdk[fastapi]
33
+ ```
34
+
35
+ ## Quick Start
36
+
37
+ ### Permission Checking
38
+
39
+ ```python
40
+ from kaappu import check_permission, check_all_permissions, check_any_permission
41
+
42
+ user_perms = ["users:read", "roles:read", "gateway:*"]
43
+
44
+ check_permission(user_perms, "users:read") # True
45
+ check_permission(user_perms, "users:delete") # False
46
+ check_permission(user_perms, "gateway:view") # True (wildcard)
47
+ check_all_permissions(user_perms, ["users:read", "roles:read"]) # True
48
+ check_any_permission(user_perms, ["users:delete", "roles:read"]) # True
49
+ ```
50
+
51
+ Wildcard support:
52
+ - `*` -- super wildcard, matches any permission
53
+ - `resource:*` -- matches any action on that resource
54
+
55
+ ### Flask Route Protection
56
+
57
+ ```python
58
+ from flask import Flask
59
+ from kaappu.decorators import require_permission
60
+
61
+ app = Flask(__name__)
62
+
63
+ @app.route("/api/users")
64
+ @require_permission("users:read")
65
+ def list_users():
66
+ return {"users": []}
67
+ ```
68
+
69
+ ### Security Context
70
+
71
+ ```python
72
+ from kaappu import SecurityContext
73
+ from kaappu.context import set_context, get_context
74
+
75
+ # Set context (typically in middleware)
76
+ ctx = SecurityContext(
77
+ user_id="u_123",
78
+ account_id="acc_456",
79
+ email="user@example.com",
80
+ permissions=["users:read", "roles:read"],
81
+ )
82
+ set_context(ctx)
83
+
84
+ # Read context anywhere in the same thread
85
+ current = get_context()
86
+ print(current.email)
87
+ ```
88
+
89
+ ### API Client
90
+
91
+ ```python
92
+ from kaappu import KaappuClient
93
+
94
+ client = KaappuClient(
95
+ base_url="https://your-kaappu-instance",
96
+ publishable_key="pk_live_...",
97
+ )
98
+
99
+ # Sign in
100
+ result = client.sign_in("user@example.com", "password")
101
+ access_token = result["accessToken"]
102
+
103
+ # Get current user
104
+ user = client.get_me(access_token)
105
+
106
+ # Refresh token
107
+ new_tokens = client.refresh_token(result["refreshToken"])
108
+ ```
109
+
110
+ ## API
111
+
112
+ | Function / Class | Description |
113
+ |------------------|-------------|
114
+ | `check_permission(perms, required)` | Check a single permission with wildcard support |
115
+ | `check_all_permissions(perms, required)` | Check that ALL permissions are satisfied |
116
+ | `check_any_permission(perms, required)` | Check that ANY permission is satisfied |
117
+ | `SecurityContext` | Dataclass holding user identity and permissions |
118
+ | `set_context(ctx)` / `get_context()` | Thread-local security context storage |
119
+ | `require_permission(perm)` | Flask route decorator for permission enforcement |
120
+ | `KaappuClient` | API client for sign-in, sign-up, refresh, and user fetch |
121
+
122
+ ## Requirements
123
+
124
+ - Python 3.9+
125
+ - PyJWT 2.8+
126
+
127
+ ## License
128
+
129
+ MIT
@@ -0,0 +1,14 @@
1
+ LICENSE
2
+ README.md
3
+ pyproject.toml
4
+ kaappu/__init__.py
5
+ kaappu/client.py
6
+ kaappu/context.py
7
+ kaappu/decorators.py
8
+ kaappu/permissions.py
9
+ kaappu_sdk.egg-info/PKG-INFO
10
+ kaappu_sdk.egg-info/SOURCES.txt
11
+ kaappu_sdk.egg-info/dependency_links.txt
12
+ kaappu_sdk.egg-info/requires.txt
13
+ kaappu_sdk.egg-info/top_level.txt
14
+ tests/test_permissions.py
@@ -0,0 +1,11 @@
1
+ PyJWT[crypto]>=2.8.0
2
+ requests>=2.31.0
3
+
4
+ [dev]
5
+ pytest>=7.0.0
6
+
7
+ [fastapi]
8
+ fastapi>=0.100.0
9
+
10
+ [flask]
11
+ Flask>=2.3.0
@@ -0,0 +1 @@
1
+ kaappu
@@ -0,0 +1,23 @@
1
+ [build-system]
2
+ requires = ["setuptools>=68.0", "wheel"]
3
+ build-backend = "setuptools.build_meta"
4
+
5
+ [project]
6
+ name = "kaappu-sdk"
7
+ version = "0.1.0"
8
+ description = "JWT authentication and permission-based authorization for Python services"
9
+ readme = "README.md"
10
+ license = {text = "MIT"}
11
+ requires-python = ">=3.9"
12
+ dependencies = [
13
+ "PyJWT[crypto]>=2.8.0",
14
+ "requests>=2.31.0",
15
+ ]
16
+
17
+ [project.optional-dependencies]
18
+ flask = ["Flask>=2.3.0"]
19
+ fastapi = ["fastapi>=0.100.0"]
20
+ dev = ["pytest>=7.0.0"]
21
+
22
+ [tool.setuptools.packages.find]
23
+ include = ["kaappu*"]
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
@@ -0,0 +1,65 @@
1
+ """Tests for the permission checking module."""
2
+
3
+ import pytest
4
+ from kaappu.permissions import check_permission, check_all_permissions, check_any_permission
5
+
6
+
7
+ class TestCheckPermission:
8
+ def test_exact_match(self):
9
+ assert check_permission(["users:read"], "users:read") is True
10
+
11
+ def test_no_match(self):
12
+ assert check_permission(["users:read"], "users:delete") is False
13
+
14
+ def test_super_wildcard(self):
15
+ assert check_permission(["*"], "anything:here") is True
16
+
17
+ def test_resource_wildcard(self):
18
+ assert check_permission(["users:*"], "users:delete") is True
19
+
20
+ def test_resource_wildcard_no_cross(self):
21
+ assert check_permission(["users:*"], "roles:read") is False
22
+
23
+ def test_empty_perms(self):
24
+ assert check_permission([], "users:read") is False
25
+
26
+ def test_none_perms(self):
27
+ assert check_permission(None, "users:read") is False
28
+
29
+ def test_empty_required(self):
30
+ assert check_permission([], "") is True
31
+
32
+ def test_owner_role(self):
33
+ assert check_permission(["*"], "gateway_instances:manage") is True
34
+
35
+ def test_admin_wildcards(self):
36
+ perms = ["users:*", "roles:*", "gateway:*"]
37
+ assert check_permission(perms, "users:delete") is True
38
+ assert check_permission(perms, "gateway:view") is True
39
+ assert check_permission(perms, "audit:read") is False
40
+
41
+ def test_viewer_denied_write(self):
42
+ perms = ["users:read", "roles:read"]
43
+ assert check_permission(perms, "users:read") is True
44
+ assert check_permission(perms, "users:delete") is False
45
+
46
+ def test_member_chat(self):
47
+ perms = ["governance_chat:use", "gateway_chat:use", "users:read"]
48
+ assert check_permission(perms, "governance_chat:use") is True
49
+ assert check_permission(perms, "gateway_instances:manage") is False
50
+
51
+
52
+ class TestCheckAllPermissions:
53
+ def test_all_present(self):
54
+ assert check_all_permissions(["users:read", "roles:read"], ["users:read", "roles:read"]) is True
55
+
56
+ def test_one_missing(self):
57
+ assert check_all_permissions(["users:read"], ["users:read", "users:delete"]) is False
58
+
59
+
60
+ class TestCheckAnyPermission:
61
+ def test_one_matches(self):
62
+ assert check_any_permission(["users:read"], ["users:delete", "users:read"]) is True
63
+
64
+ def test_none_match(self):
65
+ assert check_any_permission(["users:read"], ["roles:read", "groups:read"]) is False