From 35266de2f0e91ac73995ab8ced1bbcb12e35f773 Mon Sep 17 00:00:00 2001 From: Chris McKnight Date: Wed, 6 Jan 2016 11:20:52 -0600 Subject: [PATCH 001/196] Updates git lfs initialize command --- doc/workflow/lfs/manage_large_binaries_with_git_lfs.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/doc/workflow/lfs/manage_large_binaries_with_git_lfs.md b/doc/workflow/lfs/manage_large_binaries_with_git_lfs.md index ba91685a20b..83db44c10b1 100644 --- a/doc/workflow/lfs/manage_large_binaries_with_git_lfs.md +++ b/doc/workflow/lfs/manage_large_binaries_with_git_lfs.md @@ -44,7 +44,7 @@ check it into your Git repository: ```bash git clone git@gitlab.example.com:group/project.git -git lfs init # initialize the Git LFS project project +git lfs install # initialize the Git LFS project project git lfs track "*.iso" # select the file extensions that you want to treat as large files ``` @@ -152,4 +152,4 @@ If you are using OS X you can use `osxkeychain` to store and encrypt your creden For Windows, you can use `wincred` or Microsoft's [Git Credential Manager for Windows](https://github.com/Microsoft/Git-Credential-Manager-for-Windows/releases). More details about various methods of storing the user credentials can be found -on [Git Credential Storage documentation](https://git-scm.com/book/en/v2/Git-Tools-Credential-Storage). \ No newline at end of file +on [Git Credential Storage documentation](https://git-scm.com/book/en/v2/Git-Tools-Credential-Storage). From de95ab6257fcb38f109d9d1aea03618ae9981738 Mon Sep 17 00:00:00 2001 From: Connor Shea Date: Sat, 16 Apr 2016 18:03:20 -0600 Subject: [PATCH 002/196] Enable the UrlFormat and UrlQuotes SCSS Linters. --- .scss-lint.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.scss-lint.yml b/.scss-lint.yml index 835a4a88c44..bddb48eb090 100644 --- a/.scss-lint.yml +++ b/.scss-lint.yml @@ -244,11 +244,11 @@ linters: # URLs should be valid and not contain protocols or domain names. UrlFormat: - enabled: false + enabled: true # URLs should always be enclosed within quotes. UrlQuotes: - enabled: false + enabled: true # Properties, like color and font, are easier to read and maintain # when defined using variables rather than literals. From a6ba8647f919cca5f37f663502186d8b6b7642ec Mon Sep 17 00:00:00 2001 From: Robert Speicher Date: Tue, 19 Apr 2016 16:00:45 -0400 Subject: [PATCH 003/196] Improve uniqueness of field names on the signup form Closes https://gitlab.com/gitlab-org/gitlab-ce/issues/15075 --- app/controllers/registrations_controller.rb | 7 ++++++ app/views/devise/shared/_signup_box.html.haml | 4 ++-- spec/features/signup_spec.rb | 24 +++++++++---------- spec/features/users_spec.rb | 16 ++++++------- 4 files changed, 29 insertions(+), 22 deletions(-) diff --git a/app/controllers/registrations_controller.rb b/app/controllers/registrations_controller.rb index c48175a4c5a..b441b34d0be 100644 --- a/app/controllers/registrations_controller.rb +++ b/app/controllers/registrations_controller.rb @@ -8,6 +8,13 @@ class RegistrationsController < Devise::RegistrationsController def create if !Gitlab::Recaptcha.load_configurations! || verify_recaptcha + # To avoid duplicate form fields on the login page, the registration form + # names fields using `new_user`, but Devise still wants the params in + # `user`. + if params["new_#{resource_name}"].present? && params[resource_name].blank? + params[resource_name] = params.delete(:"new_#{resource_name}") + end + super else flash[:alert] = "There was an error with the reCAPTCHA code below. Please re-enter the code." diff --git a/app/views/devise/shared/_signup_box.html.haml b/app/views/devise/shared/_signup_box.html.haml index e5607dacd0d..510215bb8cd 100644 --- a/app/views/devise/shared/_signup_box.html.haml +++ b/app/views/devise/shared/_signup_box.html.haml @@ -6,7 +6,7 @@ .login-heading %h3 Create an account .login-body - = form_for(resource, as: resource_name, url: registration_path(resource_name)) do |f| + = form_for(resource, as: "new_#{resource_name}", url: registration_path(resource_name)) do |f| .devise-errors = devise_error_messages! %div @@ -16,7 +16,7 @@ %div = f.email_field :email, class: "form-control middle", placeholder: "Email", required: true .form-group.append-bottom-20#password-strength - = f.password_field :password, class: "form-control bottom", id: "user_password_sign_up", placeholder: "Password", required: true + = f.password_field :password, class: "form-control bottom", placeholder: "Password", required: true %div - if current_application_settings.recaptcha_enabled = recaptcha_tags diff --git a/spec/features/signup_spec.rb b/spec/features/signup_spec.rb index 01472743b2a..a9b81733897 100644 --- a/spec/features/signup_spec.rb +++ b/spec/features/signup_spec.rb @@ -7,10 +7,10 @@ feature 'Signup', feature: true do visit root_path - fill_in 'user_name', with: user.name - fill_in 'user_username', with: user.username - fill_in 'user_email', with: user.email - fill_in 'user_password_sign_up', with: user.password + fill_in 'new_user_name', with: user.name + fill_in 'new_user_username', with: user.username + fill_in 'new_user_email', with: user.email + fill_in 'new_user_password', with: user.password click_button "Sign up" expect(current_path).to eq user_session_path @@ -25,10 +25,10 @@ feature 'Signup', feature: true do visit root_path - fill_in 'user_name', with: user.name - fill_in 'user_username', with: user.username - fill_in 'user_email', with: existing_user.email - fill_in 'user_password_sign_up', with: user.password + fill_in 'new_user_name', with: user.name + fill_in 'new_user_username', with: user.username + fill_in 'new_user_email', with: existing_user.email + fill_in 'new_user_password', with: user.password click_button "Sign up" expect(current_path).to eq user_registration_path @@ -42,10 +42,10 @@ feature 'Signup', feature: true do visit root_path - fill_in 'user_name', with: user.name - fill_in 'user_username', with: user.username - fill_in 'user_email', with: existing_user.email - fill_in 'user_password_sign_up', with: user.password + fill_in 'new_user_name', with: user.name + fill_in 'new_user_username', with: user.username + fill_in 'new_user_email', with: existing_user.email + fill_in 'new_user_password', with: user.password click_button "Sign up" expect(current_path).to eq user_registration_path diff --git a/spec/features/users_spec.rb b/spec/features/users_spec.rb index c1248162031..cf116040394 100644 --- a/spec/features/users_spec.rb +++ b/spec/features/users_spec.rb @@ -5,10 +5,10 @@ feature 'Users', feature: true do scenario 'GET /users/sign_in creates a new user account' do visit new_user_session_path - fill_in 'user_name', with: 'Name Surname' - fill_in 'user_username', with: 'Great' - fill_in 'user_email', with: 'name@mail.com' - fill_in 'user_password_sign_up', with: 'password1234' + fill_in 'new_user_name', with: 'Name Surname' + fill_in 'new_user_username', with: 'Great' + fill_in 'new_user_email', with: 'name@mail.com' + fill_in 'new_user_password', with: 'password1234' expect { click_button 'Sign up' }.to change { User.count }.by(1) end @@ -31,10 +31,10 @@ feature 'Users', feature: true do scenario 'Should show one error if email is already taken' do visit new_user_session_path - fill_in 'user_name', with: 'Another user name' - fill_in 'user_username', with: 'anotheruser' - fill_in 'user_email', with: user.email - fill_in 'user_password_sign_up', with: '12341234' + fill_in 'new_user_name', with: 'Another user name' + fill_in 'new_user_username', with: 'anotheruser' + fill_in 'new_user_email', with: user.email + fill_in 'new_user_password', with: '12341234' expect { click_button 'Sign up' }.to change { User.count }.by(0) expect(page).to have_text('Email has already been taken') expect(number_of_errors_on_page(page)).to be(1), 'errors on page:\n #{errors_on_page page}' From d3462e711c0b3cc17ef47e1ffffa6f40f98b5e98 Mon Sep 17 00:00:00 2001 From: Douwe Maan Date: Fri, 22 Apr 2016 23:19:55 +0200 Subject: [PATCH 004/196] Fix issue with impersonation --- .../admin/application_controller.rb | 8 +- .../admin/impersonation_controller.rb | 38 -------- .../admin/impersonations_controller.rb | 28 ++++++ app/controllers/admin/users_controller.rb | 16 ++++ app/views/layouts/header/_default.html.haml | 2 +- config/routes.rb | 6 +- .../admin/impersonation_controller_spec.rb | 19 ---- .../admin/impersonations_controller_spec.rb | 95 +++++++++++++++++++ .../admin/users_controller_spec.rb | 49 +++++++++- 9 files changed, 192 insertions(+), 69 deletions(-) delete mode 100644 app/controllers/admin/impersonation_controller.rb create mode 100644 app/controllers/admin/impersonations_controller.rb delete mode 100644 spec/controllers/admin/impersonation_controller_spec.rb create mode 100644 spec/controllers/admin/impersonations_controller_spec.rb diff --git a/app/controllers/admin/application_controller.rb b/app/controllers/admin/application_controller.rb index 9083bfb41cf..cf795d977ce 100644 --- a/app/controllers/admin/application_controller.rb +++ b/app/controllers/admin/application_controller.rb @@ -6,12 +6,6 @@ class Admin::ApplicationController < ApplicationController layout 'admin' def authenticate_admin! - return render_404 unless current_user.is_admin? - end - - def authorize_impersonator! - if session[:impersonator_id] - User.find_by!(username: session[:impersonator_id]).admin? - end + render_404 unless current_user.is_admin? end end diff --git a/app/controllers/admin/impersonation_controller.rb b/app/controllers/admin/impersonation_controller.rb deleted file mode 100644 index bf98af78615..00000000000 --- a/app/controllers/admin/impersonation_controller.rb +++ /dev/null @@ -1,38 +0,0 @@ -class Admin::ImpersonationController < Admin::ApplicationController - skip_before_action :authenticate_admin!, only: :destroy - - before_action :user - before_action :authorize_impersonator! - - def create - if @user.blocked? - flash[:alert] = "You cannot impersonate a blocked user" - - redirect_to admin_user_path(@user) - else - session[:impersonator_id] = current_user.username - session[:impersonator_return_to] = admin_user_path(@user) - - warden.set_user(user, scope: 'user') - - flash[:alert] = "You are impersonating #{user.username}." - - redirect_to root_path - end - end - - def destroy - redirect = session[:impersonator_return_to] - - warden.set_user(user, scope: 'user') - - session[:impersonator_return_to] = nil - session[:impersonator_id] = nil - - redirect_to redirect || root_path - end - - def user - @user ||= User.find_by!(username: params[:id] || session[:impersonator_id]) - end -end diff --git a/app/controllers/admin/impersonations_controller.rb b/app/controllers/admin/impersonations_controller.rb new file mode 100644 index 00000000000..1ca3dd8228d --- /dev/null +++ b/app/controllers/admin/impersonations_controller.rb @@ -0,0 +1,28 @@ +class Admin::ImpersonationsController < Admin::ApplicationController + skip_before_action :authenticate_admin! + before_action :authenticate_impersonator! + + def destroy + redirect_path = admin_user_path(current_user) + + warden.set_user(impersonator, scope: :user) + + session[:impersonator_id] = nil + + redirect_to redirect_path + end + + private + + def user + @user ||= User.find(params[:id]) + end + + def impersonator + @impersonator ||= User.find(session[:impersonator_id]) if session[:impersonator_id] + end + + def authenticate_impersonator! + render_404 unless impersonator && impersonator.is_admin? && !impersonator.blocked? + end +end diff --git a/app/controllers/admin/users_controller.rb b/app/controllers/admin/users_controller.rb index 9abf08d0e19..b8976fa09a9 100644 --- a/app/controllers/admin/users_controller.rb +++ b/app/controllers/admin/users_controller.rb @@ -31,6 +31,22 @@ class Admin::UsersController < Admin::ApplicationController user end + def impersonate + if user.blocked? + flash[:alert] = "You cannot impersonate a blocked user" + + redirect_to admin_user_path(user) + else + session[:impersonator_id] = current_user.id + + warden.set_user(user, scope: :user) + + flash[:alert] = "You are now impersonating #{user.username}" + + redirect_to root_path + end + end + def block if user.block redirect_back_or_admin_user(notice: "Successfully blocked") diff --git a/app/views/layouts/header/_default.html.haml b/app/views/layouts/header/_default.html.haml index 3beb8ff7c0d..cde9e1b918b 100644 --- a/app/views/layouts/header/_default.html.haml +++ b/app/views/layouts/header/_default.html.haml @@ -15,7 +15,7 @@ - if current_user - if session[:impersonator_id] %li.impersonation - = link_to stop_impersonation_admin_users_path, method: :delete, title: 'Stop Impersonation', data: { toggle: 'tooltip', placement: 'bottom', container: 'body' } do + = link_to admin_impersonation_path, method: :delete, title: 'Stop Impersonation', data: { toggle: 'tooltip', placement: 'bottom', container: 'body' } do = icon('user-secret fw') - if current_user.is_admin? %li diff --git a/config/routes.rb b/config/routes.rb index 2f820aafed1..9c6fb682fc9 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -212,8 +212,6 @@ Rails.application.routes.draw do resources :keys, only: [:show, :destroy] resources :identities, except: [:show] - delete 'stop_impersonation' => 'impersonation#destroy', on: :collection - member do get :projects get :keys @@ -223,12 +221,14 @@ Rails.application.routes.draw do put :unblock put :unlock put :confirm - post 'impersonate' => 'impersonation#create' + post :impersonate patch :disable_two_factor delete 'remove/:email_id', action: 'remove_email', as: 'remove_email' end end + resource :impersonation, only: :destroy + resources :abuse_reports, only: [:index, :destroy] resources :spam_logs, only: [:index, :destroy] diff --git a/spec/controllers/admin/impersonation_controller_spec.rb b/spec/controllers/admin/impersonation_controller_spec.rb deleted file mode 100644 index d7a7ba1c5b6..00000000000 --- a/spec/controllers/admin/impersonation_controller_spec.rb +++ /dev/null @@ -1,19 +0,0 @@ -require 'spec_helper' - -describe Admin::ImpersonationController do - let(:admin) { create(:admin) } - - before do - sign_in(admin) - end - - describe 'CREATE #impersonation when blocked' do - let(:blocked_user) { create(:user, state: :blocked) } - - it 'does not allow impersonation' do - post :create, id: blocked_user.username - - expect(flash[:alert]).to eq 'You cannot impersonate a blocked user' - end - end -end diff --git a/spec/controllers/admin/impersonations_controller_spec.rb b/spec/controllers/admin/impersonations_controller_spec.rb new file mode 100644 index 00000000000..8b48c6b6ea3 --- /dev/null +++ b/spec/controllers/admin/impersonations_controller_spec.rb @@ -0,0 +1,95 @@ +require 'spec_helper' + +describe Admin::ImpersonationsController do + let(:impersonator) { create(:admin) } + let(:user) { create(:user) } + + describe "DELETE destroy" do + context "when not signed in" do + it "redirects to the sign in page" do + delete :destroy + + expect(response).to redirect_to(new_user_session_path) + end + end + + context "when signed in" do + before do + sign_in(user) + end + + context "when not impersonating" do + it "responds with status 404" do + delete :destroy + + expect(response.status).to eq(404) + end + + it "doesn't sign us in" do + delete :destroy + + expect(warden.user).to eq(user) + end + end + + context "when impersonating" do + before do + session[:impersonator_id] = impersonator.id + end + + context "when the impersonator is not impersonator (anymore)" do + before do + impersonator.admin = false + impersonator.save + end + + it "responds with status 404" do + delete :destroy + + expect(response.status).to eq(404) + end + + it "doesn't sign us in as the impersonator" do + delete :destroy + + expect(warden.user).to eq(user) + end + end + + context "when the impersonator is admin" do + context "when the impersonator is blocked" do + before do + impersonator.block! + end + + it "responds with status 404" do + delete :destroy + + expect(response.status).to eq(404) + end + + it "doesn't sign us in as the impersonator" do + delete :destroy + + expect(warden.user).to eq(user) + end + end + + context "when the impersonator is not blocked" do + it "redirects to the impersonated user's page" do + delete :destroy + + expect(response).to redirect_to(admin_user_path(user)) + end + + it "signs us in as the impersonator" do + delete :destroy + + expect(warden.user).to eq(impersonator) + end + end + end + end + end + end +end diff --git a/spec/controllers/admin/users_controller_spec.rb b/spec/controllers/admin/users_controller_spec.rb index 9ef8ba1b097..ce2a62ae1fd 100644 --- a/spec/controllers/admin/users_controller_spec.rb +++ b/spec/controllers/admin/users_controller_spec.rb @@ -2,9 +2,10 @@ require 'spec_helper' describe Admin::UsersController do let(:user) { create(:user) } + let(:admin) { create(:admin) } before do - sign_in(create(:admin)) + sign_in(admin) end describe 'DELETE #user with projects' do @@ -112,4 +113,50 @@ describe Admin::UsersController do patch :disable_two_factor, id: user.to_param end end + + describe "POST impersonate" do + context "when the user is blocked" do + before do + user.block! + end + + it "shows a notice" do + post :impersonate, id: user.username + + expect(flash[:alert]).to eq("You cannot impersonate a blocked user") + end + + it "doesn't sign us in as the user" do + post :impersonate, id: user.username + + expect(warden.user).to eq(admin) + end + end + + context "when the user is not blocked" do + it "stores the impersonator in the session" do + post :impersonate, id: user.username + + expect(session[:impersonator_id]).to eq(admin.id) + end + + it "signs us in as the user" do + post :impersonate, id: user.username + + expect(warden.user).to eq(user) + end + + it "redirects to root" do + post :impersonate, id: user.username + + expect(response).to redirect_to(root_path) + end + + it "shows a notice" do + post :impersonate, id: user.username + + expect(flash[:alert]).to eq("You are now impersonating #{user.username}") + end + end + end end From b992e2520cc93b3161f6e7c46dde66f10fe13a12 Mon Sep 17 00:00:00 2001 From: Douwe Maan Date: Fri, 22 Apr 2016 21:46:47 +0000 Subject: [PATCH 005/196] Fix typo --- spec/controllers/admin/impersonations_controller_spec.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spec/controllers/admin/impersonations_controller_spec.rb b/spec/controllers/admin/impersonations_controller_spec.rb index 8b48c6b6ea3..eb82476b179 100644 --- a/spec/controllers/admin/impersonations_controller_spec.rb +++ b/spec/controllers/admin/impersonations_controller_spec.rb @@ -37,7 +37,7 @@ describe Admin::ImpersonationsController do session[:impersonator_id] = impersonator.id end - context "when the impersonator is not impersonator (anymore)" do + context "when the impersonator is not admin (anymore)" do before do impersonator.admin = false impersonator.save From 0ab98a8a407cb9764c9c28554c8f077906a9eb9b Mon Sep 17 00:00:00 2001 From: Douwe Maan Date: Fri, 22 Apr 2016 21:55:49 +0000 Subject: [PATCH 006/196] Remove unused method --- app/controllers/admin/impersonations_controller.rb | 4 ---- 1 file changed, 4 deletions(-) diff --git a/app/controllers/admin/impersonations_controller.rb b/app/controllers/admin/impersonations_controller.rb index 1ca3dd8228d..2d64923c478 100644 --- a/app/controllers/admin/impersonations_controller.rb +++ b/app/controllers/admin/impersonations_controller.rb @@ -14,10 +14,6 @@ class Admin::ImpersonationsController < Admin::ApplicationController private - def user - @user ||= User.find(params[:id]) - end - def impersonator @impersonator ||= User.find(session[:impersonator_id]) if session[:impersonator_id] end From c6c985bc690c88f2819787824a50b22cc86cacf4 Mon Sep 17 00:00:00 2001 From: Douwe Maan Date: Fri, 22 Apr 2016 21:58:09 +0000 Subject: [PATCH 007/196] Store original user in variable --- app/controllers/admin/impersonations_controller.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/controllers/admin/impersonations_controller.rb b/app/controllers/admin/impersonations_controller.rb index 2d64923c478..2db824c87ef 100644 --- a/app/controllers/admin/impersonations_controller.rb +++ b/app/controllers/admin/impersonations_controller.rb @@ -3,13 +3,13 @@ class Admin::ImpersonationsController < Admin::ApplicationController before_action :authenticate_impersonator! def destroy - redirect_path = admin_user_path(current_user) + original_user = current_user warden.set_user(impersonator, scope: :user) session[:impersonator_id] = nil - redirect_to redirect_path + redirect_to admin_user_path(original_user) end private From 03ae2cdbff49d4f72d32529963a2173c7308da40 Mon Sep 17 00:00:00 2001 From: Stan Hu Date: Sun, 24 Apr 2016 20:07:59 -0700 Subject: [PATCH 008/196] Filter confidential issues from milestones API if user does not have access Closes https://gitlab.com/gitlab-org/gitlab-ce/issues/15579 --- CHANGELOG | 2 ++ lib/api/milestones.rb | 10 ++++++- spec/requests/api/milestones_spec.rb | 40 +++++++++++++++++++++++++++- 3 files changed, 50 insertions(+), 2 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index 2d1c561fb82..4ce16b44493 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -8,6 +8,8 @@ v 8.7.1 (unreleased) - Fix license detection to detect all license files, not only known licenses. !3878 - Use the `can?` helper instead of `current_user.can?`. !3882 - Prevent users from deleting Webhooks via API they do not own + - Use the `can?` helper instead of `current_user.can?` + - Filter confidential issues from milestones API if user does not have access v 8.7.0 - Gitlab::GitAccess and Gitlab::GitAccessWiki are now instrumented diff --git a/lib/api/milestones.rb b/lib/api/milestones.rb index 84b4d4cdd6d..132043cf3f7 100644 --- a/lib/api/milestones.rb +++ b/lib/api/milestones.rb @@ -105,7 +105,15 @@ module API authorize! :read_milestone, user_project @milestone = user_project.milestones.find(params[:milestone_id]) - present paginate(@milestone.issues), with: Entities::Issue, current_user: current_user + + finder_params = { + project_id: user_project.id, + milestone_title: @milestone.title, + state: 'all' + } + + issues = IssuesFinder.new(current_user, finder_params).execute + present paginate(issues), with: Entities::Issue, current_user: current_user end end diff --git a/spec/requests/api/milestones_spec.rb b/spec/requests/api/milestones_spec.rb index 344f0fe0b7f..cb9c3dde5ee 100644 --- a/spec/requests/api/milestones_spec.rb +++ b/spec/requests/api/milestones_spec.rb @@ -127,7 +127,7 @@ describe API::API, api: true do describe 'GET /projects/:id/milestones/:milestone_id/issues' do before do - milestone.issues << create(:issue) + milestone.issues << create(:issue, project: project) end it 'should return project issues for a particular milestone' do get api("/projects/#{project.id}/milestones/#{milestone.id}/issues", user) @@ -141,4 +141,42 @@ describe API::API, api: true do expect(response.status).to eq(401) end end + + describe 'confidential issues' do + it 'should return confidential issues to team members' do + public_project = create(:project, :public) + user = create(:user) + milestone = create(:milestone, project: public_project) + issue = create(:issue, project: public_project) + confidential_issue = create(:issue, confidential: true, project: public_project) + public_project.team << [user, :developer] + milestone.issues << issue + milestone.issues << confidential_issue + + get api("/projects/#{public_project.id}/milestones/#{milestone.id}/issues", user) + + expect(response.status).to eq(200) + expect(json_response).to be_an Array + expect(json_response.size).to eq(2) + expect(json_response.map { |issue| issue['id'] }).to include(issue.id, confidential_issue.id) + end + + it 'should not return confidential issues to regular users' do + public_project = create(:project, :public) + normal_user = create(:user) + milestone = create(:milestone, project: public_project) + issue = create(:issue, project: public_project) + confidential_issue = create(:issue, confidential: true, project: public_project) + public_project.team << [user, :developer] + milestone.issues << issue + milestone.issues << confidential_issue + + get api("/projects/#{public_project.id}/milestones/#{milestone.id}/issues", normal_user) + + expect(response.status).to eq(200) + expect(json_response).to be_an Array + expect(json_response.size).to eq(1) + expect(json_response.map { |issue| issue['id'] }).to include(issue.id) + end + end end From 756185533cfef42f85f5ad5cf687768e9ed82bb5 Mon Sep 17 00:00:00 2001 From: Phil Hughes Date: Mon, 25 Apr 2016 16:26:41 +0100 Subject: [PATCH 009/196] Project deploy keys updated UI Closes #13992 --- app/assets/stylesheets/pages/projects.scss | 27 ++++++- .../projects/deploy_keys_controller.rb | 31 ++++---- .../deploy_keys/_deploy_key.html.haml | 45 +++++------- .../projects/deploy_keys/_form.html.haml | 31 ++++---- .../projects/deploy_keys/index.html.haml | 73 +++++++++---------- 5 files changed, 110 insertions(+), 97 deletions(-) diff --git a/app/assets/stylesheets/pages/projects.scss b/app/assets/stylesheets/pages/projects.scss index fcca9d4faf5..9561ac39050 100644 --- a/app/assets/stylesheets/pages/projects.scss +++ b/app/assets/stylesheets/pages/projects.scss @@ -202,8 +202,31 @@ min-width: 200px; } -.deploy-project-label { - margin: 1px; +.deploy-key-content { + @media (min-width: $screen-sm-min) { + float: left; + + &:last-child { + float: right; + } + } +} + +.deploy-key-projects { + @media (min-width: $screen-sm-min) { + line-height: 42px; + } +} + +a.deploy-project-label { + padding: 5px; + margin-right: 5px; + color: $gl-gray; + background-color: $row-hover; + + &:hover { + color: $gl-link-color; + } } .vs-public { diff --git a/app/controllers/projects/deploy_keys_controller.rb b/app/controllers/projects/deploy_keys_controller.rb index 7d09288bc80..83d5ced9be8 100644 --- a/app/controllers/projects/deploy_keys_controller.rb +++ b/app/controllers/projects/deploy_keys_controller.rb @@ -7,31 +7,24 @@ class Projects::DeployKeysController < Projects::ApplicationController layout "project_settings" def index - @enabled_keys = @project.deploy_keys - - @available_keys = accessible_keys - @enabled_keys - @available_project_keys = current_user.project_deploy_keys - @enabled_keys - @available_public_keys = DeployKey.are_public - @enabled_keys - - # Public keys that are already used by another accessible project are already - # in @available_project_keys. - @available_public_keys -= @available_project_keys + @key = DeployKey.new + set_index_vars end def new - @key = @project.deploy_keys.new - - respond_with(@key) + redirect_to namespace_project_deploy_keys_path(@project.namespace, + @project) end def create @key = DeployKey.new(deploy_key_params) + set_index_vars if @key.valid? && @project.deploy_keys << @key redirect_to namespace_project_deploy_keys_path(@project.namespace, @project) else - render "new" + render "index" end end @@ -51,6 +44,18 @@ class Projects::DeployKeysController < Projects::ApplicationController protected + def set_index_vars + @enabled_keys ||= @project.deploy_keys + + @available_keys ||= accessible_keys - @enabled_keys + @available_project_keys ||= current_user.project_deploy_keys - @enabled_keys + @available_public_keys ||= DeployKey.are_public - @enabled_keys + + # Public keys that are already used by another accessible project are already + # in @available_project_keys. + @available_public_keys -= @available_project_keys + end + def accessible_keys @accessible_keys ||= current_user.accessible_deploy_keys end diff --git a/app/views/projects/deploy_keys/_deploy_key.html.haml b/app/views/projects/deploy_keys/_deploy_key.html.haml index 8d66bae8cdf..450aaeb367c 100644 --- a/app/views/projects/deploy_keys/_deploy_key.html.haml +++ b/app/views/projects/deploy_keys/_deploy_key.html.haml @@ -1,32 +1,27 @@ %li - .pull-right + .pull-left.append-right-10.hidden-xs + = icon "key", class: "key-icon" + .deploy-key-content.key-list-item-info + %strong.title + = deploy_key.title + .description + = deploy_key.fingerprint + .deploy-key-content.prepend-left-default.deploy-key-projects + - deploy_key.projects.each do |project| + - if can?(current_user, :read_project, project) + = link_to namespace_project_path(project.namespace, project), class: "label deploy-project-label" do + = project.name_with_namespace + .deploy-key-content + %span.key-created-at + created #{time_ago_with_tooltip(deploy_key.created_at)} + .visible-xs-block.visible-sm-block - if @available_keys.include?(deploy_key) - = link_to enable_namespace_project_deploy_key_path(@project.namespace, @project, deploy_key), class: 'btn btn-sm', method: :put do - = icon('plus') + = link_to enable_namespace_project_deploy_key_path(@project.namespace, @project, deploy_key), class: "btn btn-sm prepend-left-10", method: :put do Enable - else - if deploy_key.destroyed_when_orphaned? && deploy_key.almost_orphaned? - = link_to 'Remove', disable_namespace_project_deploy_key_path(@project.namespace, @project, deploy_key), data: { confirm: 'You are going to remove deploy key. Are you sure?'}, method: :put, class: "btn btn-remove delete-key btn-sm pull-right" + = link_to disable_namespace_project_deploy_key_path(@project.namespace, @project, deploy_key), data: { confirm: "You are going to remove deploy key. Are you sure?" }, method: :put, class: "btn btn-warning btn-sm prepend-left-10" do + Remove - else - = link_to disable_namespace_project_deploy_key_path(@project.namespace, @project, deploy_key), class: 'btn btn-sm', method: :put do - = icon('power-off') + = link_to disable_namespace_project_deploy_key_path(@project.namespace, @project, deploy_key), class: "btn btn-warning btn-sm prepend-left-10", method: :put do Disable - - = icon('key') - %strong= deploy_key.title - %br - %code.key-fingerprint= deploy_key.fingerprint - - %p.light.prepend-top-10 - - if deploy_key.public? - %span.label.label-info.deploy-project-label - Public deploy key - - - deploy_key.projects.each do |project| - - if can?(current_user, :read_project, project) - %span.label.label-gray.deploy-project-label - = link_to namespace_project_path(project.namespace, project) do - = project.name_with_namespace - - %small.pull-right - Created #{time_ago_with_tooltip(deploy_key.created_at)} diff --git a/app/views/projects/deploy_keys/_form.html.haml b/app/views/projects/deploy_keys/_form.html.haml index f6565f85836..894c36a96df 100644 --- a/app/views/projects/deploy_keys/_form.html.haml +++ b/app/views/projects/deploy_keys/_form.html.haml @@ -1,18 +1,13 @@ -%div - = form_for [@project.namespace.becomes(Namespace), @project, @key], url: namespace_project_deploy_keys_path, html: { class: 'deploy-key-form form-horizontal js-requires-input' } do |f| - = form_errors(@key) - - .form-group - = f.label :title, class: "control-label" - .col-sm-10= f.text_field :title, class: 'form-control', autofocus: true, required: true - .form-group - = f.label :key, class: "control-label" - .col-sm-10 - %p.light - Paste a machine public key here. Read more about how to generate it - = link_to "here", help_page_path("ssh", "README") - = f.text_area :key, class: "form-control thin_area", rows: 5, required: true - - .form-actions - = f.submit 'Create Deploy Key', class: "btn-create btn" - = link_to "Cancel", namespace_project_deploy_keys_path(@project.namespace, @project), class: "btn btn-cancel" += form_for [@project.namespace.becomes(Namespace), @project, @key], url: namespace_project_deploy_keys_path, html: { class: "js-requires-input" } do |f| + = form_errors(@key) + .form-group + = f.label :title, class: "label-light" + = f.text_field :title, class: 'form-control', autofocus: true, required: true + .form-group + = f.label :key, class: "label-light" + = f.text_area :key, class: "form-control", rows: 5, required: true + .form-group + %p.light.append-bottom-0 + Paste a machine public key here. Read more about how to generate it + = link_to "here", help_page_path("ssh", "README") + = f.submit "Add key", class: "btn-create btn" diff --git a/app/views/projects/deploy_keys/index.html.haml b/app/views/projects/deploy_keys/index.html.haml index 8e24c778b7c..d82ef31ab4b 100644 --- a/app/views/projects/deploy_keys/index.html.haml +++ b/app/views/projects/deploy_keys/index.html.haml @@ -1,43 +1,38 @@ - page_title "Deploy Keys" -%h3.page-title - Deploy keys allow read-only access to the repository - - = link_to new_namespace_project_deploy_key_path(@project.namespace, @project), class: "btn btn-new pull-right", title: "New Deploy Key" do - %i.fa.fa-plus - New Deploy Key - -%p.light - Deploy keys can be used for CI, staging or production servers. - You can create a deploy key or add an existing one - -%hr.clearfix - -.row - .col-md-6.enabled-keys - %h5 - %strong.cgreen Enabled deploy keys - for this project - %ul.bordered-list - = render @enabled_keys - - if @enabled_keys.blank? - .light-well - .nothing-here-block Create a #{link_to 'new deploy key', new_namespace_project_deploy_key_path(@project.namespace, @project)} or add an existing one - .col-md-6.available-keys - - # If there are available public deploy keys but no available project deploy keys, only public deploy keys are shown. - - if @available_project_keys.any? || @available_public_keys.blank? - %h5 - %strong Deploy keys - from projects you have access to - %ul.bordered-list +.row.prepend-top-default + .col-lg-3.profile-settings-sidebar + %h4.prepend-top-0 + = page_title + %p + Deploy keys allow read-only access to your repository. Deploy keys can be used for CI, staging or production servers. You can create a deploy key or add an existing one. + .col-lg-9 + %h5.prepend-top-0 + Create a new deploy key for this project + = render "form" + .col-lg-9.col-lg-offset-3 + %hr + .col-lg-9.col-lg-offset-3.append-bottom-default + %h5.prepend-top-0 + Enabled deploy keys for this project (#{@enabled_keys.size}) + - if @enabled_keys.any? + %ul.well-list + = render @enabled_keys + - else + .profile-settings-message.text-center + No deploy keys found. Create one with the form above or add existing one below. + .col-lg-9.col-lg-offset-3.append-bottom-default + %h5.prepend-top-0 + Deploy keys from projects you have access to (#{@available_project_keys.size}) + - if @available_project_keys.any? + %ul.well-list = render @available_project_keys - - if @available_project_keys.blank? - .light-well - .nothing-here-block Deploy keys from projects you have access to will be displayed here - - - if @available_public_keys.any? - %h5 - %strong Public deploy keys - available to any project - %ul.bordered-list + - else + .profile-settings-message.text-center + No deploy keys from your projects could be found. Create one with the form above or add existing one below. + - if @available_public_keys.any? + .col-lg-9.col-lg-offset-3.append-bottom-default + %h5.prepend-top-0 + Public deploy keys available to any project (#{@available_public_keys.size}) + %ul.well-list = render @available_public_keys From 97bd349146bfb3acef77ec413cd0def552d00472 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A9my=20Coutable?= Date: Mon, 25 Apr 2016 11:49:52 +0200 Subject: [PATCH 010/196] Improve Milestones API specs MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Rémy Coutable --- CHANGELOG | 2 - spec/requests/api/milestones_spec.rb | 55 ++++++++++++---------------- 2 files changed, 23 insertions(+), 34 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index 4ce16b44493..2d1c561fb82 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -8,8 +8,6 @@ v 8.7.1 (unreleased) - Fix license detection to detect all license files, not only known licenses. !3878 - Use the `can?` helper instead of `current_user.can?`. !3882 - Prevent users from deleting Webhooks via API they do not own - - Use the `can?` helper instead of `current_user.can?` - - Filter confidential issues from milestones API if user does not have access v 8.7.0 - Gitlab::GitAccess and Gitlab::GitAccessWiki are now instrumented diff --git a/spec/requests/api/milestones_spec.rb b/spec/requests/api/milestones_spec.rb index cb9c3dde5ee..241995041bb 100644 --- a/spec/requests/api/milestones_spec.rb +++ b/spec/requests/api/milestones_spec.rb @@ -140,43 +140,34 @@ describe API::API, api: true do get api("/projects/#{project.id}/milestones/#{milestone.id}/issues") expect(response.status).to eq(401) end - end - describe 'confidential issues' do - it 'should return confidential issues to team members' do - public_project = create(:project, :public) - user = create(:user) - milestone = create(:milestone, project: public_project) - issue = create(:issue, project: public_project) - confidential_issue = create(:issue, confidential: true, project: public_project) - public_project.team << [user, :developer] - milestone.issues << issue - milestone.issues << confidential_issue + describe 'confidential issues' do + let(:public_project) { create(:project, :public) } + let(:milestone) { create(:milestone, project: public_project) } + let(:issue) { create(:issue, project: public_project) } + let(:confidential_issue) { create(:issue, confidential: true, project: public_project) } + before do + public_project.team << [user, :developer] + milestone.issues << issue << confidential_issue + end - get api("/projects/#{public_project.id}/milestones/#{milestone.id}/issues", user) + it 'returns confidential issues to team members' do + get api("/projects/#{public_project.id}/milestones/#{milestone.id}/issues", user) - expect(response.status).to eq(200) - expect(json_response).to be_an Array - expect(json_response.size).to eq(2) - expect(json_response.map { |issue| issue['id'] }).to include(issue.id, confidential_issue.id) - end + expect(response.status).to eq(200) + expect(json_response).to be_an Array + expect(json_response.size).to eq(2) + expect(json_response.map { |issue| issue['id'] }).to include(issue.id, confidential_issue.id) + end - it 'should not return confidential issues to regular users' do - public_project = create(:project, :public) - normal_user = create(:user) - milestone = create(:milestone, project: public_project) - issue = create(:issue, project: public_project) - confidential_issue = create(:issue, confidential: true, project: public_project) - public_project.team << [user, :developer] - milestone.issues << issue - milestone.issues << confidential_issue + it 'does not return confidential issues to regular users' do + get api("/projects/#{public_project.id}/milestones/#{milestone.id}/issues", create(:user)) - get api("/projects/#{public_project.id}/milestones/#{milestone.id}/issues", normal_user) - - expect(response.status).to eq(200) - expect(json_response).to be_an Array - expect(json_response.size).to eq(1) - expect(json_response.map { |issue| issue['id'] }).to include(issue.id) + expect(response.status).to eq(200) + expect(json_response).to be_an Array + expect(json_response.size).to eq(1) + expect(json_response.map { |issue| issue['id'] }).to include(issue.id) + end end end end From 54493b9aaef6b2209889a414cf3a26da131eb0ca Mon Sep 17 00:00:00 2001 From: Phil Hughes Date: Mon, 25 Apr 2016 17:45:05 +0100 Subject: [PATCH 011/196] Updated tests for deploy keys --- app/views/projects/deploy_keys/index.html.haml | 10 ++++------ features/project/deploy_keys.feature | 1 - features/steps/project/deploy_keys.rb | 14 +++++++------- 3 files changed, 11 insertions(+), 14 deletions(-) diff --git a/app/views/projects/deploy_keys/index.html.haml b/app/views/projects/deploy_keys/index.html.haml index d82ef31ab4b..e230834e8ba 100644 --- a/app/views/projects/deploy_keys/index.html.haml +++ b/app/views/projects/deploy_keys/index.html.haml @@ -12,7 +12,7 @@ = render "form" .col-lg-9.col-lg-offset-3 %hr - .col-lg-9.col-lg-offset-3.append-bottom-default + .col-lg-9.col-lg-offset-3.append-bottom-default.deploy-keys %h5.prepend-top-0 Enabled deploy keys for this project (#{@enabled_keys.size}) - if @enabled_keys.any? @@ -21,8 +21,7 @@ - else .profile-settings-message.text-center No deploy keys found. Create one with the form above or add existing one below. - .col-lg-9.col-lg-offset-3.append-bottom-default - %h5.prepend-top-0 + %h5.prepend-top-default Deploy keys from projects you have access to (#{@available_project_keys.size}) - if @available_project_keys.any? %ul.well-list @@ -30,9 +29,8 @@ - else .profile-settings-message.text-center No deploy keys from your projects could be found. Create one with the form above or add existing one below. - - if @available_public_keys.any? - .col-lg-9.col-lg-offset-3.append-bottom-default - %h5.prepend-top-0 + - if @available_public_keys.any? + %h5.prepend-top-default Public deploy keys available to any project (#{@available_public_keys.size}) %ul.well-list = render @available_public_keys diff --git a/features/project/deploy_keys.feature b/features/project/deploy_keys.feature index 47cf774094f..960b4100ee5 100644 --- a/features/project/deploy_keys.feature +++ b/features/project/deploy_keys.feature @@ -21,7 +21,6 @@ Feature: Project Deploy Keys Scenario: I add new deploy key Given I visit project deploy keys page - When I click 'New Deploy Key' And I submit new deploy key Then I should be on deploy keys page And I should see newly created deploy key diff --git a/features/steps/project/deploy_keys.rb b/features/steps/project/deploy_keys.rb index a4d6c9a1b8e..83b9ef48392 100644 --- a/features/steps/project/deploy_keys.rb +++ b/features/steps/project/deploy_keys.rb @@ -8,19 +8,19 @@ class Spinach::Features::ProjectDeployKeys < Spinach::FeatureSteps end step 'I should see project deploy key' do - page.within '.enabled-keys' do + page.within '.deploy-keys' do expect(page).to have_content deploy_key.title end end step 'I should see other project deploy key' do - page.within '.available-keys' do + page.within '.deploy-keys' do expect(page).to have_content other_deploy_key.title end end step 'I should see public deploy key' do - page.within '.available-keys' do + page.within '.deploy-keys' do expect(page).to have_content public_deploy_key.title end end @@ -32,7 +32,7 @@ class Spinach::Features::ProjectDeployKeys < Spinach::FeatureSteps step 'I submit new deploy key' do fill_in "deploy_key_title", with: "laptop" fill_in "deploy_key_key", with: "ssh-rsa AAAAB3NzaC1yc2EAAAABIwAAAQEAzrEJUIR6Y03TCE9rIJ+GqTBvgb8t1jI9h5UBzCLuK4VawOmkLornPqLDrGbm6tcwM/wBrrLvVOqi2HwmkKEIecVO0a64A4rIYScVsXIniHRS6w5twyn1MD3sIbN+socBDcaldECQa2u1dI3tnNVcs8wi77fiRe7RSxePsJceGoheRQgC8AZ510UdIlO+9rjIHUdVN7LLyz512auAfYsgx1OfablkQ/XJcdEwDNgi9imI6nAXhmoKUm1IPLT2yKajTIC64AjLOnE0YyCh6+7RFMpiMyu1qiOCpdjYwTgBRiciNRZCH8xIedyCoAmiUgkUT40XYHwLuwiPJICpkAzp7Q== user@laptop" - click_button "Create" + click_button "Add key" end step 'I should be on deploy keys page' do @@ -40,7 +40,7 @@ class Spinach::Features::ProjectDeployKeys < Spinach::FeatureSteps end step 'I should see newly created deploy key' do - page.within '.enabled-keys' do + page.within '.deploy-keys' do expect(page).to have_content(deploy_key.title) end end @@ -56,7 +56,7 @@ class Spinach::Features::ProjectDeployKeys < Spinach::FeatureSteps end step 'I should only see the same deploy key once' do - page.within '.available-keys' do + page.within '.deploy-keys' do expect(page).to have_selector('ul li', count: 1) end end @@ -66,7 +66,7 @@ class Spinach::Features::ProjectDeployKeys < Spinach::FeatureSteps end step 'I click attach deploy key' do - page.within '.available-keys' do + page.within '.deploy-keys' do click_link 'Enable' end end From d5267dfd0dac8e4cab4919bf8aca611de3a5497b Mon Sep 17 00:00:00 2001 From: Stan Hu Date: Sun, 24 Apr 2016 21:45:26 -0700 Subject: [PATCH 012/196] Prevent private snippets in public/internal projects from being leaked via API Closes https://gitlab.com/gitlab-org/gitlab-ce/issues/15580 --- app/finders/snippets_finder.rb | 2 +- lib/api/project_snippets.rb | 15 ++-- spec/requests/api/project_snippets_spec.rb | 87 ++++++++++++++++++++++ spec/requests/api/projects_spec.rb | 2 +- 4 files changed, 99 insertions(+), 7 deletions(-) diff --git a/app/finders/snippets_finder.rb b/app/finders/snippets_finder.rb index a41172816b8..01cbf91c658 100644 --- a/app/finders/snippets_finder.rb +++ b/app/finders/snippets_finder.rb @@ -51,7 +51,7 @@ class SnippetsFinder snippets = project.snippets.fresh if current_user - if project.team.member?(current_user.id) + if project.team.member?(current_user.id) || current_user.admin? snippets else snippets.public_and_internal diff --git a/lib/api/project_snippets.rb b/lib/api/project_snippets.rb index 22ce3c6a066..ce1bf0d26d2 100644 --- a/lib/api/project_snippets.rb +++ b/lib/api/project_snippets.rb @@ -11,6 +11,11 @@ module API end not_found! end + + def snippets_for_current_user + finder_params = { filter: :by_project, project: user_project } + SnippetsFinder.new.execute(current_user, finder_params) + end end # Get a project snippets @@ -20,7 +25,7 @@ module API # Example Request: # GET /projects/:id/snippets get ":id/snippets" do - present paginate(user_project.snippets), with: Entities::ProjectSnippet + present paginate(snippets_for_current_user), with: Entities::ProjectSnippet end # Get a project snippet @@ -31,7 +36,7 @@ module API # Example Request: # GET /projects/:id/snippets/:snippet_id get ":id/snippets/:snippet_id" do - @snippet = user_project.snippets.find(params[:snippet_id]) + @snippet = snippets_for_current_user.find(params[:snippet_id]) present @snippet, with: Entities::ProjectSnippet end @@ -73,7 +78,7 @@ module API # Example Request: # PUT /projects/:id/snippets/:snippet_id put ":id/snippets/:snippet_id" do - @snippet = user_project.snippets.find(params[:snippet_id]) + @snippet = snippets_for_current_user.find(params[:snippet_id]) authorize! :update_project_snippet, @snippet attrs = attributes_for_keys [:title, :file_name, :visibility_level] @@ -97,7 +102,7 @@ module API # DELETE /projects/:id/snippets/:snippet_id delete ":id/snippets/:snippet_id" do begin - @snippet = user_project.snippets.find(params[:snippet_id]) + @snippet = snippets_for_current_user.find(params[:snippet_id]) authorize! :update_project_snippet, @snippet @snippet.destroy rescue @@ -113,7 +118,7 @@ module API # Example Request: # GET /projects/:id/snippets/:snippet_id/raw get ":id/snippets/:snippet_id/raw" do - @snippet = user_project.snippets.find(params[:snippet_id]) + @snippet = snippets_for_current_user.find(params[:snippet_id]) env['api.format'] = :txt content_type 'text/plain' diff --git a/spec/requests/api/project_snippets_spec.rb b/spec/requests/api/project_snippets_spec.rb index 3722ddf5a33..9706d060cfa 100644 --- a/spec/requests/api/project_snippets_spec.rb +++ b/spec/requests/api/project_snippets_spec.rb @@ -15,4 +15,91 @@ describe API::API, api: true do expect(json_response['expires_at']).to be_nil end end + + describe 'GET /projects/:project_id/snippets/' do + it 'all snippets available to team member' do + project = create(:project, :public) + user = create(:user) + project.team << [user, :developer] + public_snippet = create(:project_snippet, :public, project: project) + internal_snippet = create(:project_snippet, :internal, project: project) + private_snippet = create(:project_snippet, :private, project: project) + + get api("/projects/#{project.id}/snippets/", user) + + expect(response.status).to eq(200) + expect(json_response.size).to eq(3) + expect(json_response.map{ |snippet| snippet['id']} ).to include(public_snippet.id, internal_snippet.id, private_snippet.id) + end + + it 'hides private snippets from regular user' do + project = create(:project, :public) + user = create(:user) + create(:project_snippet, :private, project: project) + + get api("/projects/#{project.id}/snippets/", user) + expect(response.status).to eq(200) + expect(json_response.size).to eq(0) + end + end + + describe 'POST /projects/:project_id/snippets/' do + it 'creates a new snippet' do + admin = create(:admin) + project = create(:project) + params = { + title: 'Test Title', + file_name: 'test.rb', + code: 'puts "hello world"', + visibility_level: Gitlab::VisibilityLevel::PUBLIC + } + + post api("/projects/#{project.id}/snippets/", admin), params + + expect(response.status).to eq(201) + snippet = ProjectSnippet.find(json_response['id']) + expect(snippet.content).to eq(params[:code]) + expect(snippet.title).to eq(params[:title]) + expect(snippet.file_name).to eq(params[:file_name]) + expect(snippet.visibility_level).to eq(params[:visibility_level]) + end + end + + describe 'PUT /projects/:project_id/snippets/:id/' do + it 'updates snippet' do + admin = create(:admin) + snippet = create(:project_snippet, author: admin) + new_content = 'New content' + + put api("/projects/#{snippet.project.id}/snippets/#{snippet.id}/", admin), code: new_content + + expect(response.status).to eq(200) + snippet.reload + expect(snippet.content).to eq(new_content) + end + end + + describe 'DELETE /projects/:project_id/snippets/:id/' do + it 'deletes snippet' do + admin = create(:admin) + snippet = create(:project_snippet, author: admin) + + delete api("/projects/#{snippet.project.id}/snippets/#{snippet.id}/", admin) + + expect(response.status).to eq(200) + end + end + + describe 'GET /projects/:project_id/snippets/:id/raw' do + it 'returns raw text' do + admin = create(:admin) + snippet = create(:project_snippet, author: admin) + + get api("/projects/#{snippet.project.id}/snippets/#{snippet.id}/raw", admin) + + expect(response.status).to eq(200) + expect(response.content_type).to eq 'text/plain' + expect(response.body).to eq(snippet.content) + end + end end diff --git a/spec/requests/api/projects_spec.rb b/spec/requests/api/projects_spec.rb index fccd08bd6da..66193eac051 100644 --- a/spec/requests/api/projects_spec.rb +++ b/spec/requests/api/projects_spec.rb @@ -11,7 +11,7 @@ describe API::API, api: true do let(:project) { create(:project, creator_id: user.id, namespace: user.namespace) } let(:project2) { create(:project, path: 'project2', creator_id: user.id, namespace: user.namespace) } let(:project3) { create(:project, path: 'project3', creator_id: user.id, namespace: user.namespace) } - let(:snippet) { create(:project_snippet, author: user, project: project, title: 'example') } + let(:snippet) { create(:project_snippet, :public, author: user, project: project, title: 'example') } let(:project_member) { create(:project_member, :master, user: user, project: project) } let(:project_member2) { create(:project_member, :developer, user: user3, project: project) } let(:user4) { create(:user) } From ef340f6e777875e1bbb38752e64ba7bea3ab2f31 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A9my=20Coutable?= Date: Thu, 21 Apr 2016 17:13:14 +0200 Subject: [PATCH 013/196] Ensure URL in all Service subclasses are valid MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Rémy Coutable --- app/helpers/issues_helper.rb | 48 +++++--- .../project_services/buildkite_service.rb | 4 +- .../project_services/issue_tracker_service.rb | 2 +- app/models/project_services/jira_service.rb | 2 + app/models/project_services/slack_service.rb | 2 +- spec/helpers/issues_helper_spec.rb | 36 ++++++ .../project_services/bamboo_service_spec.rb | 109 ++++++------------ .../buildkite_service_spec.rb | 17 +++ .../builds_email_service_spec.rb | 81 ++++++------- .../project_services/campfire_service_spec.rb | 42 +++++++ .../custom_issue_tracker_service_spec.rb | 49 ++++++++ .../project_services/drone_ci_service_spec.rb | 13 +-- .../emails_on_push_service_spec.rb | 17 +++ .../external_wiki_service_spec.rb | 17 ++- .../project_services/flowdock_service_spec.rb | 14 +++ .../gemnasium_service_spec.rb | 16 +++ .../gitlab_issue_tracker_service_spec.rb | 14 +++ .../project_services/hipchat_service_spec.rb | 14 +++ .../project_services/irker_service_spec.rb | 14 ++- .../project_services/jira_service_spec.rb | 26 ++++- .../pivotaltracker_service_spec.rb | 42 +++++++ .../project_services/pushover_service_spec.rb | 20 ++-- .../project_services/redmine_service_spec.rb | 49 ++++++++ .../project_services/slack_service_spec.rb | 17 ++- .../project_services/teamcity_service_spec.rb | 109 ++++++------------ .../issue_tracker_service_shared_example.rb | 7 ++ 26 files changed, 539 insertions(+), 242 deletions(-) create mode 100644 spec/models/project_services/campfire_service_spec.rb create mode 100644 spec/models/project_services/custom_issue_tracker_service_spec.rb create mode 100644 spec/models/project_services/emails_on_push_service_spec.rb rename spec/models/{ => project_services}/external_wiki_service_spec.rb (78%) create mode 100644 spec/models/project_services/pivotaltracker_service_spec.rb create mode 100644 spec/models/project_services/redmine_service_spec.rb create mode 100644 spec/support/issue_tracker_service_shared_example.rb diff --git a/app/helpers/issues_helper.rb b/app/helpers/issues_helper.rb index afe1e11a0da..198d39455d7 100644 --- a/app/helpers/issues_helper.rb +++ b/app/helpers/issues_helper.rb @@ -16,31 +16,49 @@ module IssuesHelper def url_for_project_issues(project = @project, options = {}) return '' if project.nil? - if options[:only_path] - project.issues_tracker.project_path - else - project.issues_tracker.project_url - end + url = + if options[:only_path] + project.issues_tracker.project_path + else + project.issues_tracker.project_url + end + + # Ensure we return a valid URL to prevent possible XSS. + URI.parse(url).to_s + rescue URI::InvalidURIError + '' end def url_for_new_issue(project = @project, options = {}) return '' if project.nil? - if options[:only_path] - project.issues_tracker.new_issue_path - else - project.issues_tracker.new_issue_url - end + url = + if options[:only_path] + project.issues_tracker.new_issue_path + else + project.issues_tracker.new_issue_url + end + + # Ensure we return a valid URL to prevent possible XSS. + URI.parse(url).to_s + rescue URI::InvalidURIError + '' end def url_for_issue(issue_iid, project = @project, options = {}) return '' if project.nil? - if options[:only_path] - project.issues_tracker.issue_path(issue_iid) - else - project.issues_tracker.issue_url(issue_iid) - end + url = + if options[:only_path] + project.issues_tracker.issue_path(issue_iid) + else + project.issues_tracker.issue_url(issue_iid) + end + + # Ensure we return a valid URL to prevent possible XSS. + URI.parse(url).to_s + rescue URI::InvalidURIError + '' end def bulk_update_milestone_options diff --git a/app/models/project_services/buildkite_service.rb b/app/models/project_services/buildkite_service.rb index 3efbfd2eec3..861cc974ec4 100644 --- a/app/models/project_services/buildkite_service.rb +++ b/app/models/project_services/buildkite_service.rb @@ -26,7 +26,7 @@ class BuildkiteService < CiService prop_accessor :project_url, :token, :enable_ssl_verification - validates :project_url, presence: true, if: :activated? + validates :project_url, presence: true, url: true, if: :activated? validates :token, presence: true, if: :activated? after_save :compose_service_hook, if: :activated? @@ -91,7 +91,7 @@ class BuildkiteService < CiService { type: 'text', name: 'project_url', placeholder: "#{ENDPOINT}/example/project" }, - + { type: 'checkbox', name: 'enable_ssl_verification', title: "Enable SSL verification" } diff --git a/app/models/project_services/issue_tracker_service.rb b/app/models/project_services/issue_tracker_service.rb index 25045224ce5..c5501e06411 100644 --- a/app/models/project_services/issue_tracker_service.rb +++ b/app/models/project_services/issue_tracker_service.rb @@ -21,7 +21,7 @@ class IssueTrackerService < Service - validates :project_url, :issues_url, :new_issue_url, presence: true, if: :activated? + validates :project_url, :issues_url, :new_issue_url, presence: true, url: true, if: :activated? default_value_for :category, 'issue_tracker' diff --git a/app/models/project_services/jira_service.rb b/app/models/project_services/jira_service.rb index 1ed42c4f3e7..b4418ba9284 100644 --- a/app/models/project_services/jira_service.rb +++ b/app/models/project_services/jira_service.rb @@ -28,6 +28,8 @@ class JiraService < IssueTrackerService prop_accessor :username, :password, :api_url, :jira_issue_transition_id, :title, :description, :project_url, :issues_url, :new_issue_url + validates :api_url, presence: true, url: true, if: :activated? + before_validation :set_api_url, :set_jira_issue_transition_id before_update :reset_password diff --git a/app/models/project_services/slack_service.rb b/app/models/project_services/slack_service.rb index fd65027f084..7092b757549 100644 --- a/app/models/project_services/slack_service.rb +++ b/app/models/project_services/slack_service.rb @@ -22,7 +22,7 @@ class SlackService < Service prop_accessor :webhook, :username, :channel boolean_accessor :notify_only_broken_builds - validates :webhook, presence: true, if: :activated? + validates :webhook, presence: true, url: true, if: :activated? def initialize_properties if properties.nil? diff --git a/spec/helpers/issues_helper_spec.rb b/spec/helpers/issues_helper_spec.rb index 543593cf389..bffe2c18b6f 100644 --- a/spec/helpers/issues_helper_spec.rb +++ b/spec/helpers/issues_helper_spec.rb @@ -30,6 +30,18 @@ describe IssuesHelper do expect(url_for_project_issues).to eq "" end + it 'returns an empty string if project_url is invalid' do + expect(project).to receive_message_chain('issues_tracker.project_url') { 'javascript:alert("foo");' } + + expect(url_for_project_issues(project)).to eq '' + end + + it 'returns an empty string if project_path is invalid' do + expect(project).to receive_message_chain('issues_tracker.project_path') { 'javascript:alert("foo");' } + + expect(url_for_project_issues(project, only_path: true)).to eq '' + end + describe "when external tracker was enabled and then config removed" do before do @project = ext_project @@ -68,6 +80,18 @@ describe IssuesHelper do expect(url_for_issue(issue.iid)).to eq "" end + it 'returns an empty string if issue_url is invalid' do + expect(project).to receive_message_chain('issues_tracker.issue_url') { 'javascript:alert("foo");' } + + expect(url_for_issue(issue.iid, project)).to eq '' + end + + it 'returns an empty string if issue_path is invalid' do + expect(project).to receive_message_chain('issues_tracker.issue_path') { 'javascript:alert("foo");' } + + expect(url_for_issue(issue.iid, project, only_path: true)).to eq '' + end + describe "when external tracker was enabled and then config removed" do before do @project = ext_project @@ -105,6 +129,18 @@ describe IssuesHelper do expect(url_for_new_issue).to eq "" end + it 'returns an empty string if issue_url is invalid' do + expect(project).to receive_message_chain('issues_tracker.new_issue_url') { 'javascript:alert("foo");' } + + expect(url_for_new_issue(project)).to eq '' + end + + it 'returns an empty string if issue_path is invalid' do + expect(project).to receive_message_chain('issues_tracker.new_issue_path') { 'javascript:alert("foo");' } + + expect(url_for_new_issue(project, only_path: true)).to eq '' + end + describe "when external tracker was enabled and then config removed" do before do @project = ext_project diff --git a/spec/models/project_services/bamboo_service_spec.rb b/spec/models/project_services/bamboo_service_spec.rb index 31b2c90122d..e771f35811e 100644 --- a/spec/models/project_services/bamboo_service_spec.rb +++ b/spec/models/project_services/bamboo_service_spec.rb @@ -27,86 +27,51 @@ describe BambooService, models: true do end describe 'Validations' do - describe '#bamboo_url' do - it 'does not validate the presence of bamboo_url if service is not active' do - bamboo_service = service - bamboo_service.active = false + subject { service } - expect(bamboo_service).not_to validate_presence_of(:bamboo_url) + context 'when service is active' do + before { subject.active = true } + + it { is_expected.to validate_presence_of(:build_key) } + it { is_expected.to validate_presence_of(:bamboo_url) } + it_behaves_like 'issue tracker service URL attribute', :bamboo_url + + describe '#username' do + it 'does not validate the presence of username if password is nil' do + subject.password = nil + + expect(subject).not_to validate_presence_of(:username) + end + + it 'validates the presence of username if password is present' do + subject.password = 'secret' + + expect(subject).to validate_presence_of(:username) + end end - it 'validates the presence of bamboo_url if service is active' do - bamboo_service = service - bamboo_service.active = true + describe '#password' do + it 'does not validate the presence of password if username is nil' do + subject.username = nil - expect(bamboo_service).to validate_presence_of(:bamboo_url) + expect(subject).not_to validate_presence_of(:password) + end + + it 'validates the presence of password if username is present' do + subject.username = 'john' + + expect(subject).to validate_presence_of(:password) + end end end - describe '#build_key' do - it 'does not validate the presence of build_key if service is not active' do - bamboo_service = service - bamboo_service.active = false + context 'when service is inactive' do + before { subject.active = false } - expect(bamboo_service).not_to validate_presence_of(:build_key) - end - - it 'validates the presence of build_key if service is active' do - bamboo_service = service - bamboo_service.active = true - - expect(bamboo_service).to validate_presence_of(:build_key) - end - end - - describe '#username' do - it 'does not validate the presence of username if service is not active' do - bamboo_service = service - bamboo_service.active = false - - expect(bamboo_service).not_to validate_presence_of(:username) - end - - it 'does not validate the presence of username if username is nil' do - bamboo_service = service - bamboo_service.active = true - bamboo_service.password = nil - - expect(bamboo_service).not_to validate_presence_of(:username) - end - - it 'validates the presence of username if service is active and username is present' do - bamboo_service = service - bamboo_service.active = true - bamboo_service.password = 'secret' - - expect(bamboo_service).to validate_presence_of(:username) - end - end - - describe '#password' do - it 'does not validate the presence of password if service is not active' do - bamboo_service = service - bamboo_service.active = false - - expect(bamboo_service).not_to validate_presence_of(:password) - end - - it 'does not validate the presence of password if username is nil' do - bamboo_service = service - bamboo_service.active = true - bamboo_service.username = nil - - expect(bamboo_service).not_to validate_presence_of(:password) - end - - it 'validates the presence of password if service is active and username is present' do - bamboo_service = service - bamboo_service.active = true - bamboo_service.username = 'john' - - expect(bamboo_service).to validate_presence_of(:password) - end + it { is_expected.not_to validate_presence_of(:build_key) } + it { is_expected.not_to validate_presence_of(:bamboo_url) } + it { is_expected.not_to validate_presence_of(:username) } + it { is_expected.not_to validate_presence_of(:password) } end end diff --git a/spec/models/project_services/buildkite_service_spec.rb b/spec/models/project_services/buildkite_service_spec.rb index 88cd624877a..60364df2015 100644 --- a/spec/models/project_services/buildkite_service_spec.rb +++ b/spec/models/project_services/buildkite_service_spec.rb @@ -26,6 +26,23 @@ describe BuildkiteService, models: true do it { is_expected.to have_one :service_hook } end + describe 'Validations' do + context 'when service is active' do + before { subject.active = true } + + it { is_expected.to validate_presence_of(:project_url) } + it { is_expected.to validate_presence_of(:token) } + it_behaves_like 'issue tracker service URL attribute', :project_url + end + + context 'when service is inactive' do + before { subject.active = false } + + it { is_expected.not_to validate_presence_of(:project_url) } + it { is_expected.not_to validate_presence_of(:token) } + end + end + describe 'commits methods' do before do @project = Project.new diff --git a/spec/models/project_services/builds_email_service_spec.rb b/spec/models/project_services/builds_email_service_spec.rb index 7c23c2efccd..236df8f047d 100644 --- a/spec/models/project_services/builds_email_service_spec.rb +++ b/spec/models/project_services/builds_email_service_spec.rb @@ -1,76 +1,71 @@ require 'spec_helper' describe BuildsEmailService do - let(:build) { create(:ci_build) } - let(:data) { Gitlab::BuildDataBuilder.build(build) } - let!(:project) { create(:project, :public, ci_id: 1) } - let(:service) { described_class.new(project: project, active: true) } + let(:data) { Gitlab::BuildDataBuilder.build(create(:ci_build)) } + + describe 'Validations' do + context 'when service is active' do + before { subject.active = true } + + it { is_expected.to validate_presence_of(:recipients) } + + context 'when pusher is added' do + before { subject.add_pusher = true } + + it { is_expected.not_to validate_presence_of(:recipients) } + end + end + + context 'when service is inactive' do + before { subject.active = false } + + it { is_expected.not_to validate_presence_of(:recipients) } + end + end describe '#execute' do it 'sends email' do - service.recipients = 'test@gitlab.com' + subject.recipients = 'test@gitlab.com' data[:build_status] = 'failed' + expect(BuildEmailWorker).to receive(:perform_async) - service.execute(data) + + subject.execute(data) end it 'does not send email with succeeded build and notify_only_broken_builds on' do - expect(service).to receive(:notify_only_broken_builds).and_return(true) + expect(subject).to receive(:notify_only_broken_builds).and_return(true) data[:build_status] = 'success' + expect(BuildEmailWorker).not_to receive(:perform_async) - service.execute(data) + + subject.execute(data) end it 'does not send email with failed build and build_allow_failure is true' do data[:build_status] = 'failed' data[:build_allow_failure] = true + expect(BuildEmailWorker).not_to receive(:perform_async) - service.execute(data) + + subject.execute(data) end it 'does not send email with unknown build status' do data[:build_status] = 'foo' + expect(BuildEmailWorker).not_to receive(:perform_async) - service.execute(data) + + subject.execute(data) end it 'does not send email when recipients list is empty' do - service.recipients = ' ,, ' + subject.recipients = ' ,, ' data[:build_status] = 'failed' + expect(BuildEmailWorker).not_to receive(:perform_async) - service.execute(data) - end - end - describe 'validations' do - - context 'when pusher is not added' do - before { service.add_pusher = false } - - it 'does not allow empty recipient input' do - service.recipients = '' - expect(service.valid?).to be false - end - - it 'does allow non-empty recipient input' do - service.recipients = 'test@example.com' - expect(service.valid?).to be true - end - - end - - context 'when pusher is added' do - before { service.add_pusher = true } - - it 'does allow empty recipient input' do - service.recipients = '' - expect(service.valid?).to be true - end - - it 'does allow non-empty recipient input' do - service.recipients = 'test@example.com' - expect(service.valid?).to be true - end + subject.execute(data) end end end diff --git a/spec/models/project_services/campfire_service_spec.rb b/spec/models/project_services/campfire_service_spec.rb new file mode 100644 index 00000000000..3e6da42803b --- /dev/null +++ b/spec/models/project_services/campfire_service_spec.rb @@ -0,0 +1,42 @@ +# == Schema Information +# +# Table name: services +# +# id :integer not null, primary key +# type :string(255) +# title :string(255) +# project_id :integer +# created_at :datetime +# updated_at :datetime +# active :boolean default(FALSE), not null +# properties :text +# template :boolean default(FALSE) +# push_events :boolean default(TRUE) +# issues_events :boolean default(TRUE) +# merge_requests_events :boolean default(TRUE) +# tag_push_events :boolean default(TRUE) +# note_events :boolean default(TRUE), not null +# + +require 'spec_helper' + +describe CampfireService, models: true do + describe 'Associations' do + it { is_expected.to belong_to :project } + it { is_expected.to have_one :service_hook } + end + + describe 'Validations' do + context 'when service is active' do + before { subject.active = true } + + it { is_expected.to validate_presence_of(:token) } + end + + context 'when service is inactive' do + before { subject.active = false } + + it { is_expected.not_to validate_presence_of(:token) } + end + end +end diff --git a/spec/models/project_services/custom_issue_tracker_service_spec.rb b/spec/models/project_services/custom_issue_tracker_service_spec.rb new file mode 100644 index 00000000000..ff976f8ec59 --- /dev/null +++ b/spec/models/project_services/custom_issue_tracker_service_spec.rb @@ -0,0 +1,49 @@ +# == Schema Information +# +# Table name: services +# +# id :integer not null, primary key +# type :string(255) +# title :string(255) +# project_id :integer +# created_at :datetime +# updated_at :datetime +# active :boolean default(FALSE), not null +# properties :text +# template :boolean default(FALSE) +# push_events :boolean default(TRUE) +# issues_events :boolean default(TRUE) +# merge_requests_events :boolean default(TRUE) +# tag_push_events :boolean default(TRUE) +# note_events :boolean default(TRUE), not null +# + +require 'spec_helper' + +describe CustomIssueTrackerService, models: true do + describe 'Associations' do + it { is_expected.to belong_to :project } + it { is_expected.to have_one :service_hook } + end + + describe 'Validations' do + context 'when service is active' do + before { subject.active = true } + + it { is_expected.to validate_presence_of(:project_url) } + it { is_expected.to validate_presence_of(:issues_url) } + it { is_expected.to validate_presence_of(:new_issue_url) } + it_behaves_like 'issue tracker service URL attribute', :project_url + it_behaves_like 'issue tracker service URL attribute', :issues_url + it_behaves_like 'issue tracker service URL attribute', :new_issue_url + end + + context 'when service is inactive' do + before { subject.active = false } + + it { is_expected.not_to validate_presence_of(:project_url) } + it { is_expected.not_to validate_presence_of(:issues_url) } + it { is_expected.not_to validate_presence_of(:new_issue_url) } + end + end +end diff --git a/spec/models/project_services/drone_ci_service_spec.rb b/spec/models/project_services/drone_ci_service_spec.rb index a2cf68a9e38..3a8e67438fc 100644 --- a/spec/models/project_services/drone_ci_service_spec.rb +++ b/spec/models/project_services/drone_ci_service_spec.rb @@ -28,25 +28,18 @@ describe DroneCiService, models: true do describe 'validations' do context 'active' do - before { allow(subject).to receive(:activated?).and_return(true) } + before { subject.active = true } it { is_expected.to validate_presence_of(:token) } it { is_expected.to validate_presence_of(:drone_url) } - it { is_expected.to allow_value('ewf9843kdnfdfs89234n').for(:token) } - it { is_expected.to allow_value('http://ci.example.com').for(:drone_url) } - it { is_expected.not_to allow_value('this is not url').for(:drone_url) } - it { is_expected.not_to allow_value('http//noturl').for(:drone_url) } - it { is_expected.not_to allow_value('ftp://ci.example.com').for(:drone_url) } + it_behaves_like 'issue tracker service URL attribute', :drone_url end context 'inactive' do - before { allow(subject).to receive(:activated?).and_return(false) } + before { subject.active = false } it { is_expected.not_to validate_presence_of(:token) } it { is_expected.not_to validate_presence_of(:drone_url) } - it { is_expected.to allow_value('ewf9843kdnfdfs89234n').for(:token) } - it { is_expected.to allow_value('http://drone.example.com').for(:drone_url) } - it { is_expected.to allow_value('ftp://drone.example.com').for(:drone_url) } end end diff --git a/spec/models/project_services/emails_on_push_service_spec.rb b/spec/models/project_services/emails_on_push_service_spec.rb new file mode 100644 index 00000000000..e6f78898c82 --- /dev/null +++ b/spec/models/project_services/emails_on_push_service_spec.rb @@ -0,0 +1,17 @@ +require 'spec_helper' + +describe EmailsOnPushService do + describe 'Validations' do + context 'when service is active' do + before { subject.active = true } + + it { is_expected.to validate_presence_of(:recipients) } + end + + context 'when service is inactive' do + before { subject.active = false } + + it { is_expected.not_to validate_presence_of(:recipients) } + end + end +end diff --git a/spec/models/external_wiki_service_spec.rb b/spec/models/project_services/external_wiki_service_spec.rb similarity index 78% rename from spec/models/external_wiki_service_spec.rb rename to spec/models/project_services/external_wiki_service_spec.rb index d37978720bf..5fe5ea7d2df 100644 --- a/spec/models/external_wiki_service_spec.rb +++ b/spec/models/project_services/external_wiki_service_spec.rb @@ -28,13 +28,18 @@ describe ExternalWikiService, models: true do it { should have_one :service_hook } end - describe "Validations" do - context "active" do - before do - subject.active = true - end + describe 'Validations' do + context 'when service is active' do + before { subject.active = true } - it { should validate_presence_of :external_wiki_url } + it { is_expected.to validate_presence_of(:external_wiki_url) } + it_behaves_like 'issue tracker service URL attribute', :external_wiki_url + end + + context 'when service is inactive' do + before { subject.active = false } + + it { is_expected.not_to validate_presence_of(:external_wiki_url) } end end diff --git a/spec/models/project_services/flowdock_service_spec.rb b/spec/models/project_services/flowdock_service_spec.rb index ff7fbcaa004..b7e627e6518 100644 --- a/spec/models/project_services/flowdock_service_spec.rb +++ b/spec/models/project_services/flowdock_service_spec.rb @@ -26,6 +26,20 @@ describe FlowdockService, models: true do it { is_expected.to have_one :service_hook } end + describe 'Validations' do + context 'when service is active' do + before { subject.active = true } + + it { is_expected.to validate_presence_of(:token) } + end + + context 'when service is inactive' do + before { subject.active = false } + + it { is_expected.not_to validate_presence_of(:token) } + end + end + describe "Execute" do let(:user) { create(:user) } let(:project) { create(:project) } diff --git a/spec/models/project_services/gemnasium_service_spec.rb b/spec/models/project_services/gemnasium_service_spec.rb index ecb3ccb1673..a08f1ac229f 100644 --- a/spec/models/project_services/gemnasium_service_spec.rb +++ b/spec/models/project_services/gemnasium_service_spec.rb @@ -26,6 +26,22 @@ describe GemnasiumService, models: true do it { is_expected.to have_one :service_hook } end + describe 'Validations' do + context 'when service is active' do + before { subject.active = true } + + it { is_expected.to validate_presence_of(:token) } + it { is_expected.to validate_presence_of(:api_key) } + end + + context 'when service is inactive' do + before { subject.active = false } + + it { is_expected.not_to validate_presence_of(:token) } + it { is_expected.not_to validate_presence_of(:api_key) } + end + end + describe "Execute" do let(:user) { create(:user) } let(:project) { create(:project) } diff --git a/spec/models/project_services/gitlab_issue_tracker_service_spec.rb b/spec/models/project_services/gitlab_issue_tracker_service_spec.rb index 3518dbd1728..7a1f106d6e3 100644 --- a/spec/models/project_services/gitlab_issue_tracker_service_spec.rb +++ b/spec/models/project_services/gitlab_issue_tracker_service_spec.rb @@ -26,6 +26,20 @@ describe GitlabIssueTrackerService, models: true do it { is_expected.to have_one :service_hook } end + describe 'Validations' do + context 'when service is active' do + subject { described_class.new(project: create(:project), active: true) } + + it { is_expected.to validate_presence_of(:issues_url) } + it_behaves_like 'issue tracker service URL attribute', :issues_url + end + + context 'when service is inactive' do + subject { described_class.new(project: create(:project), active: false) } + + it { is_expected.not_to validate_presence_of(:issues_url) } + end + end describe 'project and issue urls' do let(:project) { create(:project) } diff --git a/spec/models/project_services/hipchat_service_spec.rb b/spec/models/project_services/hipchat_service_spec.rb index d878162a220..6fb5cad5011 100644 --- a/spec/models/project_services/hipchat_service_spec.rb +++ b/spec/models/project_services/hipchat_service_spec.rb @@ -26,6 +26,20 @@ describe HipchatService, models: true do it { is_expected.to have_one :service_hook } end + describe 'Validations' do + context 'when service is active' do + before { subject.active = true } + + it { is_expected.to validate_presence_of(:token) } + end + + context 'when service is inactive' do + before { subject.active = false } + + it { is_expected.not_to validate_presence_of(:token) } + end + end + describe "Execute" do let(:hipchat) { HipchatService.new } let(:user) { create(:user, username: 'username') } diff --git a/spec/models/project_services/irker_service_spec.rb b/spec/models/project_services/irker_service_spec.rb index b783b1a576e..4ee022a5171 100644 --- a/spec/models/project_services/irker_service_spec.rb +++ b/spec/models/project_services/irker_service_spec.rb @@ -29,14 +29,16 @@ describe IrkerService, models: true do end describe 'Validations' do - before do - subject.active = true - subject.properties['recipients'] = _recipients + context 'when service is active' do + before { subject.active = true } + + it { is_expected.to validate_presence_of(:recipients) } end - context 'active' do - let(:_recipients) { nil } - it { should validate_presence_of :recipients } + context 'when service is inactive' do + before { subject.active = false } + + it { is_expected.not_to validate_presence_of(:recipients) } end end diff --git a/spec/models/project_services/jira_service_spec.rb b/spec/models/project_services/jira_service_spec.rb index 2f8193170ae..5309cfb99ff 100644 --- a/spec/models/project_services/jira_service_spec.rb +++ b/spec/models/project_services/jira_service_spec.rb @@ -26,6 +26,30 @@ describe JiraService, models: true do it { is_expected.to have_one :service_hook } end + describe 'Validations' do + context 'when service is active' do + before { subject.active = true } + + it { is_expected.to validate_presence_of(:api_url) } + it { is_expected.to validate_presence_of(:project_url) } + it { is_expected.to validate_presence_of(:issues_url) } + it { is_expected.to validate_presence_of(:new_issue_url) } + it_behaves_like 'issue tracker service URL attribute', :api_url + it_behaves_like 'issue tracker service URL attribute', :project_url + it_behaves_like 'issue tracker service URL attribute', :issues_url + it_behaves_like 'issue tracker service URL attribute', :new_issue_url + end + + context 'when service is inactive' do + before { subject.active = false } + + it { is_expected.not_to validate_presence_of(:api_url) } + it { is_expected.not_to validate_presence_of(:project_url) } + it { is_expected.not_to validate_presence_of(:issues_url) } + it { is_expected.not_to validate_presence_of(:new_issue_url) } + end + end + describe "Execute" do let(:user) { create(:user) } let(:project) { create(:project) } @@ -72,7 +96,7 @@ describe JiraService, models: true do context "when a password was previously set" do before do - @jira_service = JiraService.create( + @jira_service = JiraService.create!( project: create(:project), properties: { api_url: 'http://jira.example.com/rest/api/2', diff --git a/spec/models/project_services/pivotaltracker_service_spec.rb b/spec/models/project_services/pivotaltracker_service_spec.rb new file mode 100644 index 00000000000..f37edd4d970 --- /dev/null +++ b/spec/models/project_services/pivotaltracker_service_spec.rb @@ -0,0 +1,42 @@ +# == Schema Information +# +# Table name: services +# +# id :integer not null, primary key +# type :string(255) +# title :string(255) +# project_id :integer +# created_at :datetime +# updated_at :datetime +# active :boolean default(FALSE), not null +# properties :text +# template :boolean default(FALSE) +# push_events :boolean default(TRUE) +# issues_events :boolean default(TRUE) +# merge_requests_events :boolean default(TRUE) +# tag_push_events :boolean default(TRUE) +# note_events :boolean default(TRUE), not null +# + +require 'spec_helper' + +describe PivotaltrackerService, models: true do + describe 'Associations' do + it { is_expected.to belong_to :project } + it { is_expected.to have_one :service_hook } + end + + describe 'Validations' do + context 'when service is active' do + before { subject.active = true } + + it { is_expected.to validate_presence_of(:token) } + end + + context 'when service is inactive' do + before { subject.active = false } + + it { is_expected.not_to validate_presence_of(:token) } + end + end +end diff --git a/spec/models/project_services/pushover_service_spec.rb b/spec/models/project_services/pushover_service_spec.rb index 96039f9491b..555d9757b47 100644 --- a/spec/models/project_services/pushover_service_spec.rb +++ b/spec/models/project_services/pushover_service_spec.rb @@ -27,14 +27,20 @@ describe PushoverService, models: true do end describe 'Validations' do - context 'active' do - before do - subject.active = true - end + context 'when service is active' do + before { subject.active = true } - it { is_expected.to validate_presence_of :api_key } - it { is_expected.to validate_presence_of :user_key } - it { is_expected.to validate_presence_of :priority } + it { is_expected.to validate_presence_of(:api_key) } + it { is_expected.to validate_presence_of(:user_key) } + it { is_expected.to validate_presence_of(:priority) } + end + + context 'when service is inactive' do + before { subject.active = false } + + it { is_expected.not_to validate_presence_of(:api_key) } + it { is_expected.not_to validate_presence_of(:user_key) } + it { is_expected.not_to validate_presence_of(:priority) } end end diff --git a/spec/models/project_services/redmine_service_spec.rb b/spec/models/project_services/redmine_service_spec.rb new file mode 100644 index 00000000000..7d14f6e8280 --- /dev/null +++ b/spec/models/project_services/redmine_service_spec.rb @@ -0,0 +1,49 @@ +# == Schema Information +# +# Table name: services +# +# id :integer not null, primary key +# type :string(255) +# title :string(255) +# project_id :integer +# created_at :datetime +# updated_at :datetime +# active :boolean default(FALSE), not null +# properties :text +# template :boolean default(FALSE) +# push_events :boolean default(TRUE) +# issues_events :boolean default(TRUE) +# merge_requests_events :boolean default(TRUE) +# tag_push_events :boolean default(TRUE) +# note_events :boolean default(TRUE), not null +# + +require 'spec_helper' + +describe RedmineService, models: true do + describe 'Associations' do + it { is_expected.to belong_to :project } + it { is_expected.to have_one :service_hook } + end + + describe 'Validations' do + context 'when service is active' do + before { subject.active = true } + + it { is_expected.to validate_presence_of(:project_url) } + it { is_expected.to validate_presence_of(:issues_url) } + it { is_expected.to validate_presence_of(:new_issue_url) } + it_behaves_like 'issue tracker service URL attribute', :project_url + it_behaves_like 'issue tracker service URL attribute', :issues_url + it_behaves_like 'issue tracker service URL attribute', :new_issue_url + end + + context 'when service is inactive' do + before { subject.active = false } + + it { is_expected.not_to validate_presence_of(:project_url) } + it { is_expected.not_to validate_presence_of(:issues_url) } + it { is_expected.not_to validate_presence_of(:new_issue_url) } + end + end +end diff --git a/spec/models/project_services/slack_service_spec.rb b/spec/models/project_services/slack_service_spec.rb index 478d59be08b..a97b7560137 100644 --- a/spec/models/project_services/slack_service_spec.rb +++ b/spec/models/project_services/slack_service_spec.rb @@ -26,13 +26,18 @@ describe SlackService, models: true do it { is_expected.to have_one :service_hook } end - describe "Validations" do - context "active" do - before do - subject.active = true - end + describe 'Validations' do + context 'when service is active' do + before { subject.active = true } - it { is_expected.to validate_presence_of :webhook } + it { is_expected.to validate_presence_of(:webhook) } + it_behaves_like 'issue tracker service URL attribute', :webhook + end + + context 'when service is inactive' do + before { subject.active = false } + + it { is_expected.not_to validate_presence_of(:webhook) } end end diff --git a/spec/models/project_services/teamcity_service_spec.rb b/spec/models/project_services/teamcity_service_spec.rb index bc7423cee69..ad24b895170 100644 --- a/spec/models/project_services/teamcity_service_spec.rb +++ b/spec/models/project_services/teamcity_service_spec.rb @@ -27,86 +27,51 @@ describe TeamcityService, models: true do end describe 'Validations' do - describe '#teamcity_url' do - it 'does not validate the presence of teamcity_url if service is not active' do - teamcity_service = service - teamcity_service.active = false + subject { service } - expect(teamcity_service).not_to validate_presence_of(:teamcity_url) + context 'when service is active' do + before { subject.active = true } + + it { is_expected.to validate_presence_of(:build_type) } + it { is_expected.to validate_presence_of(:teamcity_url) } + it_behaves_like 'issue tracker service URL attribute', :teamcity_url + + describe '#username' do + it 'does not validate the presence of username if password is nil' do + subject.password = nil + + expect(subject).not_to validate_presence_of(:username) + end + + it 'validates the presence of username if password is present' do + subject.password = 'secret' + + expect(subject).to validate_presence_of(:username) + end end - it 'validates the presence of teamcity_url if service is active' do - teamcity_service = service - teamcity_service.active = true + describe '#password' do + it 'does not validate the presence of password if username is nil' do + subject.username = nil - expect(teamcity_service).to validate_presence_of(:teamcity_url) + expect(subject).not_to validate_presence_of(:password) + end + + it 'validates the presence of password if username is present' do + subject.username = 'john' + + expect(subject).to validate_presence_of(:password) + end end end - describe '#build_type' do - it 'does not validate the presence of build_type if service is not active' do - teamcity_service = service - teamcity_service.active = false + context 'when service is inactive' do + before { subject.active = false } - expect(teamcity_service).not_to validate_presence_of(:build_type) - end - - it 'validates the presence of build_type if service is active' do - teamcity_service = service - teamcity_service.active = true - - expect(teamcity_service).to validate_presence_of(:build_type) - end - end - - describe '#username' do - it 'does not validate the presence of username if service is not active' do - teamcity_service = service - teamcity_service.active = false - - expect(teamcity_service).not_to validate_presence_of(:username) - end - - it 'does not validate the presence of username if username is nil' do - teamcity_service = service - teamcity_service.active = true - teamcity_service.password = nil - - expect(teamcity_service).not_to validate_presence_of(:username) - end - - it 'validates the presence of username if service is active and username is present' do - teamcity_service = service - teamcity_service.active = true - teamcity_service.password = 'secret' - - expect(teamcity_service).to validate_presence_of(:username) - end - end - - describe '#password' do - it 'does not validate the presence of password if service is not active' do - teamcity_service = service - teamcity_service.active = false - - expect(teamcity_service).not_to validate_presence_of(:password) - end - - it 'does not validate the presence of password if username is nil' do - teamcity_service = service - teamcity_service.active = true - teamcity_service.username = nil - - expect(teamcity_service).not_to validate_presence_of(:password) - end - - it 'validates the presence of password if service is active and username is present' do - teamcity_service = service - teamcity_service.active = true - teamcity_service.username = 'john' - - expect(teamcity_service).to validate_presence_of(:password) - end + it { is_expected.not_to validate_presence_of(:build_type) } + it { is_expected.not_to validate_presence_of(:teamcity_url) } + it { is_expected.not_to validate_presence_of(:username) } + it { is_expected.not_to validate_presence_of(:password) } end end diff --git a/spec/support/issue_tracker_service_shared_example.rb b/spec/support/issue_tracker_service_shared_example.rb new file mode 100644 index 00000000000..b6d7436c360 --- /dev/null +++ b/spec/support/issue_tracker_service_shared_example.rb @@ -0,0 +1,7 @@ +RSpec.shared_examples 'issue tracker service URL attribute' do |url_attr| + it { is_expected.to allow_value('https://example.com').for(url_attr) } + + it { is_expected.not_to allow_value('example.com').for(url_attr) } + it { is_expected.not_to allow_value('ftp://example.com').for(url_attr) } + it { is_expected.not_to allow_value('herp-and-derp').for(url_attr) } +end From cd0750e0457f26f8165be301ad628e1830bd1e40 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A9my=20Coutable?= Date: Mon, 25 Apr 2016 15:46:15 +0200 Subject: [PATCH 014/196] Prevent private project name and namespace from leaking in the new MR view MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Fixes #15591. Signed-off-by: Rémy Coutable --- app/services/merge_requests/build_service.rb | 3 +++ spec/features/merge_requests/create_new_mr_spec.rb | 10 ++++++++++ 2 files changed, 13 insertions(+) diff --git a/app/services/merge_requests/build_service.rb b/app/services/merge_requests/build_service.rb index fa34753c4fd..3544752d47a 100644 --- a/app/services/merge_requests/build_service.rb +++ b/app/services/merge_requests/build_service.rb @@ -7,6 +7,9 @@ module MergeRequests merge_request.can_be_created = false merge_request.compare_commits = [] merge_request.source_project = project unless merge_request.source_project + + merge_request.target_project = nil unless can?(current_user, :read_project, merge_request.target_project) + merge_request.target_project ||= (project.forked_from_project || project) merge_request.target_branch ||= merge_request.target_project.default_branch diff --git a/spec/features/merge_requests/create_new_mr_spec.rb b/spec/features/merge_requests/create_new_mr_spec.rb index 00b60bd0e75..e296078bad8 100644 --- a/spec/features/merge_requests/create_new_mr_spec.rb +++ b/spec/features/merge_requests/create_new_mr_spec.rb @@ -30,4 +30,14 @@ feature 'Create New Merge Request', feature: true, js: true do expect(page).to have_content 'git checkout -b orphaned-branch origin/orphaned-branch' end + + context 'when target project cannot be viewed by the current user' do + it 'does not leak the private project name & namespace' do + private_project = create(:project, :private) + + visit new_namespace_project_merge_request_path(project.namespace, project, merge_request: { target_project_id: private_project.id }) + + expect(page).not_to have_content private_project.to_reference + end + end end From 180c45b37ee16c24ddcb5c26a631dbca4ac76335 Mon Sep 17 00:00:00 2001 From: Valery Sizov Date: Fri, 22 Apr 2016 13:25:32 +0300 Subject: [PATCH 015/196] AwardsHandler follows code style conventions --- app/assets/javascripts/awards_handler.coffee | 66 +++++++++++--------- app/assets/javascripts/notes.js.coffee | 8 +-- app/views/votes/_votes_block.html.haml | 18 +++--- 3 files changed, 50 insertions(+), 42 deletions(-) diff --git a/app/assets/javascripts/awards_handler.coffee b/app/assets/javascripts/awards_handler.coffee index fcba9818726..21bd19ef07c 100644 --- a/app/assets/javascripts/awards_handler.coffee +++ b/app/assets/javascripts/awards_handler.coffee @@ -1,5 +1,5 @@ class @AwardsHandler - constructor: (@get_emojis_url, @post_emoji_url, @noteable_type, @noteable_id, @unicodes) -> + constructor: (@getEmojisUrl, @postEmojiUrl, @noteableType, @noteableId, @unicodes) -> $(".js-add-award").on "click", (event) => event.stopPropagation() event.preventDefault() @@ -23,13 +23,13 @@ class @AwardsHandler .find(".icon") .data "emoji" - if emoji is "thumbsup" and awards_handler.didUserClickEmoji $(this), "thumbsdown" - awards_handler.addAward "thumbsdown" + if emoji is "thumbsup" and awardsHandler.didUserClickEmoji $(this), "thumbsdown" + awardsHandler.addAward "thumbsdown" - else if emoji is "thumbsdown" and awards_handler.didUserClickEmoji $(this), "thumbsup" - awards_handler.addAward "thumbsup" + else if emoji is "thumbsdown" and awardsHandler.didUserClickEmoji $(this), "thumbsup" + awardsHandler.addAward "thumbsup" - awards_handler.addAward emoji + awardsHandler.addAward emoji $(this).trigger 'blur' @@ -47,7 +47,7 @@ class @AwardsHandler $("#emoji_search").focus() else $('.js-add-award').addClass "is-loading" - $.get @get_emojis_url, (response) => + $.get @getEmojisUrl, (response) => $('.js-add-award').removeClass "is-loading" $(".js-award-holder").append response setTimeout => @@ -99,25 +99,25 @@ class @AwardsHandler emojiIcon.remove() removeMeFromAuthorList: (emoji) -> - award_block = @findEmojiIcon(emoji).parent() - authors = award_block + awardBlock = @findEmojiIcon(emoji).parent() + authors = awardBlock .attr("data-original-title") .split(", ") authors.splice(authors.indexOf("me"),1) - award_block + awardBlock .closest(".js-emoji-btn") .attr("data-original-title", authors.join(", ")) - @resetTooltip(award_block) + @resetTooltip(awardBlock) addMeToAuthorList: (emoji) -> - award_block = @findEmojiIcon(emoji).parent() - origTitle = award_block.attr("data-original-title").trim() + awardBlock = @findEmojiIcon(emoji).parent() + origTitle = awardBlock.attr("data-original-title").trim() authors = [] if origTitle authors = origTitle.split(', ') authors.push("me") - award_block.attr("data-original-title", authors.join(", ")) - @resetTooltip(award_block) + awardBlock.attr("data-original-title", authors.join(", ")) + @resetTooltip(awardBlock) resetTooltip: (award) -> award.tooltip("destroy") @@ -139,20 +139,28 @@ class @AwardsHandler "" ) - emoji_node = $(nodes.join("\n")) + $(nodes.join("\n")) .insertBefore(".js-award-holder") .find(".emoji-icon") .data("emoji", emoji) $('.award-control').tooltip() resolveNameToCssClass: (emoji) -> - "emoji-#{@unicodes[emoji]}" + emoji_icon = $(".emoji-menu-content [data-emoji='#{emoji}']") + + if emoji_icon.length > 0 + unicodeName = emoji_icon.data('unicode-name') + else + # Find by alias + unicodeName = $(".emoji-menu-content [data-aliases*=':#{emoji}:']").data('unicode-name') + + "emoji-#{unicodeName}" postEmoji: (emoji, callback) -> - $.post @post_emoji_url, { note: { + $.post @postEmojiUrl, { note: { note: ":#{emoji}:" - noteable_type: @noteable_type - noteable_id: @noteable_id + noteable_type: @noteableType + noteable_id: @noteableId }},(data) -> if data.ok callback.call() @@ -166,21 +174,21 @@ class @AwardsHandler }, 200) addEmojiToFrequentlyUsedList: (emoji) -> - frequently_used_emojis = @getFrequentlyUsedEmojis() - frequently_used_emojis.push(emoji) - $.cookie('frequently_used_emojis', frequently_used_emojis.join(","), { expires: 365 }) + frequentlyUsedEmojis = @getFrequentlyUsedEmojis() + frequentlyUsedEmojis.push(emoji) + $.cookie('frequently_used_emojis', frequentlyUsedEmojis.join(","), { expires: 365 }) getFrequentlyUsedEmojis: -> - frequently_used_emojis = ($.cookie('frequently_used_emojis') || "").split(",") - _.compact(_.uniq(frequently_used_emojis)) + frequentlyUsedEmojis = ($.cookie('frequently_used_emojis') || "").split(",") + _.compact(_.uniq(frequentlyUsedEmojis)) renderFrequentlyUsedBlock: -> if $.cookie('frequently_used_emojis') - frequently_used_emojis = @getFrequentlyUsedEmojis() + frequentlyUsedEmojis = @getFrequentlyUsedEmojis() ul = $("