Skip to content

Commit

Permalink
Merge pull request #96 from alphagov/permissions-model
Browse files Browse the repository at this point in the history
Introduce permissions, restrict certain actions to editors and admins
  • Loading branch information
bishboria committed Mar 20, 2014
2 parents e02e3f1 + f818b27 commit fe85082
Show file tree
Hide file tree
Showing 23 changed files with 239 additions and 25 deletions.
1 change: 1 addition & 0 deletions Gemfile
Expand Up @@ -14,6 +14,7 @@ end

gem 'kaminari', '0.14.1'
gem 'logstasher', '0.4.8'
gem 'cancan', '1.6.10'

group :test do
gem 'capybara', '2.1.0'
Expand Down
2 changes: 2 additions & 0 deletions Gemfile.lock
Expand Up @@ -39,6 +39,7 @@ GEM
bootstrap-sass (2.3.2.2)
sass (~> 3.2)
builder (3.0.4)
cancan (1.6.10)
capybara (2.1.0)
mime-types (>= 1.16)
nokogiri (>= 1.3.3)
Expand Down Expand Up @@ -229,6 +230,7 @@ PLATFORMS
DEPENDENCIES
aws-ses
bootstrap-sass (= 2.3.2.2)
cancan (= 1.6.10)
capybara (= 2.1.0)
chosen-rails
coffee-rails (~> 3.2.1)
Expand Down
5 changes: 5 additions & 0 deletions app/controllers/application_controller.rb
@@ -1,10 +1,15 @@
class ApplicationController < ActionController::Base
protect_from_forgery
check_authorization

rescue_from ActionController::InvalidAuthenticityToken do
render text: "Invalid authenticity token", status: 403
end

rescue_from CanCan::AccessDenied do |exception|
redirect_to needs_path, alert: "You do not have permission to perform this action."
end

include GDS::SSO::ControllerMethods

before_filter :authenticate_user!
Expand Down
2 changes: 2 additions & 0 deletions app/controllers/bookmarklet_controller.rb
@@ -1,4 +1,6 @@
class BookmarkletController < ApplicationController
skip_authorization_check

def bookmarklet
# bookmarklet.html.erb
end
Expand Down
13 changes: 13 additions & 0 deletions app/controllers/needs_controller.rb
Expand Up @@ -12,6 +12,7 @@ class Http404 < StandardError
end

def index
authorize! :index, Need
opts = params.slice("organisation_id", "page", "q").select { |k, v| v.present? }
@needs = Need.list(opts)
respond_to do |format|
Expand All @@ -25,18 +26,22 @@ def index
end

def show
authorize! :read, Need
@need = load_need
end

def actions
authorize! :perform_actions_on, Need
@need = load_need
end

def revisions
authorize! :see_revisions_of, Need
@need = load_need
end

def edit
authorize! :update, Need
@need = load_need
if @need.duplicate?
redirect_to need_url(@need.need_id),
Expand All @@ -47,10 +52,12 @@ def edit
end

def new
authorize! :create, Need
@need = Need.new({})
end

def create
authorize! :create, Need
@need = Need.new( prepare_need_params(params) )

add_or_remove_criteria(:new) and return if criteria_params_present?
Expand All @@ -71,6 +78,7 @@ def create
end

def update
authorize! :update, Need
@need = load_need
@need.update(prepare_need_params(params))

Expand All @@ -92,6 +100,7 @@ def update
end

def close_as_duplicate
authorize! :close, Need
@need = load_need
if @need.duplicate?
redirect_to need_url(@need.need_id),
Expand All @@ -102,6 +111,7 @@ def close_as_duplicate
end

def closed
authorize! :close, Need
@need = load_need
@need.duplicate_of = Integer(params["need"]["duplicate_of"])

Expand All @@ -123,6 +133,7 @@ def closed
end

def reopen
authorize! :reopen, Need
@need = load_need
old_canonical_id = @need.duplicate_of

Expand All @@ -138,6 +149,7 @@ def reopen
end

def out_of_scope
authorize! :descope, Need
@need = load_need
unless @need.in_scope.nil?
flash[:error] = "This need has already been marked as out of scope"
Expand All @@ -147,6 +159,7 @@ def out_of_scope
end

def descope
authorize! :descope, Need
@need = load_need

unless @need.in_scope.nil?
Expand Down
1 change: 1 addition & 0 deletions app/controllers/notes_controller.rb
Expand Up @@ -4,6 +4,7 @@

class NotesController < ApplicationController
def create
authorize! :create, Note
text = params["notes"]["text"]
need_id = params["need_id"]
@note = Note.new(text, need_id, current_user)
Expand Down
4 changes: 2 additions & 2 deletions app/helpers/nav_tabs_helper.rb
Expand Up @@ -2,8 +2,8 @@ module NavTabsHelper
def nav_tabs_for(need)
tabs = []
tabs << [ "View", need_path(need) ]
tabs << [ "Edit", edit_need_path(need) ] unless need.duplicate?
tabs << [ "Actions", actions_need_path(need) ]
tabs << [ "Edit", edit_need_path(need) ] if !need.duplicate? && current_user.can?(:update, Need)
tabs << [ "Actions", actions_need_path(need) ] if current_user.can?(:perform_actions_on, Need)
tabs << [ "History & Notes", revisions_need_path(need) ]
tabs
end
Expand Down
14 changes: 14 additions & 0 deletions app/models/ability.rb
@@ -0,0 +1,14 @@
class Ability
include CanCan::Ability

def initialize(user)
can [ :read, :index, :see_revisions_of ], Need

if user.editor?
can [ :create, :update, :close, :reopen, :perform_actions_on ], Need
can :create, Note
end

can :descope, Need if user.admin?
end
end
19 changes: 19 additions & 0 deletions app/models/user.rb
@@ -1,4 +1,5 @@
require "gds-sso/user"
require 'ability'

class User
include Mongoid::Document
Expand All @@ -15,7 +16,25 @@ class User

attr_accessible :email, :name, :uid, :version, :organisation_slug

delegate :can?, :cannot?, :to => :ability

def ability
@ability ||= Ability.new(self)
end

def self.find_by_uid(uid)
where(uid: uid).first
end

def viewer?
has_permission?('signin')
end

def editor?
has_permission?('editor') || admin?
end

def admin?
viewer? && has_permission?('admin')
end
end
10 changes: 6 additions & 4 deletions app/views/needs/_workflow_buttons.html.erb
@@ -1,6 +1,8 @@
<%= render layout: 'workflow_footer' do %>
<%= link_to "Add a new need", new_need_path, class: "btn btn-primary" %>
<% if defined?(need) && !need.duplicate? %>
<%= link_to "Edit", edit_need_path(need), class: "btn btn-success" %>
<% if current_user.can?(:create, Need) || current_user.can?(:update, Need) %>
<%= render layout: 'workflow_footer' do %>
<%= link_to "Add a new need", new_need_path, class: "btn btn-primary" %>
<% if defined?(need) && !need.duplicate? && current_user.can?(:update, Need) %>
<%= link_to "Edit", edit_need_path(need), class: "btn btn-success" %>
<% end %>
<% end %>
<% end %>
10 changes: 7 additions & 3 deletions app/views/needs/actions.html.erb
Expand Up @@ -8,9 +8,13 @@
<%= render :partial => "need_header" %>

<div id="actions">
<%= render partial: "needs/actions/out_of_scope" %>
<hr/>
<%= render partial: "needs/actions/duplicate_of" %>
<% if current_user.can?(:descope, Need) %>
<%= render partial: "needs/actions/out_of_scope" %>
<% end %>
<% if current_user.can?(:close, Need) %>
<hr/>
<%= render partial: "needs/actions/duplicate_of" %>
<% end %>
</div>

<%= render partial: "workflow_buttons", locals: { need: @need } %>
Expand Down
8 changes: 5 additions & 3 deletions app/views/needs/revisions.html.erb
Expand Up @@ -15,9 +15,11 @@
<% end %>

<div class="row-fluid">
<div id="notes" class="span6">
<%= render partial: "needs/revisions/new_note_form", locals: { need: @need } %>
</div>
<% if current_user.can?(:create, Note) %>
<div id="notes" class="span6">
<%= render partial: "needs/revisions/new_note_form", locals: { need: @need } %>
</div>
<% end %>
<div id="revision-history" class="span6 accordion">
<%= render partial: "needs/revisions/recent_changes", locals: { need: @need } %>
</div>
Expand Down
8 changes: 8 additions & 0 deletions test/factories/factories.rb
Expand Up @@ -2,5 +2,13 @@
factory :user do
sequence(:name) { |n| "Winston #{n}"}
permissions { ["signin"] }

factory :editor do
permissions { ["signin", "editor"] }
end

factory :admin do
permissions { ["signin", "editor", "admin"] }
end
end
end

0 comments on commit fe85082

Please sign in to comment.