yirifi-ops-auth-client 3.2.3__py3-none-any.whl
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.
- yirifi_ops_auth/__init__.py +58 -0
- yirifi_ops_auth/client.py +154 -0
- yirifi_ops_auth/decorators.py +213 -0
- yirifi_ops_auth/deeplink/__init__.py +210 -0
- yirifi_ops_auth/deeplink/blueprint.py +155 -0
- yirifi_ops_auth/deeplink/environment.py +156 -0
- yirifi_ops_auth/deeplink/federation.py +409 -0
- yirifi_ops_auth/deeplink/jinja.py +316 -0
- yirifi_ops_auth/deeplink/registry.py +401 -0
- yirifi_ops_auth/deeplink/resolver.py +208 -0
- yirifi_ops_auth/deeplink/yaml_loader.py +242 -0
- yirifi_ops_auth/exceptions.py +32 -0
- yirifi_ops_auth/local_user.py +124 -0
- yirifi_ops_auth/middleware.py +281 -0
- yirifi_ops_auth/models.py +80 -0
- yirifi_ops_auth_client-3.2.3.dist-info/METADATA +15 -0
- yirifi_ops_auth_client-3.2.3.dist-info/RECORD +19 -0
- yirifi_ops_auth_client-3.2.3.dist-info/WHEEL +5 -0
- yirifi_ops_auth_client-3.2.3.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
"""Yirifi Ops Auth Client - Authentication library for Yirifi Ops microsites."""
|
|
2
|
+
|
|
3
|
+
from yirifi_ops_auth.client import YirifiOpsAuthClient
|
|
4
|
+
from yirifi_ops_auth.middleware import setup_auth_middleware, AuthMiddleware
|
|
5
|
+
from yirifi_ops_auth.decorators import (
|
|
6
|
+
require_auth,
|
|
7
|
+
require_admin,
|
|
8
|
+
require_access,
|
|
9
|
+
# RBAC decorators
|
|
10
|
+
require_permission,
|
|
11
|
+
require_any_permission,
|
|
12
|
+
require_all_permissions,
|
|
13
|
+
require_role,
|
|
14
|
+
# Helpers
|
|
15
|
+
get_current_user,
|
|
16
|
+
is_authenticated,
|
|
17
|
+
)
|
|
18
|
+
from yirifi_ops_auth.models import AuthUser, VerifyResult
|
|
19
|
+
from yirifi_ops_auth.exceptions import AuthenticationError, AuthorizationError
|
|
20
|
+
from yirifi_ops_auth.local_user import (
|
|
21
|
+
ensure_local_user,
|
|
22
|
+
get_current_user_id,
|
|
23
|
+
get_current_user_context,
|
|
24
|
+
LocalUserMixin,
|
|
25
|
+
)
|
|
26
|
+
|
|
27
|
+
__version__ = '3.2.1'
|
|
28
|
+
|
|
29
|
+
__all__ = [
|
|
30
|
+
# Client
|
|
31
|
+
'YirifiOpsAuthClient',
|
|
32
|
+
# Middleware
|
|
33
|
+
'setup_auth_middleware',
|
|
34
|
+
'AuthMiddleware',
|
|
35
|
+
# Auth decorators
|
|
36
|
+
'require_auth',
|
|
37
|
+
'require_admin', # deprecated
|
|
38
|
+
'require_access',
|
|
39
|
+
# RBAC decorators
|
|
40
|
+
'require_permission',
|
|
41
|
+
'require_any_permission',
|
|
42
|
+
'require_all_permissions',
|
|
43
|
+
'require_role',
|
|
44
|
+
# Helpers
|
|
45
|
+
'get_current_user',
|
|
46
|
+
'is_authenticated',
|
|
47
|
+
# Local user helpers
|
|
48
|
+
'ensure_local_user',
|
|
49
|
+
'get_current_user_id',
|
|
50
|
+
'get_current_user_context',
|
|
51
|
+
'LocalUserMixin',
|
|
52
|
+
# Models
|
|
53
|
+
'AuthUser',
|
|
54
|
+
'VerifyResult',
|
|
55
|
+
# Exceptions
|
|
56
|
+
'AuthenticationError',
|
|
57
|
+
'AuthorizationError',
|
|
58
|
+
]
|
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
"""HTTP client for auth service communication."""
|
|
2
|
+
import httpx
|
|
3
|
+
from typing import Optional
|
|
4
|
+
|
|
5
|
+
from yirifi_ops_auth.models import AuthUser, VerifyResult
|
|
6
|
+
from yirifi_ops_auth.exceptions import AuthServiceError
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class YirifiOpsAuthClient:
|
|
10
|
+
"""Client for communicating with the Yirifi Ops Auth Service."""
|
|
11
|
+
|
|
12
|
+
def __init__(
|
|
13
|
+
self,
|
|
14
|
+
auth_service_url: str,
|
|
15
|
+
timeout: float = 5.0,
|
|
16
|
+
verify_ssl: bool = True
|
|
17
|
+
):
|
|
18
|
+
"""
|
|
19
|
+
Initialize the auth client.
|
|
20
|
+
|
|
21
|
+
Args:
|
|
22
|
+
auth_service_url: Base URL of the auth service (e.g., 'http://localhost:5100')
|
|
23
|
+
timeout: Request timeout in seconds
|
|
24
|
+
verify_ssl: Whether to verify SSL certificates
|
|
25
|
+
"""
|
|
26
|
+
self.auth_service_url = auth_service_url.rstrip('/')
|
|
27
|
+
self.timeout = timeout
|
|
28
|
+
self._client = httpx.Client(
|
|
29
|
+
timeout=timeout,
|
|
30
|
+
verify=verify_ssl
|
|
31
|
+
)
|
|
32
|
+
|
|
33
|
+
def verify(
|
|
34
|
+
self,
|
|
35
|
+
session_cookie: Optional[str] = None,
|
|
36
|
+
api_key: Optional[str] = None,
|
|
37
|
+
microsite_id: Optional[str] = None,
|
|
38
|
+
app_id: Optional[str] = None,
|
|
39
|
+
permission: Optional[str] = None
|
|
40
|
+
) -> VerifyResult:
|
|
41
|
+
"""
|
|
42
|
+
Verify session cookie or API key with auth service.
|
|
43
|
+
|
|
44
|
+
Args:
|
|
45
|
+
session_cookie: Session cookie value (yirifi_ops_session)
|
|
46
|
+
api_key: API key for programmatic access
|
|
47
|
+
microsite_id: Optional microsite ID to check access for (deprecated, use app_id)
|
|
48
|
+
app_id: Application ID to get app-specific roles/permissions
|
|
49
|
+
permission: Specific permission to verify
|
|
50
|
+
|
|
51
|
+
Returns:
|
|
52
|
+
VerifyResult with validation status, user info, and permissions
|
|
53
|
+
"""
|
|
54
|
+
if not session_cookie and not api_key:
|
|
55
|
+
return VerifyResult(
|
|
56
|
+
valid=False,
|
|
57
|
+
error='no_credentials',
|
|
58
|
+
redirect_url=f'{self.auth_service_url}/auth/login'
|
|
59
|
+
)
|
|
60
|
+
|
|
61
|
+
headers = {}
|
|
62
|
+
if session_cookie:
|
|
63
|
+
headers['Cookie'] = f'yirifi_ops_session={session_cookie}'
|
|
64
|
+
if api_key:
|
|
65
|
+
headers['X-API-Key'] = api_key
|
|
66
|
+
|
|
67
|
+
payload = {}
|
|
68
|
+
# Support both app_id (new) and microsite_id (legacy)
|
|
69
|
+
if app_id:
|
|
70
|
+
payload['app_id'] = app_id
|
|
71
|
+
elif microsite_id:
|
|
72
|
+
payload['app_id'] = microsite_id
|
|
73
|
+
if permission:
|
|
74
|
+
payload['permission'] = permission
|
|
75
|
+
|
|
76
|
+
try:
|
|
77
|
+
response = self._client.post(
|
|
78
|
+
f'{self.auth_service_url}/api/v1/auth/verify',
|
|
79
|
+
headers=headers,
|
|
80
|
+
json=payload if payload else None
|
|
81
|
+
)
|
|
82
|
+
|
|
83
|
+
data = response.json()
|
|
84
|
+
|
|
85
|
+
if data.get('valid'):
|
|
86
|
+
user_data = data['user']
|
|
87
|
+
access_data = data.get('access', {})
|
|
88
|
+
|
|
89
|
+
return VerifyResult(
|
|
90
|
+
valid=True,
|
|
91
|
+
user=AuthUser(
|
|
92
|
+
user_id=user_data.get('user_id'),
|
|
93
|
+
id=user_data['id'],
|
|
94
|
+
email=user_data['email'],
|
|
95
|
+
display_name=user_data['display_name'],
|
|
96
|
+
is_admin=user_data.get('is_admin', False),
|
|
97
|
+
microsites=access_data.get('microsites', user_data.get('microsites', [])),
|
|
98
|
+
# RBAC fields
|
|
99
|
+
roles=access_data.get('roles', []),
|
|
100
|
+
permissions=access_data.get('permissions', []),
|
|
101
|
+
effective_role=access_data.get('effective_role')
|
|
102
|
+
),
|
|
103
|
+
has_access=access_data.get('has_access', True),
|
|
104
|
+
role=access_data.get('effective_role', access_data.get('role'))
|
|
105
|
+
)
|
|
106
|
+
else:
|
|
107
|
+
# Ensure redirect_url is absolute (prepend auth_service_url if relative)
|
|
108
|
+
redirect_url = data.get('redirect_url', '/auth/login')
|
|
109
|
+
if redirect_url.startswith('/'):
|
|
110
|
+
redirect_url = f'{self.auth_service_url}{redirect_url}'
|
|
111
|
+
return VerifyResult(
|
|
112
|
+
valid=False,
|
|
113
|
+
error=data.get('error'),
|
|
114
|
+
redirect_url=redirect_url
|
|
115
|
+
)
|
|
116
|
+
|
|
117
|
+
except httpx.RequestError as e:
|
|
118
|
+
raise AuthServiceError(f'Failed to connect to auth service: {e}')
|
|
119
|
+
except Exception as e:
|
|
120
|
+
raise AuthServiceError(f'Auth verification failed: {e}')
|
|
121
|
+
|
|
122
|
+
def get_login_url(self, return_url: str = '') -> str:
|
|
123
|
+
"""Get the login URL with optional return URL."""
|
|
124
|
+
if return_url:
|
|
125
|
+
return f'{self.auth_service_url}/auth/login?return_url={return_url}'
|
|
126
|
+
return f'{self.auth_service_url}/auth/login'
|
|
127
|
+
|
|
128
|
+
def get_access_denied_url(self, app_id: str = '', return_url: str = '') -> str:
|
|
129
|
+
"""Get the access denied page URL with app context."""
|
|
130
|
+
from urllib.parse import urlencode
|
|
131
|
+
params = {}
|
|
132
|
+
if app_id:
|
|
133
|
+
params['app_id'] = app_id
|
|
134
|
+
if return_url:
|
|
135
|
+
params['return_url'] = return_url
|
|
136
|
+
if params:
|
|
137
|
+
return f'{self.auth_service_url}/auth/access-denied?{urlencode(params)}'
|
|
138
|
+
return f'{self.auth_service_url}/auth/access-denied'
|
|
139
|
+
|
|
140
|
+
def get_logout_url(self, return_url: str = '') -> str:
|
|
141
|
+
"""Get the logout URL with optional return URL."""
|
|
142
|
+
if return_url:
|
|
143
|
+
return f'{self.auth_service_url}/auth/logout?return_url={return_url}'
|
|
144
|
+
return f'{self.auth_service_url}/auth/logout'
|
|
145
|
+
|
|
146
|
+
def close(self):
|
|
147
|
+
"""Close the HTTP client."""
|
|
148
|
+
self._client.close()
|
|
149
|
+
|
|
150
|
+
def __enter__(self):
|
|
151
|
+
return self
|
|
152
|
+
|
|
153
|
+
def __exit__(self, *args):
|
|
154
|
+
self.close()
|
|
@@ -0,0 +1,213 @@
|
|
|
1
|
+
"""Authentication decorators for Flask routes."""
|
|
2
|
+
from functools import wraps
|
|
3
|
+
from flask import g
|
|
4
|
+
|
|
5
|
+
from yirifi_ops_auth.exceptions import AuthenticationError, AuthorizationError
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
def require_auth(f):
|
|
9
|
+
"""
|
|
10
|
+
Decorator to require authentication on a route.
|
|
11
|
+
|
|
12
|
+
The middleware already handles authentication, but this decorator
|
|
13
|
+
provides an explicit check for routes that must have a user.
|
|
14
|
+
|
|
15
|
+
Example:
|
|
16
|
+
@app.route('/profile')
|
|
17
|
+
@require_auth
|
|
18
|
+
def profile():
|
|
19
|
+
return f"Hello {g.current_user.display_name}"
|
|
20
|
+
"""
|
|
21
|
+
@wraps(f)
|
|
22
|
+
def decorated(*args, **kwargs):
|
|
23
|
+
if not hasattr(g, 'current_user') or g.current_user is None:
|
|
24
|
+
raise AuthenticationError('Authentication required')
|
|
25
|
+
return f(*args, **kwargs)
|
|
26
|
+
return decorated
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
def require_admin(f):
|
|
30
|
+
"""
|
|
31
|
+
Decorator to require admin privileges.
|
|
32
|
+
|
|
33
|
+
DEPRECATED: Use @require_permission('admin:access') or @require_role('super_admin') instead.
|
|
34
|
+
|
|
35
|
+
Example:
|
|
36
|
+
@app.route('/admin')
|
|
37
|
+
@require_admin
|
|
38
|
+
def admin_panel():
|
|
39
|
+
return "Admin only content"
|
|
40
|
+
"""
|
|
41
|
+
@wraps(f)
|
|
42
|
+
def decorated(*args, **kwargs):
|
|
43
|
+
if not hasattr(g, 'current_user') or g.current_user is None:
|
|
44
|
+
raise AuthenticationError('Authentication required')
|
|
45
|
+
# Use role check instead of deprecated is_admin field
|
|
46
|
+
if not g.current_user.has_role('super_admin'):
|
|
47
|
+
raise AuthorizationError('Admin access required')
|
|
48
|
+
return f(*args, **kwargs)
|
|
49
|
+
return decorated
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
def require_permission(permission: str):
|
|
53
|
+
"""
|
|
54
|
+
Decorator factory to require a specific permission.
|
|
55
|
+
|
|
56
|
+
Args:
|
|
57
|
+
permission: Permission code to require (e.g., 'report:create')
|
|
58
|
+
|
|
59
|
+
Example:
|
|
60
|
+
@app.route('/reports', methods=['POST'])
|
|
61
|
+
@require_permission('report:create')
|
|
62
|
+
def create_report():
|
|
63
|
+
# User has report:create permission
|
|
64
|
+
return create_new_report(request.json)
|
|
65
|
+
"""
|
|
66
|
+
def decorator(f):
|
|
67
|
+
@wraps(f)
|
|
68
|
+
def decorated(*args, **kwargs):
|
|
69
|
+
if not hasattr(g, 'current_user') or g.current_user is None:
|
|
70
|
+
raise AuthenticationError('Authentication required')
|
|
71
|
+
|
|
72
|
+
if not g.current_user.has_permission(permission):
|
|
73
|
+
raise AuthorizationError(f'Permission denied: {permission}')
|
|
74
|
+
|
|
75
|
+
return f(*args, **kwargs)
|
|
76
|
+
return decorated
|
|
77
|
+
return decorator
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
def require_any_permission(*permissions: str):
|
|
81
|
+
"""
|
|
82
|
+
Decorator factory to require at least one of the specified permissions.
|
|
83
|
+
|
|
84
|
+
Args:
|
|
85
|
+
*permissions: Permission codes to check
|
|
86
|
+
|
|
87
|
+
Example:
|
|
88
|
+
@require_any_permission('report:read', 'report:create')
|
|
89
|
+
def view_reports():
|
|
90
|
+
return get_reports()
|
|
91
|
+
"""
|
|
92
|
+
def decorator(f):
|
|
93
|
+
@wraps(f)
|
|
94
|
+
def decorated(*args, **kwargs):
|
|
95
|
+
if not hasattr(g, 'current_user') or g.current_user is None:
|
|
96
|
+
raise AuthenticationError('Authentication required')
|
|
97
|
+
|
|
98
|
+
if not g.current_user.has_any_permission(*permissions):
|
|
99
|
+
raise AuthorizationError(
|
|
100
|
+
f'Permission denied. Required one of: {", ".join(permissions)}'
|
|
101
|
+
)
|
|
102
|
+
|
|
103
|
+
return f(*args, **kwargs)
|
|
104
|
+
return decorated
|
|
105
|
+
return decorator
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
def require_all_permissions(*permissions: str):
|
|
109
|
+
"""
|
|
110
|
+
Decorator factory to require all of the specified permissions.
|
|
111
|
+
|
|
112
|
+
Args:
|
|
113
|
+
*permissions: Permission codes to check
|
|
114
|
+
|
|
115
|
+
Example:
|
|
116
|
+
@require_all_permissions('report:read', 'data:export')
|
|
117
|
+
def export_report():
|
|
118
|
+
return generate_export()
|
|
119
|
+
"""
|
|
120
|
+
def decorator(f):
|
|
121
|
+
@wraps(f)
|
|
122
|
+
def decorated(*args, **kwargs):
|
|
123
|
+
if not hasattr(g, 'current_user') or g.current_user is None:
|
|
124
|
+
raise AuthenticationError('Authentication required')
|
|
125
|
+
|
|
126
|
+
if not g.current_user.has_all_permissions(*permissions):
|
|
127
|
+
missing = [p for p in permissions if not g.current_user.has_permission(p)]
|
|
128
|
+
raise AuthorizationError(
|
|
129
|
+
f'Permission denied. Missing: {", ".join(missing)}'
|
|
130
|
+
)
|
|
131
|
+
|
|
132
|
+
return f(*args, **kwargs)
|
|
133
|
+
return decorated
|
|
134
|
+
return decorator
|
|
135
|
+
|
|
136
|
+
|
|
137
|
+
def require_role(role: str):
|
|
138
|
+
"""
|
|
139
|
+
Decorator factory to require a specific role.
|
|
140
|
+
|
|
141
|
+
Note: Prefer using @require_permission() over @require_role() as it
|
|
142
|
+
provides more granular access control.
|
|
143
|
+
|
|
144
|
+
Args:
|
|
145
|
+
role: Role code to require (e.g., 'admin', 'editor')
|
|
146
|
+
|
|
147
|
+
Example:
|
|
148
|
+
@require_role('admin')
|
|
149
|
+
def admin_dashboard():
|
|
150
|
+
return render_template('admin/dashboard.html')
|
|
151
|
+
"""
|
|
152
|
+
def decorator(f):
|
|
153
|
+
@wraps(f)
|
|
154
|
+
def decorated(*args, **kwargs):
|
|
155
|
+
if not hasattr(g, 'current_user') or g.current_user is None:
|
|
156
|
+
raise AuthenticationError('Authentication required')
|
|
157
|
+
|
|
158
|
+
if not g.current_user.has_role(role):
|
|
159
|
+
raise AuthorizationError(f'Role required: {role}')
|
|
160
|
+
|
|
161
|
+
return f(*args, **kwargs)
|
|
162
|
+
return decorated
|
|
163
|
+
return decorator
|
|
164
|
+
|
|
165
|
+
|
|
166
|
+
def require_access(microsite_id: str):
|
|
167
|
+
"""
|
|
168
|
+
Decorator factory to require access to a specific microsite.
|
|
169
|
+
|
|
170
|
+
This is useful when a route needs access to a different microsite
|
|
171
|
+
than the one the app is registered under.
|
|
172
|
+
|
|
173
|
+
Example:
|
|
174
|
+
@app.route('/cross-site-data')
|
|
175
|
+
@require_access('other-microsite')
|
|
176
|
+
def cross_site():
|
|
177
|
+
return "Data from other microsite"
|
|
178
|
+
"""
|
|
179
|
+
def decorator(f):
|
|
180
|
+
@wraps(f)
|
|
181
|
+
def decorated(*args, **kwargs):
|
|
182
|
+
if not hasattr(g, 'current_user') or g.current_user is None:
|
|
183
|
+
raise AuthenticationError('Authentication required')
|
|
184
|
+
if not g.current_user.has_access_to(microsite_id):
|
|
185
|
+
raise AuthorizationError(f'Access denied to {microsite_id}')
|
|
186
|
+
return f(*args, **kwargs)
|
|
187
|
+
return decorated
|
|
188
|
+
return decorator
|
|
189
|
+
|
|
190
|
+
|
|
191
|
+
def get_current_user():
|
|
192
|
+
"""
|
|
193
|
+
Get the current authenticated user.
|
|
194
|
+
|
|
195
|
+
Returns:
|
|
196
|
+
AuthUser or None if not authenticated
|
|
197
|
+
|
|
198
|
+
Example:
|
|
199
|
+
user = get_current_user()
|
|
200
|
+
if user:
|
|
201
|
+
print(f"Logged in as {user.email}")
|
|
202
|
+
"""
|
|
203
|
+
return getattr(g, 'current_user', None)
|
|
204
|
+
|
|
205
|
+
|
|
206
|
+
def is_authenticated() -> bool:
|
|
207
|
+
"""
|
|
208
|
+
Check if the current request is authenticated.
|
|
209
|
+
|
|
210
|
+
Returns:
|
|
211
|
+
True if user is authenticated
|
|
212
|
+
"""
|
|
213
|
+
return hasattr(g, 'current_user') and g.current_user is not None
|
|
@@ -0,0 +1,210 @@
|
|
|
1
|
+
"""Deep linking module for cross-microsite navigation.
|
|
2
|
+
|
|
3
|
+
This module provides tools to generate URLs to entities across different
|
|
4
|
+
Yirifi Ops microsites with permission-aware rendering.
|
|
5
|
+
|
|
6
|
+
Quick Start:
|
|
7
|
+
1. Register your microsite's entities:
|
|
8
|
+
from yirifi_ops_auth.deeplink import register_entities
|
|
9
|
+
|
|
10
|
+
register_entities(
|
|
11
|
+
microsite_id="risk",
|
|
12
|
+
name="Risk Dashboard",
|
|
13
|
+
urls={"dev": "http://localhost:5012", "uat": "...", "prd": "..."},
|
|
14
|
+
entities=[
|
|
15
|
+
{"type": "risk_item", "path": "/items/{id}"},
|
|
16
|
+
]
|
|
17
|
+
)
|
|
18
|
+
|
|
19
|
+
2. Set up in your Flask app:
|
|
20
|
+
from yirifi_ops_auth.deeplink import setup_deeplinks
|
|
21
|
+
setup_deeplinks(app, env='dev')
|
|
22
|
+
|
|
23
|
+
3. Use in templates:
|
|
24
|
+
{{ cross_link('risk_item', item.r_yid, 'Open in Risk Dashboard') }}
|
|
25
|
+
|
|
26
|
+
4. Or use in Python:
|
|
27
|
+
from yirifi_ops_auth.deeplink import resolve_link
|
|
28
|
+
url = resolve_link('risk_item', 'r_yid_123')
|
|
29
|
+
|
|
30
|
+
Federation (v3.1.0+):
|
|
31
|
+
Enable cross-microsite entity discovery without manual registration:
|
|
32
|
+
|
|
33
|
+
setup_deeplinks(
|
|
34
|
+
app,
|
|
35
|
+
env='dev',
|
|
36
|
+
enable_federation=True, # Queries auth service + microsites
|
|
37
|
+
expose_references=True, # Exposes /api/v1/references
|
|
38
|
+
)
|
|
39
|
+
|
|
40
|
+
Now templates can use entities from ANY microsite:
|
|
41
|
+
{{ cross_link('reg_link', link.id) }} # Works without registering!
|
|
42
|
+
|
|
43
|
+
Registration Functions:
|
|
44
|
+
- register_entities: Register a microsite and its entities (bulk)
|
|
45
|
+
- register_microsite: Register a microsite configuration
|
|
46
|
+
- register_entity: Register a single entity definition
|
|
47
|
+
- load_from_yaml: Load entity definitions from YAML file
|
|
48
|
+
- clear_registry: Clear all registrations (for testing)
|
|
49
|
+
|
|
50
|
+
Resolution Functions:
|
|
51
|
+
- setup_deeplinks: Initialize deep linking for a Flask app
|
|
52
|
+
- resolve_link: Generate a URL for an entity
|
|
53
|
+
- deep_link_info: Get URL + accessibility info
|
|
54
|
+
- can_link_to: Check if user can access an entity's microsite
|
|
55
|
+
|
|
56
|
+
Template Helpers (available after setup_deeplinks):
|
|
57
|
+
- {{ deep_link(entity_type, entity_id) }} - Returns URL string
|
|
58
|
+
- {{ deep_link_info(entity_type, entity_id) }} - Returns DeepLinkInfo object
|
|
59
|
+
- {{ cross_link(entity_type, entity_id, label) }} - Renders permission-aware link
|
|
60
|
+
- {{ cross_link_button(entity_type, entity_id, label) }} - Renders styled button
|
|
61
|
+
|
|
62
|
+
Registry Query Functions:
|
|
63
|
+
- get_entity_definition: Get definition for an entity type
|
|
64
|
+
- list_entity_types: List all registered entity types
|
|
65
|
+
- list_entity_types_for_microsite: List entity types for a microsite
|
|
66
|
+
- list_microsites: List all registered microsites
|
|
67
|
+
|
|
68
|
+
Federation Functions:
|
|
69
|
+
- configure_federation: Configure and start federation client
|
|
70
|
+
- get_federation_client: Get the federation client singleton
|
|
71
|
+
- stop_federation: Stop the federation client
|
|
72
|
+
- FederationConfig: Configuration dataclass for federation
|
|
73
|
+
|
|
74
|
+
Environment:
|
|
75
|
+
- Environment: Thread-safe environment class with get/set/override
|
|
76
|
+
- get_environment: Get current environment (dev/uat/prd)
|
|
77
|
+
- set_environment: Explicitly set environment
|
|
78
|
+
"""
|
|
79
|
+
|
|
80
|
+
# Flask integration
|
|
81
|
+
from .jinja import setup_deeplinks
|
|
82
|
+
|
|
83
|
+
# Core resolution
|
|
84
|
+
from .resolver import (
|
|
85
|
+
resolve_link,
|
|
86
|
+
deep_link_info,
|
|
87
|
+
can_link_to,
|
|
88
|
+
get_entity_microsite_name,
|
|
89
|
+
DeepLinkInfo,
|
|
90
|
+
DeepLinkError,
|
|
91
|
+
UnknownEntityTypeError,
|
|
92
|
+
UnknownMicrositeError,
|
|
93
|
+
)
|
|
94
|
+
|
|
95
|
+
# Registry - registration functions
|
|
96
|
+
from .registry import (
|
|
97
|
+
register_entities,
|
|
98
|
+
register_microsite,
|
|
99
|
+
register_entity,
|
|
100
|
+
clear_registry,
|
|
101
|
+
get_registry,
|
|
102
|
+
# Query functions
|
|
103
|
+
get_entity_definition,
|
|
104
|
+
get_microsite_for_entity,
|
|
105
|
+
get_microsite_config,
|
|
106
|
+
get_microsite_name,
|
|
107
|
+
get_microsite_base_url,
|
|
108
|
+
list_entity_types,
|
|
109
|
+
list_entity_types_for_microsite,
|
|
110
|
+
list_microsites,
|
|
111
|
+
# Types
|
|
112
|
+
EntityDefinition,
|
|
113
|
+
MicrositeConfig,
|
|
114
|
+
RegistrationError,
|
|
115
|
+
DeepLinkRegistry,
|
|
116
|
+
)
|
|
117
|
+
|
|
118
|
+
# YAML loading
|
|
119
|
+
from .yaml_loader import (
|
|
120
|
+
load_from_yaml,
|
|
121
|
+
load_from_string,
|
|
122
|
+
discover_and_load,
|
|
123
|
+
YamlLoadError,
|
|
124
|
+
)
|
|
125
|
+
|
|
126
|
+
# Environment
|
|
127
|
+
from .environment import (
|
|
128
|
+
Environment,
|
|
129
|
+
get_environment,
|
|
130
|
+
set_environment,
|
|
131
|
+
VALID_ENVIRONMENTS,
|
|
132
|
+
)
|
|
133
|
+
|
|
134
|
+
# Federation
|
|
135
|
+
from .federation import (
|
|
136
|
+
configure_federation,
|
|
137
|
+
get_federation_client,
|
|
138
|
+
stop_federation,
|
|
139
|
+
clear_federation_cache,
|
|
140
|
+
FederationConfig,
|
|
141
|
+
FederationClient,
|
|
142
|
+
FederationError,
|
|
143
|
+
FederationTimeoutError,
|
|
144
|
+
FederationConnectionError,
|
|
145
|
+
)
|
|
146
|
+
|
|
147
|
+
# Blueprint for /api/v1/references
|
|
148
|
+
from .blueprint import references_bp
|
|
149
|
+
|
|
150
|
+
__all__ = [
|
|
151
|
+
# Flask integration
|
|
152
|
+
'setup_deeplinks',
|
|
153
|
+
|
|
154
|
+
# Core resolution
|
|
155
|
+
'resolve_link',
|
|
156
|
+
'deep_link_info',
|
|
157
|
+
'can_link_to',
|
|
158
|
+
'get_entity_microsite_name',
|
|
159
|
+
'DeepLinkInfo',
|
|
160
|
+
'DeepLinkError',
|
|
161
|
+
'UnknownEntityTypeError',
|
|
162
|
+
'UnknownMicrositeError',
|
|
163
|
+
|
|
164
|
+
# Registration
|
|
165
|
+
'register_entities',
|
|
166
|
+
'register_microsite',
|
|
167
|
+
'register_entity',
|
|
168
|
+
'clear_registry',
|
|
169
|
+
'get_registry',
|
|
170
|
+
'load_from_yaml',
|
|
171
|
+
'load_from_string',
|
|
172
|
+
'discover_and_load',
|
|
173
|
+
'RegistrationError',
|
|
174
|
+
'YamlLoadError',
|
|
175
|
+
|
|
176
|
+
# Registry queries
|
|
177
|
+
'get_entity_definition',
|
|
178
|
+
'get_microsite_for_entity',
|
|
179
|
+
'get_microsite_config',
|
|
180
|
+
'get_microsite_name',
|
|
181
|
+
'get_microsite_base_url',
|
|
182
|
+
'list_entity_types',
|
|
183
|
+
'list_entity_types_for_microsite',
|
|
184
|
+
'list_microsites',
|
|
185
|
+
|
|
186
|
+
# Types
|
|
187
|
+
'EntityDefinition',
|
|
188
|
+
'MicrositeConfig',
|
|
189
|
+
'DeepLinkRegistry',
|
|
190
|
+
|
|
191
|
+
# Environment
|
|
192
|
+
'Environment',
|
|
193
|
+
'get_environment',
|
|
194
|
+
'set_environment',
|
|
195
|
+
'VALID_ENVIRONMENTS',
|
|
196
|
+
|
|
197
|
+
# Federation
|
|
198
|
+
'configure_federation',
|
|
199
|
+
'get_federation_client',
|
|
200
|
+
'stop_federation',
|
|
201
|
+
'clear_federation_cache',
|
|
202
|
+
'FederationConfig',
|
|
203
|
+
'FederationClient',
|
|
204
|
+
'FederationError',
|
|
205
|
+
'FederationTimeoutError',
|
|
206
|
+
'FederationConnectionError',
|
|
207
|
+
|
|
208
|
+
# Blueprint
|
|
209
|
+
'references_bp',
|
|
210
|
+
]
|