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 java.io.IOException;
19  import java.nio.file.attribute.AclFileAttributeView;
20  import java.nio.file.attribute.GroupPrincipal;
21  import java.nio.file.attribute.PosixFileAttributeView;
22  import java.nio.file.attribute.PosixFileAttributes;
23  import java.nio.file.attribute.UserPrincipal;
24  import java.util.ArrayList;
25  import java.util.List;
26  import java.util.Locale;
27  import java.util.Map;
28  
29  import org.apache.logging.log4j.LogManager;
30  import org.apache.logging.log4j.Logger;
31  import org.codelibs.core.lang.StringUtil;
32  import org.codelibs.fess.crawler.client.fs.FileSystemClient;
33  import org.codelibs.fess.crawler.client.ftp.FtpClient;
34  import org.codelibs.fess.crawler.client.smb.SmbClient;
35  import org.codelibs.fess.crawler.entity.ResponseData;
36  import org.codelibs.fess.crawler.exception.CrawlingAccessException;
37  import org.codelibs.fess.mylasta.direction.FessConfig;
38  import org.codelibs.fess.util.ComponentUtil;
39  import org.codelibs.jcifs.smb.SID;
40  
41  import jakarta.annotation.Resource;
42  
43  /**
44   * Helper class for handling permission-related operations in Fess.
45   * Provides functionality to encode/decode permission strings and extract
46   * role type information from various file system protocols (SMB, file, FTP).
47   */
48  public class PermissionHelper {
49      /** Logger instance for this class */
50      private static final Logger logger = LogManager.getLogger(PermissionHelper.class);
51  
52      /** Prefix used to identify role-based permissions */
53      protected String rolePrefix = "{role}";
54  
55      /** Prefix used to identify group-based permissions */
56      protected String groupPrefix = "{group}";
57  
58      /** Prefix used to identify user-based permissions */
59      protected String userPrefix = "{user}";
60  
61      /** Prefix used to identify allow permissions */
62      protected String allowPrefix = "(allow)";
63  
64      /** Prefix used to identify deny permissions */
65      protected String denyPrefix = "(deny)";
66  
67      /** System helper for user/group/role search operations */
68      @Resource
69      protected SystemHelper systemHelper;
70  
71      /**
72       * Default constructor for PermissionHelper.
73       * Initializes the permission helper with default configuration.
74       */
75      public PermissionHelper() {
76          // Default constructor
77      }
78  
79      /**
80       * Encodes a permission string into a search role format.
81       * Processes user, group, and role prefixes along with allow/deny prefixes.
82       *
83       * @param value the permission string to encode
84       * @return the encoded permission string, or null if the input is blank or invalid
85       */
86      public String encode(final String value) {
87          if (StringUtil.isBlank(value)) {
88              return null;
89          }
90  
91          String permission = value.trim();
92          String lower = permission.toLowerCase(Locale.ROOT);
93          final String aclPrefix;
94          if (lower.startsWith(allowPrefix)) {
95              lower = lower.substring(allowPrefix.length());
96              permission = permission.substring(allowPrefix.length());
97              aclPrefix = StringUtil.EMPTY;
98          } else if (lower.startsWith(denyPrefix)) {
99              lower = lower.substring(denyPrefix.length());
100             permission = permission.substring(denyPrefix.length());
101             aclPrefix = ComponentUtil.getFessConfig().getRoleSearchDeniedPrefix();
102         } else {
103             aclPrefix = StringUtil.EMPTY;
104         }
105         if (StringUtil.isBlank(permission)) {
106             return null;
107         }
108         if (lower.startsWith(userPrefix)) {
109             if (permission.length() > userPrefix.length()) {
110                 return aclPrefix + systemHelper.getSearchRoleByUser(permission.substring(userPrefix.length()));
111             }
112             return null;
113         }
114         if (lower.startsWith(groupPrefix)) {
115             if (permission.length() > groupPrefix.length()) {
116                 return aclPrefix + systemHelper.getSearchRoleByGroup(permission.substring(groupPrefix.length()));
117             }
118             return null;
119         }
120         if (lower.startsWith(rolePrefix)) {
121             if (permission.length() > rolePrefix.length()) {
122                 return aclPrefix + systemHelper.getSearchRoleByRole(permission.substring(rolePrefix.length()));
123             }
124             return null;
125         }
126         return permission;
127     }
128 
129     /**
130      * Decodes a search role format string back to a permission string.
131      * Reverses the encoding process to restore original permission format.
132      *
133      * @param value the encoded permission string to decode
134      * @return the decoded permission string, or null if the input is blank or invalid
135      */
136     public String decode(final String value) {
137         if (StringUtil.isBlank(value)) {
138             return null;
139         }
140 
141         final FessConfig fessConfig = ComponentUtil.getFessConfig();
142         final String aclPrefix;
143         final String permission;
144         final String deniedPrefix = fessConfig.getRoleSearchDeniedPrefix();
145         if (value.startsWith(deniedPrefix)) {
146             permission = value.substring(deniedPrefix.length());
147             aclPrefix = denyPrefix;
148         } else {
149             permission = value;
150             aclPrefix = StringUtil.EMPTY;
151         }
152         if (StringUtil.isBlank(permission)) {
153             return null;
154         }
155         if (permission.startsWith(fessConfig.getRoleSearchUserPrefix())
156                 && permission.length() > fessConfig.getRoleSearchUserPrefix().length()) {
157             return aclPrefix + userPrefix + permission.substring(fessConfig.getRoleSearchUserPrefix().length());
158         }
159         if (permission.startsWith(fessConfig.getRoleSearchGroupPrefix())
160                 && permission.length() > fessConfig.getRoleSearchGroupPrefix().length()) {
161             return aclPrefix + groupPrefix + permission.substring(fessConfig.getRoleSearchGroupPrefix().length());
162         }
163         if (permission.startsWith(fessConfig.getRoleSearchRolePrefix())
164                 && permission.length() > fessConfig.getRoleSearchRolePrefix().length()) {
165             return aclPrefix + rolePrefix + permission.substring(fessConfig.getRoleSearchRolePrefix().length());
166         }
167         return permission;
168     }
169 
170     /**
171      * Sets the prefix used to identify role-based permissions.
172      *
173      * @param rolePrefix the role prefix to set
174      */
175     public void setRolePrefix(final String rolePrefix) {
176         this.rolePrefix = rolePrefix;
177     }
178 
179     /**
180      * Sets the prefix used to identify group-based permissions.
181      *
182      * @param groupPrefix the group prefix to set
183      */
184     public void setGroupPrefix(final String groupPrefix) {
185         this.groupPrefix = groupPrefix;
186     }
187 
188     /**
189      * Sets the prefix used to identify user-based permissions.
190      *
191      * @param userPrefix the user prefix to set
192      */
193     public void setUserPrefix(final String userPrefix) {
194         this.userPrefix = userPrefix;
195     }
196 
197     /**
198      * Extracts role type information from SMB (Server Message Block) response data.
199      * Processes both SMB and SMB1 protocols to extract allowed and denied SIDs.
200      *
201      * @param responseData the response data containing SMB metadata
202      * @return a list of role type strings extracted from the SMB permissions
203      */
204     public List<String> getSmbRoleTypeList(final ResponseData responseData) {
205         final List<String> roleTypeList = new ArrayList<>();
206         final FessConfig fessConfig = ComponentUtil.getFessConfig();
207         if (fessConfig.isSmbRoleFromFile()) {
208             final SambaHelper sambaHelper = ComponentUtil.getSambaHelper();
209             final Map<String, Object> metaDataMap = responseData.getMetaDataMap();
210             if (responseData.getUrl().startsWith("smb:")) {
211                 final SID[] allowedSids = (SID[]) metaDataMap.get(SmbClient.SMB_ALLOWED_SID_ENTRIES);
212                 if (allowedSids != null) {
213                     for (final SID sid : allowedSids) {
214                         final String accountId = sambaHelper.getAccountId(sid);
215                         if (accountId != null) {
216                             roleTypeList.add(accountId);
217                         }
218                     }
219                 }
220                 final SID[] deniedSids = (SID[]) metaDataMap.get(SmbClient.SMB_DENIED_SID_ENTRIES);
221                 if (deniedSids != null) {
222                     for (final SID sid : deniedSids) {
223                         final String accountId = sambaHelper.getAccountId(sid);
224                         if (accountId != null) {
225                             roleTypeList.add(fessConfig.getRoleSearchDeniedPrefix() + accountId);
226                         }
227                     }
228                 }
229                 if (logger.isDebugEnabled()) {
230                     logger.debug("smbUrl:{} roleType:{}", responseData.getUrl(), roleTypeList);
231                 }
232             } else if (responseData.getUrl().startsWith("smb1:")) {
233                 final org.codelibs.jcifs.smb1.SID[] allowedSids = (org.codelibs.jcifs.smb1.SID[]) metaDataMap
234                         .get(org.codelibs.fess.crawler.client.smb1.SmbClient.SMB_ALLOWED_SID_ENTRIES);
235                 if (allowedSids != null) {
236                     for (final org.codelibs.jcifs.smb1.SID sid : allowedSids) {
237                         final String accountId = sambaHelper.getAccountId(sid);
238                         if (accountId != null) {
239                             roleTypeList.add(accountId);
240                         }
241                     }
242                 }
243                 final org.codelibs.jcifs.smb1.SID[] deniedSids = (org.codelibs.jcifs.smb1.SID[]) metaDataMap
244                         .get(org.codelibs.fess.crawler.client.smb1.SmbClient.SMB_DENIED_SID_ENTRIES);
245                 if (deniedSids != null) {
246                     for (final org.codelibs.jcifs.smb1.SID sid : deniedSids) {
247                         final String accountId = sambaHelper.getAccountId(sid);
248                         if (accountId != null) {
249                             roleTypeList.add(fessConfig.getRoleSearchDeniedPrefix() + accountId);
250                         }
251                     }
252                 }
253                 if (logger.isDebugEnabled()) {
254                     logger.debug("smb1Url:{} roleType:{}", responseData.getUrl(), roleTypeList);
255                 }
256             }
257         }
258         return roleTypeList;
259     }
260 
261     /**
262      * Extracts role type information from file system response data.
263      * Processes ACL (Access Control List) or POSIX file attributes to extract user and group information.
264      *
265      * @param responseData the response data containing file system metadata
266      * @return a list of role type strings extracted from the file permissions
267      */
268     public List<String> getFileRoleTypeList(final ResponseData responseData) {
269         final List<String> roleTypeList = new ArrayList<>();
270         final FessConfig fessConfig = ComponentUtil.getFessConfig();
271         if (fessConfig.isFileRoleFromFile() && responseData.getUrl().startsWith("file:")) {
272             final Map<String, Object> metaDataMap = responseData.getMetaDataMap();
273             final Object fileAttributeView = metaDataMap.get(FileSystemClient.FILE_ATTRIBUTE_VIEW);
274             try {
275                 if (fileAttributeView instanceof final AclFileAttributeView aclFileAttributeView) {
276                     aclFileAttributeView.getAcl().stream().forEach(acl -> {
277                         final UserPrincipal principal = acl.principal();
278                         if (logger.isDebugEnabled()) {
279                             logger.debug("Principal: [{}] {}", principal.getClass().getName(), principal);
280                         }
281                         if (principal instanceof final GroupPrincipal groupPrincipal) {
282                             roleTypeList.add(systemHelper.getSearchRoleByGroup(groupPrincipal.getName()));
283                         } else if (principal != null) {
284                             roleTypeList.add(systemHelper.getSearchRoleByUser(principal.getName()));
285                         }
286                     });
287                 } else if (fileAttributeView instanceof final PosixFileAttributeView posixFileAttributeView) {
288                     final PosixFileAttributes attributes = posixFileAttributeView.readAttributes();
289                     final UserPrincipal userPrincipal = attributes.owner();
290                     if (logger.isDebugEnabled()) {
291                         logger.debug("Principal: [{}] {}", userPrincipal.getClass().getName(), userPrincipal);
292                     }
293                     if (userPrincipal != null) {
294                         roleTypeList.add(systemHelper.getSearchRoleByUser(userPrincipal.getName()));
295                     }
296                     final GroupPrincipal groupPrincipal = attributes.group();
297                     if (logger.isDebugEnabled()) {
298                         logger.debug("Principal: [{}] {}", groupPrincipal.getClass().getName(), groupPrincipal);
299                     }
300                     if (groupPrincipal != null) {
301                         roleTypeList.add(systemHelper.getSearchRoleByGroup(groupPrincipal.getName()));
302                     }
303                 }
304             } catch (final IOException e) {
305                 throw new CrawlingAccessException("Failed to access permission info", e);
306             }
307             if (logger.isDebugEnabled()) {
308                 logger.debug("fileUrl:{} roleType:{}", responseData.getUrl(), roleTypeList);
309             }
310         }
311         return roleTypeList;
312     }
313 
314     /**
315      * Extracts role type information from FTP (File Transfer Protocol) response data.
316      * Processes FTP metadata to extract file owner and group information.
317      *
318      * @param responseData the response data containing FTP metadata
319      * @return a list of role type strings extracted from the FTP file permissions
320      */
321     public List<String> getFtpRoleTypeList(final ResponseData responseData) {
322         final List<String> roleTypeList = new ArrayList<>();
323         final FessConfig fessConfig = ComponentUtil.getFessConfig();
324         if (fessConfig.isFtpRoleFromFile() && responseData.getUrl().startsWith("ftp:")) {
325             final String owner = (String) responseData.getMetaDataMap().get(FtpClient.FTP_FILE_USER);
326             if (owner != null) {
327                 roleTypeList.add(systemHelper.getSearchRoleByUser(owner));
328             }
329             final String group = (String) responseData.getMetaDataMap().get(FtpClient.FTP_FILE_GROUP);
330             if (group != null) {
331                 roleTypeList.add(systemHelper.getSearchRoleByGroup(group));
332             }
333             if (logger.isDebugEnabled()) {
334                 logger.debug("ftpUrl:{} roleType:{}", responseData.getUrl(), roleTypeList);
335             }
336         }
337         return roleTypeList;
338     }
339 
340     /**
341      * Sets the prefix used to identify allow permissions.
342      *
343      * @param allowPrefix the allow prefix to set
344      */
345     public void setAllowPrefix(final String allowPrefix) {
346         this.allowPrefix = allowPrefix;
347     }
348 
349     /**
350      * Sets the prefix used to identify deny permissions.
351      *
352      * @param denyPrefix the deny prefix to set
353      */
354     public void setDenyPrefix(final String denyPrefix) {
355         this.denyPrefix = denyPrefix;
356     }
357 }