diff options
| -rw-r--r-- | CHANGELOG | 4 | ||||
| -rw-r--r-- | app/assets/javascripts/user_tabs.js.coffee | 9 | ||||
| -rw-r--r-- | app/controllers/projects/hooks_controller.rb | 6 | ||||
| -rw-r--r-- | app/controllers/snippets_controller.rb | 2 | ||||
| -rw-r--r-- | app/controllers/users_controller.rb | 22 | ||||
| -rw-r--r-- | app/helpers/events_helper.rb | 9 | ||||
| -rw-r--r-- | app/models/hooks/web_hook.rb | 2 | ||||
| -rw-r--r-- | app/views/layouts/header/_default.html.haml | 5 | ||||
| -rw-r--r-- | app/views/users/show.html.haml | 6 | ||||
| -rw-r--r-- | config/routes.rb | 32 | ||||
| -rw-r--r-- | doc/web_hooks/web_hooks.md | 13 | ||||
| -rw-r--r-- | features/steps/project/hooks.rb | 2 | ||||
| -rw-r--r-- | lib/banzai/filter/wiki_link_filter.rb | 11 | ||||
| -rw-r--r-- | spec/controllers/users_controller_spec.rb | 22 | ||||
| -rw-r--r-- | spec/lib/banzai/filter/wiki_link_filter_spec.rb | 85 | ||||
| -rw-r--r-- | spec/models/hooks/web_hook_spec.rb | 4 | ||||
| -rw-r--r-- | spec/routing/routing_spec.rb | 41 | 
17 files changed, 231 insertions, 44 deletions
| diff --git a/CHANGELOG b/CHANGELOG index 4841361482d..187011c6016 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,6 +1,7 @@  Please view this file on the master branch, on stable branches it's out of date.  v 8.8.0 (unreleased) +  - Snippets tab under user profile. !4001 (Long Nguyen)    - Fix error when using link to uploads in global snippets    - Assign labels and milestone to target project when moving issue. !3934 (Long Nguyen)    - Use a case-insensitive comparison in sanitizing URI schemes @@ -43,6 +44,9 @@ v 8.8.0 (unreleased)    - Fix adding a todo for private group members (Ahmad Sherif)    - Bump ace-rails-ap gem version from 2.0.1 to 4.0.2 which upgrades Ace Editor from 1.1.2 to 1.2.3 +v 8.7.5 +  - Fix relative links in wiki pages. !4050 +  v 8.7.4    - Links for Redmine issue references are generated correctly again !4048 (Benedikt Huss)    - Fix setting trusted proxies !3970 diff --git a/app/assets/javascripts/user_tabs.js.coffee b/app/assets/javascripts/user_tabs.js.coffee index c2aeffe2381..70614396a4e 100644 --- a/app/assets/javascripts/user_tabs.js.coffee +++ b/app/assets/javascripts/user_tabs.js.coffee @@ -26,6 +26,10 @@  #         Personal projects  #       </a>  #     </li> +#    <li class="snippets-tab"> +#       <a data-action="snippets" data-target="#snippets" data-toggle="tab" href="/u/username/snippets"> +#       </a> +#     </li>  #   </ul>  #  #   <div class="tab-content"> @@ -41,6 +45,9 @@  #     <div class="tab-pane" id="projects">  #       Projects content  #     </div> +#     <div class="tab-pane" id="snippets"> +#       Snippets content +#     </div>  #   </div>  #  #   <div class="loading-status"> @@ -100,7 +107,7 @@ class @UserTabs      if action is 'activity'        @loadActivities(source) -    if action in ['groups', 'contributed', 'projects'] +    if action in ['groups', 'contributed', 'projects', 'snippets']        @loadTab(source, action)    loadTab: (source, action) -> diff --git a/app/controllers/projects/hooks_controller.rb b/app/controllers/projects/hooks_controller.rb index dfa9bd259e8..47524b1cf0b 100644 --- a/app/controllers/projects/hooks_controller.rb +++ b/app/controllers/projects/hooks_controller.rb @@ -27,8 +27,10 @@ class Projects::HooksController < Projects::ApplicationController      if !@project.empty_repo?        status, message = TestHookService.new.execute(hook, current_user) -      if status -        flash[:notice] = 'Hook successfully executed.' +      if status && status >= 200 && status < 400 +        flash[:notice] = "Hook executed successfully: HTTP #{status}" +      elsif status +        flash[:alert] = "Hook executed successfully but returned HTTP #{status} #{message}"        else          flash[:alert] = "Hook execution failed: #{message}"        end diff --git a/app/controllers/snippets_controller.rb b/app/controllers/snippets_controller.rb index 2daceed039b..2a17c1f34db 100644 --- a/app/controllers/snippets_controller.rb +++ b/app/controllers/snippets_controller.rb @@ -10,7 +10,7 @@ class SnippetsController < ApplicationController    # Allow destroy snippet    before_action :authorize_admin_snippet!, only: [:destroy] -  skip_before_action :authenticate_user!, only: [:index, :user_index, :show, :raw] +  skip_before_action :authenticate_user!, only: [:index, :show, :raw]    layout 'snippets'    respond_to :html diff --git a/app/controllers/users_controller.rb b/app/controllers/users_controller.rb index 2ae180c8a12..799421c185b 100644 --- a/app/controllers/users_controller.rb +++ b/app/controllers/users_controller.rb @@ -58,6 +58,19 @@ class UsersController < ApplicationController      end    end +  def snippets +    load_snippets + +    respond_to do |format| +      format.html { render 'show' } +      format.json do +        render json: { +          html: view_to_html_string("snippets/_snippets", collection: @snippets) +        } +      end +    end +  end +    def calendar      calendar = contributions_calendar      @timestamps = calendar.timestamps @@ -116,6 +129,15 @@ class UsersController < ApplicationController      @groups = JoinedGroupsFinder.new(user).execute(current_user)    end +  def load_snippets +    @snippets = SnippetsFinder.new.execute( +      current_user, +      filter: :by_user, +      user: user, +      scope: params[:scope] +    ).page(params[:page]) +  end +    def projects_for_current_user      ProjectsFinder.new.execute(current_user)    end diff --git a/app/helpers/events_helper.rb b/app/helpers/events_helper.rb index 592bad8ba24..0bf328e7d19 100644 --- a/app/helpers/events_helper.rb +++ b/app/helpers/events_helper.rb @@ -39,15 +39,6 @@ module EventsHelper      end    end -  def icon_for_event -    { -      EventFilter.push     => 'upload', -      EventFilter.merged   => 'check-square-o', -      EventFilter.comments => 'comments', -      EventFilter.team     => 'user', -    } -  end -    def event_preposition(event)      if event.push? || event.commented? || event.target        "at" diff --git a/app/models/hooks/web_hook.rb b/app/models/hooks/web_hook.rb index fde05f729dc..8b87b6c3d64 100644 --- a/app/models/hooks/web_hook.rb +++ b/app/models/hooks/web_hook.rb @@ -38,7 +38,7 @@ class WebHook < ActiveRecord::Base                                basic_auth: auth)      end -    [(response.code >= 200 && response.code < 300), ActionView::Base.full_sanitizer.sanitize(response.to_s)] +    [response.code, response.to_s]    rescue SocketError, OpenSSL::SSL::SSLError, Errno::ECONNRESET, Errno::ECONNREFUSED, Net::OpenTimeout => e      logger.error("WebHook Error => #{e}")      [false, e.to_s] diff --git a/app/views/layouts/header/_default.html.haml b/app/views/layouts/header/_default.html.haml index 172579dafda..c33740e23fa 100644 --- a/app/views/layouts/header/_default.html.haml +++ b/app/views/layouts/header/_default.html.haml @@ -27,8 +27,9 @@              %li                = link_to dashboard_todos_path, title: 'Todos', data: {toggle: 'tooltip', placement: 'bottom', container: 'body'} do                  = icon('bell fw') -                %span.badge.todos-pending-count -                  = todos_pending_count +                - unless todos_pending_count == 0 +                  %span.badge.todos-pending-count +                    = todos_pending_count              - if current_user.can_create_project?                %li                  = link_to new_project_path, title: 'New project', data: {toggle: 'tooltip', placement: 'bottom', container: 'body'} do diff --git a/app/views/users/show.html.haml b/app/views/users/show.html.haml index 3c0b89c6741..9017fd54fcc 100644 --- a/app/views/users/show.html.haml +++ b/app/views/users/show.html.haml @@ -81,6 +81,9 @@        %li.projects-tab          = link_to user_projects_path, data: {target: 'div#projects', action: 'projects', toggle: 'tab'} do            Personal projects +      %li.snippets-tab +        = link_to user_snippets_path, data: {target: 'div#snippets', action: 'snippets', toggle: 'tab'} do +          Snippets    %div{ class: container_class }      .tab-content @@ -104,6 +107,9 @@        #projects.tab-pane          - # This tab is always loaded via AJAX +      #snippets.tab-pane +        - # This tab is always loaded via AJAX +      .loading-status        = spinner diff --git a/config/routes.rb b/config/routes.rb index dafecc94648..881de37f10e 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -91,7 +91,8 @@ Rails.application.routes.draw do      end    end -  get '/s/:username' => 'snippets#index', as: :user_snippets, constraints: { username: /.*/ } +  get '/s/:username', to: redirect('/u/%{username}/snippets'), +                      constraints: { username: /[a-zA-Z.0-9_\-]+(?<!\.atom)/ }    #    # Invites @@ -342,23 +343,18 @@ Rails.application.routes.draw do      end    end -  get 'u/:username/calendar' => 'users#calendar', as: :user_calendar, -      constraints: { username: /.*/ } - -  get 'u/:username/calendar_activities' => 'users#calendar_activities', as: :user_calendar_activities, -      constraints: { username: /.*/ } - -  get 'u/:username/groups' => 'users#groups', as: :user_groups, -      constraints: { username: /.*/ } - -  get 'u/:username/projects' => 'users#projects', as: :user_projects, -      constraints: { username: /.*/ } - -  get 'u/:username/contributed' => 'users#contributed', as: :user_contributed_projects, -      constraints: { username: /.*/ } - -  get '/u/:username' => 'users#show', as: :user, -      constraints: { username: /[a-zA-Z.0-9_\-]+(?<!\.atom)/ } +  scope(path: 'u/:username', +        as: :user, +        constraints: { username: /[a-zA-Z.0-9_\-]+(?<!\.atom)/ }, +        controller: :users) do +    get :calendar +    get :calendar_activities +    get :groups +    get :projects +    get :contributed, as: :contributed_projects +    get :snippets +    get '/', action: :show +  end    #    # Dashboard Area diff --git a/doc/web_hooks/web_hooks.md b/doc/web_hooks/web_hooks.md index c1c51302e79..45506ac1d7c 100644 --- a/doc/web_hooks/web_hooks.md +++ b/doc/web_hooks/web_hooks.md @@ -13,6 +13,19 @@ You can configure webhooks to listen for specific events like pushes, issues or  Webhooks can be used to update an external issue tracker, trigger CI builds, update a backup mirror, or even deploy to your production server. +## Webhook endpoint tips + +If you are writing your own endpoint (web server) that will receive +GitLab webhooks keep in mind the following things: + +-   Your endpoint should send its HTTP response as fast as possible. If +    you wait too long, GitLab may decide the hook failed and retry it. +-   Your endpoint should ALWAYS return a valid HTTP response. If you do +    not do this then GitLab will think the hook failed and retry it. +    Most HTTP libraries take care of this for you automatically but if +    you are writing a low-level hook this is important to remember. +-   GitLab ignores the HTTP status code returned by your endpoint. +  ## SSL Verification  By default, the SSL certificate of the webhook endpoint is verified based on diff --git a/features/steps/project/hooks.rb b/features/steps/project/hooks.rb index b1ffe7f7b4c..13c0713669a 100644 --- a/features/steps/project/hooks.rb +++ b/features/steps/project/hooks.rb @@ -59,7 +59,7 @@ class Spinach::Features::ProjectHooks < Spinach::FeatureSteps    step 'hook should be triggered' do      expect(current_path).to eq namespace_project_hooks_path(current_project.namespace, current_project)      expect(page).to have_selector '.flash-notice', -                              text: 'Hook successfully executed.' +                              text: 'Hook executed successfully: HTTP 200'    end    step 'I should see hook error message' do diff --git a/lib/banzai/filter/wiki_link_filter.rb b/lib/banzai/filter/wiki_link_filter.rb index 06d10c98501..7dc771afd71 100644 --- a/lib/banzai/filter/wiki_link_filter.rb +++ b/lib/banzai/filter/wiki_link_filter.rb @@ -25,7 +25,7 @@ module Banzai        end        def process_link_attr(html_attr) -        return if html_attr.blank? || file_reference?(html_attr) +        return if html_attr.blank? || file_reference?(html_attr) || hierarchical_link?(html_attr)          uri = URI(html_attr.value)          if uri.relative? && uri.path.present? @@ -40,12 +40,17 @@ module Banzai          uri        end +      def project_wiki +        context[:project_wiki] +      end +        def file_reference?(html_attr)          !File.extname(html_attr.value).blank?        end -      def project_wiki -        context[:project_wiki] +      # Of the form `./link`, `../link`, or similar +      def hierarchical_link?(html_attr) +        html_attr.value[0] == '.'        end        def project_wiki_base_path diff --git a/spec/controllers/users_controller_spec.rb b/spec/controllers/users_controller_spec.rb index 8045c8b940d..c61ec174665 100644 --- a/spec/controllers/users_controller_spec.rb +++ b/spec/controllers/users_controller_spec.rb @@ -112,4 +112,26 @@ describe UsersController do        expect(response).to render_template('calendar_activities')      end    end + +  describe 'GET #snippets' do +    before do +      sign_in(user) +    end + +    context 'format html' do +      it 'renders snippets page' do +        get :snippets, username: user.username +        expect(response.status).to eq(200) +        expect(response).to render_template('show') +      end +    end + +    context 'format json' do +      it 'response with snippets json data' do +        get :snippets, username: user.username, format: :json +        expect(response.status).to eq(200) +        expect(JSON.parse(response.body)).to have_key('html') +      end +    end +  end  end diff --git a/spec/lib/banzai/filter/wiki_link_filter_spec.rb b/spec/lib/banzai/filter/wiki_link_filter_spec.rb new file mode 100644 index 00000000000..185abbb2108 --- /dev/null +++ b/spec/lib/banzai/filter/wiki_link_filter_spec.rb @@ -0,0 +1,85 @@ +require 'spec_helper' + +describe Banzai::Filter::WikiLinkFilter, lib: true do +  include FilterSpecHelper + +  let(:namespace) { build_stubbed(:namespace, name: "wiki_link_ns") } +  let(:project)   { build_stubbed(:empty_project, :public, name: "wiki_link_project", namespace: namespace) } +  let(:user) { double } +  let(:project_wiki) { ProjectWiki.new(project, user) } + +  describe "links within the wiki (relative)" do +    describe "hierarchical links to the current directory" do +      it "doesn't rewrite non-file links" do +        link = "<a href='./page'>Link to Page</a>" +        filtered_link = filter(link, project_wiki: project_wiki).children[0] + +        expect(filtered_link.attribute('href').value).to eq('./page') +      end + +      it "doesn't rewrite file links" do +        link = "<a href='./page.md'>Link to Page</a>" +        filtered_link = filter(link, project_wiki: project_wiki).children[0] + +        expect(filtered_link.attribute('href').value).to eq('./page.md') +      end +    end + +    describe "hierarchical links to the parent directory" do +      it "doesn't rewrite non-file links" do +        link = "<a href='../page'>Link to Page</a>" +        filtered_link = filter(link, project_wiki: project_wiki).children[0] + +        expect(filtered_link.attribute('href').value).to eq('../page') +      end + +      it "doesn't rewrite file links" do +        link = "<a href='../page.md'>Link to Page</a>" +        filtered_link = filter(link, project_wiki: project_wiki).children[0] + +        expect(filtered_link.attribute('href').value).to eq('../page.md') +      end +    end + +    describe "hierarchical links to a sub-directory" do +      it "doesn't rewrite non-file links" do +        link = "<a href='./subdirectory/page'>Link to Page</a>" +        filtered_link = filter(link, project_wiki: project_wiki).children[0] + +        expect(filtered_link.attribute('href').value).to eq('./subdirectory/page') +      end + +      it "doesn't rewrite file links" do +        link = "<a href='./subdirectory/page.md'>Link to Page</a>" +        filtered_link = filter(link, project_wiki: project_wiki).children[0] + +        expect(filtered_link.attribute('href').value).to eq('./subdirectory/page.md') +      end +    end + +    describe "non-hierarchical links" do +      it 'rewrites non-file links to be at the scope of the wiki root' do +        link = "<a href='page'>Link to Page</a>" +        filtered_link = filter(link, project_wiki: project_wiki).children[0] + +        expect(filtered_link.attribute('href').value).to match('/wiki_link_ns/wiki_link_project/wikis/page') +      end + +      it "doesn't rewrite file links" do +        link = "<a href='page.md'>Link to Page</a>" +        filtered_link = filter(link, project_wiki: project_wiki).children[0] + +        expect(filtered_link.attribute('href').value).to eq('page.md') +      end +    end +  end + +  describe "links outside the wiki (absolute)" do +    it "doesn't rewrite links" do +      link = "<a href='http://example.com/page'>Link to Page</a>" +      filtered_link = filter(link, project_wiki: project_wiki).children[0] + +      expect(filtered_link.attribute('href').value).to eq('http://example.com/page') +    end +  end +end diff --git a/spec/models/hooks/web_hook_spec.rb b/spec/models/hooks/web_hook_spec.rb index 37a27d73aab..f9bab487b96 100644 --- a/spec/models/hooks/web_hook_spec.rb +++ b/spec/models/hooks/web_hook_spec.rb @@ -95,13 +95,13 @@ describe WebHook, models: true do      it "handles 200 status code" do        WebMock.stub_request(:post, project_hook.url).to_return(status: 200, body: "Success") -      expect(project_hook.execute(@data, 'push_hooks')).to eq([true, 'Success']) +      expect(project_hook.execute(@data, 'push_hooks')).to eq([200, 'Success'])      end      it "handles 2xx status codes" do        WebMock.stub_request(:post, project_hook.url).to_return(status: 201, body: "Success") -      expect(project_hook.execute(@data, 'push_hooks')).to eq([true, 'Success']) +      expect(project_hook.execute(@data, 'push_hooks')).to eq([201, 'Success'])      end    end  end diff --git a/spec/routing/routing_spec.rb b/spec/routing/routing_spec.rb index 1527eddfa48..8530a2f31d5 100644 --- a/spec/routing/routing_spec.rb +++ b/spec/routing/routing_spec.rb @@ -1,5 +1,42 @@  require 'spec_helper' +# user                       GET    /u/:username/ +# user_groups                GET    /u/:username/groups(.:format) +# user_projects              GET    /u/:username/projects(.:format) +# user_contributed_projects  GET    /u/:username/contributed(.:format) +# user_snippets              GET    /u/:username/snippets(.:format) +# user_calendar              GET    /u/:username/calendar(.:format) +# user_calendar_activities   GET    /u/:username/calendar_activities(.:format) +describe UsersController, "routing" do +  it "to #show" do +    expect(get("/u/User")).to route_to('users#show', username: 'User') +  end + +  it "to #groups" do +    expect(get("/u/User/groups")).to route_to('users#groups', username: 'User') +  end + +  it "to #projects" do +    expect(get("/u/User/projects")).to route_to('users#projects', username: 'User') +  end + +  it "to #contributed" do +    expect(get("/u/User/contributed")).to route_to('users#contributed', username: 'User') +  end + +  it "to #snippets" do +    expect(get("/u/User/snippets")).to route_to('users#snippets', username: 'User') +  end + +  it "to #calendar" do +    expect(get("/u/User/calendar")).to route_to('users#calendar', username: 'User') +  end + +  it "to #calendar_activities" do +    expect(get("/u/User/calendar_activities")).to route_to('users#calendar_activities', username: 'User') +  end +end +  # search GET    /search(.:format) search#show  describe SearchController, "routing" do    it "to #show" do @@ -27,10 +64,6 @@ end  #          PUT    /snippets/:id(.:format)      snippets#update  #          DELETE /snippets/:id(.:format)      snippets#destroy  describe SnippetsController, "routing" do -  it "to #user_index" do -    expect(get("/s/User")).to route_to('snippets#index', username: 'User') -  end -    it "to #raw" do      expect(get("/snippets/1/raw")).to route_to('snippets#raw', id: '1')    end | 
