From c4fbf2c89b33446576269a38ffaa16a6ee43146b Mon Sep 17 00:00:00 2001 From: Adam Coldrick Date: Fri, 8 Mar 2019 16:39:48 +0000 Subject: [PATCH] Allow marking stories as security-related This commit adds a checkbox when creating or editing stories which allows them to be explicitly marked as security-related. It also rewords the label for the privacy checkbox to avoid any confusion. When creating a security-related story, privacy is enforced and the checkbox disabled and replaced with a message explaining that. Public security-related stories can be created by editing a story after the fact to make it public, since there is no limitation on privacy when editing a story. This is intended to make it difficult to accidentally file a security story publically. In order for the checkbox to actually be useful, this commit also adds code which automates the addition of relevant security teams to the story's permissions as tasks are added (or the security checkbox is toggled). Story: 2000568 Task: 29892 Task: 29893 Change-Id: I8a2c6e47f926aedcbe77878f2bc5991cd84f815d --- .../controller/story_detail_controller.js | 14 +++- .../controller/story_modal_controller.js | 13 +++- .../controller/story_new_controller.js | 27 ++++++- src/app/stories/module.js | 2 +- src/app/stories/service/story_helper.js | 70 +++++++++++++++++++ src/app/stories/template/detail.html | 35 ++++++++-- src/app/stories/template/new.html | 53 +++++++++----- src/app/stories/template/new_page.html | 56 +++++++++++---- 8 files changed, 227 insertions(+), 43 deletions(-) create mode 100644 src/app/stories/service/story_helper.js diff --git a/src/app/stories/controller/story_detail_controller.js b/src/app/stories/controller/story_detail_controller.js index 7c85f2ce..0d028a21 100644 --- a/src/app/stories/controller/story_detail_controller.js +++ b/src/app/stories/controller/story_detail_controller.js @@ -24,7 +24,7 @@ angular.module('sb.story').controller('StoryDetailController', Story, Project, Branch, creator, tasks, Task, DSCacheFactory, User, $q, storyboardApiBase, SessionModalService, moment, $document, $anchorScroll, $timeout, $location, currentUser, - enableEditableComments, Tags, worklists, Team) { + enableEditableComments, Tags, worklists, Team, StoryHelper) { 'use strict'; var pageSize = Preference.get('story_detail_page_size'); @@ -362,6 +362,16 @@ angular.module('sb.story').controller('StoryDetailController', $scope.showEditForm = false; }; + $scope.privacyLocked = false; + + /** + * Handle any change to whether or not the story is security-related + */ + $scope.updateSecurity = function(forcePrivate, update) { + $scope.privacyLocked = StoryHelper.updateSecurity( + forcePrivate, update, $scope.story, $scope.tasks); + }; + /** * Delete method. */ @@ -637,6 +647,7 @@ angular.module('sb.story').controller('StoryDetailController', branch.tasks.push(savedTask); } else { mapTaskToProject(savedTask); + $scope.updateSecurity(false, true); } $scope.loadEvents(); task.title = ''; @@ -698,6 +709,7 @@ angular.module('sb.story').controller('StoryDetailController', cleanBranchAndProject(projectName, branchName); mapTaskToProject(updated); + $scope.updateSecurity(false, true); } }); } diff --git a/src/app/stories/controller/story_modal_controller.js b/src/app/stories/controller/story_modal_controller.js index 5701ee26..54f72547 100644 --- a/src/app/stories/controller/story_modal_controller.js +++ b/src/app/stories/controller/story_modal_controller.js @@ -19,7 +19,7 @@ */ angular.module('sb.story').controller('StoryModalController', function ($scope, $modalInstance, params, Project, Story, Task, User, - Team, $q, CurrentUser) { + Team, $q, CurrentUser, StoryHelper) { 'use strict'; var currentUser = CurrentUser.resolve(); @@ -38,6 +38,15 @@ angular.module('sb.story').controller('StoryModalController', title: '' })]; + + /** + * Handle any change to whether or not the story is security-related + */ + $scope.updateSecurity = function(forcePrivate, update) { + $scope.privacyLocked = StoryHelper.updateSecurity( + forcePrivate, update, $scope.story, $scope.tasks); + }; + // Preload the project if (params.projectId) { Project.get({ @@ -129,6 +138,7 @@ angular.module('sb.story').controller('StoryModalController', }); } $scope.tasks.push(current_task); + $scope.updateSecurity(true, false); }; /** @@ -165,6 +175,7 @@ angular.module('sb.story').controller('StoryModalController', */ $scope.selectNewProject = function (model, task) { task.project_id = model.id; + $scope.updateSecurity(true, false); }; /** diff --git a/src/app/stories/controller/story_new_controller.js b/src/app/stories/controller/story_new_controller.js index 7aea210f..182e9326 100644 --- a/src/app/stories/controller/story_new_controller.js +++ b/src/app/stories/controller/story_new_controller.js @@ -28,6 +28,7 @@ * (i.e. private with the option to change privacy hidden). * - private: If truthy, the story will begin set to private. * Unlike force_private, this allows the user to change to public. + * - security: If truthy, the story will begin set as security-related. * - tags: Tags to set on the story. Can be given multiple times. * - team_id: A team ID to grant permissions for this story to. Can be * given multiple times. @@ -36,13 +37,25 @@ */ angular.module('sb.story').controller('StoryNewController', function ($scope, $state, $stateParams, Story, Project, Branch, Tags, - Task, Team, User, $q, storyboardApiBase, currentUser) { + Task, Team, User, StoryHelper, $q, storyboardApiBase, + currentUser) { 'use strict'; + /** + * Handle any change to whether or not the story is security-related + */ + $scope.updateSecurity = function(forcePrivate, update) { + $scope.privacyLocked = StoryHelper.updateSecurity( + forcePrivate, update, $scope.story, $scope.tasks); + }; + var story = new Story({ title: $stateParams.title, description: $stateParams.description, - private: !!$stateParams.private || !!$stateParams.force_private, + private: (!!$stateParams.private || + !!$stateParams.force_private || + !!$stateParams.security), + security: !!$stateParams.security, users: [currentUser], teams: [] }); @@ -99,6 +112,7 @@ angular.module('sb.story').controller('StoryNewController', $scope.projectNames = []; $scope.projects = {}; $scope.tasks = []; + $scope.updateSecurity(true, false); /** * UI flag for when we're initially loading the view. @@ -248,7 +262,8 @@ angular.module('sb.story').controller('StoryNewController', $scope.newTask = new Task({ project_id: $stateParams.project_id, show: true, - status: 'todo' + status: 'todo', + title: $stateParams.title }); function mapTaskToProject(task) { @@ -286,6 +301,10 @@ angular.module('sb.story').controller('StoryNewController', }); } + $scope.validTask = function(task) { + return !isNaN(task.project_id) && !!task.title; + }; + /** * Adds a task. */ @@ -305,6 +324,7 @@ angular.module('sb.story').controller('StoryNewController', }); } $scope.tasks.push(savedTask); + $scope.updateSecurity(true, false); task.title = ''; }; @@ -358,6 +378,7 @@ angular.module('sb.story').controller('StoryNewController', cleanBranchAndProject(projectName, branchName); mapTaskToProject(task); + $scope.updateSecurity(true, false); } }; diff --git a/src/app/stories/module.js b/src/app/stories/module.js index f452252e..f8b5dfb5 100644 --- a/src/app/stories/module.js +++ b/src/app/stories/module.js @@ -32,7 +32,7 @@ angular.module('sb.story', ['ui.router', 'sb.services', 'sb.util', + 'project_id&assignee_id'; var creationParams = 'title&description&project_id&' - + 'private&force_private&tags&team_id&user_id'; + + 'private&force_private&security&tags&team_id&user_id'; // Set our page routes. $stateProvider diff --git a/src/app/stories/service/story_helper.js b/src/app/stories/service/story_helper.js new file mode 100644 index 00000000..5e46ba72 --- /dev/null +++ b/src/app/stories/service/story_helper.js @@ -0,0 +1,70 @@ +/* + * Copyright (c) 2019 Codethink Ltd. + * + * Licensed under the Apache License, Version 2.0 (the 'License'); you may + * not use this file except in compliance with the License. You may obtain + * a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an 'AS IS' BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ + +angular.module('sb.story').factory('StoryHelper', + function(Story, Team) { + 'use strict'; + + function updateSecurity(forcePrivate, update, story, tasks) { + var privacyLocked; + if (story.security) { + if (forcePrivate) { + story.private = true; + privacyLocked = true; + } + + // Add security teams for affected projects + var projects = tasks.map(function(task) { + return task.project_id; + }).filter(function(value) { + // Remove any unset project_ids we've somehow got. + // Otherwise, the browse will return all teams, rather + // than only relevant teams. + return !isNaN(value); + }); + angular.forEach(projects, function(project_id) { + Team.browse({project_id: project_id}, function(teams) { + var teamIds = story.teams.map(function(team) { + return team.id; + }); + teams = teams.filter(function(team) { + return ((teamIds.indexOf(team.id) === -1) + && team.security); + }); + angular.forEach(teams, function(team) { + story.teams.push(team); + if (update) { + Story.TeamsController.create({ + story_id: story.id, + team_id: team.id + }); + } + }); + }); + }); + } else { + if (forcePrivate) { + privacyLocked = false; + } + } + return privacyLocked; + } + + return { + updateSecurity: updateSecurity + }; + } +); diff --git a/src/app/stories/template/detail.html b/src/app/stories/template/detail.html index e1141b6b..141b4ee1 100644 --- a/src/app/stories/template/detail.html +++ b/src/app/stories/template/detail.html @@ -17,12 +17,19 @@ -->
-
+
-
- - This story is private. - Edit this story to change the privacy settings. +
+

+   + This story is private. + Edit this story to change the privacy settings. +

+

+   + This story is security-related. + Security Teams related to any affected projects will be automatically added to the story. +

@@ -183,6 +190,20 @@ type="checkbox" class="modal-checkbox" ng-model="story.private" + ng-disabled="isUpdating || privacyLocked" + /> +
+
+
+ +
+
@@ -209,7 +230,7 @@ - +   {{ team.name }} - +   {{user.full_name}}
-
- - -