Commit 6413bfb5c640d44d38fe4054c7b33f1db285c687

Authored by Sytse Sijbrandij
2 parents 250a86d2 6cc3cc51

Merge branch 'master' into full-post-to-oss-security

Conflicts:
	doc/release/security.md
CHANGELOG
... ... @@ -9,6 +9,7 @@ v 6.3.0
9 9 - Fixed issue with 500 error when group did not exist
10 10 - Ability to leave project
11 11 - You can create file in repo using UI
  12 + - You can remove file from repo using UI
12 13 - API: dropped default_branch attribute from project during creation
13 14 - Project default_branch is not stored in db any more. It takes from repo now.
14 15 - Admin broadcast messages
... ... @@ -16,8 +17,26 @@ v 6.3.0
16 17 - Dont show last push widget if user removed this branch
17 18 - Fix 500 error for repos with newline in file name
18 19 - Extended html titles
19   - - API: create/update repo files
  20 + - API: create/update/delete repo files
20 21 - Admin can transfer project to any namespace
  22 + - API: projects/all for admin users
  23 + - Fix recent branches order
  24 +
  25 +v 6.2.4
  26 + - Security: Cast API private_token to string (CVE-2013-4580)
  27 + - Security: Require gitlab-shell 1.7.8 (CVE-2013-4581, CVE-2013-4582, CVE-2013-4583)
  28 + - Fix for Git SSH access for LDAP users
  29 +
  30 +v 6.2.3
  31 + - Security: More protection against CVE-2013-4489
  32 + - Security: Require gitlab-shell 1.7.4 (CVE-2013-4490, CVE-2013-4546)
  33 + - Fix sidekiq rake tasks
  34 +
  35 +v 6.2.2
  36 + - Security: Update gitlab_git (CVE-2013-4489)
  37 +
  38 +v 6.2.1
  39 + - Security: Fix issue with generated passwords for new users
21 40  
22 41 v 6.2.0
23 42 - Public project pages are now visible to everyone (files, issues, wik, etc.)
... ... @@ -104,6 +123,14 @@ v 6.0.0
104 123 - Improved MR comments logic
105 124 - Render readme file for projects in public area
106 125  
  126 +v 5.4.2
  127 + - Security: Cast API private_token to string (CVE-2013-4580)
  128 + - Security: Require gitlab-shell 1.7.8 (CVE-2013-4581, CVE-2013-4582, CVE-2013-4583)
  129 +
  130 +v 5.4.1
  131 + - Security: Fixes for CVE-2013-4489
  132 + - Security: Require gitlab-shell 1.7.4 (CVE-2013-4490, CVE-2013-4546)
  133 +
107 134 v 5.4.0
108 135 - Ability to edit own comments
109 136 - Documentation improvements
... ...
VERSION
1   -6.3.0.pre
  1 +6.3.0.beta1
... ...
app/assets/stylesheets/gitlab_bootstrap/common.scss
... ... @@ -93,6 +93,12 @@ pre.well-pre {
93 93 font-size: 12px;
94 94 font-style: normal;
95 95 font-weight: normal;
  96 +
  97 + &.label-gray {
  98 + background-color: #eee;
  99 + color: #999;
  100 + text-shadow: none;
  101 + }
96 102 }
97 103  
98 104 /** Big Labels **/
... ...
app/assets/stylesheets/gitlab_bootstrap/forms.scss
... ... @@ -3,6 +3,16 @@ form {
3 3  
4 4 label {
5 5 @extend .control-label;
  6 +
  7 + &.radio-label {
  8 + text-align: left;
  9 + width: 100%;
  10 + margin-left: 0;
  11 +
  12 + input[type="radio"] {
  13 + margin-top: 1px !important;
  14 + }
  15 + }
6 16 }
7 17 }
8 18  
... ...
app/contexts/files/delete_context.rb 0 → 100644
... ... @@ -0,0 +1,38 @@
  1 +module Files
  2 + class DeleteContext < BaseContext
  3 + def execute
  4 + allowed = if project.protected_branch?(ref)
  5 + can?(current_user, :push_code_to_protected_branches, project)
  6 + else
  7 + can?(current_user, :push_code, project)
  8 + end
  9 +
  10 + unless allowed
  11 + return error("You are not allowed to push into this branch")
  12 + end
  13 +
  14 + unless repository.branch_names.include?(ref)
  15 + return error("You can only create files if you are on top of a branch")
  16 + end
  17 +
  18 + blob = repository.blob_at(ref, path)
  19 +
  20 + unless blob
  21 + return error("You can only edit text files")
  22 + end
  23 +
  24 + delete_file_action = Gitlab::Satellite::DeleteFileAction.new(current_user, project, ref, path)
  25 +
  26 + deleted_successfully = delete_file_action.commit!(
  27 + nil,
  28 + params[:commit_message]
  29 + )
  30 +
  31 + if deleted_successfully
  32 + success
  33 + else
  34 + error("Your changes could not be commited, because the file has been changed")
  35 + end
  36 + end
  37 + end
  38 +end
... ...
app/controllers/projects/blob_controller.rb
... ... @@ -7,9 +7,30 @@ class Projects::BlobController &lt; Projects::ApplicationController
7 7 before_filter :authorize_code_access!
8 8 before_filter :require_non_empty_project
9 9  
  10 + before_filter :blob
  11 +
10 12 def show
11   - @blob = @repository.blob_at(@commit.id, @path)
  13 + end
  14 +
  15 + def destroy
  16 + result = Files::DeleteContext.new(@project, current_user, params, @ref, @path).execute
  17 +
  18 + if result[:status] == :success
  19 + flash[:notice] = "Your changes have been successfully commited"
  20 + redirect_to project_tree_path(@project, @ref)
  21 + else
  22 + flash[:alert] = result[:error]
  23 + render :show
  24 + end
  25 + end
  26 +
  27 + private
  28 +
  29 + def blob
  30 + @blob ||= @repository.blob_at(@commit.id, @path)
  31 +
  32 + return not_found! unless @blob
12 33  
13   - not_found! unless @blob
  34 + @blob
14 35 end
15 36 end
... ...
app/views/projects/blob/_actions.html.haml
... ... @@ -4,7 +4,7 @@
4 4 - if allowed_tree_edit?
5 5 = link_to "edit", project_edit_tree_path(@project, @id), class: "btn btn-small"
6 6 - else
7   - %span.btn.btn-small.disabled Edit
  7 + %span.btn.btn-small.disabled edit
8 8 = link_to "raw", project_raw_path(@project, @id), class: "btn btn-small", target: "_blank"
9 9 -# only show normal/blame view links for text files
10 10 - if @blob.text?
... ... @@ -13,3 +13,7 @@
13 13 - else
14 14 = link_to "blame", project_blame_path(@project, @id), class: "btn btn-small" unless @blob.empty?
15 15 = link_to "history", project_commits_path(@project, @id), class: "btn btn-small"
  16 +
  17 + - if allowed_tree_edit?
  18 + = link_to '#modal-remove-blob', class: "remove-blob btn btn-small btn-remove", "data-toggle" => "modal" do
  19 + remove
... ...
app/views/projects/blob/_remove.html.haml 0 → 100644
... ... @@ -0,0 +1,19 @@
  1 +%div#modal-remove-blob.modal.hide
  2 + .modal-header
  3 + %a.close{href: "#", "data-dismiss" => "modal"} ×
  4 + %h3.page-title Remove #{@blob.name}
  5 + %p.light
  6 + From branch
  7 + %strong= @ref
  8 +
  9 + .modal-body
  10 + = form_tag project_blob_path(@project, @id), method: :delete do
  11 + .control-group.commit_message-group
  12 + = label_tag 'commit_message', class: "control-label" do
  13 + Commit message
  14 + .controls
  15 + = text_area_tag 'commit_message', params[:commit_message], placeholder: "Removed this file because...", required: true, rows: 3
  16 + .control-group
  17 + .controls
  18 + = submit_tag 'Remove file', class: 'btn btn-remove'
  19 + = link_to "Cancel", '#', class: "btn btn-cancel", "data-dismiss" => "modal"
... ...
app/views/projects/blob/show.html.haml
... ... @@ -2,3 +2,6 @@
2 2 = render 'shared/ref_switcher', destination: 'blob', path: @path
3 3 %div#tree-holder.tree-holder
4 4 = render 'blob', blob: @blob
  5 +
  6 +- if allowed_tree_edit?
  7 + = render 'projects/blob/remove'
... ...
app/views/snippets/_form.html.haml
... ... @@ -13,9 +13,20 @@
13 13 = f.label :title
14 14 .controls= f.text_field :title, placeholder: "Example Snippet", class: 'input-xlarge', required: true
15 15 .control-group
16   - = f.label "Private?"
  16 + = f.label "Access"
17 17 .controls
18   - = f.check_box :private, {class: ''}
  18 + = f.label :private_true, class: 'radio-label' do
  19 + = f.radio_button :private, true
  20 + %span
  21 + %strong Private
  22 + (only you can see this snippet)
  23 + %br
  24 + = f.label :private_false, class: 'radio-label' do
  25 + = f.radio_button :private, false
  26 + %span
  27 + %strong Public
  28 + (GitLab users can can see this snippet)
  29 +
19 30 .control-group
20 31 .file-editor
21 32 = f.label :file_name, "File"
... ... @@ -33,9 +44,10 @@
33 44 - else
34 45 = f.submit 'Save', class: "btn-save btn"
35 46  
36   - = link_to "Cancel", snippets_path(@project), class: "btn btn-cancel"
37 47 - unless @snippet.new_record?
38   - .pull-right= link_to 'Destroy', snippet_path(@snippet), confirm: 'Removed snippet cannot be restored! Are you sure?', method: :delete, class: "btn pull-right danger delete-snippet", id: "destroy_snippet_#{@snippet.id}"
  48 + .pull-right.prepend-left-20
  49 + = link_to 'Remove', snippet_path(@snippet), confirm: 'Removed snippet cannot be restored! Are you sure?', method: :delete, class: "btn btn-remove delete-snippet", id: "destroy_snippet_#{@snippet.id}"
  50 + = link_to "Cancel", snippets_path(@project), class: "btn btn-cancel"
39 51  
40 52  
41 53 :javascript
... ...
app/views/snippets/_snippet.html.haml
... ... @@ -3,7 +3,7 @@
3 3 = link_to reliable_snippet_path(snippet) do
4 4 = truncate(snippet.title, length: 60)
5 5 - if snippet.private?
6   - %span.label.label-success
  6 + %span.label.label-gray
7 7 %i.icon-lock
8 8 private
9 9 %span.cgray.monospace.tiny.pull-right
... ...
config/routes.rb
... ... @@ -173,7 +173,7 @@ Gitlab::Application.routes.draw do
173 173 end
174 174  
175 175 scope module: :projects do
176   - resources :blob, only: [:show], constraints: {id: /.+/}
  176 + resources :blob, only: [:show, :destroy], constraints: {id: /.+/}
177 177 resources :raw, only: [:show], constraints: {id: /.+/}
178 178 resources :tree, only: [:show], constraints: {id: /.+/, format: /(html|js)/ }
179 179 resources :edit_tree, only: [:show, :update], constraints: {id: /.+/}, path: 'edit'
... ...
doc/api/projects.md
... ... @@ -2,7 +2,7 @@
2 2  
3 3 ### List projects
4 4  
5   -Get a list of projects owned by the authenticated user.
  5 +Get a list of projects accessible by the authenticated user.
6 6  
7 7 ```
8 8 GET /projects
... ... @@ -82,6 +82,22 @@ GET /projects
82 82 ```
83 83  
84 84  
  85 +#### List owned projects
  86 +
  87 +Get a list of projects owned by the authenticated user.
  88 +
  89 +```
  90 +GET /projects/owned
  91 +```
  92 +
  93 +#### List ALL projects
  94 +
  95 +Get a list of all GitLab projects (admin only).
  96 +
  97 +```
  98 +GET /projects/all
  99 +```
  100 +
85 101 ### Get single project
86 102  
87 103 Get a specific project, identified by project ID or NAMESPACE/PROJECT_NAME , which is owned by the authentication user.
... ...
doc/api/repositories.md
... ... @@ -397,3 +397,15 @@ Parameters:
397 397 + `branch_name` (required) - The name of branch
398 398 + `content` (required) - New file content
399 399 + `commit_message` (required) - Commit message
  400 +
  401 +## Delete existing file in repository
  402 +
  403 +```
  404 +DELETE /projects/:id/repository/files
  405 +```
  406 +
  407 +Parameters:
  408 +
  409 ++ `file_path` (required) - Full path to file. Ex. lib/class.rb
  410 ++ `branch_name` (required) - The name of branch
  411 ++ `commit_message` (required) - Commit message
... ...
doc/release/security.md
... ... @@ -13,12 +13,14 @@ Please report suspected security vulnerabilities in private to support@gitlab.co
13 13  
14 14 1. Verify that the issue can be repoduced
15 15 1. Acknowledge the issue to the researcher that disclosed it
16   -1. Fix the issue on a feature branch, do this on the private dev.gitlab.org server and update the VERSION and CHANGELOG
  16 +1. Fix the issue on a feature branch, do this on the private GitLab development server and update the VERSION and CHANGELOG in this branch
17 17 1. Consider creating and testing workarounds
18 18 1. Create feature branches for the blog posts on GitLab.org and GitLab.com and link them from the code branch
19   -1. Merge the code feature branch
20   -1. Create a git tag vX.X.X for CE and another one for EE
  19 +1. Merge the code feature branch into master
  20 +1. Cherry-pick the code into the latest stable branch
  21 +1. Create a git tag vX.X.X for CE and another patch release for EE
21 22 1. Push the code and the tags to all the CE and EE repositories
  23 +1. Apply the patch to GitLab Cloud and the private GitLab development server
22 24 1. Merge and publish the blog posts
23 25 1. Send tweets about the release from @gitlabhq and @git_lab
24 26 1. Send out an email to the subscribers mailing list on MailChimp
... ... @@ -27,13 +29,17 @@ Please report suspected security vulnerabilities in private to support@gitlab.co
27 29 1. Post a signed copy of our complete announcement to [oss-security](http://www.openwall.com/lists/oss-security/) and request a CVE number
28 30 1. Add the security researcher to the [Security Researcher Acknowledgments list](http://www.gitlab.com/vulnerability-acknowledgements/)
29 31 1. Thank the security researcher in an email for their cooperation
30   -1. Update the blogposts and the CHANGELOG when we receive a CVE number
  32 +1. Update the blogpost and the CHANGELOG when we receive the CVE number
  33 +
  34 +The timing of the code merge into master should be coordinated in advance.
  35 +After the merge we strive to publish the announcements within 60 minutes.
31 36  
32 37 ## Blog post template
33 38  
34 39 XXX Security Advisory for GitLab
35 40  
36 41 A recently discovered critical vulnerability in GitLab allows [unauthenticated API access|remote code execution|unauthorized access to repositories|XXX|PICKSOMETHING]. All users should update GitLab and gitlab-shell immediately.
  42 +We [have|haven't|XXX|PICKSOMETHING|] heard of this vulnerability being actively exploited.
37 43  
38 44 ### Version affected
39 45  
... ...
features/steps/snippets/snippets.rb
... ... @@ -19,7 +19,7 @@ class SnippetsFeature &lt; Spinach::FeatureSteps
19 19 end
20 20  
21 21 And 'I click link "Destroy"' do
22   - click_link "Destroy"
  22 + click_link "Remove"
23 23 end
24 24  
25 25 And 'I submit new snippet "Personal snippet three"' do
... ... @@ -46,7 +46,7 @@ class SnippetsFeature &lt; Spinach::FeatureSteps
46 46 end
47 47  
48 48 And 'I uncheck "Private" checkbox' do
49   - find(:xpath, "//input[@id='personal_snippet_private']").set true
  49 + choose "Public"
50 50 click_button "Save"
51 51 end
52 52  
... ...
lib/api/files.rb
... ... @@ -40,8 +40,7 @@ module API
40 40 # Update existing file in repository
41 41 #
42 42 # Parameters:
43   - # file_name (required) - The name of new file. Ex. class.rb
44   - # file_path (optional) - The path to new file. Ex. lib/
  43 + # file_path (optional) - The path to file. Ex. lib/class.rb
45 44 # branch_name (required) - The name of branch
46 45 # content (required) - File content
47 46 # commit_message (required) - Commit message
... ... @@ -67,7 +66,36 @@ module API
67 66 render_api_error!(result[:error], 400)
68 67 end
69 68 end
  69 +
  70 + # Delete existing file in repository
  71 + #
  72 + # Parameters:
  73 + # file_path (optional) - The path to file. Ex. lib/class.rb
  74 + # branch_name (required) - The name of branch
  75 + # content (required) - File content
  76 + # commit_message (required) - Commit message
  77 + #
  78 + # Example Request:
  79 + # DELETE /projects/:id/repository/files
  80 + #
  81 + delete ":id/repository/files" do
  82 + required_attributes! [:file_path, :branch_name, :commit_message]
  83 + attrs = attributes_for_keys [:file_path, :branch_name, :commit_message]
  84 + branch_name = attrs.delete(:branch_name)
  85 + file_path = attrs.delete(:file_path)
  86 + result = ::Files::DeleteContext.new(user_project, current_user, attrs, branch_name, file_path).execute
  87 +
  88 + if result[:status] == :success
  89 + status(200)
  90 +
  91 + {
  92 + file_path: file_path,
  93 + branch_name: branch_name
  94 + }
  95 + else
  96 + render_api_error!(result[:error], 400)
  97 + end
  98 + end
70 99 end
71 100 end
72 101 end
73   -
... ...
lib/api/projects.rb
... ... @@ -31,6 +31,16 @@ module API
31 31 present @projects, with: Entities::Project
32 32 end
33 33  
  34 + # Get all projects for admin user
  35 + #
  36 + # Example Request:
  37 + # GET /projects/all
  38 + get '/all' do
  39 + authenticated_as_admin!
  40 + @projects = paginate Project
  41 + present @projects, with: Entities::Project
  42 + end
  43 +
34 44 # Get a single project
35 45 #
36 46 # Parameters:
... ...
lib/gitlab/satellite/files/delete_file_action.rb 0 → 100644
... ... @@ -0,0 +1,43 @@
  1 +require_relative 'file_action'
  2 +
  3 +module Gitlab
  4 + module Satellite
  5 + class DeleteFileAction < FileAction
  6 + # Deletes file and creates a new commit for it
  7 + #
  8 + # Returns false if committing the change fails
  9 + # Returns false if pushing from the satellite to bare repo failed or was rejected
  10 + # Returns true otherwise
  11 + def commit!(content, commit_message)
  12 + in_locked_and_timed_satellite do |repo|
  13 + prepare_satellite!(repo)
  14 +
  15 + # create target branch in satellite at the corresponding commit from bare repo
  16 + repo.git.checkout({raise: true, timeout: true, b: true}, ref, "origin/#{ref}")
  17 +
  18 + # update the file in the satellite's working dir
  19 + file_path_in_satellite = File.join(repo.working_dir, file_path)
  20 + File.delete(file_path_in_satellite)
  21 +
  22 + # add removed file
  23 + repo.remove(file_path_in_satellite)
  24 +
  25 + # commit the changes
  26 + # will raise CommandFailed when commit fails
  27 + repo.git.commit(raise: true, timeout: true, a: true, m: commit_message)
  28 +
  29 +
  30 + # push commit back to bare repo
  31 + # will raise CommandFailed when push fails
  32 + repo.git.push({raise: true, timeout: true}, :origin, ref)
  33 +
  34 + # everything worked
  35 + true
  36 + end
  37 + rescue Grit::Git::CommandFailed => ex
  38 + Gitlab::GitLogger.error(ex.message)
  39 + false
  40 + end
  41 + end
  42 + end
  43 +end
... ...
lib/gitlab/satellite/files/edit_file_action.rb
... ... @@ -8,13 +8,13 @@ module Gitlab
8 8 #
9 9 # Returns false if the ref has been updated while editing the file
10 10 # Returns false if committing the change fails
11   - # Returns false if pushing from the satellite to Gitolite failed or was rejected
  11 + # Returns false if pushing from the satellite to bare repo failed or was rejected
12 12 # Returns true otherwise
13 13 def commit!(content, commit_message)
14 14 in_locked_and_timed_satellite do |repo|
15 15 prepare_satellite!(repo)
16 16  
17   - # create target branch in satellite at the corresponding commit from Gitolite
  17 + # create target branch in satellite at the corresponding commit from bare repo
18 18 repo.git.checkout({raise: true, timeout: true, b: true}, ref, "origin/#{ref}")
19 19  
20 20 # update the file in the satellite's working dir
... ... @@ -26,7 +26,7 @@ module Gitlab
26 26 repo.git.commit(raise: true, timeout: true, a: true, m: commit_message)
27 27  
28 28  
29   - # push commit back to Gitolite
  29 + # push commit back to bare repo
30 30 # will raise CommandFailed when push fails
31 31 repo.git.push({raise: true, timeout: true}, :origin, ref)
32 32  
... ...
lib/gitlab/satellite/files/new_file_action.rb
... ... @@ -7,13 +7,13 @@ module Gitlab
7 7 #
8 8 # Returns false if the ref has been updated while editing the file
9 9 # Returns false if committing the change fails
10   - # Returns false if pushing from the satellite to Gitolite failed or was rejected
  10 + # Returns false if pushing from the satellite to bare repo failed or was rejected
11 11 # Returns true otherwise
12 12 def commit!(content, commit_message)
13 13 in_locked_and_timed_satellite do |repo|
14 14 prepare_satellite!(repo)
15 15  
16   - # create target branch in satellite at the corresponding commit from Gitolite
  16 + # create target branch in satellite at the corresponding commit from bare repo
17 17 repo.git.checkout({raise: true, timeout: true, b: true}, ref, "origin/#{ref}")
18 18  
19 19 # update the file in the satellite's working dir
... ... @@ -28,7 +28,7 @@ module Gitlab
28 28 repo.git.commit(raise: true, timeout: true, a: true, m: commit_message)
29 29  
30 30  
31   - # push commit back to Gitolite
  31 + # push commit back to bare repo
32 32 # will raise CommandFailed when push fails
33 33 repo.git.push({raise: true, timeout: true}, :origin, ref)
34 34  
... ...
lib/gitlab/satellite/merge_action.rb
... ... @@ -28,7 +28,7 @@ module Gitlab
28 28 in_locked_and_timed_satellite do |merge_repo|
29 29 prepare_satellite!(merge_repo)
30 30 if merge_in_satellite!(merge_repo)
31   - # push merge back to Gitolite
  31 + # push merge back to bare repo
32 32 # will raise CommandFailed when push fails
33 33 merge_repo.git.push(default_options, :origin, merge_request.target_branch)
34 34 # remove source branch
... ...
lib/gitlab/satellite/satellite.rb
... ... @@ -123,7 +123,7 @@ module Gitlab
123 123 remotes.each { |name| repo.git.remote(default_options,'rm', name)}
124 124 end
125 125  
126   - # Updates the satellite from Gitolite
  126 + # Updates the satellite from bare repo
127 127 #
128 128 # Note: this will only update remote branches (i.e. origin/*)
129 129 def update_from_source!
... ...
spec/requests/api/files_spec.rb
... ... @@ -78,4 +78,38 @@ describe API::API do
78 78 response.status.should == 400
79 79 end
80 80 end
  81 +
  82 + describe "DELETE /projects/:id/repository/files" do
  83 + let(:valid_params) {
  84 + {
  85 + file_path: 'spec/spec_helper.rb',
  86 + branch_name: 'master',
  87 + commit_message: 'Changed file'
  88 + }
  89 + }
  90 +
  91 + it "should delete existing file in project repo" do
  92 + Gitlab::Satellite::DeleteFileAction.any_instance.stub(
  93 + commit!: true,
  94 + )
  95 +
  96 + delete api("/projects/#{project.id}/repository/files", user), valid_params
  97 + response.status.should == 200
  98 + json_response['file_path'].should == 'spec/spec_helper.rb'
  99 + end
  100 +
  101 + it "should return a 400 bad request if no params given" do
  102 + delete api("/projects/#{project.id}/repository/files", user)
  103 + response.status.should == 400
  104 + end
  105 +
  106 + it "should return a 400 if satellite fails to create file" do
  107 + Gitlab::Satellite::DeleteFileAction.any_instance.stub(
  108 + commit!: false,
  109 + )
  110 +
  111 + delete api("/projects/#{project.id}/repository/files", user), valid_params
  112 + response.status.should == 400
  113 + end
  114 + end
81 115 end
... ...
spec/requests/api/projects_spec.rb
... ... @@ -36,6 +36,32 @@ describe API::API do
36 36 end
37 37 end
38 38  
  39 + describe "GET /projects/all" do
  40 + context "when unauthenticated" do
  41 + it "should return authentication error" do
  42 + get api("/projects/all")
  43 + response.status.should == 401
  44 + end
  45 + end
  46 +
  47 + context "when authenticated as regular user" do
  48 + it "should return authentication error" do
  49 + get api("/projects/all", user)
  50 + response.status.should == 403
  51 + end
  52 + end
  53 +
  54 + context "when authenticated as admin" do
  55 + it "should return an array of all projects" do
  56 + get api("/projects/all", admin)
  57 + response.status.should == 200
  58 + json_response.should be_an Array
  59 + json_response.first['name'].should == project.name
  60 + json_response.first['owner']['email'].should == user.email
  61 + end
  62 + end
  63 + end
  64 +
39 65 describe "POST /projects" do
40 66 context "maximum number of projects reached" do
41 67 before do
... ...