Skip to content

Commit bf576a1

Browse files
authored
[Comp-792-2] Remove simple cache from staff and auth services (#717)
1 parent 956f5c7 commit bf576a1

File tree

5 files changed

+33
-768
lines changed

5 files changed

+33
-768
lines changed

compliance-api/src/compliance_api/services/authorize_service/auth_service.py

Lines changed: 7 additions & 106 deletions
Original file line numberDiff line numberDiff line change
@@ -4,102 +4,36 @@
44
from flask import current_app, g
55

66
from compliance_api.exceptions import BusinessError
7-
from compliance_api.utils.cache import cache
87
from compliance_api.utils.constant import AUTH_APP
98
from compliance_api.utils.enum import HttpMethod
109

1110
from .constant import API_REQUEST_TIMEOUT
1211

1312

1413
class AuthService:
15-
"""Handle service request for epic.authorize with integrated cache management."""
16-
17-
AUTH_CACHE_VERSION_KEY = "auth_cache_version"
18-
19-
@classmethod
20-
def _get_auth_cache_version(cls):
21-
"""
22-
Get current auth cache version from shared cache.
23-
24-
This ensures all pods see the same version number.
25-
"""
26-
version = cache.get(cls.AUTH_CACHE_VERSION_KEY)
27-
if version is None:
28-
version = 0
29-
cache.set(cls.AUTH_CACHE_VERSION_KEY, version)
30-
return version
14+
"""Handle service request for epic.authorize."""
3115

3216
@staticmethod
3317
def get_epic_user_by_guid(auth_user_guid: str):
34-
"""
35-
Return the user representation from epic.authorize (cached).
36-
37-
This is called frequently for individual user lookups,
38-
so we cache it with the user's token hash for security.
39-
"""
40-
from compliance_api.services.cached_staff_user import CachedStaffUserService
41-
42-
# Include version AND token hash in cache key
43-
version = AuthService._get_auth_cache_version()
44-
token_hash = CachedStaffUserService._get_token_hash()
45-
cache_key = f"auth_user:{auth_user_guid}:{token_hash}:v{version}"
46-
47-
cached_result = cache.get(cache_key)
48-
if cached_result is not None:
49-
current_app.logger.debug(f"Cache hit for auth user {auth_user_guid}")
50-
return cached_result
51-
52-
current_app.logger.debug(f"Cache miss for auth user {auth_user_guid}")
53-
18+
"""Return the user representation from epic.authorize."""
5419
auth_user_response = _request_auth_service(f"users/{auth_user_guid}")
5520
if auth_user_response.status_code != 200:
5621
raise BusinessError(
5722
f"Error finding user with ID {auth_user_guid} from auth server"
5823
)
59-
60-
result = auth_user_response.json()
61-
cache.set(cache_key, result, timeout=180) # 3 minutes
62-
return result
24+
return auth_user_response.json()
6325

6426
@staticmethod
6527
def get_epic_users_by_app():
66-
"""
67-
Return all users belonging to COMPLIANCE app with caching.
68-
69-
This caches per-user to prevent data leakage.
70-
"""
71-
from compliance_api.services.cached_staff_user import CachedStaffUserService
72-
from compliance_api.utils.constant import AUTH_APP
73-
from compliance_api.exceptions import BusinessError
74-
75-
# Include version AND token hash in cache key
76-
version = AuthService._get_auth_cache_version()
77-
token_hash = CachedStaffUserService._get_token_hash()
78-
cache_key = f"auth_users_app:{AUTH_APP}:{token_hash}:v{version}"
79-
80-
cached_result = cache.get(cache_key)
81-
if cached_result is not None:
82-
current_app.logger.debug("Cache hit for auth users by app")
83-
return cached_result
84-
85-
current_app.logger.debug("Cache miss for auth users by app")
86-
87-
# Fetch from auth service
28+
"""Return all users belonging to COMPLIANCE app."""
8829
auth_users_response = _request_auth_service(f"users?app_name={AUTH_APP}")
8930
if auth_users_response.status_code != 200:
9031
raise BusinessError(f"Error fetching users for the app {AUTH_APP}")
91-
92-
result = auth_users_response.json()
93-
cache.set(cache_key, result, timeout=180) # 3 minutes
94-
return result
32+
return auth_users_response.json()
9533

9634
@staticmethod
9735
def update_user_group(auth_user_guid: str, payload: dict):
98-
"""Update user group and invalidate all related caches."""
99-
from compliance_api.services.cached_staff_user import CachedStaffUserService
100-
from compliance_api.utils.enum import HttpMethod
101-
from compliance_api.exceptions import BusinessError
102-
36+
"""Update the group of the user in the identity server."""
10337
update_group_response = _request_auth_service(
10438
f"users/{auth_user_guid}/groups", HttpMethod.PUT, payload
10539
)
@@ -108,53 +42,20 @@ def update_user_group(auth_user_guid: str, payload: dict):
10842
f"Update group in the auth server failed for user : {auth_user_guid}"
10943
)
11044

111-
# Invalidate all auth caches by bumping version
112-
AuthService._invalidate_all_auth_cache()
113-
114-
# Invalidate ALL staff caches since permissions changed
115-
CachedStaffUserService.invalidate_staff_cache(auth_user_guid)
116-
11745
return update_group_response
11846

11947
@staticmethod
12048
def delete_user_group(auth_user_guid: str, group: str, del_sub_group_mappings=True):
121-
"""Delete user group and invalidate all related caches."""
122-
from compliance_api.services.cached_staff_user import CachedStaffUserService
123-
from compliance_api.utils.enum import HttpMethod
124-
from compliance_api.exceptions import BusinessError
125-
49+
"""Delete user group."""
12650
delete_response = _request_auth_service(
12751
f"users/{auth_user_guid}/groups/{group}?del_sub_group_mappings={del_sub_group_mappings}",
12852
HttpMethod.DELETE,
12953
)
13054
if delete_response.status_code != 204:
13155
raise BusinessError("Delete group mapping failed")
13256

133-
# Invalidate all auth caches by bumping version
134-
AuthService._invalidate_all_auth_cache()
135-
136-
# Invalidate all staff caches since permissions changed
137-
CachedStaffUserService.invalidate_staff_cache(auth_user_guid)
138-
13957
return delete_response
14058

141-
@staticmethod
142-
def _invalidate_all_auth_cache():
143-
"""Invalidate all auth cache by bumping version number."""
144-
try:
145-
current_version = cache.get(AuthService.AUTH_CACHE_VERSION_KEY)
146-
if current_version is None:
147-
current_version = 0
148-
149-
new_version = current_version + 1
150-
cache.set(AuthService.AUTH_CACHE_VERSION_KEY, new_version)
151-
152-
current_app.logger.debug(
153-
f"Bumped auth cache version from {current_version} to {new_version}"
154-
)
155-
except (AttributeError, RuntimeError) as e:
156-
current_app.logger.error(f"Error invalidating auth cache: {e}")
157-
15859

15960
def _request_auth_service(
16061
relative_url, http_method: HttpMethod = HttpMethod.GET, data=None
Lines changed: 21 additions & 128 deletions
Original file line numberDiff line numberDiff line change
@@ -1,36 +1,23 @@
1-
"""Integrated caching strategy for staff users."""
1+
"""
2+
CachedStaffUserService provides methods for staff user data retrieval.
23
3-
import hashlib
4-
from flask import current_app, g
4+
NOTE:
5+
Staff caching has been intentionally removed for in production.
6+
This service always fetches fresh data to avoid cross-pod
7+
permission inconsistencies.
8+
Re-enable only with a shared cache, not a Simple cache.
9+
"""
10+
11+
from flask import current_app
512

613
from compliance_api.models.staff_user import StaffUser as StaffUserModel
714
from compliance_api.schemas.staff_user import StaffUserSchema
815
from compliance_api.services.authorize_service.auth_service import AuthService
9-
from compliance_api.utils.cache import cache
1016

1117

1218
class CachedStaffUserService:
1319
"""Cached staff user service for performance optimization during token validation."""
1420

15-
CACHE_TIMEOUT = 3600 # 1 hour
16-
STAFF_CACHE_KEY_PREFIX = "staff_user:"
17-
ALL_STAFF_CACHE_KEY = "all_staff_users"
18-
ALL_STAFF_WITH_AUTH_PREFIX = "all_staff_with_auth:"
19-
CACHE_VERSION_KEY = "staff_cache_version"
20-
21-
@classmethod
22-
def _get_cache_version(cls):
23-
"""
24-
Get current cache version from shared cache.
25-
26-
This ensures all pods see the same version number.
27-
"""
28-
version = cache.get(cls.CACHE_VERSION_KEY)
29-
if version is None:
30-
version = 0
31-
cache.set(cls.CACHE_VERSION_KEY, version)
32-
return version
33-
3421
@classmethod
3522
def exists_staff_by_auth_guid(cls, auth_guid: str) -> bool:
3623
"""
@@ -48,130 +35,36 @@ def exists_staff_by_auth_guid(cls, auth_guid: str) -> bool:
4835
if not auth_guid:
4936
return False
5037

51-
# Include version in cache key
52-
version = cls._get_cache_version()
53-
cache_key = f"{cls.STAFF_CACHE_KEY_PREFIX}{auth_guid}:v{version}"
54-
55-
cached_staff_exists = cache.get(cache_key)
56-
if cached_staff_exists is not None:
57-
current_app.logger.debug(
58-
f"Cache hit for staff existence check: {auth_guid}"
59-
)
60-
return bool(cached_staff_exists)
38+
current_app.logger.debug(
39+
f"Fetching staff existence from DB for auth_guid={auth_guid}"
40+
)
6141

62-
current_app.logger.debug(f"Cache miss for staff existence check: {auth_guid}")
6342
staff_user = StaffUserModel.get_by_auth_guid(auth_guid)
64-
65-
cache_value = bool(staff_user)
66-
cache.set(cache_key, cache_value, timeout=cls.CACHE_TIMEOUT)
67-
68-
return cache_value
43+
return bool(staff_user)
6944

7045
@staticmethod
7146
def get_all_staff_users_with_auth():
7247
"""
73-
Get all staff users with merged auth data (cached as serialized data).
48+
Get all staff users with merged auth data.
7449
7550
Returns:
7651
List of serialized staff user dictionaries
7752
"""
7853
# pylint: disable=import-outside-toplevel
7954
from compliance_api.services.staff_user import _set_permission_level_in_compliance_user_obj
8055

81-
# Get current version from shared cache
82-
version = CachedStaffUserService._get_cache_version()
83-
84-
# Create cache key that includes token hash AND version
85-
token_hash = CachedStaffUserService._get_token_hash()
86-
cache_key = (
87-
f"{CachedStaffUserService.ALL_STAFF_WITH_AUTH_PREFIX}"
88-
f"{token_hash}:v{version}"
56+
current_app.logger.debug(
57+
"Fetching all staff users with auth data (no cache)"
8958
)
9059

91-
cached_result = cache.get(cache_key)
92-
if cached_result is not None:
93-
current_app.logger.debug("Cache hit for all staff users with auth data")
94-
return cached_result
95-
96-
current_app.logger.debug("Cache miss for all staff users with auth data")
97-
98-
# Get users from compliance database with eager loading
9960
users = StaffUserModel.get_all_with_relationships(default_filters=False)
10061

101-
# Get compliance users from epic system
10262
auth_users = AuthService.get_epic_users_by_app()
103-
104-
# Merge the two sets of users to set the permission in the result
10563
index_auth_users = {user["username"]: user for user in auth_users}
64+
10665
for user in users:
107-
auth_user = index_auth_users.get(user.auth_user_guid, None)
108-
user = _set_permission_level_in_compliance_user_obj(user, auth_user)
66+
auth_user = index_auth_users.get(user.auth_user_guid)
67+
_set_permission_level_in_compliance_user_obj(user, auth_user)
10968

110-
# Serialize the data BEFORE caching to avoid detached instance issues
11169
user_schema = StaffUserSchema(many=True)
112-
serialized_users = user_schema.dump(users)
113-
114-
# Cache the serialized data
115-
cache.set(cache_key, serialized_users, timeout=300) # 5 minutes
116-
117-
return serialized_users
118-
119-
@staticmethod
120-
def invalidate_staff_cache(auth_guid: str = None):
121-
"""
122-
Invalidate cached staff user data.
123-
124-
Args:
125-
auth_guid: Specific auth_guid to invalidate, or None to clear all staff cache
126-
"""
127-
# Bump version to invalidate all caches across all pods
128-
CachedStaffUserService._invalidate_all_staff_cache()
129-
130-
if auth_guid:
131-
current_app.logger.info(f"Invalidated cache for staff user: {auth_guid}")
132-
133-
@staticmethod
134-
def _invalidate_all_staff_cache():
135-
"""
136-
Invalidate all staff cache by bumping version number.
137-
138-
This invalidates cache for across pods since the version
139-
is part of every cache key.
140-
"""
141-
try:
142-
current_version = cache.get(CachedStaffUserService.CACHE_VERSION_KEY)
143-
if current_version is None:
144-
current_version = 0
145-
146-
new_version = current_version + 1
147-
cache.set(CachedStaffUserService.CACHE_VERSION_KEY, new_version)
148-
current_app.logger.info(
149-
f"Bumped staff cache version from {current_version} to {new_version}"
150-
)
151-
except (AttributeError, RuntimeError) as e:
152-
current_app.logger.error(f"Error invalidating staff cache: {e}")
153-
154-
@classmethod
155-
def _clear_all_staff_cache(cls):
156-
"""Clear all staff-related cache entries."""
157-
try:
158-
cache.clear()
159-
current_app.logger.info("Cleared all cache (simple cache type)")
160-
except (AttributeError, RuntimeError) as e:
161-
current_app.logger.error(f"Error clearing cache: {str(e)}")
162-
163-
@staticmethod
164-
def _get_token_hash():
165-
"""
166-
Generate a cache key suffix based on the current user's token.
167-
168-
This ensures different users get different cached data while
169-
not exposing the actual token in cache keys.
170-
"""
171-
token = getattr(g, "access_token", None)
172-
if not token:
173-
return "no_token"
174-
175-
# Use first 16 chars of SHA256 hash
176-
token_hash = hashlib.sha256(token.encode()).hexdigest()[:16]
177-
return token_hash
70+
return user_schema.dump(users)

0 commit comments

Comments
 (0)