From a5521bad4012a9eea21cb067271c8ec1810c6d8c Mon Sep 17 00:00:00 2001 From: Hiroyuki Sato Date: Sat, 4 Mar 2017 23:03:14 +0900 Subject: Prevent concurrent editing wiki --- app/controllers/projects/wikis_controller.rb | 5 ++++- app/models/wiki_page.rb | 17 ++++++++++++----- app/services/wiki_pages/update_service.rb | 2 +- app/views/projects/wikis/_form.html.haml | 2 ++ app/views/projects/wikis/edit.html.haml | 6 ++++++ .../unreleased/1827-prevent-concurrent-editing-wiki.yml | 4 ++++ .../projects/wiki/user_updates_wiki_page_spec.rb | 12 +++++++++++- spec/models/wiki_page_spec.rb | 14 ++++++++++++++ 8 files changed, 54 insertions(+), 8 deletions(-) create mode 100644 changelogs/unreleased/1827-prevent-concurrent-editing-wiki.yml diff --git a/app/controllers/projects/wikis_controller.rb b/app/controllers/projects/wikis_controller.rb index 2d8064c9878..d27af79e003 100644 --- a/app/controllers/projects/wikis_controller.rb +++ b/app/controllers/projects/wikis_controller.rb @@ -56,6 +56,9 @@ class Projects::WikisController < Projects::ApplicationController else render 'edit' end + rescue WikiPage::PageChangedError + @conflict = true + render 'edit' end def create @@ -125,6 +128,6 @@ class Projects::WikisController < Projects::ApplicationController end def wiki_params - params[:wiki].slice(:title, :content, :format, :message) + params[:wiki].slice(:title, :content, :format, :message, :last_commit_sha) end end diff --git a/app/models/wiki_page.rb b/app/models/wiki_page.rb index 2caebb496db..4c5df6937f2 100644 --- a/app/models/wiki_page.rb +++ b/app/models/wiki_page.rb @@ -1,4 +1,6 @@ class WikiPage + PageChangedError = Class.new(StandardError) + include ActiveModel::Validations include ActiveModel::Conversion include StaticModel @@ -176,17 +178,22 @@ class WikiPage # Updates an existing Wiki Page, creating a new version. # - # new_content - The raw markup content to replace the existing. - # format - Optional symbol representing the content format. - # See ProjectWiki::MARKUPS Hash for available formats. - # message - Optional commit message to set on the new version. + # new_content - The raw markup content to replace the existing. + # format - Optional symbol representing the content format. + # See ProjectWiki::MARKUPS Hash for available formats. + # message - Optional commit message to set on the new version. + # last_commit_sha - Optional last commit sha to validate the page unchanged. # # Returns the String SHA1 of the newly created page # or False if the save was unsuccessful. - def update(new_content = "", format = :markdown, message = nil) + def update(new_content = "", format = :markdown, message = nil, last_commit_sha = nil) @attributes[:content] = new_content @attributes[:format] = format + if last_commit_sha && last_commit_sha != commit.sha + raise PageChangedError.new("You are attempting to update a page that has changed since you started editing it.") + end + save :update_page, @page, content, format, message end diff --git a/app/services/wiki_pages/update_service.rb b/app/services/wiki_pages/update_service.rb index 8f6a50da838..1046bb3be01 100644 --- a/app/services/wiki_pages/update_service.rb +++ b/app/services/wiki_pages/update_service.rb @@ -1,7 +1,7 @@ module WikiPages class UpdateService < WikiPages::BaseService def execute(page) - if page.update(@params[:content], @params[:format], @params[:message]) + if page.update(@params[:content], @params[:format], @params[:message], @params[:last_commit_sha]) execute_hooks(page, 'update') end diff --git a/app/views/projects/wikis/_form.html.haml b/app/views/projects/wikis/_form.html.haml index c52527332bc..9313f9e9c96 100644 --- a/app/views/projects/wikis/_form.html.haml +++ b/app/views/projects/wikis/_form.html.haml @@ -2,6 +2,8 @@ = form_errors(@page) = f.hidden_field :title, value: @page.title + - if @page.persisted? + = f.hidden_field :last_commit_sha, value: @page.commit.sha .form-group = f.label :format, class: 'control-label' .col-sm-10 diff --git a/app/views/projects/wikis/edit.html.haml b/app/views/projects/wikis/edit.html.haml index 8cf018da1b7..2ec03a00db9 100644 --- a/app/views/projects/wikis/edit.html.haml +++ b/app/views/projects/wikis/edit.html.haml @@ -2,6 +2,12 @@ - page_title "Edit", @page.title.capitalize, "Wiki" %div{ class: container_class } + - if @conflict + .alert.alert-danger + Someone edited the page the same time you did. Please check out + = link_to "the page", namespace_project_wiki_path(@project.namespace, @project, @page), target: "_blank" + and make sure your changes will not unintentionally remove theirs. + .wiki-page-header.has-sidebar-toggle %button.btn.btn-default.sidebar-toggle.js-sidebar-wiki-toggle{ role: "button", type: "button" } = icon('angle-double-left') diff --git a/changelogs/unreleased/1827-prevent-concurrent-editing-wiki.yml b/changelogs/unreleased/1827-prevent-concurrent-editing-wiki.yml new file mode 100644 index 00000000000..3ef6544c013 --- /dev/null +++ b/changelogs/unreleased/1827-prevent-concurrent-editing-wiki.yml @@ -0,0 +1,4 @@ +--- +title: Prevent concurrent editing wiki +merge_request: 9707 +author: Hiroyuki Sato diff --git a/spec/features/projects/wiki/user_updates_wiki_page_spec.rb b/spec/features/projects/wiki/user_updates_wiki_page_spec.rb index f842d14fa96..a15bb3ad46b 100644 --- a/spec/features/projects/wiki/user_updates_wiki_page_spec.rb +++ b/spec/features/projects/wiki/user_updates_wiki_page_spec.rb @@ -8,7 +8,7 @@ feature 'Projects > Wiki > User updates wiki page', feature: true do login_as(user) visit namespace_project_path(project.namespace, project) - WikiPages::CreateService.new(project, user, title: 'home', content: 'Home page').execute + @wiki_page = WikiPages::CreateService.new(project, user, title: 'home', content: 'Home page').execute click_link 'Wiki' end @@ -25,6 +25,16 @@ feature 'Projects > Wiki > User updates wiki page', feature: true do expect(page).to have_content("Last edited by #{user.name}") expect(page).to have_content('My awesome wiki!') end + + scenario 'page has been updated since the user opened the edit page' do + click_link 'Edit' + + @wiki_page.update("Update") + + click_button 'Save changes' + + expect(page).to have_content 'Someone edited the page the same time you did.' + end end context 'in a group namespace' do diff --git a/spec/models/wiki_page_spec.rb b/spec/models/wiki_page_spec.rb index 753dc938c52..d76d94c3186 100644 --- a/spec/models/wiki_page_spec.rb +++ b/spec/models/wiki_page_spec.rb @@ -208,6 +208,20 @@ describe WikiPage, models: true do expect(@page.update("more content")).to be_truthy end end + + context "with same last commit sha" do + it "returns true" do + last_commit_sha = @page.commit.sha + expect(@page.update("more content", :markdown, nil, last_commit_sha)).to be_truthy + end + end + + context "with different last commit sha" do + it "raises exception" do + last_commit_sha = "xxx" + expect { @page.update("more content", :markdown, nil, last_commit_sha) }.to raise_error(WikiPage::PageChangedError) + end + end end describe "#destroy" do -- cgit v1.2.1