[seam-commits] Seam SVN: r8001 - trunk/src/main/org/jboss/seam/security/permission.

seam-commits at lists.jboss.org seam-commits at lists.jboss.org
Tue Apr 22 00:49:09 EDT 2008


Author: shane.bryzak at jboss.com
Date: 2008-04-22 00:49:09 -0400 (Tue, 22 Apr 2008)
New Revision: 8001

Added:
   trunk/src/main/org/jboss/seam/security/permission/JpaDynamicPermissionStore.java
   trunk/src/main/org/jboss/seam/security/permission/PersistentPermissionResolver.java
Removed:
   trunk/src/main/org/jboss/seam/security/permission/acl/
   trunk/src/main/org/jboss/seam/security/permission/dynamic/
Modified:
   trunk/src/main/org/jboss/seam/security/permission/PermissionManager.java
   trunk/src/main/org/jboss/seam/security/permission/PermissionStore.java
Log:
consolidate dynamic and acl permission resolvers into PersistentPermissionResolver

Added: trunk/src/main/org/jboss/seam/security/permission/JpaDynamicPermissionStore.java
===================================================================
--- trunk/src/main/org/jboss/seam/security/permission/JpaDynamicPermissionStore.java	                        (rev 0)
+++ trunk/src/main/org/jboss/seam/security/permission/JpaDynamicPermissionStore.java	2008-04-22 04:49:09 UTC (rev 8001)
@@ -0,0 +1,440 @@
+package org.jboss.seam.security.permission;
+
+import static org.jboss.seam.ScopeType.APPLICATION;
+import static org.jboss.seam.annotations.Install.BUILT_IN;
+
+import java.io.Serializable;
+import java.security.Principal;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+import javax.persistence.EntityManager;
+import javax.persistence.NoResultException;
+import javax.persistence.Query;
+
+import org.jboss.seam.Component;
+import org.jboss.seam.annotations.Create;
+import org.jboss.seam.annotations.Install;
+import org.jboss.seam.annotations.Name;
+import org.jboss.seam.annotations.Scope;
+import org.jboss.seam.annotations.intercept.BypassInterceptors;
+import org.jboss.seam.annotations.security.permission.PermissionAction;
+import org.jboss.seam.annotations.security.permission.PermissionDiscriminator;
+import org.jboss.seam.annotations.security.permission.PermissionRole;
+import org.jboss.seam.annotations.security.permission.PermissionTarget;
+import org.jboss.seam.annotations.security.permission.PermissionUser;
+import org.jboss.seam.core.Expressions;
+import org.jboss.seam.core.Expressions.ValueExpression;
+import org.jboss.seam.log.LogProvider;
+import org.jboss.seam.log.Logging;
+import org.jboss.seam.security.Role;
+import org.jboss.seam.security.SimplePrincipal;
+import org.jboss.seam.security.management.JpaIdentityStore;
+import org.jboss.seam.util.AnnotatedBeanProperty;
+
+/**
+ * A permission store implementation that uses JPA as its persistence mechanism.
+ * 
+ * @author Shane Bryzak
+ */
+ at Name("org.jboss.seam.security.permission.jpaDynamicPermissionStore")
+ at Install(precedence = BUILT_IN, value=false) 
+ at Scope(APPLICATION)
+ at BypassInterceptors
+public class JpaDynamicPermissionStore implements PermissionStore, Serializable
+{
+   private static final LogProvider log = Logging.getLogProvider(JpaDynamicPermissionStore.class); 
+   
+   private ValueExpression<EntityManager> entityManager;
+   
+   private Class userPermissionClass;
+   private Class rolePermissionClass;
+      
+   private AnnotatedBeanProperty<PermissionUser> userProperty;
+   private AnnotatedBeanProperty<PermissionRole> roleProperty;
+   
+   private AnnotatedBeanProperty<PermissionTarget> targetProperty;
+   private AnnotatedBeanProperty<PermissionAction> actionProperty;   
+   private AnnotatedBeanProperty<PermissionDiscriminator> discriminatorProperty;
+   
+   private AnnotatedBeanProperty<PermissionTarget> roleTargetProperty;
+   private AnnotatedBeanProperty<PermissionAction> roleActionProperty;
+   
+   private String selectUserPermissionQuery;
+   private String selectRolePermissionQuery;
+
+   @Create
+   public void init()
+   {      
+      if (userPermissionClass == null)
+      {
+         log.debug("No permissionClass set, JpaDynamicPermissionStore will be unavailable.");
+         return;
+      }   
+      
+      if (entityManager == null)
+      {
+         entityManager = Expressions.instance().createValueExpression("#{entityManager}", EntityManager.class);
+      }       
+      
+      initProperties();
+      buildQueries();
+   }   
+   
+   protected void initProperties()
+   {
+      userProperty = AnnotatedBeanProperty.scanForProperty(userPermissionClass, PermissionUser.class);
+      targetProperty = AnnotatedBeanProperty.scanForProperty(userPermissionClass, PermissionTarget.class);
+      actionProperty = AnnotatedBeanProperty.scanForProperty(userPermissionClass, PermissionAction.class);
+      
+      if (rolePermissionClass != null)
+      {
+         roleProperty = AnnotatedBeanProperty.scanForProperty(rolePermissionClass, PermissionRole.class);
+         if (roleProperty != null)
+         {
+            roleTargetProperty = AnnotatedBeanProperty.scanForProperty(rolePermissionClass, PermissionTarget.class);
+            roleActionProperty = AnnotatedBeanProperty.scanForProperty(rolePermissionClass, PermissionAction.class);
+         }
+      }
+      else
+      {
+         roleProperty = AnnotatedBeanProperty.scanForProperty(userPermissionClass, PermissionRole.class);
+         if (roleProperty != null)
+         {
+            discriminatorProperty = AnnotatedBeanProperty.scanForProperty(userPermissionClass, PermissionDiscriminator.class);
+         }
+      }
+      
+      if (userProperty == null) 
+      {
+         throw new RuntimeException("Invalid userPermissionClass " + userPermissionClass.getName() + 
+               " - required annotation @PermissionUser not found on any Field or Method.");
+      }
+
+      if (rolePermissionClass != null)
+      {
+         if (roleProperty == null)
+         {
+            throw new RuntimeException("Invalid rolePermissionClass " + rolePermissionClass.getName() +
+                  " - required annotation @PermissionRole not found on any Field or Method.");
+         }
+         
+         if (roleTargetProperty == null)
+         {
+            throw new RuntimeException("Invalid rolePermissionClass " + rolePermissionClass.getName() +
+                  " - required annotation @PermissionTarget not found on any Field or Method.");
+         }
+         
+         if (roleActionProperty == null)
+         {
+            throw new RuntimeException("Invalid rolePermissionClass " + rolePermissionClass.getName() +
+                  " - required annotation @PermissionAction not found on any Field or Method.");
+         }
+      }
+      else if (discriminatorProperty == null)
+      {
+         throw new RuntimeException("Invalid userPermissionClass " + rolePermissionClass.getName() +
+               " - no rolePermissionClass set and @PermissionDiscriminator annotation not found on " +
+               "any Field or Method");
+      }
+   }   
+   
+   protected void buildQueries()
+   {
+      StringBuffer query = new StringBuffer();
+      query.append("select p from ");
+      query.append(userPermissionClass.getName());
+      query.append(" p where ");
+      query.append(targetProperty.getName());
+      query.append(" = :target and ");
+      query.append(actionProperty.getName());
+      query.append(" = :action");
+            
+      selectUserPermissionQuery = query.toString();
+      
+      if (rolePermissionClass != null)
+      {
+         query.setLength(0);
+         query.append("select p from ");
+         query.append(rolePermissionClass.getName());
+         query.append(" p where ");
+         query.append(roleTargetProperty.getName());
+         query.append(" = :target and ");
+         query.append(roleActionProperty.getName());
+         query.append(" = :action");
+         
+         selectRolePermissionQuery = query.toString();
+      }
+      else
+      {
+         selectRolePermissionQuery = selectUserPermissionQuery;
+      }
+   }
+   
+   public boolean grantPermission(Permission permission)
+   {
+      boolean recipientIsRole = permission.getRecipient() instanceof Role;
+      
+      try
+      {
+         if (recipientIsRole)
+         {
+            if (rolePermissionClass != null)
+            {
+               Object instance = rolePermissionClass.newInstance();
+               roleTargetProperty.setValue(instance, permission.getTarget().toString());
+               roleActionProperty.setValue(instance, permission.getAction());
+               roleProperty.setValue(instance, permission.getRecipient().getName());
+               lookupEntityManager().persist(instance);
+               return true;
+            }
+            
+            if (discriminatorProperty == null)
+            {
+               throw new RuntimeException("Could not grant permission, rolePermissionClass not set");   
+            }
+         }
+         
+         if (userPermissionClass == null)
+         {
+            throw new RuntimeException("Could not grant permission, userPermissionClass not set");
+         }
+                 
+         Object instance = userPermissionClass.newInstance();
+         targetProperty.setValue(instance, permission.getTarget().toString());
+         actionProperty.setValue(instance, permission.getAction());         
+         userProperty.setValue(instance, resolvePrincipal(permission.getRecipient()));
+         
+         if (discriminatorProperty != null)
+         {
+            discriminatorProperty.setValue(instance, getDiscriminatorValue(recipientIsRole));
+         }
+         
+         lookupEntityManager().persist(instance);
+         
+         return true;
+      }
+      catch (Exception ex)
+      {
+         throw new RuntimeException("Could not grant permission", ex);
+      }   
+   }
+   
+   private String getDiscriminatorValue(boolean isRole)
+   {
+      PermissionDiscriminator discriminator = (PermissionDiscriminator) discriminatorProperty.getAnnotation();
+      return isRole ? discriminator.roleValue() : discriminator.userValue();      
+   }
+   
+   public boolean revokePermission(Permission permission)
+   {
+      boolean recipientIsRole = permission.getRecipient() instanceof Role;
+
+      EntityManager em = lookupEntityManager();
+      
+      Query qry = em.createQuery(recipientIsRole ? selectRolePermissionQuery :
+         selectUserPermissionQuery)
+         .setParameter("target", permission.getTarget())
+         .setParameter("action", permission.getAction())
+         .setParameter("recipient", resolvePrincipal(permission.getRecipient()));
+      
+      if (discriminatorProperty != null)
+      {
+         qry.setParameter("discriminator", getDiscriminatorValue(recipientIsRole));
+      }
+      
+      try
+      {
+         Object instance = qry.getSingleResult();        
+         em.remove(instance);
+         return true;
+      }
+      catch (NoResultException ex)
+      {
+         return false;
+      }
+   }   
+   
+   /**
+    * If the user or role properties in the entity class refer to other entities, then this method
+    * uses the JpaIdentityStore (if available) to lookup that user or role entity.  Otherwise it
+    * simply returns the name of the recipient. 
+    * 
+    * @param recipient
+    * @return
+    */
+   protected Object resolvePrincipal(Principal recipient)
+   {
+      boolean recipientIsRole = recipient instanceof Role;
+         
+      JpaIdentityStore identityStore = (JpaIdentityStore) Component.getInstance(JpaIdentityStore.class, true);
+      
+      if (identityStore != null)
+      {
+         if (recipientIsRole && roleProperty != null && roleProperty.getPropertyClass().equals(identityStore.getRoleClass()))
+         {
+            return identityStore.lookupRole(recipient.getName());
+         }
+         else if (userProperty.getPropertyClass().equals(identityStore.getUserClass()))
+         {
+            return identityStore.lookupUser(recipient.getName());
+         }
+      }      
+      
+      return recipient.getName();
+   }
+   
+   protected String resolvePrincipalName(Object principal, boolean isUser
+         )
+   {
+      if (principal instanceof String)
+      {
+         return (String) principal;
+      }
+      
+      JpaIdentityStore identityStore = (JpaIdentityStore) Component.getInstance(JpaIdentityStore.class, true);
+      
+      if (identityStore != null)
+      {
+         if (isUser && identityStore.getUserClass().equals(principal.getClass()))
+         {
+            return identityStore.getUserName(principal);
+         }
+         
+         if (!isUser && identityStore.getRoleClass().equals(principal.getClass()))
+         {
+            return identityStore.getRoleName(principal);
+         }
+      }
+      
+      throw new IllegalArgumentException("Cannot resolve principal name for principal " + principal); 
+   }
+
+   public List<Permission> listPermissions(Object target, String action) 
+   {
+      List<Permission> permissions = new ArrayList<Permission>();
+      
+      Query permissionQuery = lookupEntityManager().createQuery(selectUserPermissionQuery)
+         .setParameter("target", target);
+      
+      if (action != null)
+      {
+         permissionQuery.setParameter("action", action);
+      }
+      
+      List userPermissions = permissionQuery.getResultList(); 
+      
+      Map<String,Principal> principalCache = new HashMap<String,Principal>();
+      
+      boolean useDiscriminator = rolePermissionClass == null && discriminatorProperty != null;
+      
+      for (Object permission : userPermissions)
+      {
+         Principal principal;
+         boolean isUser = true;
+         
+         if (useDiscriminator && 
+            discriminatorProperty.getAnnotation().roleValue().equals(discriminatorProperty.getValue(permission)))
+         {
+            isUser = false;
+         }
+
+         String name = resolvePrincipalName(isUser ? userProperty.getValue(permission) :
+            roleProperty.getValue(permission), isUser);
+         
+         String key = (isUser ? "user:" : "role:") + name;
+         
+         if (!principalCache.containsKey(key))
+         {
+            principal = isUser ? new SimplePrincipal(name) : new Role(name);
+            principalCache.put(key, principal);
+         }
+         else
+         {
+            principal = principalCache.get(key);
+         }
+         
+         permissions.add(new Permission(target, (String) (action != null ? action : actionProperty.getValue(permission)), 
+               principal));
+      }
+      
+      if (rolePermissionClass == null)
+      {
+         permissionQuery = lookupEntityManager().createQuery(selectRolePermissionQuery)
+         .setParameter("target", target);         
+         
+         if (action != null)
+         {
+            permissionQuery.setParameter("action", action);
+         }
+         
+         List rolePermissions = permissionQuery.getResultList();
+         
+         for (Object permission : rolePermissions)
+         {
+            Principal principal;
+            
+            String name = resolvePrincipalName(roleProperty.getValue(permission), false);
+            String key = "role:" + name;
+            
+            if (!principalCache.containsKey(key))
+            {
+               principal = new Role(name);
+               principalCache.put(key, principal);
+            }
+            else
+            {
+               principal = principalCache.get(key);
+            }
+            
+            permissions.add(new Permission(target, (String) (action != null ? action : 
+               roleActionProperty.getValue(permission)), principal));
+         }
+      }
+      
+      return permissions;
+   }
+
+   public List<Permission> listPermissions(Object target) 
+   {
+      return listPermissions(target, null);
+   }
+
+   private EntityManager lookupEntityManager()
+   {
+      return entityManager.getValue();
+   }
+   
+   public ValueExpression getEntityManager()
+   {
+      return entityManager;
+   }
+   
+   public void setEntityManager(ValueExpression expression)
+   {
+      this.entityManager = expression;
+   } 
+   
+   public Class getUserPermissionClass()
+   {
+      return userPermissionClass;
+   }
+   
+   public void setUserPermissionClass(Class userPermissionClass)
+   {
+      this.userPermissionClass = userPermissionClass;
+   }
+   
+   public Class getRolePermissionClass()
+   {
+      return rolePermissionClass;
+   }
+   
+   public void setRolePermissionClass(Class rolePermissionClass)
+   {
+      this.rolePermissionClass = rolePermissionClass;
+   }
+}

Modified: trunk/src/main/org/jboss/seam/security/permission/PermissionManager.java
===================================================================
--- trunk/src/main/org/jboss/seam/security/permission/PermissionManager.java	2008-04-21 22:30:08 UTC (rev 8000)
+++ trunk/src/main/org/jboss/seam/security/permission/PermissionManager.java	2008-04-22 04:49:09 UTC (rev 8001)
@@ -16,7 +16,6 @@
 import org.jboss.seam.log.LogProvider;
 import org.jboss.seam.log.Logging;
 import org.jboss.seam.security.Identity;
-import org.jboss.seam.security.permission.PermissionStore;
 
 /**
  * Permission management component, used to grant or revoke permissions on specific objects or of
@@ -29,8 +28,7 @@
 @Install(precedence = BUILT_IN)
 public class PermissionManager implements Serializable
 {
-   public static final String ACCOUNT_PERMISSION_STORE_COMPONENT_NAME = "accountPermissionStore";
-   public static final String ACL_PERMISSION_STORE_COMPONENT_NAME = "aclPermissionStore";
+   public static final String PERMISSION_STORE_COMPONENT_NAME = "permissionStore";
    
    public static final String PERMISSION_PERMISSION_NAME = "seam.permission";
    
@@ -40,34 +38,21 @@
    
    private static final LogProvider log = Logging.getLogProvider(PermissionManager.class);
    
-   private PermissionStore dynamicPermissionStore;
+   private PermissionStore permissionStore;
    
-   private PermissionStore aclPermissionStore;
-   
    @Create
    public void create()
    {
-      if (dynamicPermissionStore == null)
+      if (permissionStore == null)
       {
-         dynamicPermissionStore = (PermissionStore) Component.getInstance(ACCOUNT_PERMISSION_STORE_COMPONENT_NAME, true);
+         permissionStore = (PermissionStore) Component.getInstance(PERMISSION_STORE_COMPONENT_NAME, true);
       }         
       
-      if (dynamicPermissionStore == null)
+      if (permissionStore == null)
       {
-         log.warn("no account permission store available - please install an AccountPermissionStore with the name '" +
-               ACCOUNT_PERMISSION_STORE_COMPONENT_NAME + "' if account-based permission management is required.");
+         log.warn("no permission store available - please install a PermissionStore with the name '" +
+               PERMISSION_STORE_COMPONENT_NAME + "' if permission management is required.");
       }
-      
-      if (aclPermissionStore == null)
-      {
-         aclPermissionStore = (PermissionStore) Component.getInstance(ACL_PERMISSION_STORE_COMPONENT_NAME);
-      }
-      
-      if (aclPermissionStore == null)
-      {
-         log.warn("no ACL permission store available - please install an AclPermissionStore with the name '" +
-               ACL_PERMISSION_STORE_COMPONENT_NAME + "' if ACL-based permission management is required.");
-      }
    } 
    
    public static PermissionManager instance()
@@ -88,59 +73,37 @@
       return instance;
    }
    
-   public PermissionStore getDynamicPermissionStore()
+   public PermissionStore getPermissionStore()
    {
-      return dynamicPermissionStore;
+      return permissionStore;
    }
    
-   public void setDynamicPermissionStore(PermissionStore dynamicPermissionStore)
+   public void setPermissionStore(PermissionStore permissionStore)
    {
-      this.dynamicPermissionStore = dynamicPermissionStore;
+      this.permissionStore = permissionStore;
    }
    
    public List<Permission> listPermissions(String target, String action)
    {
       Identity.instance().checkPermission(PERMISSION_PERMISSION_NAME, PERMISSION_READ);
-      return dynamicPermissionStore.listPermissions(target, action);
+      return permissionStore.listPermissions(target, action);
    }
    
-   public List<Permission> listPermissions(String target)
-   {
-      Identity.instance().checkPermission(PERMISSION_PERMISSION_NAME, PERMISSION_READ);
-      return dynamicPermissionStore.listPermissions(target);
-   }
-   
    public List<Permission> listPermissions(Object target)
    {
       Identity.instance().checkPermission(PERMISSION_PERMISSION_NAME, PERMISSION_READ);
-      return aclPermissionStore.listPermissions(target);
+      return permissionStore.listPermissions(target);
    }
    
    public boolean grantPermission(Permission permission)
    {
       Identity.instance().checkPermission(permission, PERMISSION_GRANT);
-      
-      if (permission.getTarget() instanceof String)
-      {
-         return dynamicPermissionStore.grantPermission(permission);
-      }
-      else
-      {
-         return aclPermissionStore.grantPermission(permission);
-      }
+      return permissionStore.grantPermission(permission);
    }
    
    public boolean revokePermission(Permission permission)
    {
       Identity.instance().checkPermission(permission, PERMISSION_REVOKE);
-      
-      if (permission.getTarget() instanceof String)
-      {
-         return dynamicPermissionStore.revokePermission(permission);
-      }
-      else
-      {
-         return aclPermissionStore.revokePermission(permission);
-      }
+      return permissionStore.revokePermission(permission);
    }
 }

Modified: trunk/src/main/org/jboss/seam/security/permission/PermissionStore.java
===================================================================
--- trunk/src/main/org/jboss/seam/security/permission/PermissionStore.java	2008-04-21 22:30:08 UTC (rev 8000)
+++ trunk/src/main/org/jboss/seam/security/permission/PermissionStore.java	2008-04-22 04:49:09 UTC (rev 8001)
@@ -1,7 +1,6 @@
 package org.jboss.seam.security.permission;
 
 import java.util.List;
-import java.util.Set;
 
 /**
  * Permission store interface.
@@ -12,7 +11,6 @@
 {
    List<Permission> listPermissions(Object target);
    List<Permission> listPermissions(Object target, String action);
-   List<Permission> listPermissions(Set<Object> targets);
    boolean grantPermission(Permission permission);
    boolean revokePermission(Permission permission);
 }

Added: trunk/src/main/org/jboss/seam/security/permission/PersistentPermissionResolver.java
===================================================================
--- trunk/src/main/org/jboss/seam/security/permission/PersistentPermissionResolver.java	                        (rev 0)
+++ trunk/src/main/org/jboss/seam/security/permission/PersistentPermissionResolver.java	2008-04-22 04:49:09 UTC (rev 8001)
@@ -0,0 +1,103 @@
+package org.jboss.seam.security.permission;
+
+import static org.jboss.seam.ScopeType.APPLICATION;
+import static org.jboss.seam.annotations.Install.FRAMEWORK;
+
+import java.io.Serializable;
+import java.util.List;
+
+import org.jboss.seam.Component;
+import org.jboss.seam.Seam;
+import org.jboss.seam.annotations.Create;
+import org.jboss.seam.annotations.Install;
+import org.jboss.seam.annotations.Name;
+import org.jboss.seam.annotations.Scope;
+import org.jboss.seam.annotations.Startup;
+import org.jboss.seam.annotations.intercept.BypassInterceptors;
+import org.jboss.seam.log.LogProvider;
+import org.jboss.seam.log.Logging;
+import org.jboss.seam.security.Identity;
+
+/**
+ * Resolves dynamically-assigned permissions, mapped to a user or a role, and kept in persistent 
+ * storage, such as a relational database.
+ * 
+ * @author Shane Bryzak
+ */
+ at Name("org.jboss.seam.security.dynamicPermissionResolver")
+ at Scope(APPLICATION)
+ at BypassInterceptors
+ at Install(precedence=FRAMEWORK)
+ at Startup
+public class PersistentPermissionResolver implements PermissionResolver, Serializable
+{   
+   private static final String DEFAULT_PERMISSION_STORE_NAME = "jpaDynamicPermissionStore";
+   
+   private PermissionStore permissionStore;
+   
+   private static final LogProvider log = Logging.getLogProvider(PersistentPermissionResolver.class);   
+   
+   @Create
+   public void create()
+   {
+      initPermissionStore();
+   }
+   
+   protected void initPermissionStore()
+   {
+      if (permissionStore == null)
+      {
+         permissionStore = (PermissionStore) Component.getInstance(DEFAULT_PERMISSION_STORE_NAME, true);
+      }           
+      
+      if (permissionStore == null)
+      {
+         log.warn("no permission store available - please install a PermissionStore with the name '" +
+               DEFAULT_PERMISSION_STORE_NAME + "' if dynamic permissions are required.");
+      }
+   }     
+   
+   public PermissionStore getPermissionStore()
+   {
+      return permissionStore;
+   }
+   
+   public void setPermissionStore(PermissionStore permissionStore)
+   {
+      this.permissionStore = permissionStore;
+   }
+   
+   public boolean hasPermission(Object target, String action)
+   {      
+      if (permissionStore == null) return false;
+      
+      Identity identity = Identity.instance();
+      
+      if (!identity.isLoggedIn()) return false;
+      
+      String targetName = Seam.getComponentName(target.getClass());
+      if (targetName == null)
+      {
+         targetName = target.getClass().getName();
+      }
+      
+      List<Permission> permissions = permissionStore.listPermissions(targetName, action);
+      
+      String username = identity.getPrincipal().getName();
+      
+      for (Permission permission : permissions)
+      {
+         if (username.equals(permission.getRecipient().getName()))
+         {
+            return true;
+         }
+         
+         if (identity.hasRole(permission.getRecipient().getName()))
+         {
+            return true;
+         }
+      }      
+      
+      return false;
+   }
+}




More information about the seam-commits mailing list