launchpad-reviewers team mailing list archive
-
launchpad-reviewers team
-
Mailing list archive
-
Message #11256
[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