Improve secure logging
authorMathieu Baudier <mbaudier@argeo.org>
Wed, 22 Feb 2012 15:39:39 +0000 (15:39 +0000)
committerMathieu Baudier <mbaudier@argeo.org>
Wed, 22 Feb 2012 15:39:39 +0000 (15:39 +0000)
git-svn-id: https://svn.argeo.org/commons/trunk@5111 4cfe0d0a-d680-48aa-b62c-e0a02a3f76cc

20 files changed:
basic/runtime/org.argeo.basic.nodeps/src/main/java/org/argeo/ArgeoLogger.java [new file with mode: 0644]
osgi/plugins/org.argeo.osgi.ui.explorer/plugin.xml
security/plugins/org.argeo.security.ui.admin/plugin.xml
security/plugins/org.argeo.security.ui/META-INF/spring/commands.xml
security/plugins/org.argeo.security.ui/META-INF/spring/monitor.xml
security/plugins/org.argeo.security.ui/META-INF/spring/osgi.xml
security/plugins/org.argeo.security.ui/META-INF/spring/views.xml [new file with mode: 0644]
security/plugins/org.argeo.security.ui/icons/adminLog.gif [new file with mode: 0644]
security/plugins/org.argeo.security.ui/icons/log.gif [new file with mode: 0644]
security/plugins/org.argeo.security.ui/icons/maintenance.gif [new file with mode: 0644]
security/plugins/org.argeo.security.ui/plugin.xml
security/plugins/org.argeo.security.ui/src/main/java/org/argeo/security/ui/MaintenancePerspective.java [new file with mode: 0644]
security/plugins/org.argeo.security.ui/src/main/java/org/argeo/security/ui/UserHomePerspective.java
security/plugins/org.argeo.security.ui/src/main/java/org/argeo/security/ui/views/AdminLogView.java [new file with mode: 0644]
security/plugins/org.argeo.security.ui/src/main/java/org/argeo/security/ui/views/LogContentProvider.java
security/plugins/org.argeo.security.ui/src/main/java/org/argeo/security/ui/views/LogView.java
security/runtime/org.argeo.security.core/src/main/java/org/argeo/security/SecurityUtils.java [new file with mode: 0644]
security/runtime/org.argeo.security.core/src/main/java/org/argeo/security/log4j/SecureLogger.java
server/plugins/org.argeo.jcr.ui.explorer/plugin.xml
server/plugins/org.argeo.jcr.ui.explorer/src/main/java/org/argeo/jcr/ui/explorer/commands/AddRemoteRepository.java

diff --git a/basic/runtime/org.argeo.basic.nodeps/src/main/java/org/argeo/ArgeoLogger.java b/basic/runtime/org.argeo.basic.nodeps/src/main/java/org/argeo/ArgeoLogger.java
new file mode 100644 (file)
index 0000000..c72c537
--- /dev/null
@@ -0,0 +1,31 @@
+package org.argeo;
+
+/**
+ * Logging framework agnostic identifying a logging service, to which one can
+ * register
+ */
+public interface ArgeoLogger {
+       /**
+        * Register for events by threads with the same authentication (or all
+        * threads if admin)
+        */
+       public void register(ArgeoLogListener listener,
+                       Integer numberOfPreviousEvents);
+
+       /**
+        * For admin use only: register for all users
+        * 
+        * @param listener
+        *            the log listener
+        * @param numberOfPreviousEvents
+        *            the number of previous events to notify
+        * @param everything
+        *            if true even anonymous is logged
+        */
+       public void registerForAll(ArgeoLogListener listener,
+                       Integer numberOfPreviousEvents, boolean everything);
+
+       public void unregister(ArgeoLogListener listener);
+
+       public void unregisterForAll(ArgeoLogListener listener);
+}
index 27947067bcb9494cbbc3243df7c8ca38419339e3..848025bbd1087ab683621bfc93ffe0bd765d0a04 100644 (file)
    </extension>
   <extension
            point="org.eclipse.ui.activities">
-        <activity
-              description="Only for admins"
-              id="org.argeo.osgi.ui.explorer.adminActivity"
-              name="Admin">
-                 <enabledWhen>
-                   <with variable="roles">
-                     <iterate ifEmpty="false" operator="or">
-                       <equals value="ROLE_ADMIN" />
-                     </iterate>
-                   </with>
-                 </enabledWhen>
-        </activity>
         <activityPatternBinding
-              activityId="org.argeo.osgi.ui.explorer.adminActivity"
+              activityId="org.argeo.security.ui.adminActivity"
               isEqualityPattern="true"
               pattern="org.argeo.osgi.ui.explorer/org.argeo.osgi.ui.explorer.perspective">
         </activityPatternBinding>
index 9d09879582f39d828ff4eb4e63c19f64e22de3fb..bcb80ebd93bd61e00e930c08588411e23eed4fa8 100644 (file)
   </extension>
   <extension
            point="org.eclipse.ui.activities">
-        <activity
-              description="Only for admins"
-              id="org.argeo.security.ui.admin.adminActivity"
-              name="Admin">
-                 <enabledWhen>
-                   <with variable="roles">
-                     <iterate ifEmpty="false" operator="or">
-                       <equals value="ROLE_ADMIN" />
-                     </iterate>
-                   </with>
-                 </enabledWhen>
-        </activity>
         <!-- TODO: find a way to exclude evrything -->
         <activityPatternBinding
-              activityId="org.argeo.security.ui.admin.adminActivity"
+              activityId="org.argeo.security.ui.adminActivity"
               isEqualityPattern="true"
               pattern="org.argeo.security.ui.admin/org.argeo.security.ui.admin.adminSecurityPerspective">
         </activityPatternBinding>
index 1dc8d53cecea3c460d9fdc96a466c4ee69bb1cf7..3b2e6ccb0cc58d26d0fb193f2b4b59757384a58a 100644 (file)
@@ -4,8 +4,4 @@
        xsi:schemaLocation="http://www.springframework.org/schema/beans
         http://www.springframework.org/schema/beans/spring-beans.xsd">
 
-       <bean id="openChangePasswordDialog" class="org.argeo.security.ui.commands.OpenChangePasswordDialog"
-               scope="prototype">
-               <property name="userDetailsManager" ref="userDetailsManager" />
-       </bean>
 </beans>
index f2faeb2fd24b73f12e014635d1c8a02313fffe2e..93a370de4559c794daa53108b61329c05e269ddf 100644 (file)
@@ -5,9 +5,8 @@
         http://www.springframework.org/schema/beans/spring-beans-2.5.xsd">
 
        <!-- Log4j appender singleton -->
-       <bean id="secureAppender" class="org.argeo.security.log4j.SecureLogger"
+       <bean id="secureLogger" class="org.argeo.security.log4j.SecureLogger"
                init-method="init" destroy-method="destroy">
-               <property name="listeners" ref="logListeners" />
                <property name="configuration">
                        <value><![CDATA[
 log4j.rootLogger=WARN, console
index 010fd3bba8ec3238631edf544518bb7d9aeb8cd5..da85d5ccc7f2c959db67c86f6ef0d2984e497f22 100644 (file)
@@ -13,6 +13,5 @@
                interface="org.springframework.security.userdetails.UserDetailsManager"\r
                cardinality="0..1" />\r
 \r
-       <list id="logListeners" interface="org.argeo.ArgeoLogListener"\r
-               cardinality="0..N" />\r
+       <service ref="secureLogger" interface="org.argeo.ArgeoLogger" />\r
 </beans:beans>
\ No newline at end of file
diff --git a/security/plugins/org.argeo.security.ui/META-INF/spring/views.xml b/security/plugins/org.argeo.security.ui/META-INF/spring/views.xml
new file mode 100644 (file)
index 0000000..e5363dc
--- /dev/null
@@ -0,0 +1,17 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<beans xmlns="http://www.springframework.org/schema/beans"
+       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:p="http://www.springframework.org/schema/p"
+       xsi:schemaLocation="http://www.springframework.org/schema/beans
+        http://www.springframework.org/schema/beans/spring-beans.xsd">
+
+       <bean id="logView" class="org.argeo.security.ui.views.LogView"
+               scope="prototype">
+               <property name="argeoLogger" ref="secureLogger" />
+       </bean>
+
+       <bean id="adminLogView" class="org.argeo.security.ui.views.AdminLogView"
+               scope="prototype">
+               <property name="argeoLogger" ref="secureLogger" />
+       </bean>
+
+</beans>
diff --git a/security/plugins/org.argeo.security.ui/icons/adminLog.gif b/security/plugins/org.argeo.security.ui/icons/adminLog.gif
new file mode 100644 (file)
index 0000000..6ef3bca
Binary files /dev/null and b/security/plugins/org.argeo.security.ui/icons/adminLog.gif differ
diff --git a/security/plugins/org.argeo.security.ui/icons/log.gif b/security/plugins/org.argeo.security.ui/icons/log.gif
new file mode 100644 (file)
index 0000000..e3ecc55
Binary files /dev/null and b/security/plugins/org.argeo.security.ui/icons/log.gif differ
diff --git a/security/plugins/org.argeo.security.ui/icons/maintenance.gif b/security/plugins/org.argeo.security.ui/icons/maintenance.gif
new file mode 100644 (file)
index 0000000..e5690ec
Binary files /dev/null and b/security/plugins/org.argeo.security.ui/icons/maintenance.gif differ
index d016464e89a4aec3e4cba81157527a3a9d12cfa9..f8aeaa141e7a5030a15a3b040d449431cbba78c9 100644 (file)
    <extension
          point="org.eclipse.ui.views">
       <view
+            id="org.argeo.security.ui.userProfile"
             class="org.argeo.security.ui.views.UserProfile"
             icon="icons/user.gif"
-            id="org.argeo.security.ui.userProfile"
             name="Profile"
             restorable="true">
       </view>
       <view
-            class="org.argeo.security.ui.views.LogView"
             id="org.argeo.security.ui.logView"
+            class="org.argeo.eclipse.spring.SpringExtensionFactory"
             name="Log"
-            restorable="true">
+            icon="icons/log.gif"
+            restorable="false">
+      </view>
+      <view
+            id="org.argeo.security.ui.adminLogView"
+            class="org.argeo.eclipse.spring.SpringExtensionFactory"
+            name="Admin Log"
+            icon="icons/adminLog.gif"
+            restorable="false">
       </view>
    </extension>
    <extension
             id="org.argeo.security.ui.userHomePerspective"
             name="Home">
       </perspective>
+      <perspective
+            class="org.argeo.security.ui.MaintenancePerspective"
+            icon="icons/maintenance.gif"
+            id="org.argeo.security.ui.adminMaintenancePerspective"
+            name="Maintenance">
+      </perspective>
    </extension>
+  <extension
+           point="org.eclipse.ui.activities">
+        <activity
+              description="Only for admins"
+              id="org.argeo.security.ui.adminActivity"
+              name="Admin">
+                 <enabledWhen>
+                   <with variable="roles">
+                     <iterate ifEmpty="false" operator="or">
+                       <equals value="ROLE_ADMIN" />
+                     </iterate>
+                   </with>
+                 </enabledWhen>
+        </activity>
+        <activityPatternBinding
+              activityId="org.argeo.security.ui.adminActivity"
+              isEqualityPattern="true"
+              pattern="org.argeo.security.ui/org.argeo.security.ui.adminMaintenancePerspective">
+        </activityPatternBinding>
+        <activityPatternBinding
+              activityId="org.argeo.security.ui.adminActivity"
+              isEqualityPattern="true"
+              pattern="org.argeo.security.ui/org.argeo.security.ui.adminLogView">
+        </activityPatternBinding>
+     </extension>
  </plugin>
diff --git a/security/plugins/org.argeo.security.ui/src/main/java/org/argeo/security/ui/MaintenancePerspective.java b/security/plugins/org.argeo.security.ui/src/main/java/org/argeo/security/ui/MaintenancePerspective.java
new file mode 100644 (file)
index 0000000..0cfdda7
--- /dev/null
@@ -0,0 +1,30 @@
+package org.argeo.security.ui;
+
+import org.argeo.security.ui.views.AdminLogView;
+import org.argeo.security.ui.views.UserProfile;
+import org.eclipse.ui.IFolderLayout;
+import org.eclipse.ui.IPageLayout;
+import org.eclipse.ui.IPerspectiveFactory;
+
+/** Home perspective for the current user */
+public class MaintenancePerspective implements IPerspectiveFactory {
+       public final static String ID = SecurityUiPlugin.PLUGIN_ID
+                       + ".adminMaintenancePerspective";
+
+       public void createInitialLayout(IPageLayout layout) {
+               String editorArea = layout.getEditorArea();
+               layout.setEditorAreaVisible(true);
+               layout.setFixed(false);
+
+               IFolderLayout bottom = layout.createFolder("bottom",
+                               IPageLayout.BOTTOM, 0.50f, editorArea);
+               bottom.addView(AdminLogView.ID);
+
+               IFolderLayout left = layout.createFolder("left", IPageLayout.LEFT,
+                               0.30f, editorArea);
+               left.addView(UserProfile.ID);
+               // left.addView(RolesView.ID);
+
+       }
+
+}
index 1ce7364e88395dab092292665ff608687251f4fd..1cf4dd3864a86dd23e73f1b3be460ebde5c288b7 100644 (file)
@@ -8,7 +8,8 @@ import org.eclipse.ui.IPerspectiveFactory;
 
 /** Home perspective for the current user */
 public class UserHomePerspective implements IPerspectiveFactory {
-       public final static String ID = "org.argeo.security.ui.userHomePerspective";
+       public final static String ID = SecurityUiPlugin.PLUGIN_ID
+                       + ".userHomePerspective";
 
        public void createInitialLayout(IPageLayout layout) {
                String editorArea = layout.getEditorArea();
@@ -18,11 +19,7 @@ public class UserHomePerspective implements IPerspectiveFactory {
                IFolderLayout left = layout.createFolder("left", IPageLayout.LEFT,
                                0.30f, editorArea);
                left.addView(UserProfile.ID);
-               // left.addView(RolesView.ID);
-
-               IFolderLayout bottom = layout.createFolder("bottom",
-                               IPageLayout.BOTTOM, 0.30f, editorArea);
-               bottom.addView(LogView.ID);
+               left.addView(LogView.ID);
        }
 
 }
diff --git a/security/plugins/org.argeo.security.ui/src/main/java/org/argeo/security/ui/views/AdminLogView.java b/security/plugins/org.argeo.security.ui/src/main/java/org/argeo/security/ui/views/AdminLogView.java
new file mode 100644 (file)
index 0000000..63908cf
--- /dev/null
@@ -0,0 +1,79 @@
+package org.argeo.security.ui.views;
+
+import java.util.ArrayList;
+
+import org.argeo.security.log4j.SecureLogger;
+import org.argeo.security.ui.SecurityUiPlugin;
+import org.eclipse.jface.resource.JFaceResources;
+import org.eclipse.jface.viewers.LabelProvider;
+import org.eclipse.jface.viewers.TableViewer;
+import org.eclipse.swt.SWT;
+import org.eclipse.swt.graphics.Font;
+import org.eclipse.swt.widgets.Composite;
+import org.eclipse.ui.part.ViewPart;
+
+/**
+ * Display log lines for all users with a virtual table.
+ */
+public class AdminLogView extends ViewPart {
+       public static String ID = SecurityUiPlugin.PLUGIN_ID + ".adminLogView";
+
+       private TableViewer viewer;
+
+       private LogContentProvider logContentProvider;
+       private SecureLogger argeoLogger;
+
+       private Font font;
+
+       @Override
+       public void createPartControl(Composite parent) {
+               // FIXME doesn't return a monospace font in RAP
+               font = JFaceResources.getTextFont();
+//             if (font == JFaceResources.getDefaultFont()) {
+//                     Set<?> keySet = JFaceResources.getFontRegistry().getKeySet();
+//                     for (Object key : keySet) {
+//                             System.out.println(key);
+//                     }
+//             }
+
+               viewer = new TableViewer(parent, SWT.VIRTUAL | SWT.MULTI | SWT.H_SCROLL
+                               | SWT.V_SCROLL | SWT.FULL_SELECTION | SWT.BORDER);
+               viewer.getTable().setFont(font);
+               viewer.setLabelProvider(new LabelProvider());
+               logContentProvider = new LogContentProvider(viewer) {
+
+                       @Override
+                       protected StringBuffer prefix(String username, Long timestamp,
+                                       String level, String category, String thread) {
+                               return super
+                                               .prefix(username, timestamp, level, category, thread)
+                                               .append(norm(level, 5))
+                                               .append(' ')
+                                               .append(norm(username != null ? username
+                                                               : "<anonymous>", 16)).append(' ');
+                       }
+               };
+               viewer.setContentProvider(logContentProvider);
+               // viewer.setUseHashlookup(true);
+               viewer.setInput(new ArrayList<String>());
+
+               if (argeoLogger != null)
+                       argeoLogger.registerForAll(logContentProvider, 1000, true);
+       }
+
+       @Override
+       public void setFocus() {
+               viewer.getTable().setFocus();
+       }
+
+       @Override
+       public void dispose() {
+               if (argeoLogger != null)
+                       argeoLogger.unregisterForAll(logContentProvider);
+       }
+
+       public void setArgeoLogger(SecureLogger argeoLogger) {
+               this.argeoLogger = argeoLogger;
+       }
+
+}
index 08cca0d0a8195e67bf83ca715ac853cf2147b8c0..fb1ee13bb95574a4f1faa40519270d335e762dc1 100644 (file)
@@ -2,6 +2,7 @@ package org.argeo.security.ui.views;
 
 import java.text.DateFormat;
 import java.text.SimpleDateFormat;
+import java.util.Arrays;
 import java.util.Date;
 import java.util.LinkedList;
 import java.util.List;
@@ -25,7 +26,7 @@ class LogContentProvider implements ILazyContentProvider, ArgeoLogListener {
        private final Integer maxLineBufferSize = 10 * 1000;
 
        private final TableViewer viewer;
-       private final LinkedList<LogLine> lines;
+       private LinkedList<LogLine> lines;
 
        public LogContentProvider(TableViewer viewer) {
                this.viewer = viewer;
@@ -36,6 +37,7 @@ class LogContentProvider implements ILazyContentProvider, ArgeoLogListener {
 
        public synchronized void dispose() {
                lines.clear();
+               lines = null;
        }
 
        @SuppressWarnings("unchecked")
@@ -62,12 +64,12 @@ class LogContentProvider implements ILazyContentProvider, ArgeoLogListener {
                        return;
 
                String message = msg.toString();
-               StringBuffer buf = new StringBuffer("");
-               buf.append(dateFormat.format(new Date(timestamp))).append(" ");
-               buf.append(level).append(" ");
                int count = 0;
+               String prefix = prefix(username, timestamp, level, category, thread)
+                               .toString();
+               // String suffix = suffix(username, timestamp, level, category, thread);
                for (String line : message.split("\n")) {
-                       addLine(count == 0 ? buf + line : line);
+                       addLine(count == 0 ? prefix + line : line);
                        count++;
                }
 
@@ -76,24 +78,57 @@ class LogContentProvider implements ILazyContentProvider, ArgeoLogListener {
                                addLine(ste);
                        }
                }
-               viewer.getTable().getDisplay().syncExec(new Runnable() {
+
+               viewer.getTable().getDisplay().asyncExec(new Runnable() {
                        public void run() {
+                               if (lines == null)
+                                       return;
                                viewer.setItemCount(lines.size());
-                               // viewer.reveal(lines.peekLast());
-                               Table table = viewer.getTable();
-                               // table.setTopIndex(lines.size()-1);
-                               System.out.println("topIndex=" + table.getTopIndex()
-                                               + ", tableSize=" + lines.size());
-                               // table.select(lines.size() - 1);
-                               // table.showSelection();
-                               TableItem ti = table.getItem(lines.size() - 1);
-                               if (ti == null)
-                                       System.out.println("tableItem is null");
-                               table.showItem(ti);
+                               // doesn't work with syncExec
+                               scrollToLastLine();
                        }
                });
        }
 
+       protected StringBuffer prefix(String username, Long timestamp,
+                       String level, String category, String thread) {
+               StringBuffer buf = new StringBuffer("");
+               buf.append(dateFormat.format(new Date(timestamp))).append(" ");
+               // buf.append(level).append(" ");
+               return buf;
+       }
+
+       /** Normalize string to the given size */
+       protected String norm(String str, Integer size) {
+               int length = str.length();
+               if (length == size)
+                       return str;
+               else if (length > size)
+                       return str.substring(0, size);
+               else {
+                       char[] arr = new char[size - length];
+                       Arrays.fill(arr, ' ');
+                       return str + new String(arr);
+               }
+       }
+
+       // protected String suffix(String username, Long timestamp, String level,
+       // String category, String thread) {
+       // return "";
+       // }
+
+       /** Scroll to the last line */
+       protected void scrollToLastLine() {
+               // we try to show last line with two methods
+               // viewer.reveal(lines.peekLast());
+
+               Table table = viewer.getTable();
+               TableItem ti = table.getItem(lines.size() - 1);
+               if (ti == null)
+                       System.out.println("tableItem is null");
+               table.showItem(ti);
+       }
+
        protected synchronized LogLine addLine(String line) {
                // check for maximal size and purge if necessary
                while (lines.size() >= maxLineBufferSize) {
index f432ad34863a4a10d26559db21cfbc68d411f6a8..a612bc9f71552d5a813690ba4e0fe129d294350c 100644 (file)
@@ -3,14 +3,13 @@ package org.argeo.security.ui.views;
 import java.util.ArrayList;
 
 import org.argeo.ArgeoLogListener;
+import org.argeo.ArgeoLogger;
 import org.argeo.security.ui.SecurityUiPlugin;
 import org.eclipse.jface.viewers.LabelProvider;
 import org.eclipse.jface.viewers.TableViewer;
 import org.eclipse.swt.SWT;
 import org.eclipse.swt.widgets.Composite;
 import org.eclipse.ui.part.ViewPart;
-import org.osgi.framework.BundleContext;
-import org.osgi.framework.ServiceRegistration;
 
 /**
  * Display log lines with a virtual table. Register and unregisters a
@@ -22,8 +21,7 @@ public class LogView extends ViewPart {
        private TableViewer viewer;
 
        private LogContentProvider logContentProvider;
-
-       private ServiceRegistration serviceRegistration;
+       private ArgeoLogger argeoLogger;
 
        @Override
        public void createPartControl(Composite parent) {
@@ -31,11 +29,12 @@ public class LogView extends ViewPart {
                                | SWT.V_SCROLL | SWT.FULL_SELECTION | SWT.BORDER);
                viewer.setLabelProvider(new LabelProvider());
                logContentProvider = new LogContentProvider(viewer);
-               serviceRegistration = getBundleContext().registerService(
-                               ArgeoLogListener.class.getName(), logContentProvider, null);
                viewer.setContentProvider(logContentProvider);
-               //viewer.setUseHashlookup(true);
+               // viewer.setUseHashlookup(true);
                viewer.setInput(new ArrayList<String>());
+
+               if (argeoLogger != null)
+                       argeoLogger.register(logContentProvider, 1000);
        }
 
        @Override
@@ -45,11 +44,12 @@ public class LogView extends ViewPart {
 
        @Override
        public void dispose() {
-               if (serviceRegistration != null)
-                       serviceRegistration.unregister();
+               if (argeoLogger != null)
+                       argeoLogger.unregister(logContentProvider);
        }
 
-       private BundleContext getBundleContext() {
-               return SecurityUiPlugin.getDefault().getBundle().getBundleContext();
+       public void setArgeoLogger(ArgeoLogger argeoLogger) {
+               this.argeoLogger = argeoLogger;
        }
+
 }
diff --git a/security/runtime/org.argeo.security.core/src/main/java/org/argeo/security/SecurityUtils.java b/security/runtime/org.argeo.security.core/src/main/java/org/argeo/security/SecurityUtils.java
new file mode 100644 (file)
index 0000000..d50c9b7
--- /dev/null
@@ -0,0 +1,46 @@
+package org.argeo.security;
+
+import org.springframework.security.Authentication;
+import org.springframework.security.GrantedAuthority;
+import org.springframework.security.context.SecurityContext;
+import org.springframework.security.context.SecurityContextHolder;
+import org.springframework.security.providers.anonymous.AnonymousAuthenticationToken;
+
+/** Static utilities */
+public class SecurityUtils {
+
+       private SecurityUtils() {
+       }
+
+       /** Whether the current thread has the admin role */
+       public static boolean hasCurrentThreadAuthority(String authority) {
+               SecurityContext securityContext = SecurityContextHolder.getContext();
+               if (securityContext != null) {
+                       Authentication authentication = securityContext.getAuthentication();
+                       if (authentication != null) {
+                               for (GrantedAuthority ga : authentication.getAuthorities())
+                                       if (ga.getAuthority().equals(authority))
+                                               return true;
+                       }
+               }
+               return false;
+       }
+
+       /**
+        * @return the authenticated username or null if not authenticated /
+        *         anonymous
+        */
+       public static String getCurrentThreadUsername() {
+               SecurityContext securityContext = SecurityContextHolder.getContext();
+               if (securityContext != null) {
+                       Authentication authentication = securityContext.getAuthentication();
+                       if (authentication != null) {
+                               if (authentication instanceof AnonymousAuthenticationToken) {
+                                       return null;
+                               }
+                               return authentication.getName();
+                       }
+               }
+               return null;
+       }
+}
index 70945f07c31f29181be3413dc820c46f67d81958..a03d2b6d208401857cb4c8258444e9d61bb4f2e0 100644 (file)
 
 package org.argeo.security.log4j;
 
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
 import java.util.Iterator;
+import java.util.LinkedList;
 import java.util.List;
+import java.util.ListIterator;
+import java.util.Map;
 import java.util.Properties;
+import java.util.concurrent.BlockingQueue;
+import java.util.concurrent.LinkedBlockingQueue;
 
 import org.apache.log4j.AppenderSkeleton;
 import org.apache.log4j.Level;
+import org.apache.log4j.LogManager;
 import org.apache.log4j.Logger;
 import org.apache.log4j.PropertyConfigurator;
 import org.apache.log4j.spi.LoggingEvent;
 import org.argeo.ArgeoException;
 import org.argeo.ArgeoLogListener;
-import org.springframework.security.Authentication;
-import org.springframework.security.context.SecurityContext;
-import org.springframework.security.context.SecurityContextHolder;
-import org.springframework.security.providers.anonymous.AnonymousAuthenticationToken;
+import org.argeo.ArgeoLogger;
+import org.argeo.security.SecurityUtils;
 
 /** Not meant to be used directly in standard log4j config */
-public class SecureLogger {
-       private List<ArgeoLogListener> listeners;
+public class SecureLogger implements ArgeoLogger {
 
        private Boolean disabled = false;
 
@@ -47,6 +53,18 @@ public class SecureLogger {
 
        private AppenderImpl appender;
 
+       private final List<ArgeoLogListener> everythingListeners = Collections
+                       .synchronizedList(new ArrayList<ArgeoLogListener>());
+       private final List<ArgeoLogListener> allUsersListeners = Collections
+                       .synchronizedList(new ArrayList<ArgeoLogListener>());
+       private final Map<String, List<ArgeoLogListener>> userListeners = Collections
+                       .synchronizedMap(new HashMap<String, List<ArgeoLogListener>>());
+
+       private BlockingQueue<LogEvent> events;
+       private LogDispatcherThread logDispatcherThread = new LogDispatcherThread();
+
+       private Integer maxLastEventsCount = 10 * 1000;
+
        /** Marker to prevent stack overflow */
        private ThreadLocal<Boolean> dispatching = new ThreadLocal<Boolean>() {
 
@@ -58,16 +76,18 @@ public class SecureLogger {
 
        public void init() {
                try {
+                       events = new LinkedBlockingQueue<LogEvent>();
+
                        // if (layout != null)
                        // setLayout(layout);
                        // else
                        // setLayout(new PatternLayout(pattern));
                        appender = new AppenderImpl();
-
-                       if (configuration != null)
-                               PropertyConfigurator.configure(configuration);
-
+                       reloadConfiguration();
                        Logger.getRootLogger().addAppender(appender);
+
+                       logDispatcherThread = new LogDispatcherThread();
+                       logDispatcherThread.start();
                } catch (Exception e) {
                        throw new ArgeoException("Cannot initialize log4j");
                }
@@ -75,12 +95,71 @@ public class SecureLogger {
 
        public void destroy() throws Exception {
                Logger.getRootLogger().removeAppender(appender);
+               allUsersListeners.clear();
+               for (List<ArgeoLogListener> lst : userListeners.values())
+                       lst.clear();
+               userListeners.clear();
+
+               events.clear();
+               events = null;
+               logDispatcherThread.interrupt();
        }
 
        // public void setLayout(Layout layout) {
        // this.layout = layout;
        // }
 
+       public synchronized void register(ArgeoLogListener listener,
+                       Integer numberOfPreviousEvents) {
+               String username = SecurityUtils.getCurrentThreadUsername();
+               if (username == null)
+                       throw new ArgeoException(
+                                       "Only authenticated users can register a log listener");
+
+               if (!userListeners.containsKey(username)) {
+                       List<ArgeoLogListener> lst = Collections
+                                       .synchronizedList(new ArrayList<ArgeoLogListener>());
+                       userListeners.put(username, lst);
+               }
+               userListeners.get(username).add(listener);
+               List<LogEvent> lastEvents = logDispatcherThread.getLastEvents(username,
+                               numberOfPreviousEvents);
+               for (LogEvent evt : lastEvents)
+                       dispatchEvent(listener, evt);
+       }
+
+       public synchronized void registerForAll(ArgeoLogListener listener,
+                       Integer numberOfPreviousEvents, boolean everything) {
+               if (everything)
+                       everythingListeners.add(listener);
+               else
+                       allUsersListeners.add(listener);
+               List<LogEvent> lastEvents = logDispatcherThread.getLastEvents(null,
+                               numberOfPreviousEvents);
+               for (LogEvent evt : lastEvents)
+                       if (everything || evt.getUsername() != null)
+                               dispatchEvent(listener, evt);
+       }
+
+       public synchronized void unregister(ArgeoLogListener listener) {
+               String username = SecurityUtils.getCurrentThreadUsername();
+               if (!userListeners.containsKey(username))
+                       throw new ArgeoException("No user listeners " + listener
+                                       + " registered for user " + username);
+               if (!userListeners.get(username).contains(listener))
+                       throw new ArgeoException("No user listeners " + listener
+                                       + " registered for user " + username);
+               userListeners.get(username).remove(listener);
+               if (userListeners.get(username).isEmpty())
+                       userListeners.remove(username);
+
+       }
+
+       public synchronized void unregisterForAll(ArgeoLogListener listener) {
+               everythingListeners.remove(listener);
+               allUsersListeners.remove(listener);
+       }
+
        /** For development purpose, since using regular logging is not easy here */
        static void stdOut(Object obj) {
                System.out.println(obj);
@@ -98,14 +177,87 @@ public class SecureLogger {
                this.level = level;
        }
 
-       public void setListeners(List<ArgeoLogListener> listeners) {
-               this.listeners = listeners;
-       }
-
        public void setConfiguration(Properties configuration) {
                this.configuration = configuration;
        }
 
+       public void updateConfiguration(Properties configuration) {
+               setConfiguration(configuration);
+               reloadConfiguration();
+       }
+
+       public Properties getConfiguration() {
+               return configuration;
+       }
+
+       /** Reloads configuration (if the configuration {@link Properties} is set) */
+       protected void reloadConfiguration() {
+               if (configuration != null) {
+                       LogManager.resetConfiguration();
+                       PropertyConfigurator.configure(configuration);
+               }
+       }
+
+       protected synchronized void processLoggingEvent(LogEvent event) {
+               if (disabled)
+                       return;
+
+               if (dispatching.get())
+                       return;
+
+               if (level != null && !level.trim().equals("")) {
+                       if (log4jLevel == null || !log4jLevel.toString().equals(level))
+                               try {
+                                       log4jLevel = Level.toLevel(level);
+                               } catch (Exception e) {
+                                       System.err
+                                                       .println("Log4j level could not be set for level '"
+                                                                       + level + "', resetting it to null.");
+                                       e.printStackTrace();
+                                       level = null;
+                               }
+
+                       if (log4jLevel != null
+                                       && !event.getLoggingEvent().getLevel()
+                                                       .isGreaterOrEqual(log4jLevel)) {
+                               return;
+                       }
+               }
+
+               try {
+                       // admin listeners
+                       Iterator<ArgeoLogListener> everythingIt = everythingListeners
+                                       .iterator();
+                       while (everythingIt.hasNext())
+                               dispatchEvent(everythingIt.next(), event);
+
+                       if (event.getUsername() != null) {
+                               Iterator<ArgeoLogListener> allUsersIt = allUsersListeners
+                                               .iterator();
+                               while (allUsersIt.hasNext())
+                                       dispatchEvent(allUsersIt.next(), event);
+
+                               if (userListeners.containsKey(event.getUsername())) {
+                                       Iterator<ArgeoLogListener> userIt = userListeners.get(
+                                                       event.getUsername()).iterator();
+                                       while (userIt.hasNext())
+                                               dispatchEvent(userIt.next(), event);
+                               }
+                       }
+               } catch (Exception e) {
+                       stdOut("Cannot process logging event");
+                       e.printStackTrace();
+               }
+       }
+
+       protected void dispatchEvent(ArgeoLogListener logListener, LogEvent evt) {
+               LoggingEvent event = evt.getLoggingEvent();
+               logListener.appendLog(evt.getUsername(), event.getTimeStamp(), event
+                               .getLevel().toString(), event.getLoggerName(), event
+                               .getThreadName(), event.getMessage(), event
+                               .getThrowableStrRep());
+       }
+
        private class AppenderImpl extends AppenderSkeleton {
                public boolean requiresLayout() {
                        return false;
@@ -116,61 +268,93 @@ public class SecureLogger {
 
                @Override
                protected void append(LoggingEvent event) {
-                       if (disabled)
-                               return;
+                       if (events != null) {
+                               try {
+                                       String username = SecurityUtils.getCurrentThreadUsername();
+                                       events.put(new LogEvent(username, event));
+                               } catch (InterruptedException e) {
+                                       // silent
+                               }
+                       }
+               }
 
-                       if (dispatching.get())
-                               return;
+       }
 
-                       if (level != null && !level.trim().equals("")) {
-                               if (log4jLevel == null || !log4jLevel.toString().equals(level))
-                                       try {
-                                               log4jLevel = Level.toLevel(level);
-                                       } catch (Exception e) {
-                                               System.err
-                                                               .println("Log4j level could not be set for level '"
-                                                                               + level + "', resetting it to null.");
-                                               e.printStackTrace();
-                                               level = null;
-                                       }
-
-                               if (log4jLevel != null
-                                               && !event.getLevel().isGreaterOrEqual(log4jLevel)) {
-                                       return;
+       private class LogDispatcherThread extends Thread {
+               /** encapsulated in order to simplify concurrency management */
+               private LinkedList<LogEvent> lastEvents = new LinkedList<LogEvent>();
+
+               public LogDispatcherThread() {
+                       super("Argeo Logging Dispatcher Thread");
+               }
+
+               public void run() {
+                       while (events != null) {
+                               try {
+                                       LogEvent loggingEvent = events.take();
+                                       processLoggingEvent(loggingEvent);
+                                       addLastEvent(loggingEvent);
+                               } catch (InterruptedException e) {
+                                       if (events == null)
+                                               return;
                                }
                        }
+               }
 
-                       try {
-                               Thread currentThread = Thread.currentThread();
-                               String username = null;
-
-                               // find username
-                               SecurityContext securityContext = SecurityContextHolder
-                                               .getContext();
-                               if (securityContext != null) {
-                                       Authentication authentication = securityContext
-                                                       .getAuthentication();
-                                       if (authentication != null) {
-                                               if (authentication instanceof AnonymousAuthenticationToken) {
-                                                       username = null;
-                                               } else {
-                                                       username = authentication.getName();
-                                               }
-                                       }
-                               }
+               protected synchronized void addLastEvent(LogEvent loggingEvent) {
+                       if (lastEvents.size() >= maxLastEventsCount)
+                               lastEvents.poll();
+                       lastEvents.add(loggingEvent);
+               }
 
-                               // Spring OSGi safe
-                               Iterator<ArgeoLogListener> it = listeners.iterator();
-                               while (it.hasNext()) {
-                                       ArgeoLogListener logListener = it.next();
-                                       logListener.appendLog(username, event.getTimeStamp(), event
-                                                       .getLevel().toString(), event.getLoggerName(),
-                                                       currentThread.getName(), event.getMessage(), event.getThrowableStrRep());
+               public synchronized List<LogEvent> getLastEvents(String username,
+                               Integer maxCount) {
+                       LinkedList<LogEvent> evts = new LinkedList<LogEvent>();
+                       ListIterator<LogEvent> it = lastEvents.listIterator(lastEvents
+                                       .size());
+                       int count = 0;
+                       while (it.hasPrevious() && (count < maxCount)) {
+                               LogEvent evt = it.previous();
+                               if (username == null || username.equals(evt.getUsername())) {
+                                       evts.push(evt);
+                                       count++;
                                }
-                       } catch (Exception e) {
-                               stdOut("Cannot process logging event");
-                               e.printStackTrace();
                        }
+                       return evts;
+               }
+       }
+
+       private class LogEvent {
+               private final String username;
+               private final LoggingEvent loggingEvent;
+
+               public LogEvent(String username, LoggingEvent loggingEvent) {
+                       super();
+                       this.username = username;
+                       this.loggingEvent = loggingEvent;
+               }
+
+               @Override
+               public int hashCode() {
+                       return loggingEvent.hashCode();
+               }
+
+               @Override
+               public boolean equals(Object obj) {
+                       return loggingEvent.equals(obj);
+               }
+
+               @Override
+               public String toString() {
+                       return username + "@ " + loggingEvent.toString();
+               }
+
+               public String getUsername() {
+                       return username;
+               }
+
+               public LoggingEvent getLoggingEvent() {
+                       return loggingEvent;
                }
 
        }
index 74c69707e5f8626e763f4f62472a7a79a029dd34..09dffb8a9379647aed275545f8cb384f46b696c4 100644 (file)
        
   <extension
            point="org.eclipse.ui.activities">
-        <activity
-              description="Only for admins"
-              id="org.argeo.jcr.ui.explorer.adminActivity"
-              name="Admin">
-                 <enabledWhen>
-                   <with variable="roles">
-                     <iterate ifEmpty="false" operator="or">
-                       <equals value="ROLE_ADMIN" />
-                     </iterate>
-                   </with>
-                 </enabledWhen>
-        </activity>
-        <!-- TODO: find a way to exclude evrything -->
         <activityPatternBinding
-              activityId="org.argeo.jcr.ui.explorer.adminActivity"
+              activityId="org.argeo.security.ui.adminActivity"
               isEqualityPattern="true"
               pattern="org.argeo.jcr.ui.explorer/org.argeo.jcr.ui.explorer.perspective">
         </activityPatternBinding>
index e691b8fb65dd4a3740b90164da8b7a2265b0174c..209dcdc6a1ebd68c7709b6573af6292db47a8f22 100644 (file)
@@ -191,11 +191,10 @@ public class AddRemoteRepository extends AbstractHandler implements
                                                "Repository Added",
                                                "Remote repository '" + username.getText() + "@"
                                                                + uri.getText() + "' added");
+                               super.okPressed();
                        } catch (Exception e) {
-                               // TODO Auto-generated catch block
-                               e.printStackTrace();
+                               ErrorFeedback.show("Cannot add remote repository", e);
                        }
-                       super.okPressed();
                }
 
                /** Creates label and text. */