← Back to team overview

launchpad-reviewers team mailing list archive

[Merge] lp:~wallyworld/launchpad/filebug-non-bugsupervisors-1020790 into lp:launchpad

 

Ian Booth has proposed merging lp:~wallyworld/launchpad/filebug-non-bugsupervisors-1020790 into lp:launchpad.

Requested reviews:
  Launchpad code reviewers (launchpad-reviewers)
Related bugs:
  Bug #1020790 in Launchpad itself: "Information type widget on +filebug confuses users"
  https://bugs.launchpad.net/launchpad/+bug/1020790

For more details, see:
https://code.launchpad.net/~wallyworld/launchpad/filebug-non-bugsupervisors-1020790/+merge/113471

== Implementation ==

This branch ensures that only people in a bug supervisor role are able to choose a specific information type with which to file a new bug. Other non privileged uses just get a "security related" checkbox. 

The existing method BugTask.userHasBugSupervisorPrivilegesContext() was used to determine if a user is considered a bug supervisor. So this includes admins, drivers etc in the allowed list. 

== Tests ==

Add yui tests for the filebug form when rendered with the security_related checkbox.
Add new test case TestFileBugForNonBugSupervisors to check that the form rendering and submission works as expected

== Lint ==

Checking for conflicts and issues in changed files.

Linting changed files:
  lib/lp/app/javascript/choice.js
  lib/lp/bugs/browser/bugtarget.py
  lib/lp/bugs/browser/tests/test_bugtarget_filebug.py
  lib/lp/bugs/javascript/filebug.js
  lib/lp/bugs/javascript/tests/test_filebug.html
  lib/lp/bugs/javascript/tests/test_filebug.js
  lib/lp/bugs/templates/bugtarget-filebug-guidelines.pt
-- 
https://code.launchpad.net/~wallyworld/launchpad/filebug-non-bugsupervisors-1020790/+merge/113471
Your team Launchpad code reviewers is requested to review the proposed merge of lp:~wallyworld/launchpad/filebug-non-bugsupervisors-1020790 into lp:launchpad.
=== modified file 'lib/lp/app/javascript/choice.js'
--- lib/lp/app/javascript/choice.js	2012-07-03 02:28:08 +0000
+++ lib/lp/app/javascript/choice.js	2012-07-05 00:58:19 +0000
@@ -161,8 +161,11 @@
  */
 namespace.addPopupChoiceForRadioButtons = function(field_name, choices, cfg) {
     cfg = Y.merge(default_popup_choice_config, cfg);
-    var legacy_node = cfg.container.one('[name="field.' + field_name + '"]')
-        .ancestor('table.radio-button-widget');
+    var field_node = cfg.container.one('[name="field.' + field_name + '"]');
+    if (!Y.Lang.isValue(field_node)) {
+        return;
+    }
+    var legacy_node = field_node.ancestor('table.radio-button-widget');
     if (!Y.Lang.isValue(legacy_node)) {
         return;
     }

=== modified file 'lib/lp/bugs/browser/bugtarget.py'
--- lib/lp/bugs/browser/bugtarget.py	2012-06-29 08:40:05 +0000
+++ lib/lp/bugs/browser/bugtarget.py	2012-07-05 00:58:19 +0000
@@ -121,7 +121,6 @@
 from lp.registry.enums import (
     InformationType,
     PRIVATE_INFORMATION_TYPES,
-    PUBLIC_INFORMATION_TYPES,
     SECURITY_INFORMATION_TYPES,
     )
 from lp.registry.interfaces.distribution import IDistribution
@@ -260,7 +259,10 @@
     @property
     def field_names(self):
         """Return the list of field names to display."""
-        return ['information_type']
+        if self.is_bug_supervisor:
+            return ['information_type']
+        else:
+            return ['security_related']
 
     custom_widget('information_type', LaunchpadRadioWidgetWithDescription)
 
@@ -298,11 +300,17 @@
         """Set up the form fields. See `LaunchpadFormView`."""
         super(FileBugReportingGuidelines, self).setUpFields()
 
-        information_type_field = copy_field(
-            IBug['information_type'], readonly=False,
-            vocabulary=InformationTypeVocabulary(self.context))
-        self.form_fields = self.form_fields.omit('information_type')
-        self.form_fields += Fields(information_type_field)
+        if self.is_bug_supervisor:
+            information_type_field = copy_field(
+                IBug['information_type'], readonly=False,
+                vocabulary=InformationTypeVocabulary(self.context))
+            self.form_fields = self.form_fields.omit('information_type')
+            self.form_fields += Fields(information_type_field)
+        else:
+            security_related_field = copy_field(
+                IBug['security_related'], readonly=False)
+            self.form_fields = self.form_fields.omit('security_related')
+            self.form_fields += Fields(security_related_field)
 
     @property
     def initial_values(self):
@@ -360,6 +368,19 @@
         else:
             return self.context
 
+    @cachedproperty
+    def is_bug_supervisor(self):
+        """ Return True if the logged in user is a bug supervisor.
+
+        If the main context doesn't have a bug supervisor set, return True if
+        the user is a maintainer.
+        This check allows authorised users to set the specific information type
+        when filing a bug.
+        """
+        context = self.getMainContext()
+        return BugTask.userHasBugSupervisorPrivilegesContext(
+            context, self.user)
+
 
 class FileBugViewBase(FileBugReportingGuidelines, LaunchpadFormView):
     """Base class for views related to filing a bug."""
@@ -435,9 +456,14 @@
     def field_names(self):
         """Return the list of field names to display."""
         context = self.context
-        field_names = ['title', 'comment', 'tags', 'information_type',
+        field_names = ['title', 'comment', 'tags']
+        if self.is_bug_supervisor:
+            field_names.append('information_type')
+        else:
+            field_names.append('security_related')
+        field_names.extend([
             'bug_already_reported_as', 'filecontent', 'patch',
-            'attachment_description', 'subscribe_to_existing_bug']
+            'attachment_description', 'subscribe_to_existing_bug'])
         if (IDistribution.providedBy(context) or
             IDistributionSourcePackage.providedBy(context)):
             field_names.append('packagename')
@@ -453,9 +479,7 @@
         # selected project supports them.
         include_extra_fields = IProjectGroup.providedBy(context)
         if not include_extra_fields:
-            include_extra_fields = (
-                BugTask.userHasBugSupervisorPrivilegesContext(
-                    context, self.user))
+            include_extra_fields = self.is_bug_supervisor
 
         if include_extra_fields:
             field_names.extend(
@@ -612,6 +636,7 @@
         packagename = data.get("packagename")
         information_type = data.get(
             "information_type", InformationType.PUBLIC)
+        security_related = data.get("security_related", False)
         distribution = data.get(
             "distribution", getUtility(ILaunchBag).distribution)
 
@@ -630,6 +655,14 @@
         if self.request.form.get("packagename_option") == "none":
             packagename = None
 
+        if not self.is_bug_supervisor:
+            # If the old UI is enabled, security bugs are always embargoed
+            # when filed, but can be disclosed after they've been reported.
+            if security_related:
+                information_type = InformationType.EMBARGOEDSECURITY
+            else:
+                information_type = InformationType.PUBLIC
+
         linkified_ack = structured(FormattersAPI(
             self.getAcknowledgementMessage(self.context)).text_to_html(
                 last_paragraph_class="last"))
@@ -665,12 +698,12 @@
             notifications.append(
                 'Additional information was added to the bug description.')
 
-        if extra_data.private:
-            if params.information_type in PUBLIC_INFORMATION_TYPES:
+        if not self.is_bug_supervisor and extra_data.private:
+            if params.information_type == InformationType.PUBLIC:
                 params.information_type = InformationType.USERDATA
 
         # Apply any extra options given by privileged users.
-        if BugTask.userHasBugSupervisorPrivilegesContext(context, self.user):
+        if self.is_bug_supervisor:
             if 'assignee' in data:
                 params.assignee = data['assignee']
             if 'status' in data:

=== modified file 'lib/lp/bugs/browser/tests/test_bugtarget_filebug.py'
--- lib/lp/bugs/browser/tests/test_bugtarget_filebug.py	2012-06-20 05:25:44 +0000
+++ lib/lp/bugs/browser/tests/test_bugtarget_filebug.py	2012-07-05 00:58:19 +0000
@@ -421,6 +421,19 @@
         for info_type in InformationType:
             self.assertIsNotNone(soup.find('label', text=info_type.title))
 
+    def test_filebug_view_renders_info_type_widget(self):
+        # The info type widget is rendered for bug supervisor roles.
+        product = self.factory.makeProduct(official_malone=True)
+        with person_logged_in(product.owner):
+            view = create_initialized_view(
+                product, '+filebug', principal=product.owner)
+            html = view.render()
+            soup = BeautifulSoup(html)
+        self.assertIsNone(
+            soup.find('input', attrs={'name': 'field.security_related'}))
+        self.assertIsNotNone(
+            soup.find('input', attrs={'name': 'field.information_type'}))
+
     def test_filebug_information_type_vocabulary_private_projects(self):
         # The vocabulary for information_type when filing a bug only has
         # private info types for private bug projects.
@@ -437,6 +450,69 @@
             self.assertIsNone(soup.find('label', text=info_type.title))
 
 
+class TestFileBugForNonBugSupervisors(TestCaseWithFactory):
+
+    layer = DatabaseFunctionalLayer
+
+    def filebug_via_view(self, private_bugs=False, security_related=False):
+        form = {
+            'field.title': 'A bug',
+            'field.comment': 'A comment',
+            'field.security_related': 'on' if security_related else '',
+            'field.actions.submit_bug': 'Submit Bug Request',
+        }
+        product = self.factory.makeProduct(official_malone=True)
+        if private_bugs:
+            removeSecurityProxy(product).private_bugs = True
+        anyone = self.factory.makePerson()
+        with person_logged_in(anyone):
+            view = create_initialized_view(
+                product, '+filebug', form=form, principal=anyone)
+            bug_url = view.request.response.getHeader('Location')
+            bug_number = bug_url.split('/')[-1]
+            return getUtility(IBugSet).getByNameOrID(bug_number)
+
+    def test_filebug_non_security_related(self):
+        # Non security related bugs are PUBLIC for products with
+        # private_bugs=False.
+        bug = self.filebug_via_view()
+        self.assertEqual(InformationType.PUBLIC, bug.information_type)
+
+    def test_filebug_security_related(self):
+        # Security related bugs are EMBARGOEDSECURITY for products with
+        # private_bugs=False.
+        bug = self.filebug_via_view(security_related=True)
+        self.assertEqual(
+            InformationType.EMBARGOEDSECURITY, bug.information_type)
+
+    def test_filebug_security_related_with_private_bugs(self):
+        # Security related bugs are EMBARGOEDSECURITY for products with
+        # private_bugs=True.
+        bug = self.filebug_via_view(private_bugs=True, security_related=True)
+        self.assertEqual(
+            InformationType.EMBARGOEDSECURITY, bug.information_type)
+
+    def test_filebug_with_private_bugs(self):
+        # Non security related bugs are USERDATA for products with
+        # private_bugs=True.
+        bug = self.filebug_via_view(private_bugs=True)
+        self.assertEqual(InformationType.USERDATA, bug.information_type)
+
+    def test_filebug_view_renders_security_related(self):
+        # The security_related checkbox is rendered for non bug supervisors.
+        product = self.factory.makeProduct(official_malone=True)
+        anyone = self.factory.makePerson()
+        with person_logged_in(anyone):
+            view = create_initialized_view(
+                product, '+filebug', principal=anyone)
+            html = view.render()
+            soup = BeautifulSoup(html)
+        self.assertIsNotNone(
+            soup.find('input', attrs={'name': 'field.security_related'}))
+        self.assertIsNone(
+            soup.find('input', attrs={'name': 'field.information_type'}))
+
+
 class TestFileBugSourcePackage(TestCaseWithFactory):
 
     layer = DatabaseFunctionalLayer

=== modified file 'lib/lp/bugs/javascript/filebug.js'
--- lib/lp/bugs/javascript/filebug.js	2012-06-27 14:05:07 +0000
+++ lib/lp/bugs/javascript/filebug.js	2012-07-05 00:58:19 +0000
@@ -29,8 +29,13 @@
             search_button.set('value', 'Check again');
         }
         setup_information_type();
+        setup_security_related();
         setupChoiceWidgets();
+        set_default_privacy_banner();
     }
+};
+
+var set_default_privacy_banner = function() {
     var filebug_privacy_text = "This report will be private. " +
         "You can disclose it later.";
     update_privacy_banner(
@@ -65,6 +70,9 @@
 
 var setup_information_type = function() {
     var itypes_table = Y.one('.radio-button-widget');
+    if (!Y.Lang.isValue(itypes_table)) {
+        return;
+    }
     itypes_table.delegate('change', function() {
         var banner_text = get_new_banner_text(this.get('value'));
         var private_type = (Y.Array.indexOf(
@@ -82,6 +90,25 @@
         'information_type', LP.cache.information_type_data, true);
 };
 
+var setup_security_related = function() {
+    var security_related = Y.one('[id="field.security_related"]');
+    if (!Y.Lang.isValue(security_related)) {
+        return;
+    }
+    var notification_text = "This report will be private " +
+                           "because it is a security " +
+                           "vulnerability. You can " +
+                           "disclose it later.";
+    security_related.on('change', function() {
+        var checked = security_related.get('checked');
+        if (checked) {
+            update_privacy_banner(true, notification_text);
+        } else {
+            set_default_privacy_banner();
+        }
+    });
+};
+
 namespace.setup_filebug = setup_filebug;
 
 }, "0.1", {"requires": [

=== modified file 'lib/lp/bugs/javascript/tests/test_filebug.html'
--- lib/lp/bugs/javascript/tests/test_filebug.html	2012-06-15 01:13:39 +0000
+++ lib/lp/bugs/javascript/tests/test_filebug.html	2012-07-05 00:58:19 +0000
@@ -67,7 +67,7 @@
         </ul>
         <div class='login-logout'></div>
         <div id="fixture"></div>
-        <script type="text/x-template" id="privacy-banner-template">
+        <script type="text/x-template" id="bugsupervisor-filebug-template">
         <div id="filebug-form">
         <table class="radio-button-widget">
             <tbody>
@@ -117,5 +117,27 @@
         </div>
         </div>
         </script>
+        <script type="text/x-template" id="filebug-template">
+        <div id="filebug-form">
+        <div>
+          <input type="checkbox" value="on" name="field.security_related" id="field.security_related" class="checkboxType">
+          <label for="field.security_related">
+            This bug is a security vulnerability
+          </label>
+        </div>
+        <div class="value">
+            <select size="1" name="field.status" id="field.status">
+            <option value="New" selected="selected">New</option>
+            <option value="Incomplete">Incomplete</option>
+            </select>
+        </div>
+        <div class="value">
+            <select size="1" name="field.importance" id="field.importance">
+            <option value="Undecided" selected="selected">Undecided</option>
+            <option value="High">High</option>
+            </select>
+        </div>
+        </div>
+        </script>
     </body>
 </html>

=== modified file 'lib/lp/bugs/javascript/tests/test_filebug.js'
--- lib/lp/bugs/javascript/tests/test_filebug.js	2012-07-03 01:35:50 +0000
+++ lib/lp/bugs/javascript/tests/test_filebug.js	2012-07-05 00:58:19 +0000
@@ -33,17 +33,24 @@
                     ]
                 }
             };
+        },
+
+        setupForm: function(bugsupervisor_version) {
             this.fixture = Y.one('#fixture');
-            var banner = Y.Node.create(
-                    Y.one('#privacy-banner-template').getContent());
-            this.fixture.appendChild(banner);
+            var form_id = 'filebug-template';
+            if (bugsupervisor_version) {
+                form_id = 'bugsupervisor-' + form_id;
+            }
+            var form = Y.Node.create(Y.one('#' + form_id).getContent());
+            this.fixture.appendChild(form);
+            Y.lp.bugs.filebug.setup_filebug(true);
         },
 
         tearDown: function () {
-            if (this.fixture !== null) {
+            if (Y.Lang.isValue(this.fixture)) {
                 this.fixture.empty(true);
+                delete this.fixture;
             }
-            delete this.fixture;
             delete window.LP;
         },
 
@@ -55,7 +62,7 @@
 
         // Filing a public bug does not show the privacy banner.
         test_setup_filebug_public: function () {
-            Y.lp.bugs.filebug.setup_filebug(true);
+            this.setupForm(true);
             var banner_hidden = Y.one('.yui3-privacybanner-hidden');
             Y.Assert.isNotNull(banner_hidden);
         },
@@ -64,7 +71,7 @@
         // banner.
         test_setup_filebug_private: function () {
             window.LP.cache.bug_private_by_default = true;
-            Y.lp.bugs.filebug.setup_filebug(true);
+            this.setupForm(true);
             var banner_hidden = Y.one('.yui3-privacybanner-hidden');
             Y.Assert.isNull(banner_hidden);
             var banner_text = Y.one('.banner-text').get('text');
@@ -76,7 +83,7 @@
         // Selecting a private info type using the legacy radio buttons
         // turns on the privacy banner.
         test_legacy_select_private_info_type: function () {
-            Y.lp.bugs.filebug.setup_filebug(true);
+            this.setupForm(true);
             var banner_hidden = Y.one('.yui3-privacybanner-hidden');
             Y.Assert.isNotNull(banner_hidden);
             Y.one('[id="field.information_type.2"]').simulate('click');
@@ -92,7 +99,7 @@
         // turns off the privacy banner.
         test_legacy_select_public_info_type: function () {
             window.LP.cache.bug_private_by_default = true;
-            Y.lp.bugs.filebug.setup_filebug(true);
+            this.setupForm(true);
             Y.one('[id="field.information_type.2"]').simulate('click');
             var banner_hidden = Y.one('.yui3-privacybanner-hidden');
             Y.Assert.isNull(banner_hidden);
@@ -101,6 +108,52 @@
             Y.Assert.isNotNull(banner_hidden);
         },
 
+        // When non bug supervisors select a security related bug the privacy
+        // banner is turned on.
+        test_select_security_related: function () {
+            this.setupForm(false);
+            var banner_hidden = Y.one('.yui3-privacybanner-hidden');
+            Y.Assert.isNotNull(banner_hidden);
+            Y.one('[id="field.security_related"]').simulate('click');
+            banner_hidden = Y.one('.yui3-privacybanner-hidden');
+            Y.Assert.isNull(banner_hidden);
+            var banner_text = Y.one('.banner-text').get('text');
+            Y.Assert.areEqual(
+                'This report will be private because it is a ' +
+                'security vulnerability. You can disclose it later.',
+                banner_text);
+        },
+
+        // When non bug supervisors unselect a security related bug the privacy
+        // banner is turned off.
+        test_unselect_security_related: function () {
+            this.setupForm(false);
+            Y.one('[id="field.security_related"]').simulate('click');
+            var banner_hidden = Y.one('.yui3-privacybanner-hidden');
+            Y.Assert.isNull(banner_hidden);
+            Y.one('[id="field.security_related"]').simulate('click');
+            banner_hidden = Y.one('.yui3-privacybanner-hidden');
+            Y.Assert.isNotNull(banner_hidden);
+        },
+
+        // When non bug supervisors unselect a security related bug the privacy
+        // banner remains on for private_by_default bugs.
+        test_unselect_security_related_default_private: function () {
+            window.LP.cache.bug_private_by_default = true;
+            this.setupForm(false);
+            Y.one('[id="field.security_related"]').simulate('click');
+            var banner_hidden = Y.one('.yui3-privacybanner-hidden');
+            Y.Assert.isNull(banner_hidden);
+            Y.one('[id="field.security_related"]').simulate('click');
+            banner_hidden = Y.one('.yui3-privacybanner-hidden');
+            Y.Assert.isNull(banner_hidden);
+            var banner_text = Y.one('.banner-text').get('text');
+            Y.Assert.areEqual(
+                'This report will be private. ' +
+                'You can disclose it later.',
+                banner_text);
+        },
+
         // The dupe finder functionality is setup.
         test_dupe_finder_setup: function () {
             window.LP.cache.enable_bugfiling_duplicate_search = true;
@@ -116,7 +169,7 @@
             Y.lp.bugs.filebug_dupefinder.setup_dupes = function() {
                 setup_dupes_called = true;
             };
-            Y.lp.bugs.filebug.setup_filebug(true);
+            this.setupForm(true);
             Y.Assert.isTrue(setup_dupe_finder_called);
             Y.Assert.isTrue(setup_dupes_called);
             Y.lp.bugs.filebug_dupefinder.setup_dupes = orig_setup_dupes;
@@ -126,7 +179,7 @@
 
         // The bugtask status choice popup is rendered.
         test_status_setup: function () {
-            Y.lp.bugs.filebug.setup_filebug(true);
+            this.setupForm(true);
             var status_node = Y.one('.status-content .value');
             Y.Assert.areEqual('New', status_node.get('text'));
             var status_edit_node = Y.one('.status-content a.sprite.edit');
@@ -135,9 +188,7 @@
             Y.Assert.isTrue(legacy_dropdown.hasClass('unseen'));
         },
 
-        // The bugtask importance choice popup is rendered.
-        test_importance_setup: function () {
-            Y.lp.bugs.filebug.setup_filebug(true);
+        _perform_test_importance: function() {
             var importance_node = Y.one('.importance-content .value');
             Y.Assert.areEqual('Undecided', importance_node.get('text'));
             var importance_edit_node =
@@ -147,17 +198,24 @@
             Y.Assert.isTrue(legacy_dropdown.hasClass('unseen'));
         },
 
+        // The bugtask importance choice popup is rendered.
+        test_importance_setup: function () {
+            this.setupForm(true);
+            this._perform_test_importance();
+        },
+
         // The choice popup wiring works even if the field is missing.
         // This is so fields which the user does not have permission to see
         // can be missing and everything still works as expected.
         test_missing_fields: function () {
+            this.setupForm(true);
             Y.one('[id="field.status"]').remove(true);
-            this.test_importance_setup();
+            this._perform_test_importance();
         },
 
         // The bugtask status choice popup updates the form.
         test_status_selection: function() {
-            Y.lp.bugs.filebug.setup_filebug(true);
+            this.setupForm(true);
             var status_popup = Y.one('.status-content a');
             status_popup.simulate('click');
             var status_choice = Y.one(
@@ -169,7 +227,7 @@
 
         // The bugtask importance choice popup updates the form.
         test_importance_selection: function() {
-            Y.lp.bugs.filebug.setup_filebug(true);
+            this.setupForm(true);
             var status_popup = Y.one('.importance-content a');
             status_popup.simulate('click');
             var status_choice = Y.one(
@@ -181,7 +239,7 @@
 
         // The bugtask information_type choice popup is rendered.
         test_information_type_setup: function () {
-            Y.lp.bugs.filebug.setup_filebug(true);
+            this.setupForm(true);
             var information_type_node =
                 Y.one('.information_type-content .value');
             Y.Assert.areEqual('Public', information_type_node.get('text'));
@@ -194,7 +252,7 @@
 
         // The bugtask information_type choice popup updates the form.
         test_information_type_selection: function() {
-            Y.lp.bugs.filebug.setup_filebug(true);
+            this.setupForm(true);
             var information_type_popup = Y.one('.information_type-content a');
             information_type_popup.simulate('click');
             var header_text =
@@ -211,7 +269,7 @@
         // Selecting a private info type using the popup choice widget
         // turns on the privacy banner.
         test_select_private_info_type: function () {
-            Y.lp.bugs.filebug.setup_filebug(true);
+            this.setupForm(true);
             var banner_hidden = Y.one('.yui3-privacybanner-hidden');
             Y.Assert.isNotNull(banner_hidden);
             var information_type_popup = Y.one('.information_type-content a');
@@ -229,7 +287,7 @@
 
         test_select_private_info_type_with_private_flag: function () {
             window.LP.cache.show_userdata_as_private = true;
-            Y.lp.bugs.filebug.setup_filebug(true);
+            this.setupForm(true);
             var banner_hidden = Y.one('.yui3-privacybanner-hidden');
             Y.Assert.isNotNull(banner_hidden);
             var information_type_popup = Y.one('.information_type-content a');
@@ -248,7 +306,7 @@
         // turns off the privacy banner.
         test_select_public_info_type: function () {
             window.LP.cache.bug_private_by_default = true;
-            Y.lp.bugs.filebug.setup_filebug(true);
+            this.setupForm(true);
             var information_type_popup = Y.one('.information_type-content a');
             information_type_popup.simulate('click');
             var information_type_choice = Y.one(

=== modified file 'lib/lp/bugs/templates/bugtarget-filebug-guidelines.pt'
--- lib/lp/bugs/templates/bugtarget-filebug-guidelines.pt	2012-06-20 05:25:44 +0000
+++ lib/lp/bugs/templates/bugtarget-filebug-guidelines.pt	2012-07-05 00:58:19 +0000
@@ -11,6 +11,7 @@
   </tr>
 
   <tr tal:define="security_context view/getMainContext">
+  <tal:information_type tal:condition="view/is_bug_supervisor">
     <td colspan="2" width="100%"
         tal:define="widget nocall: view/widgets/information_type|nothing"
         tal:condition="widget">
@@ -19,6 +20,32 @@
       </label>
       <input tal:replace="structure widget" />
     </td>
+  </tal:information_type>
+  <tal:security_related tal:condition="not: view/is_bug_supervisor">
+      <td colspan="2" width="100%"
+        tal:define="widget nocall: view/widgets/security_related|nothing"
+        tal:condition="widget">
+      <table>
+        <tbody>
+          <tr>
+            <td>
+              <input type="checkbox" tal:replace="structure widget" />
+            </td>
+            <td>
+              <label tal:attributes="for widget/name">
+                This bug is a security vulnerability
+              </label>
+              <div>
+                  The security group for
+                  <tal:security-context content="security_context/displayname" />
+                  will be notified.
+              </div>
+            </td>
+          </tr>
+        </tbody>
+      </table>
+    </td>
+  </tal:security_related>
   </tr>
   </tbody></table></td></tr>
 </tal:root>


Follow ups