diff options
25 files changed, 344 insertions, 29 deletions
diff --git a/CHANGELOG b/CHANGELOG index baddbbf747b..30710fcebf2 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -9,6 +9,7 @@ v 6.3.0 - Fixed issue with 500 error when group did not exist - Ability to leave project - You can create file in repo using UI + - You can remove file from repo using UI - API: dropped default_branch attribute from project during creation - Project default_branch is not stored in db any more. It takes from repo now. - Admin broadcast messages @@ -16,8 +17,26 @@ v 6.3.0 - Dont show last push widget if user removed this branch - Fix 500 error for repos with newline in file name - Extended html titles - - API: create/update repo files + - API: create/update/delete repo files - Admin can transfer project to any namespace + - API: projects/all for admin users + - Fix recent branches order + +v 6.2.4 + - Security: Cast API private_token to string (CVE-2013-4580) + - Security: Require gitlab-shell 1.7.8 (CVE-2013-4581, CVE-2013-4582, CVE-2013-4583) + - Fix for Git SSH access for LDAP users + +v 6.2.3 + - Security: More protection against CVE-2013-4489 + - Security: Require gitlab-shell 1.7.4 (CVE-2013-4490, CVE-2013-4546) + - Fix sidekiq rake tasks + +v 6.2.2 + - Security: Update gitlab_git (CVE-2013-4489) + +v 6.2.1 + - Security: Fix issue with generated passwords for new users v 6.2.0 - Public project pages are now visible to everyone (files, issues, wik, etc.) @@ -104,6 +123,14 @@ v 6.0.0 - Improved MR comments logic - Render readme file for projects in public area +v 5.4.2 + - Security: Cast API private_token to string (CVE-2013-4580) + - Security: Require gitlab-shell 1.7.8 (CVE-2013-4581, CVE-2013-4582, CVE-2013-4583) + +v 5.4.1 + - Security: Fixes for CVE-2013-4489 + - Security: Require gitlab-shell 1.7.4 (CVE-2013-4490, CVE-2013-4546) + v 5.4.0 - Ability to edit own comments - Documentation improvements @@ -1 +1 @@ -6.3.0.pre +6.3.0.beta1 diff --git a/app/assets/stylesheets/gitlab_bootstrap/common.scss b/app/assets/stylesheets/gitlab_bootstrap/common.scss index 82ec7762ba8..018c8d9e3aa 100644 --- a/app/assets/stylesheets/gitlab_bootstrap/common.scss +++ b/app/assets/stylesheets/gitlab_bootstrap/common.scss @@ -93,6 +93,12 @@ pre.well-pre { font-size: 12px; font-style: normal; font-weight: normal; + + &.label-gray { + background-color: #eee; + color: #999; + text-shadow: none; + } } /** Big Labels **/ diff --git a/app/assets/stylesheets/gitlab_bootstrap/forms.scss b/app/assets/stylesheets/gitlab_bootstrap/forms.scss index 3df5ebab276..d4da3fe7139 100644 --- a/app/assets/stylesheets/gitlab_bootstrap/forms.scss +++ b/app/assets/stylesheets/gitlab_bootstrap/forms.scss @@ -3,6 +3,16 @@ form { label { @extend .control-label; + + &.radio-label { + text-align: left; + width: 100%; + margin-left: 0; + + input[type="radio"] { + margin-top: 1px !important; + } + } } } diff --git a/app/contexts/files/delete_context.rb b/app/contexts/files/delete_context.rb new file mode 100644 index 00000000000..b1937721d42 --- /dev/null +++ b/app/contexts/files/delete_context.rb @@ -0,0 +1,38 @@ +module Files + class DeleteContext < BaseContext + def execute + allowed = if project.protected_branch?(ref) + can?(current_user, :push_code_to_protected_branches, project) + else + can?(current_user, :push_code, project) + end + + unless allowed + return error("You are not allowed to push into this branch") + end + + unless repository.branch_names.include?(ref) + return error("You can only create files if you are on top of a branch") + end + + blob = repository.blob_at(ref, path) + + unless blob + return error("You can only edit text files") + end + + delete_file_action = Gitlab::Satellite::DeleteFileAction.new(current_user, project, ref, path) + + deleted_successfully = delete_file_action.commit!( + nil, + params[:commit_message] + ) + + if deleted_successfully + success + else + error("Your changes could not be commited, because the file has been changed") + end + end + end +end diff --git a/app/controllers/projects/blob_controller.rb b/app/controllers/projects/blob_controller.rb index ba466251b29..087c1639ac6 100644 --- a/app/controllers/projects/blob_controller.rb +++ b/app/controllers/projects/blob_controller.rb @@ -7,9 +7,30 @@ class Projects::BlobController < Projects::ApplicationController before_filter :authorize_code_access! before_filter :require_non_empty_project + before_filter :blob + def show - @blob = @repository.blob_at(@commit.id, @path) + end + + def destroy + result = Files::DeleteContext.new(@project, current_user, params, @ref, @path).execute + + if result[:status] == :success + flash[:notice] = "Your changes have been successfully commited" + redirect_to project_tree_path(@project, @ref) + else + flash[:alert] = result[:error] + render :show + end + end + + private + + def blob + @blob ||= @repository.blob_at(@commit.id, @path) + + return not_found! unless @blob - not_found! unless @blob + @blob end end diff --git a/app/views/projects/blob/_actions.html.haml b/app/views/projects/blob/_actions.html.haml index f6cc62e707e..2f82bfe52f3 100644 --- a/app/views/projects/blob/_actions.html.haml +++ b/app/views/projects/blob/_actions.html.haml @@ -4,7 +4,7 @@ - if allowed_tree_edit? = link_to "edit", project_edit_tree_path(@project, @id), class: "btn btn-small" - else - %span.btn.btn-small.disabled Edit + %span.btn.btn-small.disabled edit = link_to "raw", project_raw_path(@project, @id), class: "btn btn-small", target: "_blank" -# only show normal/blame view links for text files - if @blob.text? @@ -13,3 +13,7 @@ - else = link_to "blame", project_blame_path(@project, @id), class: "btn btn-small" unless @blob.empty? = link_to "history", project_commits_path(@project, @id), class: "btn btn-small" + + - if allowed_tree_edit? + = link_to '#modal-remove-blob', class: "remove-blob btn btn-small btn-remove", "data-toggle" => "modal" do + remove diff --git a/app/views/projects/blob/_remove.html.haml b/app/views/projects/blob/_remove.html.haml new file mode 100644 index 00000000000..1c097330c44 --- /dev/null +++ b/app/views/projects/blob/_remove.html.haml @@ -0,0 +1,19 @@ +%div#modal-remove-blob.modal.hide + .modal-header + %a.close{href: "#", "data-dismiss" => "modal"} × + %h3.page-title Remove #{@blob.name} + %p.light + From branch + %strong= @ref + + .modal-body + = form_tag project_blob_path(@project, @id), method: :delete do + .control-group.commit_message-group + = label_tag 'commit_message', class: "control-label" do + Commit message + .controls + = text_area_tag 'commit_message', params[:commit_message], placeholder: "Removed this file because...", required: true, rows: 3 + .control-group + .controls + = submit_tag 'Remove file', class: 'btn btn-remove' + = link_to "Cancel", '#', class: "btn btn-cancel", "data-dismiss" => "modal" diff --git a/app/views/projects/blob/show.html.haml b/app/views/projects/blob/show.html.haml index d96595bc7f0..56220e520f3 100644 --- a/app/views/projects/blob/show.html.haml +++ b/app/views/projects/blob/show.html.haml @@ -2,3 +2,6 @@ = render 'shared/ref_switcher', destination: 'blob', path: @path %div#tree-holder.tree-holder = render 'blob', blob: @blob + +- if allowed_tree_edit? + = render 'projects/blob/remove' diff --git a/app/views/snippets/_form.html.haml b/app/views/snippets/_form.html.haml index e77550e7be3..517c81fae8d 100644 --- a/app/views/snippets/_form.html.haml +++ b/app/views/snippets/_form.html.haml @@ -13,9 +13,20 @@ = f.label :title .controls= f.text_field :title, placeholder: "Example Snippet", class: 'input-xlarge', required: true .control-group - = f.label "Private?" + = f.label "Access" .controls - = f.check_box :private, {class: ''} + = f.label :private_true, class: 'radio-label' do + = f.radio_button :private, true + %span + %strong Private + (only you can see this snippet) + %br + = f.label :private_false, class: 'radio-label' do + = f.radio_button :private, false + %span + %strong Public + (GitLab users can can see this snippet) + .control-group .file-editor = f.label :file_name, "File" @@ -33,9 +44,10 @@ - else = f.submit 'Save', class: "btn-save btn" - = link_to "Cancel", snippets_path(@project), class: "btn btn-cancel" - unless @snippet.new_record? - .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}" + .pull-right.prepend-left-20 + = 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}" + = link_to "Cancel", snippets_path(@project), class: "btn btn-cancel" :javascript diff --git a/app/views/snippets/_snippet.html.haml b/app/views/snippets/_snippet.html.haml index 8514bc3ddd0..a50d813825f 100644 --- a/app/views/snippets/_snippet.html.haml +++ b/app/views/snippets/_snippet.html.haml @@ -3,7 +3,7 @@ = link_to reliable_snippet_path(snippet) do = truncate(snippet.title, length: 60) - if snippet.private? - %span.label.label-success + %span.label.label-gray %i.icon-lock private %span.cgray.monospace.tiny.pull-right diff --git a/config/routes.rb b/config/routes.rb index 06e8fddf705..d89fc20c6c9 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -173,7 +173,7 @@ Gitlab::Application.routes.draw do end scope module: :projects do - resources :blob, only: [:show], constraints: {id: /.+/} + resources :blob, only: [:show, :destroy], constraints: {id: /.+/} resources :raw, only: [:show], constraints: {id: /.+/} resources :tree, only: [:show], constraints: {id: /.+/, format: /(html|js)/ } resources :edit_tree, only: [:show, :update], constraints: {id: /.+/}, path: 'edit' diff --git a/doc/api/projects.md b/doc/api/projects.md index d4d883e54ec..9b5d62ac832 100644 --- a/doc/api/projects.md +++ b/doc/api/projects.md @@ -2,7 +2,7 @@ ### List projects -Get a list of projects owned by the authenticated user. +Get a list of projects accessible by the authenticated user. ``` GET /projects @@ -82,6 +82,22 @@ GET /projects ``` +#### List owned projects + +Get a list of projects owned by the authenticated user. + +``` +GET /projects/owned +``` + +#### List ALL projects + +Get a list of all GitLab projects (admin only). + +``` +GET /projects/all +``` + ### Get single project Get a specific project, identified by project ID or NAMESPACE/PROJECT_NAME , which is owned by the authentication user. diff --git a/doc/api/repositories.md b/doc/api/repositories.md index 9ec6ba74125..af760795d00 100644 --- a/doc/api/repositories.md +++ b/doc/api/repositories.md @@ -397,3 +397,15 @@ Parameters: + `branch_name` (required) - The name of branch + `content` (required) - New file content + `commit_message` (required) - Commit message + +## Delete existing file in repository + +``` +DELETE /projects/:id/repository/files +``` + +Parameters: + ++ `file_path` (required) - Full path to file. Ex. lib/class.rb ++ `branch_name` (required) - The name of branch ++ `commit_message` (required) - Commit message diff --git a/doc/release/security.md b/doc/release/security.md index 289db184091..a77cbae3eaa 100644 --- a/doc/release/security.md +++ b/doc/release/security.md @@ -13,12 +13,14 @@ Please report suspected security vulnerabilities in private to support@gitlab.co 1. Verify that the issue can be repoduced 1. Acknowledge the issue to the researcher that disclosed it -1. Fix the issue on a feature branch, do this on the private dev.gitlab.org server and update the VERSION and CHANGELOG +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 1. Consider creating and testing workarounds 1. Create feature branches for the blog posts on GitLab.org and GitLab.com and link them from the code branch -1. Merge the code feature branch -1. Create a git tag vX.X.X for CE and another one for EE +1. Merge the code feature branch into master +1. Cherry-pick the code into the latest stable branch +1. Create a git tag vX.X.X for CE and another patch release for EE 1. Push the code and the tags to all the CE and EE repositories +1. Apply the patch to GitLab Cloud and the private GitLab development server 1. Merge and publish the blog posts 1. Send tweets about the release from @gitlabhq and @git_lab 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 1. Post a signed copy of our complete announcement to [oss-security](http://www.openwall.com/lists/oss-security/) and request a CVE number 1. Add the security researcher to the [Security Researcher Acknowledgments list](http://www.gitlab.com/vulnerability-acknowledgements/) 1. Thank the security researcher in an email for their cooperation -1. Update the blogposts and the CHANGELOG when we receive a CVE number +1. Update the blogpost and the CHANGELOG when we receive the CVE number + +The timing of the code merge into master should be coordinated in advance. +After the merge we strive to publish the announcements within 60 minutes. ## Blog post template XXX Security Advisory for GitLab 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. +We [have|haven't|XXX|PICKSOMETHING|] heard of this vulnerability being actively exploited. ### Version affected diff --git a/features/steps/snippets/snippets.rb b/features/steps/snippets/snippets.rb index bbdf5b97c84..1aea01f6cdf 100644 --- a/features/steps/snippets/snippets.rb +++ b/features/steps/snippets/snippets.rb @@ -19,7 +19,7 @@ class SnippetsFeature < Spinach::FeatureSteps end And 'I click link "Destroy"' do - click_link "Destroy" + click_link "Remove" end And 'I submit new snippet "Personal snippet three"' do @@ -46,7 +46,7 @@ class SnippetsFeature < Spinach::FeatureSteps end And 'I uncheck "Private" checkbox' do - find(:xpath, "//input[@id='personal_snippet_private']").set true + choose "Public" click_button "Save" end diff --git a/lib/api/files.rb b/lib/api/files.rb index e467b0f8e9a..588c27d5692 100644 --- a/lib/api/files.rb +++ b/lib/api/files.rb @@ -40,8 +40,7 @@ module API # Update existing file in repository # # Parameters: - # file_name (required) - The name of new file. Ex. class.rb - # file_path (optional) - The path to new file. Ex. lib/ + # file_path (optional) - The path to file. Ex. lib/class.rb # branch_name (required) - The name of branch # content (required) - File content # commit_message (required) - Commit message @@ -67,7 +66,36 @@ module API render_api_error!(result[:error], 400) end end + + # Delete existing file in repository + # + # Parameters: + # file_path (optional) - The path to file. Ex. lib/class.rb + # branch_name (required) - The name of branch + # content (required) - File content + # commit_message (required) - Commit message + # + # Example Request: + # DELETE /projects/:id/repository/files + # + delete ":id/repository/files" do + required_attributes! [:file_path, :branch_name, :commit_message] + attrs = attributes_for_keys [:file_path, :branch_name, :commit_message] + branch_name = attrs.delete(:branch_name) + file_path = attrs.delete(:file_path) + result = ::Files::DeleteContext.new(user_project, current_user, attrs, branch_name, file_path).execute + + if result[:status] == :success + status(200) + + { + file_path: file_path, + branch_name: branch_name + } + else + render_api_error!(result[:error], 400) + end + end end end end - diff --git a/lib/api/projects.rb b/lib/api/projects.rb index 42560572046..b927e63f4a4 100644 --- a/lib/api/projects.rb +++ b/lib/api/projects.rb @@ -31,6 +31,16 @@ module API present @projects, with: Entities::Project end + # Get all projects for admin user + # + # Example Request: + # GET /projects/all + get '/all' do + authenticated_as_admin! + @projects = paginate Project + present @projects, with: Entities::Project + end + # Get a single project # # Parameters: diff --git a/lib/gitlab/satellite/files/delete_file_action.rb b/lib/gitlab/satellite/files/delete_file_action.rb new file mode 100644 index 00000000000..10d23f7c243 --- /dev/null +++ b/lib/gitlab/satellite/files/delete_file_action.rb @@ -0,0 +1,43 @@ +require_relative 'file_action' + +module Gitlab + module Satellite + class DeleteFileAction < FileAction + # Deletes file and creates a new commit for it + # + # Returns false if committing the change fails + # Returns false if pushing from the satellite to bare repo failed or was rejected + # Returns true otherwise + def commit!(content, commit_message) + in_locked_and_timed_satellite do |repo| + prepare_satellite!(repo) + + # create target branch in satellite at the corresponding commit from bare repo + repo.git.checkout({raise: true, timeout: true, b: true}, ref, "origin/#{ref}") + + # update the file in the satellite's working dir + file_path_in_satellite = File.join(repo.working_dir, file_path) + File.delete(file_path_in_satellite) + + # add removed file + repo.remove(file_path_in_satellite) + + # commit the changes + # will raise CommandFailed when commit fails + repo.git.commit(raise: true, timeout: true, a: true, m: commit_message) + + + # push commit back to bare repo + # will raise CommandFailed when push fails + repo.git.push({raise: true, timeout: true}, :origin, ref) + + # everything worked + true + end + rescue Grit::Git::CommandFailed => ex + Gitlab::GitLogger.error(ex.message) + false + end + end + end +end diff --git a/lib/gitlab/satellite/files/edit_file_action.rb b/lib/gitlab/satellite/files/edit_file_action.rb index 3747c6afc48..ee9d31ed129 100644 --- a/lib/gitlab/satellite/files/edit_file_action.rb +++ b/lib/gitlab/satellite/files/edit_file_action.rb @@ -8,13 +8,13 @@ module Gitlab # # Returns false if the ref has been updated while editing the file # Returns false if committing the change fails - # Returns false if pushing from the satellite to Gitolite failed or was rejected + # Returns false if pushing from the satellite to bare repo failed or was rejected # Returns true otherwise def commit!(content, commit_message) in_locked_and_timed_satellite do |repo| prepare_satellite!(repo) - # create target branch in satellite at the corresponding commit from Gitolite + # create target branch in satellite at the corresponding commit from bare repo repo.git.checkout({raise: true, timeout: true, b: true}, ref, "origin/#{ref}") # update the file in the satellite's working dir @@ -26,7 +26,7 @@ module Gitlab repo.git.commit(raise: true, timeout: true, a: true, m: commit_message) - # push commit back to Gitolite + # push commit back to bare repo # will raise CommandFailed when push fails repo.git.push({raise: true, timeout: true}, :origin, ref) diff --git a/lib/gitlab/satellite/files/new_file_action.rb b/lib/gitlab/satellite/files/new_file_action.rb index 97b19809c8d..833a3777158 100644 --- a/lib/gitlab/satellite/files/new_file_action.rb +++ b/lib/gitlab/satellite/files/new_file_action.rb @@ -7,13 +7,13 @@ module Gitlab # # Returns false if the ref has been updated while editing the file # Returns false if committing the change fails - # Returns false if pushing from the satellite to Gitolite failed or was rejected + # Returns false if pushing from the satellite to bare repo failed or was rejected # Returns true otherwise def commit!(content, commit_message) in_locked_and_timed_satellite do |repo| prepare_satellite!(repo) - # create target branch in satellite at the corresponding commit from Gitolite + # create target branch in satellite at the corresponding commit from bare repo repo.git.checkout({raise: true, timeout: true, b: true}, ref, "origin/#{ref}") # update the file in the satellite's working dir @@ -28,7 +28,7 @@ module Gitlab repo.git.commit(raise: true, timeout: true, a: true, m: commit_message) - # push commit back to Gitolite + # push commit back to bare repo # will raise CommandFailed when push fails repo.git.push({raise: true, timeout: true}, :origin, ref) diff --git a/lib/gitlab/satellite/merge_action.rb b/lib/gitlab/satellite/merge_action.rb index d74d4194ff6..54afd6ab95c 100644 --- a/lib/gitlab/satellite/merge_action.rb +++ b/lib/gitlab/satellite/merge_action.rb @@ -28,7 +28,7 @@ module Gitlab in_locked_and_timed_satellite do |merge_repo| prepare_satellite!(merge_repo) if merge_in_satellite!(merge_repo) - # push merge back to Gitolite + # push merge back to bare repo # will raise CommandFailed when push fails merge_repo.git.push(default_options, :origin, merge_request.target_branch) # remove source branch diff --git a/lib/gitlab/satellite/satellite.rb b/lib/gitlab/satellite/satellite.rb index 6cb7814fae5..353c3024aad 100644 --- a/lib/gitlab/satellite/satellite.rb +++ b/lib/gitlab/satellite/satellite.rb @@ -123,7 +123,7 @@ module Gitlab remotes.each { |name| repo.git.remote(default_options,'rm', name)} end - # Updates the satellite from Gitolite + # Updates the satellite from bare repo # # Note: this will only update remote branches (i.e. origin/*) def update_from_source! diff --git a/spec/requests/api/files_spec.rb b/spec/requests/api/files_spec.rb index 345f2bae65a..0e2a48689ac 100644 --- a/spec/requests/api/files_spec.rb +++ b/spec/requests/api/files_spec.rb @@ -78,4 +78,38 @@ describe API::API do response.status.should == 400 end end + + describe "DELETE /projects/:id/repository/files" do + let(:valid_params) { + { + file_path: 'spec/spec_helper.rb', + branch_name: 'master', + commit_message: 'Changed file' + } + } + + it "should delete existing file in project repo" do + Gitlab::Satellite::DeleteFileAction.any_instance.stub( + commit!: true, + ) + + delete api("/projects/#{project.id}/repository/files", user), valid_params + response.status.should == 200 + json_response['file_path'].should == 'spec/spec_helper.rb' + end + + it "should return a 400 bad request if no params given" do + delete api("/projects/#{project.id}/repository/files", user) + response.status.should == 400 + end + + it "should return a 400 if satellite fails to create file" do + Gitlab::Satellite::DeleteFileAction.any_instance.stub( + commit!: false, + ) + + delete api("/projects/#{project.id}/repository/files", user), valid_params + response.status.should == 400 + end + end end diff --git a/spec/requests/api/projects_spec.rb b/spec/requests/api/projects_spec.rb index 2ae186b967a..e4cef6c587c 100644 --- a/spec/requests/api/projects_spec.rb +++ b/spec/requests/api/projects_spec.rb @@ -36,6 +36,32 @@ describe API::API do end end + describe "GET /projects/all" do + context "when unauthenticated" do + it "should return authentication error" do + get api("/projects/all") + response.status.should == 401 + end + end + + context "when authenticated as regular user" do + it "should return authentication error" do + get api("/projects/all", user) + response.status.should == 403 + end + end + + context "when authenticated as admin" do + it "should return an array of all projects" do + get api("/projects/all", admin) + response.status.should == 200 + json_response.should be_an Array + json_response.first['name'].should == project.name + json_response.first['owner']['email'].should == user.email + end + end + end + describe "POST /projects" do context "maximum number of projects reached" do before do |