Merge "Allow marking stories as security-related"

This commit is contained in:
Zuul 2019-06-07 23:10:05 +00:00 committed by Gerrit Code Review
commit b4673f8b3f
8 changed files with 227 additions and 43 deletions

View File

@ -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');
@ -385,6 +385,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.
*/
@ -660,6 +670,7 @@ angular.module('sb.story').controller('StoryDetailController',
branch.tasks.push(savedTask);
} else {
mapTaskToProject(savedTask);
$scope.updateSecurity(false, true);
}
$scope.loadEvents();
task.title = '';
@ -721,6 +732,7 @@ angular.module('sb.story').controller('StoryDetailController',
cleanBranchAndProject(projectName, branchName);
mapTaskToProject(updated);
$scope.updateSecurity(false, true);
}
});
}

View File

@ -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);
};
/**

View File

@ -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);
}
};

View File

@ -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

View File

@ -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
};
}
);

View File

@ -17,12 +17,19 @@
-->
<div class="container-fluid">
<div class="row">
<div class="row" ng-show="story.private && !showEditForm">
<div class="col-xs-12">
<div class="alert alert-danger" ng-show="story.private">
<i class="fa fa-eye-slash"></i>
<strong>This story is private</strong>.
Edit this story to change the privacy settings.
<div class="alert alert-danger">
<p>
<i class="fa fa-fw fa-eye-slash"></i>&emsp;
<strong>This story is private</strong>.
Edit this story to change the privacy settings.
</p>
<p ng-show="story.security">
<i class="fa fa-fw fa-lock"></i>&emsp;
<strong>This story is security-related</strong>.
Security Teams related to any affected projects will be automatically added to the story.
</p>
</div>
</div>
</div>
@ -183,6 +190,20 @@
type="checkbox"
class="modal-checkbox"
ng-model="story.private"
ng-disabled="isUpdating || privacyLocked"
/>
</div>
</div>
<div class="form-group">
<label for="security" class="col-sm-2 control-label">
Vulnerability or Security-related
</label>
<div class="col-sm-10 checkbox">
<input id="security"
type="checkbox"
class="modal-checkbox"
ng-model="story.security"
ng-change="updateSecurity(false)"
ng-disabled="isUpdating"
/>
</div>
@ -209,7 +230,7 @@
<tbody>
<tr ng-repeat="team in story.teams">
<td colspan="2">
<i class="fa fa-sb-team"></i>
<i class="fa fa-fw fa-sb-team text-muted"></i>&emsp;
{{ team.name }}
<a class="close"
ng-click="removeTeam(team)"
@ -220,7 +241,7 @@
</tr>
<tr ng-repeat="user in story.users">
<td colspan="2">
<i class="fa fa-sb-user"></i>
<i class="fa fa-fw fa-sb-user text-muted"></i>&emsp;
{{user.full_name}}
<a class="close"
ng-click="removeUser(user)"

View File

@ -49,20 +49,40 @@
</textarea>
</div>
</div>
<div class="form-group">
<label for="private"
class="col-sm-2 control-label">
Private or Security Vulnerability
</label>
<div class="col-sm-10 checkbox">
<input id="private"
type="checkbox"
class="modal-checkbox"
ng-model="story.private"
ng-disabled="isSaving"
/>
<div class="form-group" ng-if="!forcePrivate">
<label for="private" class="col-sm-2 control-label">
Private
</label>
<div class="col-sm-10">
<div class="checkbox" ng-show="!privacyLocked">
<input id="private"
type="checkbox"
class="modal-checkbox"
ng-model="story.private"
ng-disabled="isSaving || privacyLocked"
/>
</div>
<div class="checkbox help-block"
ng-show="privacyLocked">
Security-related stories are always private when created.
If privacy is really not required, the story can be edited
after creation to be made public.
</div>
</div>
</div>
<div class="form-group" ng-if="!forcePrivate">
<label for="security" class="col-sm-2 control-label">
Vulnerability or Security-related
</label>
<div class="col-sm-10 checkbox">
<input id="security"
type="checkbox"
class="modal-checkbox"
ng-model="story.security"
ng-change="updateSecurity(true, false)"
ng-disabled="isSaving"
/>
</div>
</div>
<div class="row">
<div class="col-md-6 col-md-offset-3"
@ -70,8 +90,7 @@
<table class="table table-striped">
<thead>
<tr>
<th>Teams and Users that can see this story. If it is a security vulnerability,
add the associated teams, e.g. vmt, $project coresec, etc.</th>
<th>Teams and Users that can see this story</th>
<th class="text-right">
<small>
<a href
@ -87,7 +106,7 @@
<tbody>
<tr ng-repeat="team in story.teams">
<td colspan="2">
<i class="fa fa-sb-team"></i>
<i class="fa fa-fw fa-sb-team text-muted"></i>&emsp;
{{ team.name }}
<a class="close"
ng-click="removeTeam(team)"
@ -98,7 +117,7 @@
</tr>
<tr ng-repeat="user in story.users">
<td colspan="2">
<i class="fa fa-sb-user"></i>
<i class="fa fa-fw fa-sb-user text-muted"></i>&emsp;
{{user.full_name}}
<a class="close"
ng-click="removeUser(user)"

View File

@ -20,8 +20,15 @@
<div class="row">
<div class="col-xs-12">
<div class="alert alert-danger" ng-show="story.private">
<i class="fa fa-eye-slash"></i>
<strong>This story is private</strong>.
<p>
<i class="fa fa-fw fa-eye-slash"></i>&emsp;
<strong>This story is private</strong>.
</p>
<p ng-show="story.security">
<i class="fa fa-fw fa-lock"></i>&emsp;
<strong>This story is security-related</strong>.
Security Teams related to any affected projects will be automatically added to the story.
</p>
</div>
</div>
</div>
@ -131,13 +138,35 @@
</div>
<div class="form-group" ng-if="!forcePrivate">
<label for="private" class="col-sm-2 control-label">
Private or Security Vulnerability
Private
</label>
<div class="col-sm-10">
<div class="checkbox" ng-show="!privacyLocked">
<input id="private"
type="checkbox"
class="modal-checkbox"
ng-model="story.private"
ng-disabled="isUpdating || privacyLocked"
/>
</div>
<div class="checkbox help-block"
ng-show="privacyLocked">
Security-related stories are always private when created.
If privacy is really not required, the story can be edited
after creation to be made public.
</div>
</div>
</div>
<div class="form-group" ng-if="!forcePrivate">
<label for="security" class="col-sm-2 control-label">
Vulnerability or Security-related
</label>
<div class="col-sm-10 checkbox">
<input id="private"
<input id="security"
type="checkbox"
class="modal-checkbox"
ng-model="story.private"
ng-model="story.security"
ng-change="updateSecurity(true, false)"
ng-disabled="isUpdating"
/>
</div>
@ -148,8 +177,7 @@
<table class="table table-striped">
<thead>
<tr>
<th>Teams and Users that can see this story. If it is a security vulnerability,
add the associated teams, e.g. vmt, $project coresec, etc.</th>
<th>Teams and Users that can see this story</th>
<th class="text-right">
<small>
<a href
@ -165,7 +193,7 @@
<tbody>
<tr ng-repeat="team in story.teams">
<td colspan="2">
<i class="fa fa-sb-team"></i>
<i class="fa fa-fw fa-sb-team text-muted"></i>&emsp;
{{ team.name }}
<a class="close"
ng-click="removeTeam(team)"
@ -176,7 +204,7 @@
</tr>
<tr ng-repeat="user in story.users">
<td colspan="2">
<i class="fa fa-sb-user"></i>
<i class="fa fa-fw fa-sb-user text-muted"></i>&emsp;
{{user.full_name}}
<a class="close"
ng-click="removeUser(user)"
@ -368,7 +396,7 @@
empty-prompt="Click to set a project"
empty-disabled-prompt="No project set."
as-inline="false"
placeholder="Enter project name">
placeholder="Type and select project">
</project-typeahead>
<button class="btn btn-xs btn-default"
ng-show="isLoggedIn"
@ -484,7 +512,8 @@
<div class="col-xs-2 text-right col-xs-offset-1">
<button ng-click="createTask(projects[name].branches[branchName].newTask,
projects[name].branches[branchName])"
class="btn btn-primary">
class="btn btn-primary"
ng-disabled="!validTask(projects[name].branches[branchName].newTask)">
Save
</button>
<button ng-click="projects[name].branches[branchName].showAddTask =
@ -521,7 +550,7 @@
empty-prompt="Click to set a project"
empty-disabled-prompt="No project set."
as-inline="false"
placeholder="Enter project name">
placeholder="Type and select project">
</project-typeahead>
</h4>
<div class="row">
@ -572,7 +601,8 @@
<!-- Review link should go here once implemented in the API -->
<div class="col-xs-2 text-right col-xs-offset-1">
<button ng-click="createTask(newTask)"
class="btn btn-primary">
class="btn btn-primary"
ng-disabled="!validTask(newTask)">
Add
</button>
<button ng-click="newTask.show = !newTask.show"