View Javadoc
1   /*
2    * Copyright 2012-2025 CodeLibs Project and the Others.
3    *
4    * Licensed under the Apache License, Version 2.0 (the "License");
5    * you may not use this file except in compliance with the License.
6    * You may obtain a copy of the License at
7    *
8    *     http://www.apache.org/licenses/LICENSE-2.0
9    *
10   * Unless required by applicable law or agreed to in writing, software
11   * distributed under the License is distributed on an "AS IS" BASIS,
12   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
13   * either express or implied. See the License for the specific language
14   * governing permissions and limitations under the License.
15   */
16  package org.codelibs.fess.helper;
17  
18  import static org.codelibs.core.stream.StreamUtil.stream;
19  
20  import java.util.ArrayList;
21  import java.util.HashMap;
22  import java.util.HashSet;
23  import java.util.List;
24  import java.util.Map;
25  import java.util.Set;
26  
27  import org.apache.logging.log4j.LogManager;
28  import org.apache.logging.log4j.Logger;
29  import org.codelibs.core.crypto.CachedCipher;
30  import org.codelibs.core.lang.StringUtil;
31  import org.codelibs.fess.app.service.AccessTokenService;
32  import org.codelibs.fess.entity.SearchRequestParams.SearchRequestType;
33  import org.codelibs.fess.exception.InvalidAccessTokenException;
34  import org.codelibs.fess.mylasta.action.FessUserBean;
35  import org.codelibs.fess.mylasta.direction.FessConfig;
36  import org.codelibs.fess.util.ComponentUtil;
37  import org.lastaflute.web.login.LoginManager;
38  import org.lastaflute.web.servlet.request.RequestManager;
39  import org.lastaflute.web.util.LaRequestUtil;
40  
41  import jakarta.annotation.PostConstruct;
42  import jakarta.servlet.http.Cookie;
43  import jakarta.servlet.http.HttpServletRequest;
44  
45  /**
46   * This class returns a list of a role from a request parameter,
47   * a request header and a cookie. The format of the default value
48   * is "[\d]+\nrole1,role2,role3", which you can encrypt.
49   *
50   *
51   */
52  public class RoleQueryHelper {
53  
54      /**
55       * Constructor.
56       */
57      public RoleQueryHelper() {
58          super();
59      }
60  
61      private static final Logger logger = LogManager.getLogger(RoleQueryHelper.class);
62  
63      /**
64       * The key for user roles in the request attribute.
65       */
66      protected static final String USER_ROLES = "userRoles";
67  
68      /**
69       * The cached cipher for encryption and decryption.
70       */
71      protected CachedCipher cipher;
72  
73      /**
74       * The separator for values in the role string.
75       */
76      protected String valueSeparator = "\n";
77  
78      /**
79       * The separator for roles in the role string.
80       */
81      protected String roleSeparator = ",";
82  
83      /**
84       * The key for the request parameter that contains role information.
85       */
86      protected String parameterKey;
87  
88      /**
89       * Whether the parameter value is encrypted.
90       */
91      protected boolean encryptedParameterValue = true;
92  
93      /**
94       * The key for the request header that contains role information.
95       */
96      protected String headerKey;
97  
98      /**
99       * Whether the header value is encrypted.
100      */
101     protected boolean encryptedHeaderValue = true;
102 
103     /**
104      * The key for the cookie that stores role information.
105      */
106     protected String cookieKey;
107 
108     /**
109      * Whether the cookie value is encrypted.
110      */
111     protected boolean encryptedCookieValue = true;
112 
113     /**
114      * The maximum age of the role information in seconds.
115      */
116     protected long maxAge = 30 * 60; // sec
117 
118     /**
119      * A map of cookie names to role names.
120      */
121     protected Map<String, String> cookieNameMap;
122 
123     /**
124      * A list of default roles.
125      */
126     protected final List<String> defaultRoleList = new ArrayList<>();
127 
128     /**
129      * Initializes the RoleQueryHelper.
130      */
131     @PostConstruct
132     public void init() {
133         if (logger.isDebugEnabled()) {
134             logger.debug("Initializing {}", this.getClass().getSimpleName());
135         }
136         stream(ComponentUtil.getFessConfig().getSearchDefaultPermissionsAsArray()).of(stream -> stream.forEach(name -> {
137             defaultRoleList.add(name);
138         }));
139     }
140 
141     /**
142      * Builds a set of roles from the request.
143      * @param searchRequestType The type of the search request.
144      * @return A set of roles.
145      */
146     public Set<String> build(final SearchRequestType searchRequestType) {
147         final Set<String> roleSet = new HashSet<>();
148         final HttpServletRequest request = LaRequestUtil.getOptionalRequest().orElse(null);
149         final FessConfig fessConfig = ComponentUtil.getFessConfig();
150         final boolean isApiRequest =
151                 !SearchRequestType.SEARCH.equals(searchRequestType) && !SearchRequestType.ADMIN_SEARCH.equals(searchRequestType);
152 
153         if (request != null) {
154             @SuppressWarnings("unchecked")
155             final Set<String> list = (Set<String>) request.getAttribute(USER_ROLES);
156             if (list != null) {
157                 return list;
158             }
159 
160             // request parameter
161             if (StringUtil.isNotBlank(parameterKey)) {
162                 processParameter(request, roleSet);
163             }
164 
165             // request header
166             if (StringUtil.isNotBlank(headerKey)) {
167                 processHeader(request, roleSet);
168             }
169 
170             // cookie
171             if (StringUtil.isNotBlank(cookieKey)) {
172                 processCookie(request, roleSet);
173             }
174 
175             // cookie mapping
176             if (cookieNameMap != null) {
177                 buildByCookieNameMapping(request, roleSet);
178             }
179 
180             final boolean hasAccessToken = processAccessToken(request, roleSet, isApiRequest);
181 
182             final RequestManager requestManager = ComponentUtil.getRequestManager();
183             try {
184                 requestManager.findUserBean(FessUserBean.class)
185                         .ifPresent(fessUserBean -> stream(fessUserBean.getPermissions()).of(stream -> stream.forEach(roleSet::add)))
186                         .orElse(() -> {
187                             if (isApiRequest && ComponentUtil.getFessConfig().getApiAccessTokenRequiredAsBoolean()) {
188                                 throw new InvalidAccessTokenException("invalid_token", "Access token is requried.");
189                             }
190                             if (!hasAccessToken || roleSet.isEmpty()) {
191                                 roleSet.addAll(fessConfig.getSearchGuestRoleList());
192                             }
193                         });
194             } catch (final RuntimeException e) {
195                 try {
196                     requestManager.findLoginManager(FessUserBean.class).ifPresent(LoginManager::logout);
197                 } catch (final Exception e1) {
198                     // ignore
199                 }
200                 throw e;
201             }
202         }
203 
204         if (defaultRoleList != null) {
205             roleSet.addAll(defaultRoleList);
206         }
207 
208         if (logger.isDebugEnabled()) {
209             logger.debug("roleSet: {}", roleSet);
210         }
211 
212         if (request != null) {
213             request.setAttribute(USER_ROLES, roleSet);
214         }
215         return roleSet;
216     }
217 
218     /**
219      * Processes the access token.
220      * @param request The HTTP request.
221      * @param roleSet The set of roles.
222      * @param isApiRequest Whether the request is an API request.
223      * @return true if the access token is processed, false otherwise.
224      */
225     protected boolean processAccessToken(final HttpServletRequest request, final Set<String> roleSet, final boolean isApiRequest) {
226         if (isApiRequest) {
227             return ComponentUtil.getComponent(AccessTokenService.class).getPermissions(request).map(p -> {
228                 p.forEach(roleSet::add);
229                 return true;
230             }).orElse(false);
231         }
232         return false;
233     }
234 
235     /**
236      * Processes the request parameter.
237      * @param request The HTTP request.
238      * @param roleSet The set of roles.
239      */
240     protected void processParameter(final HttpServletRequest request, final Set<String> roleSet) {
241         final String parameter = request.getParameter(parameterKey);
242         if (logger.isDebugEnabled()) {
243             logger.debug("{}:{}", parameterKey, parameter);
244         }
245         if (StringUtil.isNotEmpty(parameter)) {
246             parseRoleSet(parameter, encryptedParameterValue, roleSet);
247         }
248 
249     }
250 
251     /**
252      * Processes the request header.
253      * @param request The HTTP request.
254      * @param roleSet The set of roles.
255      */
256     protected void processHeader(final HttpServletRequest request, final Set<String> roleSet) {
257 
258         final String parameter = request.getHeader(headerKey);
259         if (logger.isDebugEnabled()) {
260             logger.debug("{}:{}", headerKey, parameter);
261         }
262         if (StringUtil.isNotEmpty(parameter)) {
263             parseRoleSet(parameter, encryptedHeaderValue, roleSet);
264         }
265 
266     }
267 
268     /**
269      * Processes the cookie.
270      * @param request The HTTP request.
271      * @param roleSet The set of roles.
272      */
273     protected void processCookie(final HttpServletRequest request, final Set<String> roleSet) {
274 
275         final Cookie[] cookies = request.getCookies();
276         if (cookies != null) {
277             for (final Cookie cookie : cookies) {
278                 if (cookieKey.equals(cookie.getName())) {
279                     final String value = cookie.getValue();
280                     if (logger.isDebugEnabled()) {
281                         logger.debug("{}:{}", cookieKey, value);
282                     }
283                     if (StringUtil.isNotEmpty(value)) {
284                         parseRoleSet(value, encryptedCookieValue, roleSet);
285                     }
286                 }
287             }
288         }
289 
290     }
291 
292     /**
293      * Builds roles from the cookie name mapping.
294      * @param request The HTTP request.
295      * @param roleSet The set of roles.
296      */
297     protected void buildByCookieNameMapping(final HttpServletRequest request, final Set<String> roleSet) {
298         final Cookie[] cookies = request.getCookies();
299         if (cookies != null) {
300             for (final Cookie cookie : cookies) {
301                 addRoleFromCookieMapping(roleSet, cookie);
302             }
303         }
304 
305     }
306 
307     /**
308      * Adds a role from the cookie mapping.
309      * @param roleNameList The list of role names.
310      * @param cookie The cookie.
311      */
312     protected void addRoleFromCookieMapping(final Set<String> roleNameList, final Cookie cookie) {
313         final String roleName = cookieNameMap.get(cookie.getName());
314         if (StringUtil.isNotBlank(roleName)) {
315             roleNameList.add(roleName);
316         }
317     }
318 
319     /**
320      * Parses the role set from a string.
321      * @param value The string to parse.
322      * @param encrypted Whether the string is encrypted.
323      * @param roleSet The set of roles.
324      */
325     protected void parseRoleSet(final String value, final boolean encrypted, final Set<String> roleSet) {
326         String rolesStr = value;
327         if (encrypted && cipher != null) {
328             try {
329                 rolesStr = cipher.decryptoText(rolesStr);
330             } catch (final Exception e) {
331                 if (logger.isDebugEnabled()) {
332                     logger.debug("Failed to decrypt {}", rolesStr, e);
333                 }
334                 return;
335             }
336         }
337 
338         if (logger.isDebugEnabled()) {
339             logger.debug("role: original: {}, decrypto: {}", value, rolesStr);
340         }
341 
342         if (valueSeparator.length() > 0) {
343             final String[] values = rolesStr.split(valueSeparator);
344             if (maxAge > 0) {
345                 try {
346                     final long time = getCurrentTime() / 1000 - Long.parseLong(values[0]);
347                     if (time > maxAge || time < 0) {
348                         if (logger.isDebugEnabled()) {
349                             logger.debug("role info is expired: {} > {}", time, maxAge);
350                         }
351                         return;
352                     }
353                 } catch (final NumberFormatException e) {
354                     logger.warn("Invalid role info: failed to parse timestamp from '{}'", rolesStr, e);
355                     return;
356                 }
357             }
358             if (values.length > 1) {
359                 final String[] roles = values[1].split(roleSeparator);
360                 for (final String role : roles) {
361                     if (StringUtil.isNotEmpty(role)) {
362                         roleSet.add(role);
363                     }
364                 }
365             }
366         } else {
367             final String[] roles = rolesStr.split(roleSeparator);
368             for (final String role : roles) {
369                 if (StringUtil.isNotEmpty(role)) {
370                     roleSet.add(role);
371                 }
372             }
373         }
374     }
375 
376     /**
377      * Gets the current time in milliseconds.
378      * @return The current time in milliseconds.
379      */
380     protected long getCurrentTime() {
381         return ComponentUtil.getSystemHelper().getCurrentTimeAsLong();
382     }
383 
384     /**
385      * Adds a cookie name mapping.
386      * @param cookieName The name of the cookie.
387      * @param roleName The name of the role.
388      */
389     public void addCookieNameMapping(final String cookieName, final String roleName) {
390         if (cookieNameMap == null) {
391             cookieNameMap = new HashMap<>();
392         }
393         cookieNameMap.put(cookieName, roleName);
394     }
395 
396     /**
397      * Sets the cached cipher.
398      * @param cipher The cached cipher.
399      */
400     public void setCipher(final CachedCipher cipher) {
401         this.cipher = cipher;
402     }
403 
404     /**
405      * Sets the value separator.
406      * @param valueSeparator The value separator.
407      */
408     public void setValueSeparator(final String valueSeparator) {
409         this.valueSeparator = valueSeparator;
410     }
411 
412     /**
413      * Sets the role separator.
414      * @param roleSeparator The role separator.
415      */
416     public void setRoleSeparator(final String roleSeparator) {
417         this.roleSeparator = roleSeparator;
418     }
419 
420     /**
421      * Sets the parameter key.
422      * @param parameterKey The parameter key.
423      */
424     public void setParameterKey(final String parameterKey) {
425         this.parameterKey = parameterKey;
426     }
427 
428     /**
429      * Sets whether the parameter value is encrypted.
430      * @param encryptedParameterValue Whether the parameter value is encrypted.
431      */
432     public void setEncryptedParameterValue(final boolean encryptedParameterValue) {
433         this.encryptedParameterValue = encryptedParameterValue;
434     }
435 
436     /**
437      * Sets the header key.
438      * @param headerKey The header key.
439      */
440     public void setHeaderKey(final String headerKey) {
441         this.headerKey = headerKey;
442     }
443 
444     /**
445      * Sets whether the header value is encrypted.
446      * @param encryptedHeaderValue Whether the header value is encrypted.
447      */
448     public void setEncryptedHeaderValue(final boolean encryptedHeaderValue) {
449         this.encryptedHeaderValue = encryptedHeaderValue;
450     }
451 
452     /**
453      * Sets the cookie key.
454      * @param cookieKey The cookie key.
455      */
456     public void setCookieKey(final String cookieKey) {
457         this.cookieKey = cookieKey;
458     }
459 
460     /**
461      * Sets whether the cookie value is encrypted.
462      * @param encryptedCookieValue Whether the cookie value is encrypted.
463      */
464     public void setEncryptedCookieValue(final boolean encryptedCookieValue) {
465         this.encryptedCookieValue = encryptedCookieValue;
466     }
467 
468     /**
469      * Sets the maximum age of the role information in seconds.
470      * @param maxAge The maximum age of the role information in seconds.
471      */
472     public void setMaxAge(final long maxAge) {
473         this.maxAge = maxAge;
474     }
475 
476 }