← Back to team overview

launchpad-reviewers team mailing list archive

[Merge] lp:~wallyworld/launchpad/bug-security-warning-1033893 into lp:launchpad

 

Ian Booth has proposed merging lp:~wallyworld/launchpad/bug-security-warning-1033893 into lp:launchpad.

Requested reviews:
  Curtis Hovey (sinzui)
Related bugs:
  Bug #1033893 in Launchpad itself: "should give warnings and ask for confirmation to set a bug as a security issue"
  https://bugs.launchpad.net/launchpad/+bug/1033893

For more details, see:
https://code.launchpad.net/~wallyworld/launchpad/bug-security-warning-1033893/+merge/120916

== Implementation ==

When a user changes a bug to a private type from being public using the popup in the privacy portlet, a confirmation is requested asking them to confirm they want to make the bug private. The bug report linked to this mp asked for a warning for transition to private security, but I thought we would want it for transition from public to any private type. If you don't want this, I can make it just for private security.

== Tests ==

Update information_type_choice yui tests.

== Lint ==

Checking for conflicts and issues in changed files.

Linting changed files:
  lib/lp/bugs/javascript/information_type_choice.js
  lib/lp/bugs/javascript/tests/test_information_type_choice.js


-- 
https://code.launchpad.net/~wallyworld/launchpad/bug-security-warning-1033893/+merge/120916
Your team Launchpad code reviewers is subscribed to branch lp:launchpad.
=== modified file 'lib/lp/bugs/javascript/information_type_choice.js'
--- lib/lp/bugs/javascript/information_type_choice.js	2012-08-03 15:30:38 +0000
+++ lib/lp/bugs/javascript/information_type_choice.js	2012-08-23 06:12:29 +0000
@@ -138,6 +138,61 @@
     ns.update_subscription_status(skip_animation);
 };
 
+/**
+ * Possibly prompt the user to confirm the change of information type.
+ * If the old value is public, and the new value is private, we want to
+ * confirm that the user really wants to make the change.
+ *
+ * @param widget
+ * @param initial_value
+ * @param lp_client
+ * @private
+ */
+namespace._confirm_information_type_change = function(widget, initial_value,
+                                                      lp_client) {
+    var value = widget.get('value');
+    var do_save = function() {
+        update_privacy_portlet(value);
+        namespace.save_information_type(
+            widget, value, lp_client);
+    };
+    // We only want to show the confirmation for private types where the
+    // previous value was public.
+    var is_new_value_private = (Y.Array.indexOf(
+        LP.cache.private_types, value) >= 0);
+    var is_old_value_private = (Y.Array.indexOf(
+        LP.cache.private_types, initial_value) >= 0);
+    if (!is_new_value_private || is_old_value_private) {
+        do_save();
+        return;
+    }
+    // Reset the widget back to it's original value so the user doesn't see it
+    // change while the confirmation dialog is showing.
+    var new_value = widget.get('value');
+    widget.set('value', initial_value);
+    var confirm_text_template = [
+        '<p class="block-sprite large-warning">',
+        '    You are about to mark this bug as ',
+        '    <strong>{information_type}</strong>.<br>',
+        '    This may make the bug invisible to contributors.',
+        '</p><p>',
+        '    <strong>Please confirm you really want to do this.</strong>',
+        '</p>'
+        ].join('');
+    var title = information_type_value_from_key(value, 'value', 'name');
+    var confirm_text = Y.Lang.sub(confirm_text_template,
+            {information_type: title});
+    var co = new Y.lp.app.confirmationoverlay.ConfirmationOverlay({
+        submit_fn: function() {
+            widget.set('value', new_value);
+            do_save();
+        },
+        form_content: confirm_text,
+        headerContent: '<h2>Confirm change to private</h2>'
+    });
+    co.show();
+};
+
 namespace.setup_information_type_choice = function(privacy_link, lp_client,
                                                    skip_anim) {
     skip_animation = skip_anim;
@@ -157,10 +212,8 @@
     Y.lp.app.choice.hook_up_choicesource_spinner(information_type_edit);
     information_type_edit.render();
     information_type_edit.on("save", function(e) {
-        var value = information_type_edit.get('value');
-        update_privacy_portlet(value);
-        namespace.save_information_type(
-            information_type_edit, value, lp_client);
+        namespace._confirm_information_type_change(
+                information_type_edit, initial_value, lp_client);
     });
     privacy_link.addClass('js-action');
     return information_type_edit;

=== modified file 'lib/lp/bugs/javascript/tests/test_information_type_choice.js'
--- lib/lp/bugs/javascript/tests/test_information_type_choice.js	2012-08-03 15:30:38 +0000
+++ lib/lp/bugs/javascript/tests/test_information_type_choice.js	2012-08-23 06:12:29 +0000
@@ -26,6 +26,8 @@
                     information_type_data: [
                         {'value': 'PUBLIC', 'name': 'Public',
                             'description': 'Public Description'},
+                        {'value': 'PUBLICSECURITY', 'name': 'Public Security',
+                            'description': 'Public Security Description'},
                         {'value': 'PROPRIETARY', 'name': 'Proprietary',
                             'description': 'Private Description'},
                         {'value': 'USERDATA', 'name': 'Private',
@@ -37,25 +39,34 @@
             var portlet = Y.Node.create(
                     Y.one('#portlet-template').getContent());
             this.fixture.appendChild(portlet);
+            this.mockio = new Y.lp.testing.mockio.MockIo();
+            this.lp_client = new Y.lp.client.Launchpad({
+                io_provider: this.mockio
+            });
 
-            var lp_client = new Y.lp.client.Launchpad();
-            var privacy_link = Y.one('#privacy-link');
-            this.widget = ns.setup_information_type_choice(
-                privacy_link, lp_client, true);
             Y.lp.bugs.subscribers.createBugSubscribersLoader({
                 container_box: '#other-bug-subscribers',
                 subscribers_details_view:
                     '/+bug-portlet-subscribers-details'});
 
         },
+
         tearDown: function () {
             if (this.fixture !== null) {
                 this.fixture.empty(true);
             }
             delete this.fixture;
+            delete this.mockio;
+            delete this.lp_client;
             delete window.LP;
         },
 
+        makeWidget: function() {
+            var privacy_link = Y.one('#privacy-link');
+            this.widget = ns.setup_information_type_choice(
+                privacy_link, this.lp_client, true);
+        },
+
         _shim_privacy_banner: function () {
             var old_func = Y.lp.app.banner.privacy.getPrivacyBanner;
             Y.lp.app.banner.privacy.getPrivacyBanner = function () {
@@ -79,10 +90,7 @@
 
         // The save XHR call works as expected.
         test_save_information_type: function() {
-            var mockio = new Y.lp.testing.mockio.MockIo();
-            var lp_client = new Y.lp.client.Launchpad({
-                io_provider: mockio
-            });
+            this.makeWidget();
             var orig_save_success = ns.information_type_save_success;
             var save_success_called = false;
             ns.information_type_save_success = function(widget, value,
@@ -95,23 +103,24 @@
                     'value', subscribers_data.cache_data.item);
                 save_success_called = true;
             };
-            ns.save_information_type(this.widget, 'USERDATA', lp_client);
-            mockio.success({
+            ns.save_information_type(this.widget, 'USERDATA', this.lp_client);
+            this.mockio.success({
                 responseText: '{"subscription_data": "subscribers",' +
                     '"cache_data": {"item": "value"}}',
                 responseHeaders: {'Content-Type': 'application/json'}});
             Y.Assert.areEqual(
-                document.URL + '/+secrecy', mockio.last_request.url);
+                document.URL + '/+secrecy', this.mockio.last_request.url);
             Y.Assert.areEqual(
                 'field.actions.change=Change&' +
                 'field.information_type=USERDATA',
-                mockio.last_request.config.data);
+                this.mockio.last_request.config.data);
             Y.Assert.isTrue(save_success_called);
             ns.information_type_save_success = orig_save_success;
         },
 
         // Setting a private type shows the privacy banner.
         test_information_type_save_success_private: function() {
+            this.makeWidget();
             var old_func = this._shim_privacy_banner();
             var hide_flag = false;
             var update_flag = false;
@@ -134,6 +143,7 @@
 
         // Setting a private type hides the privacy banner.
         test_information_type_save_success_public: function() {
+            this.makeWidget();
             var old_func = this._shim_privacy_banner();
             var flag = false;
             Y.on('test:banner:hide', function() {
@@ -152,6 +162,7 @@
 
         // A successful save updates the subscribers portlet.
         test_information_type_save_success_with_subscribers_data: function() {
+            this.makeWidget();
             var old_func = this._shim_privacy_banner();
             var flag = false;
             Y.on('test:banner:hide', function() {
@@ -182,8 +193,74 @@
             this._unshim_privacy_banner(old_func);
         },
 
-        // Selecting a new information type calls save correctly.
-        test_perform_update_information_type: function() {
+        // Selecting a new information type calls save correctly when no
+        // confirmation popup is expected.
+        _assert_perform_update_information_type_no_confirmation: function(
+                from_value, to_value) {
+            var from_title;
+            Y.some(window.LP.cache.information_type_data, function(item) {
+                if (item.value === from_value) {
+                    from_title = item.name;
+                    return true;
+                }
+                return false;
+            });
+            window.LP.cache.bug.information_type = from_title;
+            this.widget.set('value', from_title);
+            var privacy_link = Y.one('#privacy-link');
+            privacy_link.simulate('click');
+            var private_choice = Y.one(
+                '.yui3-ichoicelist-content a[href="#'  + to_value + '"]');
+            var orig_save_information_type = ns.save_information_type;
+            var function_called = false;
+            ns.save_information_type = function(widget, value, lp_client) {
+                Y.Assert.areEqual(to_value, value);
+                function_called = true;
+            };
+            private_choice.simulate('click');
+            // No confirmation dialog please.
+            var co = Y.one('.yui3-overlay.yui3-lp-app-confirmationoverlay');
+            Y.Assert.isNull(co);
+            var description_node = Y.one('#information-type-description');
+            var to_description;
+            Y.some(window.LP.cache.information_type_data, function(item) {
+                if (item.value === to_value) {
+                    to_description = item.description;
+                    return true;
+                }
+                return false;
+            });
+            Y.Assert.areEqual(to_description, description_node.get('text'));
+            var summary = Y.one('#information-type-summary');
+            var is_private = (Y.Array.indexOf(
+                LP.cache.private_types, to_value) >= 0);
+            Y.Assert.areEqual(is_private, summary.hasClass('private'));
+            Y.Assert.isTrue(function_called);
+            ns.save_information_type = orig_save_information_type;
+        },
+
+        // Selecting a new public information type doesn't show the
+        // confirmation dialog and calls save correctly.
+        test_perform_update_information_type_to_public: function() {
+            this.makeWidget();
+            this._assert_perform_update_information_type_no_confirmation(
+                    'PUBLIC', 'PUBLICSECURITY');
+        },
+
+        // Selecting a new private information type doesn't show the
+        // confirmation dialog if the original type is also private,
+        // and calls save correctly.
+        test_perform_update_information_type_private_to_private: function() {
+            LP.cache.bug.information_type = 'Private';
+            this.makeWidget();
+            this._assert_perform_update_information_type_no_confirmation(
+                    'USERDATA', 'PROPRIETARY');
+        },
+
+        // Selecting a new private information type shows the
+        // confirmation dialog and calls save correctly when user says 'yes'.
+        test_perform_update_information_type_to_private: function() {
+            this.makeWidget();
             var privacy_link = Y.one('#privacy-link');
             privacy_link.simulate('click');
             var private_choice = Y.one(
@@ -191,27 +268,61 @@
             var orig_save_information_type = ns.save_information_type;
             var function_called = false;
             ns.save_information_type = function(widget, value, lp_client) {
-                Y.Assert.areEqual(
-                    'USERDATA', value); function_called = true; };
+                Y.Assert.areEqual('USERDATA', value);
+                function_called = true;
+            };
             private_choice.simulate('click');
+            // We click 'yes' on the confirmation dialog.
+            var co = Y.one('.yui3-overlay.yui3-lp-app-confirmationoverlay');
+            var div = co.one('.yui3-lazr-formoverlay-actions');
+            var ok = div.one('.ok-btn');
+            ok.simulate('click');
             var description_node = Y.one('#information-type-description');
             Y.Assert.areEqual(
-                'Private Description', description_node.get('text'));
+                    'Private Description', description_node.get('text'));
             var summary = Y.one('#information-type-summary');
             Y.Assert.isTrue(summary.hasClass('private'));
             Y.Assert.isTrue(function_called);
             ns.save_information_type = orig_save_information_type;
         },
 
+        // Selecting a new private information type shows the
+        // confirmation dialog and doesn't call save when user says 'no'.
+        test_perform_update_information_type_to_private_no: function() {
+            this.makeWidget();
+            var privacy_link = Y.one('#privacy-link');
+            privacy_link.simulate('click');
+            var private_choice = Y.one(
+                '.yui3-ichoicelist-content a[href="#USERDATA"]');
+            var orig_save_information_type = ns.save_information_type;
+            var function_called = false;
+            ns.save_information_type = function(widget, value, lp_client) {
+                function_called = true;
+            };
+            private_choice.simulate('click');
+            // We click 'no' on the confirmation dialog.
+            var co = Y.one('.yui3-overlay.yui3-lp-app-confirmationoverlay');
+            var div = co.one('.yui3-lazr-formoverlay-actions');
+            var ok = div.one('.cancel-btn');
+            ok.simulate('click');
+            // Original widget value, description etc should be retained.
+            Y.Assert.areEqual('PUBLIC', this.widget.get('value'));
+            var description_node = Y.one('#information-type-description');
+            Y.Assert.areEqual(
+                    'Everyone can see this information.',
+                    description_node.get('text'));
+            var summary = Y.one('#information-type-summary');
+            Y.Assert.isFalse(summary.hasClass('private'));
+            Y.Assert.isFalse(function_called);
+            ns.save_information_type = orig_save_information_type;
+        },
+
         // Test error handling when a save fails.
         test_information_type_save_error: function() {
-            var mockio = new Y.lp.testing.mockio.MockIo();
-            var lp_client = new Y.lp.client.Launchpad({
-                io_provider: mockio
-            });
+            this.makeWidget();
             this.widget.set('value', 'USERDATA');
-            ns.save_information_type(this.widget, 'USERDATA', lp_client);
-            mockio.last_request.respond({
+            ns.save_information_type(this.widget, 'USERDATA', this.lp_client);
+            this.mockio.last_request.respond({
                 status: 500,
                 statusText: 'An error occurred'
             });


Follow ups