From d74152c5f93198209bba71d0b41436343570df86 Mon Sep 17 00:00:00 2001 From: Andrew Tomaka Date: Mon, 14 Dec 2015 13:28:39 -0500 Subject: [PATCH] Add authorization via Pundit --- .gitlab-ci.yml | 1 + Gemfile | 10 +- Gemfile.lock | 5 +- app/controllers/application_controller.rb | 12 +- app/controllers/comments_controller.rb | 12 ++ app/controllers/posts_controller.rb | 14 ++ app/controllers/subcreddits_controller.rb | 12 ++ app/models/guest_user.rb | 6 + app/models/user.rb | 4 + app/policies/application_policy.rb | 37 +++++ app/policies/comment_policy.rb | 22 +++ app/policies/post_policy.rb | 22 +++ app/policies/subcreddit_policy.rb | 18 ++ app/views/application/_sidebar.html.slim | 12 +- app/views/comments/show.html.slim | 5 +- app/views/posts/show.html.slim | 5 +- .../application_controller_spec.rb | 2 +- spec/controllers/comments_controller_spec.rb | 154 +++++++++--------- spec/controllers/posts_controller_spec.rb | 109 +++++++------ .../subcreddits_controller_spec.rb | 69 ++++---- spec/features/comments/edit_comment_spec.rb | 56 +++++-- spec/features/posts/edit_post_spec.rb | 61 +++++-- spec/features/posts/new_post_spec.rb | 14 +- .../subcreddits/close_subcreddit_spec.rb | 64 +++++--- .../subcreddits/new_subcreddit_spec.rb | 6 +- spec/policies/application_policy_spec.rb | 15 ++ spec/policies/comment_policy_spec.rb | 48 ++++++ spec/policies/post_policy_spec.rb | 48 ++++++ spec/policies/subcreddit_policy_spec.rb | 49 ++++++ spec/rails_helper.rb | 2 + spec/support/pundit_matcher.rb | 15 ++ 31 files changed, 678 insertions(+), 231 deletions(-) create mode 100644 app/models/guest_user.rb create mode 100644 app/policies/application_policy.rb create mode 100644 app/policies/comment_policy.rb create mode 100644 app/policies/post_policy.rb create mode 100644 app/policies/subcreddit_policy.rb create mode 100644 spec/policies/application_policy_spec.rb create mode 100644 spec/policies/comment_policy_spec.rb create mode 100644 spec/policies/post_policy_spec.rb create mode 100644 spec/policies/subcreddit_policy_spec.rb create mode 100644 spec/support/pundit_matcher.rb diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 5709604..d78fa62 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -4,4 +4,5 @@ test: - apt-get install -y nodejs libqtwebkit-dev qt4-qmake sqlite3 libsqlite3-dev - bundle install --path /cache - bundle exec rake db:create RAILS_ENV=test + - bundle exec rake db:test:prepare - bundle exec rspec diff --git a/Gemfile b/Gemfile index 28204f4..58e39a3 100644 --- a/Gemfile +++ b/Gemfile @@ -1,13 +1,5 @@ source 'https://rubygems.org' -def darwin_only(require_as) - RbConfig::CONFIG['host_os'] =~ /darwin/ && require_as -end - -def linux_only(require_as) - RbConfig::CONFIG['host_os'] =~ /linux/ && require_as -end - gem 'rails', '4.2.3' gem 'sqlite3' @@ -25,6 +17,8 @@ gem 'ancestry' gem 'bcrypt' +gem 'pundit' + gem 'sdoc', '~> 0.4.0', group: :doc group :development do diff --git a/Gemfile.lock b/Gemfile.lock index b2f291f..2f6335d 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -212,6 +212,8 @@ GEM coderay (~> 1.1.0) method_source (~> 0.8.1) slop (~> 3.4) + pundit (1.0.1) + activesupport (>= 3.0.0) quiet_assets (1.1.0) railties (>= 3.1, < 5.0) rack (1.6.4) @@ -387,6 +389,7 @@ DEPENDENCIES launchy metric_fu pry + pundit quiet_assets rails (= 4.2.3) rspec-rails @@ -403,4 +406,4 @@ DEPENDENCIES uglifier (>= 1.3.0) BUNDLED WITH - 1.10.5 + 1.10.6 diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb index 2c6d2b6..fc12865 100644 --- a/app/controllers/application_controller.rb +++ b/app/controllers/application_controller.rb @@ -6,10 +6,15 @@ class ApplicationController < ActionController::Base helper_method :current_session helper_method :logged_in? + include Pundit + + rescue_from Pundit::NotAuthorizedError, with: :user_not_authorized + private def current_user @current_user ||= User.find(current_session[:user_id]) if current_session + @current_user ||= GuestUser.new end def current_session @@ -17,6 +22,11 @@ class ApplicationController < ActionController::Base end def logged_in? - !!current_user + current_user.registered? + end + + def user_not_authorized + flash[:alert] = 'You are not authorized to perform this action.' + redirect_to(request.referrer || root_path) end end diff --git a/app/controllers/comments_controller.rb b/app/controllers/comments_controller.rb index 0b3dcb6..16047ac 100644 --- a/app/controllers/comments_controller.rb +++ b/app/controllers/comments_controller.rb @@ -3,22 +3,29 @@ class CommentsController < ApplicationController before_filter :set_comment, only: [:show, :edit, :update, :destroy] before_filter :set_post before_filter :set_subcreddit + after_action :verify_authorized def show @comments = @comment .subtree .includes(:post, :user) .arrange(order: :created_at) + + authorize @comment end def new @comment = Comment.new(params[:parent_id]) + + authorize @comment end def create @comment = @post.comments.build comment_params @comment.user = current_user + authorize @comment + if @comment.save flash[:notice] = 'Comment saved' else @@ -29,9 +36,12 @@ class CommentsController < ApplicationController end def edit + authorize @comment end def update + authorize @comment + if @comment.update comment_params redirect_to subcreddit_post_path(@subcreddit, @post), notice: 'Comment updated' @@ -41,6 +51,8 @@ class CommentsController < ApplicationController end def destroy + authorize @comment + @comment.destroy redirect_to subcreddit_post_path(@subcreddit, @post), notice: 'Comment deleted' diff --git a/app/controllers/posts_controller.rb b/app/controllers/posts_controller.rb index 6d95440..b46e7fb 100644 --- a/app/controllers/posts_controller.rb +++ b/app/controllers/posts_controller.rb @@ -2,23 +2,32 @@ class PostsController < ApplicationController before_filter :set_post, except: [:index, :new, :create] before_filter :set_subcreddit + after_action :verify_authorized def index @posts = Post.includes(:subcreddit, :user).all + + authorize Post end def show @comments = @post.comments.includes(:user).arrange(order: :created_at) + + authorize @post end def new @post = Post.new + + authorize @post end def create @post = @subcreddit.posts.build(post_params) @post.user = current_user + authorize @post + if @post.save redirect_to subcreddit_post_path(@subcreddit, @post), notice: 'Post created' @@ -28,9 +37,12 @@ class PostsController < ApplicationController end def edit + authorize @post end def update + authorize @post + if @post.update(post_params) redirect_to subcreddit_post_path(@subcreddit, @post), notice: 'Post was updated' @@ -40,6 +52,8 @@ class PostsController < ApplicationController end def destroy + authorize @post + @post.destroy redirect_to subcreddits_path(@subcreddit), notice: 'Post was deleted' diff --git a/app/controllers/subcreddits_controller.rb b/app/controllers/subcreddits_controller.rb index 3ed4101..083abea 100644 --- a/app/controllers/subcreddits_controller.rb +++ b/app/controllers/subcreddits_controller.rb @@ -1,23 +1,32 @@ # controllers/subcreddits_controller.rb class SubcredditsController < ApplicationController before_filter :set_subcreddit, only: [:show, :edit, :update] + after_action :verify_authorized def index @subcreddits = Subcreddit.all + + authorize Subcreddit end def show @posts = @subcreddit.posts + + authorize @subcreddit end def new @subcreddit = Subcreddit.new + + authorize @subcreddit end def create @subcreddit = Subcreddit.new(create_subcreddit_params) @subcreddit.owner = current_user + authorize @subcreddit + if @subcreddit.save redirect_to @subcreddit, notice: 'Subcreddit was created!' else @@ -26,9 +35,12 @@ class SubcredditsController < ApplicationController end def edit + authorize @subcreddit end def update + authorize @subcreddit + if @subcreddit.update(update_subcreddit_params) redirect_to @subcreddit, notice: 'Subcreddit was updated!' else diff --git a/app/models/guest_user.rb b/app/models/guest_user.rb new file mode 100644 index 0000000..fb1d70a --- /dev/null +++ b/app/models/guest_user.rb @@ -0,0 +1,6 @@ +# models/guest_user.rb +class GuestUser + def registered? + false + end +end diff --git a/app/models/user.rb b/app/models/user.rb index 0ee835e..e3094d0 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -15,6 +15,10 @@ class User < ActiveRecord::Base validates :username, presence: true, uniqueness: true, sluguuidless: true validates :password, length: { minimum: 8 } + def registered? + true + end + private def downcase_email diff --git a/app/policies/application_policy.rb b/app/policies/application_policy.rb new file mode 100644 index 0000000..6efacdc --- /dev/null +++ b/app/policies/application_policy.rb @@ -0,0 +1,37 @@ +# policies/application_policy.rb +class ApplicationPolicy + attr_reader :user, :record + + def initialize(user, record) + @user = user + @record = record + end + + def index? + false + end + + def show? + false + end + + def create? + false + end + + def new? + create? + end + + def update? + false + end + + def edit? + update? + end + + def destroy? + false + end +end diff --git a/app/policies/comment_policy.rb b/app/policies/comment_policy.rb new file mode 100644 index 0000000..eaf4eef --- /dev/null +++ b/app/policies/comment_policy.rb @@ -0,0 +1,22 @@ +# policies/comment_policy.rb +class CommentPolicy < ApplicationPolicy + def index? + true + end + + def show? + true + end + + def create? + user.registered? + end + + def update? + record.user == user + end + + def destroy? + record.user == user + end +end diff --git a/app/policies/post_policy.rb b/app/policies/post_policy.rb new file mode 100644 index 0000000..46f6c06 --- /dev/null +++ b/app/policies/post_policy.rb @@ -0,0 +1,22 @@ +# policies/post_policy.rb +class PostPolicy < ApplicationPolicy + def index? + true + end + + def show? + true + end + + def create? + user.registered? + end + + def update? + record.user == user + end + + def destroy? + record.user == user + end +end diff --git a/app/policies/subcreddit_policy.rb b/app/policies/subcreddit_policy.rb new file mode 100644 index 0000000..fd05ecf --- /dev/null +++ b/app/policies/subcreddit_policy.rb @@ -0,0 +1,18 @@ +# policies/subcreddit_policy.rb +class SubcredditPolicy < ApplicationPolicy + def index? + true + end + + def show? + true + end + + def create? + user.registered? + end + + def update? + record.owner == user + end +end diff --git a/app/views/application/_sidebar.html.slim b/app/views/application/_sidebar.html.slim index aebf356..e9f4558 100644 --- a/app/views/application/_sidebar.html.slim +++ b/app/views/application/_sidebar.html.slim @@ -5,12 +5,14 @@ button.btn.btn-default type="button" span.glyphicon.glyphicon-search aria-hidden="true" - - if @subcreddit && @subcreddit.id - = link_to 'Submit a new link', new_subcreddit_post_path(@subcreddit), class: 'button btn btn-primary btn-block' - = link_to 'Submit a new text post', new_subcreddit_post_path(@subcreddit), class: 'button btn btn-primary btn-block' - = link_to 'Create your own subcreddit', new_subcreddit_path, class: 'button btn btn-primary btn-block' + - if policy(:post).new? + - if @subcreddit && @subcreddit.id + = link_to 'Submit a new link', new_subcreddit_post_path(@subcreddit), class: 'button btn btn-primary btn-block' + = link_to 'Submit a new text post', new_subcreddit_post_path(@subcreddit), class: 'button btn btn-primary btn-block' + - if policy(:subcreddit).new? + = link_to 'Create your own subcreddit', new_subcreddit_path, class: 'button btn btn-primary btn-block' - - if @subcreddit && @subcreddit.id + - if @subcreddit && @subcreddit.id && policy(@subcreddit).edit? .title Moderation Tools .box ul diff --git a/app/views/comments/show.html.slim b/app/views/comments/show.html.slim index 420f1ba..f350f12 100644 --- a/app/views/comments/show.html.slim +++ b/app/views/comments/show.html.slim @@ -3,7 +3,8 @@ .alert.alert-info.in-page p you are viewing a single comment's thread. p #{link_to 'view the rest of the comments', subcreddit_post_path(@subcreddit, @post)} → -= "Commenting as: #{current_user.username}" -== render 'comments/form', subcreddit: @subcreddit, post: @post, comment: @post.comments.build, parent: @comment +- if policy(:comment).new? + = "Commenting as: #{current_user.username}" + == render 'comments/form', subcreddit: @subcreddit, post: @post, comment: @post.comments.build, parent: @comment .comments.contents == nested_comments(@comments) diff --git a/app/views/posts/show.html.slim b/app/views/posts/show.html.slim index 5a86e55..c3a03df 100644 --- a/app/views/posts/show.html.slim +++ b/app/views/posts/show.html.slim @@ -4,7 +4,8 @@ .title= "all #{@post.comments_count} comments" - else .title= "no comments (yet)" -= "Commenting as: #{current_user.username}" -== render 'comments/form', subcreddit: @subcreddit, post: @post, comment: @post.comments.build, parent: nil +- if policy(:comment).new? + = "Commenting as: #{current_user.username}" + == render 'comments/form', subcreddit: @subcreddit, post: @post, comment: @post.comments.build, parent: nil .comments.contents == nested_comments(@comments) diff --git a/spec/controllers/application_controller_spec.rb b/spec/controllers/application_controller_spec.rb index d12643c..fdcd981 100644 --- a/spec/controllers/application_controller_spec.rb +++ b/spec/controllers/application_controller_spec.rb @@ -31,7 +31,7 @@ describe ApplicationController, type: :controller do context 'when not logged in' do it 'should return nil' do - expect(controller.send(:current_user)).to be_nil + expect(controller.send(:current_user)).to be_a(GuestUser) end end end diff --git a/spec/controllers/comments_controller_spec.rb b/spec/controllers/comments_controller_spec.rb index 4047c6a..905bd0e 100644 --- a/spec/controllers/comments_controller_spec.rb +++ b/spec/controllers/comments_controller_spec.rb @@ -99,103 +99,111 @@ describe CommentsController, type: :controller do end describe '#edit' do - let!(:comment) { create(:comment) } - before(:each) do - get :edit, - id: comment, - post_id: comment.post, - subcreddit_id: comment.post.subcreddit - end - - context 'with valid comment' do - it 'should render :edit' do - expect(response).to render_template(:edit) + context 'when owner' do + let!(:comment) { create(:comment, user: user) } + before(:each) do + get :edit, + id: comment, + post_id: comment.post, + subcreddit_id: comment.post.subcreddit end - it 'should assign correct Comment to @comment' do - expect(assigns(:comment)).to eq(comment) + context 'with valid comment' do + it 'should render :edit' do + expect(response).to render_template(:edit) + end + + it 'should assign correct Comment to @comment' do + expect(assigns(:comment)).to eq(comment) + end end end end context '#update' do - let!(:comment) { create(:comment) } let(:data) { { content: 'Some edited comment content goes here' } } context 'with valid data' do - before(:each) do - put :update, - id: comment, - post_id: comment.post, - subcreddit_id: comment.post.subcreddit, - comment: data + let!(:comment) { create(:comment, user: user) } + + context 'when owner' do + before(:each) do + put :update, + id: comment, + post_id: comment.post, + subcreddit_id: comment.post.subcreddit, + comment: data + end + + it 'should assign correct Comment to @comment' do + expect(assigns(:comment)).to eq(comment) + end + + it 'should update the comment' do + comment.reload + + expect(comment.content).to eq(data[:content]) + end + + it 'should redirect to the post' do + expect(response) + .to redirect_to(subcreddit_post_path(assigns(:post).subcreddit, + assigns(:post))) + end + + it 'should display a notice flash message' do + expect(flash[:notice]).to be_present + end end - it 'should assign correct Comment to @comment' do - expect(assigns(:comment)).to eq(comment) - end + context 'with invalid data' do + before(:each) { data[:content] = '' } - it 'should update the comment' do - comment.reload + it 'should render :edit' do + put :update, + id: comment, + post_id: comment.post, + subcreddit_id: comment.post.subcreddit, + comment: data - expect(comment.content).to eq(data[:content]) - end - - it 'should redirect to the post' do - expect(response) - .to redirect_to(subcreddit_post_path(assigns(:post).subcreddit, - assigns(:post))) - end - - it 'should display a notice flash message' do - expect(flash[:notice]).to be_present - end - end - - context 'with invalid data' do - before(:each) { data[:content] = '' } - - it 'should render :edit' do - put :update, - id: comment, - post_id: comment.post, - subcreddit_id: comment.post.subcreddit, - comment: data - - expect(response).to render_template(:edit) + expect(response).to render_template(:edit) + end end end end context '#destroy' do - let!(:comment) { create(:comment) } + context 'when owner' do + let!(:comment) { create(:comment, user: user) } - it 'should delete the post' do - delete :destroy, - id: comment, - post_id: comment.post, - subcreddit_id: comment.post.subcreddit + it 'should delete the post' do + delete :destroy, + id: comment, + post_id: comment.post, + subcreddit_id: comment.post.subcreddit - comment.reload - expect(comment.destroyed?).to be(true) - end + comment.reload + expect(comment.destroyed?).to be(true) + end - it 'should redirect to the post' do - delete :destroy, - id: comment, - post_id: comment.post, - subcreddit_id: comment.post.subcreddit + it 'should redirect to the post' do + delete :destroy, + id: comment, + post_id: comment.post, + subcreddit_id: comment.post.subcreddit - expect(response).to redirect_to(subcreddit_post_path(assigns(:subcreddit), - assigns(:post))) - end + expect(response) + .to redirect_to(subcreddit_post_path(assigns(:subcreddit), + assigns(:post))) + end - it 'should send a notice flash message' do - delete :destroy, - id: comment, - post_id: comment.post, - subcreddit_id: comment.post.subcreddit - expect(flash[:notice]).to be_present + it 'should send a notice flash message' do + delete :destroy, + id: comment, + post_id: comment.post, + subcreddit_id: comment.post.subcreddit + expect(flash[:notice]).to be_present + end end end end diff --git a/spec/controllers/posts_controller_spec.rb b/spec/controllers/posts_controller_spec.rb index 7f746fa..d56bce4 100644 --- a/spec/controllers/posts_controller_spec.rb +++ b/spec/controllers/posts_controller_spec.rb @@ -94,22 +94,23 @@ describe PostsController, type: :controller do end context '#edit' do - let!(:post) { create(:post) } - before(:each) { get :edit, id: post, subcreddit_id: post.subcreddit } + context 'when owner' do + let!(:post) { create(:post, user: user) } + before(:each) { get :edit, id: post, subcreddit_id: post.subcreddit } - context 'with valid post' do - it 'should render :edit' do - expect(response).to render_template(:edit) - end + context 'with valid post' do + it 'should render :edit' do + expect(response).to render_template(:edit) + end - it 'should assign correct Post to @post' do - expect(assigns(:post)).to eq(post) + it 'should assign correct Post to @post' do + expect(assigns(:post)).to eq(post) + end end end end context '#update' do - let!(:post) { create(:post) } let(:data) do { title: 'New title', @@ -117,61 +118,67 @@ describe PostsController, type: :controller do } end - context 'with valid data' do - before(:each) do - put :update, id: post, subcreddit_id: post.subcreddit, post: data + context 'when owner' do + let!(:post) { create(:post, user: user) } + + context 'with valid data' do + before(:each) do + put :update, id: post, subcreddit_id: post.subcreddit, post: data + end + + it 'should assign correct Post to @post' do + expect(assigns(:post)).to eq(post) + end + + it 'should update the post' do + post.reload + + expect(post.title).to eq(data[:title]) + expect(post.content).to eq(data[:content]) + end + + it 'should redirect to the post' do + expect(response) + .to redirect_to(subcreddit_post_path(assigns(:post).subcreddit, + assigns(:post))) + end + + it 'should display a notice flash message' do + expect(flash[:notice]).to be_present + end end - it 'should assign correct Post to @post' do - expect(assigns(:post)).to eq(post) - end + context 'with invalid data' do + before(:each) { data[:title] = '' } - it 'should update the post' do - post.reload + it 'should render :edit' do + put :update, id: post, subcreddit_id: post.subcreddit, post: data - expect(post.title).to eq(data[:title]) - expect(post.content).to eq(data[:content]) - end - - it 'should redirect to the post' do - expect(response) - .to redirect_to(subcreddit_post_path(assigns(:post).subcreddit, - assigns(:post))) - end - - it 'should display a notice flash message' do - expect(flash[:notice]).to be_present - end - end - - context 'with invalid data' do - before(:each) { data[:title] = '' } - - it 'should render :edit' do - put :update, id: post, subcreddit_id: post.subcreddit, post: data - - expect(response).to render_template(:edit) + expect(response).to render_template(:edit) + end end end end context '#destroy' do - let!(:post) { create(:post, subcreddit: subcreddit) } + context 'when owner' do + let!(:post) { create(:post, user: user, subcreddit: subcreddit) } - it 'should delete the post' do - expect { delete :destroy, subcreddit_id: subcreddit, id: post } - .to change(Post, :count).by(-1) - end + it 'should delete the post' do + expect { delete :destroy, subcreddit_id: subcreddit, id: post } + .to change(Post, :count).by(-1) + end - it 'should redirect to the subcreddit index' do - expect(delete :destroy, subcreddit_id: subcreddit, id: post) - .to redirect_to(subcreddits_path(subcreddit)) - end + it 'should redirect to the subcreddit index' do + expect(delete :destroy, subcreddit_id: subcreddit, id: post) + .to redirect_to(subcreddits_path(subcreddit)) + end - it 'should flash notify that the post was deleted' do - delete :destroy, subcreddit_id: subcreddit, id: post + it 'should flash notify that the post was deleted' do + delete :destroy, subcreddit_id: subcreddit, id: post - expect(flash[:notice]).to be_present + expect(flash[:notice]).to be_present + end end end end diff --git a/spec/controllers/subcreddits_controller_spec.rb b/spec/controllers/subcreddits_controller_spec.rb index c6163d8..88c3a6f 100644 --- a/spec/controllers/subcreddits_controller_spec.rb +++ b/spec/controllers/subcreddits_controller_spec.rb @@ -97,24 +97,25 @@ describe SubcredditsController, type: :controller do describe '#edit' do context 'with valid subcreddit' do - let(:subcreddit) { create(:subcreddit) } + context 'when owner' do + let(:subcreddit) { create(:subcreddit, owner: user) } - it 'should assign @subcreddit to the existing subcreddit' do - get :edit, id: subcreddit + it 'should assign @subcreddit to the existing subcreddit' do + get :edit, id: subcreddit - expect(assigns(:subcreddit)).to eq(subcreddit) - end + expect(assigns(:subcreddit)).to eq(subcreddit) + end - it 'should render :edit' do - get :edit, id: subcreddit + it 'should render :edit' do + get :edit, id: subcreddit - expect(response).to render_template(:edit) + expect(response).to render_template(:edit) + end end end end describe '#update' do - let(:subcreddit) { create(:subcreddit) } let(:data) do { closed: '1' @@ -122,39 +123,43 @@ describe SubcredditsController, type: :controller do end context 'wth valid data' do - it 'should assign @subcreddit to the existing subcreddit' do - put :update, id: subcreddit, subcreddit: data + context 'when owner' do + let(:subcreddit) { create(:subcreddit, owner: user) } - expect(assigns(:subcreddit)).to eq(subcreddit) - end + it 'should assign @subcreddit to the existing subcreddit' do + put :update, id: subcreddit, subcreddit: data - it 'should update the subcreddit' do - put :update, id: subcreddit, subcreddit: data - subcreddit.reload + expect(assigns(:subcreddit)).to eq(subcreddit) + end - expect(subcreddit.closed_at).to_not eq(nil) - end + it 'should update the subcreddit' do + put :update, id: subcreddit, subcreddit: data + subcreddit.reload - it 'should redirect to the subcreddit' do - put :update, id: subcreddit, subcreddit: data + expect(subcreddit.closed_at).to_not eq(nil) + end - expect(response).to redirect_to(subcreddit_url(subcreddit)) - end + it 'should redirect to the subcreddit' do + put :update, id: subcreddit, subcreddit: data - it 'should display a notice flash message' do - put :update, id: subcreddit, subcreddit: data + expect(response).to redirect_to(subcreddit_url(subcreddit)) + end - expect(flash[:notice]).to be_present - end - end + it 'should display a notice flash message' do + put :update, id: subcreddit, subcreddit: data - context 'with invalid data' do - before(:each) { data[:closed] = 'bad' } + expect(flash[:notice]).to be_present + end - it 'should render :edit' do - put :update, id: subcreddit, subcreddit: data + context 'with invalid data' do + before(:each) { data[:closed] = 'bad' } - expect(response).to render_template(:edit) + it 'should render :edit' do + put :update, id: subcreddit, subcreddit: data + + expect(response).to render_template(:edit) + end + end end end end diff --git a/spec/features/comments/edit_comment_spec.rb b/spec/features/comments/edit_comment_spec.rb index 2dfd64f..2bead44 100644 --- a/spec/features/comments/edit_comment_spec.rb +++ b/spec/features/comments/edit_comment_spec.rb @@ -3,28 +3,54 @@ require 'rails_helper' describe 'Edit Comment', type: :feature do let!(:user) { create(:user) } let!(:post) { create(:post) } - let!(:comment) { create(:comment, post: post, user: user) } context 'when signed in' do - let(:content) { 'Some different data' } + let!(:comment) { create(:comment, post: post, user: user) } + before(:each) { signin(user: user) } - context 'with valid data' do - before(:each) do + context 'when owner' do + let(:content) { 'Some different data' } + + context 'with valid data' do + before(:each) do + visit edit_subcreddit_post_comment_path(post.subcreddit, + post, + comment) + + fill_in :comment_content, with: content + + click_button 'Update Comment' + end + + it 'should notify that the comment was edited' do + expect(page).to have_content('updated') + end + + it 'should update the comment' do + expect(page).to have_content(content) + end + end + end + + context 'when not owner' do + let!(:comment) { create(:comment, post: post) } + + it 'should not allow editing of comment' do visit edit_subcreddit_post_comment_path(post.subcreddit, post, comment) - fill_in :comment_content, with: content - - click_button 'Update Comment' - end - - it 'should notify that the comment was edited' do - expect(page).to have_content('updated') - end - - it 'should update the comment' do - expect(page).to have_content(content) + expect(page).to have_content('not authorized') end end end + + context 'when not signed in' do + let!(:comment) { create(:comment, post: post) } + + it 'should not allow editing of comment' do + visit edit_subcreddit_post_comment_path(post.subcreddit, post, comment) + + expect(page).to have_content('not authorized') + end + end end diff --git a/spec/features/posts/edit_post_spec.rb b/spec/features/posts/edit_post_spec.rb index 0038cde..fcd9346 100644 --- a/spec/features/posts/edit_post_spec.rb +++ b/spec/features/posts/edit_post_spec.rb @@ -1,33 +1,58 @@ require 'rails_helper' describe 'Edit Post', type: :feature do + let!(:subcreddit) { create(:subcreddit) } + context 'when signed in' do let!(:user) { create(:user) } - let!(:subcreddit) { create(:subcreddit) } - let!(:post) { create(:post, subcreddit: subcreddit, user: user) } let(:new_post) { build_stubbed(:post) } before(:each) { signin(user: user) } - context 'with valid data' do - before(:each) do + context 'when owner' do + let!(:post) { create(:post, subcreddit: subcreddit, user: user) } + + context 'with valid data' do + before(:each) do + visit edit_subcreddit_post_path(subcreddit, post) + + fill_in :post_title, with: new_post.title + fill_in :post_link, with: new_post.link + fill_in :post_content, with: new_post.content + + click_button 'Update Post' + end + + it 'should notify that the post was edited' do + expect(page).to have_content('updated') + end + + it 'should show the updated post' do + expect(page).to have_content(new_post.title) + expect(page).to have_content(new_post.content) + end + end + end + + context 'when not owner' do + let!(:post) { create(:post, subcreddit: subcreddit) } + + it 'should notify user that they cannot edit' do visit edit_subcreddit_post_path(subcreddit, post) - fill_in :post_title, with: new_post.title - fill_in :post_link, with: new_post.link - fill_in :post_content, with: new_post.content - - click_button 'Update Post' - end - - it 'should notify that the post was edited' do - expect(page).to have_content('updated') - end - - it 'should show the updated post' do - expect(page).to have_content(new_post.title) - expect(page).to have_content(new_post.content) + expect(page).to have_content 'not authorized' end end end + + context 'when not signed in' do + let!(:user) { GuestUser.new } + let!(:post) { create(:post, subcreddit: subcreddit) } + + it 'should notify user they cannot edit' do + visit edit_subcreddit_post_path(subcreddit, post) + + expect(page).to have_content 'not authorized' + end + end end diff --git a/spec/features/posts/new_post_spec.rb b/spec/features/posts/new_post_spec.rb index 2d37634..0ea7c45 100644 --- a/spec/features/posts/new_post_spec.rb +++ b/spec/features/posts/new_post_spec.rb @@ -1,9 +1,10 @@ require 'rails_helper' describe 'New Post', type: :feature do + let!(:subcreddit) { create(:subcreddit) } + context 'when signed in' do let!(:user) { create(:user) } - let!(:subcreddit) { create(:subcreddit) } let!(:post) { build(:post, subcreddit: subcreddit) } before(:each) { signin(user: user) } @@ -46,4 +47,15 @@ describe 'New Post', type: :feature do end end end + + context 'when not signed in' do + let!(:user) { GuestUser.new } + let!(:post) { create(:post, subcreddit: subcreddit) } + + it 'should notify user they cannot create' do + visit new_subcreddit_post_path(subcreddit) + + expect(page).to have_content 'not authorized' + end + end end diff --git a/spec/features/subcreddits/close_subcreddit_spec.rb b/spec/features/subcreddits/close_subcreddit_spec.rb index cc59678..1bd0bf9 100644 --- a/spec/features/subcreddits/close_subcreddit_spec.rb +++ b/spec/features/subcreddits/close_subcreddit_spec.rb @@ -4,36 +4,60 @@ describe 'Edit Subcreddit', type: :feature do before(:each) { signout } context 'when logged in' do - context 'when board is open' do - let!(:subcreddit) { create(:subcreddit) } - before(:each) do - visit subcreddits_path - click_link 'Edit' - check :subcreddit_closed - click_button 'Update Subcreddit' + let!(:user) { create(:user) } + before(:each) { signin(user: user) } + + context 'when user is owner' do + let!(:subcreddit) { create(:subcreddit, owner: user) } + + context 'when board is open' do + before(:each) do + visit subcreddits_path + click_link 'Edit' + check :subcreddit_closed + click_button 'Update Subcreddit' + end + + it 'should be notified the subcreddit was updated' do + expect(page).to have_content('updated') + end + + it 'should close the board when closed is checked' do + expect(page).to have_content('closed') + end end - it 'should be notified the subcreddit was updated' do - expect(page).to have_content('updated') - end + context 'when board is closed' do + let!(:subcreddit) do + create(:subcreddit, owner: user, closed_at: Time.now) + end - it 'should close the board when closed is checked' do - expect(page).to have_content('closed') + before(:each) do + visit subcreddits_path + click_link 'Edit' + uncheck :subcreddit_closed + click_button 'Update Subcreddit' + end + + it 'should open the board when closed is checked' do + expect(page).to_not have_content('closed') + end end end - context 'when board is closed' do - let!(:subcreddit) { create(:subcreddit, closed_at: Time.now) } - before(:each) do + context 'when not owner' do + let!(:subcreddit) { create(:subcreddit) } + + it 'should not allow editing of subcreddit' do visit subcreddits_path click_link 'Edit' - uncheck :subcreddit_closed - click_button 'Update Subcreddit' - end - it 'should open the board when closed is checked' do - expect(page).to_not have_content('closed') + expect(page).to have_content('not authorized') end end end + + context 'when not logged in' do + + end end diff --git a/spec/features/subcreddits/new_subcreddit_spec.rb b/spec/features/subcreddits/new_subcreddit_spec.rb index 1082604..b0226b5 100644 --- a/spec/features/subcreddits/new_subcreddit_spec.rb +++ b/spec/features/subcreddits/new_subcreddit_spec.rb @@ -4,7 +4,11 @@ describe 'New Subcreddit', type: :feature do before(:each) { signout } context 'when not signed in' do - it 'should not be able to create a new subcreddit' + it 'should not be able to create a new subcreddit' do + visit new_subcreddit_path + + expect(page).to have_content('not authorized') + end end context 'when signed in' do diff --git a/spec/policies/application_policy_spec.rb b/spec/policies/application_policy_spec.rb new file mode 100644 index 0000000..34cb6e6 --- /dev/null +++ b/spec/policies/application_policy_spec.rb @@ -0,0 +1,15 @@ +require 'rails_helper' + +describe ApplicationPolicy do + subject { ApplicationPolicy.new(user, object) } + let(:user) { create(:user) } + let(:object) { double('Object') } + + it { should_not grant(:index) } + it { should_not grant(:show) } + it { should_not grant(:new) } + it { should_not grant(:create) } + it { should_not grant(:edit) } + it { should_not grant(:update) } + it { should_not grant(:destroy) } +end diff --git a/spec/policies/comment_policy_spec.rb b/spec/policies/comment_policy_spec.rb new file mode 100644 index 0000000..be65fcb --- /dev/null +++ b/spec/policies/comment_policy_spec.rb @@ -0,0 +1,48 @@ +require 'rails_helper' + +describe CommentPolicy do + subject { CommentPolicy.new(user, comment) } + + context 'when user is a guest' do + let(:comment) { create(:comment) } + let(:user) { GuestUser.new } + + it { should grant(:index) } + it { should grant(:show) } + + it { should_not grant(:new) } + it { should_not grant(:create) } + it { should_not grant(:edit) } + it { should_not grant(:update) } + it { should_not grant(:destroy) } + end + + context 'when user is registered' do + let(:user) { create(:user) } + + context 'when not owner' do + let(:comment) { create(:comment) } + + it { should grant(:index) } + it { should grant(:show) } + it { should grant(:new) } + it { should grant(:create) } + + it { should_not grant(:edit) } + it { should_not grant(:update) } + it { should_not grant(:destroy) } + end + + context 'when owner' do + let(:comment) { create(:comment, user: user) } + + it { should grant(:index) } + it { should grant(:show) } + it { should grant(:new) } + it { should grant(:create) } + it { should grant(:edit) } + it { should grant(:update) } + it { should grant(:destroy) } + end + end +end diff --git a/spec/policies/post_policy_spec.rb b/spec/policies/post_policy_spec.rb new file mode 100644 index 0000000..8a2c161 --- /dev/null +++ b/spec/policies/post_policy_spec.rb @@ -0,0 +1,48 @@ +require 'rails_helper' + +describe PostPolicy do + subject { PostPolicy.new(user, post) } + + context 'when user is a guest' do + let(:post) { create(:post) } + let(:user) { GuestUser.new } + + it { should grant(:index) } + it { should grant(:show) } + + it { should_not grant(:new) } + it { should_not grant(:create) } + it { should_not grant(:edit) } + it { should_not grant(:update) } + it { should_not grant(:destroy) } + end + + context 'when user is registered' do + let(:user) { create(:user) } + + context 'when not owner' do + let(:post) { create(:post) } + + it { should grant(:index) } + it { should grant(:show) } + it { should grant(:new) } + it { should grant(:create) } + + it { should_not grant(:edit) } + it { should_not grant(:update) } + it { should_not grant(:destroy) } + end + + context 'when owner' do + let(:post) { create(:post, user: user) } + + it { should grant(:index) } + it { should grant(:show) } + it { should grant(:new) } + it { should grant(:create) } + it { should grant(:edit) } + it { should grant(:update) } + it { should grant(:destroy) } + end + end +end diff --git a/spec/policies/subcreddit_policy_spec.rb b/spec/policies/subcreddit_policy_spec.rb new file mode 100644 index 0000000..3229c45 --- /dev/null +++ b/spec/policies/subcreddit_policy_spec.rb @@ -0,0 +1,49 @@ +require 'rails_helper' + +describe SubcredditPolicy do + subject { SubcredditPolicy.new(user, subcreddit) } + + context 'when user is a guest' do + let(:subcreddit) { create(:subcreddit) } + let(:user) { GuestUser.new } + + it { should grant(:index) } + it { should grant(:show) } + + it { should_not grant(:new) } + it { should_not grant(:create) } + it { should_not grant(:edit) } + it { should_not grant(:update) } + it { should_not grant(:destroy) } + end + + context 'when user is registered' do + let(:user) { create(:user) } + + context 'when not owner' do + let(:subcreddit) { create(:subcreddit) } + + it { should grant(:index) } + it { should grant(:show) } + it { should grant(:new) } + it { should grant(:create) } + + it { should_not grant(:edit) } + it { should_not grant(:update) } + it { should_not grant(:destroy) } + end + + context 'when owner' do + let(:subcreddit) { create(:subcreddit, owner: user) } + + it { should grant(:index) } + it { should grant(:show) } + it { should grant(:new) } + it { should grant(:create) } + it { should grant(:edit) } + it { should grant(:update) } + + it { should_not grant(:destroy) } + end + end +end diff --git a/spec/rails_helper.rb b/spec/rails_helper.rb index 27e54ae..64de852 100644 --- a/spec/rails_helper.rb +++ b/spec/rails_helper.rb @@ -12,11 +12,13 @@ require 'rspec/rails' require 'shoulda/matchers' require 'capybara/rails' require 'capybara/rspec' +require 'pundit/rspec' require 'support/capybara' require 'support/database_cleaner' require 'support/factory_girl' require 'support/helpers' +require 'support/pundit_matcher' ActiveRecord::Migration.maintain_test_schema! diff --git a/spec/support/pundit_matcher.rb b/spec/support/pundit_matcher.rb new file mode 100644 index 0000000..c9653a1 --- /dev/null +++ b/spec/support/pundit_matcher.rb @@ -0,0 +1,15 @@ +RSpec::Matchers.define :grant do |action| + match do |policy| + policy.public_send("#{action}?") + end + + failure_message do |policy| + "#{policy.class} does not permit #{action} on #{policy.record} " + + "for #{policy.user.inspect}." + end + + failure_message_when_negated do |policy| + "#{policy.class} does not forbid #{action} on #{policy.record} " + + "for #{policy.user.inspect}." + end +end