You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@storm.apache.org by bo...@apache.org on 2015/10/09 16:09:23 UTC

[1/3] storm git commit: STORM-1096: Fix some issues with impersonation on the UI

Repository: storm
Updated Branches:
  refs/heads/master 2cd27e044 -> 3f79c2f40


STORM-1096: Fix some issues with impersonation on the UI


Project: http://git-wip-us.apache.org/repos/asf/storm/repo
Commit: http://git-wip-us.apache.org/repos/asf/storm/commit/0476b92d
Tree: http://git-wip-us.apache.org/repos/asf/storm/tree/0476b92d
Diff: http://git-wip-us.apache.org/repos/asf/storm/diff/0476b92d

Branch: refs/heads/master
Commit: 0476b92dd82e726adab200d5a77944a5f4f3883c
Parents: 1822491
Author: Robert (Bobby) Evans <ev...@yahoo-inc.com>
Authored: Wed Oct 7 21:03:38 2015 +0000
Committer: Robert (Bobby) Evans <ev...@yahoo-inc.com>
Committed: Wed Oct 7 21:03:38 2015 +0000

----------------------------------------------------------------------
 conf/defaults.yaml                              |  1 +
 .../src/clj/backtype/storm/daemon/drpc.clj      | 26 +++--
 storm-core/src/clj/backtype/storm/ui/core.clj   | 99 ++++++++++++--------
 .../storm/security/auth/ReqContext.java         |  7 ++
 .../auth/kerberos/ServerCallbackHandler.java    |  2 +
 .../auth/DefaultHttpCredentialsPlugin_test.clj  | 40 ++++----
 6 files changed, 104 insertions(+), 71 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/storm/blob/0476b92d/conf/defaults.yaml
----------------------------------------------------------------------
diff --git a/conf/defaults.yaml b/conf/defaults.yaml
index 4a8e354..495cec1 100644
--- a/conf/defaults.yaml
+++ b/conf/defaults.yaml
@@ -71,6 +71,7 @@ nimbus.topology.validator: "backtype.storm.nimbus.DefaultTopologyValidator"
 topology.min.replication.count: 1
 topology.max.replication.wait.time.sec: 60
 nimbus.credential.renewers.freq.secs: 600
+nimbus.impersonation.authorizer: "backtype.storm.security.auth.authorizer.ImpersonationAuthorizer"
 
 ### ui.* configs are for the master
 ui.host: 0.0.0.0

http://git-wip-us.apache.org/repos/asf/storm/blob/0476b92d/storm-core/src/clj/backtype/storm/daemon/drpc.clj
----------------------------------------------------------------------
diff --git a/storm-core/src/clj/backtype/storm/daemon/drpc.clj b/storm-core/src/clj/backtype/storm/daemon/drpc.clj
index bbc9611..9475910 100644
--- a/storm-core/src/clj/backtype/storm/daemon/drpc.clj
+++ b/storm-core/src/clj/backtype/storm/daemon/drpc.clj
@@ -158,35 +158,31 @@
   (fn [request]
     (handler request)))
 
+(defn populate-context!
+  "Populate the Storm RequestContext from an servlet-request. This should be called in each handler"
+  [http-creds-handler servlet-request]
+    (when http-creds-handler
+      (.populateContext http-creds-handler (ReqContext/context) servlet-request)))
+
 (defn webapp [handler http-creds-handler]
   (->
     (routes
       (POST "/drpc/:func" [:as {:keys [body servlet-request]} func & m]
         (let [args (slurp body)]
-          (if http-creds-handler
-            (.populateContext http-creds-handler (ReqContext/context)
-                              servlet-request))
+          (populate-context! http-creds-handler servlet-request)
           (.execute handler func args)))
       (POST "/drpc/:func/" [:as {:keys [body servlet-request]} func & m]
         (let [args (slurp body)]
-          (if http-creds-handler
-            (.populateContext http-creds-handler (ReqContext/context)
-                              servlet-request))
+          (populate-context! http-creds-handler servlet-request)
           (.execute handler func args)))
       (GET "/drpc/:func/:args" [:as {:keys [servlet-request]} func args & m]
-          (if http-creds-handler
-            (.populateContext http-creds-handler (ReqContext/context)
-                              servlet-request))
+          (populate-context! http-creds-handler servlet-request)
           (.execute handler func args))
       (GET "/drpc/:func/" [:as {:keys [servlet-request]} func & m]
-          (if http-creds-handler
-            (.populateContext http-creds-handler (ReqContext/context)
-                              servlet-request))
+          (populate-context! http-creds-handler servlet-request)
           (.execute handler func ""))
       (GET "/drpc/:func" [:as {:keys [servlet-request]} func & m]
-          (if http-creds-handler
-            (.populateContext http-creds-handler (ReqContext/context)
-                              servlet-request))
+          (populate-context! http-creds-handler servlet-request)
           (.execute handler func "")))
     (wrap-reload '[backtype.storm.daemon.drpc])
     handle-request))

http://git-wip-us.apache.org/repos/asf/storm/blob/0476b92d/storm-core/src/clj/backtype/storm/ui/core.clj
----------------------------------------------------------------------
diff --git a/storm-core/src/clj/backtype/storm/ui/core.clj b/storm-core/src/clj/backtype/storm/ui/core.clj
index 5fa487f..1f94098 100644
--- a/storm-core/src/clj/backtype/storm/ui/core.clj
+++ b/storm-core/src/clj/backtype/storm/ui/core.clj
@@ -57,12 +57,10 @@
 (def http-creds-handler (AuthUtils/GetUiHttpCredentialsPlugin *STORM-CONF*))
 
 (defn assert-authorized-user
-  ([servlet-request op]
-    (assert-authorized-user servlet-request op nil))
-  ([servlet-request op topology-conf]
+  ([op]
+    (assert-authorized-user op nil))
+  ([op topology-conf]
     (let [context (ReqContext/context)]
-      (if http-creds-handler (.populateContext http-creds-handler context servlet-request))
-
       (if (.isImpersonating context)
         (if *UI-IMPERSONATION-HANDLER*
             (if-not (.permit *UI-IMPERSONATION-HANDLER* context op topology-conf)
@@ -823,43 +821,62 @@
 
 (def http-creds-handler (AuthUtils/GetUiHttpCredentialsPlugin *STORM-CONF*))
 
+(defn populate-context!
+  "Populate the Storm RequestContext from an servlet-request. This should be called in each handler"
+  [servlet-request]
+    (when http-creds-handler
+      (.populateContext http-creds-handler (ReqContext/context) servlet-request)))
+
+(defn get-user-name
+  [servlet-request]
+  (.getUserName http-creds-handler servlet-request))
+
 (defroutes main-routes
   (GET "/api/v1/cluster/configuration" [& m]
-       (json-response (cluster-configuration)
-                      (:callback m) :serialize-fn identity))
+    (json-response (cluster-configuration)
+                   (:callback m) :serialize-fn identity))
   (GET "/api/v1/cluster/summary" [:as {:keys [cookies servlet-request]} & m]
-       (let [user (.getUserName http-creds-handler servlet-request)]
-         (assert-authorized-user servlet-request "getClusterInfo")
-         (json-response (cluster-summary user) (:callback m))))
+    (populate-context! servlet-request)
+    (assert-authorized-user "getClusterInfo")
+    (let [user (get-user-name servlet-request)]
+      (json-response (cluster-summary user) (:callback m))))
   (GET "/api/v1/nimbus/summary" [:as {:keys [cookies servlet-request]} & m]
-    (assert-authorized-user servlet-request "getClusterInfo")
+    (populate-context! servlet-request)
+    (assert-authorized-user "getClusterInfo")
     (json-response (nimbus-summary) (:callback m)))
   (GET "/api/v1/supervisor/summary" [:as {:keys [cookies servlet-request]} & m]
-       (assert-authorized-user servlet-request "getClusterInfo")
-       (json-response (supervisor-summary) (:callback m)))
+    (populate-context! servlet-request)
+    (assert-authorized-user "getClusterInfo")
+    (json-response (supervisor-summary) (:callback m)))
   (GET "/api/v1/topology/summary" [:as {:keys [cookies servlet-request]} & m]
-       (assert-authorized-user servlet-request "getClusterInfo")
-       (json-response (all-topologies-summary) (:callback m)))
-  (GET  "/api/v1/topology/:id" [:as {:keys [cookies servlet-request schema]} id & m]
-        (let [user (.getUserName http-creds-handler servlet-request)]
-          (assert-authorized-user servlet-request "getTopology" (topology-config id))
-          (json-response (topology-page id (:window m) (check-include-sys? (:sys m)) user (= schema :https)) (:callback m))))
+    (populate-context! servlet-request)
+    (assert-authorized-user "getClusterInfo")
+    (json-response (all-topologies-summary) (:callback m)))
+  (GET "/api/v1/topology/:id" [:as {:keys [cookies servlet-request schema]} id & m]
+    (populate-context! servlet-request)
+    (assert-authorized-user "getTopology" (topology-config id))
+    (let [user (get-user-name servlet-request)]
+      (json-response (topology-page id (:window m) (check-include-sys? (:sys m)) user (= schema :https)) (:callback m))))
   (GET "/api/v1/topology/:id/visualization" [:as {:keys [cookies servlet-request]} id & m]
-        (assert-authorized-user servlet-request "getTopology" (topology-config id))
-        (json-response (mk-visualization-data id (:window m) (check-include-sys? (:sys m))) (:callback m)))
+    (populate-context! servlet-request)
+    (assert-authorized-user "getTopology" (topology-config id))
+    (json-response (mk-visualization-data id (:window m) (check-include-sys? (:sys m))) (:callback m)))
   (GET "/api/v1/topology/:id/component/:component" [:as {:keys [cookies servlet-request scheme]} id component & m]
-       (let [user (.getUserName http-creds-handler servlet-request)]
-         (assert-authorized-user servlet-request "getTopology" (topology-config id))
-         (json-response
+    (populate-context! servlet-request)
+    (assert-authorized-user "getTopology" (topology-config id))
+    (let [user (get-user-name servlet-request)]
+      (json-response
           (component-page id component (:window m) (check-include-sys? (:sys m)) user (= scheme :https))
           (:callback m))))
   (GET "/api/v1/topology/:id/logconfig" [:as {:keys [cookies servlet-request]} id & m]
-       (assert-authorized-user servlet-request "getTopology" (topology-config id))
+    (populate-context! servlet-request)
+    (assert-authorized-user "getTopology" (topology-config id))
        (json-response (log-config id) (:callback m)))
   (POST "/api/v1/topology/:id/activate" [:as {:keys [cookies servlet-request]} id & m]
+    (populate-context! servlet-request)
+    (assert-authorized-user "activate" (topology-config id))
     (thrift/with-configured-nimbus-connection nimbus
-    (assert-authorized-user servlet-request "activate" (topology-config id))
-      (let [tplg (->> (doto
+       (let [tplg (->> (doto
                         (GetInfoOptions.)
                         (.set_num_err_choice NumErrorsChoice/NONE))
                       (.getTopologyInfoWithOpts ^Nimbus$Client nimbus id))
@@ -868,9 +885,10 @@
         (log-message "Activating topology '" name "'")))
     (json-response (topology-op-response id "activate") (m "callback")))
   (POST "/api/v1/topology/:id/deactivate" [:as {:keys [cookies servlet-request]} id & m]
+    (populate-context! servlet-request)
+    (assert-authorized-user "deactivate" (topology-config id))
     (thrift/with-configured-nimbus-connection nimbus
-    (assert-authorized-user servlet-request "deactivate" (topology-config id))
-      (let [tplg (->> (doto
+        (let [tplg (->> (doto
                         (GetInfoOptions.)
                         (.set_num_err_choice NumErrorsChoice/NONE))
                       (.getTopologyInfoWithOpts ^Nimbus$Client nimbus id))
@@ -879,9 +897,10 @@
         (log-message "Deactivating topology '" name "'")))
     (json-response (topology-op-response id "deactivate") (m "callback")))
   (POST "/api/v1/topology/:id/debug/:action/:spct" [:as {:keys [cookies servlet-request]} id action spct & m]
-    (assert-authorized-user servlet-request "debug" (topology-config id))
+    (populate-context! servlet-request)
+    (assert-authorized-user "debug" (topology-config id))
     (thrift/with-configured-nimbus-connection nimbus
-      (let [tplg (->> (doto
+        (let [tplg (->> (doto
                         (GetInfoOptions.)
                         (.set_num_err_choice NumErrorsChoice/NONE))
                    (.getTopologyInfoWithOpts ^Nimbus$Client nimbus id))
@@ -889,9 +908,10 @@
             enable? (= "enable" action)]
         (.debug nimbus name "" enable? (Integer/parseInt spct))
         (log-message "Debug topology [" name "] action [" action "] sampling pct [" spct "]")))
-    (json-response (topology-op-response id (str "debug/" action)) (m "callback")))
+     (json-response (topology-op-response id (str "debug/" action)) (m "callback")))
   (POST "/api/v1/topology/:id/component/:component/debug/:action/:spct" [:as {:keys [cookies servlet-request]} id component action spct & m]
-    (assert-authorized-user servlet-request "debug" (topology-config id))
+    (populate-context! servlet-request)
+    (assert-authorized-user "debug" (topology-config id))
     (thrift/with-configured-nimbus-connection nimbus
       (let [tplg (->> (doto
                         (GetInfoOptions.)
@@ -903,8 +923,9 @@
         (log-message "Debug topology [" name "] component [" component "] action [" action "] sampling pct [" spct "]")))
     (json-response (component-op-response id component (str "/debug/" action)) (m "callback")))
   (POST "/api/v1/topology/:id/rebalance/:wait-time" [:as {:keys [cookies servlet-request]} id wait-time & m]
+    (populate-context! servlet-request)
+    (assert-authorized-user "rebalance" (topology-config id))
     (thrift/with-configured-nimbus-connection nimbus
-    (assert-authorized-user servlet-request "rebalance" (topology-config id))
       (let [tplg (->> (doto
                         (GetInfoOptions.)
                         (.set_num_err_choice NumErrorsChoice/NONE))
@@ -922,7 +943,8 @@
         (log-message "Rebalancing topology '" name "' with wait time: " wait-time " secs")))
     (json-response (topology-op-response id "rebalance") (m "callback")))
   (POST "/api/v1/topology/:id/kill/:wait-time" [:as {:keys [cookies servlet-request]} id wait-time & m]
-    (assert-authorized-user servlet-request "killTopology" (topology-config id))
+    (populate-context! servlet-request)
+    (assert-authorized-user "killTopology" (topology-config id))
     (thrift/with-configured-nimbus-connection nimbus
       (let [tplg (->> (doto
                         (GetInfoOptions.)
@@ -934,9 +956,9 @@
         (.killTopologyWithOpts nimbus name options)
         (log-message "Killing topology '" name "' with wait time: " wait-time " secs")))
     (json-response (topology-op-response id "kill") (m "callback")))
-
   (POST "/api/v1/topology/:id/logconfig" [:as {:keys [cookies servlet-request]} id namedLoggerLevels & m]
-    (assert-authorized-user servlet-request "setLogConfig" (topology-config id))
+    (populate-context! servlet-request)
+    (assert-authorized-user "setLogConfig" (topology-config id))
     (thrift/with-configured-nimbus-connection
       nimbus
       (let [new-log-config (LogConfig.)]
@@ -961,9 +983,8 @@
         (log-message "Setting topology " id " log config " new-log-config)
         (.setLogConfig nimbus id new-log-config)
         (json-response (log-config id) (m "callback")))))
-
   (GET "/" [:as {cookies :cookies}]
-       (resp/redirect "/index.html"))
+    (resp/redirect "/index.html"))
   (route/resources "/")
   (route/not-found "Page not found"))
 

http://git-wip-us.apache.org/repos/asf/storm/blob/0476b92d/storm-core/src/jvm/backtype/storm/security/auth/ReqContext.java
----------------------------------------------------------------------
diff --git a/storm-core/src/jvm/backtype/storm/security/auth/ReqContext.java b/storm-core/src/jvm/backtype/storm/security/auth/ReqContext.java
index a252f85..2f18982 100644
--- a/storm-core/src/jvm/backtype/storm/security/auth/ReqContext.java
+++ b/storm-core/src/jvm/backtype/storm/security/auth/ReqContext.java
@@ -55,6 +55,13 @@ public class ReqContext {
         return ctxt.get();
     }
 
+    /**
+     * Reset the context back to a default.  used for testing.
+     */
+    public static void reset() {
+        ctxt.remove();
+    }
+
     //each thread will have its own request context
     private static final ThreadLocal < ReqContext > ctxt = 
             new ThreadLocal < ReqContext > () {

http://git-wip-us.apache.org/repos/asf/storm/blob/0476b92d/storm-core/src/jvm/backtype/storm/security/auth/kerberos/ServerCallbackHandler.java
----------------------------------------------------------------------
diff --git a/storm-core/src/jvm/backtype/storm/security/auth/kerberos/ServerCallbackHandler.java b/storm-core/src/jvm/backtype/storm/security/auth/kerberos/ServerCallbackHandler.java
index 7b143f0..46ffa61 100644
--- a/storm-core/src/jvm/backtype/storm/security/auth/kerberos/ServerCallbackHandler.java
+++ b/storm-core/src/jvm/backtype/storm/security/auth/kerberos/ServerCallbackHandler.java
@@ -87,6 +87,8 @@ public class ServerCallbackHandler implements CallbackHandler {
         //add the authNid as the real user in reqContext's subject which will be used during authorization.
         if(!ac.getAuthenticationID().equals(ac.getAuthorizationID())) {
             ReqContext.context().setRealPrincipal(new SaslTransportPlugin.User(ac.getAuthenticationID()));
+        } else {
+            ReqContext.context().setRealPrincipal(null);
         }
 
         ac.setAuthorized(true);

http://git-wip-us.apache.org/repos/asf/storm/blob/0476b92d/storm-core/test/clj/backtype/storm/security/auth/DefaultHttpCredentialsPlugin_test.clj
----------------------------------------------------------------------
diff --git a/storm-core/test/clj/backtype/storm/security/auth/DefaultHttpCredentialsPlugin_test.clj b/storm-core/test/clj/backtype/storm/security/auth/DefaultHttpCredentialsPlugin_test.clj
index e7b44cf..00c1d76 100644
--- a/storm-core/test/clj/backtype/storm/security/auth/DefaultHttpCredentialsPlugin_test.clj
+++ b/storm-core/test/clj/backtype/storm/security/auth/DefaultHttpCredentialsPlugin_test.clj
@@ -48,22 +48,28 @@
         (is (.equals exp-name (.getUserName handler req)))))
 
     (testing "returns doAsUser from requests principal when Header has doAsUser param set"
-      (let [exp-name "Alice"
-            do-as-user-name "Bob"
-            princ (SingleUserPrincipal. exp-name)
-            req (Mockito/mock HttpServletRequest)
-            _ (. (Mockito/when (. req getUserPrincipal))
-              thenReturn princ)
-            _ (. (Mockito/when (. req getHeader "doAsUser"))
-              thenReturn do-as-user-name)
-            context (.populateContext handler (ReqContext/context) req)]
-        (is (= true (.isImpersonating context)))
-        (is (.equals exp-name (.getName (.realPrincipal context))))
-        (is (.equals do-as-user-name (.getName (.principal context))))))))
+      (try
+        (let [exp-name "Alice"
+              do-as-user-name "Bob"
+              princ (SingleUserPrincipal. exp-name)
+              req (Mockito/mock HttpServletRequest)
+              _ (. (Mockito/when (. req getUserPrincipal))
+                thenReturn princ)
+              _ (. (Mockito/when (. req getHeader "doAsUser"))
+                thenReturn do-as-user-name)
+              context (.populateContext handler (ReqContext/context) req)]
+          (is (= true (.isImpersonating context)))
+          (is (.equals exp-name (.getName (.realPrincipal context))))
+          (is (.equals do-as-user-name (.getName (.principal context)))))
+        (finally
+          (ReqContext/reset))))))
 
 (deftest test-populate-req-context-on-null-user
-  (let [req (Mockito/mock HttpServletRequest)
-        handler (doto (DefaultHttpCredentialsPlugin.) (.prepare {}))
-        subj (Subject. false (set [(SingleUserPrincipal. "test")]) (set []) (set []))
-        context (ReqContext. subj)]
-    (is (= 0 (-> handler (.populateContext context req) (.subject) (.getPrincipals) (.size))))))
+  (try
+    (let [req (Mockito/mock HttpServletRequest)
+          handler (doto (DefaultHttpCredentialsPlugin.) (.prepare {}))
+          subj (Subject. false (set [(SingleUserPrincipal. "test")]) (set []) (set []))
+          context (ReqContext. subj)]
+      (is (= 0 (-> handler (.populateContext context req) (.subject) (.getPrincipals) (.size)))))
+    (finally
+      (ReqContext/reset))))


[2/3] storm git commit: Merge branch 'STORM-1096' of https://github.com/revans2/incubator-storm into STORM-1096

Posted by bo...@apache.org.
Merge branch 'STORM-1096' of https://github.com/revans2/incubator-storm into STORM-1096

STORM-1096: Fix some issues with impersonation on the UI


Project: http://git-wip-us.apache.org/repos/asf/storm/repo
Commit: http://git-wip-us.apache.org/repos/asf/storm/commit/0aa19370
Tree: http://git-wip-us.apache.org/repos/asf/storm/tree/0aa19370
Diff: http://git-wip-us.apache.org/repos/asf/storm/diff/0aa19370

Branch: refs/heads/master
Commit: 0aa19370c1569f3efb28aac705303d2f5c489e8b
Parents: 2cd27e0 0476b92
Author: Robert (Bobby) Evans <ev...@yahoo-inc.com>
Authored: Fri Oct 9 08:58:33 2015 -0500
Committer: Robert (Bobby) Evans <ev...@yahoo-inc.com>
Committed: Fri Oct 9 08:58:33 2015 -0500

----------------------------------------------------------------------
 conf/defaults.yaml                              |  1 +
 .../src/clj/backtype/storm/daemon/drpc.clj      | 26 +++--
 storm-core/src/clj/backtype/storm/ui/core.clj   | 99 ++++++++++++--------
 .../storm/security/auth/ReqContext.java         |  7 ++
 .../auth/kerberos/ServerCallbackHandler.java    |  2 +
 .../auth/DefaultHttpCredentialsPlugin_test.clj  | 40 ++++----
 6 files changed, 104 insertions(+), 71 deletions(-)
----------------------------------------------------------------------



[3/3] storm git commit: Added STORM-1096 to Changelog

Posted by bo...@apache.org.
Added STORM-1096 to Changelog


Project: http://git-wip-us.apache.org/repos/asf/storm/repo
Commit: http://git-wip-us.apache.org/repos/asf/storm/commit/3f79c2f4
Tree: http://git-wip-us.apache.org/repos/asf/storm/tree/3f79c2f4
Diff: http://git-wip-us.apache.org/repos/asf/storm/diff/3f79c2f4

Branch: refs/heads/master
Commit: 3f79c2f40380ce2632db324afec1f251a1746846
Parents: 0aa1937
Author: Robert (Bobby) Evans <ev...@yahoo-inc.com>
Authored: Fri Oct 9 08:59:15 2015 -0500
Committer: Robert (Bobby) Evans <ev...@yahoo-inc.com>
Committed: Fri Oct 9 08:59:15 2015 -0500

----------------------------------------------------------------------
 CHANGELOG.md             | 3 +++
 dev-tools/storm-merge.py | 2 +-
 2 files changed, 4 insertions(+), 1 deletion(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/storm/blob/3f79c2f4/CHANGELOG.md
----------------------------------------------------------------------
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 96bec08..1fb0633 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -107,6 +107,9 @@
  * STORM-742: Let ShellBolt treat all messages to update heartbeat
  * STORM-992: A bug in the timer.clj might cause unexpected delay to schedule new event
 
+## 0.10.0
+ * STORM-1096: Fix some issues with impersonation on the UI
+
 ## 0.10.0-beta1
  * STORM-873: Flux does not handle diamond topologies
 

http://git-wip-us.apache.org/repos/asf/storm/blob/3f79c2f4/dev-tools/storm-merge.py
----------------------------------------------------------------------
diff --git a/dev-tools/storm-merge.py b/dev-tools/storm-merge.py
index ed06216..33690c2 100755
--- a/dev-tools/storm-merge.py
+++ b/dev-tools/storm-merge.py
@@ -24,7 +24,7 @@ def main():
 
         for pullNumber in args:
 		pull = github.pull("apache", "storm", pullNumber)
-		print "git pull "+pull.from_repo()+" "+pull.from_branch()
+		print "git pull --no-ff "+pull.from_repo()+" "+pull.from_branch()
 
 if __name__ == "__main__":
 	main()