From 4f12c2cd104f4db63ef4b624257a47276217c910 Mon Sep 17 00:00:00 2001 From: Alex Sanford Date: Mon, 16 Apr 2012 15:17:11 -0300 Subject: [PATCH 01/17] Created some initial features for API authentication --- features/api/authorization/pages.feature | 141 +++++++++++++++++++++++ features/step_definitions/api_steps.rb | 72 ++++++++++++ features/support/http.rb | 22 ++++ 3 files changed, 235 insertions(+) create mode 100644 features/api/authorization/pages.feature create mode 100644 features/step_definitions/api_steps.rb create mode 100644 features/support/http.rb diff --git a/features/api/authorization/pages.feature b/features/api/authorization/pages.feature new file mode 100644 index 00000000..67286d8f --- /dev/null +++ b/features/api/authorization/pages.feature @@ -0,0 +1,141 @@ +Feature: Pages + In order to ensure pages are not tampered with + As an admin, designer or author + I will be restricted based on my role + + Background: + Given I have the site: "test site" set up + And I have a custom model named "Projects" with + | label | type | required | + | Name | string | true | + | Description | text | false | + And I have a designer and an author + And a page named "hello-world" with id "4f832c2cb0d86d3f42fffffe" + And a page named "goodbye-world" with id "4f832c2cb0d86d3f42ffffff" + + Scenario: As an unauthenticated user + Given I am not authenticated + When I do an API GET to pages.json + Then the JSON response should be the following: + """ + { + "error": "You need to sign in or sign up before continuing." + } + """ + + # listing pages + + Scenario: Accessing pages as an Admin + Given I have an "admin" token + When I do an API GET request to pages.json + Then the JSON response should contain all pages + + Scenario: Accessing pages as a Designer + Given I have a "designer" token + When I do an API GET request to pages.json + Then the JSON response should contain all pages + + Scenario: Accessing pages as an Author + Given I have an "author" token + When I do an API GET request to pages.json + Then the JSON response should contain all pages + + # create page + + Scenario: Creating new page as an Admin + Given I have an "admin" token + When I do an API GET request to pages.json + Then the JSON response should contain 4 pages + And the JSON response should contain all pages + When I do an API POST to pages.json with: + """ + { + "page": { + "title": "New Page", + "slug": "new-page", + "parent_id": "4f832c2cb0d86d3f42fffffe" + } + } + """ + When I do an API GET request to pages.json + Then the JSON response should contain 5 pages + And the JSON response should contain all pages + + Scenario: Creating new page as a Designer + Given I have a "designer" token + When I do an API GET request to pages.json + Then the JSON response should contain 4 pages + And the JSON response should contain all pages + When I do an API POST to pages.json with: + """ + { + "page": { + "title": "New Page", + "slug": "new-page", + "parent_id": "4f832c2cb0d86d3f42fffffe" + } + } + """ + When I do an API GET request to pages.json + Then the JSON response should contain 5 pages + And the JSON response should contain all pages + + Scenario: Creating new page as an Author + Given I have an "author" token + When I do an API POST to pages.json with: + """ + { + "page": { + "title": "New Page", + "slug": "new-page", + "parent_id": "4f832c2cb0d86d3f42fffffe" + } + } + """ + Then the JSON response should be an access denied error + + # update page + + Scenario: Updating page as an Admin + Given I have an "admin" token + When I do an API PUT to pages/4f832c2cb0d86d3f42fffffe.json with: + """ + { + "title": "Brand new updated title" + } + """ + When I do an API GET request to pages/4f832c2cb0d86d3f42fffffe.json + Then the JSON response should contain: + """ + { + "id": "4f832c2cb0d86d3f42fffffe", + "title": "Brand new updated title" + } + """ + + Scenario: Updating page as a Designer + Given I have a "designer" token + When I do an API PUT to pages/4f832c2cb0d86d3f42fffffe.json with: + """ + { + "title": "Brand new updated title" + } + """ + When I do an API GET request to pages/4f832c2cb0d86d3f42fffffe.json + Then the JSON response should contain: + """ + { + "id": "4f832c2cb0d86d3f42fffffe", + "title": "Brand new updated title" + } + """ + + Scenario: Updating page as an Author + Given I have a "designer" token + When I do an API PUT to pages/4f832c2cb0d86d3f42fffffe.json with: + """ + { + "title": "Brand new updated title" + } + """ + Then the JSON response should be an access denied error diff --git a/features/step_definitions/api_steps.rb b/features/step_definitions/api_steps.rb new file mode 100644 index 00000000..10ff24c1 --- /dev/null +++ b/features/step_definitions/api_steps.rb @@ -0,0 +1,72 @@ + +def new_content_page(page_slug, page_contents = '', template = '') + @home = @site.pages.where(:slug => 'index').first || FactoryGirl.create(:page) + page = @site.pages.build(:slug => page_slug, :body => page_contents, :parent => @home, :title => "some title", :published => true, :raw_template => template) + page.should be_valid + page +end + +def api_base_url + '/locomotive/api' +end + +def do_api_request(type, url, param_string = nil) + params = param_string && JSON.parse(param_string) || {} + @raw_response = do_request(type, api_base_url, url, params) + @response = JSON.parse(@raw_response.body) +end + +Given /^a page named "([^"]*)" with id "([^"]*)"$/ do |name, id| + @page = new_content_page(name) + @page.id = BSON::ObjectId(id) + @page.save! +end + +Given /^I have an? "([^"]*)" token$/ do |role| + @membership = Locomotive::Site.first.memberships.where(:role => role.downcase).first \ + || FactoryGirl.create(role.downcase.to_sym, :site => Locomotive::Site.first) + + login_params = { + :email => @membership.account.email, + :password => 'easyone' + } + response = do_request('POST', api_base_url, 'tokens.json', login_params) + + if response.status == 200 + @token = JSON.parse(response.body)['token'] + else + raise JSON.parse(response.body)['message'] + end + + add_default_params(:auth_token => @token) +end + +When /^I do an API (\w+) (?:request )?to ([\w.\/]+)$/ do |request_type, url| + do_api_request(request_type, url) +end + +When /^I do an API (\w+) (?:request )?to ([\w.\/]+) with:$/ do |request_type, url, param_string| + do_api_request(request_type, url, param_string) +end + +Then /^the JSON response should be the following:$/ do |json| + @response.should == JSON.parse(json) +end + +Then /^the JSON response should contain all pages$/ do + page_ids_in_response = @response.collect { |page| page['id'].to_s }.sort + all_page_ids = Locomotive::Page.all.collect { |page| page.id.to_s }.sort + page_ids_in_response.should == all_page_ids +end + +Then /^the JSON response should contain (\d+) pages$/ do |n| + @response.count.should == n.to_i +end + +Then /^the JSON response should be an access denied error$/ do + @response['message'].should == 'You are not authorized to access this page' +end + +Then /^the JSON response should contain:$/ do |json| + @response.merge(JSON.parse(json)).should == @response +end diff --git a/features/support/http.rb b/features/support/http.rb new file mode 100644 index 00000000..3cc44ce2 --- /dev/null +++ b/features/support/http.rb @@ -0,0 +1,22 @@ +module HTTPHelpers + + attr_accessor :default_params + + def add_default_params(params) + default_params.merge!(params) + end + + def do_request(type, base_url, url, params) + request_method = type.downcase.to_sym + page.driver.send(request_method, "#{base_url}/#{url}", default_params.merge(params)) + end + + protected + + def default_params + @default_params ||= {} + end + +end + +World(HTTPHelpers) From 97edb0e4b7651ad02514768f3d29396d6c14b287 Mon Sep 17 00:00:00 2001 From: Alex Sanford Date: Mon, 16 Apr 2012 15:36:39 -0300 Subject: [PATCH 02/17] Added show action to page API and fixed some features --- .../locomotive/api/pages_controller.rb | 5 ++ features/api/authorization/pages.feature | 51 +++++++++++++++++-- features/step_definitions/api_steps.rb | 9 +++- 3 files changed, 58 insertions(+), 7 deletions(-) diff --git a/app/controllers/locomotive/api/pages_controller.rb b/app/controllers/locomotive/api/pages_controller.rb index df7734d8..0f4309d4 100644 --- a/app/controllers/locomotive/api/pages_controller.rb +++ b/app/controllers/locomotive/api/pages_controller.rb @@ -7,6 +7,11 @@ module Locomotive respond_with(@pages) end + def show + @page = current_site.pages.find(params[:id]) + respond_with(@page) + end + def create @page = current_site.pages.create(params[:page]) respond_with @page, :location => main_app.locomotive_api_pages_url diff --git a/features/api/authorization/pages.feature b/features/api/authorization/pages.feature index 67286d8f..0e1028b4 100644 --- a/features/api/authorization/pages.feature +++ b/features/api/authorization/pages.feature @@ -40,6 +40,41 @@ Feature: Pages When I do an API GET request to pages.json Then the JSON response should contain all pages + # showing page + + Scenario: Accessing page as an Admin + Given I have an "admin" token + When I do an API GET request to pages/4f832c2cb0d86d3f42fffffe.json + Then the JSON response hash should contain: + """ + { + "id": "4f832c2cb0d86d3f42fffffe", + "slug": "hello-world" + } + """ + + Scenario: Accessing page as a Designer + Given I have a "designer" token + When I do an API GET request to pages/4f832c2cb0d86d3f42fffffe.json + Then the JSON response hash should contain: + """ + { + "id": "4f832c2cb0d86d3f42fffffe", + "slug": "hello-world" + } + """ + + Scenario: Accessing page as an Author + Given I have an "author" token + When I do an API GET request to pages/4f832c2cb0d86d3f42fffffe.json + Then the JSON response hash should contain: + """ + { + "id": "4f832c2cb0d86d3f42fffffe", + "slug": "hello-world" + } + """ + # create page Scenario: Creating new page as an Admin @@ -101,11 +136,13 @@ Feature: Pages When I do an API PUT to pages/4f832c2cb0d86d3f42fffffe.json with: """ { - "title": "Brand new updated title" + "page": { + "title": "Brand new updated title" + } } """ When I do an API GET request to pages/4f832c2cb0d86d3f42fffffe.json - Then the JSON response should contain: + Then the JSON response hash should contain: """ { "id": "4f832c2cb0d86d3f42fffffe", @@ -118,11 +155,13 @@ Feature: Pages When I do an API PUT to pages/4f832c2cb0d86d3f42fffffe.json with: """ { - "title": "Brand new updated title" + "page": { + "title": "Brand new updated title" + } } """ When I do an API GET request to pages/4f832c2cb0d86d3f42fffffe.json - Then the JSON response should contain: + Then the JSON response hash should contain: """ { "id": "4f832c2cb0d86d3f42fffffe", @@ -135,7 +174,9 @@ Feature: Pages When I do an API PUT to pages/4f832c2cb0d86d3f42fffffe.json with: """ { - "title": "Brand new updated title" + "page": { + "title": "Brand new updated title" + } } """ Then the JSON response should be an access denied error diff --git a/features/step_definitions/api_steps.rb b/features/step_definitions/api_steps.rb index 10ff24c1..c4cece31 100644 --- a/features/step_definitions/api_steps.rb +++ b/features/step_definitions/api_steps.rb @@ -67,6 +67,11 @@ Then /^the JSON response should be an access denied error$/ do @response['message'].should == 'You are not authorized to access this page' end -Then /^the JSON response should contain:$/ do |json| - @response.merge(JSON.parse(json)).should == @response +Then /^the JSON response hash should contain:$/ do |json| + sub_response = {} + parsed_json = JSON.parse(json) + parsed_json.each do |k, v| + sub_response[k] = @response[k] + end + sub_response.should == parsed_json end From edd236b202613a9afa36f683fbc76cc5a1a74b4d Mon Sep 17 00:00:00 2001 From: Alex Sanford Date: Tue, 17 Apr 2012 10:12:42 -0300 Subject: [PATCH 03/17] Got authorization working for pages --- app/controllers/locomotive/api/base_controller.rb | 4 +--- app/controllers/locomotive/api/pages_controller.rb | 2 ++ features/step_definitions/api_steps.rb | 13 +++++++++---- 3 files changed, 12 insertions(+), 7 deletions(-) diff --git a/app/controllers/locomotive/api/base_controller.rb b/app/controllers/locomotive/api/base_controller.rb index 1afc9d41..ba949003 100644 --- a/app/controllers/locomotive/api/base_controller.rb +++ b/app/controllers/locomotive/api/base_controller.rb @@ -7,8 +7,6 @@ module Locomotive skip_before_filter :verify_authenticity_token - skip_load_and_authorize_resource - before_filter :require_account before_filter :require_site @@ -40,4 +38,4 @@ module Locomotive end end -end \ No newline at end of file +end diff --git a/app/controllers/locomotive/api/pages_controller.rb b/app/controllers/locomotive/api/pages_controller.rb index 0f4309d4..6e6001cf 100644 --- a/app/controllers/locomotive/api/pages_controller.rb +++ b/app/controllers/locomotive/api/pages_controller.rb @@ -2,6 +2,8 @@ module Locomotive module Api class PagesController < BaseController + load_and_authorize_resource :class => Locomotive::Page + def index @pages = current_site.pages.all respond_with(@pages) diff --git a/features/step_definitions/api_steps.rb b/features/step_definitions/api_steps.rb index c4cece31..7a40474a 100644 --- a/features/step_definitions/api_steps.rb +++ b/features/step_definitions/api_steps.rb @@ -11,9 +11,13 @@ def api_base_url end def do_api_request(type, url, param_string = nil) - params = param_string && JSON.parse(param_string) || {} - @raw_response = do_request(type, api_base_url, url, params) - @response = JSON.parse(@raw_response.body) + begin + params = param_string && JSON.parse(param_string) || {} + @raw_response = do_request(type, api_base_url, url, params) + @response = JSON.parse(@raw_response.body) + rescue Exception + @error = $! + end end Given /^a page named "([^"]*)" with id "([^"]*)"$/ do |name, id| @@ -64,7 +68,8 @@ Then /^the JSON response should contain (\d+) pages$/ do |n| end Then /^the JSON response should be an access denied error$/ do - @response['message'].should == 'You are not authorized to access this page' + @error.should_not be_nil + @error.message.should == 'You are not authorized to access this page.' end Then /^the JSON response hash should contain:$/ do |json| From 494e3c9b514813cc57c8b043bcfbaa09f0bdcdd0 Mon Sep 17 00:00:00 2001 From: Alex Sanford Date: Tue, 17 Apr 2012 12:02:29 -0300 Subject: [PATCH 04/17] Fixed author page update scenario --- features/api/authorization/pages.feature | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/features/api/authorization/pages.feature b/features/api/authorization/pages.feature index 0e1028b4..02619255 100644 --- a/features/api/authorization/pages.feature +++ b/features/api/authorization/pages.feature @@ -170,7 +170,7 @@ Feature: Pages """ Scenario: Updating page as an Author - Given I have a "designer" token + Given I have a "author" token When I do an API PUT to pages/4f832c2cb0d86d3f42fffffe.json with: """ { @@ -179,4 +179,11 @@ Feature: Pages } } """ - Then the JSON response should be an access denied error + When I do an API GET request to pages/4f832c2cb0d86d3f42fffffe.json + Then the JSON response hash should contain: + """ + { + "id": "4f832c2cb0d86d3f42fffffe", + "title": "Brand new updated title" + } + """ From f53ab18f90d6461f61d9973934fb7fa21353ad8f Mon Sep 17 00:00:00 2001 From: Alex Sanford Date: Tue, 24 Apr 2012 12:04:24 -0300 Subject: [PATCH 05/17] Added features for destroying pages --- features/api/authorization/pages.feature | 26 ++++++++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/features/api/authorization/pages.feature b/features/api/authorization/pages.feature index 02619255..c9d0f41d 100644 --- a/features/api/authorization/pages.feature +++ b/features/api/authorization/pages.feature @@ -187,3 +187,29 @@ Feature: Pages "title": "Brand new updated title" } """ + + # destroy page + + Scenario: Destroying page as an Admin + Given I have an "admin" token + When I do an API GET request to pages.json + Then the JSON response should contain 4 pages + When I do an API DELETE to pages/4f832c2cb0d86d3f42fffffe.json + When I do an API GET request to pages.json + Then the JSON response should contain 3 pages + + Scenario: Destroying page as a Designer + Given I have a "designer" token + When I do an API GET request to pages.json + Then the JSON response should contain 4 pages + When I do an API DELETE to pages/4f832c2cb0d86d3f42fffffe.json + When I do an API GET request to pages.json + Then the JSON response should contain 3 pages + + Scenario: Deleting page as an Author + Given I have a "author" token + When I do an API GET request to pages.json + Then the JSON response should contain 4 pages + When I do an API DELETE to pages/4f832c2cb0d86d3f42fffffe.json + When I do an API GET request to pages.json + Then the JSON response should contain 3 pages From bf65fa47f37a584c870573d792a676dd40b92346 Mon Sep 17 00:00:00 2001 From: Alex Sanford Date: Tue, 24 Apr 2012 12:13:26 -0300 Subject: [PATCH 06/17] Added destroy API method for pages and fixed feature --- app/controllers/locomotive/api/pages_controller.rb | 6 ++++++ features/api/authorization/pages.feature | 3 +-- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/app/controllers/locomotive/api/pages_controller.rb b/app/controllers/locomotive/api/pages_controller.rb index 6e6001cf..0af07e37 100644 --- a/app/controllers/locomotive/api/pages_controller.rb +++ b/app/controllers/locomotive/api/pages_controller.rb @@ -25,6 +25,12 @@ module Locomotive respond_with @page, :location => main_app.locomotive_api_pages_url end + def destroy + @page = current_site.pages.find(params[:id]) + @page.destroy + respond_with @page + end + end end diff --git a/features/api/authorization/pages.feature b/features/api/authorization/pages.feature index c9d0f41d..14b3170e 100644 --- a/features/api/authorization/pages.feature +++ b/features/api/authorization/pages.feature @@ -211,5 +211,4 @@ Feature: Pages When I do an API GET request to pages.json Then the JSON response should contain 4 pages When I do an API DELETE to pages/4f832c2cb0d86d3f42fffffe.json - When I do an API GET request to pages.json - Then the JSON response should contain 3 pages + Then the JSON response should be an access denied error From 1344463222406510cf33a7f710bfd6d9feff517b Mon Sep 17 00:00:00 2001 From: Alex Sanford Date: Tue, 24 Apr 2012 12:47:32 -0300 Subject: [PATCH 07/17] Cleaned up features --- .../api/authorization/content_assets.feature | 212 ++++++++++++++++++ features/step_definitions/api_steps.rb | 13 -- features/step_definitions/page_steps.rb | 17 +- 3 files changed, 227 insertions(+), 15 deletions(-) create mode 100644 features/api/authorization/content_assets.feature diff --git a/features/api/authorization/content_assets.feature b/features/api/authorization/content_assets.feature new file mode 100644 index 00000000..e86f0ab5 --- /dev/null +++ b/features/api/authorization/content_assets.feature @@ -0,0 +1,212 @@ +Feature: Content Assets + In order to ensure content assets are not tampered with + As an admin, designer or author + I will be restricted based on my role + + Background: + Given I have the site: "test site" set up + And I have a designer and an author + And a page named "hello-world" with id "4f832c2cb0d86d3f42fffffe" + And a page named "goodbye-world" with id "4f832c2cb0d86d3f42ffffff" + + # unauthenticated + + Scenario: As an unauthenticated user + Given I am not authenticated + When I do an API GET to pages.json + Then the JSON response should be the following: + """ + { + "error": "You need to sign in or sign up before continuing." + } + """ + + # listing pages + + Scenario: Accessing pages as an Admin + Given I have an "admin" token + When I do an API GET request to pages.json + Then the JSON response should contain all pages + + Scenario: Accessing pages as a Designer + Given I have a "designer" token + When I do an API GET request to pages.json + Then the JSON response should contain all pages + + Scenario: Accessing pages as an Author + Given I have an "author" token + When I do an API GET request to pages.json + Then the JSON response should contain all pages + + # showing page + + Scenario: Accessing page as an Admin + Given I have an "admin" token + When I do an API GET request to pages/4f832c2cb0d86d3f42fffffe.json + Then the JSON response hash should contain: + """ + { + "id": "4f832c2cb0d86d3f42fffffe", + "slug": "hello-world" + } + """ + + Scenario: Accessing page as a Designer + Given I have a "designer" token + When I do an API GET request to pages/4f832c2cb0d86d3f42fffffe.json + Then the JSON response hash should contain: + """ + { + "id": "4f832c2cb0d86d3f42fffffe", + "slug": "hello-world" + } + """ + + Scenario: Accessing page as an Author + Given I have an "author" token + When I do an API GET request to pages/4f832c2cb0d86d3f42fffffe.json + Then the JSON response hash should contain: + """ + { + "id": "4f832c2cb0d86d3f42fffffe", + "slug": "hello-world" + } + """ + + # create page + + Scenario: Creating new page as an Admin + Given I have an "admin" token + When I do an API GET request to pages.json + Then the JSON response should contain 4 pages + And the JSON response should contain all pages + When I do an API POST to pages.json with: + """ + { + "page": { + "title": "New Page", + "slug": "new-page", + "parent_id": "4f832c2cb0d86d3f42fffffe" + } + } + """ + When I do an API GET request to pages.json + Then the JSON response should contain 5 pages + And the JSON response should contain all pages + + Scenario: Creating new page as a Designer + Given I have a "designer" token + When I do an API GET request to pages.json + Then the JSON response should contain 4 pages + And the JSON response should contain all pages + When I do an API POST to pages.json with: + """ + { + "page": { + "title": "New Page", + "slug": "new-page", + "parent_id": "4f832c2cb0d86d3f42fffffe" + } + } + """ + When I do an API GET request to pages.json + Then the JSON response should contain 5 pages + And the JSON response should contain all pages + + Scenario: Creating new page as an Author + Given I have an "author" token + When I do an API POST to pages.json with: + """ + { + "page": { + "title": "New Page", + "slug": "new-page", + "parent_id": "4f832c2cb0d86d3f42fffffe" + } + } + """ + Then the JSON response should be an access denied error + + # update page + + Scenario: Updating page as an Admin + Given I have an "admin" token + When I do an API PUT to pages/4f832c2cb0d86d3f42fffffe.json with: + """ + { + "page": { + "title": "Brand new updated title" + } + } + """ + When I do an API GET request to pages/4f832c2cb0d86d3f42fffffe.json + Then the JSON response hash should contain: + """ + { + "id": "4f832c2cb0d86d3f42fffffe", + "title": "Brand new updated title" + } + """ + + Scenario: Updating page as a Designer + Given I have a "designer" token + When I do an API PUT to pages/4f832c2cb0d86d3f42fffffe.json with: + """ + { + "page": { + "title": "Brand new updated title" + } + } + """ + When I do an API GET request to pages/4f832c2cb0d86d3f42fffffe.json + Then the JSON response hash should contain: + """ + { + "id": "4f832c2cb0d86d3f42fffffe", + "title": "Brand new updated title" + } + """ + + Scenario: Updating page as an Author + Given I have a "author" token + When I do an API PUT to pages/4f832c2cb0d86d3f42fffffe.json with: + """ + { + "page": { + "title": "Brand new updated title" + } + } + """ + When I do an API GET request to pages/4f832c2cb0d86d3f42fffffe.json + Then the JSON response hash should contain: + """ + { + "id": "4f832c2cb0d86d3f42fffffe", + "title": "Brand new updated title" + } + """ + + # destroy page + + Scenario: Destroying page as an Admin + Given I have an "admin" token + When I do an API GET request to pages.json + Then the JSON response should contain 4 pages + When I do an API DELETE to pages/4f832c2cb0d86d3f42fffffe.json + When I do an API GET request to pages.json + Then the JSON response should contain 3 pages + + Scenario: Destroying page as a Designer + Given I have a "designer" token + When I do an API GET request to pages.json + Then the JSON response should contain 4 pages + When I do an API DELETE to pages/4f832c2cb0d86d3f42fffffe.json + When I do an API GET request to pages.json + Then the JSON response should contain 3 pages + + Scenario: Deleting page as an Author + Given I have a "author" token + When I do an API GET request to pages.json + Then the JSON response should contain 4 pages + When I do an API DELETE to pages/4f832c2cb0d86d3f42fffffe.json + Then the JSON response should be an access denied error diff --git a/features/step_definitions/api_steps.rb b/features/step_definitions/api_steps.rb index 7a40474a..0ecaf4e9 100644 --- a/features/step_definitions/api_steps.rb +++ b/features/step_definitions/api_steps.rb @@ -1,11 +1,4 @@ -def new_content_page(page_slug, page_contents = '', template = '') - @home = @site.pages.where(:slug => 'index').first || FactoryGirl.create(:page) - page = @site.pages.build(:slug => page_slug, :body => page_contents, :parent => @home, :title => "some title", :published => true, :raw_template => template) - page.should be_valid - page -end - def api_base_url '/locomotive/api' end @@ -20,12 +13,6 @@ def do_api_request(type, url, param_string = nil) end end -Given /^a page named "([^"]*)" with id "([^"]*)"$/ do |name, id| - @page = new_content_page(name) - @page.id = BSON::ObjectId(id) - @page.save! -end - Given /^I have an? "([^"]*)" token$/ do |role| @membership = Locomotive::Site.first.memberships.where(:role => role.downcase).first \ || FactoryGirl.create(role.downcase.to_sym, :site => Locomotive::Site.first) diff --git a/features/step_definitions/page_steps.rb b/features/step_definitions/page_steps.rb index c4d46fdb..d06e1c9d 100644 --- a/features/step_definitions/page_steps.rb +++ b/features/step_definitions/page_steps.rb @@ -2,9 +2,16 @@ # helps create a simple content page (parent: "index") with a slug, contents, and template def create_content_page(page_slug, page_contents, template = nil) - @home = @site.pages.where(:slug => "index").first || FactoryGirl.create(:page) - page = @site.pages.create(:slug => page_slug, :body => page_contents, :parent => @home, :title => "some title", :published => true, :raw_template => template) + page = new_content_page(page_slug, page_contents, template) page.should be_valid + page.save! + page +end + +# build page without saving +def new_content_page(page_slug, page_contents, template = nil) + @home = @site.pages.where(:slug => "index").first || FactoryGirl.create(:page) + page = @site.pages.new(:slug => page_slug, :body => page_contents, :parent => @home, :title => "some title", :published => true, :raw_template => template) page end @@ -17,6 +24,12 @@ Given /^a page named "([^"]*)" with the template:$/ do |page_slug, template| @page = create_content_page(page_slug, '', template) end +Given /^a page named "([^"]*)" with id "([^"]*)"$/ do |page_slug, id| + @page = new_content_page(page_slug, '') + @page.id = BSON::ObjectId(id) + @page.save! +end + # change the title When /^I change the page title to "([^"]*)"$/ do |page_title| page.evaluate_script "window.prompt = function() { return '#{page_title}'; }" From 75e694a6f002887af5cb96456e2e4728057786ba Mon Sep 17 00:00:00 2001 From: Alex Sanford Date: Tue, 24 Apr 2012 13:36:25 -0300 Subject: [PATCH 08/17] Cleaned up API features --- .../api/authorization/content_assets.feature | 212 ------------------ features/api/authorization/pages.feature | 135 +++++------ features/step_definitions/api_steps.rb | 25 --- features/support/http.rb | 2 +- 4 files changed, 55 insertions(+), 319 deletions(-) delete mode 100644 features/api/authorization/content_assets.feature diff --git a/features/api/authorization/content_assets.feature b/features/api/authorization/content_assets.feature deleted file mode 100644 index e86f0ab5..00000000 --- a/features/api/authorization/content_assets.feature +++ /dev/null @@ -1,212 +0,0 @@ -Feature: Content Assets - In order to ensure content assets are not tampered with - As an admin, designer or author - I will be restricted based on my role - - Background: - Given I have the site: "test site" set up - And I have a designer and an author - And a page named "hello-world" with id "4f832c2cb0d86d3f42fffffe" - And a page named "goodbye-world" with id "4f832c2cb0d86d3f42ffffff" - - # unauthenticated - - Scenario: As an unauthenticated user - Given I am not authenticated - When I do an API GET to pages.json - Then the JSON response should be the following: - """ - { - "error": "You need to sign in or sign up before continuing." - } - """ - - # listing pages - - Scenario: Accessing pages as an Admin - Given I have an "admin" token - When I do an API GET request to pages.json - Then the JSON response should contain all pages - - Scenario: Accessing pages as a Designer - Given I have a "designer" token - When I do an API GET request to pages.json - Then the JSON response should contain all pages - - Scenario: Accessing pages as an Author - Given I have an "author" token - When I do an API GET request to pages.json - Then the JSON response should contain all pages - - # showing page - - Scenario: Accessing page as an Admin - Given I have an "admin" token - When I do an API GET request to pages/4f832c2cb0d86d3f42fffffe.json - Then the JSON response hash should contain: - """ - { - "id": "4f832c2cb0d86d3f42fffffe", - "slug": "hello-world" - } - """ - - Scenario: Accessing page as a Designer - Given I have a "designer" token - When I do an API GET request to pages/4f832c2cb0d86d3f42fffffe.json - Then the JSON response hash should contain: - """ - { - "id": "4f832c2cb0d86d3f42fffffe", - "slug": "hello-world" - } - """ - - Scenario: Accessing page as an Author - Given I have an "author" token - When I do an API GET request to pages/4f832c2cb0d86d3f42fffffe.json - Then the JSON response hash should contain: - """ - { - "id": "4f832c2cb0d86d3f42fffffe", - "slug": "hello-world" - } - """ - - # create page - - Scenario: Creating new page as an Admin - Given I have an "admin" token - When I do an API GET request to pages.json - Then the JSON response should contain 4 pages - And the JSON response should contain all pages - When I do an API POST to pages.json with: - """ - { - "page": { - "title": "New Page", - "slug": "new-page", - "parent_id": "4f832c2cb0d86d3f42fffffe" - } - } - """ - When I do an API GET request to pages.json - Then the JSON response should contain 5 pages - And the JSON response should contain all pages - - Scenario: Creating new page as a Designer - Given I have a "designer" token - When I do an API GET request to pages.json - Then the JSON response should contain 4 pages - And the JSON response should contain all pages - When I do an API POST to pages.json with: - """ - { - "page": { - "title": "New Page", - "slug": "new-page", - "parent_id": "4f832c2cb0d86d3f42fffffe" - } - } - """ - When I do an API GET request to pages.json - Then the JSON response should contain 5 pages - And the JSON response should contain all pages - - Scenario: Creating new page as an Author - Given I have an "author" token - When I do an API POST to pages.json with: - """ - { - "page": { - "title": "New Page", - "slug": "new-page", - "parent_id": "4f832c2cb0d86d3f42fffffe" - } - } - """ - Then the JSON response should be an access denied error - - # update page - - Scenario: Updating page as an Admin - Given I have an "admin" token - When I do an API PUT to pages/4f832c2cb0d86d3f42fffffe.json with: - """ - { - "page": { - "title": "Brand new updated title" - } - } - """ - When I do an API GET request to pages/4f832c2cb0d86d3f42fffffe.json - Then the JSON response hash should contain: - """ - { - "id": "4f832c2cb0d86d3f42fffffe", - "title": "Brand new updated title" - } - """ - - Scenario: Updating page as a Designer - Given I have a "designer" token - When I do an API PUT to pages/4f832c2cb0d86d3f42fffffe.json with: - """ - { - "page": { - "title": "Brand new updated title" - } - } - """ - When I do an API GET request to pages/4f832c2cb0d86d3f42fffffe.json - Then the JSON response hash should contain: - """ - { - "id": "4f832c2cb0d86d3f42fffffe", - "title": "Brand new updated title" - } - """ - - Scenario: Updating page as an Author - Given I have a "author" token - When I do an API PUT to pages/4f832c2cb0d86d3f42fffffe.json with: - """ - { - "page": { - "title": "Brand new updated title" - } - } - """ - When I do an API GET request to pages/4f832c2cb0d86d3f42fffffe.json - Then the JSON response hash should contain: - """ - { - "id": "4f832c2cb0d86d3f42fffffe", - "title": "Brand new updated title" - } - """ - - # destroy page - - Scenario: Destroying page as an Admin - Given I have an "admin" token - When I do an API GET request to pages.json - Then the JSON response should contain 4 pages - When I do an API DELETE to pages/4f832c2cb0d86d3f42fffffe.json - When I do an API GET request to pages.json - Then the JSON response should contain 3 pages - - Scenario: Destroying page as a Designer - Given I have a "designer" token - When I do an API GET request to pages.json - Then the JSON response should contain 4 pages - When I do an API DELETE to pages/4f832c2cb0d86d3f42fffffe.json - When I do an API GET request to pages.json - Then the JSON response should contain 3 pages - - Scenario: Deleting page as an Author - Given I have a "author" token - When I do an API GET request to pages.json - Then the JSON response should contain 4 pages - When I do an API DELETE to pages/4f832c2cb0d86d3f42fffffe.json - Then the JSON response should be an access denied error diff --git a/features/api/authorization/pages.feature b/features/api/authorization/pages.feature index 14b3170e..2cc7d855 100644 --- a/features/api/authorization/pages.feature +++ b/features/api/authorization/pages.feature @@ -16,72 +16,55 @@ Feature: Pages Scenario: As an unauthenticated user Given I am not authenticated When I do an API GET to pages.json - Then the JSON response should be the following: - """ - { - "error": "You need to sign in or sign up before continuing." - } - """ + Then the JSON response at "error" should be "You need to sign in or sign up before continuing." # listing pages Scenario: Accessing pages as an Admin - Given I have an "admin" token + Given I have an "admin" API token When I do an API GET request to pages.json - Then the JSON response should contain all pages + Then the JSON response should be an array + And the JSON response should have 4 entries Scenario: Accessing pages as a Designer - Given I have a "designer" token + Given I have a "designer" API token When I do an API GET request to pages.json - Then the JSON response should contain all pages + Then the JSON response should be an array + And the JSON response should have 4 entries Scenario: Accessing pages as an Author - Given I have an "author" token + Given I have an "author" API token When I do an API GET request to pages.json - Then the JSON response should contain all pages + Then the JSON response should be an array + And the JSON response should have 4 entries # showing page Scenario: Accessing page as an Admin - Given I have an "admin" token + Given I have an "admin" API token When I do an API GET request to pages/4f832c2cb0d86d3f42fffffe.json - Then the JSON response hash should contain: - """ - { - "id": "4f832c2cb0d86d3f42fffffe", - "slug": "hello-world" - } - """ + Then the JSON response at "id" should be "4f832c2cb0d86d3f42fffffe" + And the JSON response at "slug" should be "hello-world" Scenario: Accessing page as a Designer - Given I have a "designer" token + Given I have a "designer" API token When I do an API GET request to pages/4f832c2cb0d86d3f42fffffe.json - Then the JSON response hash should contain: - """ - { - "id": "4f832c2cb0d86d3f42fffffe", - "slug": "hello-world" - } - """ + Then the JSON response at "id" should be "4f832c2cb0d86d3f42fffffe" + And the JSON response at "slug" should be "hello-world" Scenario: Accessing page as an Author - Given I have an "author" token + Given I have an "author" API token When I do an API GET request to pages/4f832c2cb0d86d3f42fffffe.json - Then the JSON response hash should contain: - """ - { - "id": "4f832c2cb0d86d3f42fffffe", - "slug": "hello-world" - } - """ + Then the JSON response at "id" should be "4f832c2cb0d86d3f42fffffe" + And the JSON response at "slug" should be "hello-world" # create page Scenario: Creating new page as an Admin - Given I have an "admin" token + Given I have an "admin" API token When I do an API GET request to pages.json - Then the JSON response should contain 4 pages - And the JSON response should contain all pages + Then the JSON response should be an array + And the JSON response should have 4 entries When I do an API POST to pages.json with: """ { @@ -93,14 +76,14 @@ Feature: Pages } """ When I do an API GET request to pages.json - Then the JSON response should contain 5 pages - And the JSON response should contain all pages + Then the JSON response should be an array + And the JSON response should have 5 entries Scenario: Creating new page as a Designer - Given I have a "designer" token + Given I have a "designer" API token When I do an API GET request to pages.json - Then the JSON response should contain 4 pages - And the JSON response should contain all pages + Then the JSON response should be an array + And the JSON response should have 4 entries When I do an API POST to pages.json with: """ { @@ -112,11 +95,11 @@ Feature: Pages } """ When I do an API GET request to pages.json - Then the JSON response should contain 5 pages - And the JSON response should contain all pages + Then the JSON response should be an array + And the JSON response should have 5 entries Scenario: Creating new page as an Author - Given I have an "author" token + Given I have an "author" API token When I do an API POST to pages.json with: """ { @@ -127,12 +110,12 @@ Feature: Pages } } """ - Then the JSON response should be an access denied error + Then an access denied error should occur # update page Scenario: Updating page as an Admin - Given I have an "admin" token + Given I have an "admin" API token When I do an API PUT to pages/4f832c2cb0d86d3f42fffffe.json with: """ { @@ -142,16 +125,11 @@ Feature: Pages } """ When I do an API GET request to pages/4f832c2cb0d86d3f42fffffe.json - Then the JSON response hash should contain: - """ - { - "id": "4f832c2cb0d86d3f42fffffe", - "title": "Brand new updated title" - } - """ + Then the JSON response at "id" should be "4f832c2cb0d86d3f42fffffe" + And the JSON response at "title" should be "Brand new updated title" Scenario: Updating page as a Designer - Given I have a "designer" token + Given I have a "designer" API token When I do an API PUT to pages/4f832c2cb0d86d3f42fffffe.json with: """ { @@ -161,16 +139,11 @@ Feature: Pages } """ When I do an API GET request to pages/4f832c2cb0d86d3f42fffffe.json - Then the JSON response hash should contain: - """ - { - "id": "4f832c2cb0d86d3f42fffffe", - "title": "Brand new updated title" - } - """ + Then the JSON response at "id" should be "4f832c2cb0d86d3f42fffffe" + And the JSON response at "title" should be "Brand new updated title" Scenario: Updating page as an Author - Given I have a "author" token + Given I have a "author" API token When I do an API PUT to pages/4f832c2cb0d86d3f42fffffe.json with: """ { @@ -180,35 +153,35 @@ Feature: Pages } """ When I do an API GET request to pages/4f832c2cb0d86d3f42fffffe.json - Then the JSON response hash should contain: - """ - { - "id": "4f832c2cb0d86d3f42fffffe", - "title": "Brand new updated title" - } - """ + Then the JSON response at "id" should be "4f832c2cb0d86d3f42fffffe" + And the JSON response at "title" should be "Brand new updated title" # destroy page Scenario: Destroying page as an Admin - Given I have an "admin" token + Given I have an "admin" API token When I do an API GET request to pages.json - Then the JSON response should contain 4 pages + Then the JSON response should be an array + And the JSON response should have 4 entries When I do an API DELETE to pages/4f832c2cb0d86d3f42fffffe.json When I do an API GET request to pages.json - Then the JSON response should contain 3 pages + Then the JSON response should be an array + And the JSON response should have 3 entries Scenario: Destroying page as a Designer - Given I have a "designer" token + Given I have a "designer" API token When I do an API GET request to pages.json - Then the JSON response should contain 4 pages + Then the JSON response should be an array + And the JSON response should have 4 entries When I do an API DELETE to pages/4f832c2cb0d86d3f42fffffe.json When I do an API GET request to pages.json - Then the JSON response should contain 3 pages + Then the JSON response should be an array + And the JSON response should have 3 entries Scenario: Deleting page as an Author - Given I have a "author" token + Given I have a "author" API token When I do an API GET request to pages.json - Then the JSON response should contain 4 pages + Then the JSON response should be an array + And the JSON response should have 4 entries When I do an API DELETE to pages/4f832c2cb0d86d3f42fffffe.json - Then the JSON response should be an access denied error + Then an access denied error should occur diff --git a/features/step_definitions/api_steps.rb b/features/step_definitions/api_steps.rb index 6b3d82b9..42a428c1 100644 --- a/features/step_definitions/api_steps.rb +++ b/features/step_definitions/api_steps.rb @@ -17,10 +17,6 @@ def last_json @json_response.try(:body) || page.source end -def parsed_response - @parsed_response ||= JSON.parse(last_json) -end - Given /^I have an? "([^"]*)" API token$/ do |role| @membership = Locomotive::Site.first.memberships.where(:role => role.downcase).first \ || FactoryGirl.create(role.downcase.to_sym, :site => Locomotive::Site.first) @@ -67,28 +63,7 @@ When /^I do an API (\w+) (?:request )?to ([\w.\/]+) with:$/ do |request_type, ur do_api_request(request_type, url, param_string) end -Then /^the JSON response should contain all pages$/ do - page_ids_in_response = parsed_response.collect { |page| page['id'].to_s }.sort - all_page_ids = Locomotive::Page.all.collect { |page| page.id.to_s }.sort - page_ids_in_response.should == all_page_ids -end - -Then /^the JSON response should contain (\d+) pages$/ do |n| - parsed_response.count.should == n.to_i -end - Then /^an access denied error should occur$/ do @error.should_not be_nil @error.message.should == 'You are not authorized to access this page.' end - -=begin -Then /^the JSON response hash should contain:$/ do |json| - sub_response = {} - parsed_json = JSON.parse(json) - parsed_json.each do |k, v| - sub_response[k] = @response[k] - end - sub_response.should == parsed_json -end -=end diff --git a/features/support/http.rb b/features/support/http.rb index 3cc44ce2..5c08e0ee 100644 --- a/features/support/http.rb +++ b/features/support/http.rb @@ -8,7 +8,7 @@ module HTTPHelpers def do_request(type, base_url, url, params) request_method = type.downcase.to_sym - page.driver.send(request_method, "#{base_url}/#{url}", default_params.merge(params)) + send(request_method, "#{base_url}/#{url}", default_params.merge(params)) end protected From 44aadb89260904355a563ab94018411d5c511536 Mon Sep 17 00:00:00 2001 From: Alex Sanford Date: Tue, 24 Apr 2012 15:40:54 -0300 Subject: [PATCH 09/17] Added auth feature for content_types --- .../api/content_types_controller.rb | 13 + app/models/locomotive/ability.rb | 2 + .../api/authorization/content_types.feature | 237 ++++++++++++++++++ .../step_definitions/content_types_steps.rb | 20 +- 4 files changed, 270 insertions(+), 2 deletions(-) create mode 100644 features/api/authorization/content_types.feature diff --git a/app/controllers/locomotive/api/content_types_controller.rb b/app/controllers/locomotive/api/content_types_controller.rb index afd4ed6a..9b5014d6 100644 --- a/app/controllers/locomotive/api/content_types_controller.rb +++ b/app/controllers/locomotive/api/content_types_controller.rb @@ -2,11 +2,18 @@ module Locomotive module Api class ContentTypesController < BaseController + load_and_authorize_resource :class => Locomotive::ContentType + def index @content_types = current_site.content_types respond_with(@content_types) end + def show + @content_type = current_site.content_types.find(params[:id]) + respond_with @content_type + end + def create @content_type = current_site.content_types.create(params[:content_type]) respond_with @content_type, :location => main_app.locomotive_api_content_types_url @@ -18,6 +25,12 @@ module Locomotive respond_with @content_type, :location => main_app.locomotive_api_content_types_url end + def destroy + @content_type = current_site.content_types.find(params[:id]) + @content_type.destroy + respond_with @content_type + end + end end end diff --git a/app/models/locomotive/ability.rb b/app/models/locomotive/ability.rb index db42da23..f5f841f4 100644 --- a/app/models/locomotive/ability.rb +++ b/app/models/locomotive/ability.rb @@ -37,6 +37,8 @@ module Locomotive can :touch, Site do |site| site == @site end + + can :read, ContentType end def setup_designer_permissions! diff --git a/features/api/authorization/content_types.feature b/features/api/authorization/content_types.feature new file mode 100644 index 00000000..0012d91a --- /dev/null +++ b/features/api/authorization/content_types.feature @@ -0,0 +1,237 @@ +Feature: Content Types + In order to ensure content types are not tampered with + As an admin, designer or author + I will be restricted based on my role + + Background: + Given I have the site: "test site" set up + And I have a custom model named "Projects" with id "4f832c2cb0d86d3f42fffffe" and + | label | type | required | + | Name | string | true | + | Description | text | false | + And I have a designer and an author + + Scenario: As an unauthenticated user + Given I am not authenticated + When I do an API GET to content_types.json + Then the JSON response at "error" should be "You need to sign in or sign up before continuing." + + # listing content types + + Scenario: Accessing content types as an Admin + Given I have an "admin" API token + When I do an API GET request to content_types.json + Then the JSON response should be an array + And the JSON response should have 1 entry + + Scenario: Accessing content types as a Designer + Given I have a "designer" API token + When I do an API GET request to content_types.json + Then the JSON response should be an array + And the JSON response should have 1 entry + + Scenario: Accessing content types as an Author + Given I have an "author" API token + When I do an API GET request to content_types.json + Then the JSON response should be an array + And the JSON response should have 1 entry + + # showing content type + + Scenario: Accessing content type as an Admin + Given I have an "admin" API token + When I do an API GET request to content_types/4f832c2cb0d86d3f42fffffe.json + Then the JSON response at "id" should be "4f832c2cb0d86d3f42fffffe" + And the JSON response at "name" should be "Projects" + + Scenario: Accessing content type as a Designer + Given I have a "designer" API token + When I do an API GET request to content_types/4f832c2cb0d86d3f42fffffe.json + Then the JSON response at "id" should be "4f832c2cb0d86d3f42fffffe" + And the JSON response at "name" should be "Projects" + + Scenario: Accessing content type as an Author + Given I have an "author" API token + When I do an API GET request to content_types/4f832c2cb0d86d3f42fffffe.json + Then the JSON response at "id" should be "4f832c2cb0d86d3f42fffffe" + And the JSON response at "name" should be "Projects" + + # create content type + + Scenario: Creating new content type as an Admin + Given I have an "admin" API token + When I do an API GET request to content_types.json + Then the JSON response should be an array + And the JSON response should have 1 entry + When I do an API POST to content_types.json with: + """ + { + "content_type": { + "name": "Employees", + "slug": "employees", + "entries_custom_fields": [ + { + "label": "Name", + "name": "name", + "type": "string" + }, + { + "label": "Position", + "name": "position", + "type": "string" + } + ] + } + } + """ + When I do an API GET request to content_types.json + Then the JSON response should be an array + And the JSON response should have 2 entries + And the JSON should have the following: + | 1/name | "Employees" | + | 1/slug | "employees" | + | 1/entries_custom_fields/0/label | "Name" | + | 1/entries_custom_fields/0/name | "name" | + | 1/entries_custom_fields/0/type | "string" | + | 1/entries_custom_fields/1/label | "Position" | + | 1/entries_custom_fields/1/name | "position" | + | 1/entries_custom_fields/1/type | "string" | + + Scenario: Creating new content type as a Designer + Given I have a "designer" API token + When I do an API GET request to content_types.json + Then the JSON response should be an array + And the JSON response should have 1 entry + When I do an API POST to content_types.json with: + """ + { + "content_type": { + "name": "Employees", + "slug": "employees", + "entries_custom_fields": [ + { + "label": "Name", + "name": "name", + "type": "string" + }, + { + "label": "Position", + "name": "position", + "type": "string" + } + ] + } + } + """ + When I do an API GET request to content_types.json + Then the JSON response should be an array + And the JSON response should have 2 entries + And the JSON should have the following: + | 1/name | "Employees" | + | 1/slug | "employees" | + | 1/entries_custom_fields/0/label | "Name" | + | 1/entries_custom_fields/0/name | "name" | + | 1/entries_custom_fields/0/type | "string" | + | 1/entries_custom_fields/1/label | "Position" | + | 1/entries_custom_fields/1/name | "position" | + | 1/entries_custom_fields/1/type | "string" | + + Scenario: Creating new content type as an Author + Given I have an "author" API token + When I do an API GET request to content_types.json + Then the JSON response should be an array + And the JSON response should have 1 entry + When I do an API POST to content_types.json with: + """ + { + "content_type": { + "name": "Employees", + "slug": "employees", + "entries_custom_fields": [ + { + "label": "Name", + "name": "name", + "type": "string" + }, + { + "label": "Position", + "name": "position", + "type": "string" + } + ] + } + } + """ + Then an access denied error should occur + + # update content type + + Scenario: Updating content type as an Admin + Given I have an "admin" API token + When I do an API PUT to content_types/4f832c2cb0d86d3f42fffffe.json with: + """ + { + "content_type": { + "name": "Brand new updated name" + } + } + """ + When I do an API GET request to content_types/4f832c2cb0d86d3f42fffffe.json + Then the JSON response at "id" should be "4f832c2cb0d86d3f42fffffe" + And the JSON response at "name" should be "Brand new updated name" + + Scenario: Updating content type as a Designer + Given I have a "designer" API token + When I do an API PUT to content_types/4f832c2cb0d86d3f42fffffe.json with: + """ + { + "content_type": { + "name": "Brand new updated name" + } + } + """ + When I do an API GET request to content_types/4f832c2cb0d86d3f42fffffe.json + Then the JSON response at "id" should be "4f832c2cb0d86d3f42fffffe" + And the JSON response at "name" should be "Brand new updated name" + + Scenario: Updating content type as an Author + Given I have a "author" API token + When I do an API PUT to content_types/4f832c2cb0d86d3f42fffffe.json with: + """ + { + "content_type": { + "name": "Brand new updated name" + } + } + """ + Then an access denied error should occur + + # destroy content type + + Scenario: Destroying content type as an Admin + Given I have an "admin" API token + When I do an API GET request to content_types.json + Then the JSON response should be an array + And the JSON response should have 1 entry + When I do an API DELETE to content_types/4f832c2cb0d86d3f42fffffe.json + When I do an API GET request to content_types.json + Then the JSON response should be an array + And the JSON response should have 0 entries + + Scenario: Destroying content type as a Designer + Given I have a "designer" API token + When I do an API GET request to content_types.json + Then the JSON response should be an array + And the JSON response should have 1 entry + When I do an API DELETE to content_types/4f832c2cb0d86d3f42fffffe.json + When I do an API GET request to content_types.json + Then the JSON response should be an array + And the JSON response should have 0 entries + + Scenario: Deleting content type as an Author + Given I have a "author" API token + When I do an API GET request to content_types.json + Then the JSON response should be an array + And the JSON response should have 1 entries + When I do an API DELETE to content_types/4f832c2cb0d86d3f42fffffe.json + Then an access denied error should occur diff --git a/features/step_definitions/content_types_steps.rb b/features/step_definitions/content_types_steps.rb index 8852c0b6..ed45bf9e 100644 --- a/features/step_definitions/content_types_steps.rb +++ b/features/step_definitions/content_types_steps.rb @@ -1,6 +1,9 @@ -Given %r{^I have a custom model named "([^"]*)" with$} do |name, fields| +def build_content_type(name) site = Locomotive::Site.first - content_type = FactoryGirl.build(:content_type, :site => site, :name => name, :order_by => '_position') + FactoryGirl.build(:content_type, :site => site, :name => name, :order_by => '_position') +end + +def set_custom_fields_from_table(content_type, fields) fields.hashes.each do |field| # found a belongs_to association if field['type'] == 'belongs_to' @@ -12,6 +15,19 @@ Given %r{^I have a custom model named "([^"]*)" with$} do |name, fields| content_type.entries_custom_fields.build field end +end + +Given %r{^I have a custom model named "([^"]*)" with id "([^"]*)" and$} do |name, id, fields| + content_type = build_content_type(name) + content_type.id = BSON::ObjectId(id) + set_custom_fields_from_table(content_type, fields) + content_type.valid? + content_type.save.should be_true +end + +Given %r{^I have a custom model named "([^"]*)" with$} do |name, fields| + content_type = build_content_type(name) + set_custom_fields_from_table(content_type, fields) content_type.valid? content_type.save.should be_true end From d2da4b659ebfcbd691dccf7faf4049e84d8e412b Mon Sep 17 00:00:00 2001 From: Alex Sanford Date: Wed, 25 Apr 2012 09:51:11 -0300 Subject: [PATCH 10/17] Added auth feature for content entries --- .../api/authorization/content_entries.feature | 202 ++++++++++++++++++ 1 file changed, 202 insertions(+) create mode 100644 features/api/authorization/content_entries.feature diff --git a/features/api/authorization/content_entries.feature b/features/api/authorization/content_entries.feature new file mode 100644 index 00000000..af305298 --- /dev/null +++ b/features/api/authorization/content_entries.feature @@ -0,0 +1,202 @@ +Feature: Content Entries + In order to ensure content entries are not tampered with + As an admin, designer or author + I will be restricted based on my role + + Background: + Given I have the site: "test site" set up + And I have a custom model named "Projects" with + | label | type | required | + | Name | string | true | + | Description | text | false | + And I have entries for "Projects" with + | id | name | description | + | 4f832c2cb0d86d3f42fffffe | Project 1 | The first project | + | 4f832c2cb0d86d3f42ffffff | Project 2 | The second project | + And I have a designer and an author + + Scenario: As an unauthenticated user + Given I am not authenticated + When I do an API GET to content_types/projects/entries.json + Then the JSON response at "error" should be "You need to sign in or sign up before continuing." + + # listing content entries + + Scenario: Accessing content entries as an Admin + Given I have an "admin" API token + When I do an API GET request to content_types/projects/entries.json + Then the JSON response should be an array + And the JSON response should have 2 entries + + Scenario: Accessing content entries as a Designer + Given I have a "designer" API token + When I do an API GET request to content_types/projects/entries.json + Then the JSON response should be an array + And the JSON response should have 2 entries + + Scenario: Accessing content entries as an Author + Given I have an "author" API token + When I do an API GET request to content_types/projects/entries.json + Then the JSON response should be an array + And the JSON response should have 2 entries + + # showing content entry + + Scenario: Accessing content entry as an Admin + Given I have an "admin" API token + When I do an API GET request to content_types/projects/entries/4f832c2cb0d86d3f42fffffe.json + Then the JSON response at "id" should be "4f832c2cb0d86d3f42fffffe" + And the JSON response at "name" should be "Project 1" + + Scenario: Accessing content entry as a Designer + Given I have a "designer" API token + When I do an API GET request to content_types/projects/entries/4f832c2cb0d86d3f42fffffe.json + Then the JSON response at "id" should be "4f832c2cb0d86d3f42fffffe" + And the JSON response at "name" should be "Project 1" + + Scenario: Accessing content entry as an Author + Given I have an "author" API token + When I do an API GET request to content_types/projects/entries/4f832c2cb0d86d3f42fffffe.json + Then the JSON response at "id" should be "4f832c2cb0d86d3f42fffffe" + And the JSON response at "name" should be "Project 1" + + # create content entry + + Scenario: Creating new content entry as an Admin + Given I have an "admin" API token + When I do an API GET request to content_types/projects/entries.json + Then the JSON response should be an array + And the JSON response should have 2 entries + When I do an API POST to content_types/projects/entries.json with: + """ + { + "content_entry": { + "name": "Project 3", + "description": "The third..." + } + } + """ + When I do an API GET request to content_types/projects/entries.json + Then the JSON response should be an array + And the JSON response should have 3 entries + And the JSON should have the following: + | 2/name | "Project 3" | + | 2/description | "The third..." | + + Scenario: Creating new content entry as a Designer + Given I have a "designer" API token + When I do an API GET request to content_types/projects/entries.json + Then the JSON response should be an array + And the JSON response should have 2 entries + When I do an API POST to content_types/projects/entries.json with: + """ + { + "content_entry": { + "name": "Project 3", + "description": "The third..." + } + } + """ + When I do an API GET request to content_types/projects/entries.json + Then the JSON response should be an array + And the JSON response should have 3 entries + And the JSON should have the following: + | 2/name | "Project 3" | + | 2/description | "The third..." | + + Scenario: Creating new content entry as an Author + Given I have an "author" API token + When I do an API GET request to content_types/projects/entries.json + Then the JSON response should be an array + And the JSON response should have 2 entries + When I do an API POST to content_types/projects/entries.json with: + """ + { + "content_entry": { + "name": "Project 3", + "description": "The third..." + } + } + """ + When I do an API GET request to content_types/projects/entries.json + Then the JSON response should be an array + And the JSON response should have 3 entries + And the JSON should have the following: + | 2/name | "Project 3" | + | 2/description | "The third..." | + + # update content entry + + Scenario: Updating content entry as an Admin + Given I have an "admin" API token + When I do an API PUT to content_types/projects/entries/4f832c2cb0d86d3f42fffffe.json with: + """ + { + "content_entry": { + "description": "The awesomest project ever!" + } + } + """ + When I do an API GET request to content_types/projects/entries/4f832c2cb0d86d3f42fffffe.json + Then the JSON response at "name" should be "Project 1" + And the JSON response at "description" should be "The awesomest project ever!" + + Scenario: Updating content entry as a Designer + Given I have a "designer" API token + When I do an API PUT to content_types/projects/entries/4f832c2cb0d86d3f42fffffe.json with: + """ + { + "content_entry": { + "description": "The awesomest project ever!" + } + } + """ + When I do an API GET request to content_types/projects/entries/4f832c2cb0d86d3f42fffffe.json + Then the JSON response at "name" should be "Project 1" + And the JSON response at "description" should be "The awesomest project ever!" + + Scenario: Updating content entry as an Author + Given I have a "author" API token + When I do an API PUT to content_types/projects/entries/4f832c2cb0d86d3f42fffffe.json with: + """ + { + "content_entry": { + "description": "The awesomest project ever!" + } + } + """ + When I do an API GET request to content_types/projects/entries/4f832c2cb0d86d3f42fffffe.json + Then the JSON response at "name" should be "Project 1" + And the JSON response at "description" should be "The awesomest project ever!" + + # destroy content entry + + Scenario: Destroying content entry as an Admin + Given I have an "admin" API token + When I do an API GET request to content_types/projects/entries.json + Then the JSON response should be an array + And the JSON response should have 2 entries + When I do an API DELETE to content_types/projects/entries/4f832c2cb0d86d3f42fffffe.json + When I do an API GET request to content_types/projects/entries.json + Then the JSON response should be an array + And the JSON response should have 1 entry + + Scenario: Destroying content entry as a Designer + Given I have a "designer" API token + When I do an API GET request to content_types/projects/entries.json + Then the JSON response should be an array + And the JSON response should have 2 entries + When I do an API DELETE to content_types/projects/entries/4f832c2cb0d86d3f42fffffe.json + When I do an API GET request to content_types/projects/entries.json + Then the JSON response should be an array + And the JSON response should have 1 entry + + Scenario: Deleting content entry as an Author + Given I have a "author" API token + When I do an API GET request to content_types/projects/entries.json + Then the JSON response should be an array + And the JSON response should have 2 entries + When I do an API DELETE to content_types/projects/entries/4f832c2cb0d86d3f42fffffe.json + When I do an API GET request to content_types/projects/entries.json + Then the JSON response should be an array + And the JSON response should have 1 entry From 4e6c10677229f353a1bace558711bfc2839e2c1f Mon Sep 17 00:00:00 2001 From: Alex Sanford Date: Wed, 25 Apr 2012 10:56:38 -0300 Subject: [PATCH 11/17] Added auth feature for snippets --- .../locomotive/api/snippets_controller.rb | 13 ++ features/api/authorization/snippets.feature | 179 ++++++++++++++++++ features/step_definitions/snippet_steps.rb | 14 +- 3 files changed, 204 insertions(+), 2 deletions(-) create mode 100644 features/api/authorization/snippets.feature diff --git a/app/controllers/locomotive/api/snippets_controller.rb b/app/controllers/locomotive/api/snippets_controller.rb index b7479745..24067166 100644 --- a/app/controllers/locomotive/api/snippets_controller.rb +++ b/app/controllers/locomotive/api/snippets_controller.rb @@ -2,11 +2,18 @@ module Locomotive module Api class SnippetsController < BaseController + load_and_authorize_resource :class => Locomotive::Snippet + def index @snippets = current_site.snippets.all respond_with(@snippets) end + def show + @snippet = current_site.snippets.find(params[:id]) + respond_with @snippet + end + def create @snippet = current_site.snippets.create(params[:snippet]) respond_with @snippet, :location => main_app.locomotive_api_snippets_url @@ -18,6 +25,12 @@ module Locomotive respond_with @snippet, :location => main_app.locomotive_api_snippets_url end + def destroy + @snippet = current_site.snippets.find(params[:id]) + @snippet.destroy + respond_with @snippet + end + end end end diff --git a/features/api/authorization/snippets.feature b/features/api/authorization/snippets.feature new file mode 100644 index 00000000..058ca7e1 --- /dev/null +++ b/features/api/authorization/snippets.feature @@ -0,0 +1,179 @@ +Feature: Snippets + In order to ensure snippets are not tampered with + As an admin, designer or author + I will be restricted based on my role + + Background: + Given I have the site: "test site" set up + And a snippet named "My Snippet" with id "4f832c2cb0d86d3f42fffffe" and template: + """ + My Snippet + """ + And I have a designer and an author + + Scenario: As an unauthenticated user + Given I am not authenticated + When I do an API GET to snippets.json + Then the JSON response at "error" should be "You need to sign in or sign up before continuing." + + # listing content types + + Scenario: Accessing snippets as an Admin + Given I have an "admin" API token + When I do an API GET request to snippets.json + Then the JSON response should be an array + And the JSON response should have 1 entry + + Scenario: Accessing snippets as a Designer + Given I have a "designer" API token + When I do an API GET request to snippets.json + Then the JSON response should be an array + And the JSON response should have 1 entry + + Scenario: Accessing snippets as an Author + Given I have an "author" API token + When I do an API GET request to snippets.json + Then an access denied error should occur + + # showing snippet + + Scenario: Accessing snippet as an Admin + Given I have an "admin" API token + When I do an API GET request to snippets/4f832c2cb0d86d3f42fffffe.json + Then the JSON response at "id" should be "4f832c2cb0d86d3f42fffffe" + And the JSON response at "name" should be "My Snippet" + + Scenario: Accessing snippet as a Designer + Given I have a "designer" API token + When I do an API GET request to snippets/4f832c2cb0d86d3f42fffffe.json + Then the JSON response at "id" should be "4f832c2cb0d86d3f42fffffe" + And the JSON response at "name" should be "My Snippet" + + Scenario: Accessing snippet as an Author + Given I have an "author" API token + When I do an API GET request to snippets/4f832c2cb0d86d3f42fffffe.json + Then an access denied error should occur + + # create snippet + + Scenario: Creating new snippet as an Admin + Given I have an "admin" API token + When I do an API GET request to snippets.json + Then the JSON response should be an array + And the JSON response should have 1 entry + When I do an API POST to snippets.json with: + """ + { + "snippet": { + "name": "Another snippet", + "template": "

Another Snippet!

" + } + } + """ + When I do an API GET request to snippets.json + Then the JSON response should be an array + And the JSON response should have 2 entries + And the JSON should have the following: + | 1/name | "Another Snippet" | + | 1/template | "

Another Snippet!

" | + + Scenario: Creating new snippet as a Designer + Given I have a "designer" API token + When I do an API GET request to snippets.json + Then the JSON response should be an array + And the JSON response should have 1 entry + When I do an API POST to snippets.json with: + """ + { + "snippet": { + "name": "Another snippet", + "template": "

Another Snippet!

" + } + } + """ + When I do an API GET request to snippets.json + Then the JSON response should be an array + And the JSON response should have 2 entries + And the JSON should have the following: + | 1/name | "Another Snippet" | + | 1/template | "

Another Snippet!

" | + + Scenario: Creating new snippet as an Author + Given I have an "author" API token + When I do an API POST to snippets.json with: + """ + { + "snippet": { + "name": "Another snippet", + "template": "

Another Snippet!

" + } + } + """ + Then an access denied error should occur + + # update snippet + + Scenario: Updating snippet as an Admin + Given I have an "admin" API token + When I do an API PUT to snippets/4f832c2cb0d86d3f42fffffe.json with: + """ + { + "snippet": { + "name": "Brand new updated name" + } + } + """ + When I do an API GET request to snippets/4f832c2cb0d86d3f42fffffe.json + Then the JSON response at "name" should be "Brand new updated name" + + Scenario: Updating snippet as a Designer + Given I have a "designer" API token + When I do an API PUT to snippets/4f832c2cb0d86d3f42fffffe.json with: + """ + { + "snippet": { + "name": "Brand new updated name" + } + } + """ + When I do an API GET request to snippets/4f832c2cb0d86d3f42fffffe.json + Then the JSON response at "name" should be "Brand new updated name" + + Scenario: Updating snippet as an Author + Given I have a "author" API token + When I do an API PUT to snippets/4f832c2cb0d86d3f42fffffe.json with: + """ + { + "snippet": { + "name": "Brand new updated name" + } + } + """ + Then an access denied error should occur + + # destroy snippet + + Scenario: Destroying snippet as an Admin + Given I have an "admin" API token + When I do an API GET request to snippets.json + Then the JSON response should be an array + And the JSON response should have 1 entry + When I do an API DELETE to snippets/4f832c2cb0d86d3f42fffffe.json + When I do an API GET request to snippets.json + Then the JSON response should be an array + And the JSON response should have 0 entries + + Scenario: Destroying snippet as a Designer + Given I have a "designer" API token + When I do an API GET request to snippets.json + Then the JSON response should be an array + And the JSON response should have 1 entry + When I do an API DELETE to snippets/4f832c2cb0d86d3f42fffffe.json + When I do an API GET request to snippets.json + Then the JSON response should be an array + And the JSON response should have 0 entries + + Scenario: Deleting snippet as an Author + Given I have a "author" API token + When I do an API DELETE to snippets/4f832c2cb0d86d3f42fffffe.json + Then an access denied error should occur diff --git a/features/step_definitions/snippet_steps.rb b/features/step_definitions/snippet_steps.rb index aa2b0ac6..7909931b 100644 --- a/features/step_definitions/snippet_steps.rb +++ b/features/step_definitions/snippet_steps.rb @@ -1,9 +1,13 @@ ### Snippets # helps create a simple snippet with a slug and template +def new_snippet(name, template = nil) + @site.snippets.new(:name => name, :template => template) +end + def create_snippet(name, template = nil) - snippet = @site.snippets.create(:name => name, :template => template) - snippet.should be_valid + snippet = new_snippet(name, template) + snippet.save! snippet end @@ -13,6 +17,12 @@ Given /^a snippet named "([^"]*)" with the template:$/ do |name, template| @snippet = create_snippet(name, template) end +Given /^a snippet named "([^"]*)" with id "([^"]*)" and template:$/ do |name, id, template| + @snippet = new_snippet(name, template) + @snippet.id = BSON::ObjectId(id) + @snippet.save! +end + When /^I change the snippet template to "([^"]*)"$/ do |code| page.evaluate_script "window.application_view.view.editor.setValue('#{code}')" end From 37f87e694cc163e4bc0a0cf12820796c14ce9715 Mon Sep 17 00:00:00 2001 From: Alex Sanford Date: Wed, 25 Apr 2012 16:26:02 -0300 Subject: [PATCH 12/17] Added auth feature for theme assets --- .../locomotive/api/theme_assets_controller.rb | 13 ++ .../api/authorization/theme_assets.feature | 185 ++++++++++++++++++ .../step_definitions/theme_asset_steps.rb | 19 +- 3 files changed, 215 insertions(+), 2 deletions(-) create mode 100644 features/api/authorization/theme_assets.feature diff --git a/app/controllers/locomotive/api/theme_assets_controller.rb b/app/controllers/locomotive/api/theme_assets_controller.rb index c1cfae8d..c7cd0cd6 100644 --- a/app/controllers/locomotive/api/theme_assets_controller.rb +++ b/app/controllers/locomotive/api/theme_assets_controller.rb @@ -2,11 +2,18 @@ module Locomotive module Api class ThemeAssetsController < BaseController + load_and_authorize_resource :class => Locomotive::ThemeAsset + def index @theme_assets = current_site.theme_assets.all respond_with(@theme_assets) end + def show + @theme_asset = current_site.theme_assets.find(params[:id]) + respond_with @theme_asset + end + def create @theme_asset = current_site.theme_assets.create(params[:theme_asset]) respond_with @theme_asset, :location => main_app.locomotive_api_theme_assets_url @@ -18,6 +25,12 @@ module Locomotive respond_with @theme_asset, :location => main_app.locomotive_api_theme_assets_url end + def destroy + @theme_asset = current_site.theme_assets.find(params[:id]) + @theme_asset.destroy + respond_with @theme_asset + end + end end end diff --git a/features/api/authorization/theme_assets.feature b/features/api/authorization/theme_assets.feature new file mode 100644 index 00000000..e366209b --- /dev/null +++ b/features/api/authorization/theme_assets.feature @@ -0,0 +1,185 @@ +Feature: Theme Assets + In order to ensure theme assets are not tampered with + As an admin, designer or author + I will be restricted based on my role + + Background: + Given I have the site: "test site" set up + And a javascript asset named "my_javascript.js" with id "4f832c2cb0d86d3f42fffffe" + And a stylesheet asset named "my_stylesheet.css" with id "4f832c2cb0d86d3f42ffffff" + + Scenario: As an unauthenticated user + Given I am not authenticated + When I do an API GET to theme_assets.json + Then the JSON response at "error" should be "You need to sign in or sign up before continuing." + + # listing theme assets + + Scenario: Accessing theme assets as an Admin + Given I have an "admin" API token + When I do an API GET request to theme_assets.json + Then the JSON response should be an array + And the JSON response should have 2 entries + + Scenario: Accessing theme assets as a Designer + Given I have a "designer" API token + When I do an API GET request to theme_assets.json + Then the JSON response should be an array + And the JSON response should have 2 entries + + Scenario: Accessing theme assets as an Author + Given I have an "author" API token + When I do an API GET request to theme_assets.json + Then the JSON response should be an array + And the JSON response should have 2 entries + + # showing theme asset + + Scenario: Accessing theme asset as an Admin + Given I have an "admin" API token + When I do an API GET request to theme_assets/4f832c2cb0d86d3f42fffffe.json + Then the JSON response at "local_path" should be "my_javascript.js" + + Scenario: Accessing theme asset as a Designer + Given I have a "designer" API token + When I do an API GET request to theme_assets/4f832c2cb0d86d3f42fffffe.json + Then the JSON response at "local_path" should be "my_javascript.js" + + Scenario: Accessing theme asset as an Author + Given I have an "author" API token + When I do an API GET request to theme_assets/4f832c2cb0d86d3f42fffffe.json + Then the JSON response at "local_path" should be "my_javascript.js" + + # create theme asset + + Scenario: Creating new theme asset as an Admin + Given I have an "admin" API token + When I do an API GET request to theme_assets.json + Then the JSON response should be an array + And the JSON response should have 2 entries + When I do an API POST to theme_assets.json with: + """ + { + "theme_asset": { + "plain_text_name": "new-javascript.js", + "plain_text": "function doNothing() {}", + "plain_text_type": "javascript", + "performing_plain_text": "true" + } + } + """ + When I do an API GET request to theme_assets.json + Then the JSON response should be an array + And the JSON response should have 3 entries + And the JSON should have the following: + | 2/local_path | "new-javascript.js" | + | 2/content_type | "javascript" | + + Scenario: Creating new theme asset as a Designer + Given I have a "designer" API token + When I do an API GET request to theme_assets.json + Then the JSON response should be an array + And the JSON response should have 2 entries + When I do an API POST to theme_assets.json with: + """ + { + "theme_asset": { + "plain_text_name": "new-javascript.js", + "plain_text": "function doNothing() {}", + "plain_text_type": "javascript", + "performing_plain_text": "true" + } + } + """ + When I do an API GET request to theme_assets.json + Then the JSON response should be an array + And the JSON response should have 3 entries + And the JSON should have the following: + | 2/local_path | "new-javascript.js" | + | 2/content_type | "javascript" | + + Scenario: Creating new theme asset as an Author + Given I have an "author" API token + When I do an API POST to theme_assets.json with: + """ + { + "theme_asset": { + "plain_text_name": "new-javascript.js", + "plain_text": "function doNothing() {}", + "plain_text_type": "javascript", + "performing_plain_text": "true" + } + } + """ + Then an access denied error should occur + + # update theme asset + + Scenario: Updating theme asset as an Admin + Given I have an "admin" API token + When I do an API PUT to theme_assets/4f832c2cb0d86d3f42fffffe.json with: + """ + { + "theme_asset": { + "plain_text_name": "newer-javascript.js" + } + } + """ + When I do an API GET request to theme_assets/4f832c2cb0d86d3f42fffffe.json + Then the JSON response should have the following: + | local_path | "newer-javascript.js" | + + Scenario: Updating theme asset as a Designer + Given I have a "designer" API token + When I do an API PUT to theme_assets/4f832c2cb0d86d3f42fffffe.json with: + """ + { + "theme_asset": { + "plain_text_name": "newer-javascript.js" + } + } + """ + When I do an API GET request to theme_assets/4f832c2cb0d86d3f42fffffe.json + Then the JSON response should have the following: + | local_path | "newer-javascript.js" | + + Scenario: Updating theme asset as an Author + Given I have a "author" API token + When I do an API PUT to theme_assets/4f832c2cb0d86d3f42fffffe.json with: + """ + { + "theme_asset": { + "plain_text_name": "newer-javascript.js" + } + } + """ + When I do an API GET request to theme_assets/4f832c2cb0d86d3f42fffffe.json + Then the JSON response should have the following: + | local_path | "newer-javascript.js" | + + # destroy theme asset + + Scenario: Destroying theme asset as an Admin + Given I have an "admin" API token + When I do an API GET request to theme_assets.json + Then the JSON response should be an array + And the JSON response should have 2 entries + When I do an API DELETE to theme_assets/4f832c2cb0d86d3f42fffffe.json + When I do an API GET request to theme_assets.json + Then the JSON response should be an array + And the JSON response should have 1 entries + + Scenario: Destroying theme asset as a Designer + Given I have a "designer" API token + When I do an API GET request to theme_assets.json + Then the JSON response should be an array + And the JSON response should have 2 entries + When I do an API DELETE to theme_assets/4f832c2cb0d86d3f42fffffe.json + When I do an API GET request to theme_assets.json + Then the JSON response should be an array + And the JSON response should have 1 entries + + Scenario: Deleting theme asset as an Author + Given I have a "author" API token + When I do an API DELETE to theme_assets/4f832c2cb0d86d3f42fffffe.json + Then an access denied error should occur diff --git a/features/step_definitions/theme_asset_steps.rb b/features/step_definitions/theme_asset_steps.rb index 6cf5b71c..191b2178 100644 --- a/features/step_definitions/theme_asset_steps.rb +++ b/features/step_definitions/theme_asset_steps.rb @@ -1,15 +1,18 @@ ### Theme assets # helps create a theme asset -def create_plain_text_asset(name, type) - asset = FactoryGirl.build(:theme_asset, { +def new_plain_text_asset(name, type) + FactoryGirl.build(:theme_asset, { :site => @site, :plain_text_name => name, :plain_text => 'Lorem ipsum', :plain_text_type => type, :performing_plain_text => true }) +end +def create_plain_text_asset(name, type) + asset = new_plain_text_asset(name, type) asset.save! end @@ -19,10 +22,22 @@ Given /^a javascript asset named "([^"]*)"$/ do |name| @asset = create_plain_text_asset(name, 'javascript') end +Given /^a javascript asset named "([^"]*)" with id "([^"]*)"$/ do |name, id| + @asset = new_plain_text_asset(name, 'javascript') + @asset.id = BSON::ObjectId(id) + @asset.save! +end + Given /^a stylesheet asset named "([^"]*)"$/ do |name| @asset = create_plain_text_asset(name, 'stylesheet') end +Given /^a stylesheet asset named "([^"]*)" with id "([^"]*)"$/ do |name, id| + @asset = new_plain_text_asset(name, 'stylesheet') + @asset.id = BSON::ObjectId(id) + @asset.save! +end + Given /^I have an image theme asset named "([^"]*)"$/ do |name| @asset = FactoryGirl.create(:theme_asset, :site => @site, :source => File.open(Rails.root.join('..', 'fixtures', 'assets', '5k.png'))) @asset.source_filename = name From a921c44ce986adc133da4425cb516ae8324414c8 Mon Sep 17 00:00:00 2001 From: Alex Sanford Date: Thu, 26 Apr 2012 16:10:40 -0300 Subject: [PATCH 13/17] Fixed access denied error checking step --- features/step_definitions/api_steps.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/features/step_definitions/api_steps.rb b/features/step_definitions/api_steps.rb index 42a428c1..8d9b3059 100644 --- a/features/step_definitions/api_steps.rb +++ b/features/step_definitions/api_steps.rb @@ -8,7 +8,7 @@ def do_api_request(type, url, param_string = nil) params = param_string && JSON.parse(param_string) || {} @json_response = do_request(type, api_base_url, url, params.merge({ 'CONTENT_TYPE' => 'application/json' })) - rescue Exception + rescue CanCan::AccessDenied @error = $! end end @@ -65,5 +65,5 @@ end Then /^an access denied error should occur$/ do @error.should_not be_nil - @error.message.should == 'You are not authorized to access this page.' + @error.is_a?(CanCan::AccessDenied).should be_true end From 96007174cbdd4f24ab89dd83b0614905bbc3b462 Mon Sep 17 00:00:00 2001 From: Alex Sanford Date: Thu, 26 Apr 2012 16:14:23 -0300 Subject: [PATCH 14/17] Added auth feature for content_assets --- .../api/content_assets_controller.rb | 13 ++ .../api/authorization/content_assets.feature | 147 ++++++++++++++++++ features/step_definitions/api_steps.rb | 22 ++- .../step_definitions/content_assets_steps.rb | 12 ++ spec/fixtures/assets/5k_2.png | Bin 0 -> 1284 bytes 5 files changed, 192 insertions(+), 2 deletions(-) create mode 100644 features/api/authorization/content_assets.feature create mode 100644 features/step_definitions/content_assets_steps.rb create mode 100644 spec/fixtures/assets/5k_2.png diff --git a/app/controllers/locomotive/api/content_assets_controller.rb b/app/controllers/locomotive/api/content_assets_controller.rb index abde5f8c..2acd8c12 100644 --- a/app/controllers/locomotive/api/content_assets_controller.rb +++ b/app/controllers/locomotive/api/content_assets_controller.rb @@ -2,11 +2,18 @@ module Locomotive module Api class ContentAssetsController < BaseController + load_and_authorize_resource :class => Locomotive::ContentAsset + def index @content_assets = current_site.content_assets respond_with(@content_assets) end + def show + @content_asset = current_site.content_assets.find(params[:id]) + respond_with(@content_asset) + end + def create @content_asset = current_site.content_assets.create(params[:content_asset]) respond_with @content_asset, :location => main_app.locomotive_api_content_assets_url @@ -18,6 +25,12 @@ module Locomotive respond_with @content_asset, :location => main_app.locomotive_api_content_assets_url end + def destroy + @content_asset = current_site.content_assets.find(params[:id]) + @content_asset.destroy + respond_with @content_asset + end + end end end diff --git a/features/api/authorization/content_assets.feature b/features/api/authorization/content_assets.feature new file mode 100644 index 00000000..ff3f8aec --- /dev/null +++ b/features/api/authorization/content_assets.feature @@ -0,0 +1,147 @@ +Feature: Content Assets + In order to ensure content assets are not tampered with + As an admin, designer or author + I will be restricted based on my role + + Background: + Given I have the site: "test site" set up + And I have the following content assets: + | id | file | + | 4f832c2cb0d86d3f42fffffe | 5k.png | + | 4f832c2cb0d86d3f42ffffff | 5k_2.png | + And I have a designer and an author + + Scenario: As an unauthenticated user + Given I am not authenticated + When I do an API GET to content_assets.json + Then the JSON response at "error" should be "You need to sign in or sign up before continuing." + + # listing content assets + + Scenario: Accessing content assets as an Admin + Given I have an "admin" API token + When I do an API GET request to content_assets.json + Then the JSON response should be an array + And the JSON response should have 2 entries + + Scenario: Accessing content assets as a Designer + Given I have a "designer" API token + When I do an API GET request to content_assets.json + Then the JSON response should be an array + And the JSON response should have 2 entries + + Scenario: Accessing content assets as an Author + Given I have an "author" API token + When I do an API GET request to content_assets.json + Then the JSON response should be an array + And the JSON response should have 2 entries + + # showing content asset + + Scenario: Accessing content asset as an Admin + Given I have an "admin" API token + When I do an API GET request to content_assets/4f832c2cb0d86d3f42fffffe.json + Then the JSON response at "filename" should be "5k.png" + + Scenario: Accessing content asset as a Designer + Given I have a "designer" API token + When I do an API GET request to content_assets/4f832c2cb0d86d3f42fffffe.json + Then the JSON response at "filename" should be "5k.png" + + Scenario: Accessing content asset as an Author + Given I have an "author" API token + When I do an API GET request to content_assets/4f832c2cb0d86d3f42fffffe.json + Then the JSON response at "filename" should be "5k.png" + + # create content asset + + Scenario: Creating new content asset as an Admin + Given I have an "admin" API token + When I do an API GET request to content_assets.json + Then the JSON response should be an array + And the JSON response should have 2 entries + When I do a multipart API POST to content_assets.json with base key "content_asset" and: + | source | assets/application.js | + When I do an API GET request to content_assets.json + Then the JSON response should be an array + And the JSON response should have 3 entries + And the JSON at "2/filename" should be "application.js" + + Scenario: Creating new content asset as a Designer + Given I have a "designer" API token + When I do an API GET request to content_assets.json + Then the JSON response should be an array + And the JSON response should have 2 entries + When I do a multipart API POST to content_assets.json with base key "content_asset" and: + | source | assets/application.js | + When I do an API GET request to content_assets.json + Then the JSON response should be an array + And the JSON response should have 3 entries + And the JSON at "2/filename" should be "application.js" + + Scenario: Creating new content asset as an Author + Given I have an "author" API token + When I do an API GET request to content_assets.json + Then the JSON response should be an array + And the JSON response should have 2 entries + When I do a multipart API POST to content_assets.json with base key "content_asset" and: + | source | assets/application.js | + When I do an API GET request to content_assets.json + Then the JSON response should be an array + And the JSON response should have 3 entries + And the JSON at "2/filename" should be "application.js" + + # update content asset + + Scenario: Updating content asset as an Admin + Given I have an "admin" API token + When I do a multipart API PUT to content_assets/4f832c2cb0d86d3f42fffffe.json with base key "content_asset" and: + | source | assets/main.css | + When I do an API GET request to content_assets/4f832c2cb0d86d3f42fffffe.json + Then the JSON response at "filename" should be "main.css" + + Scenario: Updating content asset as a Designer + Given I have a "designer" API token + When I do a multipart API PUT to content_assets/4f832c2cb0d86d3f42fffffe.json with base key "content_asset" and: + | source | assets/main.css | + When I do an API GET request to content_assets/4f832c2cb0d86d3f42fffffe.json + Then the JSON response at "filename" should be "main.css" + + Scenario: Updating content asset as an Author + Given I have a "author" API token + When I do a multipart API PUT to content_assets/4f832c2cb0d86d3f42fffffe.json with base key "content_asset" and: + | source | assets/main.css | + When I do an API GET request to content_assets/4f832c2cb0d86d3f42fffffe.json + Then the JSON response at "filename" should be "main.css" + + # destroy content asset + + Scenario: Destroying content asset as an Admin + Given I have an "admin" API token + When I do an API GET request to content_assets.json + Then the JSON response should be an array + And the JSON response should have 2 entries + When I do an API DELETE to content_assets/4f832c2cb0d86d3f42fffffe.json + When I do an API GET request to content_assets.json + Then the JSON response should be an array + And the JSON response should have 1 entry + + Scenario: Destroying content asset as a Designer + Given I have a "designer" API token + When I do an API GET request to content_assets.json + Then the JSON response should be an array + And the JSON response should have 2 entries + When I do an API DELETE to content_assets/4f832c2cb0d86d3f42fffffe.json + When I do an API GET request to content_assets.json + Then the JSON response should be an array + And the JSON response should have 1 entry + + Scenario: Deleting content asset as an Author + Given I have a "author" API token + When I do an API GET request to content_assets.json + Then the JSON response should be an array + And the JSON response should have 2 entries + When I do an API DELETE to content_assets/4f832c2cb0d86d3f42fffffe.json + When I do an API GET request to content_assets.json + Then the JSON response should be an array + And the JSON response should have 1 entry diff --git a/features/step_definitions/api_steps.rb b/features/step_definitions/api_steps.rb index 8d9b3059..92745a03 100644 --- a/features/step_definitions/api_steps.rb +++ b/features/step_definitions/api_steps.rb @@ -3,9 +3,17 @@ def api_base_url "http://#{@site.domains.first}/locomotive/api/" end -def do_api_request(type, url, param_string = nil) +def do_api_request(type, url, param_string_or_hash = nil) begin - params = param_string && JSON.parse(param_string) || {} + if param_string_or_hash + if param_string_or_hash.is_a? Hash + params = param_string_or_hash + else + params = JSON.parse(param_string_or_hash) + end + else + params = {} + end @json_response = do_request(type, api_base_url, url, params.merge({ 'CONTENT_TYPE' => 'application/json' })) rescue CanCan::AccessDenied @@ -67,3 +75,13 @@ Then /^an access denied error should occur$/ do @error.should_not be_nil @error.is_a?(CanCan::AccessDenied).should be_true end + +When /^I do a multipart API (\w+) (?:request )?to ([\w.\/]+) with base key "([^"]*)" and:$/ \ + do |request_type, url, base_key, table| + params = {} + params = table.rows_hash + params.each do |key, filename| + params[key] = Rack::Test::UploadedFile.new(Rails.root.join('..', 'fixtures', filename)) + end + do_api_request(request_type, url, { base_key => params }) +end diff --git a/features/step_definitions/content_assets_steps.rb b/features/step_definitions/content_assets_steps.rb new file mode 100644 index 00000000..0a53b29f --- /dev/null +++ b/features/step_definitions/content_assets_steps.rb @@ -0,0 +1,12 @@ + +Given /^I have the following content assets:$/ do |table| + site = Locomotive::Site.first + table.hashes.each do |asset_hash| + asset_hash['site'] = site + asset_hash['source'] = FixturedAsset.open(asset_hash['file']) + asset_hash.delete('file') + + asset = FactoryGirl.build(:asset, asset_hash) + asset.save.should be_true + end +end diff --git a/spec/fixtures/assets/5k_2.png b/spec/fixtures/assets/5k_2.png new file mode 100644 index 0000000000000000000000000000000000000000..cf85f7cf573f05059b71d39bd2a104c496fb4f16 GIT binary patch literal 1284 zcmex=_1P|rX?qqI0P zFI~aY%U!`Mz|~!$%*;qrN1?DZF(6Oj-S5fuR$!pIEN!@|nR z%E~Fi%grl7GWdUhL6Cz%fkA4 zD%dK(z{JSR%*4VBay3wOEl{3;MUYiU(a@1iI53f2sZhkIapFP_Wv7h?MT0JWP%%y_ zYU1P)6PJ*bQdLve(9|+9H8Z!cv~qTFb#wRd^a>6M4GWKmj7m;PO-s+n%qlJ^Ei136 ztZHs)ZENr7?3y%r%G7DoXUv?nXz`Mz%a*TLxoXqqEnBy3-?4Mop~FXx9y@;G&P778mFHFAhJOBVf)5?CRc4#|IT0iV{y!7 z&#o7lE2VB9HM75J(s$rVN$#o}mNFAuYq>w3K4SHsq2(Wc#pS3Ed$@}q$=ZAD+O@HB zdV#Tu&*@M98N@iwSw9y2vHrJow&wbpl53{AnP>m)tX+OL*0I*gU1fFIrNuUn&-`@G zH8M;-Al>iv>}c4pZE{QN;_^bP%dHH)OwHQ3?C$z2e?9(Atgm2H{H;Ra_6K0aaI+P>l( zZ{^IeWSc|6Q@v{X^zKIe39>5O5%Dlpl(SfEUHXs9hXX6lAF{gFXmct0;Hn$vlT|J} zuF2NPX`OC+AgAk3@Q3RM-pl?w_n%?w{pLUFb}esL+`Q*)68`Z-qp7ax+{f7s<^ub7 zedfD#Q-YbHXa1d-v^~=wZ$IQ4{Yd7X+|?TU`sCC#8IycI9ADwMOS5H#U}Gukb!HoV zcAGCOWod>hgW1DG7N1oYn^bLfb*truucBHP=WO$hyZx>r;8x5D`!2^mJckd(azO5|NkZcW*-N_ literal 0 HcmV?d00001 From 06e493477c1f267bd21a7b1b370c21baee1b2b73 Mon Sep 17 00:00:00 2001 From: Alex Sanford Date: Fri, 27 Apr 2012 12:05:21 -0300 Subject: [PATCH 15/17] Added auth feature for current site --- .../locomotive/api/current_site_controller.rb | 4 ++- .../api/authorization/current_site.feature | 30 +++++++++++++++++++ 2 files changed, 33 insertions(+), 1 deletion(-) create mode 100644 features/api/authorization/current_site.feature diff --git a/app/controllers/locomotive/api/current_site_controller.rb b/app/controllers/locomotive/api/current_site_controller.rb index 71665ea8..408cda14 100644 --- a/app/controllers/locomotive/api/current_site_controller.rb +++ b/app/controllers/locomotive/api/current_site_controller.rb @@ -3,7 +3,9 @@ module Locomotive class CurrentSiteController < BaseController def show - respond_with(current_site) + @site = current_site + authorize! :show, @site + respond_with(@site) end end diff --git a/features/api/authorization/current_site.feature b/features/api/authorization/current_site.feature new file mode 100644 index 00000000..8a61a372 --- /dev/null +++ b/features/api/authorization/current_site.feature @@ -0,0 +1,30 @@ +Feature: Current Site + In order to ensure the current site can be viewed by all authenticated users + As an admin, designer or author + I should be able to show the current site + + Background: + Given I have the site: "test site" set up + And I have a designer and an author + + Scenario: As an unauthenticated user + Given I am not authenticated + When I do an API GET to current_site.json + Then the JSON response at "error" should be "You need to sign in or sign up before continuing." + + # showing current site + + Scenario: Accessing current site as an Admin + Given I have an "admin" API token + When I do an API GET to current_site.json + Then the JSON response at "name" should be "Locomotive test website" + + Scenario: Accessing current site as a Designer + Given I have a "designer" API token + When I do an API GET to current_site.json + Then the JSON response at "name" should be "Locomotive test website" + + Scenario: Accessing current site as an Author + Given I have an "author" API token + When I do an API GET to current_site.json + Then the JSON response at "name" should be "Locomotive test website" From 9c15f1cbabaf5a3cec8c66511a24f70e9a757c6b Mon Sep 17 00:00:00 2001 From: Alex Sanford Date: Fri, 27 Apr 2012 14:52:09 -0300 Subject: [PATCH 16/17] Added API sites_controller and auth feature --- .../locomotive/api/sites_controller.rb | 44 ++++ config/routes.rb | 2 + features/api/authorization/sites.feature | 206 ++++++++++++++++++ features/step_definitions/api_steps.rb | 13 +- 4 files changed, 263 insertions(+), 2 deletions(-) create mode 100644 app/controllers/locomotive/api/sites_controller.rb create mode 100644 features/api/authorization/sites.feature diff --git a/app/controllers/locomotive/api/sites_controller.rb b/app/controllers/locomotive/api/sites_controller.rb new file mode 100644 index 00000000..11469abe --- /dev/null +++ b/app/controllers/locomotive/api/sites_controller.rb @@ -0,0 +1,44 @@ +module Locomotive + module Api + class SitesController < BaseController + + load_and_authorize_resource :class => Locomotive::Site + + # FIXME: the auto-loaded site won't pass authorization for show, update, or destroy + skip_load_and_authorize_resource :only => [ :show, :update, :destroy ] + + def index + @sites = Locomotive::Site.all + respond_with(@sites) + end + + def show + @site = Locomotive::Site.find(params[:id]) + authorize! :show, @site + respond_with(@site) + end + + def create + @site = Locomotive::Site.create(params[:site]) + respond_with(@site) + end + + def update + @site = Locomotive::Site.find(params[:id]) + authorize! :update, @site + @site.update_attributes(params[:site]) + respond_with @site + end + + def destroy + @site = Locomotive::Site.find(params[:id]) + authorize! :destroy, @site + @site.destroy + respond_with @site + end + + end + + end +end + diff --git a/config/routes.rb b/config/routes.rb index f69a9e31..3c16bf32 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -68,6 +68,8 @@ Rails.application.routes.draw do resources :content_entries, :path => 'content_types/:slug/entries' + resources :sites + resource :current_site, :controller => 'current_site' end diff --git a/features/api/authorization/sites.feature b/features/api/authorization/sites.feature new file mode 100644 index 00000000..bb7b393a --- /dev/null +++ b/features/api/authorization/sites.feature @@ -0,0 +1,206 @@ +Feature: Sites + In order to ensure sites are not tampered with + As an admin, designer or author + I will be restricted based on my role + + Background: + Given I have the site: "test site" set up with id: "4f832c2cb0d86d3f42fffffe" + And I have the site: "another site" set up with id: "4f832c2cb0d86d3f42ffffff" + And I have a designer and an author + + Scenario: As an unauthenticated user + Given I am not authenticated + When I do an API GET to sites.json + Then the JSON response at "error" should be "You need to sign in or sign up before continuing." + + # listing sites + + Scenario: Accessing sites as an Admin + Given I have an "admin" API token + When I do an API GET request to sites.json + Then the JSON response should be an array + And the JSON response should have 2 entries + + Scenario: Accessing sites as a Designer + Given I have a "designer" API token + When I do an API GET request to sites.json + Then an access denied error should occur + + Scenario: Accessing sites as an Author + Given I have an "author" API token + When I do an API GET request to sites.json + Then an access denied error should occur + + # showing site + + Scenario: Accessing site as an Admin + Given I have an "admin" API token + When I do an API GET request to sites/4f832c2cb0d86d3f42fffffe.json + Then the JSON response at "name" should be "Locomotive test website" + + Scenario: Accessing my site as a Designer + Given I have a "designer" API token + When I do an API GET request to sites/4f832c2cb0d86d3f42fffffe.json + Then the JSON response at "name" should be "Locomotive test website" + + Scenario: Accessing other site as a Designer + Given I have a "designer" API token + When I do an API GET request to sites/4f832c2cb0d86d3f42ffffff.json + Then an access denied error should occur + + Scenario: Accessing my site as an Author + Given I have an "author" API token + When I do an API GET request to sites/4f832c2cb0d86d3f42fffffe.json + Then the JSON response at "name" should be "Locomotive test website" + + Scenario: Accessing other site as an Author + Given I have an "author" API token + When I do an API GET request to sites/4f832c2cb0d86d3f42ffffff.json + Then an access denied error should occur + + # create site + + Scenario: Creating new site as an Admin + Given I have an "admin" API token + When I do an API GET request to sites.json + Then the JSON response should be an array + And the JSON response should have 2 entries + When I do an API POST to sites.json with: + """ + { + "site": { + "name": "New site", + "subdomain": "new-site" + } + } + """ + When I do an API GET request to sites.json + Then the JSON response should be an array + And the JSON response should have 3 entries + + Scenario: Creating new site as a Designer + Given I have a "designer" API token + When I do an API POST to sites.json with: + """ + { + "site": { + "name": "New site", + "subdomain": "new-site" + } + } + """ + Then an access denied error should occur + + Scenario: Creating new site as an Author + Given I have an "author" API token + When I do an API POST to sites.json with: + """ + { + "site": { + "name": "New site", + "subdomain": "new-site" + } + } + """ + Then an access denied error should occur + + # update site + + Scenario: Updating site as an Admin + Given I have an "admin" API token + When I do an API PUT to sites/4f832c2cb0d86d3f42fffffe.json with: + """ + { + "site": { + "name": "Brand new updated name" + } + } + """ + When I do an API GET request to sites/4f832c2cb0d86d3f42fffffe.json + Then the JSON response at "id" should be "4f832c2cb0d86d3f42fffffe" + And the JSON response at "name" should be "Brand new updated name" + + Scenario: Updating my site as a Designer + Given I have a "designer" API token + When I do an API PUT to sites/4f832c2cb0d86d3f42fffffe.json with: + """ + { + "site": { + "name": "Brand new updated name" + } + } + """ + When I do an API GET request to sites/4f832c2cb0d86d3f42fffffe.json + Then the JSON response at "id" should be "4f832c2cb0d86d3f42fffffe" + And the JSON response at "name" should be "Brand new updated name" + + Scenario: Updating other site as a Designer + Given I have a "designer" API token + When I do an API PUT to sites/4f832c2cb0d86d3f42ffffff.json with: + """ + { + "site": { + "name": "Brand new updated name" + } + } + """ + Then an access denied error should occur + + Scenario: Updating my site as an Author + Given I have a "author" API token + When I do an API PUT to sites/4f832c2cb0d86d3f42fffffe.json with: + """ + { + "site": { + "name": "Brand new updated name" + } + } + """ + When I do an API GET request to sites/4f832c2cb0d86d3f42fffffe.json + Then the JSON response at "id" should be "4f832c2cb0d86d3f42fffffe" + And the JSON response at "name" should be "Brand new updated name" + + Scenario: Updating other site as an Author + Given I have a "author" API token + When I do an API PUT to sites/4f832c2cb0d86d3f42ffffff.json with: + """ + { + "site": { + "name": "Brand new updated name" + } + } + """ + Then an access denied error should occur + + # destroy site + + Scenario: Destroying site as an Admin + Given I have an "admin" API token + When I do an API GET request to sites.json + Then the JSON response should be an array + And the JSON response should have 2 entries + When I do an API DELETE to sites/4f832c2cb0d86d3f42fffffe.json + When I do an API GET request to sites.json + Then the JSON response should be an array + And the JSON response should have 1 entries + + Scenario: Destroying my site as a Designer + Given I have a "designer" API token + When I do an API DELETE to sites/4f832c2cb0d86d3f42fffffe.json + When I do an API GET request to sites/4f832c2cb0d86d3f42fffffe.json + Then it should not exist + + Scenario: Deleting other site as a Designer + Given I have a "designer" API token + When I do an API DELETE to sites/4f832c2cb0d86d3f42ffffff.json + Then an access denied error should occur + + Scenario: Deleting my site as an Author + Given I have a "author" API token + When I do an API DELETE to sites/4f832c2cb0d86d3f42fffffe.json + Then an access denied error should occur + + Scenario: Deleting other site as an Author + Given I have a "author" API token + When I do an API DELETE to sites/4f832c2cb0d86d3f42ffffff.json + Then an access denied error should occur diff --git a/features/step_definitions/api_steps.rb b/features/step_definitions/api_steps.rb index 92745a03..0d3ad2fb 100644 --- a/features/step_definitions/api_steps.rb +++ b/features/step_definitions/api_steps.rb @@ -1,6 +1,6 @@ def api_base_url - "http://#{@site.domains.first}/locomotive/api/" + "http://#{Locomotive::Site.first.domains.first}/locomotive/api/" end def do_api_request(type, url, param_string_or_hash = nil) @@ -16,7 +16,7 @@ def do_api_request(type, url, param_string_or_hash = nil) end @json_response = do_request(type, api_base_url, url, params.merge({ 'CONTENT_TYPE' => 'application/json' })) - rescue CanCan::AccessDenied + rescue CanCan::AccessDenied, Mongoid::Errors::DocumentNotFound @error = $! end end @@ -76,6 +76,11 @@ Then /^an access denied error should occur$/ do @error.is_a?(CanCan::AccessDenied).should be_true end +Then /^it should not exist$/ do + @error.should_not be_nil + @error.is_a?(Mongoid::Errors::DocumentNotFound).should be_true +end + When /^I do a multipart API (\w+) (?:request )?to ([\w.\/]+) with base key "([^"]*)" and:$/ \ do |request_type, url, base_key, table| params = {} @@ -85,3 +90,7 @@ When /^I do a multipart API (\w+) (?:request )?to ([\w.\/]+) with base key "([^" end do_api_request(request_type, url, { base_key => params }) end + +Then /^I print the json response$/ do + puts %{JSON: "#{last_json}"} +end From 24e5eb855ae8d0f79d0bbc07da91cf318bbe2314 Mon Sep 17 00:00:00 2001 From: Alex Sanford Date: Mon, 30 Apr 2012 16:29:32 -0300 Subject: [PATCH 17/17] Added auth feature for memberships --- .../locomotive/api/base_controller.rb | 7 +- .../locomotive/api/memberships_controller.rb | 49 ++++ config/routes.rb | 2 + .../api/authorization/memberships.feature | 225 ++++++++++++++++++ features/step_definitions/membership_steps.rb | 19 ++ 5 files changed, 301 insertions(+), 1 deletion(-) create mode 100644 app/controllers/locomotive/api/memberships_controller.rb create mode 100644 features/api/authorization/memberships.feature create mode 100644 features/step_definitions/membership_steps.rb diff --git a/app/controllers/locomotive/api/base_controller.rb b/app/controllers/locomotive/api/base_controller.rb index ba949003..8d0519b8 100644 --- a/app/controllers/locomotive/api/base_controller.rb +++ b/app/controllers/locomotive/api/base_controller.rb @@ -13,7 +13,7 @@ module Locomotive before_filter :set_locale - # before_filter :validate_site_membership + before_filter :set_current_thread_variables self.responder = Locomotive::ActionController::Responder # custom responder @@ -21,6 +21,11 @@ module Locomotive protected + def set_current_thread_variables + Thread.current[:account] = current_locomotive_account + Thread.current[:site] = current_site + end + def current_ability @current_ability ||= Ability.new(current_locomotive_account, current_site) end diff --git a/app/controllers/locomotive/api/memberships_controller.rb b/app/controllers/locomotive/api/memberships_controller.rb new file mode 100644 index 00000000..c889f532 --- /dev/null +++ b/app/controllers/locomotive/api/memberships_controller.rb @@ -0,0 +1,49 @@ +module Locomotive + module Api + class MembershipsController < BaseController + + # It's an embedded document, so we'll just load manually + before_filter :load_membership, :only => [ :show, :update, :destroy ] + before_filter :load_memberships, :only => [ :index ] + + authorize_resource :class => Locomotive::Membership + + def index + respond_with(@memberships) + end + + def show + respond_with(@membership) + end + + def create + build_params = params[:membership].merge({ :role => 'author' }) # force author by default + @membership = current_site.memberships.create(build_params) + respond_with(@membership) + end + + def update + @membership.update_attributes(params[:membership]) + respond_with(@membership) + end + + def destroy + @membership.destroy + respond_with(@membership) + end + + protected + + def load_membership + @membership ||= load_memberships.find(params[:id]) + end + + def load_memberships + @memberships ||= current_site.memberships + end + + end + + end +end + diff --git a/config/routes.rb b/config/routes.rb index 3c16bf32..b6974a20 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -70,6 +70,8 @@ Rails.application.routes.draw do resources :sites + resources :memberships + resource :current_site, :controller => 'current_site' end diff --git a/features/api/authorization/memberships.feature b/features/api/authorization/memberships.feature new file mode 100644 index 00000000..35663c50 --- /dev/null +++ b/features/api/authorization/memberships.feature @@ -0,0 +1,225 @@ +Feature: Memberships + In order to ensure memberships are not tampered with + As an admin, designer or author + I will be restricted based on my role + + Background: + Given I have the site: "test site" set up with id: "4f832c2cb0d86d3f42fffffb" + And I have accounts: + | email | id | + | new-user@a.com | 4f832c2cb0d86d3f42fffffc | + And I have memberships: + | email | role | id | + | admin@a.com | admin | 4f832c2cb0d86d3f42fffffd | + | designer@a.com | designer | 4f832c2cb0d86d3f42fffffe | + | author@a.com | author | 4f832c2cb0d86d3f42ffffff | + + Scenario: As an unauthenticated user + Given I am not authenticated + When I do an API GET to memberships.json + Then the JSON response at "error" should be "You need to sign in or sign up before continuing." + + # listing memberships + + Scenario: Accessing memberships as an Admin + Given I have an "admin" API token + When I do an API GET request to memberships.json + Then the JSON response should be an array + And the JSON response should have 4 entries + + Scenario: Accessing memberships as a Designer + Given I have a "designer" API token + When I do an API GET request to memberships.json + Then the JSON response should be an array + And the JSON response should have 4 entries + + Scenario: Accessing memberships as an Author + Given I have an "author" API token + When I do an API GET request to memberships.json + Then an access denied error should occur + + # showing membership + + Scenario: Accessing membership as an Admin + Given I have an "admin" API token + When I do an API GET request to memberships/4f832c2cb0d86d3f42fffffd.json + Then the JSON response at "email" should be "admin@a.com" + When I do an API GET request to memberships/4f832c2cb0d86d3f42fffffe.json + Then the JSON response at "email" should be "designer@a.com" + When I do an API GET request to memberships/4f832c2cb0d86d3f42ffffff.json + Then the JSON response at "email" should be "author@a.com" + + Scenario: Accessing membership as a Designer + Given I have a "designer" API token + When I do an API GET request to memberships/4f832c2cb0d86d3f42fffffd.json + Then the JSON response at "email" should be "admin@a.com" + When I do an API GET request to memberships/4f832c2cb0d86d3f42fffffe.json + Then the JSON response at "email" should be "designer@a.com" + When I do an API GET request to memberships/4f832c2cb0d86d3f42ffffff.json + Then the JSON response at "email" should be "author@a.com" + + Scenario: Accessing membership as an Author + Given I have an "author" API token + When I do an API GET request to memberships/4f832c2cb0d86d3f42fffffe.json + Then an access denied error should occur + + # create membership + + Scenario: Creating new membership as an Admin + Given I have an "admin" API token + When I do an API POST to memberships.json with: + """ + { + "membership": { + "site_id": "4f832c2cb0d86d3f42fffffb", + "account_id": "4f832c2cb0d86d3f42fffffc" + } + } + """ + When I do an API GET request to memberships.json + Then the JSON response should be an array + And the JSON response should have 5 entries + + Scenario: Creating new membership as a Designer + Given I have a "designer" API token + When I do an API POST to memberships.json with: + """ + { + "membership": { + "site_id": "4f832c2cb0d86d3f42fffffb", + "account_id": "4f832c2cb0d86d3f42fffffc" + } + } + """ + When I do an API GET request to memberships.json + Then the JSON response should be an array + And the JSON response should have 5 entries + + Scenario: Creating new membership as an Author + Given I have an "author" API token + When I do an API POST to memberships.json with: + """ + { + "membership": { + "site_id": "4f832c2cb0d86d3f42fffffb", + "account_id": "4f832c2cb0d86d3f42fffffc" + } + } + """ + Then an access denied error should occur + + Scenario: Created membership should always be Author + Given I have an "admin" API token + When I do an API POST to memberships.json with: + """ + { + "membership": { + "site_id": "4f832c2cb0d86d3f42fffffb", + "account_id": "4f832c2cb0d86d3f42fffffc", + "role": "admin" + } + } + """ + When I do an API GET request to memberships.json + Then the JSON response should be an array + And the JSON response should have 5 entries + And the JSON at "4/role" should be "author" + + # update membership + + Scenario: Updating membership as an Admin + Given I have an "admin" API token + When I do an API PUT to memberships/4f832c2cb0d86d3f42ffffff.json with: + """ + { + "membership": { + "role": "admin" + } + } + """ + When I do an API GET request to memberships/4f832c2cb0d86d3f42ffffff.json + Then the JSON response at "role" should be "admin" + + Scenario: Updating membership as a Designer + Given I have a "designer" API token + When I do an API PUT to memberships/4f832c2cb0d86d3f42ffffff.json with: + """ + { + "membership": { + "role": "admin" + } + } + """ + When I do an API GET request to memberships/4f832c2cb0d86d3f42ffffff.json + Then the JSON response at "role" should be "author" + When I do an API PUT to memberships/4f832c2cb0d86d3f42ffffff.json with: + """ + { + "membership": { + "role": "designer" + } + } + """ + When I do an API GET request to memberships/4f832c2cb0d86d3f42ffffff.json + Then the JSON response at "role" should be "designer" + + Scenario: Updating membership as an Author + Given I have a "author" API token + When I do an API PUT to memberships/4f832c2cb0d86d3f42ffffff.json with: + """ + { + "membership": { + "role": "admin" + } + } + """ + Then an access denied error should occur + When I do an API PUT to memberships/4f832c2cb0d86d3f42ffffff.json with: + """ + { + "membership": { + "role": "designer" + } + } + """ + Then an access denied error should occur + When I do an API PUT to memberships/4f832c2cb0d86d3f42ffffff.json with: + """ + { + "membership": { + "role": "author" + } + } + """ + Then an access denied error should occur + + # destroy membership + + Scenario: Destroying membership as an Admin + Given I have an "admin" API token + When I do an API GET request to memberships.json + Then the JSON response should be an array + And the JSON response should have 4 entries + When I do an API DELETE to memberships/4f832c2cb0d86d3f42ffffff.json + When I do an API GET request to memberships.json + Then the JSON response should be an array + And the JSON response should have 3 entries + + Scenario: Destroying membership as a Designer + Given I have a "designer" API token + When I do an API GET request to memberships.json + Then the JSON response should be an array + And the JSON response should have 4 entries + When I do an API DELETE to memberships/4f832c2cb0d86d3f42ffffff.json + When I do an API GET request to memberships.json + Then the JSON response should be an array + And the JSON response should have 3 entries + When I do an API DELETE to memberships/4f832c2cb0d86d3f42fffffe.json + Then an access denied error should occur + When I do an API DELETE to memberships/4f832c2cb0d86d3f42fffffd.json + Then an access denied error should occur + + Scenario: Deleting membership as an Author + Given I have a "author" API token + When I do an API DELETE to memberships/4f832c2cb0d86d3f42fffffe.json + Then an access denied error should occur diff --git a/features/step_definitions/membership_steps.rb b/features/step_definitions/membership_steps.rb new file mode 100644 index 00000000..780cac2c --- /dev/null +++ b/features/step_definitions/membership_steps.rb @@ -0,0 +1,19 @@ + +Given /^I have accounts:$/ do |accounts_table| + accounts_table.hashes.each do |account_hash| + FactoryGirl.create(:account, account_hash) + end +end + +Given /^I have memberships:$/ do |members_table| + members_table.hashes.each do |member_hash| + email = member_hash[:email] + account = Locomotive::Account.where(:email => email).first \ + || FactoryGirl.create(:account, :email => email) + + member_hash.delete(:email) + member_hash.merge!({ :account => account, :site => @site }) + + FactoryGirl.create(:membership, member_hash) + end +end