diff options
author | Lin Jen-Shin <godfat@godfat.org> | 2017-05-23 02:10:29 +0800 |
---|---|---|
committer | Lin Jen-Shin <godfat@godfat.org> | 2017-05-23 02:10:29 +0800 |
commit | 1a4130d3a6cfb4956f8bb1186cc499ea549d8e18 (patch) | |
tree | 076adcb3e6f3800a1a7bbc6809839d5cb3b3f372 /app/controllers | |
parent | 3c8a6fba67998eb17240b15db85f8d1c8aff338e (diff) | |
parent | 18a6d9c5326bc2b90a1f0cc8664d638a39885924 (diff) | |
download | gitlab-ce-27377-preload-pipeline-entity.tar.gz |
Merge remote-tracking branch 'upstream/master' into 27377-preload-pipeline-entity27377-preload-pipeline-entity
* upstream/master: (2534 commits)
Update VERSION to 9.3.0-pre
Update CHANGELOG.md for 9.2.0
removes unnecessary redundacy in usage ping doc
Respect the typo as rubocop said
Add a test to ensure this works on MySQL
Change pipelines schedules help page path
change domain to hostname in usage ping doc
Fixes broken MySQL migration for retried
Show password field mask while editing service settings
Add notes for supported schedulers and cloud providers
Move environment monitoring to environments doc
Add docs for change of Cache/Artifact restore order"
Avoid resource intensive login checks if password is not provided
Change translation for 'coding' by 'desarrollo' for Spanish
Add to docs: issues multiple assignees
rename "Add emoji" and "Award emoji" to "Add reaction" where appropriate
Add project and group notification settings info
32570 Fix border-bottom for project activity tab
Add users endpoint to frontend API class
Rename users on mysql
...
Diffstat (limited to 'app/controllers')
104 files changed, 1876 insertions, 1064 deletions
diff --git a/app/controllers/admin/abuse_reports_controller.rb b/app/controllers/admin/abuse_reports_controller.rb index 5055c318a5f..dc9a6df5f75 100644 --- a/app/controllers/admin/abuse_reports_controller.rb +++ b/app/controllers/admin/abuse_reports_controller.rb @@ -1,6 +1,7 @@ class Admin::AbuseReportsController < Admin::ApplicationController def index @abuse_reports = AbuseReport.order(id: :desc).page(params[:page]) + @abuse_reports.includes(:reporter, :user) end def destroy diff --git a/app/controllers/admin/application_controller.rb b/app/controllers/admin/application_controller.rb index cf795d977ce..a4648b33cfa 100644 --- a/app/controllers/admin/application_controller.rb +++ b/app/controllers/admin/application_controller.rb @@ -6,6 +6,6 @@ class Admin::ApplicationController < ApplicationController layout 'admin' def authenticate_admin! - render_404 unless current_user.is_admin? + render_404 unless current_user.admin? end end diff --git a/app/controllers/admin/application_settings_controller.rb b/app/controllers/admin/application_settings_controller.rb index 0bfbe47eb4f..152d7baad49 100644 --- a/app/controllers/admin/application_settings_controller.rb +++ b/app/controllers/admin/application_settings_controller.rb @@ -17,6 +17,18 @@ class Admin::ApplicationSettingsController < Admin::ApplicationController end end + def usage_data + respond_to do |format| + format.html do + usage_data = Gitlab::UsageData.data + usage_data_json = params[:pretty] ? JSON.pretty_generate(usage_data) : usage_data.to_json + + render html: Gitlab::Highlight.highlight('payload.json', usage_data_json) + end + format.json { render json: Gitlab::UsageData.to_json } + end + end + def reset_runners_token @application_setting.reset_runners_registration_token! flash[:notice] = 'New runners registration token has been generated!' @@ -121,6 +133,8 @@ class Admin::ApplicationSettingsController < Admin::ApplicationController :signup_enabled, :sentry_dsn, :sentry_enabled, + :clientside_sentry_dsn, + :clientside_sentry_enabled, :send_user_confirmation_email, :shared_runners_enabled, :shared_runners_text, @@ -134,6 +148,8 @@ class Admin::ApplicationSettingsController < Admin::ApplicationController :unique_ips_limit_enabled, :version_check_enabled, :terminal_max_session_time, + :polling_interval_multiplier, + :usage_ping_enabled, disabled_oauth_sign_in_sources: [], import_sources: [], diff --git a/app/controllers/admin/cohorts_controller.rb b/app/controllers/admin/cohorts_controller.rb new file mode 100644 index 00000000000..9b77c554908 --- /dev/null +++ b/app/controllers/admin/cohorts_controller.rb @@ -0,0 +1,11 @@ +class Admin::CohortsController < Admin::ApplicationController + def index + if current_application_settings.usage_ping_enabled + cohorts_results = Rails.cache.fetch('cohorts', expires_in: 1.day) do + CohortsService.new.execute + end + + @cohorts = CohortsSerializer.new.represent(cohorts_results) + end + end +end diff --git a/app/controllers/admin/groups_controller.rb b/app/controllers/admin/groups_controller.rb index cea3d088e94..5885b3543bb 100644 --- a/app/controllers/admin/groups_controller.rb +++ b/app/controllers/admin/groups_controller.rb @@ -28,7 +28,7 @@ class Admin::GroupsController < Admin::ApplicationController if @group.save @group.add_owner(current_user) - redirect_to [:admin, @group], notice: 'Group was successfully created.' + redirect_to [:admin, @group], notice: "Group '#{@group.name}' was successfully created." else render "new" end @@ -43,9 +43,13 @@ class Admin::GroupsController < Admin::ApplicationController end def members_update - @group.add_users(params[:user_ids].split(','), params[:access_level], current_user: current_user) + status = Members::CreateService.new(@group, current_user, params).execute - redirect_to [:admin, @group], notice: 'Users were successfully added.' + if status + redirect_to [:admin, @group], notice: 'Users were successfully added.' + else + redirect_to [:admin, @group], alert: 'No users specified.' + end end def destroy @@ -72,7 +76,9 @@ class Admin::GroupsController < Admin::ApplicationController :name, :path, :request_access_enabled, - :visibility_level + :visibility_level, + :require_two_factor_authentication, + :two_factor_grace_period ] end end diff --git a/app/controllers/admin/hooks_controller.rb b/app/controllers/admin/hooks_controller.rb index cbfc4581411..ccfe553c89e 100644 --- a/app/controllers/admin/hooks_controller.rb +++ b/app/controllers/admin/hooks_controller.rb @@ -1,4 +1,6 @@ class Admin::HooksController < Admin::ApplicationController + before_action :hook, only: :edit + def index @hooks = SystemHook.all @hook = SystemHook.new @@ -15,15 +17,25 @@ class Admin::HooksController < Admin::ApplicationController end end + def edit + end + + def update + if hook.update_attributes(hook_params) + flash[:notice] = 'System hook was successfully updated.' + redirect_to admin_hooks_path + else + render 'edit' + end + end + def destroy - @hook = SystemHook.find(params[:id]) - @hook.destroy + hook.destroy redirect_to admin_hooks_path end def test - @hook = SystemHook.find(params[:hook_id]) data = { event_name: "project_create", name: "Ruby", @@ -32,16 +44,23 @@ class Admin::HooksController < Admin::ApplicationController owner_name: "Someone", owner_email: "example@gitlabhq.com" } - @hook.execute(data, 'system_hooks') + hook.execute(data, 'system_hooks') redirect_back_or_default end + private + + def hook + @hook ||= SystemHook.find(params[:id]) + end + def hook_params params.require(:hook).permit( :enable_ssl_verification, :push_events, :tag_push_events, + :repository_update_events, :token, :url ) diff --git a/app/controllers/admin/impersonations_controller.rb b/app/controllers/admin/impersonations_controller.rb index 9433da02f64..8e7adc06584 100644 --- a/app/controllers/admin/impersonations_controller.rb +++ b/app/controllers/admin/impersonations_controller.rb @@ -21,6 +21,6 @@ class Admin::ImpersonationsController < Admin::ApplicationController end def authenticate_impersonator! - render_404 unless impersonator && impersonator.is_admin? && !impersonator.blocked? + render_404 unless impersonator && impersonator.admin? && !impersonator.blocked? end end diff --git a/app/controllers/admin/projects_controller.rb b/app/controllers/admin/projects_controller.rb index daecfc832bf..a1975c0e341 100644 --- a/app/controllers/admin/projects_controller.rb +++ b/app/controllers/admin/projects_controller.rb @@ -3,6 +3,7 @@ class Admin::ProjectsController < Admin::ApplicationController before_action :group, only: [:show, :transfer] def index + params[:sort] ||= 'latest_activity_desc' @projects = Project.with_statistics @projects = @projects.in_namespace(params[:namespace_id]) if params[:namespace_id].present? @projects = @projects.where(visibility_level: params[:visibility_level]) if params[:visibility_level].present? diff --git a/app/controllers/admin/services_controller.rb b/app/controllers/admin/services_controller.rb index 37a1a23178e..4c3d336b3af 100644 --- a/app/controllers/admin/services_controller.rb +++ b/app/controllers/admin/services_controller.rb @@ -16,6 +16,8 @@ class Admin::ServicesController < Admin::ApplicationController def update if service.update_attributes(service_params[:service]) + PropagateServiceTemplateWorker.perform_async(service.id) if service.active? + redirect_to admin_application_settings_services_path, notice: 'Application settings saved successfully' else diff --git a/app/controllers/admin/spam_logs_controller.rb b/app/controllers/admin/spam_logs_controller.rb index 2abfa22712d..1d66955bb71 100644 --- a/app/controllers/admin/spam_logs_controller.rb +++ b/app/controllers/admin/spam_logs_controller.rb @@ -7,7 +7,7 @@ class Admin::SpamLogsController < Admin::ApplicationController spam_log = SpamLog.find(params[:id]) if params[:remove_user] - spam_log.remove_user + spam_log.remove_user(deleted_by: current_user) redirect_to admin_spam_logs_path, notice: "User #{spam_log.user.username} was successfully removed." else spam_log.destroy diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb index 6a6e335d314..8ce9150e4a9 100644 --- a/app/controllers/application_controller.rb +++ b/app/controllers/application_controller.rb @@ -8,12 +8,12 @@ class ApplicationController < ActionController::Base include PageLayoutHelper include SentryHelper include WorkhorseHelper + include EnforcesTwoFactorAuthentication before_action :authenticate_user_from_private_token! before_action :authenticate_user! before_action :validate_user_service_ticket! before_action :check_password_expiration - before_action :check_2fa_requirement before_action :ldap_security_check before_action :sentry_context before_action :default_headers @@ -21,6 +21,8 @@ class ApplicationController < ActionController::Base before_action :configure_permitted_parameters, if: :devise_controller? before_action :require_email, unless: :devise_controller? + around_action :set_locale + protect_from_forgery with: :exception helper_method :can?, :current_application_settings @@ -56,7 +58,7 @@ class ApplicationController < ActionController::Base if current_user not_found else - redirect_to new_user_session_path + authenticate_user! end end @@ -98,7 +100,10 @@ class ApplicationController < ActionController::Base end def access_denied! - render "errors/access_denied", layout: "errors", status: 404 + respond_to do |format| + format.json { head :not_found } + format.any { render "errors/access_denied", layout: "errors", status: 404 } + end end def git_not_found! @@ -118,6 +123,10 @@ class ApplicationController < ActionController::Base end end + def respond_422 + head :unprocessable_entity + end + def no_cache_headers response.headers["Cache-Control"] = "no-cache, no-store, max-age=0, must-revalidate" response.headers["Pragma"] = "no-cache" @@ -151,12 +160,6 @@ class ApplicationController < ActionController::Base end end - def check_2fa_requirement - if two_factor_authentication_required? && current_user && !current_user.two_factor_enabled? && !skip_two_factor? - redirect_to profile_two_factor_auth_path - end - end - def ldap_security_check if current_user && current_user.requires_ldap_check? return unless current_user.try_obtain_ldap_lease @@ -265,27 +268,18 @@ class ApplicationController < ActionController::Base current_application_settings.import_sources.include?('gitlab_project') end - def two_factor_authentication_required? - current_application_settings.require_two_factor_authentication - end - - def two_factor_grace_period - current_application_settings.two_factor_grace_period - end - - def two_factor_grace_period_expired? - date = current_user.otp_grace_period_started_at - date && (date + two_factor_grace_period.hours) < Time.current - end - - def skip_two_factor? - session[:skip_tfa] && session[:skip_tfa] > Time.current - end - # U2F (universal 2nd factor) devices need a unique identifier for the application # to perform authentication. # https://developers.yubico.com/U2F/App_ID.html def u2f_app_id request.base_url end + + def set_locale + Gitlab::I18n.set_locale(current_user) + + yield + ensure + Gitlab::I18n.reset_locale + end end diff --git a/app/controllers/autocomplete_controller.rb b/app/controllers/autocomplete_controller.rb index b79ca034c5b..e2f5aa8508e 100644 --- a/app/controllers/autocomplete_controller.rb +++ b/app/controllers/autocomplete_controller.rb @@ -41,7 +41,7 @@ class AutocompleteController < ApplicationController no_project = { id: 0, - name_with_namespace: 'No project', + name_with_namespace: 'No project' } projects.unshift(no_project) unless params[:offset_id].present? diff --git a/app/controllers/concerns/continue_params.rb b/app/controllers/concerns/continue_params.rb index 0a995c45bdf..eb3a623acdd 100644 --- a/app/controllers/concerns/continue_params.rb +++ b/app/controllers/concerns/continue_params.rb @@ -7,6 +7,7 @@ module ContinueParams continue_params = continue_params.permit(:to, :notice, :notice_now) return unless continue_params[:to] && continue_params[:to].start_with?('/') + return if continue_params[:to].start_with?('//') continue_params end diff --git a/app/controllers/concerns/creates_commit.rb b/app/controllers/concerns/creates_commit.rb index 9ac8197e45a..183eb00ef67 100644 --- a/app/controllers/concerns/creates_commit.rb +++ b/app/controllers/concerns/creates_commit.rb @@ -1,17 +1,29 @@ module CreatesCommit extend ActiveSupport::Concern + def set_start_branch_to_branch_name + branch_exists = @repository.find_branch(@branch_name) + @start_branch = @branch_name if branch_exists + end + def create_commit(service, success_path:, failure_path:, failure_view: nil, success_notice: nil) - set_commit_variables + if can?(current_user, :push_code, @project) + @project_to_commit_into = @project + @branch_name ||= @ref + else + @project_to_commit_into = current_user.fork_of(@project) + @branch_name ||= @project_to_commit_into.repository.next_branch('patch') + end + + @start_branch ||= @ref || @branch_name commit_params = @commit_params.merge( - start_project: @mr_target_project, - start_branch: @mr_target_branch, - target_branch: @mr_source_branch + start_project: @project, + start_branch: @start_branch, + branch_name: @branch_name ) - result = service.new( - @mr_source_project, current_user, commit_params).execute + result = service.new(@project_to_commit_into, current_user, commit_params).execute if result[:status] == :success update_flash_notice(success_notice) @@ -72,30 +84,30 @@ module CreatesCommit def new_merge_request_path new_namespace_project_merge_request_path( - @mr_source_project.namespace, - @mr_source_project, + @project_to_commit_into.namespace, + @project_to_commit_into, merge_request: { - source_project_id: @mr_source_project.id, - target_project_id: @mr_target_project.id, - source_branch: @mr_source_branch, - target_branch: @mr_target_branch + source_project_id: @project_to_commit_into.id, + target_project_id: @project.id, + source_branch: @branch_name, + target_branch: @start_branch } ) end def existing_merge_request_path - namespace_project_merge_request_path(@mr_target_project.namespace, @mr_target_project, @merge_request) + namespace_project_merge_request_path(@project.namespace, @project, @merge_request) end def merge_request_exists? return @merge_request if defined?(@merge_request) - @merge_request = MergeRequestsFinder.new(current_user, project_id: @mr_target_project.id).execute.opened. - find_by(source_branch: @mr_source_branch, target_branch: @mr_target_branch, source_project_id: @mr_source_project) + @merge_request = MergeRequestsFinder.new(current_user, project_id: @project.id).execute.opened. + find_by(source_project_id: @project_to_commit_into, source_branch: @branch_name, target_branch: @start_branch) end def different_project? - @mr_source_project != @mr_target_project + @project_to_commit_into != @project end def create_merge_request? @@ -103,22 +115,6 @@ module CreatesCommit # as the target branch in the same project, # we don't want to create a merge request. params[:create_merge_request].present? && - (different_project? || @mr_target_branch != @mr_source_branch) - end - - def set_commit_variables - if can?(current_user, :push_code, @project) - @mr_source_project = @project - @target_branch ||= @ref - else - @mr_source_project = current_user.fork_of(@project) - @target_branch ||= @mr_source_project.repository.next_branch('patch') - end - - # Merge request to this project - @mr_target_project = @project - @mr_target_branch ||= @ref || @target_branch - - @mr_source_branch = @target_branch + (different_project? || @start_branch != @branch_name) end end diff --git a/app/controllers/concerns/enforces_two_factor_authentication.rb b/app/controllers/concerns/enforces_two_factor_authentication.rb new file mode 100644 index 00000000000..688e8bd4a37 --- /dev/null +++ b/app/controllers/concerns/enforces_two_factor_authentication.rb @@ -0,0 +1,58 @@ +# == EnforcesTwoFactorAuthentication +# +# Controller concern to enforce two-factor authentication requirements +# +# Upon inclusion, adds `check_two_factor_requirement` as a before_action, +# and makes `two_factor_grace_period_expired?` and `two_factor_skippable?` +# available as view helpers. +module EnforcesTwoFactorAuthentication + extend ActiveSupport::Concern + + included do + before_action :check_two_factor_requirement + helper_method :two_factor_grace_period_expired?, :two_factor_skippable? + end + + def check_two_factor_requirement + if two_factor_authentication_required? && current_user && !current_user.two_factor_enabled? && !skip_two_factor? + redirect_to profile_two_factor_auth_path + end + end + + def two_factor_authentication_required? + current_application_settings.require_two_factor_authentication? || + current_user.try(:require_two_factor_authentication_from_group?) + end + + def two_factor_authentication_reason(global: -> {}, group: -> {}) + if two_factor_authentication_required? + if current_application_settings.require_two_factor_authentication? + global.call + else + groups = current_user.expanded_groups_requiring_two_factor_authentication.reorder(name: :asc) + group.call(groups) + end + end + end + + def two_factor_grace_period + periods = [current_application_settings.two_factor_grace_period] + periods << current_user.two_factor_grace_period if current_user.try(:require_two_factor_authentication_from_group?) + periods.min + end + + def two_factor_grace_period_expired? + date = current_user.otp_grace_period_started_at + date && (date + two_factor_grace_period.hours) < Time.current + end + + def two_factor_skippable? + two_factor_authentication_required? && + !current_user.two_factor_enabled? && + !two_factor_grace_period_expired? + end + + def skip_two_factor? + session[:skip_two_factor] && session[:skip_two_factor] > Time.current + end +end diff --git a/app/controllers/concerns/filter_projects.rb b/app/controllers/concerns/filter_projects.rb deleted file mode 100644 index 6014112256a..00000000000 --- a/app/controllers/concerns/filter_projects.rb +++ /dev/null @@ -1,17 +0,0 @@ -# == FilterProjects -# -# Controller concern to handle projects filtering -# * by name -# * by archived state -# -module FilterProjects - extend ActiveSupport::Concern - - def filter_projects(projects) - projects = projects.search(params[:name]) if params[:name].present? - projects = projects.non_archived if params[:archived].blank? - projects = projects.personal(current_user) if params[:personal].present? && current_user - - projects - end -end diff --git a/app/controllers/concerns/issuable_actions.rb b/app/controllers/concerns/issuable_actions.rb index 3ccf2a9ce33..4cf645d6341 100644 --- a/app/controllers/concerns/issuable_actions.rb +++ b/app/controllers/concerns/issuable_actions.rb @@ -60,7 +60,7 @@ module IssuableActions end def bulk_update_params - params.require(:update).permit( + permitted_keys = [ :issuable_ids, :assignee_id, :milestone_id, @@ -69,7 +69,15 @@ module IssuableActions label_ids: [], add_label_ids: [], remove_label_ids: [] - ) + ] + + if resource_name == 'issue' + permitted_keys << { assignee_ids: [] } + else + permitted_keys.unshift(:assignee_id) + end + + params.require(:update).permit(permitted_keys) end def resource_name diff --git a/app/controllers/concerns/issuable_collections.rb b/app/controllers/concerns/issuable_collections.rb index 85ae4985e58..650ec1e326a 100644 --- a/app/controllers/concerns/issuable_collections.rb +++ b/app/controllers/concerns/issuable_collections.rb @@ -15,6 +15,9 @@ module IssuableCollections # a new order into the collection. # We cannot use reorder to not mess up the paginated collection. issuable_ids = issuable_collection.map(&:id) + + return {} if issuable_ids.empty? + issuable_note_count = Note.count_for_collection(issuable_ids, @collection_type) issuable_votes_count = AwardEmoji.votes_for_collection(issuable_ids, @collection_type) issuable_merge_requests_count = @@ -40,11 +43,11 @@ module IssuableCollections end def issues_collection - issues_finder.execute.preload(:project, :author, :assignee, :labels, :milestone, project: :namespace) + issues_finder.execute.preload(:project, :author, :assignees, :labels, :milestone, project: :namespace) end def merge_requests_collection - merge_requests_finder.execute.preload(:source_project, :target_project, :author, :assignee, :labels, :milestone, :merge_request_diff, target_project: :namespace) + merge_requests_finder.execute.preload(:source_project, :target_project, :author, :assignee, :labels, :milestone, :merge_request_diff, :head_pipeline, target_project: :namespace) end def issues_finder diff --git a/app/controllers/concerns/lfs_request.rb b/app/controllers/concerns/lfs_request.rb index ed22b1e5470..ae91e02488a 100644 --- a/app/controllers/concerns/lfs_request.rb +++ b/app/controllers/concerns/lfs_request.rb @@ -23,7 +23,7 @@ module LfsRequest render( json: { message: 'Git LFS is not enabled on this GitLab server, contact your admin.', - documentation_url: help_url, + documentation_url: help_url }, status: 501 ) @@ -48,7 +48,7 @@ module LfsRequest render( json: { message: 'Access forbidden. Check your access level.', - documentation_url: help_url, + documentation_url: help_url }, content_type: "application/vnd.git-lfs+json", status: 403 @@ -59,7 +59,7 @@ module LfsRequest render( json: { message: 'Not found.', - documentation_url: help_url, + documentation_url: help_url }, content_type: "application/vnd.git-lfs+json", status: 404 diff --git a/app/controllers/concerns/membership_actions.rb b/app/controllers/concerns/membership_actions.rb index c13333641d3..b1bacc8ffe5 100644 --- a/app/controllers/concerns/membership_actions.rb +++ b/app/controllers/concerns/membership_actions.rb @@ -1,6 +1,32 @@ module MembershipActions extend ActiveSupport::Concern + def create + status = Members::CreateService.new(membershipable, current_user, params).execute + + redirect_url = members_page_url + + if status + redirect_to redirect_url, notice: 'Users were successfully added.' + else + redirect_to redirect_url, alert: 'No users specified.' + end + end + + def destroy + Members::DestroyService.new(membershipable, current_user, params). + execute(:all) + + respond_to do |format| + format.html do + message = "User was successfully removed from #{source_type}." + redirect_to members_page_url, notice: message + end + + format.js { head :ok } + end + end + def request_access membershipable.request_access(current_user) @@ -11,20 +37,20 @@ module MembershipActions def approve_access_request Members::ApproveAccessRequestService.new(membershipable, current_user, params).execute - redirect_to polymorphic_url([membershipable, :members]) + redirect_to members_page_url end def leave member = Members::DestroyService.new(membershipable, current_user, user_id: current_user.id). execute(:all) - source_type = membershipable.class.to_s.humanize(capitalize: false) notice = if member.request? "Your access request to the #{source_type} has been withdrawn." else "You left the \"#{membershipable.human_name}\" #{source_type}." end + redirect_path = member.request? ? member.source : [:dashboard, membershipable.class.to_s.tableize] redirect_to redirect_path, notice: notice @@ -35,4 +61,16 @@ module MembershipActions def membershipable raise NotImplementedError end + + def members_page_url + if membershipable.is_a?(Project) + project_settings_members_path(membershipable) + else + polymorphic_url([membershipable, :members]) + end + end + + def source_type + @source_type ||= membershipable.class.to_s.humanize(capitalize: false) + end end diff --git a/app/controllers/concerns/milestone_actions.rb b/app/controllers/concerns/milestone_actions.rb new file mode 100644 index 00000000000..3e2a0fe4f8b --- /dev/null +++ b/app/controllers/concerns/milestone_actions.rb @@ -0,0 +1,53 @@ +module MilestoneActions + extend ActiveSupport::Concern + + def merge_requests + respond_to do |format| + format.html { redirect_to milestone_redirect_path } + format.json do + render json: tabs_json("shared/milestones/_merge_requests_tab", { + merge_requests: @milestone.merge_requests, + show_project_name: true + }) + end + end + end + + def participants + respond_to do |format| + format.html { redirect_to milestone_redirect_path } + format.json do + render json: tabs_json("shared/milestones/_participants_tab", { + users: @milestone.participants + }) + end + end + end + + def labels + respond_to do |format| + format.html { redirect_to milestone_redirect_path } + format.json do + render json: tabs_json("shared/milestones/_labels_tab", { + labels: @milestone.labels + }) + end + end + end + + private + + def tabs_json(partial, data = {}) + { + html: view_to_html_string(partial, data) + } + end + + def milestone_redirect_path + if @project + namespace_project_milestone_path(@project.namespace, @project, @milestone) + else + group_milestone_path(@group, @milestone.safe_title, title: @milestone.title) + end + end +end diff --git a/app/controllers/concerns/notes_actions.rb b/app/controllers/concerns/notes_actions.rb new file mode 100644 index 00000000000..a57d9e6e6c0 --- /dev/null +++ b/app/controllers/concerns/notes_actions.rb @@ -0,0 +1,180 @@ +module NotesActions + include RendersNotes + extend ActiveSupport::Concern + + included do + before_action :authorize_admin_note!, only: [:update, :destroy] + end + + def index + current_fetched_at = Time.now.to_i + + notes_json = { notes: [], last_fetched_at: current_fetched_at } + + @notes = notes_finder.execute.inc_relations_for_view + @notes = prepare_notes_for_rendering(@notes) + + @notes.each do |note| + next if note.cross_reference_not_visible_for?(current_user) + + notes_json[:notes] << note_json(note) + end + + render json: notes_json + end + + def create + create_params = note_params.merge( + merge_request_diff_head_sha: params[:merge_request_diff_head_sha], + in_reply_to_discussion_id: params[:in_reply_to_discussion_id] + ) + @note = Notes::CreateService.new(project, current_user, create_params).execute + + if @note.is_a?(Note) + Banzai::NoteRenderer.render([@note], @project, current_user) + end + + respond_to do |format| + format.json { render json: note_json(@note) } + format.html { redirect_back_or_default } + end + end + + def update + @note = Notes::UpdateService.new(project, current_user, note_params).execute(note) + + if @note.is_a?(Note) + Banzai::NoteRenderer.render([@note], @project, current_user) + end + + respond_to do |format| + format.json { render json: note_json(@note) } + format.html { redirect_back_or_default } + end + end + + def destroy + if note.editable? + Notes::DestroyService.new(project, current_user).execute(note) + end + + respond_to do |format| + format.js { head :ok } + end + end + + private + + def note_html(note) + render_to_string( + "shared/notes/_note", + layout: false, + formats: [:html], + locals: { note: note } + ) + end + + def note_json(note) + attrs = { + commands_changes: note.commands_changes + } + + if note.persisted? + attrs.merge!( + valid: true, + id: note.id, + discussion_id: note.discussion_id(noteable), + html: note_html(note), + note: note.note + ) + + discussion = note.to_discussion(noteable) + unless discussion.individual_note? + attrs.merge!( + discussion_resolvable: discussion.resolvable?, + + diff_discussion_html: diff_discussion_html(discussion), + discussion_html: discussion_html(discussion) + ) + end + else + attrs.merge!( + valid: false, + errors: note.errors + ) + end + + attrs + end + + def diff_discussion_html(discussion) + return unless discussion.diff_discussion? + + if params[:view] == 'parallel' + template = "discussions/_parallel_diff_discussion" + locals = + if params[:line_type] == 'old' + { discussions_left: [discussion], discussions_right: nil } + else + { discussions_left: nil, discussions_right: [discussion] } + end + else + template = "discussions/_diff_discussion" + locals = { discussions: [discussion] } + end + + render_to_string( + template, + layout: false, + formats: [:html], + locals: locals + ) + end + + def discussion_html(discussion) + return if discussion.individual_note? + + render_to_string( + "discussions/_discussion", + layout: false, + formats: [:html], + locals: { discussion: discussion } + ) + end + + def authorize_admin_note! + return access_denied! unless can?(current_user, :admin_note, note) + end + + def note_params + params.require(:note).permit( + :project_id, + :noteable_type, + :noteable_id, + :commit_id, + :noteable, + :type, + + :note, + :attachment, + + # LegacyDiffNote + :line_code, + + # DiffNote + :position + ) + end + + def noteable + @noteable ||= notes_finder.target + end + + def last_fetched_at + request.headers['X-Last-Fetched-At'] + end + + def notes_finder + @notes_finder ||= NotesFinder.new(project, current_user, finder_params) + end +end diff --git a/app/controllers/concerns/params_backward_compatibility.rb b/app/controllers/concerns/params_backward_compatibility.rb new file mode 100644 index 00000000000..b0e3d9c7b34 --- /dev/null +++ b/app/controllers/concerns/params_backward_compatibility.rb @@ -0,0 +1,7 @@ +module ParamsBackwardCompatibility + private + + def set_non_archived_param + params[:non_archived] = params[:archived].blank? + end +end diff --git a/app/controllers/concerns/renders_blob.rb b/app/controllers/concerns/renders_blob.rb new file mode 100644 index 00000000000..1d37e4cb3bd --- /dev/null +++ b/app/controllers/concerns/renders_blob.rb @@ -0,0 +1,24 @@ +module RendersBlob + extend ActiveSupport::Concern + + def render_blob_json(blob) + viewer = + case params[:viewer] + when 'rich' + blob.rich_viewer + when 'auxiliary' + blob.auxiliary_viewer + else + blob.simple_viewer + end + return render_404 unless viewer + + render json: { + html: view_to_html_string("projects/blob/_viewer", viewer: viewer, load_async: false) + } + end + + def override_max_blob_size(blob) + blob.override_max_size! if params[:override_max_size] == 'true' + end +end diff --git a/app/controllers/concerns/renders_notes.rb b/app/controllers/concerns/renders_notes.rb new file mode 100644 index 00000000000..41c3114ad1e --- /dev/null +++ b/app/controllers/concerns/renders_notes.rb @@ -0,0 +1,22 @@ +module RendersNotes + def prepare_notes_for_rendering(notes) + preload_noteable_for_regular_notes(notes) + preload_max_access_for_authors(notes, @project) + Banzai::NoteRenderer.render(notes, @project, current_user) + + notes + end + + private + + def preload_max_access_for_authors(notes, project) + return nil unless project + + user_ids = notes.map(&:author_id) + project.team.max_member_access_for_user_ids(user_ids) + end + + def preload_noteable_for_regular_notes(notes) + ActiveRecord::Associations::Preloader.new.preload(notes.reject(&:for_commit?), :noteable) + end +end diff --git a/app/controllers/concerns/requires_health_token.rb b/app/controllers/concerns/requires_health_token.rb new file mode 100644 index 00000000000..34ab1a97649 --- /dev/null +++ b/app/controllers/concerns/requires_health_token.rb @@ -0,0 +1,25 @@ +module RequiresHealthToken + extend ActiveSupport::Concern + included do + before_action :validate_health_check_access! + end + + private + + def validate_health_check_access! + render_404 unless token_valid? + end + + def token_valid? + token = params[:token].presence || request.headers['TOKEN'] + token.present? && + ActiveSupport::SecurityUtils.variable_size_secure_compare( + token, + current_application_settings.health_check_access_token + ) + end + + def render_404 + render file: Rails.root.join('public', '404'), layout: false, status: '404' + end +end diff --git a/app/controllers/concerns/routable_actions.rb b/app/controllers/concerns/routable_actions.rb new file mode 100644 index 00000000000..4199da9cdf5 --- /dev/null +++ b/app/controllers/concerns/routable_actions.rb @@ -0,0 +1,38 @@ +module RoutableActions + extend ActiveSupport::Concern + + def find_routable!(routable_klass, requested_full_path, extra_authorization_proc: nil) + routable = routable_klass.find_by_full_path(requested_full_path, follow_redirects: request.get?) + + if routable_authorized?(routable, extra_authorization_proc) + ensure_canonical_path(routable, requested_full_path) + routable + else + route_not_found + nil + end + end + + def routable_authorized?(routable, extra_authorization_proc) + action = :"read_#{routable.class.to_s.underscore}" + return false unless can?(current_user, action, routable) + + if extra_authorization_proc + extra_authorization_proc.call(routable) + else + true + end + end + + def ensure_canonical_path(routable, requested_full_path) + return unless request.get? + + canonical_path = routable.full_path + if canonical_path != requested_full_path + if canonical_path.casecmp(requested_full_path) != 0 + flash[:notice] = "#{routable.class.to_s.titleize} '#{requested_full_path}' was moved to '#{canonical_path}'. Please update any links and bookmarks that may still have the old path." + end + redirect_to build_canonical_path(routable) + end + end +end diff --git a/app/controllers/concerns/service_params.rb b/app/controllers/concerns/service_params.rb index a8c0937569c..be2e6c7f193 100644 --- a/app/controllers/concerns/service_params.rb +++ b/app/controllers/concerns/service_params.rb @@ -38,6 +38,7 @@ module ServiceParams :new_issue_url, :notify, :notify_only_broken_pipelines, + :notify_only_default_branch, :password, :priority, :project_key, diff --git a/app/controllers/concerns/snippets_actions.rb b/app/controllers/concerns/snippets_actions.rb index ca6dffe1cc5..ffea712a833 100644 --- a/app/controllers/concerns/snippets_actions.rb +++ b/app/controllers/concerns/snippets_actions.rb @@ -5,10 +5,12 @@ module SnippetsActions end def raw + disposition = params[:inline] == 'false' ? 'attachment' : 'inline' + send_data( convert_line_endings(@snippet.content), type: 'text/plain; charset=utf-8', - disposition: 'inline', + disposition: disposition, filename: @snippet.sanitized_file_name ) end diff --git a/app/controllers/concerns/toggle_award_emoji.rb b/app/controllers/concerns/toggle_award_emoji.rb index fbf9a026b10..ba5b7d33f87 100644 --- a/app/controllers/concerns/toggle_award_emoji.rb +++ b/app/controllers/concerns/toggle_award_emoji.rb @@ -22,7 +22,8 @@ module ToggleAwardEmoji def to_todoable(awardable) case awardable when Note - awardable.noteable + # we don't create todos for personal snippet comments for now + awardable.for_personal_snippet? ? nil : awardable.noteable when MergeRequest, Issue awardable when Snippet diff --git a/app/controllers/concerns/uploads_actions.rb b/app/controllers/concerns/uploads_actions.rb new file mode 100644 index 00000000000..dec2e27335a --- /dev/null +++ b/app/controllers/concerns/uploads_actions.rb @@ -0,0 +1,27 @@ +module UploadsActions + def create + link_to_file = UploadService.new(model, params[:file], uploader_class).execute + + respond_to do |format| + if link_to_file + format.json do + render json: { link: link_to_file } + end + else + format.json do + render json: 'Invalid file.', status: :unprocessable_entity + end + end + end + end + + def show + return render_404 unless uploader.exists? + + disposition = uploader.image_or_video? ? 'inline' : 'attachment' + + expires_in 0.seconds, must_revalidate: true, private: true + + send_file uploader.file.path, disposition: disposition + end +end diff --git a/app/controllers/dashboard/labels_controller.rb b/app/controllers/dashboard/labels_controller.rb index d5031da867a..dd1d46a68c7 100644 --- a/app/controllers/dashboard/labels_controller.rb +++ b/app/controllers/dashboard/labels_controller.rb @@ -3,7 +3,7 @@ class Dashboard::LabelsController < Dashboard::ApplicationController labels = LabelsFinder.new(current_user).execute respond_to do |format| - format.json { render json: labels.as_json(only: [:id, :title, :color]) } + format.json { render json: LabelSerializer.new.represent_appearance(labels) } end end end diff --git a/app/controllers/dashboard/projects_controller.rb b/app/controllers/dashboard/projects_controller.rb index be00d765f73..5a1efcab1a3 100644 --- a/app/controllers/dashboard/projects_controller.rb +++ b/app/controllers/dashboard/projects_controller.rb @@ -1,10 +1,11 @@ class Dashboard::ProjectsController < Dashboard::ApplicationController - include FilterProjects + include ParamsBackwardCompatibility + + before_action :set_non_archived_param + before_action :default_sorting def index - @projects = load_projects(current_user.authorized_projects) - @projects = @projects.sort(@sort = params[:sort]) - @projects = @projects.page(params[:page]) + @projects = load_projects(params.merge(non_public: true)).page(params[:page]) respond_to do |format| format.html { @last_push = current_user.recent_push } @@ -21,10 +22,8 @@ class Dashboard::ProjectsController < Dashboard::ApplicationController end def starred - @projects = load_projects(current_user.viewable_starred_projects) - @projects = @projects.includes(:forked_from_project, :tags) - @projects = @projects.sort(@sort = params[:sort]) - @projects = @projects.page(params[:page]) + @projects = load_projects(params.merge(starred: true)). + includes(:forked_from_project, :tags).page(params[:page]) @last_push = current_user.recent_push @groups = [] @@ -41,14 +40,18 @@ class Dashboard::ProjectsController < Dashboard::ApplicationController private - def load_projects(base_scope) - projects = base_scope.sorted_by_activity.includes(:route, namespace: :route) + def default_sorting + params[:sort] ||= 'latest_activity_desc' + @sort = params[:sort] + end - filter_projects(projects) + def load_projects(finder_params) + ProjectsFinder.new(params: finder_params, current_user: current_user). + execute.includes(:route, namespace: :route) end def load_events - @events = Event.in_projects(load_projects(current_user.authorized_projects)) + @events = Event.in_projects(load_projects(params.merge(non_public: true))) @events = event_filter.apply_filter(@events).with_associations @events = @events.limit(20).offset(params[:offset] || 0) end diff --git a/app/controllers/dashboard/snippets_controller.rb b/app/controllers/dashboard/snippets_controller.rb index bcfdbe14be9..8dd91264451 100644 --- a/app/controllers/dashboard/snippets_controller.rb +++ b/app/controllers/dashboard/snippets_controller.rb @@ -1,11 +1,10 @@ class Dashboard::SnippetsController < Dashboard::ApplicationController def index - @snippets = SnippetsFinder.new.execute( + @snippets = SnippetsFinder.new( current_user, - filter: :by_user, - user: current_user, + author: current_user, scope: params[:scope] - ) + ).execute @snippets = @snippets.page(params[:page]) end end diff --git a/app/controllers/dashboard/todos_controller.rb b/app/controllers/dashboard/todos_controller.rb index 498690e8f11..4d7d45787fc 100644 --- a/app/controllers/dashboard/todos_controller.rb +++ b/app/controllers/dashboard/todos_controller.rb @@ -7,7 +7,7 @@ class Dashboard::TodosController < Dashboard::ApplicationController @sort = params[:sort] @todos = @todos.page(params[:page]) if @todos.out_of_range? && @todos.total_pages != 0 - redirect_to url_for(params.merge(page: @todos.total_pages)) + redirect_to url_for(params.merge(page: @todos.total_pages, only_path: true)) end end diff --git a/app/controllers/explore/groups_controller.rb b/app/controllers/explore/groups_controller.rb index 68228c095da..81883c543ba 100644 --- a/app/controllers/explore/groups_controller.rb +++ b/app/controllers/explore/groups_controller.rb @@ -1,6 +1,6 @@ class Explore::GroupsController < Explore::ApplicationController def index - @groups = GroupsFinder.new.execute(current_user) + @groups = GroupsFinder.new(current_user).execute @groups = @groups.search(params[:filter_groups]) if params[:filter_groups].present? @groups = @groups.sort(@sort = params[:sort]) @groups = @groups.page(params[:page]) diff --git a/app/controllers/explore/projects_controller.rb b/app/controllers/explore/projects_controller.rb index 6167f9bd335..8f1870759e4 100644 --- a/app/controllers/explore/projects_controller.rb +++ b/app/controllers/explore/projects_controller.rb @@ -1,14 +1,12 @@ class Explore::ProjectsController < Explore::ApplicationController - include FilterProjects + include ParamsBackwardCompatibility + + before_action :set_non_archived_param def index - @projects = load_projects - @tags = @projects.tags_on(:tags) - @projects = @projects.tagged_with(params[:tag]) if params[:tag].present? - @projects = @projects.where(visibility_level: params[:visibility_level]) if params[:visibility_level].present? - @projects = filter_projects(@projects) - @projects = @projects.sort(@sort = params[:sort]) - @projects = @projects.includes(:namespace).page(params[:page]) + params[:sort] ||= 'latest_activity_desc' + @sort = params[:sort] + @projects = load_projects.page(params[:page]) respond_to do |format| format.html @@ -21,10 +19,9 @@ class Explore::ProjectsController < Explore::ApplicationController end def trending - @projects = load_projects(Project.trending) - @projects = filter_projects(@projects) - @projects = @projects.sort(@sort = params[:sort]) - @projects = @projects.page(params[:page]) + params[:trending] = true + @sort = params[:sort] + @projects = load_projects.page(params[:page]) respond_to do |format| format.html @@ -37,10 +34,7 @@ class Explore::ProjectsController < Explore::ApplicationController end def starred - @projects = load_projects - @projects = filter_projects(@projects) - @projects = @projects.reorder('star_count DESC') - @projects = @projects.page(params[:page]) + @projects = load_projects.reorder('star_count DESC').page(params[:page]) respond_to do |format| format.html @@ -52,10 +46,10 @@ class Explore::ProjectsController < Explore::ApplicationController end end - protected + private - def load_projects(base_scope = nil) - base_scope ||= ProjectsFinder.new.execute(current_user) - base_scope.includes(:route, namespace: :route) + def load_projects + ProjectsFinder.new(current_user: current_user, params: params). + execute.includes(:route, namespace: :route) end end diff --git a/app/controllers/explore/snippets_controller.rb b/app/controllers/explore/snippets_controller.rb index 28760c3f84b..d3f0e033068 100644 --- a/app/controllers/explore/snippets_controller.rb +++ b/app/controllers/explore/snippets_controller.rb @@ -1,6 +1,6 @@ class Explore::SnippetsController < Explore::ApplicationController def index - @snippets = SnippetsFinder.new.execute(current_user, filter: :all) + @snippets = SnippetsFinder.new(current_user).execute @snippets = @snippets.page(params[:page]) end end diff --git a/app/controllers/groups/application_controller.rb b/app/controllers/groups/application_controller.rb index c411c21bb80..c0ac47e363d 100644 --- a/app/controllers/groups/application_controller.rb +++ b/app/controllers/groups/application_controller.rb @@ -1,4 +1,6 @@ class Groups::ApplicationController < ApplicationController + include RoutableActions + layout 'group' skip_before_action :authenticate_user! @@ -7,26 +9,15 @@ class Groups::ApplicationController < ApplicationController private def group - unless @group - id = params[:group_id] || params[:id] - @group = Group.find_by_full_path(id) - - unless @group && can?(current_user, :read_group, @group) - @group = nil - - if current_user.nil? - authenticate_user! - else - render_404 - end - end - end - - @group + @group ||= find_routable!(Group, params[:group_id] || params[:id]) end def group_projects - @projects ||= GroupProjectsFinder.new(group).execute(current_user) + @projects ||= GroupProjectsFinder.new(group: group, current_user: current_user).execute + end + + def group_merge_requests + @group_merge_requests = MergeRequestsFinder.new(current_user, group_id: @group.id).execute end def authorize_admin_group! @@ -40,4 +31,10 @@ class Groups::ApplicationController < ApplicationController return render_403 end end + + def build_canonical_path(group) + params[:group_id] = group.to_param + + url_for(params) + end end diff --git a/app/controllers/groups/group_members_controller.rb b/app/controllers/groups/group_members_controller.rb index 0cbf3eb58a3..8fc234a62b1 100644 --- a/app/controllers/groups/group_members_controller.rb +++ b/app/controllers/groups/group_members_controller.rb @@ -14,27 +14,13 @@ class Groups::GroupMembersController < Groups::ApplicationController @members = @members.search(params[:search]) if params[:search].present? @members = @members.sort(@sort) @members = @members.page(params[:page]).per(50) + @members.includes(:user) @requesters = AccessRequestsFinder.new(@group).execute(current_user) @group_member = @group.group_members.new end - def create - if params[:user_ids].blank? - return redirect_to(group_group_members_path(@group), alert: 'No users specified.') - end - - @group.add_users( - params[:user_ids].split(','), - params[:access_level], - current_user: current_user, - expires_at: params[:expires_at] - ) - - redirect_to group_group_members_path(@group), notice: 'Users were successfully added.' - end - def update @group_member = @group.group_members.find(params[:id]) @@ -43,15 +29,6 @@ class Groups::GroupMembersController < Groups::ApplicationController @group_member.update_attributes(member_params) end - def destroy - Members::DestroyService.new(@group, current_user, id: params[:id]).execute(:all) - - respond_to do |format| - format.html { redirect_to group_group_members_path(@group), notice: 'User was successfully removed from group.' } - format.js { head :ok } - end - end - def resend_invite redirect_path = group_group_members_path(@group) diff --git a/app/controllers/groups/labels_controller.rb b/app/controllers/groups/labels_controller.rb index facb25525b5..3fa0516fb0c 100644 --- a/app/controllers/groups/labels_controller.rb +++ b/app/controllers/groups/labels_controller.rb @@ -15,7 +15,7 @@ class Groups::LabelsController < Groups::ApplicationController format.json do available_labels = LabelsFinder.new(current_user, group_id: @group.id).execute - render json: available_labels.as_json(only: [:id, :title, :color]) + render json: LabelSerializer.new.represent_appearance(available_labels) end end end diff --git a/app/controllers/groups/milestones_controller.rb b/app/controllers/groups/milestones_controller.rb index 43102596201..e52fa766044 100644 --- a/app/controllers/groups/milestones_controller.rb +++ b/app/controllers/groups/milestones_controller.rb @@ -1,6 +1,8 @@ class Groups::MilestonesController < Groups::ApplicationController + include MilestoneActions + before_action :group_projects - before_action :milestone, only: [:show, :update] + before_action :milestone, only: [:show, :update, :merge_requests, :participants, :labels] before_action :authorize_admin_milestones!, only: [:new, :create, :update] def index diff --git a/app/controllers/groups_controller.rb b/app/controllers/groups_controller.rb index 05f9ee1ee90..965ced4d372 100644 --- a/app/controllers/groups_controller.rb +++ b/app/controllers/groups_controller.rb @@ -1,7 +1,7 @@ class GroupsController < Groups::ApplicationController - include FilterProjects include IssuesAction include MergeRequestsAction + include ParamsBackwardCompatibility respond_to :html @@ -12,8 +12,8 @@ class GroupsController < Groups::ApplicationController before_action :authorize_admin_group!, only: [:edit, :update, :destroy, :projects] before_action :authorize_create_group!, only: [:new, :create] - # Load group projects before_action :group_projects, only: [:projects, :activity, :issues, :merge_requests] + before_action :group_merge_requests, only: [:merge_requests] before_action :event_filter, only: [:activity] before_action :user_actions, only: [:show, :subgroups] @@ -64,7 +64,7 @@ class GroupsController < Groups::ApplicationController end def subgroups - @nested_groups = group.children + @nested_groups = GroupsFinder.new(current_user, parent: group).execute @nested_groups = @nested_groups.search(params[:filter_groups]) if params[:filter_groups].present? end @@ -105,15 +105,16 @@ class GroupsController < Groups::ApplicationController protected def setup_projects + set_non_archived_param + params[:sort] ||= 'latest_activity_desc' + @sort = params[:sort] + options = {} options[:only_owned] = true if params[:shared] == '0' options[:only_shared] = true if params[:shared] == '1' - @projects = GroupProjectsFinder.new(group, options).execute(current_user) + @projects = GroupProjectsFinder.new(params: params, group: group, options: options, current_user: current_user).execute @projects = @projects.includes(:namespace) - @projects = @projects.sorted_by_activity - @projects = filter_projects(@projects) - @projects = @projects.sort(@sort = params[:sort]) @projects = @projects.page(params[:page]) if params[:name].blank? end @@ -150,7 +151,9 @@ class GroupsController < Groups::ApplicationController :visibility_level, :parent_id, :create_chat_team, - :chat_team_name + :chat_team_name, + :require_two_factor_authentication, + :two_factor_grace_period ] end @@ -166,4 +169,12 @@ class GroupsController < Groups::ApplicationController @notification_setting = current_user.notification_settings_for(group) end end + + def build_canonical_path(group) + return group_path(group) if action_name == 'show' # root group path + + params[:id] = group.to_param + + url_for(params) + end end diff --git a/app/controllers/health_check_controller.rb b/app/controllers/health_check_controller.rb index 037da7d2bce..5d3109b7187 100644 --- a/app/controllers/health_check_controller.rb +++ b/app/controllers/health_check_controller.rb @@ -1,22 +1,3 @@ class HealthCheckController < HealthCheck::HealthCheckController - before_action :validate_health_check_access! - - private - - def validate_health_check_access! - render_404 unless token_valid? - end - - def token_valid? - token = params[:token].presence || request.headers['TOKEN'] - token.present? && - ActiveSupport::SecurityUtils.variable_size_secure_compare( - token, - current_application_settings.health_check_access_token - ) - end - - def render_404 - render file: Rails.root.join('public', '404'), layout: false, status: '404' - end + include RequiresHealthToken end diff --git a/app/controllers/health_controller.rb b/app/controllers/health_controller.rb new file mode 100644 index 00000000000..125746d0426 --- /dev/null +++ b/app/controllers/health_controller.rb @@ -0,0 +1,60 @@ +class HealthController < ActionController::Base + protect_from_forgery with: :exception + include RequiresHealthToken + + CHECKS = [ + Gitlab::HealthChecks::DbCheck, + Gitlab::HealthChecks::RedisCheck, + Gitlab::HealthChecks::FsShardsCheck + ].freeze + + def readiness + results = CHECKS.map { |check| [check.name, check.readiness] } + + render_check_results(results) + end + + def liveness + results = CHECKS.map { |check| [check.name, check.liveness] } + + render_check_results(results) + end + + def metrics + results = CHECKS.flat_map(&:metrics) + + response = results.map(&method(:metric_to_prom_line)).join("\n") + + render text: response, content_type: 'text/plain; version=0.0.4' + end + + private + + def metric_to_prom_line(metric) + labels = metric.labels&.map { |key, value| "#{key}=\"#{value}\"" }&.join(',') || '' + if labels.empty? + "#{metric.name} #{metric.value}" + else + "#{metric.name}{#{labels}} #{metric.value}" + end + end + + def render_check_results(results) + flattened = results.flat_map do |name, result| + if result.is_a?(Gitlab::HealthChecks::Result) + [[name, result]] + else + result.map { |r| [name, r] } + end + end + success = flattened.all? { |name, r| r.success } + + response = flattened.map do |name, r| + info = { status: r.success ? 'ok' : 'failed' } + info['message'] = r.message if r.message + info[:labels] = r.labels if r.labels + [name, info] + end + render json: response.to_h, status: success ? :ok : :service_unavailable + end +end diff --git a/app/controllers/import/base_controller.rb b/app/controllers/import/base_controller.rb index eeee027ef2d..9de0297ecfd 100644 --- a/app/controllers/import/base_controller.rb +++ b/app/controllers/import/base_controller.rb @@ -1,17 +1,27 @@ class Import::BaseController < ApplicationController private - def find_or_create_namespace(name, owner) - return current_user.namespace if name == owner + def find_or_create_namespace(names, owner) + return current_user.namespace if names == owner return current_user.namespace unless current_user.can_create_group? - begin - name = params[:target_namespace].presence || name - namespace = Group.create!(name: name, path: name, owner: current_user) - namespace.add_owner(current_user) - namespace - rescue ActiveRecord::RecordNotUnique, ActiveRecord::RecordInvalid - Namespace.find_by_full_path(name) + names = params[:target_namespace].presence || names + full_path_namespace = Namespace.find_by_full_path(names) + + return full_path_namespace if full_path_namespace + + names.split('/').inject(nil) do |parent, name| + begin + namespace = Group.create!(name: name, + path: name, + owner: current_user, + parent: parent) + namespace.add_owner(current_user) + + namespace + rescue ActiveRecord::RecordNotUnique, ActiveRecord::RecordInvalid + Namespace.where(parent: parent).find_by_path_or_name(name) + end end end end diff --git a/app/controllers/jwt_controller.rb b/app/controllers/jwt_controller.rb index 3109439b2ff..1c01be06451 100644 --- a/app/controllers/jwt_controller.rb +++ b/app/controllers/jwt_controller.rb @@ -4,7 +4,7 @@ class JwtController < ApplicationController before_action :authenticate_project_or_user SERVICES = { - Auth::ContainerRegistryAuthenticationService::AUDIENCE => Auth::ContainerRegistryAuthenticationService, + Auth::ContainerRegistryAuthenticationService::AUDIENCE => Auth::ContainerRegistryAuthenticationService }.freeze def auth diff --git a/app/controllers/omniauth_callbacks_controller.rb b/app/controllers/omniauth_callbacks_controller.rb index 58d50ad647b..2a8c8ca4bad 100644 --- a/app/controllers/omniauth_callbacks_controller.rb +++ b/app/controllers/omniauth_callbacks_controller.rb @@ -67,7 +67,7 @@ class OmniauthCallbacksController < Devise::OmniauthCallbacksController def omniauth_error @provider = params[:provider] @error = params[:error] - render 'errors/omniauth_error', layout: "errors", status: 422 + render 'errors/omniauth_error', layout: "oauth_error", status: 422 end def cas3 diff --git a/app/controllers/profiles/accounts_controller.rb b/app/controllers/profiles/accounts_controller.rb index 69959fe3687..7d1aa8d1ce0 100644 --- a/app/controllers/profiles/accounts_controller.rb +++ b/app/controllers/profiles/accounts_controller.rb @@ -1,11 +1,22 @@ class Profiles::AccountsController < Profiles::ApplicationController + include AuthHelper + def show @user = current_user end def unlink provider = params[:provider] - current_user.identities.find_by(provider: provider).destroy unless provider.to_s == 'saml' + identity = current_user.identities.find_by(provider: provider) + + return render_404 unless identity + + if unlink_allowed?(provider) + identity.destroy + else + flash[:alert] = "You are not allowed to unlink your primary login account" + end + redirect_to profile_account_path end end diff --git a/app/controllers/profiles/preferences_controller.rb b/app/controllers/profiles/preferences_controller.rb index 0d891ef4004..5414142e2df 100644 --- a/app/controllers/profiles/preferences_controller.rb +++ b/app/controllers/profiles/preferences_controller.rb @@ -33,7 +33,7 @@ class Profiles::PreferencesController < Profiles::ApplicationController :color_scheme_id, :layout, :dashboard, - :project_view, + :project_view ) end end diff --git a/app/controllers/profiles/two_factor_auths_controller.rb b/app/controllers/profiles/two_factor_auths_controller.rb index 26e7e93533e..d3fa81cd623 100644 --- a/app/controllers/profiles/two_factor_auths_controller.rb +++ b/app/controllers/profiles/two_factor_auths_controller.rb @@ -1,5 +1,5 @@ class Profiles::TwoFactorAuthsController < Profiles::ApplicationController - skip_before_action :check_2fa_requirement + skip_before_action :check_two_factor_requirement def show unless current_user.otp_secret @@ -13,11 +13,24 @@ class Profiles::TwoFactorAuthsController < Profiles::ApplicationController current_user.save! if current_user.changed? if two_factor_authentication_required? && !current_user.two_factor_enabled? - if two_factor_grace_period_expired? - flash.now[:alert] = 'You must enable Two-Factor Authentication for your account.' - else + two_factor_authentication_reason( + global: lambda do + flash.now[:alert] = + 'The global settings require you to enable Two-Factor Authentication for your account.' + end, + group: lambda do |groups| + group_links = groups.map { |group| view_context.link_to group.full_name, group_path(group) }.to_sentence + + flash.now[:alert] = %{ + The group settings for #{group_links} require you to enable + Two-Factor Authentication for your account. + }.html_safe + end + ) + + unless two_factor_grace_period_expired? grace_period_deadline = current_user.otp_grace_period_started_at + two_factor_grace_period.hours - flash.now[:alert] = "You must enable Two-Factor Authentication for your account before #{l(grace_period_deadline)}." + flash.now[:alert] << " You need to do this before #{l(grace_period_deadline)}." end end @@ -71,7 +84,7 @@ class Profiles::TwoFactorAuthsController < Profiles::ApplicationController if two_factor_grace_period_expired? redirect_to new_profile_two_factor_auth_path, alert: 'Cannot skip two factor authentication setup' else - session[:skip_tfa] = current_user.otp_grace_period_started_at + two_factor_grace_period.hours + session[:skip_two_factor] = current_user.otp_grace_period_started_at + two_factor_grace_period.hours redirect_to root_path end end diff --git a/app/controllers/profiles_controller.rb b/app/controllers/profiles_controller.rb index 987b95e89b9..57e23cea00e 100644 --- a/app/controllers/profiles_controller.rb +++ b/app/controllers/profiles_controller.rb @@ -85,7 +85,8 @@ class ProfilesController < Profiles::ApplicationController :twitter, :username, :website_url, - :organization + :organization, + :preferred_language ) end end diff --git a/app/controllers/projects/application_controller.rb b/app/controllers/projects/application_controller.rb index f1a93ccb3ad..cb4bd0ad5f5 100644 --- a/app/controllers/projects/application_controller.rb +++ b/app/controllers/projects/application_controller.rb @@ -1,5 +1,8 @@ class Projects::ApplicationController < ApplicationController + include RoutableActions + skip_before_action :authenticate_user! + before_action :redirect_git_extension before_action :project before_action :repository layout 'project' @@ -8,40 +11,29 @@ class Projects::ApplicationController < ApplicationController private + def redirect_git_extension + # Redirect from + # localhost/group/project.git + # to + # localhost/group/project + # + redirect_to url_for(params.merge(format: nil)) if params[:format] == 'git' + end + def project - unless @project - namespace = params[:namespace_id] - id = params[:project_id] || params[:id] - - # Redirect from - # localhost/group/project.git - # to - # localhost/group/project - # - if params[:format] == 'git' - redirect_to request.original_url.gsub(/\.git\/?\Z/, '') - return - end - - project_path = "#{namespace}/#{id}" - @project = Project.find_by_full_path(project_path) - - if can?(current_user, :read_project, @project) && !@project.pending_delete? - if @project.path_with_namespace != project_path - redirect_to request.original_url.gsub(project_path, @project.path_with_namespace) - end - else - @project = nil - - if current_user.nil? - authenticate_user! - else - render_404 - end - end - end + return @project if @project + + path = File.join(params[:namespace_id], params[:project_id] || params[:id]) + auth_proc = ->(project) { !project.pending_delete? } - @project + @project = find_routable!(Project, path, extra_authorization_proc: auth_proc) + end + + def build_canonical_path(project) + params[:namespace_id] = project.namespace.to_param + params[:project_id] = project.to_param + + url_for(params) end def repository @@ -55,13 +47,15 @@ class Projects::ApplicationController < ApplicationController (current_user && current_user.already_forked?(project)) end - def authorize_project!(action) - return access_denied! unless can?(current_user, action, project) + def authorize_action!(action) + unless can?(current_user, action, project) + return access_denied! + end end def method_missing(method_sym, *arguments, &block) if method_sym.to_s =~ /\Aauthorize_(.*)!\z/ - authorize_project!($1.to_sym) + authorize_action!($1.to_sym) else super end @@ -90,8 +84,7 @@ class Projects::ApplicationController < ApplicationController return render_404 unless @project.feature_available?(:builds, current_user) end - def update_ref - branch_exists = @repository.find_branch(@target_branch) - @ref = @target_branch if branch_exists + def require_pages_enabled! + not_found unless Gitlab.config.pages.enabled end end diff --git a/app/controllers/projects/artifacts_controller.rb b/app/controllers/projects/artifacts_controller.rb index 59222637961..1224e9503c9 100644 --- a/app/controllers/projects/artifacts_controller.rb +++ b/app/controllers/projects/artifacts_controller.rb @@ -1,11 +1,13 @@ class Projects::ArtifactsController < Projects::ApplicationController include ExtractsPath + include RendersBlob layout 'project' before_action :authorize_read_build! before_action :authorize_update_build!, only: [:keep] before_action :extract_ref_name_and_path before_action :validate_artifacts! + before_action :set_path_and_entry, only: [:file, :raw] def download if artifacts_file.file_storage? @@ -16,22 +18,32 @@ class Projects::ArtifactsController < Projects::ApplicationController end def browse - directory = params[:path] ? "#{params[:path]}/" : '' + @path = params[:path] + directory = @path ? "#{@path}/" : '' @entry = build.artifacts_metadata_entry(directory) render_404 unless @entry.exists? end def file - entry = build.artifacts_metadata_entry(params[:path]) + blob = @entry.blob + override_max_blob_size(blob) - if entry.exists? - send_artifacts_entry(build, entry) - else - render_404 + respond_to do |format| + format.html do + render 'file' + end + + format.json do + render_blob_json(blob) + end end end + def raw + send_artifacts_entry(build, @entry) + end + def keep build.keep_artifacts! redirect_to namespace_project_build_path(project.namespace, project, build) @@ -60,7 +72,10 @@ class Projects::ArtifactsController < Projects::ApplicationController end def build - @build ||= build_from_id || build_from_ref + @build ||= begin + build = build_from_id || build_from_ref + build&.present(current_user: current_user) + end end def build_from_id @@ -77,4 +92,11 @@ class Projects::ArtifactsController < Projects::ApplicationController def artifacts_file @artifacts_file ||= build.artifacts_file end + + def set_path_and_entry + @path = params[:path] + @entry = build.artifacts_metadata_entry(@path) + + render_404 unless @entry.exists? + end end diff --git a/app/controllers/projects/blob_controller.rb b/app/controllers/projects/blob_controller.rb index 80a95c6158b..87721fbe2f5 100644 --- a/app/controllers/projects/blob_controller.rb +++ b/app/controllers/projects/blob_controller.rb @@ -2,14 +2,17 @@ class Projects::BlobController < Projects::ApplicationController include ExtractsPath include CreatesCommit + include RendersBlob include ActionView::Helpers::SanitizeHelper # Raised when given an invalid file path InvalidPathError = Class.new(StandardError) + prepend_before_action :authenticate_user!, only: [:edit] + before_action :require_non_empty_project, except: [:new, :create] before_action :authorize_download_code! - before_action :authorize_edit_tree!, only: [:new, :create, :edit, :update, :destroy] + before_action :authorize_edit_tree!, only: [:new, :create, :update, :destroy] before_action :assign_blob_vars before_action :commit, except: [:new, :create] before_action :blob, except: [:new, :create] @@ -23,21 +26,39 @@ class Projects::BlobController < Projects::ApplicationController end def create - update_ref + set_start_branch_to_branch_name create_commit(Files::CreateService, success_notice: "The file has been successfully created.", - success_path: -> { namespace_project_blob_path(@project.namespace, @project, File.join(@target_branch, @file_path)) }, + success_path: -> { namespace_project_blob_path(@project.namespace, @project, File.join(@branch_name, @file_path)) }, failure_view: :new, failure_path: namespace_project_new_blob_path(@project.namespace, @project, @ref)) end def show - environment_params = @repository.branch_exists?(@ref) ? { ref: @ref } : { commit: @commit } - @environment = EnvironmentsFinder.new(@project, current_user, environment_params).execute.last + override_max_blob_size(@blob) + + respond_to do |format| + format.html do + environment_params = @repository.branch_exists?(@ref) ? { ref: @ref } : { commit: @commit } + @environment = EnvironmentsFinder.new(@project, current_user, environment_params).execute.last + + @last_commit = @repository.last_commit_for_path(@commit.id, @blob.path) + + render 'show' + end + + format.json do + render_blob_json(@blob) + end + end end def edit - blob.load_all_data!(@repository) + if can_collaborate_with_project? + blob.load_all_data!(@repository) + else + redirect_to action: 'show' + end end def update @@ -63,10 +84,10 @@ class Projects::BlobController < Projects::ApplicationController end def destroy - create_commit(Files::DestroyService, success_notice: "The file has been successfully deleted.", - success_path: -> { namespace_project_tree_path(@project.namespace, @project, @target_branch) }, - failure_view: :show, - failure_path: namespace_project_blob_path(@project.namespace, @project, @id)) + create_commit(Files::DeleteService, success_notice: "The file has been successfully deleted.", + success_path: -> { namespace_project_tree_path(@project.namespace, @project, @branch_name) }, + failure_view: :show, + failure_path: namespace_project_blob_path(@project.namespace, @project, @id)) end def diff @@ -90,7 +111,7 @@ class Projects::BlobController < Projects::ApplicationController private def blob - @blob ||= Blob.decorate(@repository.blob_at(@commit.id, @path)) + @blob ||= Blob.decorate(@repository.blob_at(@commit.id, @path), @project) if @blob @blob @@ -121,16 +142,16 @@ class Projects::BlobController < Projects::ApplicationController def after_edit_path from_merge_request = MergeRequestsFinder.new(current_user, project_id: @project.id).execute.find_by(iid: params[:from_merge_request_iid]) - if from_merge_request && @target_branch == @ref + if from_merge_request && @branch_name == @ref diffs_namespace_project_merge_request_path(from_merge_request.target_project.namespace, from_merge_request.target_project, from_merge_request) + "##{hexdigest(@path)}" else - namespace_project_blob_path(@project.namespace, @project, File.join(@target_branch, @path)) + namespace_project_blob_path(@project.namespace, @project, File.join(@branch_name, @path)) end end def editor_variables - @target_branch = params[:target_branch] + @branch_name = params[:branch_name] @file_path = if action_name.to_s == 'create' diff --git a/app/controllers/projects/boards/issues_controller.rb b/app/controllers/projects/boards/issues_controller.rb index 28c9646910d..da9b789d617 100644 --- a/app/controllers/projects/boards/issues_controller.rb +++ b/app/controllers/projects/boards/issues_controller.rb @@ -82,7 +82,7 @@ module Projects labels: true, only: [:id, :iid, :title, :confidential, :due_date, :relative_position], include: { - assignee: { only: [:id, :name, :username], methods: [:avatar_url] }, + assignees: { only: [:id, :name, :username], methods: [:avatar_url] }, milestone: { only: [:id, :title] } }, user: current_user diff --git a/app/controllers/projects/branches_controller.rb b/app/controllers/projects/branches_controller.rb index 840405f38cb..d8ed470e461 100644 --- a/app/controllers/projects/branches_controller.rb +++ b/app/controllers/projects/branches_controller.rb @@ -46,32 +46,45 @@ class Projects::BranchesController < Projects::ApplicationController SystemNoteService.new_issue_branch(issue, @project, current_user, branch_name) if issue end - if result[:status] == :success - @branch = result[:branch] - - if redirect_to_autodeploy - redirect_to( - url_to_autodeploy_setup(project, branch_name), - notice: view_context.autodeploy_flash_notice(branch_name)) - else - redirect_to namespace_project_tree_path(@project.namespace, @project, - @branch.name) + respond_to do |format| + format.html do + if result[:status] == :success + if redirect_to_autodeploy + redirect_to url_to_autodeploy_setup(project, branch_name), + notice: view_context.autodeploy_flash_notice(branch_name) + else + redirect_to namespace_project_tree_path(@project.namespace, @project, branch_name) + end + else + @error = result[:message] + render action: 'new' + end + end + + format.json do + if result[:status] == :success + render json: { name: branch_name, url: namespace_project_tree_url(@project.namespace, @project, branch_name) } + else + render json: result[:messsage], status: :unprocessable_entity + end end - else - @error = result[:message] - render action: 'new' end end def destroy @branch_name = Addressable::URI.unescape(params[:id]) - status = DeleteBranchService.new(project, current_user).execute(@branch_name) + result = DeleteBranchService.new(project, current_user).execute(@branch_name) + respond_to do |format| format.html do - redirect_to namespace_project_branches_path(@project.namespace, - @project), status: 303 + flash_type = result[:status] == :error ? :alert : :notice + flash[flash_type] = result[:message] + + redirect_to namespace_project_branches_path(@project.namespace, @project), status: 303 end - format.js { render nothing: true, status: status[:return_code] } + + format.js { render nothing: true, status: result[:return_code] } + format.json { render json: { message: result[:message] }, status: result[:return_code] } end end diff --git a/app/controllers/projects/builds_controller.rb b/app/controllers/projects/builds_controller.rb index 3f3c90a49ab..dfaaea71b9c 100644 --- a/app/controllers/projects/builds_controller.rb +++ b/app/controllers/projects/builds_controller.rb @@ -1,7 +1,11 @@ class Projects::BuildsController < Projects::ApplicationController before_action :build, except: [:index, :cancel_all] - before_action :authorize_read_build!, except: [:cancel, :cancel_all, :retry, :play] - before_action :authorize_update_build!, except: [:index, :show, :status, :raw, :trace] + + before_action :authorize_read_build!, + only: [:index, :show, :status, :raw, :trace] + before_action :authorize_update_build!, + except: [:index, :show, :status, :raw, :trace, :cancel_all] + layout 'project' def index @@ -19,11 +23,21 @@ class Projects::BuildsController < Projects::ApplicationController else @builds end + @builds = @builds.includes([ + { pipeline: :project }, + :project, + :tags + ]) @builds = @builds.page(params[:page]).per(30) end def cancel_all - @project.builds.running_or_pending.each(&:cancel) + return access_denied! unless can?(current_user, :update_build, project) + + @project.builds.running_or_pending.each do |build| + build.cancel if can?(current_user, :update_build, build) + end + redirect_to namespace_project_builds_path(project.namespace, project) end @@ -31,72 +45,84 @@ class Projects::BuildsController < Projects::ApplicationController @builds = @project.pipelines.find_by_sha(@build.sha).builds.order('id DESC') @builds = @builds.where("id not in (?)", @build.id) @pipeline = @build.pipeline - - respond_to do |format| - format.html - format.json do - render json: { - id: @build.id, - status: @build.status, - trace_html: @build.trace_html - } - end - end end def trace - respond_to do |format| - format.json do - state = params[:state].presence - render json: @build.trace_with_state(state: state). - merge!(id: @build.id, status: @build.status) + build.trace.read do |stream| + respond_to do |format| + format.json do + result = { + id: @build.id, status: @build.status, complete: @build.complete? + } + + if stream.valid? + stream.limit + state = params[:state].presence + trace = stream.html_with_state(state) + result.merge!(trace.to_h) + end + + render json: result + end end end end def retry - return render_404 unless @build.retryable? + return respond_422 unless @build.retryable? build = Ci::Build.retry(@build, current_user) redirect_to build_path(build) end def play - return render_404 unless @build.playable? + return respond_422 unless @build.playable? build = @build.play(current_user) redirect_to build_path(build) end def cancel + return respond_422 unless @build.cancelable? + @build.cancel redirect_to build_path(@build) end def status render json: BuildSerializer - .new(project: @project, user: @current_user) + .new(project: @project, current_user: @current_user) .represent_status(@build) end def erase - @build.erase(erased_by: current_user) - redirect_to namespace_project_build_path(project.namespace, project, @build), + if @build.erase(erased_by: current_user) + redirect_to namespace_project_build_path(project.namespace, project, @build), notice: "Build has been successfully erased!" + else + respond_422 + end end def raw - if @build.has_trace_file? - send_file @build.trace_file_path, type: 'text/plain; charset=utf-8', disposition: 'inline' - else - render_404 + build.trace.read do |stream| + if stream.file? + send_file stream.path, type: 'text/plain; charset=utf-8', disposition: 'inline' + else + render_404 + end end end private + def authorize_update_build! + return access_denied! unless can?(current_user, :update_build, build) + end + def build - @build ||= project.builds.find_by!(id: params[:id]).present(current_user: current_user) + @build ||= project.builds.find(params[:id]) + .present(current_user: current_user) end def build_path(build) diff --git a/app/controllers/projects/commit_controller.rb b/app/controllers/projects/commit_controller.rb index cc67f688d51..7c3cce1c241 100644 --- a/app/controllers/projects/commit_controller.rb +++ b/app/controllers/projects/commit_controller.rb @@ -2,6 +2,7 @@ # # Not to be confused with CommitsController, plural. class Projects::CommitController < Projects::ApplicationController + include RendersNotes include CreatesCommit include DiffForPath include DiffHelper @@ -35,8 +36,10 @@ class Projects::CommitController < Projects::ApplicationController respond_to do |format| format.html format.json do + Gitlab::PollingInterval.set_header(response, interval: 10_000) + render json: PipelineSerializer - .new(project: @project, user: @current_user) + .new(project: @project, current_user: @current_user) .represent(@pipelines) end end @@ -53,9 +56,7 @@ class Projects::CommitController < Projects::ApplicationController return render_404 if @start_branch.blank? - @target_branch = create_new_branch? ? @commit.revert_branch_name : @start_branch - - @mr_target_branch = @start_branch + @branch_name = create_new_branch? ? @commit.revert_branch_name : @start_branch create_commit(Commits::RevertService, success_notice: "The #{@commit.change_type_title(current_user)} has been successfully reverted.", success_path: -> { successful_change_path }, failure_path: failed_change_path) @@ -66,9 +67,7 @@ class Projects::CommitController < Projects::ApplicationController return render_404 if @start_branch.blank? - @target_branch = create_new_branch? ? @commit.cherry_pick_branch_name : @start_branch - - @mr_target_branch = @start_branch + @branch_name = create_new_branch? ? @commit.cherry_pick_branch_name : @start_branch create_commit(Commits::CherryPickService, success_notice: "The #{@commit.change_type_title(current_user)} has been successfully cherry-picked.", success_path: -> { successful_change_path }, failure_path: failed_change_path) @@ -81,7 +80,7 @@ class Projects::CommitController < Projects::ApplicationController end def successful_change_path - referenced_merge_request_url || namespace_project_commits_url(@project.namespace, @project, @target_branch) + referenced_merge_request_url || namespace_project_commits_url(@project.namespace, @project, @branch_name) end def failed_change_path @@ -111,22 +110,19 @@ class Projects::CommitController < Projects::ApplicationController end def define_note_vars - @grouped_diff_discussions = commit.notes.grouped_diff_discussions - @notes = commit.notes.non_diff_notes.fresh - - Banzai::NoteRenderer.render( - @grouped_diff_discussions.values.flat_map(&:notes) + @notes, - @project, - current_user, - ) - + @noteable = @commit @note = @project.build_commit_note(commit) - @noteable = @commit - @comments_target = { + @new_diff_note_attrs = { noteable_type: 'Commit', commit_id: @commit.id } + + @grouped_diff_discussions = commit.grouped_diff_discussions + @discussions = commit.discussions + + @notes = (@grouped_diff_discussions.values.flatten + @discussions).flat_map(&:notes) + @notes = prepare_notes_for_rendering(@notes) end def assign_change_commit_vars diff --git a/app/controllers/projects/compare_controller.rb b/app/controllers/projects/compare_controller.rb index c6651254d70..008d2f5815f 100644 --- a/app/controllers/projects/compare_controller.rb +++ b/app/controllers/projects/compare_controller.rb @@ -61,7 +61,6 @@ class Projects::CompareController < Projects::ApplicationController @environment = EnvironmentsFinder.new(@project, current_user, environment_params).execute.last @diff_notes_disabled = true - @grouped_diff_discussions = {} end end diff --git a/app/controllers/projects/container_registry_controller.rb b/app/controllers/projects/container_registry_controller.rb deleted file mode 100644 index d1f46497207..00000000000 --- a/app/controllers/projects/container_registry_controller.rb +++ /dev/null @@ -1,34 +0,0 @@ -class Projects::ContainerRegistryController < Projects::ApplicationController - before_action :verify_registry_enabled - before_action :authorize_read_container_image! - before_action :authorize_update_container_image!, only: [:destroy] - layout 'project' - - def index - @tags = container_registry_repository.tags - end - - def destroy - url = namespace_project_container_registry_index_path(project.namespace, project) - - if tag.delete - redirect_to url - else - redirect_to url, alert: 'Failed to remove tag' - end - end - - private - - def verify_registry_enabled - render_404 unless Gitlab.config.registry.enabled - end - - def container_registry_repository - @container_registry_repository ||= project.container_registry_repository - end - - def tag - @tag ||= container_registry_repository.tag(params[:id]) - end -end diff --git a/app/controllers/projects/deploy_keys_controller.rb b/app/controllers/projects/deploy_keys_controller.rb index d0c44e297e3..f27089b8590 100644 --- a/app/controllers/projects/deploy_keys_controller.rb +++ b/app/controllers/projects/deploy_keys_controller.rb @@ -8,7 +8,12 @@ class Projects::DeployKeysController < Projects::ApplicationController layout "project_settings" def index - redirect_to_repository_settings(@project) + respond_to do |format| + format.html { redirect_to_repository_settings(@project) } + format.json do + render json: Projects::Settings::DeployKeysPresenter.new(@project, current_user: current_user).as_json + end + end end def new @@ -19,7 +24,7 @@ class Projects::DeployKeysController < Projects::ApplicationController @key = DeployKey.new(deploy_key_params.merge(user: current_user)) unless @key.valid? && @project.deploy_keys << @key - flash[:alert] = @key.errors.full_messages.join(', ').html_safe + flash[:alert] = @key.errors.full_messages.join(', ').html_safe end redirect_to_repository_settings(@project) end @@ -27,7 +32,10 @@ class Projects::DeployKeysController < Projects::ApplicationController def enable Projects::EnableDeployKeyService.new(@project, current_user, params).execute - redirect_to_repository_settings(@project) + respond_to do |format| + format.html { redirect_to_repository_settings(@project) } + format.json { head :ok } + end end def disable @@ -35,7 +43,11 @@ class Projects::DeployKeysController < Projects::ApplicationController return render_404 unless deploy_key_project deploy_key_project.destroy! - redirect_to_repository_settings(@project) + + respond_to do |format| + format.html { redirect_to_repository_settings(@project) } + format.json { head :ok } + end end protected diff --git a/app/controllers/projects/deployments_controller.rb b/app/controllers/projects/deployments_controller.rb new file mode 100644 index 00000000000..6644deb49c9 --- /dev/null +++ b/app/controllers/projects/deployments_controller.rb @@ -0,0 +1,34 @@ +class Projects::DeploymentsController < Projects::ApplicationController + before_action :authorize_read_environment! + before_action :authorize_read_deployment! + + def index + deployments = environment.deployments.reorder(created_at: :desc) + deployments = deployments.where('created_at > ?', params[:after].to_time) if params[:after]&.to_time + + render json: { deployments: DeploymentSerializer.new(project: project) + .represent_concise(deployments) } + end + + def metrics + return render_404 unless deployment.has_metrics? + @metrics = deployment.metrics + if @metrics&.any? + render json: @metrics, status: :ok + else + head :no_content + end + rescue NotImplementedError + render_404 + end + + private + + def deployment + @deployment ||= environment.deployments.find_by(iid: params[:id]) + end + + def environment + @environment ||= project.environments.find(params[:environment_id]) + end +end diff --git a/app/controllers/projects/discussions_controller.rb b/app/controllers/projects/discussions_controller.rb index 1349b015a63..f4a18a5e8f7 100644 --- a/app/controllers/projects/discussions_controller.rb +++ b/app/controllers/projects/discussions_controller.rb @@ -28,7 +28,7 @@ class Projects::DiscussionsController < Projects::ApplicationController end def discussion - @discussion ||= @merge_request.find_diff_discussion(params[:id]) || render_404 + @discussion ||= @merge_request.find_discussion(params[:id]) || render_404 end def authorize_resolve_discussion! diff --git a/app/controllers/projects/environments_controller.rb b/app/controllers/projects/environments_controller.rb index fa37963dfd4..fd57afbd05f 100644 --- a/app/controllers/projects/environments_controller.rb +++ b/app/controllers/projects/environments_controller.rb @@ -17,7 +17,7 @@ class Projects::EnvironmentsController < Projects::ApplicationController format.json do render json: { environments: EnvironmentSerializer - .new(project: @project, user: @current_user) + .new(project: @project, current_user: @current_user) .with_pagination(request, response) .within_folders .represent(@environments), @@ -37,7 +37,7 @@ class Projects::EnvironmentsController < Projects::ApplicationController format.json do render json: { environments: EnvironmentSerializer - .new(project: @project, user: @current_user) + .new(project: @project, current_user: @current_user) .with_pagination(request, response) .represent(@environments), available_count: folder_environments.available.count, @@ -81,10 +81,16 @@ class Projects::EnvironmentsController < Projects::ApplicationController stop_action = @environment.stop_with_action!(current_user) - if stop_action - redirect_to polymorphic_path([project.namespace.becomes(Namespace), project, stop_action]) - else - redirect_to namespace_project_environment_path(project.namespace, project, @environment) + action_or_env_url = + if stop_action + polymorphic_url([project.namespace.becomes(Namespace), project, stop_action]) + else + namespace_project_environment_url(project.namespace, project, @environment) + end + + respond_to do |format| + format.html { redirect_to action_or_env_url } + format.json { render json: { redirect_url: action_or_env_url } } end end diff --git a/app/controllers/projects/forks_controller.rb b/app/controllers/projects/forks_controller.rb index ba46e2528e6..1eb3800e49d 100644 --- a/app/controllers/projects/forks_controller.rb +++ b/app/controllers/projects/forks_controller.rb @@ -9,7 +9,7 @@ class Projects::ForksController < Projects::ApplicationController def index base_query = project.forks.includes(:creator) - @forks = base_query.merge(ProjectsFinder.new.execute(current_user)) + @forks = base_query.merge(ProjectsFinder.new(current_user: current_user).execute) @total_forks_count = base_query.size @private_forks_count = @total_forks_count - @forks.size @public_forks_count = @total_forks_count - @private_forks_count diff --git a/app/controllers/projects/git_http_controller.rb b/app/controllers/projects/git_http_controller.rb index 278098fcc58..9e4edcae101 100644 --- a/app/controllers/projects/git_http_controller.rb +++ b/app/controllers/projects/git_http_controller.rb @@ -5,6 +5,8 @@ class Projects::GitHttpController < Projects::GitHttpClientController # GET /foo/bar.git/info/refs?service=git-receive-pack (git push) def info_refs if upload_pack? && upload_pack_allowed? + log_user_activity + render_ok elsif receive_pack? && receive_pack_allowed? render_ok @@ -57,7 +59,7 @@ class Projects::GitHttpController < Projects::GitHttpClientController def render_ok set_workhorse_internal_api_content_type - render json: Gitlab::Workhorse.git_http_ok(repository, user) + render json: Gitlab::Workhorse.git_http_ok(repository, wiki?, user, action_name) end def render_http_not_allowed @@ -106,4 +108,8 @@ class Projects::GitHttpController < Projects::GitHttpClientController def access_klass @access_klass ||= wiki? ? Gitlab::GitAccessWiki : Gitlab::GitAccess end + + def log_user_activity + Users::ActivityService.new(user, 'pull').execute + end end diff --git a/app/controllers/projects/hooks_controller.rb b/app/controllers/projects/hooks_controller.rb index b668a9331e7..86d13a0d222 100644 --- a/app/controllers/projects/hooks_controller.rb +++ b/app/controllers/projects/hooks_controller.rb @@ -1,6 +1,7 @@ class Projects::HooksController < Projects::ApplicationController # Authorize before_action :authorize_admin_project! + before_action :hook, only: :edit respond_to :html @@ -10,13 +11,25 @@ class Projects::HooksController < Projects::ApplicationController @hook = @project.hooks.new(hook_params) @hook.save - unless @hook.valid? + unless @hook.valid? @hooks = @project.hooks.select(&:persisted?) flash[:alert] = @hook.errors.full_messages.join.html_safe end redirect_to namespace_project_settings_integrations_path(@project.namespace, @project) end + def edit + end + + def update + if hook.update_attributes(hook_params) + flash[:notice] = 'Hook was successfully updated.' + redirect_to namespace_project_settings_integrations_path(@project.namespace, @project) + else + render 'edit' + end + end + def test if !@project.empty_repo? status, message = TestHookService.new.execute(hook, current_user) @@ -49,7 +62,7 @@ class Projects::HooksController < Projects::ApplicationController def hook_params params.require(:hook).permit( - :build_events, + :job_events, :pipeline_events, :enable_ssl_verification, :issues_events, diff --git a/app/controllers/projects/issues_controller.rb b/app/controllers/projects/issues_controller.rb index d984e6d3918..cbef8fa94d4 100644 --- a/app/controllers/projects/issues_controller.rb +++ b/app/controllers/projects/issues_controller.rb @@ -1,5 +1,5 @@ class Projects::IssuesController < Projects::ApplicationController - include NotesHelper + include RendersNotes include ToggleSubscriptionAction include IssuableActions include ToggleAwardEmoji @@ -11,10 +11,10 @@ class Projects::IssuesController < Projects::ApplicationController before_action :redirect_to_external_issue_tracker, only: [:index, :new] before_action :module_enabled before_action :issue, only: [:edit, :update, :show, :referenced_merge_requests, - :related_branches, :can_create_branch] + :related_branches, :can_create_branch, :realtime_changes, :create_merge_request] # Allow read any issue - before_action :authorize_read_issue!, only: [:show] + before_action :authorize_read_issue!, only: [:show, :realtime_changes] # Allow write(create) issue before_action :authorize_create_issue!, only: [:new, :create] @@ -22,6 +22,9 @@ class Projects::IssuesController < Projects::ApplicationController # Allow modify issue before_action :authorize_update_issue!, only: [:edit, :update] + # Allow create a new branch and empty WIP merge request from current issue + before_action :authorize_create_merge_request!, only: [:create_merge_request] + respond_to :html def index @@ -31,7 +34,7 @@ class Projects::IssuesController < Projects::ApplicationController @issuable_meta_data = issuable_meta_data(@issues, @collection_type) if @issues.out_of_range? && @issues.total_pages != 0 - return redirect_to url_for(params.merge(page: @issues.total_pages)) + return redirect_to url_for(params.merge(page: @issues.total_pages, only_path: true)) end if params[:label_name].present? @@ -64,7 +67,7 @@ class Projects::IssuesController < Projects::ApplicationController def new params[:issue] ||= ActionController::Parameters.new( - assignee_id: "" + assignee_ids: "" ) build_params = issue_params.merge( merge_request_to_resolve_discussions_of: params[:merge_request_to_resolve_discussions_of], @@ -84,15 +87,11 @@ class Projects::IssuesController < Projects::ApplicationController end def show - raw_notes = @issue.notes.inc_relations_for_view.fresh - - @notes = Banzai::NoteRenderer. - render(raw_notes, @project, current_user, @path, @project_wiki, @ref) - - @note = @project.notes.new(noteable: @issue) @noteable = @issue + @note = @project.notes.new(noteable: @issue) - preload_max_access_for_authors(@notes, @project) + @discussions = @issue.discussions + @notes = prepare_notes_for_rendering(@discussions.flat_map(&:notes)) respond_to do |format| format.html @@ -151,7 +150,7 @@ class Projects::IssuesController < Projects::ApplicationController if @issue.valid? render json: @issue.to_json(methods: [:task_status, :task_status_short], include: { milestone: {}, - assignee: { only: [:name, :username], methods: [:avatar_url] }, + assignees: { only: [:id, :name, :username], methods: [:avatar_url] }, labels: { methods: :text_color } }) else render json: { errors: @issue.errors.full_messages }, status: :unprocessable_entity @@ -195,16 +194,39 @@ class Projects::IssuesController < Projects::ApplicationController respond_to do |format| format.json do - render json: { can_create_branch: can_create } + render json: { can_create_branch: can_create, has_related_branch: @issue.has_related_branch? } end end end + def realtime_changes + Gitlab::PollingInterval.set_header(response, interval: 3_000) + + render json: { + title: view_context.markdown_field(@issue, :title), + title_text: @issue.title, + description: view_context.markdown_field(@issue, :description), + description_text: @issue.description, + task_status: @issue.task_status, + updated_at: @issue.updated_at + } + end + + def create_merge_request + result = MergeRequests::CreateFromIssueService.new(project, current_user, issue_iid: issue.iid).execute + + if result[:status] == :success + render json: MergeRequestCreateSerializer.new.represent(result[:merge_request]) + else + render json: result[:messsage], status: :unprocessable_entity + end + end + protected def issue # The Sortable default scope causes performance issues when used with find_by - @noteable = @issue ||= @project.issues.where(iid: params[:id]).reorder(nil).take || redirect_old + @noteable = @issue ||= @project.issues.where(iid: params[:id]).reorder(nil).take! end alias_method :subscribable_resource, :issue alias_method :issuable, :issue @@ -223,6 +245,10 @@ class Projects::IssuesController < Projects::ApplicationController return render_404 unless can?(current_user, :admin_issue, @project) end + def authorize_create_merge_request! + return render_404 unless can?(current_user, :push_code, @project) && @issue.can_be_worked_on?(current_user) + end + def module_enabled return render_404 unless @project.feature_available?(:issues, current_user) && @project.default_issues_tracker? end @@ -239,25 +265,10 @@ class Projects::IssuesController < Projects::ApplicationController end end - # Since iids are implemented only in 6.1 - # user may navigate to issue page using old global ids. - # - # To prevent 404 errors we provide a redirect to correct iids until 7.0 release - # - def redirect_old - issue = @project.issues.find_by(id: params[:id]) - - if issue - redirect_to issue_path(issue) - else - raise ActiveRecord::RecordNotFound.new - end - end - def issue_params params.require(:issue).permit( :title, :assignee_id, :position, :description, :confidential, - :milestone_id, :due_date, :state_event, :task_num, :lock_version, label_ids: [] + :milestone_id, :due_date, :state_event, :task_num, :lock_version, label_ids: [], assignee_ids: [] ) end @@ -266,7 +277,10 @@ class Projects::IssuesController < Projects::ApplicationController notice = "Please sign in to create the new issue." - store_location_for :user, request.fullpath + if request.get? && !request.xhr? + store_location_for :user, request.fullpath + end + redirect_to new_user_session_path, notice: notice end end diff --git a/app/controllers/projects/labels_controller.rb b/app/controllers/projects/labels_controller.rb index 2f55ba4e700..71bfb7163da 100644 --- a/app/controllers/projects/labels_controller.rb +++ b/app/controllers/projects/labels_controller.rb @@ -19,7 +19,7 @@ class Projects::LabelsController < Projects::ApplicationController respond_to do |format| format.html format.json do - render json: @available_labels.as_json(only: [:id, :title, :color]) + render json: LabelSerializer.new.represent_appearance(@available_labels) end end end diff --git a/app/controllers/projects/lfs_api_controller.rb b/app/controllers/projects/lfs_api_controller.rb index 8a5a645ed0e..1b0d3aab3fa 100644 --- a/app/controllers/projects/lfs_api_controller.rb +++ b/app/controllers/projects/lfs_api_controller.rb @@ -22,7 +22,7 @@ class Projects::LfsApiController < Projects::GitHttpClientController render( json: { message: 'Server supports batch API only, please update your Git LFS client to version 1.0.1 and up.', - documentation_url: "#{Gitlab.config.gitlab.url}/help", + documentation_url: "#{Gitlab.config.gitlab.url}/help" }, status: 501 ) @@ -55,7 +55,7 @@ class Projects::LfsApiController < Projects::GitHttpClientController else object[:error] = { code: 404, - message: "Object does not exist on the server or you don't have permissions to access it", + message: "Object does not exist on the server or you don't have permissions to access it" } end end diff --git a/app/controllers/projects/merge_requests_controller.rb b/app/controllers/projects/merge_requests_controller.rb index 9621b30b251..0352065998b 100755..100644 --- a/app/controllers/projects/merge_requests_controller.rb +++ b/app/controllers/projects/merge_requests_controller.rb @@ -3,22 +3,21 @@ class Projects::MergeRequestsController < Projects::ApplicationController include DiffForPath include DiffHelper include IssuableActions - include NotesHelper + include RendersNotes include ToggleAwardEmoji include IssuableCollections before_action :module_enabled before_action :merge_request, only: [ - :edit, :update, :show, :diffs, :commits, :conflicts, :conflict_for_path, :pipelines, :merge, :merge_check, - :ci_status, :pipeline_status, :ci_environments_status, :toggle_subscription, :cancel_merge_when_pipeline_succeeds, :remove_wip, :resolve_conflicts, :assign_related_issues + :edit, :update, :show, :diffs, :commits, :conflicts, :conflict_for_path, :pipelines, :merge, + :pipeline_status, :ci_environments_status, :toggle_subscription, :cancel_merge_when_pipeline_succeeds, :remove_wip, :resolve_conflicts, :assign_related_issues, :commit_change_content ] before_action :validates_merge_request, only: [:show, :diffs, :commits, :pipelines] - before_action :define_show_vars, only: [:show, :diffs, :commits, :conflicts, :conflict_for_path, :builds, :pipelines] - before_action :define_widget_vars, only: [:merge, :cancel_merge_when_pipeline_succeeds, :merge_check] + before_action :define_show_vars, only: [:diffs, :commits, :conflicts, :conflict_for_path, :builds, :pipelines] before_action :define_commit_vars, only: [:diffs] - before_action :define_diff_comment_vars, only: [:diffs] before_action :ensure_ref_fetched, only: [:show, :diffs, :commits, :builds, :conflicts, :conflict_for_path, :pipelines] before_action :close_merge_request_without_source_project, only: [:show, :diffs, :commits, :builds, :pipelines] + before_action :check_if_can_be_merged, only: :show before_action :apply_diff_view_cookie!, only: [:new_diffs] before_action :build_merge_request, only: [:new, :new_diffs] @@ -39,10 +38,11 @@ class Projects::MergeRequestsController < Projects::ApplicationController @collection_type = "MergeRequest" @merge_requests = merge_requests_collection @merge_requests = @merge_requests.page(params[:page]) + @merge_requests = @merge_requests.preload(merge_request_diff: :merge_request) @issuable_meta_data = issuable_meta_data(@merge_requests, @collection_type) if @merge_requests.out_of_range? && @merge_requests.total_pages != 0 - return redirect_to url_for(params.merge(page: @merge_requests.total_pages)) + return redirect_to url_for(params.merge(page: @merge_requests.total_pages, only_path: true)) end if params[:label_name].present? @@ -74,10 +74,15 @@ class Projects::MergeRequestsController < Projects::ApplicationController def show respond_to do |format| - format.html { define_discussion_vars } + format.html do + define_discussion_vars + define_show_vars + end format.json do - render json: MergeRequestSerializer.new.represent(@merge_request) + Gitlab::PollingInterval.set_header(response, interval: 10_000) + + render json: serializer.represent(@merge_request, basic: params[:basic]) end format.patch do @@ -100,34 +105,11 @@ class Projects::MergeRequestsController < Projects::ApplicationController respond_to do |format| format.html { define_discussion_vars } format.json do - @merge_request_diff = - if params[:diff_id] - @merge_request.merge_request_diffs.viewable.find(params[:diff_id]) - else - @merge_request.merge_request_diff - end - - @merge_request_diffs = @merge_request.merge_request_diffs.viewable.select_without_diff - @comparable_diffs = @merge_request_diffs.select { |diff| diff.id < @merge_request_diff.id } - - if params[:start_sha].present? - @start_sha = params[:start_sha] - @start_version = @comparable_diffs.find { |diff| diff.head_commit_sha == @start_sha } - - unless @start_version - @start_sha = @merge_request_diff.head_commit_sha - @start_version = @merge_request_diff - end - end + define_diff_vars + define_diff_comment_vars @environment = @merge_request.environments_for(current_user).last - if @start_sha - compared_diff_version - else - original_diff_version - end - render json: { html: view_to_html_string("projects/merge_requests/show/_diffs") } end end @@ -139,16 +121,18 @@ class Projects::MergeRequestsController < Projects::ApplicationController def diff_for_path if params[:id] merge_request + define_diff_vars define_diff_comment_vars else build_merge_request + @compare = @merge_request + @diffs = @compare.diffs(diff_options) @diff_notes_disabled = true - @grouped_diff_discussions = {} end define_commit_vars - render_diff_for_path(@merge_request.diffs(diff_options)) + render_diff_for_path(@diffs) end def commits @@ -175,8 +159,8 @@ class Projects::MergeRequestsController < Projects::ApplicationController format.html { define_discussion_vars } format.json do - if @merge_request.conflicts_can_be_resolved_in_ui? - render json: @merge_request.conflicts + if @conflicts_list.can_be_resolved_in_ui? + render json: @conflicts_list elsif @merge_request.can_be_merged? render json: { message: 'The merge conflicts for this merge request have already been resolved. Please return to the merge request.', @@ -193,9 +177,9 @@ class Projects::MergeRequestsController < Projects::ApplicationController end def conflict_for_path - return render_404 unless @merge_request.conflicts_can_be_resolved_in_ui? + return render_404 unless @conflicts_list.can_be_resolved_in_ui? - file = @merge_request.conflicts.file_for_path(params[:old_path], params[:new_path]) + file = @conflicts_list.file_for_path(params[:old_path], params[:new_path]) return render_404 unless file @@ -203,7 +187,7 @@ class Projects::MergeRequestsController < Projects::ApplicationController end def resolve_conflicts - return render_404 unless @merge_request.conflicts_can_be_resolved_in_ui? + return render_404 unless @conflicts_list.can_be_resolved_in_ui? if @merge_request.can_be_merged? render status: :bad_request, json: { message: 'The merge conflicts for this merge request have already been resolved.' } @@ -211,7 +195,9 @@ class Projects::MergeRequestsController < Projects::ApplicationController end begin - MergeRequests::ResolveService.new(@merge_request.source_project, current_user, params).execute(@merge_request) + MergeRequests::Conflicts::ResolveService. + new(merge_request). + execute(current_user, params) flash[:notice] = 'All merge conflicts were resolved. The merge request can now be merged.' @@ -232,8 +218,10 @@ class Projects::MergeRequestsController < Projects::ApplicationController end format.json do + Gitlab::PollingInterval.set_header(response, interval: 10_000) + render json: PipelineSerializer - .new(project: @project, user: @current_user) + .new(project: @project, current_user: @current_user) .represent(@pipelines) end end @@ -245,9 +233,11 @@ class Projects::MergeRequestsController < Projects::ApplicationController format.json do define_pipelines_vars + Gitlab::PollingInterval.set_header(response, interval: 10_000) + render json: { pipelines: PipelineSerializer - .new(project: @project, user: @current_user) + .new(project: @project, current_user: @current_user) .represent(@pipelines) } end @@ -316,17 +306,15 @@ class Projects::MergeRequestsController < Projects::ApplicationController end def remove_wip - MergeRequests::UpdateService.new(project, current_user, wip_event: 'unwip').execute(@merge_request) + @merge_request = MergeRequests::UpdateService + .new(project, current_user, wip_event: 'unwip') + .execute(@merge_request) - redirect_to namespace_project_merge_request_path(@project.namespace, @project, @merge_request), - notice: "The merge request can now be merged." + render json: serializer.represent(@merge_request) end - def merge_check - @merge_request.check_if_can_be_merged - @pipelines = @merge_request.all_pipelines - - render partial: "projects/merge_requests/widget/show.html.haml", layout: false + def commit_change_content + render partial: 'projects/merge_requests/widget/commit_change_content', layout: false end def cancel_merge_when_pipeline_succeeds @@ -337,65 +325,22 @@ class Projects::MergeRequestsController < Projects::ApplicationController MergeRequests::MergeWhenPipelineSucceedsService .new(@project, current_user) .cancel(@merge_request) + + render json: serializer.represent(@merge_request) end def merge return access_denied! unless @merge_request.can_be_merged_by?(current_user) - # Disable the CI check if merge_when_pipeline_succeeds is enabled since we have - # to wait until CI completes to know - unless @merge_request.mergeable?(skip_ci_check: merge_when_pipeline_succeeds_active?) - @status = :failed - return - end - - if params[:sha] != @merge_request.diff_head_sha - @status = :sha_mismatch - return - end - - @merge_request.update(merge_error: nil) - - if params[:merge_when_pipeline_succeeds].present? - unless @merge_request.head_pipeline - @status = :failed - return - end - - if @merge_request.head_pipeline.active? - MergeRequests::MergeWhenPipelineSucceedsService - .new(@project, current_user, merge_params) - .execute(@merge_request) + status = merge! - @status = :merge_when_pipeline_succeeds - elsif @merge_request.head_pipeline.success? - # This can be triggered when a user clicks the auto merge button while - # the tests finish at about the same time - MergeWorker.perform_async(@merge_request.id, current_user.id, params) - @status = :success - else - @status = :failed - end + if @merge_request.merge_error + render json: { status: status, merge_error: @merge_request.merge_error } else - MergeWorker.perform_async(@merge_request.id, current_user.id, params) - @status = :success + render json: { status: status } end end - def merge_widget_refresh - @status = - if merge_request.merge_when_pipeline_succeeds - :merge_when_pipeline_succeeds - else - # Only MRs that can be merged end in this action - # MR can be already picked up for merge / merged already or can be waiting for worker to be picked up - # in last case it does not have any special status. Possible error is handled inside widget js function - :success - end - - render 'merge' - end - def branch_from # This is always source @source_project = @merge_request.nil? ? @project : @merge_request.source_project @@ -445,37 +390,9 @@ class Projects::MergeRequestsController < Projects::ApplicationController end end - def ci_status - pipeline = @merge_request.head_pipeline - @pipelines = @merge_request.all_pipelines - - if pipeline - status = pipeline.status - coverage = pipeline.try(:coverage) - - status = "success_with_warnings" if pipeline.success? && pipeline.has_warnings? - - status ||= "preparing" - else - ci_service = @merge_request.source_project.try(:ci_service) - status = ci_service.commit_status(merge_request.diff_head_sha, merge_request.source_branch) if ci_service - end - - response = { - title: merge_request.title, - sha: (merge_request.diff_head_commit.short_id if merge_request.diff_head_sha), - status: status, - coverage: coverage, - pipeline: pipeline.try(:id), - has_ci: @merge_request.has_ci? - } - - render json: response - end - def pipeline_status render json: PipelineSerializer - .new(project: @project, user: @current_user) + .new(project: @project, current_user: @current_user) .represent_status(@merge_request.head_pipeline) end @@ -491,10 +408,19 @@ class Projects::MergeRequestsController < Projects::ApplicationController stop_namespace_project_environment_path(project.namespace, project, environment) end + metrics_url = + if can?(current_user, :read_environment, environment) && environment.has_metrics? + metrics_namespace_project_environment_deployment_path(environment.project.namespace, + environment.project, + environment, + deployment) + end + { id: environment.id, name: environment.name, url: namespace_project_environment_path(project.namespace, project, environment), + metrics_url: metrics_url, stop_url: stop_url, external_url: environment.external_url, external_url_formatted: environment.formatted_external_url, @@ -533,7 +459,9 @@ class Projects::MergeRequestsController < Projects::ApplicationController end def authorize_can_resolve_conflicts! - return render_404 unless @merge_request.conflicts_can_be_resolved_by?(current_user) + @conflicts_list = MergeRequests::Conflicts::ListService.new(@merge_request) + + return render_404 unless @conflicts_list.can_be_resolved_by?(current_user) end def module_enabled @@ -569,24 +497,7 @@ class Projects::MergeRequestsController < Projects::ApplicationController @note = @project.notes.new(noteable: @merge_request) @discussions = @merge_request.discussions - - preload_noteable_for_regular_notes(@discussions.flat_map(&:notes)) - - # This is not executed lazily - @notes = Banzai::NoteRenderer.render( - @discussions.flat_map(&:notes), - @project, - current_user, - @path, - @project_wiki, - @ref - ) - - preload_max_access_for_authors(@notes, @project) - end - - def define_widget_vars - @pipeline = @merge_request.head_pipeline + @notes = prepare_notes_for_rendering(@discussions.flat_map(&:notes)) end def define_commit_vars @@ -594,23 +505,49 @@ class Projects::MergeRequestsController < Projects::ApplicationController @base_commit = @merge_request.diff_base_commit || @merge_request.likely_diff_base_commit end + def define_diff_vars + @merge_request_diff = + if params[:diff_id] + @merge_request.merge_request_diffs.viewable.find(params[:diff_id]) + else + @merge_request.merge_request_diff + end + + @merge_request_diffs = @merge_request.merge_request_diffs.viewable.select_without_diff + @comparable_diffs = @merge_request_diffs.select { |diff| diff.id < @merge_request_diff.id } + + if params[:start_sha].present? + @start_sha = params[:start_sha] + @start_version = @comparable_diffs.find { |diff| diff.head_commit_sha == @start_sha } + + unless @start_version + @start_sha = @merge_request_diff.head_commit_sha + @start_version = @merge_request_diff + end + end + + @compare = + if @start_sha + @merge_request_diff.compare_with(@start_sha) + else + @merge_request_diff + end + + @diffs = @compare.diffs(diff_options) + end + def define_diff_comment_vars - @comments_target = { + @new_diff_note_attrs = { noteable_type: 'MergeRequest', noteable_id: @merge_request.id } + @diff_notes_disabled = false + @use_legacy_diff_notes = !@merge_request.has_complete_diff_refs? - @grouped_diff_discussions = @merge_request.notes.inc_relations_for_view.grouped_diff_discussions - Banzai::NoteRenderer.render( - @grouped_diff_discussions.values.flat_map(&:notes), - @project, - current_user, - @path, - @project_wiki, - @ref - ) + @grouped_diff_discussions = @merge_request.grouped_diff_discussions(@compare.diff_refs) + @notes = prepare_notes_for_rendering(@grouped_diff_discussions.values.flatten.flat_map(&:notes)) end def define_pipelines_vars @@ -693,19 +630,55 @@ class Projects::MergeRequestsController < Projects::ApplicationController @merge_request = MergeRequests::BuildService.new(project, current_user, merge_request_params.merge(diff_options: diff_options)).execute end - def compared_diff_version - @diff_notes_disabled = true - @diffs = @merge_request_diff.compare_with(@start_sha).diffs(diff_options) + def close_merge_request_without_source_project + if !@merge_request.source_project && @merge_request.open? + @merge_request.close + end end - def original_diff_version - @diff_notes_disabled = !@merge_request_diff.latest? - @diffs = @merge_request_diff.diffs(diff_options) + private + + def check_if_can_be_merged + @merge_request.check_if_can_be_merged end - def close_merge_request_without_source_project - if !@merge_request.source_project && @merge_request.open? - @merge_request.close + def merge! + # Disable the CI check if merge_when_pipeline_succeeds is enabled since we have + # to wait until CI completes to know + unless @merge_request.mergeable?(skip_ci_check: merge_when_pipeline_succeeds_active?) + return :failed end + + return :sha_mismatch if params[:sha] != @merge_request.diff_head_sha + + @merge_request.update(merge_error: nil) + + if params[:merge_when_pipeline_succeeds].present? + return :failed unless @merge_request.head_pipeline + + if @merge_request.head_pipeline.active? + MergeRequests::MergeWhenPipelineSucceedsService + .new(@project, current_user, merge_params) + .execute(@merge_request) + + :merge_when_pipeline_succeeds + elsif @merge_request.head_pipeline.success? + # This can be triggered when a user clicks the auto merge button while + # the tests finish at about the same time + MergeWorker.perform_async(@merge_request.id, current_user.id, params) + + :success + else + :failed + end + else + MergeWorker.perform_async(@merge_request.id, current_user.id, params) + + :success + end + end + + def serializer + MergeRequestSerializer.new(current_user: current_user, project: merge_request.project) end end diff --git a/app/controllers/projects/milestones_controller.rb b/app/controllers/projects/milestones_controller.rb index 5922e686cd0..c56bce19eee 100644 --- a/app/controllers/projects/milestones_controller.rb +++ b/app/controllers/projects/milestones_controller.rb @@ -1,12 +1,14 @@ class Projects::MilestonesController < Projects::ApplicationController + include MilestoneActions + before_action :module_enabled - before_action :milestone, only: [:edit, :update, :destroy, :show, :sort_issues, :sort_merge_requests] + before_action :milestone, only: [:edit, :update, :destroy, :show, :sort_issues, :sort_merge_requests, :merge_requests, :participants, :labels] # Allow read any milestone before_action :authorize_read_milestone! # Allow admin milestone - before_action :authorize_admin_milestone!, except: [:index, :show] + before_action :authorize_admin_milestone!, except: [:index, :show, :merge_requests, :participants, :labels] respond_to :html @@ -21,9 +23,10 @@ class Projects::MilestonesController < Projects::ApplicationController @sort = params[:sort] || 'due_date_asc' @milestones = @milestones.sort(@sort) - @milestones = @milestones.includes(:project) respond_to do |format| format.html do + @project_namespace = @project.namespace.becomes(Namespace) + @milestones = @milestones.includes(:project) @milestones = @milestones.page(params[:page]) end format.json do diff --git a/app/controllers/projects/notes_controller.rb b/app/controllers/projects/notes_controller.rb index d00177e7612..41a13f6f577 100644 --- a/app/controllers/projects/notes_controller.rb +++ b/app/controllers/projects/notes_controller.rb @@ -1,62 +1,22 @@ class Projects::NotesController < Projects::ApplicationController + include NotesActions include ToggleAwardEmoji - # Authorize before_action :authorize_read_note! before_action :authorize_create_note!, only: [:create] - before_action :authorize_admin_note!, only: [:update, :destroy] before_action :authorize_resolve_note!, only: [:resolve, :unresolve] - before_action :find_current_user_notes, only: [:index] - - def index - current_fetched_at = Time.now.to_i - - notes_json = { notes: [], last_fetched_at: current_fetched_at } - - @notes.each do |note| - next if note.cross_reference_not_visible_for?(current_user) - - notes_json[:notes] << note_json(note) - end - - render json: notes_json - end + # + # This is a fix to make spinach feature tests passing: + # Controller actions are returned from AbstractController::Base and methods of parent classes are + # excluded in order to return only specific controller related methods. + # That is ok for the app (no :create method in ancestors) + # but fails for tests because there is a :create method on FactoryGirl (one of the ancestors) + # + # see https://github.com/rails/rails/blob/v4.2.7/actionpack/lib/abstract_controller/base.rb#L78 + # def create - create_params = note_params.merge(merge_request_diff_head_sha: params[:merge_request_diff_head_sha]) - @note = Notes::CreateService.new(project, current_user, create_params).execute - - if @note.is_a?(Note) - Banzai::NoteRenderer.render([@note], @project, current_user) - end - - respond_to do |format| - format.json { render json: note_json(@note) } - format.html { redirect_back_or_default } - end - end - - def update - @note = Notes::UpdateService.new(project, current_user, note_params).execute(note) - - if @note.is_a?(Note) - Banzai::NoteRenderer.render([@note], @project, current_user) - end - - respond_to do |format| - format.json { render json: note_json(@note) } - format.html { redirect_back_or_default } - end - end - - def destroy - if note.editable? - Notes::DestroyService.new(project, current_user).execute(note) - end - - respond_to do |format| - format.js { head :ok } - end + super end def delete_attachment @@ -102,120 +62,11 @@ class Projects::NotesController < Projects::ApplicationController end alias_method :awardable, :note - def note_html(note) - render_to_string( - "projects/notes/_note", - layout: false, - formats: [:html], - locals: { note: note } - ) - end - - def diff_discussion_html(discussion) - return unless discussion.diff_discussion? - - if params[:view] == 'parallel' - template = "discussions/_parallel_diff_discussion" - locals = - if params[:line_type] == 'old' - { discussion_left: discussion, discussion_right: nil } - else - { discussion_left: nil, discussion_right: discussion } - end - else - template = "discussions/_diff_discussion" - locals = { discussion: discussion } - end - - render_to_string( - template, - layout: false, - formats: [:html], - locals: locals - ) - end - - def discussion_html(discussion) - return unless discussion.diff_discussion? - - render_to_string( - "discussions/_discussion", - layout: false, - formats: [:html], - locals: { discussion: discussion } - ) - end - - def note_json(note) - attrs = { - id: note.id - } - - if note.persisted? - Banzai::NoteRenderer.render([note], @project, current_user) - - attrs.merge!( - valid: true, - discussion_id: note.discussion_id, - html: note_html(note), - note: note.note - ) - - if note.diff_note? - discussion = note.to_discussion - - attrs.merge!( - diff_discussion_html: diff_discussion_html(discussion), - discussion_html: discussion_html(discussion) - ) - - # The discussion_id is used to add the comment to the correct discussion - # element on the merge request page. Among other things, the discussion_id - # contains the sha of head commit of the merge request. - # When new commits are pushed into the merge request after the initial - # load of the merge request page, the discussion elements will still have - # the old discussion_ids, with the old head commit sha. The new comment, - # however, will have the new discussion_id with the new commit sha. - # To ensure that these new comments will still end up in the correct - # discussion element, we also send the original discussion_id, with the - # old commit sha, along, and fall back on this value when no discussion - # element with the new discussion_id could be found. - if note.new_diff_note? && note.position != note.original_position - attrs[:original_discussion_id] = note.original_discussion_id - end - end - else - attrs.merge!( - valid: false, - errors: note.errors - ) - end - - attrs[:commands_changes] = note.commands_changes - attrs - end - - def authorize_admin_note! - return access_denied! unless can?(current_user, :admin_note, note) + def finder_params + params.merge(last_fetched_at: last_fetched_at) end def authorize_resolve_note! return access_denied! unless can?(current_user, :resolve_note, note) end - - def note_params - params.require(:note).permit( - :note, :noteable, :noteable_id, :noteable_type, :project_id, - :attachment, :line_code, :commit_id, :type, :position - ) - end - - def find_current_user_notes - @notes = NotesFinder.new(project, current_user, params.merge(last_fetched_at: last_fetched_at)) - .execute.inc_author - end - - def last_fetched_at - request.headers['X-Last-Fetched-At'] - end end diff --git a/app/controllers/projects/pages_controller.rb b/app/controllers/projects/pages_controller.rb index fbd18b68141..93b2c180810 100644 --- a/app/controllers/projects/pages_controller.rb +++ b/app/controllers/projects/pages_controller.rb @@ -1,6 +1,7 @@ class Projects::PagesController < Projects::ApplicationController layout 'project_settings' + before_action :require_pages_enabled! before_action :authorize_read_pages!, only: [:show] before_action :authorize_update_pages!, except: [:show] diff --git a/app/controllers/projects/pages_domains_controller.rb b/app/controllers/projects/pages_domains_controller.rb index b8c253f6ae3..3a93977fd27 100644 --- a/app/controllers/projects/pages_domains_controller.rb +++ b/app/controllers/projects/pages_domains_controller.rb @@ -1,6 +1,7 @@ class Projects::PagesDomainsController < Projects::ApplicationController layout 'project_settings' + before_action :require_pages_enabled! before_action :authorize_update_pages!, except: [:show] before_action :domain, only: [:show, :destroy] diff --git a/app/controllers/projects/pipeline_schedules_controller.rb b/app/controllers/projects/pipeline_schedules_controller.rb new file mode 100644 index 00000000000..1616b2cb6b8 --- /dev/null +++ b/app/controllers/projects/pipeline_schedules_controller.rb @@ -0,0 +1,68 @@ +class Projects::PipelineSchedulesController < Projects::ApplicationController + before_action :authorize_read_pipeline_schedule! + before_action :authorize_create_pipeline_schedule!, only: [:new, :create, :edit, :take_ownership, :update] + before_action :authorize_admin_pipeline_schedule!, only: [:destroy] + + before_action :schedule, only: [:edit, :update, :destroy, :take_ownership] + + def index + @scope = params[:scope] + @all_schedules = PipelineSchedulesFinder.new(@project).execute + @schedules = PipelineSchedulesFinder.new(@project).execute(scope: params[:scope]) + .includes(:last_pipeline) + end + + def new + @schedule = project.pipeline_schedules.new + end + + def create + @schedule = Ci::CreatePipelineScheduleService + .new(@project, current_user, schedule_params) + .execute + + if @schedule.persisted? + redirect_to pipeline_schedules_path(@project) + else + render :new + end + end + + def edit + end + + def update + if schedule.update(schedule_params) + redirect_to namespace_project_pipeline_schedules_path(@project.namespace.becomes(Namespace), @project) + else + render :edit + end + end + + def take_ownership + if schedule.update(owner: current_user) + redirect_to pipeline_schedules_path(@project) + else + redirect_to pipeline_schedules_path(@project), alert: "Failed to change the owner" + end + end + + def destroy + if schedule.destroy + redirect_to pipeline_schedules_path(@project) + else + redirect_to pipeline_schedules_path(@project), alert: "Failed to remove the pipeline schedule" + end + end + + private + + def schedule + @schedule ||= project.pipeline_schedules.find(params[:id]) + end + + def schedule_params + params.require(:schedule) + .permit(:description, :cron, :cron_timezone, :ref, :active) + end +end diff --git a/app/controllers/projects/pipelines_controller.rb b/app/controllers/projects/pipelines_controller.rb index 43a1abaa662..602d3dd8c1c 100644 --- a/app/controllers/projects/pipelines_controller.rb +++ b/app/controllers/projects/pipelines_controller.rb @@ -1,27 +1,31 @@ class Projects::PipelinesController < Projects::ApplicationController before_action :pipeline, except: [:index, :new, :create, :charts] - before_action :commit, only: [:show, :builds] + before_action :commit, only: [:show, :builds, :failures] before_action :authorize_read_pipeline! before_action :authorize_create_pipeline!, only: [:new, :create] before_action :authorize_update_pipeline!, only: [:retry, :cancel] before_action :builds_enabled, only: :charts + wrap_parameters Ci::Pipeline + + POLLING_INTERVAL = 10_000 + def index @scope = params[:scope] @pipelines = PipelinesFinder - .new(project) - .execute(scope: @scope) + .new(project, scope: @scope) + .execute .page(params[:page]) .per(30) @running_count = PipelinesFinder - .new(project).execute(scope: 'running').count + .new(project, scope: 'running').execute.count @pending_count = PipelinesFinder - .new(project).execute(scope: 'pending').count + .new(project, scope: 'pending').execute.count @finished_count = PipelinesFinder - .new(project).execute(scope: 'finished').count + .new(project, scope: 'finished').execute.count @pipelines_count = PipelinesFinder .new(project).execute.count @@ -29,16 +33,18 @@ class Projects::PipelinesController < Projects::ApplicationController respond_to do |format| format.html format.json do + Gitlab::PollingInterval.set_header(response, interval: POLLING_INTERVAL) + render json: { pipelines: PipelineSerializer - .new(project: @project, user: @current_user) + .new(project: @project, current_user: @current_user) .with_pagination(request, response) .represent(@pipelines), count: { all: @pipelines_count, running: @running_count, pending: @pending_count, - finished: @finished_count, + finished: @finished_count } } end @@ -53,28 +59,42 @@ class Projects::PipelinesController < Projects::ApplicationController @pipeline = Ci::CreatePipelineService .new(project, current_user, create_params) .execute(ignore_skip_ci: true, save_on_errors: false) - unless @pipeline.persisted? + + if @pipeline.persisted? + redirect_to namespace_project_pipeline_path(project.namespace, project, @pipeline) + else render 'new' - return end - - redirect_to namespace_project_pipeline_path(project.namespace, project, @pipeline) end def show + respond_to do |format| + format.html + format.json do + Gitlab::PollingInterval.set_header(response, interval: POLLING_INTERVAL) + + render json: PipelineSerializer + .new(project: @project, current_user: @current_user) + .represent(@pipeline, grouped: true) + end + end end def builds - respond_to do |format| - format.html do - render 'show' - end + render_show + end + + def failures + if @pipeline.statuses.latest.failed.present? + render_show + else + redirect_to pipeline_path(@pipeline) end end def status render json: PipelineSerializer - .new(project: @project, user: @current_user) + .new(project: @project, current_user: @current_user) .represent_status(@pipeline) end @@ -90,13 +110,25 @@ class Projects::PipelinesController < Projects::ApplicationController def retry pipeline.retry_failed(current_user) - redirect_back_or_default default: namespace_project_pipelines_path(project.namespace, project) + respond_to do |format| + format.html do + redirect_back_or_default default: namespace_project_pipelines_path(project.namespace, project) + end + + format.json { head :no_content } + end end def cancel pipeline.cancel_running - redirect_back_or_default default: namespace_project_pipelines_path(project.namespace, project) + respond_to do |format| + format.html do + redirect_back_or_default default: namespace_project_pipelines_path(project.namespace, project) + end + + format.json { head :no_content } + end end def charts @@ -109,12 +141,20 @@ class Projects::PipelinesController < Projects::ApplicationController private + def render_show + respond_to do |format| + format.html do + render 'show' + end + end + end + def create_params params.require(:pipeline).permit(:ref) end def pipeline - @pipeline ||= project.pipelines.find_by!(id: params[:id]) + @pipeline ||= project.pipelines.find_by!(id: params[:id]).present(current_user: current_user) end def commit diff --git a/app/controllers/projects/pipelines_settings_controller.rb b/app/controllers/projects/pipelines_settings_controller.rb index c8c80551ac9..38a47651000 100644 --- a/app/controllers/projects/pipelines_settings_controller.rb +++ b/app/controllers/projects/pipelines_settings_controller.rb @@ -7,7 +7,7 @@ class Projects::PipelinesSettingsController < Projects::ApplicationController def update if @project.update_attributes(update_params) - flash[:notice] = "CI/CD Pipelines settings for '#{@project.name}' were successfully updated." + flash[:notice] = "Pipelines settings for '#{@project.name}' were successfully updated." redirect_to namespace_project_settings_ci_cd_path(@project.namespace, @project) else render 'show' @@ -23,7 +23,7 @@ class Projects::PipelinesSettingsController < Projects::ApplicationController def update_params params.require(:project).permit( :runners_token, :builds_enabled, :build_allow_git_fetch, :build_timeout_in_minutes, :build_coverage_regex, - :public_builds + :public_builds, :auto_cancel_pending_pipelines ) end end diff --git a/app/controllers/projects/project_members_controller.rb b/app/controllers/projects/project_members_controller.rb index 6e158e685e9..d2d26738582 100644 --- a/app/controllers/projects/project_members_controller.rb +++ b/app/controllers/projects/project_members_controller.rb @@ -10,18 +10,6 @@ class Projects::ProjectMembersController < Projects::ApplicationController redirect_to namespace_project_settings_members_path(@project.namespace, @project, sort: sort) end - def create - status = Members::CreateService.new(@project, current_user, params).execute - - redirect_url = namespace_project_settings_members_path(@project.namespace, @project) - - if status - redirect_to redirect_url, notice: 'Users were successfully added.' - else - redirect_to redirect_url, alert: 'No users or groups specified.' - end - end - def update @project_member = @project.project_members.find(params[:id]) @@ -30,18 +18,6 @@ class Projects::ProjectMembersController < Projects::ApplicationController @project_member.update_attributes(member_params) end - def destroy - Members::DestroyService.new(@project, current_user, params). - execute(:all) - - respond_to do |format| - format.html do - redirect_to namespace_project_settings_members_path(@project.namespace, @project) - end - format.js { head :ok } - end - end - def resend_invite redirect_path = namespace_project_settings_members_path(@project.namespace, @project) diff --git a/app/controllers/projects/protected_branches_controller.rb b/app/controllers/projects/protected_branches_controller.rb index a8cb07eb67a..ba24fa9acfe 100644 --- a/app/controllers/projects/protected_branches_controller.rb +++ b/app/controllers/projects/protected_branches_controller.rb @@ -1,58 +1,23 @@ -class Projects::ProtectedBranchesController < Projects::ApplicationController - include RepositorySettingsRedirect - # Authorize - before_action :require_non_empty_project - before_action :authorize_admin_project! - before_action :load_protected_branch, only: [:show, :update, :destroy] +class Projects::ProtectedBranchesController < Projects::ProtectedRefsController + protected - layout "project_settings" - - def index - redirect_to_repository_settings(@project) - end - - def create - @protected_branch = ::ProtectedBranches::CreateService.new(@project, current_user, protected_branch_params).execute - unless @protected_branch.persisted? - flash[:alert] = @protected_branches.errors.full_messages.join(', ').html_safe - end - redirect_to_repository_settings(@project) - end - - def show - @matching_branches = @protected_branch.matching(@project.repository.branches) + def project_refs + @project.repository.branches end - def update - @protected_branch = ::ProtectedBranches::UpdateService.new(@project, current_user, protected_branch_params).execute(@protected_branch) - - if @protected_branch.valid? - respond_to do |format| - format.json { render json: @protected_branch, status: :ok } - end - else - respond_to do |format| - format.json { render json: @protected_branch.errors, status: :unprocessable_entity } - end - end + def create_service_class + ::ProtectedBranches::CreateService end - def destroy - @protected_branch.destroy - - respond_to do |format| - format.html { redirect_to_repository_settings(@project) } - format.js { head :ok } - end + def update_service_class + ::ProtectedBranches::UpdateService end - private - - def load_protected_branch - @protected_branch = @project.protected_branches.find(params[:id]) + def load_protected_ref + @protected_ref = @project.protected_branches.find(params[:id]) end - def protected_branch_params + def protected_ref_params params.require(:protected_branch).permit(:name, merge_access_levels_attributes: [:access_level, :id], push_access_levels_attributes: [:access_level, :id]) diff --git a/app/controllers/projects/protected_refs_controller.rb b/app/controllers/projects/protected_refs_controller.rb new file mode 100644 index 00000000000..083a70968e5 --- /dev/null +++ b/app/controllers/projects/protected_refs_controller.rb @@ -0,0 +1,47 @@ +class Projects::ProtectedRefsController < Projects::ApplicationController + include RepositorySettingsRedirect + + # Authorize + before_action :require_non_empty_project + before_action :authorize_admin_project! + before_action :load_protected_ref, only: [:show, :update, :destroy] + + layout "project_settings" + + def index + redirect_to_repository_settings(@project) + end + + def create + protected_ref = create_service_class.new(@project, current_user, protected_ref_params).execute + + unless protected_ref.persisted? + flash[:alert] = protected_ref.errors.full_messages.join(', ').html_safe + end + + redirect_to_repository_settings(@project) + end + + def show + @matching_refs = @protected_ref.matching(project_refs) + end + + def update + @protected_ref = update_service_class.new(@project, current_user, protected_ref_params).execute(@protected_ref) + + if @protected_ref.valid? + render json: @protected_ref, status: :ok + else + render json: @protected_ref.errors, status: :unprocessable_entity + end + end + + def destroy + @protected_ref.destroy + + respond_to do |format| + format.html { redirect_to_repository_settings(@project) } + format.js { head :ok } + end + end +end diff --git a/app/controllers/projects/protected_tags_controller.rb b/app/controllers/projects/protected_tags_controller.rb new file mode 100644 index 00000000000..c61ddf145e6 --- /dev/null +++ b/app/controllers/projects/protected_tags_controller.rb @@ -0,0 +1,23 @@ +class Projects::ProtectedTagsController < Projects::ProtectedRefsController + protected + + def project_refs + @project.repository.tags + end + + def create_service_class + ::ProtectedTags::CreateService + end + + def update_service_class + ::ProtectedTags::UpdateService + end + + def load_protected_ref + @protected_ref = @project.protected_tags.find(params[:id]) + end + + def protected_ref_params + params.require(:protected_tag).permit(:name, create_access_levels_attributes: [:access_level, :id]) + end +end diff --git a/app/controllers/projects/raw_controller.rb b/app/controllers/projects/raw_controller.rb index c55b37ae0dd..a02cc477e08 100644 --- a/app/controllers/projects/raw_controller.rb +++ b/app/controllers/projects/raw_controller.rb @@ -15,7 +15,7 @@ class Projects::RawController < Projects::ApplicationController return if cached_blob? - if @blob.lfs_pointer? && project.lfs_enabled? + if @blob.stored_externally? send_lfs_object else send_git_blob @repository, @blob diff --git a/app/controllers/projects/registry/application_controller.rb b/app/controllers/projects/registry/application_controller.rb new file mode 100644 index 00000000000..a56f9c58726 --- /dev/null +++ b/app/controllers/projects/registry/application_controller.rb @@ -0,0 +1,16 @@ +module Projects + module Registry + class ApplicationController < Projects::ApplicationController + layout 'project' + + before_action :verify_registry_enabled! + before_action :authorize_read_container_image! + + private + + def verify_registry_enabled! + render_404 unless Gitlab.config.registry.enabled + end + end + end +end diff --git a/app/controllers/projects/registry/repositories_controller.rb b/app/controllers/projects/registry/repositories_controller.rb new file mode 100644 index 00000000000..17f391ba07f --- /dev/null +++ b/app/controllers/projects/registry/repositories_controller.rb @@ -0,0 +1,43 @@ +module Projects + module Registry + class RepositoriesController < ::Projects::Registry::ApplicationController + before_action :authorize_update_container_image!, only: [:destroy] + before_action :ensure_root_container_repository!, only: [:index] + + def index + @images = project.container_repositories + end + + def destroy + if image.destroy + redirect_to project_container_registry_path(@project), + notice: 'Image repository has been removed successfully!' + else + redirect_to project_container_registry_path(@project), + alert: 'Failed to remove image repository!' + end + end + + private + + def image + @image ||= project.container_repositories.find(params[:id]) + end + + ## + # Container repository object for root project path. + # + # Needed to maintain a backwards compatibility. + # + def ensure_root_container_repository! + ContainerRegistry::Path.new(@project.full_path).tap do |path| + break if path.has_repository? + + ContainerRepository.build_from_path(path).tap do |repository| + repository.save! if repository.has_tags? + end + end + end + end + end +end diff --git a/app/controllers/projects/registry/tags_controller.rb b/app/controllers/projects/registry/tags_controller.rb new file mode 100644 index 00000000000..d689cade3ab --- /dev/null +++ b/app/controllers/projects/registry/tags_controller.rb @@ -0,0 +1,28 @@ +module Projects + module Registry + class TagsController < ::Projects::Registry::ApplicationController + before_action :authorize_update_container_image!, only: [:destroy] + + def destroy + if tag.delete + redirect_to project_container_registry_path(@project), + notice: 'Registry tag has been removed successfully!' + else + redirect_to project_container_registry_path(@project), + alert: 'Failed to remove registry tag!' + end + end + + private + + def image + @image ||= project.container_repositories + .find(params[:repository_id]) + end + + def tag + @tag ||= image.tag(params[:id]) + end + end + end +end diff --git a/app/controllers/projects/settings/integrations_controller.rb b/app/controllers/projects/settings/integrations_controller.rb index fb2a4837735..1ff08cce8cb 100644 --- a/app/controllers/projects/settings/integrations_controller.rb +++ b/app/controllers/projects/settings/integrations_controller.rb @@ -5,7 +5,7 @@ module Projects before_action :authorize_admin_project! layout "project_settings" - + def show @hooks = @project.hooks @hook = ProjectHook.new diff --git a/app/controllers/projects/settings/repository_controller.rb b/app/controllers/projects/settings/repository_controller.rb index b6ce4abca45..44de8a49593 100644 --- a/app/controllers/projects/settings/repository_controller.rb +++ b/app/controllers/projects/settings/repository_controller.rb @@ -4,46 +4,48 @@ module Projects before_action :authorize_admin_project! def show - @deploy_keys = DeployKeysPresenter - .new(@project, current_user: current_user) + @deploy_keys = DeployKeysPresenter.new(@project, current_user: current_user) - define_protected_branches + define_protected_refs end private - def define_protected_branches - load_protected_branches + def define_protected_refs + @protected_branches = @project.protected_branches.order(:name).page(params[:page]) + @protected_tags = @project.protected_tags.order(:name).page(params[:page]) @protected_branch = @project.protected_branches.new + @protected_tag = @project.protected_tags.new load_gon_index end - def load_protected_branches - @protected_branches = @project.protected_branches.order(:name).page(params[:page]) - end - def access_levels_options { - push_access_levels: { - roles: ProtectedBranch::PushAccessLevel.human_access_levels.map do |id, text| - { id: id, text: text, before_divider: true } - end - }, - merge_access_levels: { - roles: ProtectedBranch::MergeAccessLevel.human_access_levels.map do |id, text| - { id: id, text: text, before_divider: true } - end - } + create_access_levels: levels_for_dropdown(ProtectedTag::CreateAccessLevel), + push_access_levels: levels_for_dropdown(ProtectedBranch::PushAccessLevel), + merge_access_levels: levels_for_dropdown(ProtectedBranch::MergeAccessLevel) } end - def open_branches - branches = @project.open_branches.map { |br| { text: br.name, id: br.name, title: br.name } } - { open_branches: branches } + def levels_for_dropdown(access_level_type) + roles = access_level_type.human_access_levels.map do |id, text| + { id: id, text: text, before_divider: true } + end + { roles: roles } + end + + def protectable_tags_for_dropdown + { open_tags: ProtectableDropdown.new(@project, :tags).hash } + end + + def protectable_branches_for_dropdown + { open_branches: ProtectableDropdown.new(@project, :branches).hash } end def load_gon_index - gon.push(open_branches.merge(access_levels_options)) + gon.push(protectable_tags_for_dropdown) + gon.push(protectable_branches_for_dropdown) + gon.push(access_levels_options) end end end diff --git a/app/controllers/projects/snippets_controller.rb b/app/controllers/projects/snippets_controller.rb index ea1a97b7cf0..3b2b0d9e502 100644 --- a/app/controllers/projects/snippets_controller.rb +++ b/app/controllers/projects/snippets_controller.rb @@ -1,7 +1,9 @@ class Projects::SnippetsController < Projects::ApplicationController + include RendersNotes include ToggleAwardEmoji include SpammableActions include SnippetsActions + include RendersBlob before_action :module_enabled before_action :snippet, only: [:show, :edit, :destroy, :update, :raw, :toggle_award_emoji, :mark_as_spam] @@ -21,12 +23,11 @@ class Projects::SnippetsController < Projects::ApplicationController respond_to :html def index - @snippets = SnippetsFinder.new.execute( + @snippets = SnippetsFinder.new( current_user, - filter: :by_project, project: @project, scope: params[:scope] - ) + ).execute @snippets = @snippets.page(params[:page]) if @snippets.out_of_range? && @snippets.total_pages != 0 redirect_to namespace_project_snippets_path(page: @snippets.total_pages) @@ -54,9 +55,23 @@ class Projects::SnippetsController < Projects::ApplicationController end def show - @note = @project.notes.new(noteable: @snippet) - @notes = Banzai::NoteRenderer.render(@snippet.notes.fresh, @project, current_user) - @noteable = @snippet + blob = @snippet.blob + override_max_blob_size(blob) + + respond_to do |format| + format.html do + @note = @project.notes.new(noteable: @snippet) + @noteable = @snippet + + @discussions = @snippet.discussions + @notes = prepare_notes_for_rendering(@discussions.flat_map(&:notes)) + render 'show' + end + + format.json do + render_blob_json(blob) + end + end end def destroy diff --git a/app/controllers/projects/tags_controller.rb b/app/controllers/projects/tags_controller.rb index e13f0bde315..afbea3e2b40 100644 --- a/app/controllers/projects/tags_controller.rb +++ b/app/controllers/projects/tags_controller.rb @@ -38,6 +38,8 @@ class Projects::TagsController < Projects::ApplicationController redirect_to namespace_project_tag_path(@project.namespace, @project, @tag.name) else @error = result[:message] + @message = params[:message] + @release_description = params[:release_description] render action: 'new' end end @@ -48,7 +50,7 @@ class Projects::TagsController < Projects::ApplicationController respond_to do |format| if result[:status] == :success format.html do - redirect_to namespace_project_tags_path(@project.namespace, @project) + redirect_to namespace_project_tags_path(@project.namespace, @project), status: 303 end format.js @@ -57,7 +59,7 @@ class Projects::TagsController < Projects::ApplicationController format.html do redirect_to namespace_project_tags_path(@project.namespace, @project), - alert: @error + alert: @error, status: 303 end format.js do diff --git a/app/controllers/projects/tree_controller.rb b/app/controllers/projects/tree_controller.rb index 637b61504d8..f8eb8e00a5d 100644 --- a/app/controllers/projects/tree_controller.rb +++ b/app/controllers/projects/tree_controller.rb @@ -24,6 +24,8 @@ class Projects::TreeController < Projects::ApplicationController end end + @last_commit = @repository.last_commit_for_path(@commit.id, @tree.path) || @commit + respond_to do |format| format.html # Disable cache so browser history works @@ -34,21 +36,21 @@ class Projects::TreeController < Projects::ApplicationController def create_dir return render_404 unless @commit_params.values.all? - update_ref + set_start_branch_to_branch_name create_commit(Files::CreateDirService, success_notice: "The directory has been successfully created.", - success_path: namespace_project_tree_path(@project.namespace, @project, File.join(@target_branch, @dir_name)), + success_path: namespace_project_tree_path(@project.namespace, @project, File.join(@branch_name, @dir_name)), failure_path: namespace_project_tree_path(@project.namespace, @project, @ref)) end private def assign_dir_vars - @target_branch = params[:target_branch] + @branch_name = params[:branch_name] @dir_name = File.join(@path, params[:dir_name]) @commit_params = { file_path: @dir_name, - commit_message: params[:commit_message], + commit_message: params[:commit_message] } end end diff --git a/app/controllers/projects/triggers_controller.rb b/app/controllers/projects/triggers_controller.rb index c47198c5eb6..afa56de920b 100644 --- a/app/controllers/projects/triggers_controller.rb +++ b/app/controllers/projects/triggers_controller.rb @@ -11,7 +11,7 @@ class Projects::TriggersController < Projects::ApplicationController end def create - @trigger = project.triggers.create(create_params.merge(owner: current_user)) + @trigger = project.triggers.create(trigger_params.merge(owner: current_user)) if @trigger.valid? flash[:notice] = 'Trigger was created successfully.' @@ -36,7 +36,7 @@ class Projects::TriggersController < Projects::ApplicationController end def update - if trigger.update(update_params) + if trigger.update(trigger_params) redirect_to namespace_project_settings_ci_cd_path(@project.namespace, @project), notice: 'Trigger was successfully updated.' else render action: "edit" @@ -67,11 +67,10 @@ class Projects::TriggersController < Projects::ApplicationController @trigger ||= project.triggers.find(params[:id]) || render_404 end - def create_params - params.require(:trigger).permit(:description) - end - - def update_params - params.require(:trigger).permit(:description) + def trigger_params + params.require(:trigger).permit( + :description, + trigger_schedule_attributes: [:id, :active, :cron, :cron_timezone, :ref] + ) end end diff --git a/app/controllers/projects/uploads_controller.rb b/app/controllers/projects/uploads_controller.rb index 61686499bd3..6966a7c5fee 100644 --- a/app/controllers/projects/uploads_controller.rb +++ b/app/controllers/projects/uploads_controller.rb @@ -1,33 +1,11 @@ class Projects::UploadsController < Projects::ApplicationController + include UploadsActions + skip_before_action :project, :repository, if: -> { action_name == 'show' && image_or_video? } before_action :authorize_upload_file!, only: [:create] - def create - link_to_file = ::Projects::UploadService.new(project, params[:file]). - execute - - respond_to do |format| - if link_to_file - format.json do - render json: { link: link_to_file } - end - else - format.json do - render json: 'Invalid file.', status: :unprocessable_entity - end - end - end - end - - def show - return render_404 if uploader.nil? || !uploader.file.exists? - - disposition = uploader.image_or_video? ? 'inline' : 'attachment' - send_file uploader.file.path, disposition: disposition - end - private def uploader @@ -52,4 +30,10 @@ class Projects::UploadsController < Projects::ApplicationController def image_or_video? uploader && uploader.file.exists? && uploader.image_or_video? end + + def uploader_class + FileUploader + end + + alias_method :model, :project end diff --git a/app/controllers/projects/wikis_controller.rb b/app/controllers/projects/wikis_controller.rb index c5e24b9e365..887d18dbec3 100644 --- a/app/controllers/projects/wikis_controller.rb +++ b/app/controllers/projects/wikis_controller.rb @@ -91,23 +91,20 @@ class Projects::WikisController < Projects::ApplicationController ) end - def preview_markdown - text = params[:text] + def git_access + end - ext = Gitlab::ReferenceExtractor.new(@project, current_user) - ext.analyze(text, author: current_user) + def preview_markdown + result = PreviewMarkdownService.new(@project, current_user, params).execute render json: { - body: view_context.markdown(text, pipeline: :wiki, project_wiki: @project_wiki, page_slug: params[:id]), + body: view_context.markdown(result[:text], pipeline: :wiki, project_wiki: @project_wiki, page_slug: params[:id]), references: { - users: ext.users.map(&:username) + users: result[:users] } } end - def git_access - end - private def load_project_wiki @@ -115,7 +112,6 @@ class Projects::WikisController < Projects::ApplicationController # Call #wiki to make sure the Wiki Repo is initialized @project_wiki.wiki - @sidebar_wiki_entries = WikiPage.group_by_directory(@project_wiki.pages.first(15)) rescue ProjectWiki::CouldNotCreateWikiError flash[:notice] = "Could not create Wiki Repository at this time. Please try again later." diff --git a/app/controllers/projects_controller.rb b/app/controllers/projects_controller.rb index 47f7e0b1b28..544715d62ea 100644 --- a/app/controllers/projects_controller.rb +++ b/app/controllers/projects_controller.rb @@ -216,25 +216,11 @@ class ProjectsController < Projects::ApplicationController } end - def preview_markdown - text = params[:text] - - ext = Gitlab::ReferenceExtractor.new(@project, current_user) - ext.analyze(text, author: current_user) - - render json: { - body: view_context.markdown(text), - references: { - users: ext.users.map(&:username) - } - } - end - def refs branches = BranchesFinder.new(@repository, params).execute.map(&:name) options = { - 'Branches' => branches.take(100), + 'Branches' => branches.take(100) } unless @repository.tag_count.zero? @@ -252,6 +238,18 @@ class ProjectsController < Projects::ApplicationController render json: options.to_json end + def preview_markdown + result = PreviewMarkdownService.new(@project, current_user, params).execute + + render json: { + body: view_context.markdown(result[:text]), + references: { + users: result[:users], + commands: view_context.markdown(result[:commands]) + } + } + end + private # Render project landing depending of which features are available @@ -345,7 +343,11 @@ class ProjectsController < Projects::ApplicationController end def project_view_files? - current_user && current_user.project_view == 'files' + if current_user + current_user.project_view == 'files' + else + project_view_files_allowed? + end end # Override extract_ref from ExtractsPath, which returns the branch and file path @@ -359,4 +361,15 @@ class ProjectsController < Projects::ApplicationController def get_id project.repository.root_ref end + + def project_view_files_allowed? + !project.empty_repo? && can?(current_user, :download_code, project) + end + + def build_canonical_path(project) + params[:namespace_id] = project.namespace.to_param + params[:id] = project.to_param + + url_for(params) + end end diff --git a/app/controllers/registrations_controller.rb b/app/controllers/registrations_controller.rb index a49a1f50a81..3ca14dee33c 100644 --- a/app/controllers/registrations_controller.rb +++ b/app/controllers/registrations_controller.rb @@ -25,12 +25,12 @@ class RegistrationsController < Devise::RegistrationsController end def destroy - Users::DestroyService.new(current_user).execute(current_user) + DeleteUserWorker.perform_async(current_user.id, current_user.id) respond_to do |format| format.html do session.try(:destroy) - redirect_to new_user_session_path, notice: "Account successfully removed." + redirect_to new_user_session_path, notice: "Account scheduled for removal." end end end @@ -60,7 +60,7 @@ class RegistrationsController < Devise::RegistrationsController end def resource - @resource ||= Users::CreateService.new(current_user, sign_up_params).build + @resource ||= Users::BuildService.new(current_user, sign_up_params).execute end def devise_mapping diff --git a/app/controllers/search_controller.rb b/app/controllers/search_controller.rb index 612d69cf557..4a579601785 100644 --- a/app/controllers/search_controller.rb +++ b/app/controllers/search_controller.rb @@ -6,45 +6,19 @@ class SearchController < ApplicationController layout 'search' def show - if params[:project_id].present? - @project = Project.find_by(id: params[:project_id]) - @project = nil unless can?(current_user, :download_code, @project) - end + search_service = SearchService.new(current_user, params) - if params[:group_id].present? - @group = Group.find_by(id: params[:group_id]) - @group = nil unless can?(current_user, :read_group, @group) - end + @project = search_service.project + @group = search_service.group return if params[:search].blank? @search_term = params[:search] - @scope = params[:scope] - @show_snippets = params[:snippets].eql? 'true' - - @search_results = - if @project - unless %w(blobs notes issues merge_requests milestones wiki_blobs - commits).include?(@scope) - @scope = 'blobs' - end - - Search::ProjectService.new(@project, current_user, params).execute - elsif @show_snippets - unless %w(snippet_blobs snippet_titles).include?(@scope) - @scope = 'snippet_blobs' - end - - Search::SnippetService.new(current_user, params).execute - else - unless %w(projects issues merge_requests milestones).include?(@scope) - @scope = 'projects' - end - Search::GlobalService.new(current_user, params).execute - end - - @search_objects = @search_results.objects(@scope, params[:page]) + @scope = search_service.scope + @show_snippets = search_service.show_snippets? + @search_results = search_service.search_results + @search_objects = search_service.search_objects check_single_commit_result end diff --git a/app/controllers/sessions_controller.rb b/app/controllers/sessions_controller.rb index 7d81c96262f..8c6ba4915cd 100644 --- a/app/controllers/sessions_controller.rb +++ b/app/controllers/sessions_controller.rb @@ -3,7 +3,7 @@ class SessionsController < Devise::SessionsController include Devise::Controllers::Rememberable include Recaptcha::ClientHelper - skip_before_action :check_2fa_requirement, only: [:destroy] + skip_before_action :check_two_factor_requirement, only: [:destroy] prepend_before_action :check_initial_setup, only: [:new] prepend_before_action :authenticate_with_two_factor, @@ -35,6 +35,7 @@ class SessionsController < Devise::SessionsController # hide the signed-in notification flash[:notice] = nil log_audit_event(current_user, with: authentication_method) + log_user_activity(current_user) end end @@ -79,7 +80,7 @@ class SessionsController < Devise::SessionsController if request.referer.present? && (params['redirect_to_referer'] == 'yes') referer_uri = URI(request.referer) if referer_uri.host == Gitlab.config.gitlab.host - referer_uri.path + referer_uri.request_uri else request.fullpath end @@ -123,6 +124,10 @@ class SessionsController < Devise::SessionsController for_authentication.security_event end + def log_user_activity(user) + Users::ActivityService.new(user, 'login').execute + end + def load_recaptcha Gitlab::Recaptcha.load_configurations! end diff --git a/app/controllers/snippets/notes_controller.rb b/app/controllers/snippets/notes_controller.rb new file mode 100644 index 00000000000..f9496787b15 --- /dev/null +++ b/app/controllers/snippets/notes_controller.rb @@ -0,0 +1,35 @@ +class Snippets::NotesController < ApplicationController + include NotesActions + include ToggleAwardEmoji + + skip_before_action :authenticate_user!, only: [:index] + before_action :snippet + before_action :authorize_read_snippet!, only: [:show, :index, :create] + + private + + def note + @note ||= snippet.notes.find(params[:id]) + end + alias_method :awardable, :note + + def project + nil + end + + def snippet + PersonalSnippet.find_by(id: params[:snippet_id]) + end + + def note_params + super.merge(noteable_id: params[:snippet_id]) + end + + def finder_params + params.merge(last_fetched_at: last_fetched_at, target_id: snippet.id, target_type: 'personal_snippet') + end + + def authorize_read_snippet! + return render_404 unless can?(current_user, :read_personal_snippet, snippet) + end +end diff --git a/app/controllers/snippets_controller.rb b/app/controllers/snippets_controller.rb index f3fd3da8b20..7445f61195d 100644 --- a/app/controllers/snippets_controller.rb +++ b/app/controllers/snippets_controller.rb @@ -1,12 +1,14 @@ class SnippetsController < ApplicationController + include RendersNotes include ToggleAwardEmoji include SpammableActions include SnippetsActions + include RendersBlob - before_action :snippet, only: [:show, :edit, :destroy, :update, :raw, :download] + before_action :snippet, only: [:show, :edit, :destroy, :update, :raw] # Allow read snippet - before_action :authorize_read_snippet!, only: [:show, :raw, :download] + before_action :authorize_read_snippet!, only: [:show, :raw] # Allow modify snippet before_action :authorize_update_snippet!, only: [:edit, :update] @@ -14,7 +16,7 @@ class SnippetsController < ApplicationController # Allow destroy snippet before_action :authorize_admin_snippet!, only: [:destroy] - skip_before_action :authenticate_user!, only: [:index, :show, :raw, :download] + skip_before_action :authenticate_user!, only: [:index, :show, :raw] layout 'snippets' respond_to :html @@ -25,12 +27,8 @@ class SnippetsController < ApplicationController return render_404 unless @user - @snippets = SnippetsFinder.new.execute(current_user, { - filter: :by_user, - user: @user, - scope: params[:scope] - }) - .page(params[:page]) + @snippets = SnippetsFinder.new(current_user, author: @user, scope: params[:scope]) + .execute.page(params[:page]) render 'index' else @@ -59,6 +57,24 @@ class SnippetsController < ApplicationController end def show + blob = @snippet.blob + override_max_blob_size(blob) + + @note = Note.new(noteable: @snippet) + @noteable = @snippet + + @discussions = @snippet.discussions + @notes = prepare_notes_for_rendering(@discussions.flat_map(&:notes)) + + respond_to do |format| + format.html do + render 'show' + end + + format.json do + render_blob_json(blob) + end + end end def destroy @@ -69,31 +85,34 @@ class SnippetsController < ApplicationController redirect_to snippets_path end - def download - send_data( - convert_line_endings(@snippet.content), - type: 'text/plain; charset=utf-8', - filename: @snippet.sanitized_file_name - ) + def preview_markdown + result = PreviewMarkdownService.new(@project, current_user, params).execute + + render json: { + body: view_context.markdown(result[:text], skip_project_check: true), + references: { + users: result[:users] + } + } end protected def snippet - @snippet ||= if current_user - PersonalSnippet.where("author_id = ? OR visibility_level IN (?)", - current_user.id, - [Snippet::PUBLIC, Snippet::INTERNAL]). - find(params[:id]) - else - PersonalSnippet.find(params[:id]) - end + @snippet ||= PersonalSnippet.find_by(id: params[:id]) end + alias_method :awardable, :snippet alias_method :spammable, :snippet def authorize_read_snippet! - authenticate_user! unless can?(current_user, :read_personal_snippet, @snippet) + return if can?(current_user, :read_personal_snippet, @snippet) + + if current_user + render_404 + else + authenticate_user! + end end def authorize_update_snippet! diff --git a/app/controllers/unicorn_test_controller.rb b/app/controllers/unicorn_test_controller.rb new file mode 100644 index 00000000000..b7a1a046be0 --- /dev/null +++ b/app/controllers/unicorn_test_controller.rb @@ -0,0 +1,12 @@ +if Rails.env.test? + class UnicornTestController < ActionController::Base + def pid + render plain: Process.pid.to_s + end + + def kill + Process.kill(params[:signal], Process.pid) + render plain: 'Bye!' + end + end +end diff --git a/app/controllers/uploads_controller.rb b/app/controllers/uploads_controller.rb index f1bfd574f04..eef53730291 100644 --- a/app/controllers/uploads_controller.rb +++ b/app/controllers/uploads_controller.rb @@ -1,50 +1,45 @@ class UploadsController < ApplicationController - skip_before_action :authenticate_user! - before_action :find_model, :authorize_access! - - def show - uploader = @model.send(upload_mount) - - unless uploader.file_storage? - return redirect_to uploader.url - end + include UploadsActions - unless uploader.file && uploader.file.exists? - return render_404 - end - - disposition = uploader.image? ? 'inline' : 'attachment' - - expires_in 0.seconds, must_revalidate: true, private: true - send_file uploader.file.path, disposition: disposition - end + skip_before_action :authenticate_user! + before_action :find_model + before_action :authorize_access!, only: [:show] + before_action :authorize_create_access!, only: [:create] private def find_model - unless upload_model && upload_mount - return render_404 - end + return render_404 unless upload_model && upload_mount @model = upload_model.find(params[:id]) end def authorize_access! authorized = - case @model - when Project - can?(current_user, :read_project, @model) - when Group - can?(current_user, :read_group, @model) + case model when Note - can?(current_user, :read_project, @model.project) - else - # No authentication required for user avatars. + can?(current_user, :read_project, model.project) + when User + true + when Appearance true + else + permission = "read_#{model.class.to_s.underscore}".to_sym + + can?(current_user, permission, model) end - return if authorized + render_unauthorized unless authorized + end + + def authorize_create_access! + # for now we support only personal snippets comments + authorized = can?(current_user, :comment_personal_snippet, model) + render_unauthorized unless authorized + end + + def render_unauthorized if current_user render_404 else @@ -58,17 +53,44 @@ class UploadsController < ApplicationController "project" => Project, "note" => Note, "group" => Group, - "appearance" => Appearance + "appearance" => Appearance, + "personal_snippet" => PersonalSnippet } upload_models[params[:model]] end def upload_mount + return true unless params[:mounted_as] + upload_mounts = %w(avatar attachment file logo header_logo) if upload_mounts.include?(params[:mounted_as]) params[:mounted_as] end end + + def uploader + return @uploader if defined?(@uploader) + + if model.is_a?(PersonalSnippet) + @uploader = PersonalFileUploader.new(model, params[:secret]) + + @uploader.retrieve_from_store!(params[:filename]) + else + @uploader = @model.send(upload_mount) + + redirect_to @uploader.url unless @uploader.file_storage? + end + + @uploader + end + + def uploader_class + PersonalFileUploader + end + + def model + @model ||= find_model + end end diff --git a/app/controllers/users_controller.rb b/app/controllers/users_controller.rb index 2683614d2e8..19fc1e5de49 100644 --- a/app/controllers/users_controller.rb +++ b/app/controllers/users_controller.rb @@ -1,7 +1,8 @@ class UsersController < ApplicationController + include RoutableActions + skip_before_action :authenticate_user! before_action :user, except: [:exists] - before_action :authorize_read_user!, only: [:show] def show respond_to do |format| @@ -91,12 +92,8 @@ class UsersController < ApplicationController private - def authorize_read_user! - render_404 unless can?(current_user, :read_user, user) - end - def user - @user ||= User.find_by_username!(params[:username]) + @user ||= find_routable!(User, params[:username]) end def contributed_projects @@ -131,15 +128,18 @@ class UsersController < ApplicationController end def load_snippets - @snippets = SnippetsFinder.new.execute( + @snippets = SnippetsFinder.new( current_user, - filter: :by_user, - user: user, + author: user, scope: params[:scope] - ).page(params[:page]) + ).execute.page(params[:page]) end def projects_for_current_user - ProjectsFinder.new.execute(current_user) + ProjectsFinder.new(current_user: current_user).execute + end + + def build_canonical_path(user) + url_for(params.merge(username: user.to_param)) end end |