Merge "Allow marking stories as security-related"
This commit is contained in:
commit
b4673f8b3f
|
@ -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);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
};
|
||||
|
||||
/**
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
};
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
};
|
||||
}
|
||||
);
|
|
@ -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> 
|
||||
<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> 
|
||||
<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> 
|
||||
{{ 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> 
|
||||
{{user.full_name}}
|
||||
<a class="close"
|
||||
ng-click="removeUser(user)"
|
||||
|
|
|
@ -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> 
|
||||
{{ 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> 
|
||||
{{user.full_name}}
|
||||
<a class="close"
|
||||
ng-click="removeUser(user)"
|
||||
|
|
|
@ -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> 
|
||||
<strong>This story is private</strong>.
|
||||
</p>
|
||||
<p ng-show="story.security">
|
||||
<i class="fa fa-fw fa-lock"></i> 
|
||||
<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> 
|
||||
{{ 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> 
|
||||
{{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"
|
||||
|
|
Loading…
Reference in New Issue