BROOKLYN-282: don’t log passwords (#1)

Also refactors EffectorUtils.prepareArgsForEffector (deprecating old
method), and updates how MethodEffector calls it so we have the
parameter names.


Project: http://git-wip-us.apache.org/repos/asf/brooklyn-server/repo
Commit: http://git-wip-us.apache.org/repos/asf/brooklyn-server/commit/dfd6ef47
Tree: http://git-wip-us.apache.org/repos/asf/brooklyn-server/tree/dfd6ef47
Diff: http://git-wip-us.apache.org/repos/asf/brooklyn-server/diff/dfd6ef47

Branch: refs/heads/master
Commit: dfd6ef47ae8aafb71bddfeb504666790642ba81b
Parents: 3c0c8af
Author: Aled Sage <aled.s...@gmail.com>
Authored: Wed Jun 15 12:02:46 2016 +0100
Committer: Valentin Aitken <bos...@gmail.com>
Committed: Wed Jun 15 15:57:26 2016 +0300

----------------------------------------------------------------------
 .../brooklyn/core/effector/MethodEffector.java  |  4 +-
 .../internal/AbstractManagementContext.java     |  4 +-
 .../core/mgmt/internal/EffectorUtils.java       | 56 ++++++++++------
 .../internal/ManagementContextInternal.java     |  2 +-
 .../NonDeploymentManagementContext.java         |  2 +-
 .../mgmt/internal/TestEntityWithEffectors.java  | 68 +++++++++++++++++++-
 .../internal/TestEntityWithEffectorsImpl.java   | 24 +++++--
 7 files changed, 128 insertions(+), 32 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/dfd6ef47/core/src/main/java/org/apache/brooklyn/core/effector/MethodEffector.java
----------------------------------------------------------------------
diff --git 
a/core/src/main/java/org/apache/brooklyn/core/effector/MethodEffector.java 
b/core/src/main/java/org/apache/brooklyn/core/effector/MethodEffector.java
index ad53adb..0d5f909 100644
--- a/core/src/main/java/org/apache/brooklyn/core/effector/MethodEffector.java
+++ b/core/src/main/java/org/apache/brooklyn/core/effector/MethodEffector.java
@@ -144,9 +144,8 @@ public class MethodEffector<T> extends AbstractEffector<T> {
 
     @SuppressWarnings({ "rawtypes", "unchecked" })
     public T call(Entity entity, Map parameters) {
-        Object[] parametersArray = EffectorUtils.prepareArgsForEffector(this, 
parameters);
         if (entity instanceof AbstractEntity) {
-            return EffectorUtils.invokeMethodEffector(entity, this, 
parametersArray);
+            return EffectorUtils.invokeMethodEffector(entity, this, 
(Map<String,?>)parameters);
         } else {
             // we are dealing with a proxy here
             // this implementation invokes the method on the proxy
@@ -158,6 +157,7 @@ public class MethodEffector<T> extends AbstractEffector<T> {
             
             // TODO Should really find method with right signature, rather 
than just the right args.
             // TODO prepareArgs can miss things out that have "default 
values"! Code below will probably fail if that happens.
+            Object[] parametersArray = 
EffectorUtils.prepareArgsForEffector(this, parameters);
             Method[] methods = entity.getClass().getMethods();
             for (Method method : methods) {
                 if (method.getName().equals(getName())) {

http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/dfd6ef47/core/src/main/java/org/apache/brooklyn/core/mgmt/internal/AbstractManagementContext.java
----------------------------------------------------------------------
diff --git 
a/core/src/main/java/org/apache/brooklyn/core/mgmt/internal/AbstractManagementContext.java
 
b/core/src/main/java/org/apache/brooklyn/core/mgmt/internal/AbstractManagementContext.java
index 75af42c..da2f30c 100644
--- 
a/core/src/main/java/org/apache/brooklyn/core/mgmt/internal/AbstractManagementContext.java
+++ 
b/core/src/main/java/org/apache/brooklyn/core/mgmt/internal/AbstractManagementContext.java
@@ -302,7 +302,7 @@ public abstract class AbstractManagementContext implements 
ManagementContextInte
         return runAtEntity(entity, eff, parameters);
     }
     
-    protected <T> T invokeEffectorMethodLocal(Entity entity, Effector<T> eff, 
Object args) {
+    protected <T> T invokeEffectorMethodLocal(Entity entity, Effector<T> eff, 
Map<String, ?> args) {
         assert isManagedLocally(entity) : "cannot invoke effector method at 
"+this+" because it is not managed here";
         totalEffectorInvocationCount.incrementAndGet();
         Object[] transformedArgs = EffectorUtils.prepareArgsForEffector(eff, 
args);
@@ -315,7 +315,7 @@ public abstract class AbstractManagementContext implements 
ManagementContextInte
      * @throws ExecutionException 
      */
     @Override
-    public <T> T invokeEffectorMethodSync(final Entity entity, final 
Effector<T> eff, final Object args) throws ExecutionException {
+    public <T> T invokeEffectorMethodSync(final Entity entity, final 
Effector<T> eff, final Map<String, ?> args) throws ExecutionException {
         try {
             Task<?> current = Tasks.current();
             if (current == null || 
!entity.equals(BrooklynTaskTags.getContextEntity(current)) || 
!isManagedLocally(entity)) {

http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/dfd6ef47/core/src/main/java/org/apache/brooklyn/core/mgmt/internal/EffectorUtils.java
----------------------------------------------------------------------
diff --git 
a/core/src/main/java/org/apache/brooklyn/core/mgmt/internal/EffectorUtils.java 
b/core/src/main/java/org/apache/brooklyn/core/mgmt/internal/EffectorUtils.java
index f340041..d2a8424 100644
--- 
a/core/src/main/java/org/apache/brooklyn/core/mgmt/internal/EffectorUtils.java
+++ 
b/core/src/main/java/org/apache/brooklyn/core/mgmt/internal/EffectorUtils.java
@@ -60,21 +60,40 @@ public class EffectorUtils {
 
     private static final Logger log = 
LoggerFactory.getLogger(EffectorUtils.class);
 
+    /**
+     * Prepares arguments for an effector, taking an array, which should 
contain the arguments in 
+     * order, optionally omitting those which have defaults defined.
+     */
+    public static Object[] prepareArgsForEffector(Effector<?> eff, Object[] 
args) {
+        return prepareArgsForEffectorFromArray(eff, args);
+    }
+
+    /**
+     * Prepares arguments for an effector, taking a map, which should contain 
the arguments by 
+     * name, optionally omitting those which have defaults defined; performs 
type coercion on
+     * the values.
+     */
+    public static Object[] prepareArgsForEffector(Effector<?> eff, Map<?,?> 
args) {
+        return prepareArgsForEffectorFromMap(eff, (Map<?,?>) args);
+    }
+
     /** prepares arguments for an effector either accepting:
-     *  an array, which should contain the arguments in order, optionally 
omitting those which have defaults defined;
-     *  or a map, which should contain the arguments by name, again optionally 
omitting those which have defaults defined,
-     *  and in this case also performing type coercion.
+     *  an array (see {@link #prepareArgsForEffector(Effector, Object[])};
+     *  or a map (see {@link #prepareArgsForEffector(Effector, Map)}.
+     *  
+     * @deprecated since 0.10.0
      */
+    @Deprecated
     public static Object[] prepareArgsForEffector(Effector<?> eff, Object 
args) {
         if (args != null && args.getClass().isArray()) {
             return prepareArgsForEffectorFromArray(eff, (Object[]) args);
         }
         if (args instanceof Map) {
-            return prepareArgsForEffectorFromMap(eff, (Map) args);
+            return prepareArgsForEffectorFromMap(eff, (Map<?,?>) args);
         }
-        log.warn("Deprecated effector invocation style for call to "+eff+", 
expecting a map or an array, got: "+sanitizeArgs(args));
+        log.warn("Deprecated effector invocation style for call to "+eff+", 
expecting a map or an array, got: "+(args == null ? null : 
args.getClass().getName()));
         if (log.isDebugEnabled()) {
-            log.debug("Deprecated effector invocation style for call to 
"+eff+", expecting a map or an array, got: "+sanitizeArgs(args),
+            log.debug("Deprecated effector invocation style for call to 
"+eff+", expecting a map or an array, got: "+(args == null ? null : 
args.getClass().getName()),
                 new Throwable("Trace for deprecated effector invocation 
style"));
         }
         return oldPrepareArgsForEffector(eff, args);
@@ -115,16 +134,16 @@ public class EffectorUtils {
                 //finally, default values are used to make up for missing 
parameters
                 newArgs.put(it.getName(), 
((BasicParameterType)it).getDefaultValue());
             } else {
-                throw new IllegalArgumentException("Invalid arguments (count 
mismatch) for effector "+eff+": "+args);
+                throw new IllegalArgumentException("Invalid arguments (count 
mismatch) for effector "+eff+": "+args.length+" args");
             }
 
             newArgsNeeded--;
         }
         if (newArgsNeeded > 0) {
-            throw new IllegalArgumentException("Invalid arguments (missing 
"+newArgsNeeded+") for effector "+eff+": "+args);
+            throw new IllegalArgumentException("Invalid arguments (missing 
"+newArgsNeeded+") for effector "+eff+": "+args.length+" args");
         }
         if (!l.isEmpty()) {
-            throw new IllegalArgumentException("Invalid arguments 
("+l.size()+" extra) for effector "+eff+": "+args);
+            throw new IllegalArgumentException("Invalid arguments 
("+l.size()+" extra) for effector "+eff+": "+args.length+" args");
         }
         return newArgs;
     }
@@ -219,19 +238,19 @@ public class EffectorUtils {
                 //finally, default values are used to make up for missing 
parameters
                 newArgs.add(((BasicParameterType)it).getDefaultValue());
             } else {
-                throw new IllegalArgumentException("Invalid arguments (count 
mismatch) for effector "+eff+": "+sanitizeArgs(args));
+                throw new IllegalArgumentException("Invalid arguments (count 
mismatch) for effector "+eff+": "+argsArray.length+" args");
             }
 
             newArgsNeeded--;
         }
         if (newArgsNeeded > 0) {
-            throw new IllegalArgumentException("Invalid arguments (missing 
"+newArgsNeeded+") for effector "+eff+": "+sanitizeArgs(args));
+            throw new IllegalArgumentException("Invalid arguments (missing 
"+newArgsNeeded+") for effector "+eff+": "+argsArray.length+" args");
         }
         if (!l.isEmpty()) {
-            throw new IllegalArgumentException("Invalid arguments 
("+l.size()+" extra) for effector "+eff+": "+sanitizeArgs(args));
+            throw new IllegalArgumentException("Invalid arguments 
("+l.size()+" extra) for effector "+eff+": "+argsArray.length+" args");
         }
         if (truth(m) && !mapUsed) {
-            throw new IllegalArgumentException("Invalid arguments 
("+m.size()+" extra named) for effector "+eff+": "+sanitizeArgs(args));
+            throw new IllegalArgumentException("Invalid arguments 
("+m.size()+" extra named) for effector "+eff+": "+argsArray.length+" args");
         }
         return newArgs.toArray(new Object[newArgs.size()]);
     }
@@ -239,19 +258,20 @@ public class EffectorUtils {
     /**
      * Invokes a method effector so that its progress is tracked. For internal 
use only, when we know the effector is backed by a method which is local.
      */
-    public static <T> T invokeMethodEffector(Entity entity, Effector<T> eff, 
Object[] args) {
+    public static <T> T invokeMethodEffector(Entity entity, Effector<T> eff, 
Map<String,?> args) {
+        Object[] parametersArray = EffectorUtils.prepareArgsForEffector(eff, 
args);
         String name = eff.getName();
 
         try {
             if (log.isDebugEnabled()) log.debug("Invoking effector {} on {}", 
new Object[] {name, entity});
-            if (log.isTraceEnabled()) log.trace("Invoking effector {} on {} 
with args {}", new Object[] {name, entity, args});
+            if (log.isTraceEnabled()) log.trace("Invoking effector {} on {} 
with args {}", new Object[] {name, entity, Sanitizer.sanitize(args)});
             EntityManagementSupport mgmtSupport = 
((EntityInternal)entity).getManagementSupport();
             if (!mgmtSupport.isDeployed()) {
                 mgmtSupport.attemptLegacyAutodeployment(name);
             }
             ManagementContextInternal mgmtContext = 
(ManagementContextInternal) ((EntityInternal) entity).getManagementContext();
 
-            mgmtSupport.getEntityChangeListener().onEffectorStarting(eff, 
args);
+            mgmtSupport.getEntityChangeListener().onEffectorStarting(eff, 
parametersArray);
             try {
                 return mgmtContext.invokeEffectorMethodSync(entity, eff, args);
             } finally {
@@ -392,8 +412,4 @@ public class EffectorUtils {
                 .put("tags", tags)
                 .build();
     }
-
-    private static Object sanitizeArgs(Object args) {
-        return args instanceof Map ? Sanitizer.sanitize((Map)args) : args;
-    }
 }

http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/dfd6ef47/core/src/main/java/org/apache/brooklyn/core/mgmt/internal/ManagementContextInternal.java
----------------------------------------------------------------------
diff --git 
a/core/src/main/java/org/apache/brooklyn/core/mgmt/internal/ManagementContextInternal.java
 
b/core/src/main/java/org/apache/brooklyn/core/mgmt/internal/ManagementContextInternal.java
index e76f2fb..dd3c9d5 100644
--- 
a/core/src/main/java/org/apache/brooklyn/core/mgmt/internal/ManagementContextInternal.java
+++ 
b/core/src/main/java/org/apache/brooklyn/core/mgmt/internal/ManagementContextInternal.java
@@ -69,7 +69,7 @@ public interface ManagementContextInternal extends 
ManagementContext {
     
     long getTotalEffectorInvocations();
 
-    <T> T invokeEffectorMethodSync(final Entity entity, final Effector<T> eff, 
final Object args) throws ExecutionException;
+    <T> T invokeEffectorMethodSync(final Entity entity, final Effector<T> eff, 
final Map<String,?> args) throws ExecutionException;
     
     <T> Task<T> invokeEffector(final Entity entity, final Effector<T> eff, 
@SuppressWarnings("rawtypes") final Map parameters);
 

http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/dfd6ef47/core/src/main/java/org/apache/brooklyn/core/mgmt/internal/NonDeploymentManagementContext.java
----------------------------------------------------------------------
diff --git 
a/core/src/main/java/org/apache/brooklyn/core/mgmt/internal/NonDeploymentManagementContext.java
 
b/core/src/main/java/org/apache/brooklyn/core/mgmt/internal/NonDeploymentManagementContext.java
index 68172bb..cfc1d98 100644
--- 
a/core/src/main/java/org/apache/brooklyn/core/mgmt/internal/NonDeploymentManagementContext.java
+++ 
b/core/src/main/java/org/apache/brooklyn/core/mgmt/internal/NonDeploymentManagementContext.java
@@ -349,7 +349,7 @@ public class NonDeploymentManagementContext implements 
ManagementContextInternal
     }
     
     @Override
-    public <T> T invokeEffectorMethodSync(final Entity entity, final 
Effector<T> eff, final Object args) throws ExecutionException {
+    public <T> T invokeEffectorMethodSync(final Entity entity, final 
Effector<T> eff, final Map<String,?> args) throws ExecutionException {
         throw new IllegalStateException("Non-deployment context "+this+" is 
not valid for this operation: cannot invoke effector "+eff+" on entity 
"+entity);
     }
     

http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/dfd6ef47/core/src/test/java/org/apache/brooklyn/core/mgmt/internal/TestEntityWithEffectors.java
----------------------------------------------------------------------
diff --git 
a/core/src/test/java/org/apache/brooklyn/core/mgmt/internal/TestEntityWithEffectors.java
 
b/core/src/test/java/org/apache/brooklyn/core/mgmt/internal/TestEntityWithEffectors.java
index f986674..4bb137d 100644
--- 
a/core/src/test/java/org/apache/brooklyn/core/mgmt/internal/TestEntityWithEffectors.java
+++ 
b/core/src/test/java/org/apache/brooklyn/core/mgmt/internal/TestEntityWithEffectors.java
@@ -21,15 +21,79 @@ package org.apache.brooklyn.core.mgmt.internal;
 import org.apache.brooklyn.api.entity.ImplementedBy;
 import org.apache.brooklyn.core.annotation.Effector;
 import org.apache.brooklyn.core.annotation.EffectorParam;
+import org.apache.brooklyn.core.effector.MethodEffector;
 import org.apache.brooklyn.core.test.entity.TestEntity;
 
+/**
+ * Entity for testing that secret effector parameters are:
+ * <ul>
+ *   <li>excluded from the activities view
+ *   <li>not logged
+ *   <li>masked out in the UI
+ * </ul>
+ * Of those, only the first is unit-tested.
+ * 
+ * To test manually...
+ * 
+ * Configure logback to log everything at trace:
+ * <pre>
+ * {@code
+ * <configuration>
+ *     <include resource="logback-main.xml"/>
+ *     <logger name="org.apache.brooklyn" level="TRACE"/>
+ *     <logger name="brooklyn" level="TRACE"/>
+ * </configuration>
+ * }
+ * </pre>
+ *
+ * Ensure that {@code TestEntityWithEffectors} (and its super-class 
{@TestEntity}) is on the
+ * classpath, and then run Brooklyn with the above log configuration file:
+ * <pre>
+ * {@code
+ * cp /path/to/test-entities.jar ./lib/dropins/
+ * export JAVA_OPTS="-Xms256m -Xmx1g -XX:MaxPermSize=256m 
-Dlogback.configurationFile=/path/to/logback-trace.xml"
+ * ./bin/brooklyn launch --persist auto --persistenceDir 
/path/to/persistedState
+ * }
+ * </pre>
+ *
+ * Deploy the blueprint below:
+ * <pre>
+ * {@code
+ * services:
+ * - type: org.apache.brooklyn.core.mgmt.internal.TestEntityWithEffectors
+ *   brooklyn.children:
+ *   - type: org.apache.brooklyn.core.mgmt.internal.TestEntityWithEffectors
+ * }
+ * </pre>
+ * 
+ * Invoke the effector (e.g. {@code resetPasswordOnChildren("mypassword", 
12345678)} on the 
+ * top-level {@code TestEntityWithEffectors}, and the effector 
+ * {@code resetPasswordThrowsException("mypassword", 12345678)}).
+ * 
+ * Then manually check:
+ * <ul>
+ *   <li>the parameter values were masked out while being entered
+ *   <li>activity view of each TestEntityWithEffectors, that it does not show 
those parameter values
+ *   <li>{@code grep -E "mypassword|12345678" *.log}
+ *   <li>{@code pushd /path/to/persistedState && grep -r -E 
"mypassword|12345678" *}
+ * </ul>
+ */
 @ImplementedBy(TestEntityWithEffectorsImpl.class)
 public interface TestEntityWithEffectors extends TestEntity {
+    
+    org.apache.brooklyn.api.effector.Effector<Void> RESET_PASSWORD = new 
MethodEffector<Void>(TestEntityWithEffectors.class, "resetPassword");
+    
     @Effector(description = "Reset password")
-    Void resetPassword(@EffectorParam(name = "newPassword") String param1, 
@EffectorParam(name = "secretPin") Integer secretPin);
+    void resetPassword(@EffectorParam(name = "newPassword") String 
newPassword, @EffectorParam(name = "secretPin") Integer secretPin);
+
+    @Effector(description = "Reset password throws exception")
+    void resetPasswordThrowsException(@EffectorParam(name = "newPassword") 
String newPassword, @EffectorParam(name = "secretPin") Integer secretPin);
 
     @Effector(description = "Reset User and password effector")
-    Void invokeUserAndPassword(@EffectorParam(name = "user") String user,
+    void invokeUserAndPassword(@EffectorParam(name = "user") String user,
                                @EffectorParam(name = "newPassword", 
defaultValue = "Test") String newPassword,
                                @EffectorParam(name = "paramDefault", 
defaultValue = "DefaultValue") String paramDefault);
+    
+    @Effector(description = "Reset password on children")
+    void resetPasswordOnChildren(@EffectorParam(name = "newPassword") String 
newPassword, @EffectorParam(name = "secretPin") Integer secretPin) throws 
Exception;
 }

http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/dfd6ef47/core/src/test/java/org/apache/brooklyn/core/mgmt/internal/TestEntityWithEffectorsImpl.java
----------------------------------------------------------------------
diff --git 
a/core/src/test/java/org/apache/brooklyn/core/mgmt/internal/TestEntityWithEffectorsImpl.java
 
b/core/src/test/java/org/apache/brooklyn/core/mgmt/internal/TestEntityWithEffectorsImpl.java
index 8566d59..dd270ad 100644
--- 
a/core/src/test/java/org/apache/brooklyn/core/mgmt/internal/TestEntityWithEffectorsImpl.java
+++ 
b/core/src/test/java/org/apache/brooklyn/core/mgmt/internal/TestEntityWithEffectorsImpl.java
@@ -18,25 +18,41 @@
  */
 package org.apache.brooklyn.core.mgmt.internal;
 
+import org.apache.brooklyn.core.entity.Entities;
 import org.apache.brooklyn.core.test.entity.TestEntityImpl;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
+import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.Iterables;
+
 public class TestEntityWithEffectorsImpl extends TestEntityImpl implements 
TestEntityWithEffectors {
     private static final Logger log = 
LoggerFactory.getLogger(TestEntityWithEffectorsImpl.class);
 
     @Override
-    public Void resetPassword(String newPassword, Integer secretPin) {
+    public void resetPassword(String newPassword, Integer secretPin) {
         log.info("Invoked effector from resetPassword with params {} {}", new 
Object[] {newPassword, secretPin});
         assert newPassword != null;
-        return null;
     }
 
     @Override
-    public Void invokeUserAndPassword(String user,String newPassword, String 
paramDefault) {
+    public void resetPasswordThrowsException(String newPassword, Integer 
secretPin) {
+        log.info("Invoked effector from resetPasswordThrowsException with 
params {} {}", new Object[] {newPassword, secretPin});
+        throw new RuntimeException("Simulate failure in 
resetPasswordThrowsException");
+    }
+    
+    @Override
+    public void invokeUserAndPassword(String user,String newPassword, String 
paramDefault) {
         log.info("Invoked effector from invokeUserAndPassword with params {} 
{} {}", new Object[] {user, newPassword, paramDefault});
         assert user != null;
         assert newPassword != null;
-        return null;
+    }
+
+    @Override
+    public void resetPasswordOnChildren(String newPassword, Integer secretPin) 
throws Exception {
+        for (TestEntityWithEffectors child : Iterables.filter(getChildren(), 
TestEntityWithEffectors.class)) {
+            Entities.invokeEffector(this, child, RESET_PASSWORD, 
ImmutableMap.of("newPassword", newPassword, "secretPin", secretPin)).get();
+            child.resetPassword(newPassword, secretPin);
+        }
     }
 }

Reply via email to