diff options
author | blackst0ne <blackst0ne.ru@gmail.com> | 2017-08-18 14:26:11 +1100 |
---|---|---|
committer | blackst0ne <blackst0ne.ru@gmail.com> | 2017-08-18 14:26:11 +1100 |
commit | 2047991363844b4f17216f94778b432ed198a412 (patch) | |
tree | 6b90710c44dae00178ad323d796beb59737f47f1 /app/controllers | |
parent | 7a1c5ba7471b233c994f5b5b313085d0d1292b5d (diff) | |
parent | f3203cbbc25586df622552153cc460d2f79f414e (diff) | |
download | gitlab-ce-2047991363844b4f17216f94778b432ed198a412.tar.gz |
Merge remote-tracking branch 'upstream/master'
Diffstat (limited to 'app/controllers')
167 files changed, 3134 insertions, 2038 deletions
diff --git a/app/controllers/abuse_reports_controller.rb b/app/controllers/abuse_reports_controller.rb index 2eac0cabf7a..ed13ead63f9 100644 --- a/app/controllers/abuse_reports_controller.rb +++ b/app/controllers/abuse_reports_controller.rb @@ -1,7 +1,9 @@ class AbuseReportsController < ApplicationController + before_action :set_user, only: [:new] + def new @abuse_report = AbuseReport.new - @abuse_report.user_id = params[:user_id] + @abuse_report.user_id = @user.id @ref_url = params.fetch(:ref_url, '') end @@ -27,4 +29,14 @@ class AbuseReportsController < ApplicationController user_id )) end + + def set_user + @user = User.find_by(id: params[:user_id]) + + if @user.nil? + redirect_to root_path, alert: "Cannot create the abuse report. The user has been deleted." + elsif @user.blocked? + redirect_to @user, alert: "Cannot create the abuse report. This user has been blocked." + end + end end diff --git a/app/controllers/admin/appearances_controller.rb b/app/controllers/admin/appearances_controller.rb index 4b0ec54b3f4..92df1c8dff0 100644 --- a/app/controllers/admin/appearances_controller.rb +++ b/app/controllers/admin/appearances_controller.rb @@ -45,7 +45,7 @@ class Admin::AppearancesController < Admin::ApplicationController # Use callbacks to share common setup or constraints between actions. def set_appearance - @appearance = Appearance.last || Appearance.new + @appearance = Appearance.current || Appearance.new end # Only allow a trusted parameter "white list" through. diff --git a/app/controllers/admin/application_settings_controller.rb b/app/controllers/admin/application_settings_controller.rb index 643993d035e..8367c22d1ca 100644 --- a/app/controllers/admin/application_settings_controller.rb +++ b/app/controllers/admin/application_settings_controller.rb @@ -71,84 +71,18 @@ class Admin::ApplicationSettingsController < Admin::ApplicationController params[:application_setting][:disabled_oauth_sign_in_sources] = AuthHelper.button_based_providers.map(&:to_s) - Array(enabled_oauth_sign_in_sources) + + params[:application_setting][:restricted_visibility_levels]&.delete("") params.delete(:domain_blacklist_raw) if params[:domain_blacklist_file] params.require(:application_setting).permit( - application_setting_params_ce + visible_application_setting_attributes ) end - def application_setting_params_ce - [ - :admin_notification_email, - :after_sign_out_path, - :after_sign_up_text, - :akismet_api_key, - :akismet_enabled, - :container_registry_token_expire_delay, - :default_artifacts_expire_in, - :default_branch_protection, - :default_group_visibility, - :default_project_visibility, - :default_projects_limit, - :default_snippet_visibility, - :domain_blacklist_enabled, + def visible_application_setting_attributes + ApplicationSettingsHelper.visible_attributes + [ :domain_blacklist_file, - :domain_blacklist_raw, - :domain_whitelist_raw, - :email_author_in_body, - :enabled_git_access_protocol, - :gravatar_enabled, - :help_page_text, - :home_page_url, - :housekeeping_bitmaps_enabled, - :housekeeping_enabled, - :housekeeping_full_repack_period, - :housekeeping_gc_period, - :housekeeping_incremental_repack_period, - :html_emails_enabled, - :koding_enabled, - :koding_url, - :plantuml_enabled, - :plantuml_url, - :max_artifacts_size, - :max_attachment_size, - :max_pages_size, - :metrics_enabled, - :metrics_host, - :metrics_method_call_threshold, - :metrics_packet_size, - :metrics_pool_size, - :metrics_port, - :metrics_sample_interval, - :metrics_timeout, - :recaptcha_enabled, - :recaptcha_private_key, - :recaptcha_site_key, - :repository_checks_enabled, - :require_two_factor_authentication, - :session_expire_delay, - :sign_in_text, - :signin_enabled, - :signup_enabled, - :sentry_dsn, - :sentry_enabled, - :send_user_confirmation_email, - :shared_runners_enabled, - :shared_runners_text, - :sidekiq_throttling_enabled, - :sidekiq_throttling_factor, - :two_factor_grace_period, - :user_default_external, - :user_oauth_applications, - :unique_ips_limit_per_user, - :unique_ips_limit_time_window, - :unique_ips_limit_enabled, - :version_check_enabled, - :terminal_max_session_time, - :polling_interval_multiplier, - :usage_ping_enabled, - disabled_oauth_sign_in_sources: [], import_sources: [], repository_storages: [], diff --git a/app/controllers/admin/applications_controller.rb b/app/controllers/admin/applications_controller.rb index 9c9f420c1e0..16590e66d61 100644 --- a/app/controllers/admin/applications_controller.rb +++ b/app/controllers/admin/applications_controller.rb @@ -39,7 +39,7 @@ class Admin::ApplicationsController < Admin::ApplicationController def destroy @application.destroy - redirect_to admin_applications_url, notice: 'Application was successfully destroyed.' + redirect_to admin_applications_url, status: 302, notice: 'Application was successfully destroyed.' end private @@ -50,6 +50,6 @@ class Admin::ApplicationsController < Admin::ApplicationController # Only allow a trusted parameter "white list" through. def application_params - params[:doorkeeper_application].permit(:name, :redirect_uri, :scopes) + params.require(:doorkeeper_application).permit(:name, :redirect_uri, :trusted, :scopes) end end diff --git a/app/controllers/admin/conversational_development_index_controller.rb b/app/controllers/admin/conversational_development_index_controller.rb new file mode 100644 index 00000000000..921169d3e2b --- /dev/null +++ b/app/controllers/admin/conversational_development_index_controller.rb @@ -0,0 +1,5 @@ +class Admin::ConversationalDevelopmentIndexController < Admin::ApplicationController + def show + @metric = ConversationalDevelopmentIndex::Metric.order(:created_at).last&.present + end +end diff --git a/app/controllers/admin/dashboard_controller.rb b/app/controllers/admin/dashboard_controller.rb index 8360ce08bdc..05e749c00c0 100644 --- a/app/controllers/admin/dashboard_controller.rb +++ b/app/controllers/admin/dashboard_controller.rb @@ -1,6 +1,6 @@ class Admin::DashboardController < Admin::ApplicationController def index - @projects = Project.with_route.limit(10) + @projects = Project.without_deleted.with_route.limit(10) @users = User.limit(10) @groups = Group.with_route.limit(10) end diff --git a/app/controllers/admin/deploy_keys_controller.rb b/app/controllers/admin/deploy_keys_controller.rb index 4f6a7e9e2cb..e5cba774dcb 100644 --- a/app/controllers/admin/deploy_keys_controller.rb +++ b/app/controllers/admin/deploy_keys_controller.rb @@ -1,6 +1,6 @@ class Admin::DeployKeysController < Admin::ApplicationController before_action :deploy_keys, only: [:index] - before_action :deploy_key, only: [:destroy] + before_action :deploy_key, only: [:destroy, :edit, :update] def index end @@ -10,12 +10,24 @@ class Admin::DeployKeysController < Admin::ApplicationController end def create - @deploy_key = deploy_keys.new(deploy_key_params.merge(user: current_user)) + @deploy_key = deploy_keys.new(create_params.merge(user: current_user)) if @deploy_key.save redirect_to admin_deploy_keys_path else - render "new" + render 'new' + end + end + + def edit + end + + def update + if deploy_key.update_attributes(update_params) + flash[:notice] = 'Deploy key was successfully updated.' + redirect_to admin_deploy_keys_path + else + render 'edit' end end @@ -23,7 +35,7 @@ class Admin::DeployKeysController < Admin::ApplicationController deploy_key.destroy respond_to do |format| - format.html { redirect_to admin_deploy_keys_path } + format.html { redirect_to admin_deploy_keys_path, status: 302 } format.json { head :ok } end end @@ -38,7 +50,11 @@ class Admin::DeployKeysController < Admin::ApplicationController @deploy_keys ||= DeployKey.are_public end - def deploy_key_params + def create_params params.require(:deploy_key).permit(:key, :title, :can_push) end + + def update_params + params.require(:deploy_key).permit(:title, :can_push) + end end diff --git a/app/controllers/admin/groups_controller.rb b/app/controllers/admin/groups_controller.rb index 5885b3543bb..2ce26de1768 100644 --- a/app/controllers/admin/groups_controller.rb +++ b/app/controllers/admin/groups_controller.rb @@ -43,19 +43,22 @@ class Admin::GroupsController < Admin::ApplicationController end def members_update - status = Members::CreateService.new(@group, current_user, params).execute + member_params = params.permit(:user_ids, :access_level, :expires_at) + result = Members::CreateService.new(@group, current_user, member_params.merge(limit: -1)).execute - if status + if result[:status] == :success redirect_to [:admin, @group], notice: 'Users were successfully added.' else - redirect_to [:admin, @group], alert: 'No users specified.' + redirect_to [:admin, @group], alert: result[:message] end end def destroy Groups::DestroyService.new(@group, current_user).async_execute - redirect_to admin_groups_path, alert: "Group '#{@group.name}' was scheduled for deletion." + redirect_to admin_groups_path, + status: 302, + alert: "Group '#{@group.name}' was scheduled for deletion." end private diff --git a/app/controllers/admin/health_check_controller.rb b/app/controllers/admin/health_check_controller.rb index caf4c138da8..65a17828feb 100644 --- a/app/controllers/admin/health_check_controller.rb +++ b/app/controllers/admin/health_check_controller.rb @@ -1,5 +1,12 @@ class Admin::HealthCheckController < Admin::ApplicationController def show @errors = HealthCheck::Utils.process_checks(['standard']) + @failing_storage_statuses = Gitlab::Git::Storage::Health.for_failing_storages + end + + def reset_storage_health + Gitlab::Git::Storage::CircuitBreaker.reset_all! + redirect_to admin_health_check_path, + notice: _('Git storage health information has been reset') end end diff --git a/app/controllers/admin/hook_logs_controller.rb b/app/controllers/admin/hook_logs_controller.rb new file mode 100644 index 00000000000..3017f96c26f --- /dev/null +++ b/app/controllers/admin/hook_logs_controller.rb @@ -0,0 +1,29 @@ +class Admin::HookLogsController < Admin::ApplicationController + include HooksExecution + + before_action :hook, only: [:show, :retry] + before_action :hook_log, only: [:show, :retry] + + respond_to :html + + def show + end + + def retry + result = hook.execute(hook_log.request_data, hook_log.trigger) + + set_hook_execution_notice(result) + + redirect_to edit_admin_hook_path(@hook) + end + + private + + def hook + @hook ||= SystemHook.find(params[:hook_id]) + end + + def hook_log + @hook_log ||= hook.web_hook_logs.find(params[:id]) + end +end diff --git a/app/controllers/admin/hooks_controller.rb b/app/controllers/admin/hooks_controller.rb index cbfc4581411..77e3c95d197 100644 --- a/app/controllers/admin/hooks_controller.rb +++ b/app/controllers/admin/hooks_controller.rb @@ -1,4 +1,8 @@ class Admin::HooksController < Admin::ApplicationController + include HooksExecution + + before_action :hook_logs, only: :edit + def index @hooks = SystemHook.all @hook = SystemHook.new @@ -15,33 +19,49 @@ 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 + redirect_to admin_hooks_path, status: 302 end def test - @hook = SystemHook.find(params[:hook_id]) - data = { - event_name: "project_create", - name: "Ruby", - path: "ruby", - project_id: 1, - owner_name: "Someone", - owner_email: "example@gitlabhq.com" - } - @hook.execute(data, 'system_hooks') + result = TestHooks::SystemService.new(hook, current_user, params[:trigger]).execute + + set_hook_execution_notice(result) redirect_back_or_default end + private + + def hook + @hook ||= SystemHook.find(params[:id]) + end + + def hook_logs + @hook_logs ||= + Kaminari.paginate_array(hook.web_hook_logs.order(created_at: :desc)).page(params[:page]) + 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/identities_controller.rb b/app/controllers/admin/identities_controller.rb index 79a53556f0a..43b4e3a2cc3 100644 --- a/app/controllers/admin/identities_controller.rb +++ b/app/controllers/admin/identities_controller.rb @@ -36,9 +36,9 @@ class Admin::IdentitiesController < Admin::ApplicationController def destroy if @identity.destroy RepairLdapBlockedUserService.new(@user).execute - redirect_to admin_user_identities_path(@user), notice: 'User identity was successfully removed.' + redirect_to admin_user_identities_path(@user), status: 302, notice: 'User identity was successfully removed.' else - redirect_to admin_user_identities_path(@user), alert: 'Failed to remove user identity.' + redirect_to admin_user_identities_path(@user), status: 302, alert: 'Failed to remove user identity.' end end diff --git a/app/controllers/admin/impersonations_controller.rb b/app/controllers/admin/impersonations_controller.rb index 8e7adc06584..39dbf85f6c0 100644 --- a/app/controllers/admin/impersonations_controller.rb +++ b/app/controllers/admin/impersonations_controller.rb @@ -11,7 +11,7 @@ class Admin::ImpersonationsController < Admin::ApplicationController session[:impersonator_id] = nil - redirect_to admin_user_path(original_user) + redirect_to admin_user_path(original_user), status: 302 end private diff --git a/app/controllers/admin/builds_controller.rb b/app/controllers/admin/jobs_controller.rb index 88f3c0e2fd4..5162273ef8a 100644 --- a/app/controllers/admin/builds_controller.rb +++ b/app/controllers/admin/jobs_controller.rb @@ -1,4 +1,4 @@ -class Admin::BuildsController < Admin::ApplicationController +class Admin::JobsController < Admin::ApplicationController def index @scope = params[:scope] @all_builds = Ci::Build @@ -20,6 +20,6 @@ class Admin::BuildsController < Admin::ApplicationController def cancel_all Ci::Build.running_or_pending.each(&:cancel) - redirect_to admin_builds_path + redirect_to admin_jobs_path end end diff --git a/app/controllers/admin/keys_controller.rb b/app/controllers/admin/keys_controller.rb index 054bb52b696..0b76193a90e 100644 --- a/app/controllers/admin/keys_controller.rb +++ b/app/controllers/admin/keys_controller.rb @@ -15,9 +15,9 @@ class Admin::KeysController < Admin::ApplicationController respond_to do |format| if key.destroy - format.html { redirect_to [:admin, user], notice: 'User key was successfully removed.' } + format.html { redirect_to keys_admin_user_path(user), status: 302, notice: 'User key was successfully removed.' } else - format.html { redirect_to [:admin, user], alert: 'Failed to remove user key.' } + format.html { redirect_to keys_admin_user_path(user), status: 302, alert: 'Failed to remove user key.' } end end end diff --git a/app/controllers/admin/labels_controller.rb b/app/controllers/admin/labels_controller.rb index 4531657268c..cbc7a14ae83 100644 --- a/app/controllers/admin/labels_controller.rb +++ b/app/controllers/admin/labels_controller.rb @@ -41,7 +41,7 @@ class Admin::LabelsController < Admin::ApplicationController respond_to do |format| format.html do - redirect_to(admin_labels_path, notice: 'Label was removed') + redirect_to admin_labels_path, status: 302, notice: 'Label was removed' end format.js end diff --git a/app/controllers/admin/projects_controller.rb b/app/controllers/admin/projects_controller.rb index a1975c0e341..0b6cd71e651 100644 --- a/app/controllers/admin/projects_controller.rb +++ b/app/controllers/admin/projects_controller.rb @@ -3,18 +3,9 @@ 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? - @projects = @projects.with_push if params[:with_push].present? - @projects = @projects.abandoned if params[:abandoned].present? - @projects = @projects.where(last_repository_check_failed: true) if params[:last_repository_check_failed].present? - @projects = @projects.non_archived unless params[:archived].present? - @projects = @projects.personal(current_user) if params[:personal].present? - @projects = @projects.search(params[:name]) if params[:name].present? - @projects = @projects.sort(@sort = params[:sort]) - @projects = @projects.includes(:namespace).order("namespaces.path, projects.name ASC").page(params[:page]) + finder = Admin::ProjectsFinder.new(params: params, current_user: current_user) + @projects = finder.execute + @sort = finder.sort respond_to do |format| format.html @@ -40,14 +31,14 @@ class Admin::ProjectsController < Admin::ApplicationController ::Projects::TransferService.new(@project, current_user, params.dup).execute(namespace) @project.reload - redirect_to admin_namespace_project_path(@project.namespace, @project) + redirect_to admin_project_path(@project) end def repository_check RepositoryCheck::SingleRepositoryWorker.perform_async(@project.id) redirect_to( - admin_namespace_project_path(@project.namespace, @project), + admin_project_path(@project), notice: 'Repository check was triggered.' ) end diff --git a/app/controllers/admin/runner_projects_controller.rb b/app/controllers/admin/runner_projects_controller.rb index 70ac6a75434..7ed2de71028 100644 --- a/app/controllers/admin/runner_projects_controller.rb +++ b/app/controllers/admin/runner_projects_controller.rb @@ -18,7 +18,7 @@ class Admin::RunnerProjectsController < Admin::ApplicationController runner = rp.runner rp.destroy - redirect_to admin_runner_path(runner) + redirect_to admin_runner_path(runner), status: 302 end private diff --git a/app/controllers/admin/runners_controller.rb b/app/controllers/admin/runners_controller.rb index 348641e5ecb..719893c0bc8 100644 --- a/app/controllers/admin/runners_controller.rb +++ b/app/controllers/admin/runners_controller.rb @@ -27,7 +27,7 @@ class Admin::RunnersController < Admin::ApplicationController def destroy @runner.destroy - redirect_to admin_runners_path + redirect_to admin_runners_path, status: 302 end def resume 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 1d66955bb71..d52d67a67a5 100644 --- a/app/controllers/admin/spam_logs_controller.rb +++ b/app/controllers/admin/spam_logs_controller.rb @@ -8,7 +8,9 @@ class Admin::SpamLogsController < Admin::ApplicationController if params[: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." + redirect_to admin_spam_logs_path, + status: 302, + notice: "User #{spam_log.user.username} was successfully removed." else spam_log.destroy head :ok diff --git a/app/controllers/admin/users_controller.rb b/app/controllers/admin/users_controller.rb index 563bcc65bd6..fa1bc72560e 100644 --- a/app/controllers/admin/users_controller.rb +++ b/app/controllers/admin/users_controller.rb @@ -54,7 +54,7 @@ class Admin::UsersController < Admin::ApplicationController end def block - if user.block + if update_user { |user| user.block } redirect_back_or_admin_user(notice: "Successfully blocked") else redirect_back_or_admin_user(alert: "Error occurred. User was not blocked") @@ -64,7 +64,7 @@ class Admin::UsersController < Admin::ApplicationController def unblock if user.ldap_blocked? redirect_back_or_admin_user(alert: "This user cannot be unlocked manually from GitLab") - elsif user.activate + elsif update_user { |user| user.activate } redirect_back_or_admin_user(notice: "Successfully unblocked") else redirect_back_or_admin_user(alert: "Error occurred. User was not unblocked") @@ -72,7 +72,7 @@ class Admin::UsersController < Admin::ApplicationController end def unlock - if user.unlock_access! + if update_user { |user| user.unlock_access! } redirect_back_or_admin_user(alert: "Successfully unlocked") else redirect_back_or_admin_user(alert: "Error occurred. User was not unlocked") @@ -80,7 +80,7 @@ class Admin::UsersController < Admin::ApplicationController end def confirm - if user.confirm + if update_user { |user| user.confirm } redirect_back_or_admin_user(notice: "Successfully confirmed") else redirect_back_or_admin_user(alert: "Error occurred. User was not confirmed") @@ -88,7 +88,8 @@ class Admin::UsersController < Admin::ApplicationController end def disable_two_factor - user.disable_two_factor! + update_user { |user| user.disable_two_factor! } + redirect_to admin_user_path(user), notice: 'Two-factor Authentication has been disabled for this user' end @@ -124,37 +125,43 @@ class Admin::UsersController < Admin::ApplicationController end respond_to do |format| - user.skip_reconfirmation! - if user.update_attributes(user_params_with_pass) + result = Users::UpdateService.new(user, user_params_with_pass).execute do |user| + user.skip_reconfirmation! + end + + if result[:status] == :success format.html { redirect_to [:admin, user], notice: 'User was successfully updated.' } format.json { head :ok } else # restore username to keep form action url. user.username = params[:id] format.html { render "edit" } - format.json { render json: user.errors, status: :unprocessable_entity } + format.json { render json: [result[:message]], status: result[:status] } end end end def destroy - DeleteUserWorker.perform_async(current_user.id, user.id) + user.delete_async(deleted_by: current_user, params: params.permit(:hard_delete)) respond_to do |format| - format.html { redirect_to admin_users_path, notice: "The user is being deleted." } + format.html { redirect_to admin_users_path, status: 302, notice: "The user is being deleted." } format.json { head :ok } end end def remove_email email = user.emails.find(params[:email_id]) - email.destroy - - user.update_secondary_emails! + success = Emails::DestroyService.new(user, email: email.email).execute respond_to do |format| - format.html { redirect_back_or_admin_user(notice: "Successfully removed email.") } - format.js { head :ok } + if success + format.html { redirect_back_or_admin_user(notice: 'Successfully removed email.') } + format.json { head :ok } + else + format.html { redirect_back_or_admin_user(alert: 'There was an error removing the e-mail.') } + format.json { render json: 'There was an error removing the e-mail.', status: 400 } + end end end @@ -202,4 +209,10 @@ class Admin::UsersController < Admin::ApplicationController :website_url ] end + + def update_user(&block) + result = Users::UpdateService.new(user).execute(&block) + + result[:status] == :success + end end diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb index e77094fe2a8..1d92ea11bda 100644 --- a/app/controllers/application_controller.rb +++ b/app/controllers/application_controller.rb @@ -9,18 +9,22 @@ class ApplicationController < ActionController::Base include SentryHelper include WorkhorseHelper include EnforcesTwoFactorAuthentication + include WithPerformanceBar before_action :authenticate_user_from_private_token! + before_action :authenticate_user_from_rss_token! before_action :authenticate_user! before_action :validate_user_service_ticket! before_action :check_password_expiration before_action :ldap_security_check before_action :sentry_context before_action :default_headers - before_action :add_gon_variables + before_action :add_gon_variables, unless: -> { request.path.start_with?('/-/peek') } 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 @@ -36,6 +40,10 @@ class ApplicationController < ActionController::Base render_404 end + rescue_from(ActionController::UnknownFormat) do + render_404 + end + rescue_from Gitlab::Access::AccessDeniedError do |exception| render_403 end @@ -44,6 +52,15 @@ class ApplicationController < ActionController::Base head :forbidden, retry_after: Gitlab::Auth::UniqueIpsLimiter.config.unique_ips_limit_time_window end + rescue_from Gitlab::Git::Storage::Inaccessible, GRPC::Unavailable, Gitlab::Git::CommandError do |exception| + Raven.capture_exception(exception) if sentry_enabled? + log_exception(exception) + + headers['Retry-After'] = exception.retry_after if exception.respond_to?(:retry_after) + + render_503 + end + def redirect_back_or_default(default: root_path, options: {}) redirect_to request.referer.present? ? :back : default, options end @@ -56,12 +73,22 @@ class ApplicationController < ActionController::Base if current_user not_found else - redirect_to new_user_session_path + authenticate_user! end end protected + def append_info_to_payload(payload) + super + payload[:remote_ip] = request.remote_ip + + if current_user.present? + payload[:user_id] = current_user.id + payload[:username] = current_user.username + end + end + # This filter handles both private tokens and personal access tokens def authenticate_user_from_private_token! token = params[:private_token].presence || request.headers['PRIVATE-TOKEN'].presence @@ -70,18 +97,27 @@ class ApplicationController < ActionController::Base user = User.find_by_authentication_token(token) || User.find_by_personal_access_token(token) - if user && can?(user, :log_in) - # Notice we are passing store false, so the user is not - # actually stored in the session and a token is needed - # for every request. If you want the token to work as a - # sign in token, you can simply remove store: false. - sign_in user, store: false - end + sessionless_sign_in(user) + end + + # This filter handles authentication for atom request with an rss_token + def authenticate_user_from_rss_token! + return unless request.format.atom? + + token = params[:rss_token].presence + + return unless token.present? + + user = User.find_by_rss_token(token) + + sessionless_sign_in(user) end def log_exception(exception) + Raven.capture_exception(exception) if sentry_enabled? + application_trace = ActionDispatch::ExceptionWrapper.new(env, exception).application_trace - application_trace.map!{ |t| " #{t}\n" } + application_trace.map! { |t| " #{t}\n" } logger.error "\n#{exception.class.name} (#{exception.message}):\n#{application_trace.join}" end @@ -98,7 +134,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 +157,23 @@ class ApplicationController < ActionController::Base end end + def respond_422 + head :unprocessable_entity + end + + def render_503 + respond_to do |format| + format.html do + render( + file: Rails.root.join("public", "503"), + layout: false, + status: :service_unavailable + ) + end + format.any { head :service_unavailable } + end + end + def no_cache_headers response.headers["Cache-Control"] = "no-cache, no-store, max-age=0, must-revalidate" response.headers["Pragma"] = "no-cache" @@ -146,7 +202,7 @@ class ApplicationController < ActionController::Base end def check_password_expiration - if current_user && current_user.password_expires_at && current_user.password_expires_at < Time.now && !current_user.ldap_user? + if current_user && current_user.password_expires_at && current_user.password_expires_at < Time.now && current_user.allow_password_authentication? return redirect_to new_profile_password_path end end @@ -265,4 +321,18 @@ class ApplicationController < ActionController::Base def u2f_app_id request.base_url end + + def set_locale(&block) + Gitlab::I18n.with_user_locale(current_user, &block) + end + + def sessionless_sign_in(user) + if user && can?(user, :log_in) + # Notice we are passing store false, so the user is not + # actually stored in the session and a token is needed + # for every request. If you want the token to work as a + # sign in token, you can simply remove store: false. + sign_in user, store: false + end + end end diff --git a/app/controllers/autocomplete_controller.rb b/app/controllers/autocomplete_controller.rb index b79ca034c5b..3120916c5bb 100644 --- a/app/controllers/autocomplete_controller.rb +++ b/app/controllers/autocomplete_controller.rb @@ -5,11 +5,11 @@ class AutocompleteController < ApplicationController def users @users ||= User.none - @users = @users.search(params[:search]) if params[:search].present? - @users = @users.where.not(id: params[:skip_users]) if params[:skip_users].present? @users = @users.active @users = @users.reorder(:name) - @users = @users.page(params[:page]) + @users = @users.search(params[:search]) if params[:search].present? + @users = @users.where.not(id: params[:skip_users]) if params[:skip_users].present? + @users = @users.page(params[:page]).per(params[:per_page]) if params[:todo_filter].present? && current_user @users = @users.todo_authors(current_user.id, params[:todo_state_filter]) @@ -21,7 +21,7 @@ class AutocompleteController < ApplicationController @users = [current_user, *@users].uniq end - if params[:author_id].present? + if params[:author_id].present? && current_user author = User.find_by_id(params[:author_id]) @users = [author, *@users].uniq if author end @@ -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/authenticates_with_two_factor.rb b/app/controllers/concerns/authenticates_with_two_factor.rb index ea441b1736b..b75e401a8df 100644 --- a/app/controllers/concerns/authenticates_with_two_factor.rb +++ b/app/controllers/concerns/authenticates_with_two_factor.rb @@ -69,7 +69,7 @@ module AuthenticatesWithTwoFactor if U2fRegistration.authenticate(user, u2f_app_id, user_params[:device_response], session[:challenge]) # Remove any lingering user data from login session.delete(:otp_user_id) - session.delete(:challenges) + session.delete(:challenge) remember_me(user) if user_params[:remember_me] == '1' sign_in(user) diff --git a/app/controllers/concerns/creates_commit.rb b/app/controllers/concerns/creates_commit.rb index 183eb00ef67..782f0be9c4a 100644 --- a/app/controllers/concerns/creates_commit.rb +++ b/app/controllers/concerns/creates_commit.rb @@ -1,11 +1,6 @@ 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) if can?(current_user, :push_code, @project) @project_to_commit_into = @project @@ -83,8 +78,7 @@ module CreatesCommit end def new_merge_request_path - new_namespace_project_merge_request_path( - @project_to_commit_into.namespace, + project_new_merge_request_path( @project_to_commit_into, merge_request: { source_project_id: @project_to_commit_into.id, @@ -96,14 +90,14 @@ module CreatesCommit end def existing_merge_request_path - namespace_project_merge_request_path(@project.namespace, @project, @merge_request) + project_merge_request_path(@project, @merge_request) end def merge_request_exists? return @merge_request if defined?(@merge_request) - @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) + @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? diff --git a/app/controllers/concerns/cycle_analytics_params.rb b/app/controllers/concerns/cycle_analytics_params.rb index 52e06f4945a..1ab107168c0 100644 --- a/app/controllers/concerns/cycle_analytics_params.rb +++ b/app/controllers/concerns/cycle_analytics_params.rb @@ -6,6 +6,13 @@ module CycleAnalyticsParams end def start_date(params) - params[:start_date] == '30' ? 30.days.ago : 90.days.ago + case params[:start_date] + when '7' + 7.days.ago + when '30' + 30.days.ago + else + 90.days.ago + end end end diff --git a/app/controllers/concerns/diff_for_path.rb b/app/controllers/concerns/diff_for_path.rb index 1efa9fe060f..d5388c4cd20 100644 --- a/app/controllers/concerns/diff_for_path.rb +++ b/app/controllers/concerns/diff_for_path.rb @@ -8,17 +8,6 @@ module DiffForPath return render_404 unless diff_file - diff_commit = commit_for_diff(diff_file) - blob = diff_file.blob(diff_commit) - - locals = { - diff_file: diff_file, - diff_commit: diff_commit, - diff_refs: diffs.diff_refs, - blob: blob, - project: project - } - - render json: { html: view_to_html_string('projects/diffs/_content', locals) } + render json: { html: view_to_html_string('projects/diffs/_content', diff_file: diff_file) } end end diff --git a/app/controllers/concerns/hooks_execution.rb b/app/controllers/concerns/hooks_execution.rb new file mode 100644 index 00000000000..a22e46b4860 --- /dev/null +++ b/app/controllers/concerns/hooks_execution.rb @@ -0,0 +1,18 @@ +module HooksExecution + extend ActiveSupport::Concern + + private + + def set_hook_execution_notice(result) + http_status = result[:http_status] + message = result[:message] + + if http_status && http_status >= 200 && http_status < 400 + flash[:notice] = "Hook executed successfully: HTTP #{http_status}" + elsif http_status + flash[:alert] = "Hook executed successfully but returned HTTP #{http_status} #{message}" + else + flash[:alert] = "Hook execution failed: #{message}" + end + end +end diff --git a/app/controllers/concerns/issuable_actions.rb b/app/controllers/concerns/issuable_actions.rb index 3ccf2a9ce33..4079072a930 100644 --- a/app/controllers/concerns/issuable_actions.rb +++ b/app/controllers/concerns/issuable_actions.rb @@ -10,11 +10,20 @@ module IssuableActions def destroy issuable.destroy destroy_method = "destroy_#{issuable.class.name.underscore}".to_sym - TodoService.new.public_send(destroy_method, issuable, current_user) + TodoService.new.public_send(destroy_method, issuable, current_user) # rubocop:disable GitlabSecurity/PublicSend name = issuable.human_class_name flash[:notice] = "The #{name} was successfully deleted." - redirect_to polymorphic_path([@project.namespace.becomes(Namespace), @project, issuable.class]) + index_path = polymorphic_path([@project.namespace.becomes(Namespace), @project, issuable.class]) + + respond_to do |format| + format.html { redirect_to index_path } + format.json do + render json: { + web_url: index_path + } + end + end end def bulk_update @@ -60,7 +69,7 @@ module IssuableActions end def bulk_update_params - params.require(:update).permit( + permitted_keys = [ :issuable_ids, :assignee_id, :milestone_id, @@ -69,7 +78,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 c8a501d7319..b43b2c5621f 100644 --- a/app/controllers/concerns/issuable_collections.rb +++ b/app/controllers/concerns/issuable_collections.rb @@ -1,6 +1,7 @@ module IssuableCollections extend ActiveSupport::Concern include SortingHelper + include Gitlab::IssuableMetadata included do helper_method :issues_finder @@ -9,45 +10,12 @@ module IssuableCollections private - def issuable_meta_data(issuable_collection, collection_type) - # map has to be used here since using pluck or select will - # throw an error when ordering issuables by priority which inserts - # 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 = - if collection_type == 'Issue' - MergeRequestsClosingIssues.count_for_collection(issuable_ids) - else - [] - end - - issuable_ids.each_with_object({}) do |id, issuable_meta| - downvotes = issuable_votes_count.find { |votes| votes.awardable_id == id && votes.downvote? } - upvotes = issuable_votes_count.find { |votes| votes.awardable_id == id && votes.upvote? } - notes = issuable_note_count.find { |notes| notes.noteable_id == id } - merge_requests = issuable_merge_requests_count.find { |mr| mr.first == id } - - issuable_meta[id] = Issuable::IssuableMeta.new( - upvotes.try(:count).to_i, - downvotes.try(:count).to_i, - notes.try(:count).to_i, - merge_requests.try(:last).to_i - ) - end - 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, :head_pipeline, target_project: :namespace, merge_request_diff: :merge_request_diff_commits) end def issues_finder @@ -64,10 +32,10 @@ module IssuableCollections def filter_params set_sort_order_from_cookie - set_default_scope set_default_state - @filter_params = params.dup + # Skip irrelevant Rails routing params + @filter_params = params.dup.except(:controller, :action, :namespace_id) @filter_params[:sort] ||= default_sort_order @sort = @filter_params[:sort] @@ -87,10 +55,6 @@ module IssuableCollections @filter_params end - def set_default_scope - params[:scope] = 'all' if params[:scope].blank? - end - def set_default_state params[:state] = 'opened' if params[:state].blank? end diff --git a/app/controllers/concerns/issues_action.rb b/app/controllers/concerns/issues_action.rb index b17c138d5c7..404559c8707 100644 --- a/app/controllers/concerns/issues_action.rb +++ b/app/controllers/concerns/issues_action.rb @@ -14,7 +14,7 @@ module IssuesAction respond_to do |format| format.html - format.atom { render layout: false } + format.atom { render layout: 'xml.atom' } end end end diff --git a/app/controllers/concerns/lfs_request.rb b/app/controllers/concerns/lfs_request.rb index ed22b1e5470..2b6afaa6233 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 @@ -106,4 +106,8 @@ module LfsRequest def objects @objects ||= (params[:objects] || []).to_a end + + def has_authentication_ability?(capability) + (authentication_abilities || []).include?(capability) + end end diff --git a/app/controllers/concerns/markdown_preview.rb b/app/controllers/concerns/markdown_preview.rb deleted file mode 100644 index 40eff267348..00000000000 --- a/app/controllers/concerns/markdown_preview.rb +++ /dev/null @@ -1,19 +0,0 @@ -module MarkdownPreview - private - - def render_markdown_preview(text, markdown_context = {}) - render json: { - body: view_context.markdown(text, markdown_context), - references: { - users: preview_referenced_users(text) - } - } - end - - def preview_referenced_users(text) - extractor = Gitlab::ReferenceExtractor.new(@project, current_user) - extractor.analyze(text, author: current_user) - - extractor.users.map(&:username) - end -end diff --git a/app/controllers/concerns/membership_actions.rb b/app/controllers/concerns/membership_actions.rb index b1bacc8ffe5..c6b1e443de6 100644 --- a/app/controllers/concerns/membership_actions.rb +++ b/app/controllers/concerns/membership_actions.rb @@ -2,20 +2,21 @@ module MembershipActions extend ActiveSupport::Concern def create - status = Members::CreateService.new(membershipable, current_user, params).execute + create_params = params.permit(:user_ids, :access_level, :expires_at) + result = Members::CreateService.new(membershipable, current_user, create_params).execute redirect_url = members_page_url - if status + if result[:status] == :success redirect_to redirect_url, notice: 'Users were successfully added.' else - redirect_to redirect_url, alert: 'No users specified.' + redirect_to redirect_url, alert: result[:message] end end def destroy - Members::DestroyService.new(membershipable, current_user, params). - execute(:all) + Members::DestroyService.new(membershipable, current_user, params) + .execute(:all) respond_to do |format| format.html do @@ -41,8 +42,8 @@ module MembershipActions end def leave - member = Members::DestroyService.new(membershipable, current_user, user_id: current_user.id). - execute(:all) + member = Members::DestroyService.new(membershipable, current_user, user_id: current_user.id) + .execute(:all) notice = if member.request? @@ -51,9 +52,14 @@ module MembershipActions "You left the \"#{membershipable.human_name}\" #{source_type}." end - redirect_path = member.request? ? member.source : [:dashboard, membershipable.class.to_s.tableize] + respond_to do |format| + format.html do + redirect_path = member.request? ? member.source : [:dashboard, membershipable.class.to_s.tableize] + redirect_to redirect_path, notice: notice + end - redirect_to redirect_path, notice: notice + format.json { render json: { notice: notice } } + end end protected @@ -64,7 +70,7 @@ module MembershipActions def members_page_url if membershipable.is_a?(Project) - project_settings_members_path(membershipable) + project_project_members_path(membershipable) else polymorphic_url([membershipable, :members]) end diff --git a/app/controllers/concerns/milestone_actions.rb b/app/controllers/concerns/milestone_actions.rb new file mode 100644 index 00000000000..081f3336780 --- /dev/null +++ b/app/controllers/concerns/milestone_actions.rb @@ -0,0 +1,55 @@ +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.sorted_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 + project_milestone_path(@project, @milestone) + elsif @group + group_milestone_path(@group, @milestone.safe_title, title: @milestone.title) + else + dashboard_milestone_path(@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..af5f683bab5 --- /dev/null +++ b/app/controllers/concerns/notes_actions.rb @@ -0,0 +1,200 @@ +module NotesActions + include RendersNotes + extend ActiveSupport::Concern + + included do + before_action :authorize_admin_note!, only: [:update, :destroy] + before_action :note_project, only: [:create] + 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(note_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 + + def note_project + return @note_project if defined?(@note_project) + return nil unless project + + note_project_id = params[:note_project_id] + + @note_project = + if note_project_id.present? + Project.find(note_project_id) + else + project + end + + return access_denied! unless can?(current_user, :create_note, @note_project) + + @note_project + end +end diff --git a/app/controllers/concerns/renders_blob.rb b/app/controllers/concerns/renders_blob.rb new file mode 100644 index 00000000000..ba7adcfea86 --- /dev/null +++ b/app/controllers/concerns/renders_blob.rb @@ -0,0 +1,32 @@ +module RendersBlob + extend ActiveSupport::Concern + + def blob_json(blob) + viewer = + case params[:viewer] + when 'rich' + blob.rich_viewer + when 'auxiliary' + blob.auxiliary_viewer + else + blob.simple_viewer + end + + return unless viewer + + { + html: view_to_html_string("projects/blob/_viewer", viewer: viewer, load_async: false) + } + end + + def render_blob_json(blob) + json = blob_json(blob) + return render_404 unless json + + render json: json + end + + def conditionally_expand_blob(blob) + blob.expand! if params[:expanded] == 'true' + end +end diff --git a/app/controllers/concerns/renders_notes.rb b/app/controllers/concerns/renders_notes.rb index dd21066ac13..41c3114ad1e 100644 --- a/app/controllers/concerns/renders_notes.rb +++ b/app/controllers/concerns/renders_notes.rb @@ -10,6 +10,8 @@ module RendersNotes 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 diff --git a/app/controllers/concerns/repository_settings_redirect.rb b/app/controllers/concerns/repository_settings_redirect.rb index 0854c73a02f..0576f0e6e70 100644 --- a/app/controllers/concerns/repository_settings_redirect.rb +++ b/app/controllers/concerns/repository_settings_redirect.rb @@ -2,6 +2,6 @@ module RepositorySettingsRedirect extend ActiveSupport::Concern def redirect_to_repository_settings(project) - redirect_to namespace_project_settings_repository_path(project.namespace, project) + redirect_to project_settings_repository_path(project) end end diff --git a/app/controllers/concerns/requires_health_token.rb b/app/controllers/concerns/requires_health_token.rb deleted file mode 100644 index 34ab1a97649..00000000000 --- a/app/controllers/concerns/requires_health_token.rb +++ /dev/null @@ -1,25 +0,0 @@ -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/requires_whitelisted_monitoring_client.rb b/app/controllers/concerns/requires_whitelisted_monitoring_client.rb new file mode 100644 index 00000000000..ad2f4bbc486 --- /dev/null +++ b/app/controllers/concerns/requires_whitelisted_monitoring_client.rb @@ -0,0 +1,33 @@ +module RequiresWhitelistedMonitoringClient + extend ActiveSupport::Concern + included do + before_action :validate_ip_whitelisted_or_valid_token! + end + + private + + def validate_ip_whitelisted_or_valid_token! + render_404 unless client_ip_whitelisted? || valid_token? + end + + def client_ip_whitelisted? + ip_whitelist.any? { |e| e.include?(Gitlab::RequestContext.client_ip) } + end + + def ip_whitelist + @ip_whitelist ||= Settings.monitoring.ip_whitelist.map(&IPAddr.method(:new)) + end + + def valid_token? + 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/spammable_actions.rb b/app/controllers/concerns/spammable_actions.rb index d0a692070d9..ada0dde87fb 100644 --- a/app/controllers/concerns/spammable_actions.rb +++ b/app/controllers/concerns/spammable_actions.rb @@ -9,18 +9,26 @@ module SpammableActions def mark_as_spam if SpamService.new(spammable).mark_as_spam! - redirect_to spammable, notice: "#{spammable.spammable_entity_type.titlecase} was submitted to Akismet successfully." + redirect_to spammable_path, notice: "#{spammable.spammable_entity_type.titlecase} was submitted to Akismet successfully." else - redirect_to spammable, alert: 'Error with Akismet. Please check the logs for more info.' + redirect_to spammable_path, alert: 'Error with Akismet. Please check the logs for more info.' end end private + def ensure_spam_config_loaded! + return @spam_config_loaded if defined?(@spam_config_loaded) + + @spam_config_loaded = Gitlab::Recaptcha.load_configurations! + end + def recaptcha_check_with_fallback(&fallback) if spammable.valid? - redirect_to spammable + redirect_to spammable_path elsif render_recaptcha? + ensure_spam_config_loaded! + if params[:recaptcha_verification] flash[:alert] = 'There was an error with the reCAPTCHA. Please solve the reCAPTCHA again.' end @@ -35,7 +43,7 @@ module SpammableActions default_params = { request: request } recaptcha_check = params[:recaptcha_verification] && - Gitlab::Recaptcha.load_configurations! && + ensure_spam_config_loaded! && verify_recaptcha return default_params unless recaptcha_check @@ -48,6 +56,10 @@ module SpammableActions raise NotImplementedError, "#{self.class} does not implement #{__method__}" end + def spammable_path + raise NotImplementedError, "#{self.class} does not implement #{__method__}" + end + def authorize_submit_spammable! access_denied! unless current_user.admin? 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/concerns/with_performance_bar.rb b/app/controllers/concerns/with_performance_bar.rb new file mode 100644 index 00000000000..ed253042701 --- /dev/null +++ b/app/controllers/concerns/with_performance_bar.rb @@ -0,0 +1,17 @@ +module WithPerformanceBar + extend ActiveSupport::Concern + + included do + include Peek::Rblineprof::CustomControllerHelpers + end + + def peek_enabled? + return false unless Gitlab::PerformanceBar.enabled?(current_user) + + if RequestStore.active? + RequestStore.fetch(:peek_enabled) { cookies[:perf_bar_enabled].present? } + else + cookies[:perf_bar_enabled].present? + end + end +end diff --git a/app/controllers/dashboard/groups_controller.rb b/app/controllers/dashboard/groups_controller.rb index d03265e9f20..742157d113d 100644 --- a/app/controllers/dashboard/groups_controller.rb +++ b/app/controllers/dashboard/groups_controller.rb @@ -1,16 +1,30 @@ class Dashboard::GroupsController < Dashboard::ApplicationController def index - @group_members = current_user.group_members.includes(source: :route).joins(:group) - @group_members = @group_members.merge(Group.search(params[:filter_groups])) if params[:filter_groups].present? - @group_members = @group_members.merge(Group.sort(@sort = params[:sort])) - @group_members = @group_members.page(params[:page]) + @groups = + if params[:parent_id] && Group.supports_nested_groups? + parent = Group.find_by(id: params[:parent_id]) + + if can?(current_user, :read_group, parent) + GroupsFinder.new(current_user, parent: parent).execute + else + Group.none + end + else + current_user.groups + end + + @groups = @groups.search(params[:filter_groups]) if params[:filter_groups].present? + @groups = @groups.includes(:route) + @groups = @groups.sort(@sort = params[:sort]) + @groups = @groups.page(params[:page]) respond_to do |format| format.html format.json do - render json: { - html: view_to_html_string("dashboard/groups/_groups", locals: { group_members: @group_members }) - } + render json: GroupSerializer + .new(current_user: @current_user) + .with_pagination(request, response) + .represent(@groups) end end end diff --git a/app/controllers/dashboard/labels_controller.rb b/app/controllers/dashboard/labels_controller.rb index d5031da867a..9dcb3a0eb6d 100644 --- a/app/controllers/dashboard/labels_controller.rb +++ b/app/controllers/dashboard/labels_controller.rb @@ -1,9 +1,14 @@ class Dashboard::LabelsController < Dashboard::ApplicationController def index - 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 + + def labels + finder_params = { project_ids: projects.select(:id) } + labels = LabelsFinder.new(current_user, finder_params).execute + + GlobalLabel.build_collection(labels) + end end diff --git a/app/controllers/dashboard/milestones_controller.rb b/app/controllers/dashboard/milestones_controller.rb index df528d10f6e..751dbbd8e96 100644 --- a/app/controllers/dashboard/milestones_controller.rb +++ b/app/controllers/dashboard/milestones_controller.rb @@ -1,6 +1,8 @@ class Dashboard::MilestonesController < Dashboard::ApplicationController + include MilestoneActions + before_action :projects - before_action :milestone, only: [:show] + before_action :milestone, only: [:show, :merge_requests, :participants, :labels] def index respond_to do |format| diff --git a/app/controllers/dashboard/projects_controller.rb b/app/controllers/dashboard/projects_controller.rb index 5a1efcab1a3..f71ab702e71 100644 --- a/app/controllers/dashboard/projects_controller.rb +++ b/app/controllers/dashboard/projects_controller.rb @@ -8,10 +8,10 @@ class Dashboard::ProjectsController < Dashboard::ApplicationController @projects = load_projects(params.merge(non_public: true)).page(params[:page]) respond_to do |format| - format.html { @last_push = current_user.recent_push } + format.html format.atom do load_events - render layout: false + render layout: 'xml.atom' end format.json do render json: { @@ -22,10 +22,9 @@ class Dashboard::ProjectsController < Dashboard::ApplicationController end def starred - @projects = load_projects(params.merge(starred: true)). - includes(:forked_from_project, :tags).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 = [] respond_to do |format| @@ -46,13 +45,17 @@ class Dashboard::ProjectsController < Dashboard::ApplicationController end def load_projects(finder_params) - ProjectsFinder.new(params: finder_params, current_user: current_user). - execute.includes(:route, namespace: :route) + ProjectsFinder + .new(params: finder_params, current_user: current_user) + .execute + .includes(:route, :creator, namespace: :route) end def load_events - @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) + projects = load_projects(params.merge(non_public: true)) + + @events = EventCollection + .new(projects, offset: params[:offset].to_i, filter: event_filter) + .to_a end 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 4d7d45787fc..a8b2b93b458 100644 --- a/app/controllers/dashboard/todos_controller.rb +++ b/app/controllers/dashboard/todos_controller.rb @@ -1,6 +1,7 @@ class Dashboard::TodosController < Dashboard::ApplicationController include ActionView::Helpers::NumberHelper + before_action :authorize_read_project!, only: :index before_action :find_todos, only: [:index, :destroy_all] def index @@ -12,10 +13,14 @@ class Dashboard::TodosController < Dashboard::ApplicationController end def destroy - TodoService.new.mark_todos_as_done_by_ids([params[:id]], current_user) + TodoService.new.mark_todos_as_done_by_ids(params[:id], current_user) respond_to do |format| - format.html { redirect_to dashboard_todos_path, notice: 'Todo was successfully marked as done.' } + format.html do + redirect_to dashboard_todos_path, + status: 302, + notice: 'Todo was successfully marked as done.' + end format.js { head :ok } format.json { render json: todos_counts } end @@ -25,14 +30,14 @@ class Dashboard::TodosController < Dashboard::ApplicationController updated_ids = TodoService.new.mark_todos_as_done(@todos, current_user) respond_to do |format| - format.html { redirect_to dashboard_todos_path, notice: 'All todos were marked as done.' } + format.html { redirect_to dashboard_todos_path, status: 302, notice: 'All todos were marked as done.' } format.js { head :ok } format.json { render json: todos_counts.merge(updated_ids: updated_ids) } end end def restore - TodoService.new.mark_todos_as_pending_by_ids([params[:id]], current_user) + TodoService.new.mark_todos_as_pending_by_ids(params[:id], current_user) render json: todos_counts end @@ -43,13 +48,17 @@ class Dashboard::TodosController < Dashboard::ApplicationController render json: todos_counts end - # Used in TodosHelper also - def self.todos_count_format(count) - count >= 100 ? '99+' : count - end - private + def authorize_read_project! + project_id = params[:project_id] + + if project_id.present? + project = Project.find(project_id) + render_404 unless can?(current_user, :read_project, project) + end + end + def find_todos @todos ||= TodosFinder.new(current_user, params).execute end diff --git a/app/controllers/dashboard_controller.rb b/app/controllers/dashboard_controller.rb index 79d420a32d3..19a5db6fd17 100644 --- a/app/controllers/dashboard_controller.rb +++ b/app/controllers/dashboard_controller.rb @@ -9,8 +9,6 @@ class DashboardController < Dashboard::ApplicationController respond_to :html def activity - @last_push = current_user.recent_push - respond_to do |format| format.html @@ -26,14 +24,14 @@ class DashboardController < Dashboard::ApplicationController def load_events projects = if params[:filter] == "starred" - current_user.viewable_starred_projects + ProjectsFinder.new(current_user: current_user, params: { starred: true }).execute else current_user.authorized_projects end - @events = Event.in_projects(projects) - @events = @event_filter.apply_filter(@events).with_associations - @events = @events.limit(20).offset(params[:offset] || 0) + @events = EventCollection + .new(projects, offset: params[:offset].to_i, filter: @event_filter) + .to_a end def set_show_full_reference 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 8f1870759e4..762c6ebf3a3 100644 --- a/app/controllers/explore/projects_controller.rb +++ b/app/controllers/explore/projects_controller.rb @@ -6,7 +6,7 @@ class Explore::ProjectsController < Explore::ApplicationController def index params[:sort] ||= 'latest_activity_desc' @sort = params[:sort] - @projects = load_projects.page(params[:page]) + @projects = load_projects respond_to do |format| format.html @@ -21,7 +21,7 @@ class Explore::ProjectsController < Explore::ApplicationController def trending params[:trending] = true @sort = params[:sort] - @projects = load_projects.page(params[:page]) + @projects = load_projects respond_to do |format| format.html @@ -34,7 +34,7 @@ class Explore::ProjectsController < Explore::ApplicationController end def starred - @projects = load_projects.reorder('star_count DESC').page(params[:page]) + @projects = load_projects.reorder('star_count DESC') respond_to do |format| format.html @@ -49,7 +49,10 @@ class Explore::ProjectsController < Explore::ApplicationController private def load_projects - ProjectsFinder.new(current_user: current_user, params: params). - execute.includes(:route, namespace: :route) + ProjectsFinder.new(current_user: current_user, params: params) + .execute + .includes(:route, namespace: :route) + .page(params[:page]) + .without_count 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 29ffaeb19c1..96ce686c989 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,29 +9,17 @@ class Groups::ApplicationController < ApplicationController private def group - unless @group - id = params[:group_id] || params[:id] - @group = Group.find_by_full_path(id) - @group_merge_requests = MergeRequestsFinder.new(current_user, group_id: @group.id).execute - - 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: 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! unless can?(current_user, :admin_group, group) return render_404 @@ -41,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/avatars_controller.rb b/app/controllers/groups/avatars_controller.rb index ad2c20b42db..735915abdaa 100644 --- a/app/controllers/groups/avatars_controller.rb +++ b/app/controllers/groups/avatars_controller.rb @@ -5,6 +5,6 @@ class Groups::AvatarsController < Groups::ApplicationController @group.remove_avatar! @group.save - redirect_to edit_group_path(@group) + redirect_to edit_group_path(@group), status: 302 end end diff --git a/app/controllers/groups/labels_controller.rb b/app/controllers/groups/labels_controller.rb index facb25525b5..dda59262483 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 @@ -54,7 +54,7 @@ class Groups::LabelsController < Groups::ApplicationController respond_to do |format| format.html do - redirect_to group_labels_path(@group), notice: 'Label was removed' + redirect_to group_labels_path(@group), status: 302, notice: 'Label was removed' end format.js end diff --git a/app/controllers/groups/milestones_controller.rb b/app/controllers/groups/milestones_controller.rb index 43102596201..5c10d7bc261 100644 --- a/app/controllers/groups/milestones_controller.rb +++ b/app/controllers/groups/milestones_controller.rb @@ -1,14 +1,19 @@ class Groups::MilestonesController < Groups::ApplicationController + include MilestoneActions + before_action :group_projects - before_action :milestone, only: [:show, :update] - before_action :authorize_admin_milestones!, only: [:new, :create, :update] + before_action :milestone, only: [:edit, :show, :update, :merge_requests, :participants, :labels] + before_action :authorize_admin_milestones!, only: [:edit, :new, :create, :update] def index respond_to do |format| format.html do - @milestone_states = GlobalMilestone.states_count(@projects) + @milestone_states = GlobalMilestone.states_count(group_projects, group) @milestones = Kaminari.paginate_array(milestones).page(params[:page]) end + format.json do + render json: milestones.map { |m| m.for_display.slice(:title, :name) } + end end end @@ -17,49 +22,41 @@ class Groups::MilestonesController < Groups::ApplicationController end def create - project_ids = params[:milestone][:project_ids].reject(&:blank?) - title = milestone_params[:title] + @milestone = Milestones::CreateService.new(group, current_user, milestone_params).execute - if create_milestones(project_ids) - redirect_to milestone_path(title) + if @milestone.persisted? + redirect_to milestone_path else - render_new_with_error(project_ids.empty?) + render "new" end end def show end - def update - @milestone.milestones.each do |milestone| - Milestones::UpdateService.new(milestone.project, current_user, milestone_params).execute(milestone) - end - - redirect_back_or_default(default: milestone_path(@milestone.title)) + def edit + render_404 if @milestone.is_legacy_group_milestone? end - private - - def create_milestones(project_ids) - return false unless project_ids.present? + def update + # Keep this compatible with legacy group milestones where we have to update + # all projects milestones states at once. + if @milestone.is_legacy_group_milestone? + update_params = milestone_params.select { |key| key == "state_event" } + milestones = @milestone.milestones + else + update_params = milestone_params + milestones = [@milestone] + end - ActiveRecord::Base.transaction do - @projects.where(id: project_ids).each do |project| - Milestones::CreateService.new(project, current_user, milestone_params).execute - end + milestones.each do |milestone| + Milestones::UpdateService.new(milestone.parent, current_user, update_params).execute(milestone) end - true - rescue ActiveRecord::ActiveRecordError => e - flash.now[:alert] = "An error occurred while creating the milestone: #{e.message}" - false + redirect_to milestone_path end - def render_new_with_error(empty_project_ids) - @milestone = Milestone.new(milestone_params) - @milestone.errors.add(:base, "Please select at least one project.") if empty_project_ids - render :new - end + private def authorize_admin_milestones! return render_404 unless can?(current_user, :admin_milestones, group) @@ -69,16 +66,31 @@ class Groups::MilestonesController < Groups::ApplicationController params.require(:milestone).permit(:title, :description, :start_date, :due_date, :state_event) end - def milestone_path(title) - group_milestone_path(@group, title.to_slug.to_s, title: title) + def milestone_path + if @milestone.is_legacy_group_milestone? + group_milestone_path(group, @milestone.safe_title, title: @milestone.title) + else + group_milestone_path(group, @milestone.iid) + end end def milestones - @milestones = GroupMilestone.build_collection(@group, @projects, params) + search_params = params.merge(group_ids: group.id) + + milestones = MilestonesFinder.new(search_params).execute + legacy_milestones = GroupMilestone.build_collection(group, group_projects, params) + + milestones + legacy_milestones end def milestone - @milestone = GroupMilestone.build(@group, @projects, params[:title]) + @milestone = + if params[:title] + GroupMilestone.build(group, group_projects, params[:title]) + else + group.milestones.find_by_iid(params[:id]) + end + render_404 unless @milestone end end diff --git a/app/controllers/groups/settings/ci_cd_controller.rb b/app/controllers/groups/settings/ci_cd_controller.rb new file mode 100644 index 00000000000..0142ad8278c --- /dev/null +++ b/app/controllers/groups/settings/ci_cd_controller.rb @@ -0,0 +1,24 @@ +module Groups + module Settings + class CiCdController < Groups::ApplicationController + before_action :authorize_admin_pipeline! + + def show + define_secret_variables + end + + private + + def define_secret_variables + @variable = Ci::GroupVariable.new(group: group) + .present(current_user: current_user) + @variables = group.variables.order_key_asc + .map { |variable| variable.present(current_user: current_user) } + end + + def authorize_admin_pipeline! + return render_404 unless can?(current_user, :admin_pipeline, group) + end + end + end +end diff --git a/app/controllers/groups/variables_controller.rb b/app/controllers/groups/variables_controller.rb new file mode 100644 index 00000000000..10038ff3ad9 --- /dev/null +++ b/app/controllers/groups/variables_controller.rb @@ -0,0 +1,64 @@ +module Groups + class VariablesController < Groups::ApplicationController + before_action :variable, only: [:show, :update, :destroy] + before_action :authorize_admin_build! + + def index + redirect_to group_settings_ci_cd_path(group) + end + + def show + end + + def update + if variable.update(variable_params) + redirect_to group_variables_path(group), + notice: 'Variable was successfully updated.' + else + render "show" + end + end + + def create + @variable = group.variables.create(variable_params) + .present(current_user: current_user) + + if @variable.persisted? + redirect_to group_settings_ci_cd_path(group), + notice: 'Variable was successfully created.' + else + render "show" + end + end + + def destroy + if variable.destroy + redirect_to group_settings_ci_cd_path(group), + status: 302, + notice: 'Variable was successfully removed.' + else + redirect_to group_settings_ci_cd_path(group), + status: 302, + notice: 'Failed to remove the variable.' + end + end + + private + + def variable_params + params.require(:variable).permit(*variable_params_attributes) + end + + def variable_params_attributes + %i[key value protected] + end + + def variable + @variable ||= group.variables.find(params[:id]).present(current_user: current_user) + end + + def authorize_admin_build! + return render_404 unless can?(current_user, :admin_build, group) + end + end +end diff --git a/app/controllers/groups_controller.rb b/app/controllers/groups_controller.rb index 593001e6396..f76b3f69e9e 100644 --- a/app/controllers/groups_controller.rb +++ b/app/controllers/groups_controller.rb @@ -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] @@ -58,13 +58,15 @@ class GroupsController < Groups::ApplicationController format.atom do load_events - render layout: false + render layout: 'xml.atom' end end end def subgroups - @nested_groups = group.children + return not_found unless Group.supports_nested_groups? + + @nested_groups = GroupsFinder.new(current_user, parent: group).execute @nested_groups = @nested_groups.search(params[:filter_groups]) if params[:filter_groups].present? end @@ -99,7 +101,7 @@ class GroupsController < Groups::ApplicationController def destroy Groups::DestroyService.new(@group, current_user).async_execute - redirect_to root_path, alert: "Group '#{@group.name}' was scheduled for deletion." + redirect_to root_path, status: 302, alert: "Group '#{@group.name}' was scheduled for deletion." end protected @@ -158,15 +160,22 @@ class GroupsController < Groups::ApplicationController end def load_events - @events = Event.in_projects(@projects) - @events = event_filter.apply_filter(@events).with_associations - @events = @events.limit(20).offset(params[:offset] || 0) + @events = EventCollection + .new(@projects, offset: params[:offset].to_i, filter: event_filter) + .to_a end def user_actions if current_user - @last_push = current_user.recent_push @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 5d3109b7187..c3d18991fd4 100644 --- a/app/controllers/health_check_controller.rb +++ b/app/controllers/health_check_controller.rb @@ -1,3 +1,3 @@ class HealthCheckController < HealthCheck::HealthCheckController - include RequiresHealthToken + include RequiresWhitelistedMonitoringClient end diff --git a/app/controllers/health_controller.rb b/app/controllers/health_controller.rb index df0fc3132ed..98c2aaa3526 100644 --- a/app/controllers/health_controller.rb +++ b/app/controllers/health_controller.rb @@ -1,11 +1,14 @@ class HealthController < ActionController::Base protect_from_forgery with: :exception - include RequiresHealthToken + include RequiresWhitelistedMonitoringClient CHECKS = [ Gitlab::HealthChecks::DbCheck, - Gitlab::HealthChecks::RedisCheck, - Gitlab::HealthChecks::FsShardsCheck, + Gitlab::HealthChecks::Redis::RedisCheck, + Gitlab::HealthChecks::Redis::CacheCheck, + Gitlab::HealthChecks::Redis::QueuesCheck, + Gitlab::HealthChecks::Redis::SharedStateCheck, + Gitlab::HealthChecks::FsShardsCheck ].freeze def readiness @@ -20,25 +23,8 @@ class HealthController < ActionController::Base 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) diff --git a/app/controllers/import/github_controller.rb b/app/controllers/import/github_controller.rb index 53a5981e564..ab18d86dcae 100644 --- a/app/controllers/import/github_controller.rb +++ b/app/controllers/import/github_controller.rb @@ -64,19 +64,19 @@ class Import::GithubController < Import::BaseController end def import_enabled? - __send__("#{provider}_import_enabled?") + __send__("#{provider}_import_enabled?") # rubocop:disable GitlabSecurity/PublicSend end def new_import_url - public_send("new_import_#{provider}_url") + public_send("new_import_#{provider}_url") # rubocop:disable GitlabSecurity/PublicSend end def status_import_url - public_send("status_import_#{provider}_url") + public_send("status_import_#{provider}_url") # rubocop:disable GitlabSecurity/PublicSend end def callback_import_url - public_send("callback_import_#{provider}_url") + public_send("callback_import_#{provider}_url") # rubocop:disable GitlabSecurity/PublicSend end def provider_unauthorized diff --git a/app/controllers/import/gitlab_controller.rb b/app/controllers/import/gitlab_controller.rb index 73837ffbe67..407154e59a0 100644 --- a/app/controllers/import/gitlab_controller.rb +++ b/app/controllers/import/gitlab_controller.rb @@ -15,7 +15,7 @@ class Import::GitlabController < Import::BaseController @already_added_projects = current_user.created_projects.where(import_type: "gitlab") already_added_projects_names = @already_added_projects.pluck(:import_source) - @repos = @repos.to_a.reject{ |repo| already_added_projects_names.include? repo["path_with_namespace"] } + @repos = @repos.to_a.reject { |repo| already_added_projects_names.include? repo["path_with_namespace"] } end def jobs diff --git a/app/controllers/import/gitlab_projects_controller.rb b/app/controllers/import/gitlab_projects_controller.rb index 36d246d185b..510813846a4 100644 --- a/app/controllers/import/gitlab_projects_controller.rb +++ b/app/controllers/import/gitlab_projects_controller.rb @@ -12,15 +12,7 @@ class Import::GitlabProjectsController < Import::BaseController return redirect_back_or_default(options: { alert: "You need to upload a GitLab project export archive." }) end - import_upload_path = Gitlab::ImportExport.import_upload_path(filename: project_params[:file].original_filename) - - FileUtils.mkdir_p(File.dirname(import_upload_path)) - FileUtils.copy_entry(project_params[:file].path, import_upload_path) - - @project = Gitlab::ImportExport::ProjectCreator.new(project_params[:namespace_id], - current_user, - import_upload_path, - project_params[:path]).execute + @project = ::Projects::GitlabProjectsImportService.new(current_user, project_params).execute if @project.saved? redirect_to( diff --git a/app/controllers/invites_controller.rb b/app/controllers/invites_controller.rb index 7625187c7be..0982a61902b 100644 --- a/app/controllers/invites_controller.rb +++ b/app/controllers/invites_controller.rb @@ -63,7 +63,7 @@ class InvitesController < ApplicationController when Project project = member.source label = "project #{project.name_with_namespace}" - path = namespace_project_path(project.namespace, project) + path = project_path(project) when Group group = member.source label = "group #{group.name}" diff --git a/app/controllers/jwt_controller.rb b/app/controllers/jwt_controller.rb index 3109439b2ff..4bceb1d67a3 100644 --- a/app/controllers/jwt_controller.rb +++ b/app/controllers/jwt_controller.rb @@ -4,15 +4,15 @@ class JwtController < ApplicationController before_action :authenticate_project_or_user SERVICES = { - Auth::ContainerRegistryAuthenticationService::AUDIENCE => Auth::ContainerRegistryAuthenticationService, + Auth::ContainerRegistryAuthenticationService::AUDIENCE => Auth::ContainerRegistryAuthenticationService }.freeze def auth service = SERVICES[params[:service]] return head :not_found unless service - result = service.new(@authentication_result.project, @authentication_result.actor, auth_params). - execute(authentication_abilities: @authentication_result.authentication_abilities) + result = service.new(@authentication_result.project, @authentication_result.actor, auth_params) + .execute(authentication_abilities: @authentication_result.authentication_abilities) render json: result, status: result[:http_status] end @@ -25,8 +25,10 @@ class JwtController < ApplicationController authenticate_with_http_basic do |login, password| @authentication_result = Gitlab::Auth.find_for_git_client(login, password, project: nil, ip: request.ip) - render_unauthorized unless @authentication_result.success? && - (@authentication_result.actor.nil? || @authentication_result.actor.is_a?(User)) + if @authentication_result.failed? || + (@authentication_result.actor.present? && !@authentication_result.actor.is_a?(User)) + render_unauthorized + end end rescue Gitlab::Auth::MissingPersonalTokenError render_missing_personal_token @@ -37,7 +39,7 @@ class JwtController < ApplicationController errors: [ { code: 'UNAUTHORIZED', message: "HTTP Basic: Access denied\n" \ - "You have 2FA enabled, please use a personal access token for Git over HTTP.\n" \ + "You must use a personal access token with 'api' scope for Git over HTTP.\n" \ "You can generate one at #{profile_personal_access_tokens_url}" } ] }, status: 401 diff --git a/app/controllers/metrics_controller.rb b/app/controllers/metrics_controller.rb new file mode 100644 index 00000000000..37587a52eaf --- /dev/null +++ b/app/controllers/metrics_controller.rb @@ -0,0 +1,21 @@ +class MetricsController < ActionController::Base + include RequiresWhitelistedMonitoringClient + + protect_from_forgery with: :exception + + before_action :validate_prometheus_metrics + + def index + render text: metrics_service.metrics_text, content_type: 'text/plain; version=0.0.4' + end + + private + + def metrics_service + @metrics_service ||= MetricsService.new + end + + def validate_prometheus_metrics + render_404 unless Gitlab::Metrics.prometheus_metrics_enabled? + end +end diff --git a/app/controllers/oauth/authorized_applications_controller.rb b/app/controllers/oauth/authorized_applications_controller.rb index 4193ac11399..656107d2b26 100644 --- a/app/controllers/oauth/authorized_applications_controller.rb +++ b/app/controllers/oauth/authorized_applications_controller.rb @@ -10,6 +10,8 @@ class Oauth::AuthorizedApplicationsController < Doorkeeper::AuthorizedApplicatio Doorkeeper::AccessToken.revoke_all_for(params[:id], current_resource_owner) end - redirect_to applications_profile_url, notice: I18n.t(:notice, scope: [:doorkeeper, :flash, :authorized_applications, :destroy]) + redirect_to applications_profile_url, + status: 302, + notice: I18n.t(:notice, scope: [:doorkeeper, :flash, :authorized_applications, :destroy]) end end diff --git a/app/controllers/omniauth_callbacks_controller.rb b/app/controllers/omniauth_callbacks_controller.rb index 58d50ad647b..7444826a5d1 100644 --- a/app/controllers/omniauth_callbacks_controller.rb +++ b/app/controllers/omniauth_callbacks_controller.rb @@ -1,5 +1,6 @@ class OmniauthCallbacksController < Devise::OmniauthCallbacksController include AuthenticatesWithTwoFactor + include Devise::Controllers::Rememberable protect_from_forgery except: [:kerberos, :saml, :cas3] @@ -33,12 +34,11 @@ class OmniauthCallbacksController < Devise::OmniauthCallbacksController if @user.two_factor_enabled? prompt_for_two_factor(@user) else - log_audit_event(@user, with: :ldap) + log_audit_event(@user, with: oauth['provider']) sign_in_and_redirect(@user) end else - flash[:alert] = "Access denied for your LDAP account." - redirect_to new_user_session_path + fail_ldap_login end end @@ -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 @@ -115,14 +115,14 @@ class OmniauthCallbacksController < Devise::OmniauthCallbacksController if @user.persisted? && @user.valid? log_audit_event(@user, with: oauth['provider']) if @user.two_factor_enabled? + params[:remember_me] = '1' if remember_me? prompt_for_two_factor(@user) else + remember_me(@user) if remember_me? sign_in_and_redirect(@user) end else - error_message = @user.errors.full_messages.to_sentence - - return redirect_to omniauth_error_path(oauth['provider'], error: error_message) + fail_login end end @@ -143,8 +143,25 @@ class OmniauthCallbacksController < Devise::OmniauthCallbacksController @oauth ||= request.env['omniauth.auth'] end + def fail_login + error_message = @user.errors.full_messages.to_sentence + + return redirect_to omniauth_error_path(oauth['provider'], error: error_message) + end + + def fail_ldap_login + flash[:alert] = 'Access denied for your LDAP account.' + + redirect_to new_user_session_path + end + def log_audit_event(user, options = {}) - AuditEventService.new(user, user, options). - for_authentication.security_event + AuditEventService.new(user, user, options) + .for_authentication.security_event + end + + def remember_me? + request_params = request.env['omniauth.params'] + (request_params['remember_me'] == '1') if request_params.present? end end diff --git a/app/controllers/passwords_controller.rb b/app/controllers/passwords_controller.rb index a8575e037e4..aa8cf630032 100644 --- a/app/controllers/passwords_controller.rb +++ b/app/controllers/passwords_controller.rb @@ -1,6 +1,8 @@ class PasswordsController < Devise::PasswordsController + include Gitlab::CurrentSettings + before_action :resource_from_email, only: [:create] - before_action :prevent_ldap_reset, only: [:create] + before_action :check_password_authentication_available, only: [:create] before_action :throttle_reset, only: [:create] def edit @@ -25,7 +27,7 @@ class PasswordsController < Devise::PasswordsController def update super do |resource| - if resource.valid? && resource.require_password? + if resource.valid? && resource.require_password_creation? resource.update_attribute(:password_automatically_set, false) end end @@ -38,11 +40,11 @@ class PasswordsController < Devise::PasswordsController self.resource = resource_class.find_by_email(email) end - def prevent_ldap_reset - return unless resource && resource.ldap_user? + def check_password_authentication_available + return if current_application_settings.password_authentication_enabled? && (resource.nil? || resource.allow_password_authentication?) redirect_to after_sending_reset_password_instructions_path_for(resource_name), - alert: "Cannot reset password for LDAP user." + alert: "Password authentication is unavailable." end def throttle_reset diff --git a/app/controllers/profiles/avatars_controller.rb b/app/controllers/profiles/avatars_controller.rb index daa51ae41df..408650aac54 100644 --- a/app/controllers/profiles/avatars_controller.rb +++ b/app/controllers/profiles/avatars_controller.rb @@ -1,10 +1,9 @@ class Profiles::AvatarsController < Profiles::ApplicationController def destroy @user = current_user - @user.remove_avatar! - @user.save + Users::UpdateService.new(@user).execute { |user| user.remove_avatar! } - redirect_to profile_path + redirect_to profile_path, status: 302 end end diff --git a/app/controllers/profiles/chat_names_controller.rb b/app/controllers/profiles/chat_names_controller.rb index 6a1f468ba5a..2353f0840d6 100644 --- a/app/controllers/profiles/chat_names_controller.rb +++ b/app/controllers/profiles/chat_names_controller.rb @@ -39,7 +39,7 @@ class Profiles::ChatNamesController < Profiles::ApplicationController flash[:alert] = "Could not delete chat nickname #{@chat_name.chat_name}." end - redirect_to profile_chat_names_path + redirect_to profile_chat_names_path, status: 302 end private diff --git a/app/controllers/profiles/emails_controller.rb b/app/controllers/profiles/emails_controller.rb index 1c24c4db993..17b66df43e7 100644 --- a/app/controllers/profiles/emails_controller.rb +++ b/app/controllers/profiles/emails_controller.rb @@ -5,9 +5,9 @@ class Profiles::EmailsController < Profiles::ApplicationController end def create - @email = current_user.emails.new(email_params) + @email = Emails::CreateService.new(current_user, email_params).execute - if @email.save + if @email.errors.blank? NotificationService.new.new_email(@email) else flash[:alert] = @email.errors.full_messages.first @@ -18,12 +18,11 @@ class Profiles::EmailsController < Profiles::ApplicationController def destroy @email = current_user.emails.find(params[:id]) - @email.destroy - current_user.update_secondary_emails! + Emails::DestroyService.new(current_user, email: @email.email).execute respond_to do |format| - format.html { redirect_to profile_emails_url } + format.html { redirect_to profile_emails_url, status: 302 } format.js { head :ok } end end diff --git a/app/controllers/profiles/gpg_keys_controller.rb b/app/controllers/profiles/gpg_keys_controller.rb new file mode 100644 index 00000000000..6779cc6ddac --- /dev/null +++ b/app/controllers/profiles/gpg_keys_controller.rb @@ -0,0 +1,47 @@ +class Profiles::GpgKeysController < Profiles::ApplicationController + before_action :set_gpg_key, only: [:destroy, :revoke] + + def index + @gpg_keys = current_user.gpg_keys + @gpg_key = GpgKey.new + end + + def create + @gpg_key = current_user.gpg_keys.new(gpg_key_params) + + if @gpg_key.save + redirect_to profile_gpg_keys_path + else + @gpg_keys = current_user.gpg_keys.select(&:persisted?) + render :index + end + end + + def destroy + @gpg_key.destroy + + respond_to do |format| + format.html { redirect_to profile_gpg_keys_url, status: 302 } + format.js { head :ok } + end + end + + def revoke + @gpg_key.revoke + + respond_to do |format| + format.html { redirect_to profile_gpg_keys_url, status: 302 } + format.js { head :ok } + end + end + + private + + def gpg_key_params + params.require(:gpg_key).permit(:key) + end + + def set_gpg_key + @gpg_key = current_user.gpg_keys.find(params[:id]) + end +end diff --git a/app/controllers/profiles/keys_controller.rb b/app/controllers/profiles/keys_controller.rb index e4452f46056..88f49da555a 100644 --- a/app/controllers/profiles/keys_controller.rb +++ b/app/controllers/profiles/keys_controller.rb @@ -26,7 +26,7 @@ class Profiles::KeysController < Profiles::ApplicationController @key.destroy respond_to do |format| - format.html { redirect_to profile_keys_url } + format.html { redirect_to profile_keys_url, status: 302 } format.js { head :ok } end end diff --git a/app/controllers/profiles/notifications_controller.rb b/app/controllers/profiles/notifications_controller.rb index a271e2dfc4b..960b7512602 100644 --- a/app/controllers/profiles/notifications_controller.rb +++ b/app/controllers/profiles/notifications_controller.rb @@ -7,7 +7,9 @@ class Profiles::NotificationsController < Profiles::ApplicationController end def update - if current_user.update_attributes(user_params) + result = Users::UpdateService.new(current_user, user_params).execute + + if result[:status] == :success flash[:notice] = "Notification settings saved" else flash[:alert] = "Failed to save new settings" diff --git a/app/controllers/profiles/passwords_controller.rb b/app/controllers/profiles/passwords_controller.rb index 6217ec5ecef..c423761ab24 100644 --- a/app/controllers/profiles/passwords_controller.rb +++ b/app/controllers/profiles/passwords_controller.rb @@ -15,17 +15,17 @@ class Profiles::PasswordsController < Profiles::ApplicationController return end - new_password = user_params[:password] - new_password_confirmation = user_params[:password_confirmation] - - result = @user.update_attributes( - password: new_password, - password_confirmation: new_password_confirmation, + password_attributes = { + password: user_params[:password], + password_confirmation: user_params[:password_confirmation], password_automatically_set: false - ) + } + + result = Users::UpdateService.new(@user, password_attributes).execute + + if result[:status] == :success + Users::UpdateService.new(@user, password_expires_at: nil).execute - if result - @user.update_attributes(password_expires_at: nil) redirect_to root_path, notice: 'Password successfully changed' else render :new @@ -46,7 +46,9 @@ class Profiles::PasswordsController < Profiles::ApplicationController return end - if @user.update_attributes(password_attributes) + result = Users::UpdateService.new(@user, password_attributes).execute + + if result[:status] == :success flash[:notice] = "Password was successfully updated. Please login with it" redirect_to new_user_session_path else @@ -75,7 +77,7 @@ class Profiles::PasswordsController < Profiles::ApplicationController end def authorize_change_password! - return render_404 if @user.ldap_user? + render_404 unless @user.allow_password_authentication? end def user_params diff --git a/app/controllers/profiles/personal_access_tokens_controller.rb b/app/controllers/profiles/personal_access_tokens_controller.rb index 0abe7ea3c9b..f748d191ef4 100644 --- a/app/controllers/profiles/personal_access_tokens_controller.rb +++ b/app/controllers/profiles/personal_access_tokens_controller.rb @@ -38,7 +38,7 @@ class Profiles::PersonalAccessTokensController < Profiles::ApplicationController end def set_index_vars - @scopes = Gitlab::Auth::API_SCOPES + @scopes = Gitlab::Auth::AVAILABLE_SCOPES @personal_access_token = finder.build @inactive_personal_access_tokens = finder(state: 'inactive').execute diff --git a/app/controllers/profiles/preferences_controller.rb b/app/controllers/profiles/preferences_controller.rb index 0d891ef4004..1e557c47638 100644 --- a/app/controllers/profiles/preferences_controller.rb +++ b/app/controllers/profiles/preferences_controller.rb @@ -6,7 +6,9 @@ class Profiles::PreferencesController < Profiles::ApplicationController def update begin - if @user.update_attributes(preferences_params) + result = Users::UpdateService.new(user, preferences_params).execute + + if result[:status] == :success flash[:notice] = 'Preferences saved.' else flash[:alert] = 'Failed to save preferences.' @@ -33,7 +35,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 d3fa81cd623..1a4f77639e7 100644 --- a/app/controllers/profiles/two_factor_auths_controller.rb +++ b/app/controllers/profiles/two_factor_auths_controller.rb @@ -10,7 +10,7 @@ class Profiles::TwoFactorAuthsController < Profiles::ApplicationController current_user.otp_grace_period_started_at = Time.current end - current_user.save! if current_user.changed? + Users::UpdateService.new(current_user).execute! if two_factor_authentication_required? && !current_user.two_factor_enabled? two_factor_authentication_reason( @@ -41,9 +41,9 @@ class Profiles::TwoFactorAuthsController < Profiles::ApplicationController def create if current_user.validate_and_consume_otp!(params[:pin_code]) - current_user.otp_required_for_login = true - @codes = current_user.generate_otp_backup_codes! - current_user.save! + Users::UpdateService.new(current_user, otp_required_for_login: true).execute! do |user| + @codes = user.generate_otp_backup_codes! + end render 'create' else @@ -70,14 +70,15 @@ class Profiles::TwoFactorAuthsController < Profiles::ApplicationController end def codes - @codes = current_user.generate_otp_backup_codes! - current_user.save! + Users::UpdateService.new(current_user).execute! do |user| + @codes = user.generate_otp_backup_codes! + end end def destroy current_user.disable_two_factor! - redirect_to profile_account_path + redirect_to profile_account_path, status: 302 end def skip diff --git a/app/controllers/profiles/u2f_registrations_controller.rb b/app/controllers/profiles/u2f_registrations_controller.rb index c02fe85c3cc..e3d7737f44a 100644 --- a/app/controllers/profiles/u2f_registrations_controller.rb +++ b/app/controllers/profiles/u2f_registrations_controller.rb @@ -2,6 +2,6 @@ class Profiles::U2fRegistrationsController < Profiles::ApplicationController def destroy u2f_registration = current_user.u2f_registrations.find(params[:id]) u2f_registration.destroy - redirect_to profile_two_factor_auth_path, notice: "Successfully deleted U2F device." + redirect_to profile_two_factor_auth_path, status: 302, notice: "Successfully deleted U2F device." end end diff --git a/app/controllers/profiles_controller.rb b/app/controllers/profiles_controller.rb index 987b95e89b9..076076fd1b3 100644 --- a/app/controllers/profiles_controller.rb +++ b/app/controllers/profiles_controller.rb @@ -9,50 +9,67 @@ class ProfilesController < Profiles::ApplicationController end def update - user_params.except!(:email) if @user.ldap_user? + user_params.except!(:email) if @user.external_email? respond_to do |format| - if @user.update_attributes(user_params) + result = Users::UpdateService.new(@user, user_params).execute + + if result[:status] == :success message = "Profile was successfully updated" + format.html { redirect_back_or_default(default: { action: 'show' }, options: { notice: message }) } format.json { render json: { message: message } } else - message = @user.errors.full_messages.uniq.join('. ') - format.html { redirect_back_or_default(default: { action: 'show' }, options: { alert: "Failed to update profile. #{message}" }) } - format.json { render json: { message: message }, status: :unprocessable_entity } + format.html { redirect_back_or_default(default: { action: 'show' }, options: { alert: result[:message] }) } + format.json { render json: result } end end end def reset_private_token - if current_user.reset_authentication_token! - flash[:notice] = "Private token was successfully reset" + Users::UpdateService.new(@user).execute! do |user| + user.reset_authentication_token! end + flash[:notice] = "Private token was successfully reset" + redirect_to profile_account_path end def reset_incoming_email_token - if current_user.reset_incoming_email_token! - flash[:notice] = "Incoming email token was successfully reset" + Users::UpdateService.new(@user).execute! do |user| + user.reset_incoming_email_token! + end + + flash[:notice] = "Incoming email token was successfully reset" + + redirect_to profile_account_path + end + + def reset_rss_token + Users::UpdateService.new(@user).execute! do |user| + user.reset_rss_token! end + flash[:notice] = "RSS token was successfully reset" + redirect_to profile_account_path end def audit_log - @events = AuditEvent.where(entity_type: "User", entity_id: current_user.id). - order("created_at DESC"). - page(params[:page]) + @events = AuditEvent.where(entity_type: "User", entity_id: current_user.id) + .order("created_at DESC") + .page(params[:page]) end def update_username - if @user.update_attributes(username: user_params[:username]) - options = { notice: "Username successfully changed" } - else - message = @user.errors.full_messages.uniq.join('. ') - options = { alert: "Username change failed - #{message}" } - end + result = Users::UpdateService.new(@user, username: user_params[:username]).execute + + options = if result[:status] == :success + { notice: "Username successfully changed" } + else + { alert: "Username change failed - #{result[:message]}" } + end redirect_back_or_default(default: { action: 'show' }, options: options) end @@ -68,7 +85,7 @@ class ProfilesController < Profiles::ApplicationController end def user_params - params.require(:user).permit( + @user_params ||= params.require(:user).permit( :avatar, :bio, :email, @@ -85,7 +102,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 e2f81b09adc..221e01b415a 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,30 @@ 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 + return nil unless params[:project_id] || params[:id] - @project + path = File.join(params[:namespace_id], params[:project_id] || params[:id]) + auth_proc = ->(project) { !project.pending_delete? } + + @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 +48,27 @@ 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 check_project_feature_available!(feature) + render_404 unless project.feature_available?(feature, current_user) + end + + def check_issuables_available! + render_404 unless project.feature_available?(:issues, current_user) || + project.feature_available?(:merge_requests, current_user) end def method_missing(method_sym, *arguments, &block) - if method_sym.to_s =~ /\Aauthorize_(.*)!\z/ - authorize_project!($1.to_sym) + case method_sym.to_s + when /\Aauthorize_(.*)!\z/ + authorize_action!($1.to_sym) + when /\Acheck_(.*)_available!\z/ + check_project_feature_available!($1.to_sym) else super end @@ -70,13 +77,13 @@ class Projects::ApplicationController < ApplicationController def require_non_empty_project # Be sure to return status code 303 to avoid a double DELETE: # http://api.rubyonrails.org/classes/ActionController/Redirecting.html - redirect_to namespace_project_path(@project.namespace, @project), status: 303 if @project.empty_repo? + redirect_to project_path(@project), status: 303 if @project.empty_repo? end def require_branch_head unless @repository.branch_exists?(@ref) redirect_to( - namespace_project_tree_path(@project.namespace, @project, @ref), + project_tree_path(@project, @ref), notice: "This action is not allowed unless you are on a branch" ) end @@ -86,7 +93,7 @@ class Projects::ApplicationController < ApplicationController cookies.permanent[:diff_view] = params.delete(:view) if params[:view].present? end - def builds_enabled - return render_404 unless @project.feature_available?(:builds, current_user) + 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..f637a9a803b 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,25 +18,35 @@ 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 + conditionally_expand_blob(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) + redirect_to project_job_path(project, build) end def latest_succeeded @@ -60,11 +72,14 @@ 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 - project.builds.find_by(id: params[:build_id]) if params[:build_id] + project.builds.find_by(id: params[:job_id]) if params[:job_id] end def build_from_ref @@ -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/avatars_controller.rb b/app/controllers/projects/avatars_controller.rb index 53788687076..21a403f3765 100644 --- a/app/controllers/projects/avatars_controller.rb +++ b/app/controllers/projects/avatars_controller.rb @@ -21,6 +21,6 @@ class Projects::AvatarsController < Projects::ApplicationController @project.save - redirect_to edit_project_path(@project) + redirect_to edit_project_path(@project), status: 302 end end diff --git a/app/controllers/projects/badges_controller.rb b/app/controllers/projects/badges_controller.rb index 6c25cd83a24..06ba73d8e8d 100644 --- a/app/controllers/projects/badges_controller.rb +++ b/app/controllers/projects/badges_controller.rb @@ -3,11 +3,11 @@ class Projects::BadgesController < Projects::ApplicationController before_action :authorize_admin_project!, only: [:index] before_action :no_cache_headers, except: [:index] - def build - build_status = Gitlab::Badge::Build::Status + def pipeline + pipeline_status = Gitlab::Badge::Pipeline::Status .new(project, params[:ref]) - render_badge build_status + render_badge pipeline_status end def coverage diff --git a/app/controllers/projects/blob_controller.rb b/app/controllers/projects/blob_controller.rb index 9fce1db6742..2b8f3977e6e 100644 --- a/app/controllers/projects/blob_controller.rb +++ b/app/controllers/projects/blob_controller.rb @@ -2,6 +2,7 @@ class Projects::BlobController < Projects::ApplicationController include ExtractsPath include CreatesCommit + include RendersBlob include ActionView::Helpers::SanitizeHelper # Raised when given an invalid file path @@ -25,22 +26,29 @@ class Projects::BlobController < Projects::ApplicationController end def create - 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(@branch_name, @file_path)) }, + success_path: -> { project_blob_path(@project, File.join(@branch_name, @file_path)) }, failure_view: :new, - failure_path: namespace_project_new_blob_path(@project.namespace, @project, @ref)) + failure_path: project_new_blob_path(@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 + conditionally_expand_blob(@blob) + + respond_to do |format| + format.html do + show_html + end + + format.json do + show_json + end + end end def edit if can_collaborate_with_project? - blob.load_all_data!(@repository) + blob.load_all_data! else redirect_to action: 'show' end @@ -50,7 +58,7 @@ class Projects::BlobController < Projects::ApplicationController @path = params[:file_path] if params[:file_path].present? create_commit(Files::UpdateService, success_path: -> { after_edit_path }, failure_view: :edit, - failure_path: namespace_project_blob_path(@project.namespace, @project, @id)) + failure_path: project_blob_path(@project, @id)) rescue Files::UpdateService::FileChangedError @conflict = true @@ -59,7 +67,7 @@ class Projects::BlobController < Projects::ApplicationController def preview @content = params[:content] - @blob.load_all_data!(@repository) + @blob.load_all_data! diffy = Diffy::Diff.new(@blob.data, @content, diff: '-U 3', include_diff_info: true) diff_lines = diffy.diff.scan(/.*\n/)[2..-1] diff_lines = Gitlab::Diff::Parser.new.parse(diff_lines) @@ -70,17 +78,19 @@ class Projects::BlobController < Projects::ApplicationController def destroy create_commit(Files::DeleteService, success_notice: "The file has been successfully deleted.", - success_path: -> { namespace_project_tree_path(@project.namespace, @project, @branch_name) }, + success_path: -> { project_tree_path(@project, @branch_name) }, failure_view: :show, - failure_path: namespace_project_blob_path(@project.namespace, @project, @id)) + failure_path: project_blob_path(@project, @id)) end def diff apply_diff_view_cookie! - @form = UnfoldForm.new(params) - @lines = Gitlab::Highlight.highlight_lines(repository, @ref, @path) - @lines = @lines[@form.since - 1..@form.to - 1] + @blob.load_all_data! + @lines = Gitlab::Highlight.highlight(@blob.path, @blob.data, repository: @repository).lines + + @form = UnfoldForm.new(params) + @lines = @lines[@form.since - 1..@form.to - 1].map(&:html_safe) if @form.bottom? @match_line = '' @@ -96,14 +106,14 @@ class Projects::BlobController < Projects::ApplicationController private def blob - @blob ||= Blob.decorate(@repository.blob_at(@commit.id, @path)) + @blob ||= @repository.blob_at(@commit.id, @path) if @blob @blob else if tree = @repository.tree(@commit.id, @path) if tree.entries.any? - return redirect_to namespace_project_tree_path(@project.namespace, @project, File.join(@ref, @path)) + return redirect_to project_tree_path(@project, File.join(@ref, @path)) end end @@ -128,10 +138,10 @@ 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 && @branch_name == @ref - diffs_namespace_project_merge_request_path(from_merge_request.target_project.namespace, from_merge_request.target_project, from_merge_request) + + diffs_project_merge_request_path(from_merge_request.target_project, from_merge_request) + "##{hexdigest(@path)}" else - namespace_project_blob_path(@project.namespace, @project, File.join(@branch_name, @path)) + project_blob_path(@project, File.join(@branch_name, @path)) end end @@ -172,7 +182,42 @@ class Projects::BlobController < Projects::ApplicationController end def set_last_commit_sha - @last_commit_sha = Gitlab::Git::Commit. - last_for_path(@repository, @ref, @path).sha + @last_commit_sha = Gitlab::Git::Commit + .last_for_path(@repository, @ref, @path).sha + end + + def show_html + 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 + + def show_json + json = blob_json(@blob) + return render_404 unless json + + path_segments = @path.split('/') + path_segments.pop + tree_path = path_segments.join('/') + + render json: json.merge( + path: blob.path, + name: blob.name, + extension: blob.extension, + size: blob.raw_size, + mime_type: blob.mime_type, + binary: blob.raw_binary?, + simple_viewer: blob.simple_viewer&.class&.partial_name, + rich_viewer: blob.rich_viewer&.class&.partial_name, + show_viewer_switcher: !!blob.show_viewer_switcher?, + render_error: blob.simple_viewer&.render_error || blob.rich_viewer&.render_error, + raw_path: project_raw_path(project, @id), + blame_path: project_blame_path(project, @id), + commits_path: project_commits_path(project, @id), + tree_path: project_tree_path(project, File.join(@ref, tree_path)), + permalink: project_blob_path(project, File.join(@commit.id, @path)) + ) end end diff --git a/app/controllers/projects/boards/issues_controller.rb b/app/controllers/projects/boards/issues_controller.rb index 28c9646910d..653e7bc7e40 100644 --- a/app/controllers/projects/boards/issues_controller.rb +++ b/app/controllers/projects/boards/issues_controller.rb @@ -66,7 +66,8 @@ module Projects end def filter_params - params.merge(board_id: params[:board_id], id: params[:list_id]).compact + params.merge(board_id: params[:board_id], id: params[:list_id]) + .reject { |_, value| value.nil? } end def move_params @@ -82,7 +83,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/boards/lists_controller.rb b/app/controllers/projects/boards/lists_controller.rb index 67e3c9add81..ad53bb749a0 100644 --- a/app/controllers/projects/boards/lists_controller.rb +++ b/app/controllers/projects/boards/lists_controller.rb @@ -5,7 +5,9 @@ module Projects before_action :authorize_read_list!, only: [:index] def index - render json: serialize_as_json(board.lists) + lists = ::Boards::Lists::ListService.new(project, current_user).execute(board) + + render json: serialize_as_json(lists) end def create diff --git a/app/controllers/projects/branches_controller.rb b/app/controllers/projects/branches_controller.rb index 840405f38cb..747768eefb1 100644 --- a/app/controllers/projects/branches_controller.rb +++ b/app/controllers/projects/branches_controller.rb @@ -8,12 +8,12 @@ class Projects::BranchesController < Projects::ApplicationController before_action :authorize_push_code!, only: [:new, :create, :destroy, :destroy_all_merged] def index - @sort = params[:sort].presence || sort_value_name + @sort = params[:sort].presence || sort_value_recently_updated @branches = BranchesFinder.new(@repository, params).execute + @branches = Kaminari.paginate_array(@branches).page(params[:page]) respond_to do |format| format.html do - paginate_branches @refs_pipelines = @project.pipelines.latest_successful_for_refs(@branches.map(&:name)) @max_commits = @branches.reduce(0) do |memo, branch| @@ -22,7 +22,6 @@ class Projects::BranchesController < Projects::ApplicationController end end format.json do - paginate_branches unless params[:show_all] render json: @branches.map(&:name) end end @@ -38,47 +37,60 @@ class Projects::BranchesController < Projects::ApplicationController redirect_to_autodeploy = project.empty_repo? && project.deployment_services.present? - result = CreateBranchService.new(project, current_user). - execute(branch_name, ref) + result = CreateBranchService.new(project, current_user) + .execute(branch_name, ref) if params[:issue_iid] issue = IssuesFinder.new(current_user, project_id: @project.id).find_by(iid: params[:issue_iid]) SystemNoteService.new_issue_branch(issue, @project, current_user, branch_name) if issue end - if result[:status] == :success - @branch = result[:branch] + 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 project_tree_path(@project, branch_name) + end + else + @error = result[:message] + render action: 'new' + end + end - 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) + format.json do + if result[:status] == :success + render json: { name: branch_name, url: project_tree_url(@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 project_branches_path(@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 def destroy_all_merged DeleteMergedBranchesService.new(@project, current_user).async_execute - redirect_to namespace_project_branches_path(@project.namespace, @project), + redirect_to project_branches_path(@project), notice: 'Merged branches are being deleted. This can take some time depending on the number of branches. Please refresh the page to see changes.' end @@ -93,13 +105,8 @@ class Projects::BranchesController < Projects::ApplicationController end end - def paginate_branches - @branches = Kaminari.paginate_array(@branches).page(params[:page]) - end - def url_to_autodeploy_setup(project, branch_name) - namespace_project_new_blob_path( - project.namespace, + project_new_blob_path( project, branch_name, file_name: '.gitlab-ci.yml', diff --git a/app/controllers/projects/build_artifacts_controller.rb b/app/controllers/projects/build_artifacts_controller.rb new file mode 100644 index 00000000000..b45e5d7ff43 --- /dev/null +++ b/app/controllers/projects/build_artifacts_controller.rb @@ -0,0 +1,55 @@ +class Projects::BuildArtifactsController < Projects::ApplicationController + include ExtractsPath + include RendersBlob + + before_action :authorize_read_build! + before_action :extract_ref_name_and_path + before_action :validate_artifacts! + + def download + redirect_to download_project_job_artifacts_path(project, job) + end + + def browse + redirect_to browse_project_job_artifacts_path(project, job, path: params[:path]) + end + + def file + redirect_to file_project_job_artifacts_path(project, job, path: params[:path]) + end + + def raw + redirect_to raw_project_job_artifacts_path(project, job, path: params[:path]) + end + + def latest_succeeded + redirect_to latest_succeeded_project_artifacts_path(project, job, ref_name_and_path: params[:ref_name_and_path], job: params[:job]) + end + + private + + def validate_artifacts! + render_404 unless job && job.artifacts? + end + + def extract_ref_name_and_path + return unless params[:ref_name_and_path] + + @ref_name, @path = extract_ref(params[:ref_name_and_path]) + end + + def job + @job ||= job_from_id || job_from_ref + end + + def job_from_id + project.builds.find_by(id: params[:build_id]) if params[:build_id] + end + + def job_from_ref + return unless @ref_name + + jobs = project.latest_successful_builds_for(@ref_name) + jobs.find_by(name: params[:job]) + end +end diff --git a/app/controllers/projects/builds_controller.rb b/app/controllers/projects/builds_controller.rb index 04e8cdf6256..230b072dcea 100644 --- a/app/controllers/projects/builds_controller.rb +++ b/app/controllers/projects/builds_controller.rb @@ -1,112 +1,21 @@ 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] - layout 'project' + before_action :authorize_read_build! def index - @scope = params[:scope] - @all_builds = project.builds.relevant - @builds = @all_builds.order('created_at DESC') - @builds = - case @scope - when 'pending' - @builds.pending.reverse_order - when 'running' - @builds.running.reverse_order - when 'finished' - @builds.finished - 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) - redirect_to namespace_project_builds_path(project.namespace, project) + redirect_to project_jobs_path(project) end def show - @builds = @project.pipelines.find_by_sha(@build.sha).builds.order('id DESC') - @builds = @builds.where("id not in (?)", @build.id) - @pipeline = @build.pipeline - end - - def trace - 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? - - build = Ci::Build.retry(@build, current_user) - redirect_to build_path(build) - end - - def play - return render_404 unless @build.playable? - - build = @build.play(current_user) - redirect_to build_path(build) - end - - def cancel - @build.cancel - redirect_to build_path(@build) - end - - def status - render json: BuildSerializer - .new(project: @project, 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), - notice: "Build has been successfully erased!" + redirect_to project_job_path(project, job) end def raw - 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 + redirect_to raw_project_job_path(project, job) end private - def build - @build ||= project.builds.find_by!(id: params[:id]).present(current_user: current_user) - end - - def build_path(build) - namespace_project_build_path(build.project.namespace, build.project, build) + def job + @job ||= project.builds.find(params[:id]) end end diff --git a/app/controllers/projects/commit_controller.rb b/app/controllers/projects/commit_controller.rb index 2b5f0383ac1..6de125e7e80 100644 --- a/app/controllers/projects/commit_controller.rb +++ b/app/controllers/projects/commit_controller.rb @@ -38,9 +38,14 @@ class Projects::CommitController < Projects::ApplicationController format.json do Gitlab::PollingInterval.set_header(response, interval: 10_000) - render json: PipelineSerializer - .new(project: @project, user: @current_user) - .represent(@pipelines) + render json: { + pipelines: PipelineSerializer + .new(project: @project, current_user: @current_user) + .represent(@pipelines), + count: { + all: @pipelines.count + } + } end end end @@ -80,16 +85,16 @@ class Projects::CommitController < Projects::ApplicationController end def successful_change_path - referenced_merge_request_url || namespace_project_commits_url(@project.namespace, @project, @branch_name) + referenced_merge_request_url || project_commits_url(@project, @branch_name) end def failed_change_path - referenced_merge_request_url || namespace_project_commit_url(@project.namespace, @project, params[:id]) + referenced_merge_request_url || project_commit_url(@project, params[:id]) end def referenced_merge_request_url if merge_request = @commit.merged_merge_request(current_user) - namespace_project_merge_request_url(merge_request.target_project.namespace, merge_request.target_project, merge_request) + project_merge_request_url(merge_request.target_project, merge_request) end end diff --git a/app/controllers/projects/commits_controller.rb b/app/controllers/projects/commits_controller.rb index ad92f05a42d..2de9900d449 100644 --- a/app/controllers/projects/commits_controller.rb +++ b/app/controllers/projects/commits_controller.rb @@ -6,27 +6,18 @@ class Projects::CommitsController < Projects::ApplicationController before_action :require_non_empty_project before_action :assign_ref_vars before_action :authorize_download_code! + before_action :set_commits def show - @limit, @offset = (params[:limit] || 40).to_i, (params[:offset] || 0).to_i - search = params[:search] - - @commits = - if search.present? - @repository.find_commits_by_message(search, @ref, @path, @limit, @offset) - else - @repository.commits(@ref, path: @path, limit: @limit, offset: @offset) - end + @note_counts = project.notes.where(commit_id: @commits.map(&:id)) + .group(:commit_id).count - @note_counts = project.notes.where(commit_id: @commits.map(&:id)). - group(:commit_id).count - - @merge_request = MergeRequestsFinder.new(current_user, project_id: @project.id).execute.opened. - find_by(source_project: @project, source_branch: @ref, target_branch: @repository.root_ref) + @merge_request = MergeRequestsFinder.new(current_user, project_id: @project.id).execute.opened + .find_by(source_project: @project, source_branch: @ref, target_branch: @repository.root_ref) respond_to do |format| format.html - format.atom { render layout: false } + format.atom { render layout: 'xml.atom' } format.json do pager_json( @@ -37,4 +28,33 @@ class Projects::CommitsController < Projects::ApplicationController end end end + + def signatures + respond_to do |format| + format.json do + render json: { + signatures: @commits.select(&:has_signature?).map do |commit| + { + commit_sha: commit.sha, + html: view_to_html_string('projects/commit/_signature', signature: commit.signature) + } + end + } + end + end + end + + private + + def set_commits + @limit, @offset = (params[:limit] || 40).to_i, (params[:offset] || 0).to_i + search = params[:search] + + @commits = + if search.present? + @repository.find_commits_by_message(search, @ref, @path, @limit, @offset) + else + @repository.commits(@ref, path: @path, limit: @limit, offset: @offset) + end + end end diff --git a/app/controllers/projects/compare_controller.rb b/app/controllers/projects/compare_controller.rb index 008d2f5815f..c8613c0d634 100644 --- a/app/controllers/projects/compare_controller.rb +++ b/app/controllers/projects/compare_controller.rb @@ -31,9 +31,9 @@ class Projects::CompareController < Projects::ApplicationController from: params[:from].presence, to: params[:to].presence } - redirect_to namespace_project_compare_index_path(@project.namespace, @project, from_to_vars) + redirect_to project_compare_index_path(@project, from_to_vars) else - redirect_to namespace_project_compare_path(@project.namespace, @project, + redirect_to project_compare_path(@project, params[:from], params[:to]) end end @@ -51,13 +51,9 @@ class Projects::CompareController < Projects::ApplicationController if @compare @commits = @compare.commits - @start_commit = @compare.start_commit - @commit = @compare.commit - @base_commit = @compare.base_commit - @diffs = @compare.diffs(diff_options) - environment_params = @repository.branch_exists?(@head_ref) ? { ref: @head_ref } : { commit: @commit } + environment_params = @repository.branch_exists?(@head_ref) ? { ref: @head_ref } : { commit: @compare.commit } @environment = EnvironmentsFinder.new(@project, current_user, environment_params).execute.last @diff_notes_disabled = true @@ -65,7 +61,7 @@ class Projects::CompareController < Projects::ApplicationController end def merge_request - @merge_request ||= MergeRequestsFinder.new(current_user, project_id: @project.id).execute.opened. - find_by(source_project: @project, source_branch: @head_ref, target_branch: @start_ref) + @merge_request ||= MergeRequestsFinder.new(current_user, project_id: @project.id).execute.opened + .find_by(source_project: @project, source_branch: @head_ref, target_branch: @start_ref) end end diff --git a/app/controllers/projects/cycle_analytics/events_controller.rb b/app/controllers/projects/cycle_analytics/events_controller.rb index b69d46f2c41..26f3c114108 100644 --- a/app/controllers/projects/cycle_analytics/events_controller.rb +++ b/app/controllers/projects/cycle_analytics/events_controller.rb @@ -2,7 +2,7 @@ module Projects module CycleAnalytics class EventsController < Projects::ApplicationController include CycleAnalyticsParams - + before_action :authorize_read_cycle_analytics! before_action :authorize_read_build!, only: [:test, :staging] before_action :authorize_read_issue!, only: [:issue, :production] @@ -11,33 +11,33 @@ module Projects def issue render_events(cycle_analytics[:issue].events) end - + def plan render_events(cycle_analytics[:plan].events) end - + def code render_events(cycle_analytics[:code].events) end - + def test options(events_params)[:branch] = events_params[:branch_name] - + render_events(cycle_analytics[:test].events) end - + def review render_events(cycle_analytics[:review].events) end - + def staging render_events(cycle_analytics[:staging].events) end - + def production render_events(cycle_analytics[:production].events) end - + private def render_events(events) @@ -46,14 +46,14 @@ module Projects format.json { render json: { events: events } } end end - + def cycle_analytics @cycle_analytics ||= ::CycleAnalytics.new(project, options(events_params)) end - + def events_params return {} unless params[:events].present? - + params[:events].permit(:start_date, :branch_name) end end diff --git a/app/controllers/projects/deploy_keys_controller.rb b/app/controllers/projects/deploy_keys_controller.rb index d0c44e297e3..c2e621fa190 100644 --- a/app/controllers/projects/deploy_keys_controller.rb +++ b/app/controllers/projects/deploy_keys_controller.rb @@ -4,11 +4,17 @@ class Projects::DeployKeysController < Projects::ApplicationController # Authorize before_action :authorize_admin_project! + before_action :authorize_update_deploy_key!, only: [:edit, :update] - layout "project_settings" + 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 @@ -16,18 +22,33 @@ class Projects::DeployKeysController < Projects::ApplicationController end def create - @key = DeployKey.new(deploy_key_params.merge(user: current_user)) + @key = DeployKey.new(create_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 + def edit + end + + def update + if deploy_key.update_attributes(update_params) + flash[:notice] = 'Deploy key was successfully updated.' + redirect_to_repository_settings(@project) + else + render 'edit' + end + end + 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,12 +56,28 @@ 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 - def deploy_key_params + def deploy_key + @deploy_key ||= DeployKey.find(params[:id]) + end + + def create_params params.require(:deploy_key).permit(:key, :title, :can_push) end + + def update_params + params.require(:deploy_key).permit(:title, :can_push) + end + + def authorize_update_deploy_key! + access_denied! unless can?(current_user, :update_deploy_key, deploy_key) + end end diff --git a/app/controllers/projects/deployments_controller.rb b/app/controllers/projects/deployments_controller.rb new file mode 100644 index 00000000000..47c312ffddf --- /dev/null +++ b/app/controllers/projects/deployments_controller.rb @@ -0,0 +1,50 @@ +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 + + def additional_metrics + return render_404 unless deployment.has_additional_metrics? + + respond_to do |format| + format.json do + metrics = deployment.additional_metrics + + if metrics.any? + render json: metrics + else + head :no_content + end + end + end + 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 f4a18a5e8f7..2e6ab7903b8 100644 --- a/app/controllers/projects/discussions_controller.rb +++ b/app/controllers/projects/discussions_controller.rb @@ -1,5 +1,5 @@ class Projects::DiscussionsController < Projects::ApplicationController - before_action :module_enabled + before_action :check_merge_requests_available! before_action :merge_request before_action :discussion before_action :authorize_resolve_discussion! @@ -34,8 +34,4 @@ class Projects::DiscussionsController < Projects::ApplicationController def authorize_resolve_discussion! access_denied! unless discussion.can_resolve?(current_user) end - - def module_enabled - render_404 unless @project.feature_available?(:merge_requests, current_user) - end end diff --git a/app/controllers/projects/environments_controller.rb b/app/controllers/projects/environments_controller.rb index fa37963dfd4..29e223a5273 100644 --- a/app/controllers/projects/environments_controller.rb +++ b/app/controllers/projects/environments_controller.rb @@ -15,9 +15,11 @@ class Projects::EnvironmentsController < Projects::ApplicationController respond_to do |format| format.html format.json do + Gitlab::PollingInterval.set_header(response, interval: 3_000) + 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), @@ -31,13 +33,14 @@ class Projects::EnvironmentsController < Projects::ApplicationController def folder folder_environments = project.environments.where(environment_type: params[:id]) @environments = folder_environments.with_state(params[:scope] || :available) + .order(:name) respond_to do |format| format.html 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, @@ -62,7 +65,7 @@ class Projects::EnvironmentsController < Projects::ApplicationController @environment = project.environments.create(environment_params) if @environment.persisted? - redirect_to namespace_project_environment_path(project.namespace, project, @environment) + redirect_to project_environment_path(project, @environment) else render :new end @@ -70,7 +73,7 @@ class Projects::EnvironmentsController < Projects::ApplicationController def update if @environment.update(environment_params) - redirect_to namespace_project_environment_path(project.namespace, project, @environment) + redirect_to project_environment_path(project, @environment) else render :edit end @@ -81,10 +84,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 + project_environment_url(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 @@ -122,6 +131,16 @@ class Projects::EnvironmentsController < Projects::ApplicationController end end + def additional_metrics + respond_to do |format| + format.json do + additional_metrics = environment.additional_metrics || {} + + render json: additional_metrics, status: additional_metrics.any? ? :ok : :no_content + end + end + end + private def verify_api_request! diff --git a/app/controllers/projects/forks_controller.rb b/app/controllers/projects/forks_controller.rb index 1eb3800e49d..3f83bef2c79 100644 --- a/app/controllers/projects/forks_controller.rb +++ b/app/controllers/projects/forks_controller.rb @@ -44,12 +44,12 @@ class Projects::ForksController < Projects::ApplicationController if @forked_project.saved? && @forked_project.forked? if @forked_project.import_in_progress? - redirect_to namespace_project_import_path(@forked_project.namespace, @forked_project, continue: continue_params) + redirect_to project_import_path(@forked_project, continue: continue_params) else if continue_params redirect_to continue_params[:to], notice: continue_params[:notice] else - redirect_to namespace_project_path(@forked_project.namespace, @forked_project), notice: "The project '#{@forked_project.name}' was successfully forked." + redirect_to project_path(@forked_project), notice: "The project '#{@forked_project.name}' was successfully forked." end end else diff --git a/app/controllers/projects/git_http_client_controller.rb b/app/controllers/projects/git_http_client_controller.rb index 9a1bf037a95..7d0e2b3e2ef 100644 --- a/app/controllers/projects/git_http_client_controller.rb +++ b/app/controllers/projects/git_http_client_controller.rb @@ -4,7 +4,7 @@ class Projects::GitHttpClientController < Projects::ApplicationController include ActionController::HttpAuthentication::Basic include KerberosSpnegoHelper - attr_reader :authentication_result + attr_reader :authentication_result, :redirected_path delegate :actor, :authentication_abilities, to: :authentication_result, allow_nil: true @@ -14,7 +14,6 @@ class Projects::GitHttpClientController < Projects::ApplicationController skip_before_action :verify_authenticity_token skip_before_action :repository before_action :authenticate_user - before_action :ensure_project_found! private @@ -68,43 +67,19 @@ class Projects::GitHttpClientController < Projects::ApplicationController headers['Www-Authenticate'] = challenges.join("\n") if challenges.any? end - def ensure_project_found! - render_not_found if project.blank? - end - def project - return @project if defined?(@project) - - project_id, _ = project_id_with_suffix - @project = - if project_id.blank? - nil - else - Project.find_by_full_path("#{params[:namespace_id]}/#{project_id}") - end - end + parse_repo_path unless defined?(@project) - # This method returns two values so that we can parse - # params[:project_id] (untrusted input!) in exactly one place. - def project_id_with_suffix - id = params[:project_id] || '' - - %w[.wiki.git .git].each do |suffix| - if id.end_with?(suffix) - # Be careful to only remove the suffix from the end of 'id'. - # Accidentally removing it from the middle is how security - # vulnerabilities happen! - return [id.slice(0, id.length - suffix.length), suffix] - end - end + @project + end - # Something is wrong with params[:project_id]; do not pass it on. - [nil, nil] + def parse_repo_path + @project, @wiki, @redirected_path = Gitlab::RepoPath.parse("#{params[:namespace_id]}/#{params[:project_id]}") end def render_missing_personal_token render plain: "HTTP Basic: Access denied\n" \ - "You have 2FA enabled, please use a personal access token for Git over HTTP.\n" \ + "You must use a personal access token with 'api' scope for Git over HTTP.\n" \ "You can generate one at #{profile_personal_access_tokens_url}", status: 401 end @@ -114,46 +89,19 @@ class Projects::GitHttpClientController < Projects::ApplicationController end def wiki? - return @wiki if defined?(@wiki) + parse_repo_path unless defined?(@wiki) - _, suffix = project_id_with_suffix - @wiki = suffix == '.wiki.git' - end - - def render_not_found - render plain: 'Not Found', status: :not_found + @wiki end def handle_basic_authentication(login, password) @authentication_result = Gitlab::Auth.find_for_git_client( login, password, project: project, ip: request.ip) - return false unless @authentication_result.success? - - if download_request? - authentication_has_download_access? - else - authentication_has_upload_access? - end + @authentication_result.success? end def ci? authentication_result.ci?(project) end - - def authentication_has_download_access? - has_authentication_ability?(:download_code) || has_authentication_ability?(:build_download_code) - end - - def authentication_has_upload_access? - has_authentication_ability?(:push_code) - end - - def has_authentication_ability?(capability) - (authentication_abilities || []).include?(capability) - end - - def authentication_project - authentication_result.project - end end diff --git a/app/controllers/projects/git_http_controller.rb b/app/controllers/projects/git_http_controller.rb index 10adddb4636..71ae60cb8cd 100644 --- a/app/controllers/projects/git_http_controller.rb +++ b/app/controllers/projects/git_http_controller.rb @@ -1,38 +1,27 @@ class Projects::GitHttpController < Projects::GitHttpClientController include WorkhorseRequest + before_action :access_check + + rescue_from Gitlab::GitAccess::UnauthorizedError, with: :render_403 + rescue_from Gitlab::GitAccess::NotFoundError, with: :render_404 + # GET /foo/bar.git/info/refs?service=git-upload-pack (git pull) # 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 - elsif http_blocked? - render_http_not_allowed - else - render_denied - end + log_user_activity if upload_pack? + + render_ok end # POST /foo/bar.git/git-upload-pack (git pull) def git_upload_pack - if upload_pack? && upload_pack_allowed? - render_ok - else - render_denied - end + render_ok end # POST /foo/bar.git/git-receive-pack" (git push) def git_receive_pack - if receive_pack? && receive_pack_allowed? - render_ok - else - render_denied - end + render_ok end private @@ -45,10 +34,6 @@ class Projects::GitHttpController < Projects::GitHttpClientController git_command == 'git-upload-pack' end - def receive_pack? - git_command == 'git-receive-pack' - end - def git_command if action_name == 'info_refs' params[:service] @@ -59,50 +44,30 @@ class Projects::GitHttpController < Projects::GitHttpClientController def render_ok set_workhorse_internal_api_content_type - render json: Gitlab::Workhorse.git_http_ok(repository, user, action_name) + render json: Gitlab::Workhorse.git_http_ok(repository, wiki?, user, action_name) end - def render_http_not_allowed - render plain: access_check.message, status: :forbidden + def render_403(exception) + render plain: exception.message, status: :forbidden end - def render_denied - if user && can?(user, :read_project, project) - render plain: access_denied_message, status: :forbidden - else - # Do not leak information about project existence - render_not_found - end - end - - def access_denied_message - 'Access denied' + def render_404(exception) + render plain: exception.message, status: :not_found end - def upload_pack_allowed? - return false unless Gitlab.config.gitlab_shell.upload_pack - - access_check.allowed? || ci? + def access + @access ||= access_klass.new(access_actor, project, 'http', authentication_abilities: authentication_abilities, redirected_path: redirected_path) end - def access - @access ||= access_klass.new(user, project, 'http', authentication_abilities: authentication_abilities) + def access_actor + return user if user + return :ci if ci? end def access_check # Use the magic string '_any' to indicate we do not know what the # changes are. This is also what gitlab-shell does. - @access_check ||= access.check(git_command, '_any') - end - - def http_blocked? - !access.protocol_allowed? - end - - def receive_pack_allowed? - return false unless Gitlab.config.gitlab_shell.receive_pack - - access_check.allowed? + access.check(git_command, '_any') end def access_klass diff --git a/app/controllers/projects/graphs_controller.rb b/app/controllers/projects/graphs_controller.rb index 43fc0c39801..475d4c86294 100644 --- a/app/controllers/projects/graphs_controller.rb +++ b/app/controllers/projects/graphs_controller.rb @@ -5,7 +5,6 @@ class Projects::GraphsController < Projects::ApplicationController before_action :require_non_empty_project before_action :assign_ref_vars before_action :authorize_download_code! - before_action :builds_enabled, only: :ci def show respond_to do |format| @@ -30,7 +29,7 @@ class Projects::GraphsController < Projects::ApplicationController end def ci - redirect_to charts_namespace_project_pipelines_path(@project.namespace, @project) + redirect_to charts_project_pipelines_path(@project) end private @@ -44,23 +43,7 @@ class Projects::GraphsController < Projects::ApplicationController end def get_languages - @languages = Linguist::Repository.new(@repository.rugged, @repository.rugged.head.target_id).languages - total = @languages.map(&:last).sum - - @languages = @languages.map do |language| - name, share = language - color = Linguist::Language[name].color || "##{Digest::SHA256.hexdigest(name)[0...6]}" - { - value: (share.to_f * 100 / total).round(2), - label: name, - color: color, - highlight: color - } - end - - @languages.sort! do |x, y| - y[:value] <=> x[:value] - end + @languages = @project.repository.languages end def fetch_graph diff --git a/app/controllers/projects/group_links_controller.rb b/app/controllers/projects/group_links_controller.rb index 66b7bdbd988..f59200d3b1f 100644 --- a/app/controllers/projects/group_links_controller.rb +++ b/app/controllers/projects/group_links_controller.rb @@ -22,7 +22,7 @@ class Projects::GroupLinksController < Projects::ApplicationController flash[:alert] = 'Please select a group.' end - redirect_to namespace_project_settings_members_path(project.namespace, project) + redirect_to project_project_members_path(project) end def update @@ -36,7 +36,7 @@ class Projects::GroupLinksController < Projects::ApplicationController respond_to do |format| format.html do - redirect_to namespace_project_settings_members_path(project.namespace, project) + redirect_to project_project_members_path(project), status: 302 end format.js { head :ok } end diff --git a/app/controllers/projects/hook_logs_controller.rb b/app/controllers/projects/hook_logs_controller.rb new file mode 100644 index 00000000000..745e89fc843 --- /dev/null +++ b/app/controllers/projects/hook_logs_controller.rb @@ -0,0 +1,33 @@ +class Projects::HookLogsController < Projects::ApplicationController + include HooksExecution + + before_action :authorize_admin_project! + + before_action :hook, only: [:show, :retry] + before_action :hook_log, only: [:show, :retry] + + respond_to :html + + layout 'project_settings' + + def show + end + + def retry + result = hook.execute(hook_log.request_data, hook_log.trigger) + + set_hook_execution_notice(result) + + redirect_to edit_project_hook_path(@project, @hook) + end + + private + + def hook + @hook ||= @project.hooks.find(params[:hook_id]) + end + + def hook_log + @hook_log ||= hook.web_hook_logs.find(params[:id]) + end +end diff --git a/app/controllers/projects/hooks_controller.rb b/app/controllers/projects/hooks_controller.rb index 1e41f980f31..85d35900c71 100644 --- a/app/controllers/projects/hooks_controller.rb +++ b/app/controllers/projects/hooks_controller.rb @@ -1,11 +1,18 @@ class Projects::HooksController < Projects::ApplicationController + include HooksExecution + # Authorize before_action :authorize_admin_project! + before_action :hook_logs, only: :edit respond_to :html layout "project_settings" + def index + redirect_to project_settings_integrations_path(@project) + end + def create @hook = @project.hooks.new(hook_params) @hook.save @@ -14,23 +21,25 @@ class Projects::HooksController < Projects::ApplicationController @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) + redirect_to project_settings_integrations_path(@project) end - def test - if !@project.empty_repo? - status, message = TestHookService.new.execute(hook, current_user) - - if status && status >= 200 && status < 400 - flash[:notice] = "Hook executed successfully: HTTP #{status}" - elsif status - flash[:alert] = "Hook executed successfully but returned HTTP #{status} #{message}" - else - flash[:alert] = "Hook execution failed: #{message}" - end + def edit + end + + def update + if hook.update_attributes(hook_params) + flash[:notice] = 'Hook was successfully updated.' + redirect_to project_settings_integrations_path(@project) else - flash[:alert] = 'Hook execution failed. Ensure the project has commits.' + render 'edit' end + end + + def test + result = TestHooks::ProjectService.new(hook, current_user, params[:trigger]).execute + + set_hook_execution_notice(result) redirect_back_or_default(default: { action: 'index' }) end @@ -38,7 +47,7 @@ class Projects::HooksController < Projects::ApplicationController def destroy hook.destroy - redirect_to namespace_project_settings_integrations_path(@project.namespace, @project) + redirect_to project_settings_integrations_path(@project), status: 302 end private @@ -47,6 +56,11 @@ class Projects::HooksController < Projects::ApplicationController @hook ||= @project.hooks.find(params[:id]) end + def hook_logs + @hook_logs ||= + Kaminari.paginate_array(hook.web_hook_logs.order(created_at: :desc)).page(params[:page]) + end + def hook_params params.require(:hook).permit( :job_events, diff --git a/app/controllers/projects/imports_controller.rb b/app/controllers/projects/imports_controller.rb index a1b84afcd91..49aa32119ef 100644 --- a/app/controllers/projects/imports_controller.rb +++ b/app/controllers/projects/imports_controller.rb @@ -14,17 +14,10 @@ class Projects::ImportsController < Projects::ApplicationController @project.import_url = params[:project][:import_url] if @project.save - @project.reload - - if @project.import_failed? - @project.import_retry - else - @project.import_start - @project.add_import_job - end + @project.reload.import_schedule end - redirect_to namespace_project_import_path(@project.namespace, @project) + redirect_to project_import_path(@project) end def show @@ -32,10 +25,10 @@ class Projects::ImportsController < Projects::ApplicationController if continue_params redirect_to continue_params[:to], notice: continue_params[:notice] else - redirect_to namespace_project_path(@project.namespace, @project), notice: finished_notice + redirect_to project_path(@project), notice: finished_notice end elsif @project.import_failed? - redirect_to new_namespace_project_import_path(@project.namespace, @project) + redirect_to new_project_import_path(@project) else if continue_params && continue_params[:notice_now] flash.now[:notice] = continue_params[:notice_now] @@ -57,19 +50,19 @@ class Projects::ImportsController < Projects::ApplicationController def require_no_repo if @project.repository_exists? - redirect_to namespace_project_path(@project.namespace, @project) + redirect_to project_path(@project) end end def redirect_if_progress if @project.import_in_progress? - redirect_to namespace_project_import_path(@project.namespace, @project) + redirect_to project_import_path(@project) end end def redirect_if_no_import if @project.repository_exists? && @project.no_import? - redirect_to namespace_project_path(@project.namespace, @project) + redirect_to project_path(@project) end end end diff --git a/app/controllers/projects/issues_controller.rb b/app/controllers/projects/issues_controller.rb index cbf67137261..8893a514207 100644 --- a/app/controllers/projects/issues_controller.rb +++ b/app/controllers/projects/issues_controller.rb @@ -8,13 +8,8 @@ class Projects::IssuesController < Projects::ApplicationController prepend_before_action :authenticate_user!, only: [:new] - 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, :rendered_title] - - # Allow read any issue - before_action :authorize_read_issue!, only: [:show, :rendered_title] + before_action :check_issues_available! + before_action :issue, except: [:index, :new, :create, :bulk_update] # Allow write(create) issue before_action :authorize_create_issue!, only: [:new, :create] @@ -22,6 +17,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 @@ -52,7 +50,7 @@ class Projects::IssuesController < Projects::ApplicationController respond_to do |format| format.html - format.atom { render layout: false } + format.atom { render layout: 'xml.atom' } format.json do render json: { html: view_to_html_string("projects/issues/_issues"), @@ -64,7 +62,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], @@ -145,10 +143,7 @@ class Projects::IssuesController < Projects::ApplicationController format.json do if @issue.valid? - render json: @issue.to_json(methods: [:task_status, :task_status_short], - include: { milestone: {}, - assignee: { only: [:name, :username], methods: [:avatar_url] }, - labels: { methods: :text_color } }) + render json: IssueSerializer.new.represent(@issue) else render json: { errors: @issue.errors.full_messages }, status: :unprocessable_entity end @@ -191,75 +186,94 @@ 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 rendered_title + def realtime_changes Gitlab::PollingInterval.set_header(response, interval: 3_000) - render json: { title: view_context.markdown_field(@issue, :title) } + + response = { + 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 + } + + if @issue.is_edited? + response[:updated_at] = @issue.updated_at + response[:updated_by_name] = @issue.last_edited_by.name + response[:updated_by_path] = user_path(@issue.last_edited_by) + end + + render json: response + 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 + return @issue if defined?(@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! + + return render_404 unless can?(current_user, :read_issue, @issue) + + @issue end alias_method :subscribable_resource, :issue alias_method :issuable, :issue alias_method :awardable, :issue alias_method :spammable, :issue - def authorize_read_issue! - return render_404 unless can?(current_user, :read_issue, @issue) + def spammable_path + project_issue_path(@project, @issue) end def authorize_update_issue! - return render_404 unless can?(current_user, :update_issue, @issue) + render_404 unless can?(current_user, :update_issue, @issue) end def authorize_admin_issues! - return render_404 unless can?(current_user, :admin_issue, @project) + render_404 unless can?(current_user, :admin_issue, @project) end - def module_enabled - return render_404 unless @project.feature_available?(:issues, current_user) && @project.default_issues_tracker? + def authorize_create_merge_request! + render_404 unless can?(current_user, :push_code, @project) && @issue.can_be_worked_on?(current_user) end - def redirect_to_external_issue_tracker - external = @project.external_issue_tracker - - return unless external - - if action_name == 'new' - redirect_to external.new_issue_path - else - redirect_to external.project_path - end + def check_issues_available! + return render_404 unless @project.feature_available?(:issues, current_user) 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 + def issue_params + params.require(:issue).permit(*issue_params_attributes) 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: [] - ) + def issue_params_attributes + %i[ + title + assignee_id + position + description + confidential + milestone_id + due_date + state_event + task_num + lock_version + ] + [{ label_ids: [], assignee_ids: [] }] end def authenticate_user! @@ -267,7 +281,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/jobs_controller.rb b/app/controllers/projects/jobs_controller.rb new file mode 100644 index 00000000000..96abdac91b6 --- /dev/null +++ b/app/controllers/projects/jobs_controller.rb @@ -0,0 +1,142 @@ +class Projects::JobsController < Projects::ApplicationController + before_action :build, except: [:index, :cancel_all] + + 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 + @scope = params[:scope] + @all_builds = project.builds.relevant + @builds = @all_builds.order('created_at DESC') + @builds = + case @scope + when 'pending' + @builds.pending.reverse_order + when 'running' + @builds.running.reverse_order + when 'finished' + @builds.finished + else + @builds + end + @builds = @builds.includes([ + { pipeline: :project }, + :project, + :tags + ]) + @builds = @builds.page(params[:page]).per(30) + end + + def cancel_all + 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 project_jobs_path(project) + end + + def show + @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 + Gitlab::PollingInterval.set_header(response, interval: 10_000) + + render json: BuildSerializer + .new(project: @project, current_user: @current_user) + .represent(@build, {}, BuildDetailsEntity) + end + end + end + + def trace + 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 respond_422 unless @build.retryable? + + build = Ci::Build.retry(@build, current_user) + redirect_to build_path(build) + end + + def play + 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, current_user: @current_user) + .represent_status(@build) + end + + def erase + if @build.erase(erased_by: current_user) + redirect_to project_job_path(project, @build), + notice: "Build has been successfully erased!" + else + respond_422 + end + end + + def raw + 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(params[:id]) + .present(current_user: current_user) + end + + def build_path(build) + project_job_path(build.project, build) + end +end diff --git a/app/controllers/projects/labels_controller.rb b/app/controllers/projects/labels_controller.rb index 2f55ba4e700..480a2dff262 100644 --- a/app/controllers/projects/labels_controller.rb +++ b/app/controllers/projects/labels_controller.rb @@ -1,14 +1,14 @@ class Projects::LabelsController < Projects::ApplicationController include ToggleSubscriptionAction - before_action :module_enabled + before_action :check_issuables_available! before_action :label, only: [:edit, :update, :destroy, :promote] before_action :find_labels, only: [:index, :set_priorities, :remove_priority, :toggle_subscription] before_action :authorize_read_label! before_action :authorize_admin_labels!, only: [:new, :create, :edit, :update, :generate, :destroy, :remove_priority, :set_priorities] - before_action :authorize_admin_group!, only: [:promote] + before_action :authorize_admin_group_labels!, only: [:promote] respond_to :js, :html @@ -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 @@ -33,7 +33,7 @@ class Projects::LabelsController < Projects::ApplicationController if @label.valid? respond_to do |format| - format.html { redirect_to namespace_project_labels_path(@project.namespace, @project) } + format.html { redirect_to project_labels_path(@project) } format.json { render json: @label } end else @@ -51,7 +51,7 @@ class Projects::LabelsController < Projects::ApplicationController @label = Labels::UpdateService.new(label_params).execute(@label) if @label.valid? - redirect_to namespace_project_labels_path(@project.namespace, @project) + redirect_to project_labels_path(@project) else render :edit end @@ -61,12 +61,11 @@ class Projects::LabelsController < Projects::ApplicationController Gitlab::IssuesLabels.generate(@project) if params[:redirect] == 'issues' - redirect_to namespace_project_issues_path(@project.namespace, @project) + redirect_to project_issues_path(@project) elsif params[:redirect] == 'merge_requests' - redirect_to namespace_project_merge_requests_path(@project.namespace, - @project) + redirect_to project_merge_requests_path(@project) else - redirect_to namespace_project_labels_path(@project.namespace, @project) + redirect_to project_labels_path(@project) end end @@ -74,7 +73,9 @@ class Projects::LabelsController < Projects::ApplicationController @label.destroy @labels = find_labels - redirect_to(namespace_project_labels_path(@project.namespace, @project), notice: 'Label was removed') + redirect_to project_labels_path(@project), + status: 302, + notice: 'Label was removed' end def remove_priority @@ -112,7 +113,7 @@ class Projects::LabelsController < Projects::ApplicationController return render_404 unless promote_service.execute(@label) respond_to do |format| format.html do - redirect_to(namespace_project_labels_path(@project.namespace, @project), + redirect_to(project_labels_path(@project), notice: 'Label was promoted to a Group Label') end format.js @@ -123,7 +124,7 @@ class Projects::LabelsController < Projects::ApplicationController respond_to do |format| format.html do - redirect_to(namespace_project_labels_path(@project.namespace, @project), + redirect_to(project_labels_path(@project), notice: 'Failed to promote label due to internal error. Please contact administrators.') end format.js @@ -133,12 +134,6 @@ class Projects::LabelsController < Projects::ApplicationController protected - def module_enabled - unless @project.feature_available?(:issues, current_user) || @project.feature_available?(:merge_requests, current_user) - return render_404 - end - end - def label_params params.require(:label).permit(:title, :description, :color) end @@ -159,7 +154,7 @@ class Projects::LabelsController < Projects::ApplicationController return render_404 unless can?(current_user, :admin_label, @project) end - def authorize_admin_group! - return render_404 unless can?(current_user, :admin_group, @project.group) + def authorize_admin_group_labels! + return render_404 unless can?(current_user, :admin_label, @project.group) 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/mattermosts_controller.rb b/app/controllers/projects/mattermosts_controller.rb index 38f7e6eb5e9..0f6add3e287 100644 --- a/app/controllers/projects/mattermosts_controller.rb +++ b/app/controllers/projects/mattermosts_controller.rb @@ -16,12 +16,10 @@ class Projects::MattermostsController < Projects::ApplicationController if result flash[:notice] = 'This service is now configured' - redirect_to edit_namespace_project_service_path( - @project.namespace, @project, service) + redirect_to edit_project_service_path(@project, service) else flash[:alert] = message || 'Failed to configure service' - redirect_to new_namespace_project_mattermost_path( - @project.namespace, @project) + redirect_to new_project_mattermost_path(@project) end end diff --git a/app/controllers/projects/merge_requests/application_controller.rb b/app/controllers/projects/merge_requests/application_controller.rb new file mode 100644 index 00000000000..6602b204fcb --- /dev/null +++ b/app/controllers/projects/merge_requests/application_controller.rb @@ -0,0 +1,47 @@ +class Projects::MergeRequests::ApplicationController < Projects::ApplicationController + before_action :check_merge_requests_available! + before_action :merge_request + before_action :authorize_read_merge_request! + before_action :ensure_ref_fetched + + private + + def merge_request + @issuable = @merge_request ||= @project.merge_requests.find_by!(iid: params[:id]) + end + + # Make sure merge requests created before 8.0 + # have head file in refs/merge-requests/ + def ensure_ref_fetched + @merge_request.ensure_ref_fetched + end + + def merge_request_params + params.require(:merge_request).permit(merge_request_params_attributes) + end + + def merge_request_params_attributes + [ + :assignee_id, + :description, + :force_remove_source_branch, + :lock_version, + :milestone_id, + :source_branch, + :source_project_id, + :state_event, + :target_branch, + :target_project_id, + :task_num, + :title, + + label_ids: [] + ] + end + + def set_pipeline_variables + @pipelines = @merge_request.all_pipelines + @pipeline = @merge_request.head_pipeline + @statuses_count = @pipeline.present? ? @pipeline.statuses.relevant.count : 0 + end +end diff --git a/app/controllers/projects/merge_requests/conflicts_controller.rb b/app/controllers/projects/merge_requests/conflicts_controller.rb new file mode 100644 index 00000000000..28afef101a9 --- /dev/null +++ b/app/controllers/projects/merge_requests/conflicts_controller.rb @@ -0,0 +1,66 @@ +class Projects::MergeRequests::ConflictsController < Projects::MergeRequests::ApplicationController + include IssuableActions + + before_action :authorize_can_resolve_conflicts! + + def show + respond_to do |format| + format.html do + labels + end + + format.json do + 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.', + type: 'error' + } + else + render json: { + message: 'The merge conflicts for this merge request cannot be resolved through GitLab. Please try to resolve them locally.', + type: 'error' + } + end + end + end + end + + def conflict_for_path + return render_404 unless @conflicts_list.can_be_resolved_in_ui? + + file = @conflicts_list.file_for_path(params[:old_path], params[:new_path]) + + return render_404 unless file + + render json: file, full_content: true + end + + def resolve_conflicts + 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.' } + return + end + + begin + ::MergeRequests::Conflicts::ResolveService + .new(merge_request) + .execute(current_user, params) + + flash[:notice] = 'All merge conflicts were resolved. The merge request can now be merged.' + + render json: { redirect_to: project_merge_request_url(@project, @merge_request, resolved_conflicts: true) } + rescue Gitlab::Conflict::ResolutionError => e + render status: :bad_request, json: { message: e.message } + end + end + + def authorize_can_resolve_conflicts! + @conflicts_list = ::MergeRequests::Conflicts::ListService.new(@merge_request) + + return render_404 unless @conflicts_list.can_be_resolved_by?(current_user) + end +end diff --git a/app/controllers/projects/merge_requests/creations_controller.rb b/app/controllers/projects/merge_requests/creations_controller.rb new file mode 100644 index 00000000000..f35d53896ba --- /dev/null +++ b/app/controllers/projects/merge_requests/creations_controller.rb @@ -0,0 +1,128 @@ +class Projects::MergeRequests::CreationsController < Projects::MergeRequests::ApplicationController + include DiffForPath + include DiffHelper + + skip_before_action :merge_request + skip_before_action :ensure_ref_fetched + before_action :authorize_create_merge_request! + before_action :apply_diff_view_cookie!, only: [:diffs, :diff_for_path] + before_action :build_merge_request, except: [:create] + + def new + define_new_vars + end + + def create + @target_branches ||= [] + @merge_request = ::MergeRequests::CreateService.new(project, current_user, merge_request_params).execute + + if @merge_request.valid? + redirect_to(merge_request_path(@merge_request)) + else + @source_project = @merge_request.source_project + @target_project = @merge_request.target_project + + define_new_vars + render action: "new" + end + end + + def pipelines + @pipelines = @merge_request.all_pipelines + + Gitlab::PollingInterval.set_header(response, interval: 10_000) + + render json: { + pipelines: PipelineSerializer + .new(project: @project, current_user: @current_user) + .represent(@pipelines) + } + end + + def diffs + @diffs = if @merge_request.can_be_created + @merge_request.diffs(diff_options) + else + [] + end + @diff_notes_disabled = true + + @environment = @merge_request.environments_for(current_user).last + + render json: { html: view_to_html_string('projects/merge_requests/creations/_diffs', diffs: @diffs, environment: @environment) } + end + + def diff_for_path + @diffs = @merge_request.diffs(diff_options) + @diff_notes_disabled = true + + render_diff_for_path(@diffs) + end + + def branch_from + # This is always source + @source_project = @merge_request.nil? ? @project : @merge_request.source_project + + if params[:ref].present? + @ref = params[:ref] + @commit = @repository.commit("refs/heads/#{@ref}") + end + + render layout: false + end + + def branch_to + @target_project = selected_target_project + + if params[:ref].present? + @ref = params[:ref] + @commit = @target_project.commit("refs/heads/#{@ref}") + end + + render layout: false + end + + def update_branches + @target_project = selected_target_project + @target_branches = @target_project.repository.branch_names + + render layout: false + end + + private + + def build_merge_request + params[:merge_request] ||= ActionController::Parameters.new(source_project: @project) + @merge_request = ::MergeRequests::BuildService.new(project, current_user, merge_request_params.merge(diff_options: diff_options)).execute + end + + def define_new_vars + @noteable = @merge_request + + @target_branches = if @merge_request.target_project + @merge_request.target_project.repository.branch_names + else + [] + end + + @target_project = @merge_request.target_project + @source_project = @merge_request.source_project + @commits = @merge_request.commits + @commit = @merge_request.diff_head_commit + + @note_counts = Note.where(commit_id: @commits.map(&:id)) + .group(:commit_id).count + + @labels = LabelsFinder.new(current_user, project_id: @project.id).execute + + set_pipeline_variables + end + + def selected_target_project + if @project.id.to_s == params[:target_project_id] || @project.forked_project_link.nil? + @project + else + @project.forked_project_link.forked_from_project + end + end +end diff --git a/app/controllers/projects/merge_requests/diffs_controller.rb b/app/controllers/projects/merge_requests/diffs_controller.rb new file mode 100644 index 00000000000..330b7df4541 --- /dev/null +++ b/app/controllers/projects/merge_requests/diffs_controller.rb @@ -0,0 +1,66 @@ +class Projects::MergeRequests::DiffsController < Projects::MergeRequests::ApplicationController + include DiffForPath + include DiffHelper + include RendersNotes + + before_action :apply_diff_view_cookie! + before_action :define_diff_vars + before_action :define_diff_comment_vars + + def show + @environment = @merge_request.environments_for(current_user).last + + render json: { html: view_to_html_string("projects/merge_requests/diffs/_diffs") } + end + + def diff_for_path + render_diff_for_path(@diffs) + end + + private + + 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 + @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.grouped_diff_discussions(@compare.diff_refs) + @notes = prepare_notes_for_rendering(@grouped_diff_discussions.values.flatten.flat_map(&:notes)) + end +end diff --git a/app/controllers/projects/merge_requests_controller.rb b/app/controllers/projects/merge_requests_controller.rb index 09dc8b38229..2a3b73577a5 100755..100644 --- a/app/controllers/projects/merge_requests_controller.rb +++ b/app/controllers/projects/merge_requests_controller.rb @@ -1,39 +1,17 @@ -class Projects::MergeRequestsController < Projects::ApplicationController +class Projects::MergeRequestsController < Projects::MergeRequests::ApplicationController include ToggleSubscriptionAction - include DiffForPath - include DiffHelper include IssuableActions 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 - ] - 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_commit_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 :apply_diff_view_cookie!, only: [:new_diffs] - before_action :build_merge_request, only: [:new, :new_diffs] - - # Allow read any merge_request - before_action :authorize_read_merge_request! - - # Allow write(create) merge_request - before_action :authorize_create_merge_request!, only: [:new, :create] - - # Allow modify merge_request + skip_before_action :merge_request, only: [:index, :bulk_update] + skip_before_action :ensure_ref_fetched, only: [:index, :bulk_update] + before_action :authorize_update_merge_request!, only: [:close, :edit, :update, :remove_wip, :sort] before_action :authenticate_user!, only: [:assign_related_issues] - before_action :authorize_can_resolve_conflicts!, only: [:conflicts, :conflict_for_path, :resolve_conflicts] - def index @collection_type = "MergeRequest" @merge_requests = merge_requests_collection @@ -73,11 +51,31 @@ class Projects::MergeRequestsController < Projects::ApplicationController end def show + validates_merge_request + ensure_ref_fetched + close_merge_request_without_source_project + check_if_can_be_merged + respond_to do |format| - format.html { define_discussion_vars } + format.html do + # Build a note object for comment form + @note = @project.notes.new(noteable: @merge_request) + + @discussions = @merge_request.discussions + @notes = prepare_notes_for_rendering(@discussions.flat_map(&:notes)) + + @noteable = @merge_request + @commits_count = @merge_request.commits_count + + labels + + set_pipeline_variables + 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 @@ -94,197 +92,45 @@ class Projects::MergeRequestsController < Projects::ApplicationController end end - def diffs - apply_diff_view_cookie! - - respond_to do |format| - format.html { define_discussion_vars } - format.json do - define_diff_vars - define_diff_comment_vars - - @environment = @merge_request.environments_for(current_user).last - - render json: { html: view_to_html_string("projects/merge_requests/show/_diffs") } - end - end - end - - # With an ID param, loads the MR at that ID. Otherwise, accepts the same params as #new - # and uses that (unsaved) MR. - # - def diff_for_path - if params[:id] - merge_request - define_diff_vars - define_diff_comment_vars - else - build_merge_request - @diffs = @merge_request.diffs(diff_options) - @diff_notes_disabled = true - end - - define_commit_vars - - render_diff_for_path(@diffs) - end - def commits - respond_to do |format| - format.html do - define_discussion_vars - - render 'show' - end - format.json do - # Get commits from repository - # or from cache if already merged - @commits = @merge_request.commits - @note_counts = Note.where(commit_id: @commits.map(&:id)). - group(:commit_id).count + # Get commits from repository + # or from cache if already merged + @commits = @merge_request.commits + @note_counts = Note.where(commit_id: @commits.map(&:id)) + .group(:commit_id).count - render json: { html: view_to_html_string('projects/merge_requests/show/_commits') } - end - end - end - - def conflicts - respond_to do |format| - format.html { define_discussion_vars } - - format.json do - if @merge_request.conflicts_can_be_resolved_in_ui? - render json: @merge_request.conflicts - 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.', - type: 'error' - } - else - render json: { - message: 'The merge conflicts for this merge request cannot be resolved through GitLab. Please try to resolve them locally.', - type: 'error' - } - end - end - end - end - - def conflict_for_path - return render_404 unless @merge_request.conflicts_can_be_resolved_in_ui? - - file = @merge_request.conflicts.file_for_path(params[:old_path], params[:new_path]) - - return render_404 unless file - - render json: file, full_content: true - end - - def resolve_conflicts - return render_404 unless @merge_request.conflicts_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.' } - return - end - - begin - MergeRequests::ResolveService.new(@merge_request.source_project, current_user, params).execute(@merge_request) - - flash[:notice] = 'All merge conflicts were resolved. The merge request can now be merged.' - - render json: { redirect_to: namespace_project_merge_request_url(@project.namespace, @project, @merge_request, resolved_conflicts: true) } - rescue Gitlab::Conflict::ResolutionError => e - render status: :bad_request, json: { message: e.message } - end + render json: { html: view_to_html_string('projects/merge_requests/_commits') } end def pipelines @pipelines = @merge_request.all_pipelines - respond_to do |format| - format.html do - define_discussion_vars - - render 'show' - end - - format.json do - Gitlab::PollingInterval.set_header(response, interval: 10_000) - - render json: PipelineSerializer - .new(project: @project, user: @current_user) - .represent(@pipelines) - end - end - end - - def new - respond_to do |format| - format.html { define_new_vars } - format.json do - define_pipelines_vars - - Gitlab::PollingInterval.set_header(response, interval: 10_000) + Gitlab::PollingInterval.set_header(response, interval: 10_000) - render json: { - pipelines: PipelineSerializer - .new(project: @project, user: @current_user) - .represent(@pipelines) - } - end - end - end - - def new_diffs - respond_to do |format| - format.html do - define_new_vars - @show_changes_tab = true - render "new" - end - format.json do - @diffs = if @merge_request.can_be_created - @merge_request.diffs(diff_options) - else - [] - end - @diff_notes_disabled = true - - @environment = @merge_request.environments_for(current_user).last - - render json: { html: view_to_html_string('projects/merge_requests/_new_diffs', diffs: @diffs, environment: @environment) } - end - end - end - - def create - @target_branches ||= [] - @merge_request = MergeRequests::CreateService.new(project, current_user, merge_request_params).execute - - if @merge_request.valid? - redirect_to(merge_request_path(@merge_request)) - else - @source_project = @merge_request.source_project - @target_project = @merge_request.target_project - render action: "new" - end + render json: { + pipelines: PipelineSerializer + .new(project: @project, current_user: @current_user) + .represent(@pipelines), + count: { + all: @pipelines.count + } + } end def edit - @source_project = @merge_request.source_project - @target_project = @merge_request.target_project - @target_branches = @merge_request.target_project.repository.branch_names + define_edit_vars end def update - @merge_request = MergeRequests::UpdateService.new(project, current_user, merge_request_params).execute(@merge_request) + @merge_request = ::MergeRequests::UpdateService.new(project, current_user, merge_request_params).execute(@merge_request) respond_to do |format| format.html do if @merge_request.valid? redirect_to([@merge_request.target_project.namespace.becomes(Namespace), @merge_request.target_project, @merge_request]) else + define_edit_vars + render :edit end end @@ -294,21 +140,21 @@ class Projects::MergeRequestsController < Projects::ApplicationController end end rescue ActiveRecord::StaleObjectError + define_edit_vars if request.format.html? + render_conflict_response 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 @@ -316,148 +162,43 @@ class Projects::MergeRequestsController < Projects::ApplicationController return access_denied! end - MergeRequests::MergeWhenPipelineSucceedsService + ::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 + status = merge! - @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_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 - - if params[:ref].present? - @ref = params[:ref] - @commit = @repository.commit("refs/heads/#{@ref}") - end - - render layout: false - end - - def branch_to - @target_project = selected_target_project - - if params[:ref].present? - @ref = params[:ref] - @commit = @target_project.commit("refs/heads/#{@ref}") - end - - render layout: false - end - - def update_branches - @target_project = selected_target_project - @target_branches = @target_project.repository.branch_names - - render layout: false - end - def assign_related_issues - result = MergeRequests::AssignIssuesService.new(project, current_user, merge_request: @merge_request).execute - - respond_to do |format| - format.html do - case result[:count] - when 0 - flash[:error] = "Failed to assign you issues related to the merge request" - when 1 - flash[:notice] = "1 issue has been assigned to you" - else - flash[:notice] = "#{result[:count]} issues have been assigned to you" - end - - redirect_to(merge_request_path(@merge_request)) - end - end - end - - def ci_status - pipeline = @merge_request.head_pipeline - @pipelines = @merge_request.all_pipelines + result = ::MergeRequests::AssignIssuesService.new(project, current_user, merge_request: @merge_request).execute - if pipeline - status = pipeline.status - coverage = pipeline.coverage - - status = "success_with_warnings" if pipeline.success? && pipeline.has_warnings? - - status ||= "preparing" + case result[:count] + when 0 + flash[:error] = "Failed to assign you issues related to the merge request" + when 1 + flash[:notice] = "1 issue has been assigned to you" 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 + flash[:notice] = "#{result[:count]} issues have been assigned to you" 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 + redirect_to(merge_request_path(@merge_request)) 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 @@ -470,13 +211,25 @@ class Projects::MergeRequestsController < Projects::ApplicationController stop_url = if environment.stop_action? && can?(current_user, :create_deployment, environment) - stop_namespace_project_environment_path(project.namespace, project, environment) + stop_project_environment_path(project, environment) + end + + metrics_url = + if can?(current_user, :read_environment, environment) && environment.has_metrics? + metrics_project_environment_deployment_path(environment.project, environment, deployment) + end + + metrics_monitoring_url = + if can?(current_user, :read_environment, environment) + environment_metrics_path(environment) end { id: environment.id, name: environment.name, - url: namespace_project_environment_path(project.namespace, project, environment), + url: project_environment_path(project, environment), + metrics_url: metrics_url, + metrics_monitoring_url: metrics_monitoring_url, stop_url: stop_url, external_url: environment.external_url, external_url_formatted: environment.formatted_external_url, @@ -491,17 +244,6 @@ class Projects::MergeRequestsController < Projects::ApplicationController protected - def selected_target_project - if @project.id.to_s == params[:target_project_id] || @project.forked_project_link.nil? - @project - else - @project.forked_project_link.forked_from_project - end - end - - def merge_request - @issuable = @merge_request ||= @project.merge_requests.find_by!(iid: params[:id]) - end alias_method :subscribable_resource, :merge_request alias_method :issuable, :merge_request alias_method :awardable, :merge_request @@ -514,14 +256,6 @@ class Projects::MergeRequestsController < Projects::ApplicationController return render_404 unless can?(current_user, :admin_merge_request, @merge_request) end - def authorize_can_resolve_conflicts! - return render_404 unless @merge_request.conflicts_can_be_resolved_by?(current_user) - end - - def module_enabled - return render_404 unless @project.feature_available?(:merge_requests, current_user) - end - def validates_merge_request # Show git not found page # if there is no saved commits between source & target branch @@ -531,164 +265,79 @@ class Projects::MergeRequestsController < Projects::ApplicationController end end - def define_show_vars - @noteable = @merge_request - @commits_count = @merge_request.commits_count - - if @merge_request.locked_long_ago? - @merge_request.unlock_mr - @merge_request.close - end - - labels - define_pipelines_vars + def invalid_mr + # Render special view for MR with removed target branch + render 'invalid' end - # Discussion tab data is rendered on html responses of actions - # :show, :diff, :commits, :builds. but not when request the data through AJAX - def define_discussion_vars - # Build a note object for comment form - @note = @project.notes.new(noteable: @merge_request) - - @discussions = @merge_request.discussions - @notes = prepare_notes_for_rendering(@discussions.flat_map(&:notes)) + def merge_params + params.permit(merge_params_attributes) end - def define_widget_vars - @pipeline = @merge_request.head_pipeline + def merge_params_attributes + [:should_remove_source_branch, :commit_message] end - def define_commit_vars - @commit = @merge_request.diff_head_commit - @base_commit = @merge_request.diff_base_commit || @merge_request.likely_diff_base_commit + def merge_when_pipeline_succeeds_active? + params[:merge_when_pipeline_succeeds].present? && + @merge_request.head_pipeline && @merge_request.head_pipeline.active? 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 + def close_merge_request_without_source_project + if !@merge_request.source_project && @merge_request.open? + @merge_request.close end - - @diffs = - if @start_sha - @merge_request_diff.compare_with(@start_sha).diffs(diff_options) - else - @merge_request_diff.diffs(diff_options) - end end - def define_diff_comment_vars - @new_diff_note_attrs = { - noteable_type: 'MergeRequest', - noteable_id: @merge_request.id - } - - @diff_notes_disabled = !@merge_request_diff.latest? || @start_sha + private - @use_legacy_diff_notes = !@merge_request.has_complete_diff_refs? - - @grouped_diff_discussions = @merge_request.grouped_diff_discussions(@merge_request_diff.diff_refs) - @notes = prepare_notes_for_rendering(@grouped_diff_discussions.values.flatten.flat_map(&:notes)) - end - - def define_pipelines_vars - @pipelines = @merge_request.all_pipelines - @pipeline = @merge_request.head_pipeline - @statuses_count = @pipeline.present? ? @pipeline.statuses.relevant.count : 0 + def check_if_can_be_merged + @merge_request.check_if_can_be_merged end - def define_new_vars - @noteable = @merge_request - - @target_branches = if @merge_request.target_project - @merge_request.target_project.repository.branch_names - else - [] - end - - @target_project = merge_request.target_project - @source_project = merge_request.source_project - @commits = @merge_request.compare_commits.reverse - @commit = @merge_request.diff_head_commit - @base_commit = @merge_request.diff_base_commit - - @note_counts = Note.where(commit_id: @commits.map(&:id)). - group(:commit_id).count - - @labels = LabelsFinder.new(current_user, project_id: @project.id).execute - - @show_changes_tab = params[:show_changes].present? + 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 - define_pipelines_vars - end + return :sha_mismatch if params[:sha] != @merge_request.diff_head_sha - def invalid_mr - # Render special view for MR with removed target branch - render 'invalid' - end + @merge_request.update(merge_error: nil) - def merge_request_params - params.require(:merge_request) - .permit(merge_request_params_ce) - end + if params[:merge_when_pipeline_succeeds].present? + return :failed unless @merge_request.head_pipeline - def merge_request_params_ce - [ - :assignee_id, - :description, - :force_remove_source_branch, - :lock_version, - :milestone_id, - :source_branch, - :source_project_id, - :state_event, - :target_branch, - :target_project_id, - :task_num, - :title, - - label_ids: [] - ] - end + if @merge_request.head_pipeline.active? + ::MergeRequests::MergeWhenPipelineSucceedsService + .new(@project, current_user, merge_params) + .execute(@merge_request) - def merge_params - params.permit(:should_remove_source_branch, :commit_message) - end + :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) - # Make sure merge requests created before 8.0 - # have head file in refs/merge-requests/ - def ensure_ref_fetched - @merge_request.ensure_ref_fetched - end + :success + else + :failed + end + else + MergeWorker.perform_async(@merge_request.id, current_user.id, params) - def merge_when_pipeline_succeeds_active? - params[:merge_when_pipeline_succeeds].present? && - @merge_request.head_pipeline && @merge_request.head_pipeline.active? + :success + end end - def build_merge_request - params[:merge_request] ||= ActionController::Parameters.new(source_project: @project) - @merge_request = MergeRequests::BuildService.new(project, current_user, merge_request_params.merge(diff_options: diff_options)).execute + def serializer + MergeRequestSerializer.new(current_user: current_user, project: merge_request.project) end - def close_merge_request_without_source_project - if !@merge_request.source_project && @merge_request.open? - @merge_request.close - end + def define_edit_vars + @source_project = @merge_request.source_project + @target_project = @merge_request.target_project + @target_branches = @merge_request.target_project.repository.branch_names end end diff --git a/app/controllers/projects/milestones_controller.rb b/app/controllers/projects/milestones_controller.rb index d0dd524c484..c94384d2a1a 100644 --- a/app/controllers/projects/milestones_controller.rb +++ b/app/controllers/projects/milestones_controller.rb @@ -1,30 +1,28 @@ class Projects::MilestonesController < Projects::ApplicationController - before_action :module_enabled - before_action :milestone, only: [:edit, :update, :destroy, :show, :sort_issues, :sort_merge_requests] + include MilestoneActions + + before_action :check_issuables_available! + before_action :milestone, only: [:edit, :update, :destroy, :show, :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 def index - @milestones = - case params[:state] - when 'all' then @project.milestones - when 'closed' then @project.milestones.closed - else @project.milestones.active - end - @sort = params[:sort] || 'due_date_asc' - @milestones = @milestones.sort(@sort) + @milestones = milestones.sort(@sort) respond_to do |format| format.html do @project_namespace = @project.namespace.becomes(Namespace) - @milestones = @milestones.includes(:project) + # We need to show group milestones in the JSON response + # so that people can filter by and assign group milestones, + # but we don't need to show them on the project milestones page itself. + @milestones = @milestones.for_projects @milestones = @milestones.page(params[:page]) end format.json do @@ -43,14 +41,14 @@ class Projects::MilestonesController < Projects::ApplicationController end def show + @project_namespace = @project.namespace.becomes(Namespace) end def create @milestone = Milestones::CreateService.new(project, current_user, milestone_params).execute - if @milestone.save - redirect_to namespace_project_milestone_path(@project.namespace, - @project, @milestone) + if @milestone.valid? + redirect_to project_milestone_path(@project, @milestone) else render "new" end @@ -63,8 +61,7 @@ class Projects::MilestonesController < Projects::ApplicationController format.js format.html do if @milestone.valid? - redirect_to namespace_project_milestone_path(@project.namespace, - @project, @milestone) + redirect_to project_milestone_path(@project, @milestone) else render :edit end @@ -78,29 +75,25 @@ class Projects::MilestonesController < Projects::ApplicationController Milestones::DestroyService.new(project, current_user).execute(milestone) respond_to do |format| - format.html { redirect_to namespace_project_milestones_path } + format.html { redirect_to namespace_project_milestones_path, status: 302 } format.js { head :ok } end end - def sort_issues - @milestone.sort_issues(params['sortable_issue'].map(&:to_i)) + protected - render json: { saved: true } - end + def milestones + @milestones ||= begin + if @project.group && can?(current_user, :read_group, @project.group) + group = @project.group + end - def sort_merge_requests - @merge_requests = @milestone.merge_requests.where(id: params['sortable_merge_request']) - @merge_requests.each do |merge_request| - merge_request.position = params['sortable_merge_request'].index(merge_request.id.to_s) + 1 - merge_request.save - end + search_params = params.merge(project_ids: @project.id, group_ids: group&.id) - render json: { saved: true } + MilestonesFinder.new(search_params).execute + end end - protected - def milestone @milestone ||= @project.milestones.find_by!(iid: params[:id]) end @@ -109,12 +102,6 @@ class Projects::MilestonesController < Projects::ApplicationController return render_404 unless can?(current_user, :admin_milestone, @project) end - def module_enabled - unless @project.feature_available?(:issues, current_user) || @project.feature_available?(:merge_requests, current_user) - return render_404 - end - end - def milestone_params params.require(:milestone).permit(:title, :description, :start_date, :due_date, :state_event) end diff --git a/app/controllers/projects/network_controller.rb b/app/controllers/projects/network_controller.rb index 33a152ad34f..dfa5e4f7f46 100644 --- a/app/controllers/projects/network_controller.rb +++ b/app/controllers/projects/network_controller.rb @@ -8,8 +8,8 @@ class Projects::NetworkController < Projects::ApplicationController before_action :assign_commit def show - @url = namespace_project_network_path(@project.namespace, @project, @ref, @options.merge(format: :json)) - @commit_url = namespace_project_commit_path(@project.namespace, @project, 'ae45ca32').gsub("ae45ca32", "%s") + @url = project_network_path(@project, @ref, @options.merge(format: :json)) + @commit_url = project_commit_path(@project, 'ae45ca32').gsub("ae45ca32", "%s") respond_to do |format| format.html do diff --git a/app/controllers/projects/notes_controller.rb b/app/controllers/projects/notes_controller.rb index 405ea3c0a4f..41a13f6f577 100644 --- a/app/controllers/projects/notes_controller.rb +++ b/app/controllers/projects/notes_controller.rb @@ -1,68 +1,22 @@ class Projects::NotesController < Projects::ApplicationController - include RendersNotes + 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] - 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 - + # + # 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], - 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 + super end def delete_attachment @@ -108,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 discussion_html(discussion) - return if discussion.individual_note? - - render_to_string( - "discussions/_discussion", - layout: false, - formats: [:html], - locals: { discussion: discussion } - ) - 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 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 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( - :project_id, - :noteable_type, - :noteable_id, - :commit_id, - :noteable, - :type, - - :note, - :attachment, - - # LegacyDiffNote - :line_code, - - # DiffNote - :position - ) - end - - def notes_finder - @notes_finder ||= NotesFinder.new(project, current_user, params.merge(last_fetched_at: last_fetched_at)) - end - - def noteable - @noteable ||= notes_finder.target - 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..d421b1a8eb5 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] @@ -14,8 +15,9 @@ class Projects::PagesController < Projects::ApplicationController respond_to do |format| format.html do - redirect_to(namespace_project_pages_path(@project.namespace, @project), - notice: 'Pages were removed') + redirect_to project_pages_path(@project), + status: 302, + notice: 'Pages were removed' end end end diff --git a/app/controllers/projects/pages_domains_controller.rb b/app/controllers/projects/pages_domains_controller.rb index b8c253f6ae3..15e77d854dc 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] @@ -15,7 +16,7 @@ class Projects::PagesDomainsController < Projects::ApplicationController @domain = @project.pages_domains.create(pages_domain_params) if @domain.valid? - redirect_to namespace_project_pages_path(@project.namespace, @project) + redirect_to project_pages_path(@project) else render 'new' end @@ -26,8 +27,9 @@ class Projects::PagesDomainsController < Projects::ApplicationController respond_to do |format| format.html do - redirect_to(namespace_project_pages_path(@project.namespace, @project), - notice: 'Domain was removed') + redirect_to project_pages_path(@project), + status: 302, + notice: 'Domain was removed' end format.js end diff --git a/app/controllers/projects/pipeline_schedules_controller.rb b/app/controllers/projects/pipeline_schedules_controller.rb new file mode 100644 index 00000000000..ec7c645df5a --- /dev/null +++ b/app/controllers/projects/pipeline_schedules_controller.rb @@ -0,0 +1,80 @@ +class Projects::PipelineSchedulesController < Projects::ApplicationController + before_action :schedule, except: [:index, :new, :create] + + before_action :authorize_read_pipeline_schedule! + before_action :authorize_create_pipeline_schedule!, only: [:new, :create] + before_action :authorize_update_pipeline_schedule!, except: [:index, :new, :create] + before_action :authorize_admin_pipeline_schedule!, only: [:destroy] + + 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 project_pipeline_schedules_path(@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), status: 302 + else + redirect_to pipeline_schedules_path(@project), + status: :forbidden, + 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, + variables_attributes: [:id, :key, :value, :_destroy] ) + end + + def authorize_update_pipeline_schedule! + return access_denied! unless can?(current_user, :update_pipeline_schedule, schedule) + end + + def authorize_admin_pipeline_schedule! + return access_denied! unless can?(current_user, :admin_pipeline_schedule, schedule) + end +end diff --git a/app/controllers/projects/pipelines_controller.rb b/app/controllers/projects/pipelines_controller.rb index 1780cc0233c..a3bfbf0694e 100644 --- a/app/controllers/projects/pipelines_controller.rb +++ b/app/controllers/projects/pipelines_controller.rb @@ -1,27 +1,30 @@ 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,18 +32,18 @@ class Projects::PipelinesController < Projects::ApplicationController respond_to do |format| format.html format.json do - Gitlab::PollingInterval.set_header(response, interval: 10_000) + 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 @@ -54,34 +57,48 @@ class Projects::PipelinesController < Projects::ApplicationController def create @pipeline = Ci::CreatePipelineService .new(project, current_user, create_params) - .execute(ignore_skip_ci: true, save_on_errors: false) - unless @pipeline.persisted? + .execute(:web, ignore_skip_ci: true, save_on_errors: false) + + if @pipeline.persisted? + redirect_to project_pipeline_path(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 def stage - @stage = pipeline.stage(params[:stage]) + @stage = pipeline.legacy_stage(params[:stage]) return not_found unless @stage respond_to do |format| @@ -92,13 +109,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: project_pipelines_path(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: project_pipelines_path(project) + end + + format.json { head :no_content } + end end def charts @@ -106,11 +135,24 @@ class Projects::PipelinesController < Projects::ApplicationController @charts[:week] = Ci::Charts::WeekChart.new(project) @charts[:month] = Ci::Charts::MonthChart.new(project) @charts[:year] = Ci::Charts::YearChart.new(project) - @charts[:build_times] = Ci::Charts::BuildTime.new(project) + @charts[:pipeline_times] = Ci::Charts::PipelineTime.new(project) + + @counts = {} + @counts[:total] = @project.pipelines.count(:all) + @counts[:success] = @project.pipelines.success.count(:all) + @counts[:failed] = @project.pipelines.failed.count(:all) end 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 diff --git a/app/controllers/projects/pipelines_settings_controller.rb b/app/controllers/projects/pipelines_settings_controller.rb index ff50602831c..9d24ebe2138 100644 --- a/app/controllers/projects/pipelines_settings_controller.rb +++ b/app/controllers/projects/pipelines_settings_controller.rb @@ -2,13 +2,13 @@ class Projects::PipelinesSettingsController < Projects::ApplicationController before_action :authorize_admin_pipeline! def show - redirect_to namespace_project_settings_ci_cd_path(@project.namespace, @project, params: params) + redirect_to project_settings_ci_cd_path(@project, params: params) end def update if @project.update_attributes(update_params) - flash[:notice] = "CI/CD Pipelines settings for '#{@project.name}' were successfully updated." - redirect_to namespace_project_settings_ci_cd_path(@project.namespace, @project) + flash[:notice] = "Pipelines settings for '#{@project.name}' were successfully updated." + redirect_to project_settings_ci_cd_path(@project) else render 'show' end @@ -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, :auto_cancel_pending_pipelines + :public_builds, :auto_cancel_pending_pipelines, :ci_config_path ) end end diff --git a/app/controllers/projects/project_members_controller.rb b/app/controllers/projects/project_members_controller.rb index d2d26738582..f8ff7413b53 100644 --- a/app/controllers/projects/project_members_controller.rb +++ b/app/controllers/projects/project_members_controller.rb @@ -6,8 +6,23 @@ class Projects::ProjectMembersController < Projects::ApplicationController before_action :authorize_admin_project_member!, except: [:index, :leave, :request_access] def index - sort = params[:sort].presence || sort_value_name - redirect_to namespace_project_settings_members_path(@project.namespace, @project, sort: sort) + @sort = params[:sort].presence || sort_value_name + @group_links = @project.project_group_links + + @skip_groups = @group_links.pluck(:group_id) + @skip_groups << @project.namespace_id unless @project.personal? + @skip_groups += @project.group.ancestors.pluck(:id) if @project.group + + @project_members = MembersFinder.new(@project, current_user).execute + + if params[:search].present? + @project_members = @project_members.joins(:user).merge(User.search(params[:search])) + @group_links = @group_links.where(group_id: @project.invited_groups.search(params[:search]).select(:id)) + end + + @project_members = @project_members.sort(@sort).page(params[:page]) + @requesters = AccessRequestsFinder.new(@project).execute(current_user) + @project_member = @project.project_members.new end def update @@ -19,7 +34,7 @@ class Projects::ProjectMembersController < Projects::ApplicationController end def resend_invite - redirect_path = namespace_project_settings_members_path(@project.namespace, @project) + redirect_path = project_project_members_path(@project) @project_member = @project.project_members.find(params[:id]) @@ -42,7 +57,7 @@ class Projects::ProjectMembersController < Projects::ApplicationController return render_404 end - redirect_to(namespace_project_settings_members_path(project.namespace, project), + redirect_to(project_project_members_path(project), notice: notice) end diff --git a/app/controllers/projects/prometheus_controller.rb b/app/controllers/projects/prometheus_controller.rb new file mode 100644 index 00000000000..507468d7102 --- /dev/null +++ b/app/controllers/projects/prometheus_controller.rb @@ -0,0 +1,24 @@ +class Projects::PrometheusController < Projects::ApplicationController + before_action :authorize_read_project! + before_action :require_prometheus_metrics! + + def active_metrics + respond_to do |format| + format.json do + matched_metrics = project.prometheus_service.matched_metrics || {} + + if matched_metrics.any? + render json: matched_metrics + else + head :no_content + end + end + end + end + + private + + def require_prometheus_metrics! + render_404 unless project.prometheus_service.present? + end +end diff --git a/app/controllers/projects/protected_branches_controller.rb b/app/controllers/projects/protected_branches_controller.rb index ba24fa9acfe..d1719f12072 100644 --- a/app/controllers/projects/protected_branches_controller.rb +++ b/app/controllers/projects/protected_branches_controller.rb @@ -19,7 +19,7 @@ class Projects::ProtectedBranchesController < Projects::ProtectedRefsController def protected_ref_params params.require(:protected_branch).permit(:name, - merge_access_levels_attributes: [:access_level, :id], - push_access_levels_attributes: [:access_level, :id]) + merge_access_levels_attributes: access_level_attributes, + push_access_levels_attributes: access_level_attributes) end end diff --git a/app/controllers/projects/protected_refs_controller.rb b/app/controllers/projects/protected_refs_controller.rb index 083a70968e5..b51bdf7aa78 100644 --- a/app/controllers/projects/protected_refs_controller.rb +++ b/app/controllers/projects/protected_refs_controller.rb @@ -44,4 +44,10 @@ class Projects::ProtectedRefsController < Projects::ApplicationController format.js { head :ok } end end + + protected + + def access_level_attributes + %i(access_level id) + end end diff --git a/app/controllers/projects/protected_tags_controller.rb b/app/controllers/projects/protected_tags_controller.rb index c61ddf145e6..a5dbd7e46ae 100644 --- a/app/controllers/projects/protected_tags_controller.rb +++ b/app/controllers/projects/protected_tags_controller.rb @@ -18,6 +18,6 @@ class Projects::ProtectedTagsController < Projects::ProtectedRefsController end def protected_ref_params - params.require(:protected_tag).permit(:name, create_access_levels_attributes: [:access_level, :id]) + params.require(:protected_tag).permit(:name, create_access_levels_attributes: access_level_attributes) 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/refs_controller.rb b/app/controllers/projects/refs_controller.rb index 667f4870c7a..1eb78d8b522 100644 --- a/app/controllers/projects/refs_controller.rb +++ b/app/controllers/projects/refs_controller.rb @@ -13,21 +13,21 @@ class Projects::RefsController < Projects::ApplicationController new_path = case params[:destination] when "tree" - namespace_project_tree_path(@project.namespace, @project, @id) + project_tree_path(@project, @id) when "blob" - namespace_project_blob_path(@project.namespace, @project, @id) + project_blob_path(@project, @id) when "graph" - namespace_project_network_path(@project.namespace, @project, @id, @options) + project_network_path(@project, @id, @options) when "graphs" - namespace_project_graph_path(@project.namespace, @project, @id) + project_graph_path(@project, @id) when "find_file" - namespace_project_find_file_path(@project.namespace, @project, @id) + project_find_file_path(@project, @id) when "graphs_commits" - commits_namespace_project_graph_path(@project.namespace, @project, @id) + commits_project_graph_path(@project, @id) when "badges" - namespace_project_pipelines_settings_path(@project.namespace, @project, ref: @id) + project_pipelines_settings_path(@project, ref: @id) else - namespace_project_commits_path(@project.namespace, @project, @id) + project_commits_path(@project, @id) end redirect_to new_path @@ -62,7 +62,7 @@ class Projects::RefsController < Projects::ApplicationController offset = (@offset + @limit) if contents.size > offset - @more_log_url = logs_file_namespace_project_ref_path(@project.namespace, @project, @ref, @path || '', offset: offset) + @more_log_url = logs_file_project_ref_path(@project, @ref, @path || '', offset: offset) end respond_to do |format| @@ -74,6 +74,6 @@ class Projects::RefsController < Projects::ApplicationController private def validate_ref_id - return not_found! if params[:id].present? && params[:id] !~ Gitlab::Regex.git_reference_regex + return not_found! if params[:id].present? && params[:id] !~ Gitlab::PathRegex.git_reference_regex end end diff --git a/app/controllers/projects/registry/repositories_controller.rb b/app/controllers/projects/registry/repositories_controller.rb index 17f391ba07f..71e7dc70a4d 100644 --- a/app/controllers/projects/registry/repositories_controller.rb +++ b/app/controllers/projects/registry/repositories_controller.rb @@ -10,10 +10,12 @@ module Projects def destroy if image.destroy - redirect_to project_container_registry_path(@project), + redirect_to project_container_registry_index_path(@project), + status: 302, notice: 'Image repository has been removed successfully!' else - redirect_to project_container_registry_path(@project), + redirect_to project_container_registry_index_path(@project), + status: 302, alert: 'Failed to remove image repository!' end end diff --git a/app/controllers/projects/registry/tags_controller.rb b/app/controllers/projects/registry/tags_controller.rb index d689cade3ab..ae72bd03cfb 100644 --- a/app/controllers/projects/registry/tags_controller.rb +++ b/app/controllers/projects/registry/tags_controller.rb @@ -5,10 +5,12 @@ module Projects def destroy if tag.delete - redirect_to project_container_registry_path(@project), + redirect_to project_container_registry_index_path(@project), + status: 302, notice: 'Registry tag has been removed successfully!' else - redirect_to project_container_registry_path(@project), + redirect_to project_container_registry_index_path(@project), + status: 302, alert: 'Failed to remove registry tag!' end end diff --git a/app/controllers/projects/releases_controller.rb b/app/controllers/projects/releases_controller.rb index 2c097cb4d8d..3e0a530fdb9 100644 --- a/app/controllers/projects/releases_controller.rb +++ b/app/controllers/projects/releases_controller.rb @@ -19,7 +19,7 @@ class Projects::ReleasesController < Projects::ApplicationController release.destroy end - redirect_to namespace_project_tag_path(@project.namespace, @project, @tag.name) + redirect_to project_tag_path(@project, @tag.name) end private diff --git a/app/controllers/projects/runner_projects_controller.rb b/app/controllers/projects/runner_projects_controller.rb index 8267b14941d..3cb01405b05 100644 --- a/app/controllers/projects/runner_projects_controller.rb +++ b/app/controllers/projects/runner_projects_controller.rb @@ -22,6 +22,6 @@ class Projects::RunnerProjectsController < Projects::ApplicationController runner_project = project.runner_projects.find(params[:id]) runner_project.destroy - redirect_to runners_path(project) + redirect_to runners_path(project), status: 302 end end diff --git a/app/controllers/projects/runners_controller.rb b/app/controllers/projects/runners_controller.rb index 8b50ea207a5..9f9773575a5 100644 --- a/app/controllers/projects/runners_controller.rb +++ b/app/controllers/projects/runners_controller.rb @@ -5,7 +5,7 @@ class Projects::RunnersController < Projects::ApplicationController layout 'project_settings' def index - redirect_to namespace_project_settings_ci_cd_path(@project.namespace, @project) + redirect_to project_settings_ci_cd_path(@project) end def edit @@ -24,7 +24,7 @@ class Projects::RunnersController < Projects::ApplicationController @runner.destroy end - redirect_to runners_path(@project) + redirect_to runners_path(@project), status: 302 end def resume @@ -49,7 +49,7 @@ class Projects::RunnersController < Projects::ApplicationController def toggle_shared_runners project.toggle!(:shared_runners_enabled) - redirect_to namespace_project_settings_ci_cd_path(@project.namespace, @project) + redirect_to project_settings_ci_cd_path(@project) end protected diff --git a/app/controllers/projects/services_controller.rb b/app/controllers/projects/services_controller.rb index f9d798d0455..d54a1111f11 100644 --- a/app/controllers/projects/services_controller.rb +++ b/app/controllers/projects/services_controller.rb @@ -4,6 +4,7 @@ class Projects::ServicesController < Projects::ApplicationController # Authorize before_action :authorize_admin_project! before_action :service, only: [:edit, :update, :test] + before_action :update_service, only: [:update, :test] respond_to :html @@ -13,36 +14,46 @@ class Projects::ServicesController < Projects::ApplicationController end def update - @service.assign_attributes(service_params[:service]) if @service.save(context: :manual_change) - redirect_to( - edit_namespace_project_service_path(@project.namespace, @project, @service.to_param), - notice: 'Successfully updated.' - ) + redirect_to(project_settings_integrations_path(@project), notice: success_message) else render 'edit' end end def test - return render_404 unless @service.can_test? + message = {} + + if @service.can_test? + data = @service.test_data(project, current_user) + outcome = @service.test(data) - data = @service.test_data(project, current_user) - outcome = @service.test(data) + unless outcome[:success] + message = { error: true, message: 'Test failed.', service_response: outcome[:result].to_s } + end - if outcome[:success] - message = { notice: 'We sent a request to the provided URL' } + status = :ok else - error_message = "We tried to send a request to the provided URL but an error occurred" - error_message << ": #{outcome[:result]}" if outcome[:result].present? - message = { alert: error_message } + status = :not_found end - redirect_back_or_default(options: message) + render json: message, status: status end private + def success_message + if @service.active? + "#{@service.title} activated." + else + "#{@service.title} settings saved, but not activated." + end + end + + def update_service + @service.assign_attributes(service_params[:service]) + end + def service @service ||= @project.find_or_initialize_service(params[:id]) end diff --git a/app/controllers/projects/settings/ci_cd_controller.rb b/app/controllers/projects/settings/ci_cd_controller.rb index 6f009d61950..15a2ff56b92 100644 --- a/app/controllers/projects/settings/ci_cd_controller.rb +++ b/app/controllers/projects/settings/ci_cd_controller.rb @@ -14,14 +14,17 @@ module Projects def define_runners_variables @project_runners = @project.runners.ordered - @assignable_runners = current_user.ci_authorized_runners. - assignable_for(project).ordered.page(params[:page]).per(20) + @assignable_runners = current_user.ci_authorized_runners + .assignable_for(project).ordered.page(params[:page]).per(20) @shared_runners = Ci::Runner.shared.active @shared_runners_count = @shared_runners.count(:all) end def define_secret_variables - @variable = Ci::Variable.new + @variable = Ci::Variable.new(project: project) + .present(current_user: current_user) + @variables = project.variables.order_key_asc + .map { |variable| variable.present(current_user: current_user) } end def define_triggers_variables @@ -32,7 +35,7 @@ module Projects def define_badges_variables @ref = params[:ref] || @project.default_branch || 'master' - @badges = [Gitlab::Badge::Build::Status, + @badges = [Gitlab::Badge::Pipeline::Status, Gitlab::Badge::Coverage::Report] @badges.map! do |badge| 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/members_controller.rb b/app/controllers/projects/settings/members_controller.rb deleted file mode 100644 index 54f9dceddef..00000000000 --- a/app/controllers/projects/settings/members_controller.rb +++ /dev/null @@ -1,27 +0,0 @@ -module Projects - module Settings - class MembersController < Projects::ApplicationController - include SortingHelper - - def show - @sort = params[:sort].presence || sort_value_name - @group_links = @project.project_group_links - - @skip_groups = @group_links.pluck(:group_id) - @skip_groups << @project.namespace_id unless @project.personal? - @skip_groups += @project.group.ancestors.pluck(:id) if @project.group - - @project_members = MembersFinder.new(@project, current_user).execute - - if params[:search].present? - @project_members = @project_members.joins(:user).merge(User.search(params[:search])) - @group_links = @group_links.where(group_id: @project.invited_groups.search(params[:search]).select(:id)) - end - - @project_members = @project_members.sort(@sort).page(params[:page]) - @requesters = AccessRequestsFinder.new(@project).execute(current_user) - @project_member = @project.project_members.new - end - end - end -end diff --git a/app/controllers/projects/snippets_controller.rb b/app/controllers/projects/snippets_controller.rb index 5c9e0d4d1a1..d07143d294f 100644 --- a/app/controllers/projects/snippets_controller.rb +++ b/app/controllers/projects/snippets_controller.rb @@ -3,8 +3,9 @@ class Projects::SnippetsController < Projects::ApplicationController include ToggleAwardEmoji include SpammableActions include SnippetsActions + include RendersBlob - before_action :module_enabled + before_action :check_snippets_available! before_action :snippet, only: [:show, :edit, :destroy, :update, :raw, :toggle_award_emoji, :mark_as_spam] # Allow read any snippet @@ -22,15 +23,14 @@ 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) + redirect_to project_snippets_path(@project, page: @snippets.total_pages) end end @@ -55,11 +55,23 @@ class Projects::SnippetsController < Projects::ApplicationController end def show - @note = @project.notes.new(noteable: @snippet) - @noteable = @snippet - - @discussions = @snippet.discussions - @notes = prepare_notes_for_rendering(@discussions.flat_map(&:notes)) + blob = @snippet.blob + conditionally_expand_blob(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 @@ -67,7 +79,7 @@ class Projects::SnippetsController < Projects::ApplicationController @snippet.destroy - redirect_to namespace_project_snippets_path(@project.namespace, @project) + redirect_to project_snippets_path(@project), status: 302 end protected @@ -78,6 +90,10 @@ class Projects::SnippetsController < Projects::ApplicationController alias_method :awardable, :snippet alias_method :spammable, :snippet + def spammable_path + project_snippet_path(@project, @snippet) + end + def authorize_read_project_snippet! return render_404 unless can?(current_user, :read_project_snippet, @snippet) end @@ -90,11 +106,7 @@ class Projects::SnippetsController < Projects::ApplicationController return render_404 unless can?(current_user, :admin_project_snippet, @snippet) end - def module_enabled - return render_404 unless @project.feature_available?(:snippets, current_user) - end - def snippet_params - params.require(:project_snippet).permit(:title, :content, :file_name, :private, :visibility_level) + params.require(:project_snippet).permit(:title, :content, :file_name, :private, :visibility_level, :description) end end diff --git a/app/controllers/projects/tags_controller.rb b/app/controllers/projects/tags_controller.rb index e13f0bde315..b62d7d9b7c5 100644 --- a/app/controllers/projects/tags_controller.rb +++ b/app/controllers/projects/tags_controller.rb @@ -29,15 +29,17 @@ class Projects::TagsController < Projects::ApplicationController end def create - result = Tags::CreateService.new(@project, current_user). - execute(params[:tag_name], params[:ref], params[:message], params[:release_description]) + result = Tags::CreateService.new(@project, current_user) + .execute(params[:tag_name], params[:ref], params[:message], params[:release_description]) if result[:status] == :success @tag = result[:tag] - redirect_to namespace_project_tag_path(@project.namespace, @project, @tag.name) + redirect_to project_tag_path(@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 project_tags_path(@project), status: 303 end format.js @@ -56,8 +58,8 @@ class Projects::TagsController < Projects::ApplicationController @error = result[:message] format.html do - redirect_to namespace_project_tags_path(@project.namespace, @project), - alert: @error + redirect_to project_tags_path(@project), + 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 5e2182c883e..1fc276b8c03 100644 --- a/app/controllers/projects/tree_controller.rb +++ b/app/controllers/projects/tree_controller.rb @@ -16,7 +16,7 @@ class Projects::TreeController < Projects::ApplicationController if tree.entries.empty? if @repository.blob_at(@commit.id, @path) return redirect_to( - namespace_project_blob_path(@project.namespace, @project, + project_blob_path(@project, File.join(@ref, @path)) ) elsif @path.present? @@ -25,19 +25,27 @@ class Projects::TreeController < Projects::ApplicationController end respond_to do |format| - format.html - # Disable cache so browser history works - format.js { no_cache_headers } + format.html do + @last_commit = @repository.last_commit_for_path(@commit.id, @tree.path) || @commit + end + + format.js do + # Disable cache so browser history works + no_cache_headers + end + + format.json do + render json: TreeSerializer.new(project: @project, repository: @repository, ref: @ref).represent(@tree) + end end end def create_dir return render_404 unless @commit_params.values.all? - 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(@branch_name, @dir_name)), - failure_path: namespace_project_tree_path(@project.namespace, @project, @ref)) + success_path: project_tree_path(@project, File.join(@branch_name, @dir_name)), + failure_path: project_tree_path(@project, @ref)) end private @@ -48,7 +56,7 @@ class Projects::TreeController < Projects::ApplicationController @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 afa56de920b..e04145dd0b3 100644 --- a/app/controllers/projects/triggers_controller.rb +++ b/app/controllers/projects/triggers_controller.rb @@ -7,7 +7,7 @@ class Projects::TriggersController < Projects::ApplicationController layout 'project_settings' def index - redirect_to namespace_project_settings_ci_cd_path(@project.namespace, @project) + redirect_to project_settings_ci_cd_path(@project) end def create @@ -19,7 +19,7 @@ class Projects::TriggersController < Projects::ApplicationController flash[:alert] = 'You could not create a new trigger.' end - redirect_to namespace_project_settings_ci_cd_path(@project.namespace, @project) + redirect_to project_settings_ci_cd_path(@project) end def take_ownership @@ -29,7 +29,7 @@ class Projects::TriggersController < Projects::ApplicationController flash[:alert] = 'You could not take ownership of trigger.' end - redirect_to namespace_project_settings_ci_cd_path(@project.namespace, @project) + redirect_to project_settings_ci_cd_path(@project) end def edit @@ -37,7 +37,7 @@ class Projects::TriggersController < Projects::ApplicationController def update if trigger.update(trigger_params) - redirect_to namespace_project_settings_ci_cd_path(@project.namespace, @project), notice: 'Trigger was successfully updated.' + redirect_to project_settings_ci_cd_path(@project), notice: 'Trigger was successfully updated.' else render action: "edit" end @@ -50,7 +50,7 @@ class Projects::TriggersController < Projects::ApplicationController flash[:alert] = "Could not remove the trigger." end - redirect_to namespace_project_settings_ci_cd_path(@project.namespace, @project) + redirect_to project_settings_ci_cd_path(@project), status: 302 end private @@ -69,8 +69,7 @@ class Projects::TriggersController < Projects::ApplicationController def trigger_params params.require(:trigger).permit( - :description, - trigger_schedule_attributes: [:id, :active, :cron, :cron_timezone, :ref] + :description ) 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/variables_controller.rb b/app/controllers/projects/variables_controller.rb index a4d1b1ee69b..6a825137564 100644 --- a/app/controllers/projects/variables_controller.rb +++ b/app/controllers/projects/variables_controller.rb @@ -1,47 +1,60 @@ class Projects::VariablesController < Projects::ApplicationController + before_action :variable, only: [:show, :update, :destroy] before_action :authorize_admin_build! layout 'project_settings' def index - redirect_to namespace_project_settings_ci_cd_path(@project.namespace, @project) + redirect_to project_settings_ci_cd_path(@project) end def show - @variable = @project.variables.find(params[:id]) end def update - @variable = @project.variables.find(params[:id]) - - if @variable.update_attributes(project_params) - redirect_to namespace_project_variables_path(project.namespace, project), notice: 'Variable was successfully updated.' + if variable.update(variable_params) + redirect_to project_variables_path(project), + notice: 'Variable was successfully updated.' else - render action: "show" + render "show" end end def create - @variable = Ci::Variable.new(project_params) + @variable = project.variables.create(variable_params) + .present(current_user: current_user) - if @variable.valid? && @project.variables << @variable - flash[:notice] = 'Variables were successfully updated.' - redirect_to namespace_project_settings_ci_cd_path(project.namespace, project) + if @variable.persisted? + redirect_to project_settings_ci_cd_path(project), + notice: 'Variable was successfully created.' else render "show" end end def destroy - @key = @project.variables.find(params[:id]) - @key.destroy - - redirect_to namespace_project_settings_ci_cd_path(project.namespace, project), notice: 'Variable was successfully removed.' + if variable.destroy + redirect_to project_settings_ci_cd_path(project), + status: 302, + notice: 'Variable was successfully removed.' + else + redirect_to project_settings_ci_cd_path(project), + status: 302, + notice: 'Failed to remove the variable.' + end end private - def project_params - params.require(:variable).permit([:id, :key, :value, :_destroy]) + def variable_params + params.require(:variable).permit(*variable_params_attributes) + end + + def variable_params_attributes + %i[id key value protected _destroy] + end + + def variable + @variable ||= project.variables.find(params[:id]).present(current_user: current_user) end end diff --git a/app/controllers/projects/wikis_controller.rb b/app/controllers/projects/wikis_controller.rb index 96125684da0..968d880886c 100644 --- a/app/controllers/projects/wikis_controller.rb +++ b/app/controllers/projects/wikis_controller.rb @@ -1,6 +1,4 @@ class Projects::WikisController < Projects::ApplicationController - include MarkdownPreview - before_action :authorize_read_wiki! before_action :authorize_create_wiki!, only: [:edit, :create, :history] before_action :authorize_admin_wiki!, only: :destroy @@ -51,12 +49,15 @@ class Projects::WikisController < Projects::ApplicationController if @page.valid? redirect_to( - namespace_project_wiki_path(@project.namespace, @project, @page), + project_wiki_path(@project, @page), notice: 'Wiki was successfully updated.' ) else render 'edit' end + rescue WikiPage::PageChangedError + @conflict = true + render 'edit' end def create @@ -64,7 +65,7 @@ class Projects::WikisController < Projects::ApplicationController if @page.persisted? redirect_to( - namespace_project_wiki_path(@project.namespace, @project, @page), + project_wiki_path(@project, @page), notice: 'Wiki was successfully updated.' ) else @@ -77,7 +78,7 @@ class Projects::WikisController < Projects::ApplicationController unless @page redirect_to( - namespace_project_wiki_path(@project.namespace, @project, :home), + project_wiki_path(@project, :home), notice: "Page not found" ) end @@ -87,19 +88,23 @@ class Projects::WikisController < Projects::ApplicationController @page = @project_wiki.find_page(params[:id]) WikiPages::DestroyService.new(@project, current_user).execute(@page) - redirect_to( - namespace_project_wiki_path(@project.namespace, @project, :home), - notice: "Page was successfully deleted" - ) + redirect_to project_wiki_path(@project, :home), + status: 302, + notice: "Page was successfully deleted" end def git_access end def preview_markdown - context = { pipeline: :wiki, project_wiki: @project_wiki, page_slug: params[:id] } - - render_markdown_preview(params[:text], context) + result = PreviewMarkdownService.new(@project, current_user, params).execute + + render json: { + body: view_context.markdown(result[:text], pipeline: :wiki, project_wiki: @project_wiki, page_slug: params[:id]), + references: { + users: result[:users] + } + } end private @@ -117,6 +122,6 @@ class Projects::WikisController < Projects::ApplicationController end def wiki_params - params.require(:wiki).permit(:title, :content, :format, :message) + params.require(:wiki).permit(:title, :content, :format, :message, :last_commit_sha) end end diff --git a/app/controllers/projects_controller.rb b/app/controllers/projects_controller.rb index 9f6ee4826e6..1d24563a6a6 100644 --- a/app/controllers/projects_controller.rb +++ b/app/controllers/projects_controller.rb @@ -1,13 +1,13 @@ class ProjectsController < Projects::ApplicationController include IssuableCollections include ExtractsPath - include MarkdownPreview before_action :authenticate_user!, except: [:index, :show, :activity, :refs] before_action :project, except: [:index, :new, :create] before_action :repository, except: [:index, :new, :create] before_action :assign_ref_vars, only: [:show], if: :repo_exists? before_action :tree, only: [:show], if: [:repo_exists?, :project_view_files?] + before_action :project_export_enabled, only: [:export, :download_export, :remove_export, :generate_new_export] # Authorize before_action :authorize_admin_project!, only: [:edit, :update, :housekeeping, :download_export, :export, :remove_export, :generate_new_export] @@ -35,7 +35,7 @@ class ProjectsController < Projects::ApplicationController redirect_to( project_path(@project), - notice: "Project '#{@project.name}' was successfully created." + notice: _("Project '%{project_name}' was successfully created.") % { project_name: @project.name } ) else render 'new' @@ -50,11 +50,14 @@ class ProjectsController < Projects::ApplicationController respond_to do |format| if result[:status] == :success - flash[:notice] = "Project '#{@project.name}' was successfully updated." + flash[:notice] = _("Project '%{project_name}' was successfully updated.") % { project_name: @project.name } + format.html do redirect_to(edit_project_path(@project)) end else + flash[:alert] = result[:message] + format.html { render 'edit' } end @@ -77,7 +80,7 @@ class ProjectsController < Projects::ApplicationController return access_denied! unless can?(current_user, :remove_fork_project, @project) if ::Projects::UnlinkForkService.new(@project, current_user).execute - flash[:notice] = 'The fork relationship has been removed.' + flash[:notice] = _('The fork relationship has been removed.') end end @@ -93,12 +96,12 @@ class ProjectsController < Projects::ApplicationController def show if @project.import_in_progress? - redirect_to namespace_project_import_path(@project.namespace, @project) + redirect_to project_import_path(@project) return end if @project.pending_delete? - flash[:alert] = "Project #{@project.name} queued for deletion." + flash.now[:alert] = _("Project '%{project_name}' queued for deletion.") % { project_name: @project.name } end respond_to do |format| @@ -109,7 +112,7 @@ class ProjectsController < Projects::ApplicationController format.atom do load_events - render layout: false + render layout: 'xml.atom' end end end @@ -118,11 +121,11 @@ class ProjectsController < Projects::ApplicationController return access_denied! unless can?(current_user, :remove_project, @project) ::Projects::DestroyService.new(@project, current_user, {}).async_execute - flash[:alert] = "Project '#{@project.name_with_namespace}' will be deleted." + flash[:alert] = _("Project '%{project_name}' will be deleted.") % { project_name: @project.name_with_namespace } - redirect_to dashboard_projects_path + redirect_to dashboard_projects_path, status: 302 rescue Projects::DestroyService::DestroyError => ex - redirect_to edit_project_path(@project), alert: ex.message + redirect_to edit_project_path(@project), status: 302, alert: ex.message end def new_issue_address @@ -157,7 +160,7 @@ class ProjectsController < Projects::ApplicationController redirect_to( project_path(@project), - notice: "Housekeeping successfully started" + notice: _("Housekeeping successfully started") ) rescue ::Projects::HousekeepingService::LeaseTaken => ex redirect_to( @@ -171,7 +174,7 @@ class ProjectsController < Projects::ApplicationController redirect_to( edit_project_path(@project), - notice: "Project export started. A download link will be sent by email." + notice: _("Project export started. A download link will be sent by email.") ) end @@ -183,16 +186,16 @@ class ProjectsController < Projects::ApplicationController else redirect_to( edit_project_path(@project), - alert: "Project export link has expired. Please generate a new export from your project settings." + alert: _("Project export link has expired. Please generate a new export from your project settings.") ) end end def remove_export if @project.remove_exports - flash[:notice] = "Project export has been deleted." + flash[:notice] = _("Project export has been deleted.") else - flash[:alert] = "Project export could not be deleted." + flash[:alert] = _("Project export could not be deleted.") end redirect_to(edit_project_path(@project)) end @@ -203,7 +206,7 @@ class ProjectsController < Projects::ApplicationController else redirect_to( edit_project_path(@project), - alert: "Project export could not be deleted." + alert: _("Project export could not be deleted.") ) end end @@ -218,21 +221,34 @@ class ProjectsController < Projects::ApplicationController end def refs - branches = BranchesFinder.new(@repository, params).execute.map(&:name) + find_refs = params['find'] - options = { - 'Branches' => branches.take(100), - } + find_branches = true + find_tags = true + find_commits = true + + unless find_refs.nil? + find_branches = find_refs.include?('branches') + find_tags = find_refs.include?('tags') + find_commits = find_refs.include?('commits') + end - unless @repository.tag_count.zero? - tags = TagsFinder.new(@repository, params).execute.map(&:name) + options = {} - options['Tags'] = tags.take(100) + if find_branches + branches = BranchesFinder.new(@repository, params).execute.take(100).map(&:name) + options[s_('RefSwitcher|Branches')] = branches + end + + if find_tags && @repository.tag_count.nonzero? + tags = TagsFinder.new(@repository, params).execute.take(100).map(&:name) + + options[s_('RefSwitcher|Tags')] = tags end # If reference is commit id - we should add it to branch/tag selectbox ref = Addressable::URI.unescape(params[:ref]) - if ref && options.flatten(2).exclude?(ref) && ref =~ /\A[0-9a-zA-Z]{6,52}\z/ + if find_commits && ref && options.flatten(2).exclude?(ref) && ref =~ /\A[0-9a-zA-Z]{6,52}\z/ options['Commits'] = [ref] end @@ -240,7 +256,15 @@ class ProjectsController < Projects::ApplicationController end def preview_markdown - render_markdown_preview(params[:text]) + 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 @@ -250,7 +274,7 @@ class ProjectsController < Projects::ApplicationController # # pages list order: repository readme, wiki home, issues list, customize workflow def render_landing_page - if @project.feature_available?(:repository, current_user) + if can?(current_user, :download_code, @project) return render 'projects/no_repo' unless @project.repository_exists? render 'projects/empty' if @project.empty_repo? else @@ -278,18 +302,19 @@ class ProjectsController < Projects::ApplicationController end def load_events - @events = @project.events.recent - @events = event_filter.apply_filter(@events).with_associations - limit = (params[:limit] || 20).to_i - @events = @events.limit(limit).offset(params[:offset] || 0) + projects = Project.where(id: @project.id) + + @events = EventCollection + .new(projects, offset: params[:offset].to_i, filter: event_filter) + .to_a end def project_params params.require(:project) - .permit(project_params_ce) + .permit(project_params_attributes) end - def project_params_ce + def project_params_attributes [ :avatar, :build_allow_git_fetch, @@ -314,6 +339,7 @@ class ProjectsController < Projects::ApplicationController :runners_token, :tag_list, :visibility_level, + :template_name, project_feature_attributes: %i[ builds_access_level @@ -358,4 +384,15 @@ class ProjectsController < Projects::ApplicationController 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 + + def project_export_enabled + render_404 unless current_application_settings.project_export_enabled? + end end diff --git a/app/controllers/registrations_controller.rb b/app/controllers/registrations_controller.rb index 3ca14dee33c..1bc6520370a 100644 --- a/app/controllers/registrations_controller.rb +++ b/app/controllers/registrations_controller.rb @@ -25,12 +25,12 @@ class RegistrationsController < Devise::RegistrationsController end def destroy - DeleteUserWorker.perform_async(current_user.id, current_user.id) + current_user.delete_async(deleted_by: current_user) respond_to do |format| format.html do session.try(:destroy) - redirect_to new_user_session_path, notice: "Account scheduled for removal." + redirect_to new_user_session_path, status: 302, notice: "Account scheduled for removal." end end end diff --git a/app/controllers/search_controller.rb b/app/controllers/search_controller.rb index 4a579601785..d58c8d14a75 100644 --- a/app/controllers/search_controller.rb +++ b/app/controllers/search_controller.rb @@ -44,7 +44,7 @@ class SearchController < ApplicationController query = params[:search].strip.downcase found_by_commit_sha = Commit.valid_hash?(query) && only_commit.sha.start_with?(query) - redirect_to namespace_project_commit_path(@project.namespace, @project, only_commit) if found_by_commit_sha + redirect_to project_commit_path(@project, only_commit) if found_by_commit_sha end end end diff --git a/app/controllers/sessions_controller.rb b/app/controllers/sessions_controller.rb index 8c6ba4915cd..9e743685d60 100644 --- a/app/controllers/sessions_controller.rb +++ b/app/controllers/sessions_controller.rb @@ -5,6 +5,14 @@ class SessionsController < Devise::SessionsController skip_before_action :check_two_factor_requirement, only: [:destroy] + # Explicitly call protect from forgery before anything else. Otherwise the + # CSFR-token might be cleared before authentication is done. This was the case + # when LDAP was enabled and the `OmniauthCallbacksController` is loaded + # + # *Note:* `prepend: true` is the default for rails4, but this will be changed + # to `prepend: false` in rails5. + protect_from_forgery prepend: true, with: :exception + prepend_before_action :check_initial_setup, only: [:new] prepend_before_action :authenticate_with_two_factor, if: :two_factor_enabled?, only: [:create] @@ -15,12 +23,7 @@ class SessionsController < Devise::SessionsController def new set_minimum_password_length - @ldap_servers = - if Gitlab.config.ldap.enabled - Gitlab::LDAP::Config.servers - else - [] - end + @ldap_servers = Gitlab::LDAP::Config.available_servers super end @@ -47,6 +50,10 @@ class SessionsController < Devise::SessionsController private + def login_counter + @login_counter ||= Gitlab::Metrics.counter(:user_session_logins_total, 'User sign in count') + end + # Handle an "initial setup" state, where there's only one user, it's an admin, # and they require a password change. def check_initial_setup @@ -54,12 +61,13 @@ class SessionsController < Devise::SessionsController user = User.admins.last - return unless user && user.require_password? + return unless user && user.require_password_creation? - token = user.generate_reset_token - user.save + Users::UpdateService.new(user).execute do |user| + @token = user.generate_reset_token + end - redirect_to edit_user_password_path(reset_password_token: token), + redirect_to edit_user_password_path(reset_password_token: @token), notice: "Please create a password for your new account." end @@ -90,7 +98,7 @@ class SessionsController < Devise::SessionsController # Prevent a 'you are already signed in' message directly after signing: # we should never redirect to '/users/sign_in' after signing in successfully. - unless redirect_path == new_user_session_path + unless URI(redirect_path).path == new_user_session_path store_location_for(:redirect, redirect_path) end end @@ -103,6 +111,10 @@ class SessionsController < Devise::SessionsController provider = Gitlab.config.omniauth.auto_sign_in_with_provider return unless provider.present? + # If a "auto_sign_in" query parameter is set to a falsy value, don't auto sign-in. + # Otherwise, the default is to auto sign-in. + return if Gitlab::Utils.to_boolean(params[:auto_sign_in]) == false + # Auto sign in with an Omniauth provider only if the standard "you need to sign-in" alert is # registered or no alert at all. In case of another alert (such as a blocked user), it is safer # to do nothing to prevent redirection loops with certain Omniauth providers. @@ -120,11 +132,12 @@ class SessionsController < Devise::SessionsController end def log_audit_event(user, options = {}) - AuditEventService.new(user, user, options). - for_authentication.security_event + AuditEventService.new(user, user, options) + .for_authentication.security_event end def log_user_activity(user) + login_counter.increment Users::ActivityService.new(user, 'login').execute end diff --git a/app/controllers/sherlock/application_controller.rb b/app/controllers/sherlock/application_controller.rb index 682ca5e3821..6bdd3568a78 100644 --- a/app/controllers/sherlock/application_controller.rb +++ b/app/controllers/sherlock/application_controller.rb @@ -4,8 +4,8 @@ module Sherlock def find_transaction if params[:transaction_id] - @transaction = Gitlab::Sherlock.collection. - find_transaction(params[:transaction_id]) + @transaction = Gitlab::Sherlock.collection + .find_transaction(params[:transaction_id]) end end end diff --git a/app/controllers/sherlock/transactions_controller.rb b/app/controllers/sherlock/transactions_controller.rb index ccc739da879..cb6c3a7cd98 100644 --- a/app/controllers/sherlock/transactions_controller.rb +++ b/app/controllers/sherlock/transactions_controller.rb @@ -13,7 +13,7 @@ module Sherlock def destroy_all Gitlab::Sherlock.collection.clear - redirect_to(:back) + redirect_to :back, status: 302 end end 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 056910fad67..8c3abd0a085 100644 --- a/app/controllers/snippets_controller.rb +++ b/app/controllers/snippets_controller.rb @@ -1,13 +1,14 @@ class SnippetsController < ApplicationController + include RendersNotes include ToggleAwardEmoji include SpammableActions include SnippetsActions - include MarkdownPreview + 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] @@ -15,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 @@ -26,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 @@ -48,6 +45,8 @@ class SnippetsController < ApplicationController @snippet = CreateSnippetService.new(nil, current_user, create_params).execute + move_temporary_files if @snippet.valid? && params[:files] + recaptcha_check_with_fallback { render :new } end @@ -60,6 +59,24 @@ class SnippetsController < ApplicationController end def show + blob = @snippet.blob + conditionally_expand_blob(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 @@ -67,38 +84,41 @@ class SnippetsController < ApplicationController @snippet.destroy - 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 - ) + redirect_to snippets_path, status: 302 end def preview_markdown - render_markdown_preview(params[:text], skip_project_check: true) + 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 spammable_path + snippet_path(@snippet) + end + 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! @@ -110,6 +130,12 @@ class SnippetsController < ApplicationController end def snippet_params - params.require(:personal_snippet).permit(:title, :content, :file_name, :private, :visibility_level) + params.require(:personal_snippet).permit(:title, :content, :file_name, :private, :visibility_level, :description) + end + + def move_temporary_files + params[:files].each do |file| + FileMover.new(file, @snippet).execute + end end end diff --git a/app/controllers/unicorn_test_controller.rb b/app/controllers/unicorn_test_controller.rb new file mode 100644 index 00000000000..ed04bd1f77d --- /dev/null +++ b/app/controllers/unicorn_test_controller.rb @@ -0,0 +1,14 @@ +# :nocov: +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 +# :nocov: diff --git a/app/controllers/uploads_controller.rb b/app/controllers/uploads_controller.rb index f1bfd574f04..16a74f82d3f 100644 --- a/app/controllers/uploads_controller.rb +++ b/app/controllers/uploads_controller.rb @@ -1,50 +1,51 @@ class UploadsController < ApplicationController - skip_before_action :authenticate_user! - before_action :find_model, :authorize_access! - - def show - uploader = @model.send(upload_mount) + include UploadsActions - unless uploader.file_storage? - return redirect_to uploader.url - end - - 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 nil unless params[:id] + + return render_404 unless upload_model && upload_mount @model = upload_model.find(params[:id]) end def authorize_access! + return nil unless model + 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! + return nil unless model + # 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 +59,49 @@ 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) + + case model + when nil + @uploader = PersonalFileUploader.new(nil, params[:secret]) + + @uploader.retrieve_from_store!(params[:filename]) + when PersonalSnippet + @uploader = PersonalFileUploader.new(model, params[:secret]) + + @uploader.retrieve_from_store!(params[:filename]) + else + @uploader = @model.public_send(upload_mount) # rubocop:disable GitlabSecurity/PublicSend + + 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 a452bbba422..4ee855806ab 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| @@ -9,7 +10,7 @@ class UsersController < ApplicationController format.atom do load_events - render layout: false + render layout: 'xml.atom' end format.json do @@ -72,10 +73,7 @@ class UsersController < ApplicationController end def calendar - calendar = contributions_calendar - @activity_dates = calendar.activity_dates - - render 'calendar', layout: false + render json: contributions_calendar.activity_dates end def calendar_activities @@ -91,12 +89,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 @@ -109,11 +103,11 @@ class UsersController < ApplicationController def load_events # Get user activity feed for projects common for both users - @events = user.recent_events. - merge(projects_for_current_user). - references(:project). - with_associations. - limit_recent(20, params[:offset]) + @events = user.recent_events + .merge(projects_for_current_user) + .references(:project) + .with_associations + .limit_recent(20, params[:offset]) end def load_projects @@ -131,15 +125,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(current_user: current_user).execute end + + def build_canonical_path(user) + url_for(params.merge(username: user.to_param)) + end end |