application_controller.rb 12.8 KB
Newer Older
1
require 'gon'
Jared Szechy's avatar
Jared Szechy committed
2
require 'fogbugz'
3

4
class ApplicationController < ActionController::Base
5
  include Gitlab::CurrentSettings
6 7
  include GitlabRoutingHelper
  include PageLayoutHelper
8

9 10
  PER_PAGE = 20

11 12
  before_action :authenticate_user_from_token!
  before_action :authenticate_user!
tduehr's avatar
tduehr committed
13
  before_action :validate_user_service_ticket!
14 15
  before_action :reject_blocked!
  before_action :check_password_expiration
16
  before_action :check_2fa_requirement
17
  before_action :ldap_security_check
Douwe Maan's avatar
Douwe Maan committed
18
  before_action :sentry_user_context
19 20 21 22
  before_action :default_headers
  before_action :add_gon_variables
  before_action :configure_permitted_parameters, if: :devise_controller?
  before_action :require_email, unless: :devise_controller?
23

24
  protect_from_forgery with: :exception
25

26
  helper_method :abilities, :can?, :current_application_settings
Jared Szechy's avatar
Jared Szechy committed
27
  helper_method :import_sources_enabled?, :github_import_enabled?, :github_import_configured?, :gitlab_import_enabled?, :gitlab_import_configured?, :bitbucket_import_enabled?, :bitbucket_import_configured?, :gitorious_import_enabled?, :google_code_import_enabled?, :fogbugz_import_enabled?, :git_import_enabled?
28
  helper_method :repository
gitlabhq's avatar
gitlabhq committed
29

30
  rescue_from Encoding::CompatibilityError do |exception|
Riyad Preukschas's avatar
Riyad Preukschas committed
31
    log_exception(exception)
Cyril's avatar
Cyril committed
32
    render "errors/encoding", layout: "errors", status: 500
33 34
  end

35
  rescue_from ActiveRecord::RecordNotFound do |exception|
Riyad Preukschas's avatar
Riyad Preukschas committed
36
    log_exception(exception)
37
    render_404
gitlabhq's avatar
gitlabhq committed
38 39
  end

40 41 42 43
  def redirect_back_or_default(default: root_path, options: {})
    redirect_to request.referer.present? ? :back : default, options
  end

Nihad Abbasov's avatar
Nihad Abbasov committed
44
  protected
gitlabhq's avatar
gitlabhq committed
45

Douwe Maan's avatar
Douwe Maan committed
46 47 48 49 50 51 52 53 54 55
  def sentry_user_context
    if Rails.env.production? && current_application_settings.sentry_enabled && current_user
      Raven.user_context(
        id: current_user.id,
        email: current_user.email,
        username: current_user.username,
      )
    end
  end

56
  # From https://github.com/plataformatec/devise/wiki/How-To:-Simple-Token-Authentication-Example
57
  # https://gist.github.com/josevalim/fb706b1e933ef01e4fb6
58
  def authenticate_user_from_token!
59 60 61 62
    user_token = if params[:authenticity_token].presence
                   params[:authenticity_token].presence
                 elsif params[:private_token].presence
                   params[:private_token].presence
63 64
                 elsif request.headers['PRIVATE-TOKEN'].present?
                   request.headers['PRIVATE-TOKEN']
65
                 end
66 67 68 69 70 71 72 73 74 75 76
    user = user_token && User.find_by_authentication_token(user_token.to_s)

    if user
      # 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

77
  def authenticate_user!(*args)
78 79
    if redirect_to_home_page_url?
      redirect_to current_application_settings.home_page_url and return
80 81
    end

82
    super(*args)
83 84
  end

Riyad Preukschas's avatar
Riyad Preukschas committed
85 86 87 88 89 90
  def log_exception(exception)
    application_trace = ActionDispatch::ExceptionWrapper.new(env, exception).application_trace
    application_trace.map!{ |t| "  #{t}\n" }
    logger.error "\n#{exception.class.name} (#{exception.message}):\n#{application_trace.join}"
  end

91
  def reject_blocked!
92
    if current_user && current_user.blocked?
93
      sign_out current_user
94
      flash[:alert] = "Your account is blocked. Retry when an admin has unblocked it."
95 96 97 98
      redirect_to new_user_session_path
    end
  end

99
  def after_sign_in_path_for(resource)
100
    if resource.is_a?(User) && resource.respond_to?(:blocked?) && resource.blocked?
randx's avatar
randx committed
101
      sign_out resource
102
      flash[:alert] = "Your account is blocked. Retry when an admin has unblocked it."
randx's avatar
randx committed
103 104
      new_user_session_path
    else
105
      stored_location_for(:redirect) || stored_location_for(resource) || root_path
randx's avatar
randx committed
106 107 108
    end
  end

109
  def after_sign_out_path_for(resource)
110
    current_application_settings.after_sign_out_path || new_user_session_path
111 112
  end

gitlabhq's avatar
gitlabhq committed
113
  def abilities
Ciro Santilli's avatar
Ciro Santilli committed
114
    Ability.abilities
gitlabhq's avatar
gitlabhq committed
115 116 117 118 119 120
  end

  def can?(object, action, subject)
    abilities.allowed?(object, action, subject)
  end

Nihad Abbasov's avatar
Nihad Abbasov committed
121
  def project
122
    unless @project
Vinnie Okada's avatar
Vinnie Okada committed
123
      namespace = params[:namespace_id]
124 125 126 127 128 129 130 131
      id = params[:project_id] || params[:id]

      # Redirect from
      #   localhost/group/project.git
      # to
      #   localhost/group/project
      #
      if id =~ /\.git\Z/
132
        redirect_to request.original_url.gsub(/\.git\/?\Z/, '') and return
133 134
      end

135 136 137
      project_path = "#{namespace}/#{id}"
      @project = Project.find_with_namespace(project_path)

138
      if @project and can?(current_user, :read_project, @project)
139 140 141
        if @project.path_with_namespace != project_path
          redirect_to request.original_url.gsub(project_path, @project.path_with_namespace) and return
        end
142 143 144 145 146 147 148 149
        @project
      elsif current_user.nil?
        @project = nil
        authenticate_user!
      else
        @project = nil
        render_404 and return
      end
150
    end
151
    @project
gitlabhq's avatar
gitlabhq committed
152 153
  end

Dmitriy Zaporozhets's avatar
Dmitriy Zaporozhets committed
154 155 156 157
  def repository
    @repository ||= project.repository
  end

gitlabhq's avatar
gitlabhq committed
158
  def authorize_project!(action)
159
    return access_denied! unless can?(current_user, action, project)
gitlabhq's avatar
gitlabhq committed
160 161 162
  end

  def access_denied!
Cyril's avatar
Cyril committed
163
    render "errors/access_denied", layout: "errors", status: 404
164 165 166
  end

  def git_not_found!
167
    render "errors/git_not_found.html", layout: "errors", status: 404
gitlabhq's avatar
gitlabhq committed
168 169 170
  end

  def method_missing(method_sym, *arguments, &block)
171
    if method_sym.to_s =~ /\Aauthorize_(.*)!\z/
gitlabhq's avatar
gitlabhq committed
172 173 174 175 176
      authorize_project!($1.to_sym)
    else
      super
    end
  end
gitlabhq's avatar
gitlabhq committed
177

178 179
  def render_403
    head :forbidden
gitlabhq's avatar
gitlabhq committed
180
  end
gitlabhq's avatar
gitlabhq committed
181

182 183
  def render_404
    render file: Rails.root.join("public", "404"), layout: false, status: "404"
184 185
  end

gitlabhq's avatar
gitlabhq committed
186
  def require_non_empty_project
187
    redirect_to @project if @project.empty_repo?
gitlabhq's avatar
gitlabhq committed
188
  end
189

190 191 192 193 194
  def no_cache_headers
    response.headers["Cache-Control"] = "no-cache, no-store, max-age=0, must-revalidate"
    response.headers["Pragma"] = "no-cache"
    response.headers["Expires"] = "Fri, 01 Jan 1990 00:00:00 GMT"
  end
195

196 197 198
  def default_headers
    headers['X-Frame-Options'] = 'DENY'
    headers['X-XSS-Protection'] = '1; mode=block'
xyb's avatar
xyb committed
199
    headers['X-UA-Compatible'] = 'IE=edge'
200
    headers['X-Content-Type-Options'] = 'nosniff'
201 202 203 204
    # Enabling HSTS for non-standard ports would send clients to the wrong port
    if Gitlab.config.gitlab.https and Gitlab.config.gitlab.port == 443
      headers['Strict-Transport-Security'] = 'max-age=31536000'
    end
205
  end
206 207

  def add_gon_variables
208 209
    gon.api_version            = API::API.version
    gon.default_avatar_url     = URI::join(Gitlab.config.gitlab.url, ActionController::Base.helpers.image_path('no_avatar.png')).to_s
210
    gon.default_issues_tracker = Project.new.default_issue_tracker.to_param
211 212 213
    gon.max_file_size          = current_application_settings.max_attachment_size
    gon.relative_url_root      = Gitlab.config.gitlab.relative_url_root
    gon.user_color_scheme      = Gitlab::ColorSchemes.for_user(current_user).css_class
214 215 216 217 218

    if current_user
      gon.current_user_id = current_user.id
      gon.api_token = current_user.private_token
    end
219
  end
220

tduehr's avatar
tduehr committed
221 222 223 224 225 226 227 228 229 230 231 232 233 234
  def validate_user_service_ticket!
    return unless signed_in? && session[:service_tickets]

    valid = session[:service_tickets].all? do |provider, ticket|
      Gitlab::OAuth::Session.valid?(provider, ticket)
    end

    unless valid
      session[:service_tickets] = nil
      sign_out current_user
      redirect_to new_user_session_path
    end
  end

235
  def check_password_expiration
236
    if current_user && current_user.password_expires_at && current_user.password_expires_at < Time.now  && !current_user.ldap_user?
237 238 239
      redirect_to new_profile_password_path and return
    end
  end
240

241
  def check_2fa_requirement
242
    if two_factor_authentication_required? && current_user && !current_user.two_factor_enabled && !skip_two_factor?
243
      redirect_to new_profile_two_factor_auth_path
244 245 246
    end
  end

247
  def ldap_security_check
248
    if current_user && current_user.requires_ldap_check?
249 250 251 252
      unless Gitlab::LDAP::Access.allowed?(current_user)
        sign_out current_user
        flash[:alert] = "Access denied for your LDAP account."
        redirect_to new_user_session_path
253 254 255 256
      end
    end
  end

257 258 259 260
  def event_filter
    filters = cookies['event_filter'].split(',') if cookies['event_filter'].present?
    @event_filter ||= EventFilter.new(filters)
  end
261

262 263
  def gitlab_ldap_access(&block)
    Gitlab::LDAP::Access.open { |access| block.call(access) }
264 265
  end

266 267 268 269 270 271 272 273 274 275 276 277 278
  # JSON for infinite scroll via Pager object
  def pager_json(partial, count)
    html = render_to_string(
      partial,
      layout: false,
      formats: [:html]
    )

    render json: {
      html: html,
      count: count
    }
  end
279

Josh Frye's avatar
Josh Frye committed
280
  def view_to_html_string(partial, locals = {})
281
    render_to_string(
Josh Frye's avatar
Josh Frye committed
282
      partial,
283
      locals: locals,
284 285 286 287
      layout: false,
      formats: [:html]
    )
  end
Dmitriy Zaporozhets's avatar
Dmitriy Zaporozhets committed
288 289

  def configure_permitted_parameters
290
    devise_parameter_sanitizer.for(:sign_in) { |u| u.permit(:username, :email, :password, :login, :remember_me, :otp_attempt) }
Dmitriy Zaporozhets's avatar
Dmitriy Zaporozhets committed
291
  end
292 293 294 295

  def hexdigest(string)
    Digest::SHA1.hexdigest string
  end
296 297 298 299 300 301

  def require_email
    if current_user && current_user.temp_oauth_email?
      redirect_to profile_path, notice: 'Please complete your profile with email address' and return
    end
  end
302

303
  def set_filters_params
304 305
    set_default_sort

306 307 308
    params[:scope] = 'all' if params[:scope].blank?
    params[:state] = 'opened' if params[:state].blank?

309
    @sort = params[:sort]
310
    @filter_params = params.dup
311 312

    if @project
313
      @filter_params[:project_id] = @project.id
314
    elsif @group
315
      @filter_params[:group_id] = @group.id
316
    else
317 318 319 320 321
      # TODO: this filter ignore issues/mr created in public or
      # internal repos where you are not a member. Enable this filter
      # or improve current implementation to filter only issues you
      # created or assigned or mentioned
      #@filter_params[:authorized_only] = true
322
    end
323 324

    @filter_params
325 326
  end

327 328
  def get_issues_collection
    set_filters_params
329 330
    @issuable_finder = IssuesFinder.new(current_user, @filter_params)
    @issuable_finder.execute
331 332 333 334
  end

  def get_merge_requests_collection
    set_filters_params
335 336
    @issuable_finder = MergeRequestsFinder.new(current_user, @filter_params)
    @issuable_finder.execute
337
  end
Douwe Maan's avatar
Douwe Maan committed
338

339 340 341 342
  def import_sources_enabled?
    !current_application_settings.import_sources.empty?
  end

Douwe Maan's avatar
Douwe Maan committed
343
  def github_import_enabled?
344 345 346 347
    current_application_settings.import_sources.include?('github')
  end

  def github_import_configured?
348
    Gitlab::OAuth::Provider.enabled?(:github)
Douwe Maan's avatar
Douwe Maan committed
349 350 351
  end

  def gitlab_import_enabled?
352 353 354 355
    request.host != 'gitlab.com' && current_application_settings.import_sources.include?('gitlab')
  end

  def gitlab_import_configured?
356
    Gitlab::OAuth::Provider.enabled?(:gitlab)
Douwe Maan's avatar
Douwe Maan committed
357 358 359
  end

  def bitbucket_import_enabled?
360 361 362 363
    current_application_settings.import_sources.include?('bitbucket')
  end

  def bitbucket_import_configured?
364
    Gitlab::OAuth::Provider.enabled?(:bitbucket) && Gitlab::BitbucketImport.public_key.present?
Douwe Maan's avatar
Douwe Maan committed
365
  end
366 367 368 369 370 371 372 373 374

  def gitorious_import_enabled?
    current_application_settings.import_sources.include?('gitorious')
  end

  def google_code_import_enabled?
    current_application_settings.import_sources.include?('google_code')
  end

Jared Szechy's avatar
Jared Szechy committed
375 376 377 378
  def fogbugz_import_enabled?
    current_application_settings.import_sources.include?('fogbugz')
  end

379 380 381
  def git_import_enabled?
    current_application_settings.import_sources.include?('git')
  end
382

383 384 385 386
  def two_factor_authentication_required?
    current_application_settings.require_two_factor_authentication
  end

387 388 389 390
  def two_factor_grace_period
    current_application_settings.two_factor_grace_period
  end

391 392
  def two_factor_grace_period_expired?
    date = current_user.otp_grace_period_started_at
393 394 395 396 397 398 399
    date && (date + two_factor_grace_period.hours) < Time.current
  end

  def skip_two_factor?
    session[:skip_tfa] && session[:skip_tfa] > Time.current
  end

400 401 402 403 404 405 406 407 408 409 410 411
  def redirect_to_home_page_url?
    # If user is not signed-in and tries to access root_path - redirect him to landing page
    # Don't redirect to the default URL to prevent endless redirections
    return false unless current_application_settings.home_page_url.present?

    home_page_url = current_application_settings.home_page_url.chomp('/')
    root_urls = [Gitlab.config.gitlab['url'].chomp('/'), root_url.chomp('/')]

    return false if root_urls.include?(home_page_url)

    current_user.nil? && root_path == request.path
  end
412 413 414 415

  private

  def set_default_sort
416 417
    key = if is_a_listing_page_for?('issues') || is_a_listing_page_for?('merge_requests')
            'issuable_sort'
418
          end
419

420 421 422 423 424 425 426
    cookies[key]  = params[:sort] if key && params[:sort].present?
    params[:sort] = cookies[key] if key
    params[:sort] ||= 'id_desc'
  end

  def is_a_listing_page_for?(page_type)
    controller_name, action_name = params.values_at(:controller, :action)
427

428 429 430
    (controller_name == "projects/#{page_type}" && action_name == 'index') ||
    (controller_name == 'groups' && action_name == page_type) ||
    (controller_name == 'dashboard' && action_name == page_type)
431
  end
gitlabhq's avatar
gitlabhq committed
432
end