Joaquín Reñé
2025-10-07 146a0fb8b0e90f9196e569152f649baf60d6cc8f
securis/src/main/java/net/curisit/securis/ioc/RequestsInterceptor.java
....@@ -1,3 +1,6 @@
1
+/*
2
+* Copyright @ 2013 CurisTEC, S.A.S. All Rights Reserved.
3
+*/
14 package net.curisit.securis.ioc;
25
36 import java.io.IOException;
....@@ -31,143 +34,214 @@
3134 import net.curisit.securis.utils.CacheTTL;
3235 import net.curisit.securis.utils.TokenHelper;
3336
37
+/**
38
+* RequestsInterceptor
39
+* <p>
40
+* Authentication/authorization interceptor that:
41
+* <ul>
42
+* <li>Loads and stores the {@link EntityManager} in the request context.</li>
43
+* <li>Validates tokens for methods annotated with {@link Securable}.</li>
44
+* <li>Builds a {@link BasicSecurityContext} with roles and scoped organization/application IDs.</li>
45
+* <li>Manages transactions when {@code @EnsureTransaction} is present.</li>
46
+* </ul>
47
+*
48
+* <p><b>Cache usage:</b> Uses {@link CacheTTL} to cache roles and scope sets.
49
+* The new {@link CacheTTL#getSet(String, Class)} helper removes unchecked
50
+* conversion warnings when retrieving {@code Set<Integer>} from the cache.
51
+*
52
+* @author JRA
53
+* Last reviewed by JRA on Oct 5, 2025.
54
+*/
3455 @Provider
3556 @Priority(Priorities.AUTHENTICATION)
3657 public class RequestsInterceptor implements ContainerRequestFilter, WriterInterceptor {
3758
38
- private static final Logger LOG = LogManager.getLogger(RequestsInterceptor.class);
59
+ private static final Logger LOG = LogManager.getLogger(RequestsInterceptor.class);
3960
40
- @Context
41
- private HttpServletResponse servletResponse;
61
+ @Inject private CacheTTL cache;
62
+ @Inject private TokenHelper tokenHelper;
63
+ @Inject private EntityManagerProvider emProvider;
4264
43
- @Context
44
- private HttpServletRequest servletRequest;
65
+ @Context private HttpServletResponse servletResponse;
66
+ @Context private HttpServletRequest servletRequest;
67
+ @Context private ResourceInfo resourceInfo;
4568
46
- @Context
47
- private ResourceInfo resourceInfo;
69
+ private static final String EM_CONTEXT_PROPERTY = "curisit.entitymanager";
4870
49
- @Inject
50
- private CacheTTL cache;
71
+ // -------------------------------------------------------------
72
+ // Request filter (authN/authZ + EM handling)
73
+ // -------------------------------------------------------------
5174
52
- @Inject
53
- private TokenHelper tokenHelper;
75
+ /**
76
+ * filter<p>
77
+ * Entry point before resource method invocation.
78
+ *
79
+ * @param requestContext
80
+ * @throws IOException
81
+ */
82
+ @Override
83
+ public void filter(ContainerRequestContext requestContext) throws IOException {
84
+ EntityManager em = emProvider.getEntityManager();
85
+ LOG.debug("GETTING EM: {}", em);
5486
55
- @Inject
56
- private EntityManagerProvider emProvider;
87
+ // Store EntityManager for later retrieval (writer interceptor)
88
+ requestContext.setProperty(EM_CONTEXT_PROPERTY, em);
5789
58
- private static final String EM_CONTEXT_PROPERTY = "curisit.entitymanager";
90
+ Method method = resourceInfo.getResourceMethod();
5991
60
- @Override
61
- public void filter(ContainerRequestContext requestContext) throws IOException {
62
- EntityManager em = emProvider.getEntityManager();
63
- LOG.debug("GETTING EM: {}", em);
92
+ if (checkSecurableMethods(requestContext, method)) {
93
+ if (method.isAnnotationPresent(EnsureTransaction.class)) {
94
+ LOG.debug("Beginning transaction");
95
+ em.getTransaction().begin();
96
+ }
97
+ }
98
+ }
6499
65
- // Guardamos el EntityManager en el contexto para recuperación posterior
66
- requestContext.setProperty(EM_CONTEXT_PROPERTY, em);
100
+ /**
101
+ * checkSecurableMethods<p>
102
+ * Enforce security checks for methods annotated with {@link Securable}.
103
+ * Builds {@link BasicSecurityContext} when authorized.
104
+ *
105
+ * @param ctx
106
+ * @param method
107
+ * @return true if request can proceed; false when aborted
108
+ */
109
+ private boolean checkSecurableMethods(ContainerRequestContext ctx, Method method) {
110
+ if (!method.isAnnotationPresent(Securable.class)) return true;
67111
68
- Method method = resourceInfo.getResourceMethod();
112
+ String token = servletRequest.getHeader(TokenHelper.TOKEN_HEADER_PÀRAM);
113
+ if (token == null || !tokenHelper.isTokenValid(token)) {
114
+ LOG.warn("Access denied, invalid token");
115
+ ctx.abortWith(Response.status(Status.UNAUTHORIZED).build());
116
+ return false;
117
+ }
69118
70
- if (checkSecurableMethods(requestContext, method)) {
71
- if (method.isAnnotationPresent(EnsureTransaction.class)) {
72
- LOG.debug("Beginning transaction");
73
- em.getTransaction().begin();
74
- }
75
- }
76
- }
119
+ String username = tokenHelper.extractUserFromToken(token);
120
+ int roles = getUserRoles(username);
121
+ Securable securable = method.getAnnotation(Securable.class);
77122
78
- private boolean checkSecurableMethods(ContainerRequestContext ctx, Method method) {
79
- if (!method.isAnnotationPresent(Securable.class)) return true;
123
+ if (securable.roles() != 0 && (securable.roles() & roles) == 0) {
124
+ LOG.warn("User {} lacks required roles for method {}", username, method.getName());
125
+ ctx.abortWith(Response.status(Status.UNAUTHORIZED).build());
126
+ return false;
127
+ }
80128
81
- String token = servletRequest.getHeader(TokenHelper.TOKEN_HEADER_PÀRAM);
82
- if (token == null || !tokenHelper.isTokenValid(token)) {
83
- LOG.warn("Access denied, invalid token");
84
- ctx.abortWith(Response.status(Status.UNAUTHORIZED).build());
85
- return false;
86
- }
129
+ BasicSecurityContext sc = new BasicSecurityContext(username, roles, servletRequest.isSecure());
130
+ sc.setOrganizationsIds(getUserOrganizations(username));
131
+ sc.setApplicationsIds(getUserApplications(username));
132
+ ctx.setSecurityContext(sc);
133
+ return true;
134
+ }
87135
88
- String username = tokenHelper.extractUserFromToken(token);
89
- int roles = getUserRoles(username);
90
- Securable securable = method.getAnnotation(Securable.class);
136
+ // -------------------------------------------------------------
137
+ // Cached lookups (roles/orgs/apps)
138
+ // -------------------------------------------------------------
91139
92
- if (securable.roles() != 0 && (securable.roles() & roles) == 0) {
93
- LOG.warn("User {} lacks required roles for method {}", username, method.getName());
94
- ctx.abortWith(Response.status(Status.UNAUTHORIZED).build());
95
- return false;
96
- }
140
+ /**
141
+ * getUserRoles<p>
142
+ * Retrieve roles bitmask for the given user (cached).
143
+ *
144
+ * @param username
145
+ * @return userRoles
146
+ */
147
+ private int getUserRoles(String username) {
148
+ if (username == null) return 0;
149
+ Integer cached = cache.get("roles_" + username, Integer.class);
150
+ if (cached != null) return cached;
97151
98
- BasicSecurityContext sc = new BasicSecurityContext(username, roles, servletRequest.isSecure());
99
- sc.setOrganizationsIds(getUserOrganizations(username));
100
- sc.setApplicationsIds(getUserApplications(username));
101
- ctx.setSecurityContext(sc);
102
- return true;
103
- }
152
+ EntityManager em = emProvider.getEntityManager();
153
+ User user = em.find(User.class, username);
154
+ int roles = 0;
155
+ if (user != null) {
156
+ List<Integer> r = user.getRoles();
157
+ if (r != null) for (Integer role : r) roles += role;
158
+ cache.set("roles_" + username, roles, 3600);
159
+ // also warm some caches
160
+ cache.set("orgs_" + username, user.getOrgsIds(), 3600);
161
+ }
162
+ return roles;
163
+ }
104164
105
- private int getUserRoles(String username) {
106
- if (username == null) return 0;
107
- Integer cached = cache.get("roles_" + username, Integer.class);
108
- if (cached != null) return cached;
165
+ /**
166
+ * getUserOrganizations<p>
167
+ * Retrieve organization scope for the user as a typed {@code Set<Integer>}
168
+ * using the cache helper that validates element types.
169
+ *
170
+ * @param username
171
+ * @return userOrganizations
172
+ */
173
+ private Set<Integer> getUserOrganizations(String username) {
174
+ Set<Integer> cached = cache.getSet("orgs_" + username, Integer.class);
175
+ if (cached != null) return cached;
109176
110
- EntityManager em = emProvider.getEntityManager();
111
- User user = em.find(User.class, username);
112
- int roles = 0;
113
- if (user != null) {
114
- List<Integer> r = user.getRoles();
115
- if (r != null) for (Integer role : r) roles += role;
116
- cache.set("roles_" + username, roles, 3600);
117
- cache.set("orgs_" + username, user.getOrgsIds(), 3600);
118
- }
119
- return roles;
120
- }
177
+ User user = emProvider.getEntityManager().find(User.class, username);
178
+ if (user != null) {
179
+ Set<Integer> result = user.getAllOrgsIds();
180
+ cache.set("orgs_" + username, result, 3600);
181
+ return result;
182
+ }
183
+ return Set.of();
184
+ }
121185
122
- private Set<Integer> getUserOrganizations(String username) {
123
- Set<Integer> cached = cache.get("orgs_" + username, Set.class);
124
- if (cached != null) return cached;
125
- User user = emProvider.getEntityManager().find(User.class, username);
126
- if (user != null) {
127
- Set<Integer> result = user.getAllOrgsIds();
128
- cache.set("orgs_" + username, result, 3600);
129
- return result;
130
- }
131
- return Set.of();
132
- }
186
+ /**
187
+ * getUserApplications<p>
188
+ * Retrieve application scope for the user as a typed {@code Set<Integer>}
189
+ * using the cache helper that validates element types.
190
+ *
191
+ * @param username
192
+ * @return userApplications
193
+ */
194
+ private Set<Integer> getUserApplications(String username) {
195
+ Set<Integer> cached = cache.getSet("apps_" + username, Integer.class);
196
+ if (cached != null) return cached;
133197
134
- private Set<Integer> getUserApplications(String username) {
135
- Set<Integer> cached = cache.get("apps_" + username, Set.class);
136
- if (cached != null) return cached;
137
- User user = emProvider.getEntityManager().find(User.class, username);
138
- if (user != null) {
139
- Set<Integer> result = user.getAllAppsIds();
140
- cache.set("apps_" + username, result, 3600);
141
- return result;
142
- }
143
- return Set.of();
144
- }
198
+ User user = emProvider.getEntityManager().find(User.class, username);
199
+ if (user != null) {
200
+ Set<Integer> result = user.getAllAppsIds();
201
+ cache.set("apps_" + username, result, 3600);
202
+ return result;
203
+ }
204
+ return Set.of();
205
+ }
145206
146
- @Override
147
- public void aroundWriteTo(WriterInterceptorContext context) throws IOException, WebApplicationException {
148
- context.proceed();
207
+ // -------------------------------------------------------------
208
+ // Writer interceptor (transaction finalize)
209
+ // -------------------------------------------------------------
149210
150
- EntityManager em = (EntityManager) context.getProperty(EM_CONTEXT_PROPERTY);
151
- if (em == null) return;
211
+ /**
212
+ * aroundWriteTo<p>
213
+ * Commit/rollback and close EM after response writing.
214
+ *
215
+ * @param context
216
+ * @throws IOException
217
+ * @throws WebApplicationException
218
+ */
219
+ @Override
220
+ public void aroundWriteTo(WriterInterceptorContext context) throws IOException, WebApplicationException {
221
+ context.proceed();
152222
153
- try {
154
- if (em.getTransaction().isActive()) {
155
- if (servletResponse.getStatus() == Status.OK.getStatusCode()) {
156
- em.getTransaction().commit();
157
- LOG.debug("Transaction committed");
158
- } else {
159
- em.getTransaction().rollback();
160
- LOG.debug("Transaction rolled back");
161
- }
162
- }
163
- } finally {
164
- if (em.isOpen()) {
165
- try {
166
- em.close();
167
- } catch (Exception e) {
168
- LOG.error("Error closing EntityManager", e);
169
- }
170
- }
171
- }
172
- }
223
+ EntityManager em = (EntityManager) context.getProperty(EM_CONTEXT_PROPERTY);
224
+ if (em == null) return;
225
+
226
+ try {
227
+ if (em.getTransaction().isActive()) {
228
+ if (servletResponse.getStatus() == Status.OK.getStatusCode()) {
229
+ em.getTransaction().commit();
230
+ LOG.debug("Transaction committed");
231
+ } else {
232
+ em.getTransaction().rollback();
233
+ LOG.debug("Transaction rolled back");
234
+ }
235
+ }
236
+ } finally {
237
+ if (em.isOpen()) {
238
+ try {
239
+ em.close();
240
+ } catch (Exception e) {
241
+ LOG.error("Error closing EntityManager", e);
242
+ }
243
+ }
244
+ }
245
+ }
173246 }
247
+