View Javadoc

1   /*
2   Copyright (C) 2000 - 2007 Grid Systems, S.A.
3   
4   This program is free software; you can redistribute it and/or modify
5   it under the terms of the GNU General Public License, version 2, as
6   published by the Free Software Foundation.
7   
8   This program is distributed in the hope that it will be useful,
9   but WITHOUT ANY WARRANTY; without even the implied warranty of
10  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11  GNU General Public License for more details.
12  
13  You should have received a copy of the GNU General Public License
14  along with this program; if not, write to the Free Software
15  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
16  */
17  
18  package com.gridsystems.innergrid.kernel.ixos.usersystem;
19  
20  import java.io.File;
21  import java.util.ArrayList;
22  import java.util.Collection;
23  import java.util.Iterator;
24  import java.util.List;
25  
26  import org.apache.commons.logging.Log;
27  import org.apache.commons.logging.LogFactory;
28  
29  import com.gridsystems.utils.FileProperties;
30  
31  import com.gridsystems.innergrid.kernel.KernelException;
32  import com.gridsystems.innergrid.kernel.genericutils.ApiUtils;
33  import com.gridsystems.innergrid.kernel.server.KernelContext;
34  
35  import com.gridsystems.innergrid.kernel.ixos.Configuration;
36  import com.gridsystems.innergrid.kernel.ixos.filesystem.FilePermissions;
37  import com.gridsystems.innergrid.kernel.ixos.filesystem.IxosSecurityManager;
38  import com.gridsystems.innergrid.kernel.ixos.filesystem.PermissionsManager;
39  import com.gridsystems.innergrid.kernel.ixos.filesystem.RepositoryFileFilter;
40  
41  /**
42   * Low-priority thread that cleans permission files of deleted user/roles. The thread can
43   * be set to wait a certain delay after it has been created to start running; this way, if
44   * many users/roles are deleted together, the first deletion will create the thread but
45   * its execution will be delayed so it deals with some of the user/role changes in the
46   * same run. The preferred way of using this thread (specially if a delay is to be set) is
47   * calling ApiUserSystemImpl.cleanRepositoryPermissions(). This way, no new threads will
48   * be created until the last one created starts working. Anyway, it can also be used
49   * directly for initialization issues.
50   *
51   * @author SJM
52   */
53  public final class PermissionsCleanerThread extends Thread {
54    /**
55     * Logger of this class.
56     */
57    private static Log log = LogFactory.getLog(PermissionsCleanerThread.class);
58  
59    /**
60     * Singleton instance.
61     */
62    private static PermissionsCleanerThread instance = null;
63  
64    /**
65     * A boolean that is true if and only if the thread has started running.
66     */
67    private boolean launched = false;
68  
69    /**
70     * A long with the time to sleep before starting to perform actual tasks.
71     */
72    private long sleep = 0L;
73  
74    /**
75     * The object to get information of users and roles.
76     */
77    private ApiUserSystemImpl userSystem = ApiUserSystemImpl.getInstance();
78  
79    /**
80     * The object to get information of the permissions of files.
81     */
82    private PermissionsManager permissionsManager = PermissionsManager.getInstance();
83  
84    /**
85     * The filter of files to get the directories and the security files.
86     */
87    private RepositoryFileFilter filter = new RepositoryFileFilter(false, true, true);
88  
89    /**
90     * The base path to start the cleaning. By default the value is set to the base of the
91     * repository.
92     */
93    private File rootFile = Configuration.getRepositoryRoot();
94  
95    /**
96     * Constructor.
97     *
98     * @param sleep
99     *          a long with the time to sleep before start running the thread.
100    */
101   private PermissionsCleanerThread(long sleep) {
102     this.sleep = sleep;
103     this.setPriority(Thread.MIN_PRIORITY);
104   }
105 
106   /**
107    * Sets the base path to start the cleaning.
108    *
109    * @param rootFile
110    *          the File to the base path to start the cleaning. If it is null, it does not
111    *          exist or it does not belong to the repository, the previous value is not
112    *          changed.
113    */
114   public void setRootFile(File rootFile) {
115     if (rootFile != null && rootFile.exists()
116         && IxosSecurityManager.inRepository(rootFile)) {
117       this.rootFile = rootFile;
118     }
119   }
120 
121   /**
122    * Gets the base path to start the cleaning.
123    *
124    * @return the File to the base path to start the cleaning.
125    */
126   public File getRootFile() {
127     return this.rootFile;
128   }
129 
130   /**
131    * Tells if the thread has started running.
132    *
133    * @return a boolean that is true if and only if the thread has started running.
134    */
135   public boolean isLaunched() {
136     return launched;
137   }
138 
139   /**
140    * Runs the logic that cleans the permissions of the users and roles deleted.
141    *
142    * @see java.lang.Thread#run()
143    */
144   public void run() {
145     try {
146       Thread.sleep(this.sleep);
147 
148       if (log.isInfoEnabled()) {
149         log.info("Thread begins running");
150       }
151       // First of all, the thread acknowledges that it is already running. Any
152       // user or role deletion after this point will cause a new
153       // PermissionsCleanerThread to be launched.
154       this.launched = true;
155 
156       // Switching to superuser mode. There is no need to switch back to the
157       // current user (that has not been set) because after cleaning the
158       // permissions the task stops.
159       KernelContext.getContext().setEffectiveUser(Configuration.getPrivilegedUser());
160 
161       if (isUserSystemLoaded()) {
162         this.cleanFilePermissions();
163         this.cleanAllAclPermissions();
164       } else {
165         // If the user data was not correctly loaded, performing the cleaning
166         // of permissions will cause losing all the security data, because all
167         // the permissions would be cleaned.
168         log.error("The UserSystem data was not correctly loaded");
169         log.error("File and ACL permissions will not be cleaned");
170       }
171     } catch (InterruptedException ie) {
172       // Nothing to do here, just exit the method
173     } catch (KernelException ke) {
174       log.warn("Could not switch to superuser mode.");
175     }
176   }
177 
178   /**
179    * Starts the clening process of the repository permissions.
180    */
181   private void cleanFilePermissions() {
182     // We check every file recursively.
183     try {
184       // If it is a repository file we find its security file.
185       File fileToClean = null;
186       if (this.rootFile.isDirectory()) {
187         fileToClean = this.rootFile;
188       } else {
189         if (this.rootFile.getName().startsWith(IxosSecurityManager.SECURITY_PREFIX)) {
190           // It is a security file.
191           fileToClean = this.rootFile;
192         } else {
193           // It is a repository file. We try to find its security file
194           fileToClean = IxosSecurityManager.findOwnSecurityFile(this.rootFile);
195           if (!fileToClean.exists()) {
196             // SJM: If the file does not have its own security file, we can not
197             // be sure that changing its security properties will not affect
198             // other files.
199             return;
200           }
201         }
202       }
203       if (log.isInfoEnabled()) {
204         log.info("Cleaning lost permisions of " + fileToClean);
205       }
206       cleanFilePermissions(fileToClean, true);
207       if (log.isInfoEnabled()) {
208         log.info("Permisions of " + this.rootFile + " successfully cleaned");
209       }
210     } catch (KernelException ke) {
211       log.error("Controled exception happened while cleaning permissions of "
212           + this.rootFile, ke);
213     }
214   }
215 
216   /**
217    * Cleaning the Permissions of the deleted users and roles.
218    *
219    * @param f
220    *          the File whose permissions are going to be cleaned. It can be eiter a
221    *          security file or a directory. The security files and directories or a
222    *          directories are recursively cleaned up. If it is a directory and
223    *          <code>firstRecursion</code> is true, then its own security file is also
224    *          searched and, if found, cleaned up (if it is false the security file of a
225    *          directory would be modified by the previous recursive call).
226    * @param firstRecursion
227    *          a boolean that is true only at the first recursive call of this method.
228    * @throws KernelException
229    *           If an error occurs
230    */
231   private void cleanFilePermissions(File f, boolean firstRecursion)
232     throws KernelException {
233     // We only accept directories or security files
234     if (f.isDirectory()) {
235       if (log.isDebugEnabled()) {
236         log.debug("Going to clean files in " + f);
237       }
238       if (firstRecursion) {
239         File directorySecurityFile = IxosSecurityManager.findOwnSecurityFile(f);
240         if (directorySecurityFile.exists()) {
241           cleanFilePermissions(directorySecurityFile, false);
242         }
243       }
244       File[] files = f.listFiles(this.filter);
245       if (files != null) {
246         for (int i = 0; i < files.length; i++) {
247           cleanFilePermissions(files[i], false);
248         }
249       }
250     } else {
251       cleanFilePermissions(f);
252     }
253   }
254 
255   /**
256    * Purges a security file from references to deleted users and/or roles.
257    *
258    * @param f
259    *          a File to the security file to clean.
260    * @throws KernelException
261    *           in case of error.
262    */
263   private void cleanFilePermissions(File f) throws KernelException {
264     // If it is not a directory, then it can only be a security file.
265     this.permissionsManager.lock(f);
266 
267     boolean locked = true;
268     try {
269       FilePermissions[][] permissions;
270       synchronized (FileProperties.synchronize) {
271         FileProperties props = new FileProperties(f);
272         permissions = this.permissionsManager.getPermissionsLists(props);
273       }
274       List<FilePermissions> newPermList = new ArrayList<FilePermissions>();
275       boolean changed = false;
276       // Checking if all the users with permissions do exist
277       for (FilePermissions filePerms : permissions[0]) {
278         if (this.userSystem.userExists(filePerms.getName())) {
279           newPermList.add(filePerms);
280         } else {
281           changed = true;
282         }
283       }
284       // Checking if all the roles with permissions do exist
285       for (FilePermissions filePerms : permissions[1]) {
286         if (this.userSystem.roleExists(filePerms.getName())) {
287           newPermList.add(filePerms);
288         } else {
289           changed = true;
290         }
291       }
292       if (changed) {
293         // Some permissions have been deleted, we change the file
294         log.debug("Changing mod file " + f.getName());
295         FilePermissions[] newPermArray = new FilePermissions[newPermList.size()];
296         newPermList.toArray(newPermArray);
297         this.permissionsManager.quickChmod(f, newPermArray);
298         // quickChmod deletes all the properties, including lock information.
299         locked = false;
300       }
301     } catch (Exception e) {
302       log.error("Exception catched when cleaning lost permissions of file system: ", e);
303       throw ApiUtils.processException(e);
304     } finally {
305       if (locked) {
306         this.permissionsManager.unlock(f);
307       }
308     }
309   }
310 
311   /**
312    * Cleans all the Acl of references to deleted users and/or roles.
313    */
314   private void cleanAllAclPermissions() {
315     try {
316       if (log.isInfoEnabled()) {
317         log.info("Starting cleaning of ACLs");
318       }
319 
320       AclList aclList = AclList.getInstance();
321       String[] domains = aclList.getAllAclDomains();
322       // Check every domain
323       for (int i = 0; i < domains.length; i++) {
324 
325         // Check every ACL in a domain
326         Collection acls = aclList.getAllAcls(domains[i]);
327         Iterator it = acls.iterator();
328         while (it.hasNext()) {
329           Acl acl = (Acl) it.next();
330           if (log.isDebugEnabled()) {
331             log.debug("Going to clean Acl : " + acl.getName());
332           }
333           if (this.cleanAclPermission(acl)) {
334             if (log.isDebugEnabled()) {
335               log.debug("Saving modified Acl: " + acl.getName());
336             }
337             acl.store();
338           }
339         }
340       }
341       if (log.isInfoEnabled()) {
342         log.info("Cleaning of ACLs successfully finished");
343       }
344     } catch (KernelException e) {
345       log.error("Exception retrieved when cleaning ACLs", e);
346     }
347   }
348 
349   /**
350    * Cleans an ACL of references to deleted users and/or roles.
351    *
352    * @param acl
353    *          the Acl that is being cleaned.
354    * @return a boolean that is true if the cleaning process has modified
355    *           <code>acl</code>data.
356    * @throws KernelException
357    *           in case of error.
358    */
359   private boolean cleanAclPermission(Acl acl) throws KernelException {
360     boolean modified = false;
361 
362     // Check every permission in an ACL.
363     String[] permissions = acl.getPermissions();
364     for (int i = 0; i < permissions.length; i++) {
365 
366       // Check every role for a permission
367       String[] roles = acl.getRoles(permissions[i]);
368       for (int j = 0; j < roles.length; j++) {
369         if (!this.userSystem.roleExists(roles[j])) {
370           modified = true;
371           acl.revokeRole(permissions[i], roles[j]);
372         }
373       }
374 
375       // Check every user for a permission
376       String[] users = acl.getDirectUsers(permissions[i]);
377       for (int j = 0; j < users.length; j++) {
378         if (!this.userSystem.userExists(users[j])) {
379           modified = true;
380           acl.revokeUser(permissions[i], users[j]);
381         }
382       }
383     }
384 
385     // Check all roleOwners of the ACL.
386     String[] roleOwners = acl.getRoleOwners();
387     for (int i = 0; i < roleOwners.length; i++) {
388       if (!this.userSystem.roleExists(roleOwners[i])) {
389         modified = true;
390         acl.removeRoleOwner(roleOwners[i]);
391       }
392     }
393 
394     // Check all userOwners of the ACL.
395     String[] userOwners = acl.getUserOwners();
396     for (int i = 0; i < userOwners.length; i++) {
397       if (!this.userSystem.userExists(userOwners[i])) {
398         modified = true;
399         acl.removeUserOwner(userOwners[i]);
400       }
401     }
402 
403     // If the ACL has been modified, write it down to disk.
404     return modified;
405   }
406 
407   /**
408    * Cleans the security files of permissions to deleted users and/or roles.
409    * <p>
410    * This task is done by launching a PermissionsCleanerThread using the repository root
411    * as starting point. The task is launched with a delay so, if some users/roles are
412    * deleted together, they can be deleted from the security files in just one execution.
413    * <p>
414    * If this method is called while a task is waiting to be launched, nothing is done.
415    *
416    * @param delay
417    *          The delay for the new thread execution
418    */
419   public static void startCleaner(long delay) {
420     // If a thread is waiting, the new request will be processed by that
421     // when it begins running.
422     if (instance != null && instance.isAlive()) {
423       if (instance.isLaunched()) {
424         // Only one working thread at a time, so this one is interrupted
425         instance.interrupt();
426       } else {
427         // The current worker thread has not yet started, keep it
428         return;
429       }
430     }
431     instance = new PermissionsCleanerThread(delay);
432     instance.start();
433   }
434 
435   /**
436    * Stops the current permissions cleaner thread. To be called from the plugin shutdown()
437    * method.
438    */
439   public static void stopCleaner() {
440     if (instance != null && instance.isAlive()) {
441       instance.interrupt();
442     }
443   }
444 
445   /**
446    * Tells if the User and Role data was correctly loaded.
447    *
448    * @return true if and only if at least an user and a role exist.
449    * @throws KernelException
450    *           in case of error.
451    */
452   private boolean isUserSystemLoaded() throws KernelException {
453     ApiUserSystemImpl apiUserSystem = ApiUserSystemImpl.getInstance();
454     String[] roleNames = apiUserSystem.getAllRoleNames();
455     String[] userNames = apiUserSystem.getAllUserNames();
456     return ((userNames != null) && (userNames.length > 0) && (roleNames != null)
457         && (roleNames.length > 0));
458   }
459 }