Skip to content

Commit

Permalink
failing test for setting project admin role
Browse files Browse the repository at this point in the history
project admins can make project admins.Tests passing

failing test for unsetting project admin role

project admins can demote project admins.Tests passing

amended making and demoting project admin view

updated puma and rack vulns

Added rails vulns to Anchore whitelist

fixing module conflict

Moved project admin’s set_role test to set_role describe block. Project Admins see ‘Admin’ badges for projects of which they are admins, non-admin members see ‘Member’ badges. Refactored projects-list.component. Amended projects-list.component tests.
  • Loading branch information
Rhodine-orleans-lindsay committed Sep 11, 2020
1 parent 2957c94 commit 56fed5a
Show file tree
Hide file tree
Showing 10 changed files with 194 additions and 50 deletions.
2 changes: 1 addition & 1 deletion .drone.yml
Original file line number Diff line number Diff line change
Expand Up @@ -127,7 +127,7 @@ pipeline:
api_scan_image:
image: quay.io/ukhomeofficedigital/anchore-submission:latest
image_name: platform-hub-api:${DRONE_COMMIT_SHA}
whitelist: CVE-2019-5419,CVE-2019-5420,CVE-2020-8130,CVE-2020-11077,CVE-2020-11076,CVE-2020-5247,CVE-2019-16770
whitelist: CVE-2019-5419,CVE-2019-5420,CVE-2020-8130,CVE-2020-11077,CVE-2020-11076,CVE-2020-5247,CVE-2019-16770,CVE-2020-8164,CVE-2020-8165,CVE-2020-8162,CVE-2019-5420
when:
event: [push, tag]

Expand Down
6 changes: 3 additions & 3 deletions platform-hub-api/Gemfile.lock
Original file line number Diff line number Diff line change
Expand Up @@ -183,8 +183,8 @@ GEM
activesupport (>= 4.2)
arel (>= 6)
public_suffix (3.0.3)
puma (3.10.0)
rack (2.0.7)
puma (3.12.6)
rack (2.2.3)
rack-test (0.6.3)
rack (>= 1.0)
rails (5.0.7.2)
Expand Down Expand Up @@ -335,4 +335,4 @@ RUBY VERSION
ruby 2.3.8p459

BUNDLED WITH
1.16.2
1.17.3
6 changes: 6 additions & 0 deletions platform-hub-api/app/models/ability.rb
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,12 @@ def initialize(user)
can :remove_membership, Project do |project|
can_administer_project project, user
end
can :set_role, Project do |project|
can_administer_project project, user
end
can :unset_role, Project do |project|
can_administer_project project, user
end
can :administer_projects, Project do |project|
can_administer_project project, user
end
Expand Down
97 changes: 97 additions & 0 deletions platform-hub-api/spec/controllers/projects_controller_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -638,6 +638,54 @@ def expect_result result

end

context 'not a hub admin but is project admin of same project' do
before do
create :project_membership_as_admin, project: @project, user: current_user
end

context 'when user is not a project team member' do
it 'should return a 400 Bad Request error' do
put :set_role, params: { id: @project.id, user_id: @user.id, role: role }
expect(response).to have_http_status(400)
end
end

context 'when user is a project team member' do
before do
create :project_membership, project: @project, user: @user
end

it 'should set the role accordingly' do
expect(ProjectMembership.exists?(project_id: @project.id, user_id: @user.id, role: role)).to be false
expect(Audit.count).to eq 0
put :set_role, params: { id: @project.id, user_id: @user.id, role: role }
expect(response).to be_success
expect(json_response['role']).to eq role
expect(ProjectMembership.exists?(project_id: @project.id, user_id: @user.id, role: role)).to be true
expect(Audit.count).to eq 1
audit = Audit.first
expect(audit.action).to eq 'set_role'
expect(audit.auditable.id).to eq @project.id
expect(audit.associated.id).to eq @user.id
expect(audit.user.id).to eq current_user_id
expect(audit.data['previous_role']).to eq nil
expect(audit.data['new_role']).to eq role
end
end
end

context 'not a hub admin but is project admin of a different project' do
before do
another_project = create :project
create :project_membership_as_admin, project: another_project, user: current_user
end

it 'should not be able to set the role - returning 403 Forbidden' do
put :set_role, params: { id: @project.id, user_id: @user.id, role: role }
expect(response).to have_http_status(403)
end
end

end
end

Expand Down Expand Up @@ -700,6 +748,55 @@ def expect_result result

end

context 'not a hub admin but is project admin of same project' do
before do
create :project_membership_as_admin, project: @project, user: current_user
end

context 'when user is not a project team member' do
it 'should return a 400 Bad Request error' do
put :unset_role, params: { id: @project.id, user_id: @user.id, role: role }
expect(response).to have_http_status(400)
end
end

context 'when user is a project team member' do
before do
create :project_membership, project: @project, user: @user, role: role
end

it 'should unset the role accordingly' do
expect(ProjectMembership.exists?(project_id: @project.id, user_id: @user.id, role: role)).to be true
expect(Audit.count).to eq 0
delete :unset_role, params: { id: @project.id, user_id: @user.id, role: role }
expect(response).to be_success
expect(json_response['role']).to eq nil
expect(ProjectMembership.exists?(project_id: @project.id, user_id: @user.id, role: role)).to be false
expect(ProjectMembership.exists?(project_id: @project.id, user_id: @user.id, role: nil)).to be true
expect(Audit.count).to eq 1
audit = Audit.first
expect(audit.action).to eq 'unset_role'
expect(audit.auditable.id).to eq @project.id
expect(audit.associated.id).to eq @user.id
expect(audit.user.id).to eq current_user_id
expect(audit.data['previous_role']).to eq role
expect(audit.data['new_role']).to eq nil
end
end
end

context 'not a hub admin but is project admin of a different project' do
before do
another_project = create :project
create :project_membership_as_admin, project: another_project, user: current_user
end

it 'should not be able to unset the role - returning 403 Forbidden' do
put :unset_role, params: { id: @project.id, user_id: @user.id, role: role }
expect(response).to have_http_status(403)
end
end

end
end

Expand Down
2 changes: 1 addition & 1 deletion platform-hub-web/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@
"babel-plugin-istanbul": "^2.0.1",
"babel-polyfill": "^6.23.0",
"babel-preset-es2015": "^6.22.0",
"browser-sync": "2.26.3",
"browser-sync": "^2.18.13",
"browser-sync-spa": "^1.0.3",
"chai": "^4.0.2",
"css-loader": "^0.26.1",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -198,7 +198,7 @@ function ProjectsDetailController($rootScope, $q, $mdDialog, $state, roleChecker
}

function makeAdmin(membership, targetEvent) {
if (!ctrl.isAdmin) {
if (!ctrl.isAdmin && !ctrl.isProjectAdmin) {
return;
}

Expand All @@ -225,7 +225,7 @@ function ProjectsDetailController($rootScope, $q, $mdDialog, $state, roleChecker
}

function demoteAdmin(membership, targetEvent) {
if (!ctrl.isAdmin) {
if (!ctrl.isAdmin && !ctrl.isProjectAdmin) {
return;
}

Expand Down
4 changes: 2 additions & 2 deletions platform-hub-web/src/app/projects/projects-detail.html
Original file line number Diff line number Diff line change
Expand Up @@ -137,14 +137,14 @@ <h4>{{m.user.email}}</h4>
Offboard GitHub
</md-button>
</md-menu-item>
<md-menu-item ng-if="m.user.is_active && $ctrl.isAdmin && m.role != 'admin'">
<md-menu-item ng-if="m.user.is_active && ($ctrl.isAdmin || $ctrl.isProjectAdmin) && m.role != 'admin'">
<md-button
ng-click="$ctrl.makeAdmin(m, $event)"
aria-label="Make this person a project admin">
Make admin
</md-button>
</md-menu-item>
<md-menu-item ng-if="m.user.is_active && $ctrl.isAdmin && m.role == 'admin'">
<md-menu-item ng-if="m.user.is_active && ($ctrl.isAdmin || $ctrl.isProjectAdmin) && m.role == 'admin'">
<md-button
ng-click="$ctrl.demoteAdmin(m, $event)"
aria-label="Demote this person from their project admin role">
Expand Down
47 changes: 16 additions & 31 deletions platform-hub-web/src/app/projects/projects-list.component.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,12 +9,9 @@ function ProjectsListController(roleCheckerService, Projects, Me, _) {
const ctrl = this;

ctrl.Projects = Projects;
ctrl.myProjects = [];
ctrl.notMyProjects = [];

ctrl.loading = true;
ctrl.isAdmin = false;
ctrl.memberships = [];

init();

Expand All @@ -33,52 +30,40 @@ function ProjectsListController(roleCheckerService, Projects, Me, _) {
return Promise.all(projects.map(projectIdto => {
return Projects.getMemberships(projectIdto.id);
})).then(memberships => {
ctrl.memberships = memberships;
const myProjects = getProjects(projects, currentUserId, memberships, true);
const notMyProjects = getProjects(projects, currentUserId, memberships, false);

ctrl.myProjects = getMyProjects(projects, currentUserId, memberships);
ctrl.notMyProjects = getNotMyProjects(projects, currentUserId, memberships);

ctrl.myProjects = _.orderBy(ctrl.myProjects, [project => project.name.toLowerCase()], ['asc']);
ctrl.notMyProjects = _.orderBy(ctrl.notMyProjects, [project => project.name.toLowerCase()], ['asc']);

Projects.all = _.concat(ctrl.myProjects, ctrl.notMyProjects);
Projects.all = _.concat(myProjects, notMyProjects);

ctrl.loading = false;
});
});
});
}

function getMyProjects(allProjects, currentUserId, memberships) {
return _.chain(allProjects)
.zip(memberships)
.filter(projectsMembershipsPair => {
const membership = projectsMembershipsPair[1];
return _.some(membership, {user: {id: currentUserId}});
})
.map(projectsMembershipsPair => {
const project = projectsMembershipsPair[0];
project.isProjectTeamMember = true;
return projectsMembershipsPair;
})
.unzip()
.head().value();
}

function getNotMyProjects(allProjects, currentUserId, memberships) {
function getProjects(allProjects, currentUserId, memberships, myProjects) {
return _.chain(allProjects)
.zip(memberships)
.filter(projectsMembershipsPair => {
const membership = projectsMembershipsPair[1];
return !_.some(membership, {user: {id: currentUserId}});
const userIsProjectMember = _.some(membership, {user: {id: currentUserId}});
return myProjects ? userIsProjectMember : !userIsProjectMember;
})
.map(projectsMembershipsPair => {
const project = projectsMembershipsPair[0];
project.isProjectTeamMember = false;
project.isProjectTeamMember = myProjects;
if (myProjects) {
const membership = projectsMembershipsPair[1];
if (_.some(membership, {user: {id: currentUserId}, role: 'admin'})) {
project.isProjectAdmin = true;
}
}
return projectsMembershipsPair;
})
.unzip()
.head().value();
.head()
.orderBy([project => project.name.toLowerCase()], ['asc'])
.value();
}

function loadAdminStatus() {
Expand Down
71 changes: 63 additions & 8 deletions platform-hub-web/src/app/projects/projects-list.component.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -80,14 +80,36 @@ describe('projects-list component', () => {

it('shows all projects with user prioritised', () => {
const ctrl = $componentController('projectsList');
ctrl.myProjects = [{id: 'test1', shortname: 'Test1', name: 'Testing1', description: null, costCentreCode: null, isProjectTeamMember: true, membersCount: 1, createdAt: '2020-07-30T16:19:02Z', updatedAt: '2020-07-30T16:19:02Z'}];
ctrl.notMyProjects = [{id: 'test2', shortname: 'Test2', name: 'Testing2', description: null, costCentreCode: null, isProjectTeamMember: false, membersCount: 0, createdAt: '2020-07-30T16:29:02Z', updatedAt: '2020-07-30T16:29:02Z'}];
Projects.all = _.concat(ctrl.myProjects, ctrl.notMyProjects);
const myProjects = [{id: 'test1', shortname: 'Test1', name: 'Testing1', description: null, costCentreCode: null, isProjectTeamMember: true, membersCount: 1, createdAt: '2020-07-30T16:19:02Z', updatedAt: '2020-07-30T16:19:02Z'}];
const notMyProjects = [{id: 'test2', shortname: 'Test2', name: 'Testing2', description: null, costCentreCode: null, isProjectTeamMember: false, membersCount: 0, createdAt: '2020-07-30T16:29:02Z', updatedAt: '2020-07-30T16:29:02Z'}];
ctrl.Projects.all = _.concat(myProjects, notMyProjects);

renderComponent();
expect(Projects.all[0].isProjectTeamMember).toBe(true);
expect(Projects.all[1].isProjectTeamMember).toBe(false);
expect(Projects.all).toEqual([{id: 'test1', shortname: 'Test1', name: 'Testing1', description: null, costCentreCode: null, isProjectTeamMember: true, membersCount: 1, createdAt: '2020-07-30T16:19:02Z', updatedAt: '2020-07-30T16:19:02Z'},
{id: 'test2', shortname: 'Test2', name: 'Testing2', description: null, costCentreCode: null, isProjectTeamMember: false, membersCount: 0, createdAt: '2020-07-30T16:29:02Z', updatedAt: '2020-07-30T16:29:02Z'}]);
expect(element).toContainElement('md-card#projects');
});

it(`shows a 'Member' badge for user projects`, () => {
const ctrl = $componentController('projectsList');
const myProjects = [{id: 'test1', shortname: 'Test1', name: 'Testing1', description: null, costCentreCode: null, isProjectTeamMember: true, membersCount: 1, createdAt: '2020-07-30T16:19:02Z', updatedAt: '2020-07-30T16:19:02Z'}];
const notMyProjects = [];
ctrl.Projects.all = _.concat(myProjects, notMyProjects);

renderComponent();
expect(element).toContainElement('small#member-badge');
});

it(`does not show a 'Member' badge for non-user projects`, () => {
const ctrl = $componentController('projectsList');
const myProjects = [];
const notMyProjects = [{id: 'test2', shortname: 'Test2', name: 'Testing2', description: null, costCentreCode: null, isProjectTeamMember: false, membersCount: 0, createdAt: '2020-07-30T16:29:02Z', updatedAt: '2020-07-30T16:29:02Z'}];
ctrl.Projects.all = _.concat(myProjects, notMyProjects);

renderComponent();
expect(element).not.toContainElement('small#member-badge');
});
});

Expand All @@ -98,14 +120,47 @@ describe('projects-list component', () => {

it('shows all projects with user prioritised', () => {
const ctrl = $componentController('projectsList');
ctrl.myProjects = [{id: 'test1', shortname: 'Test1', name: 'Testing1', description: null, costCentreCode: null, isProjectTeamMember: true, membersCount: 1, createdAt: '2020-07-30T16:19:02Z', updatedAt: '2020-07-30T16:19:02Z'}];
ctrl.notMyProjects = [{id: 'test2', shortname: 'Test2', name: 'Testing2', description: null, costCentreCode: null, isProjectTeamMember: false, membersCount: 0, createdAt: '2020-07-30T16:29:02Z', updatedAt: '2020-07-30T16:29:02Z'}];
Projects.all = _.concat(ctrl.myProjects, ctrl.notMyProjects);
const myProjects = [{id: 'test1', shortname: 'Test1', name: 'Testing1', description: null, costCentreCode: null, isProjectTeamMember: true, isProjectAdmin: false, membersCount: 1, createdAt: '2020-07-30T16:19:02Z', updatedAt: '2020-07-30T16:19:02Z'}];
const notMyProjects = [{id: 'test2', shortname: 'Test2', name: 'Testing2', description: null, costCentreCode: null, isProjectTeamMember: false, isProjectAdmin: false, membersCount: 0, createdAt: '2020-07-30T16:29:02Z', updatedAt: '2020-07-30T16:29:02Z'}];
ctrl.Projects.all = _.concat(myProjects, notMyProjects);

renderComponent();
expect(Projects.all[0].isProjectTeamMember).toBe(true);
expect(Projects.all[1].isProjectTeamMember).toBe(false);
expect(Projects.all).toEqual([{id: 'test1', shortname: 'Test1', name: 'Testing1', description: null, costCentreCode: null, isProjectTeamMember: true, membersCount: 1, createdAt: '2020-07-30T16:19:02Z', updatedAt: '2020-07-30T16:19:02Z'},
{id: 'test2', shortname: 'Test2', name: 'Testing2', description: null, costCentreCode: null, isProjectTeamMember: false, membersCount: 0, createdAt: '2020-07-30T16:29:02Z', updatedAt: '2020-07-30T16:29:02Z'}]);
expect(Projects.all).toEqual([{id: 'test1', shortname: 'Test1', name: 'Testing1', description: null, costCentreCode: null, isProjectTeamMember: true, isProjectAdmin: false, membersCount: 1, createdAt: '2020-07-30T16:19:02Z', updatedAt: '2020-07-30T16:19:02Z'},
{id: 'test2', shortname: 'Test2', name: 'Testing2', description: null, costCentreCode: null, isProjectTeamMember: false, isProjectAdmin: false, membersCount: 0, createdAt: '2020-07-30T16:29:02Z', updatedAt: '2020-07-30T16:29:02Z'}]);
expect(element).toContainElement('md-card#projects');
});

it(`shows a 'Member' badge for projects where user is a member but not a project admin `, () => {
const ctrl = $componentController('projectsList');
const myProjects = [{id: 'test1', shortname: 'Test1', name: 'Testing1', description: null, costCentreCode: null, isProjectTeamMember: true, isProjectAdmin: false, membersCount: 1, createdAt: '2020-07-30T16:19:02Z', updatedAt: '2020-07-30T16:19:02Z'}];
const notMyProjects = [];
ctrl.Projects.all = _.concat(myProjects, notMyProjects);

renderComponent();
expect(element).toContainElement('small#member-badge');
});

it(`shows an 'Admin' badge for projects of which the user is a project admin `, () => {
const ctrl = $componentController('projectsList');
const myProjects = [{id: 'test1', shortname: 'Test1', name: 'Testing1', description: null, costCentreCode: null, isProjectTeamMember: true, isProjectAdmin: true, membersCount: 1, createdAt: '2020-07-30T16:19:02Z', updatedAt: '2020-07-30T16:19:02Z'}];
const notMyProjects = [];
ctrl.Projects.all = _.concat(myProjects, notMyProjects);

renderComponent();
expect(element).toContainElement('small#admin-badge');
});

it(`does not show a 'Member' badge for non-user projects`, () => {
const ctrl = $componentController('projectsList');
const myProjects = [];
const notMyProjects = [{id: 'test2', shortname: 'Test2', name: 'Testing2', description: null, costCentreCode: null, isProjectTeamMember: false, isProjectAdmin: false, membersCount: 0, createdAt: '2020-07-30T16:29:02Z', updatedAt: '2020-07-30T16:29:02Z'}];
ctrl.Projects.all = _.concat(myProjects, notMyProjects);

renderComponent();
expect(element).not.toContainElement('small#member-badge');
expect(element).not.toContainElement('small#admin-badge');
});
});
});
5 changes: 3 additions & 2 deletions platform-hub-web/src/app/projects/projects-list.html
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ <h3>

<loading-indicator loading="$ctrl.loading"></loading-indicator>

<md-content id="projects">
<md-content>

<md-card id="projects" ng-repeat="p in $ctrl.Projects.all track by p.id"
md-colors="{ 'background' : p.isProjectTeamMember ? 'grey-A100' : 'grey-300' }">
Expand All @@ -26,7 +26,8 @@ <h3>
<span class="md-headline">
{{p.name}}
({{p.shortname}})
<small class="badge" id="member-badge" ng-if="p.isProjectTeamMember" md-colors="{background: 'primary'}">Member</small>
<small class="badge" id="member-badge" ng-if="p.isProjectTeamMember && !p.isProjectAdmin" md-colors="{background: 'primary'}">Member</small>
<small class="badge" id="admin-badge" ng-if="p.isProjectTeamMember && p.isProjectAdmin" md-colors="{background: 'accent'}">Admin</small>
</span>
</md-card-title-text>
</md-card-title>
Expand Down

0 comments on commit 56fed5a

Please sign in to comment.