application_controller.rb 12 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 63
    user_token = if params[:authenticity_token].presence
                   params[:authenticity_token].presence
                 elsif params[:private_token].presence
                   params[:private_token].presence
                 end
64 65 66 67 68 69 70 71 72 73 74
    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

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

80
    super(*args)
81 82
  end

Riyad Preukschas's avatar
Riyad Preukschas committed
83 84 85 86 87 88
  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

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

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

107
  def after_sign_out_path_for(resource)
108
    current_application_settings.after_sign_out_path || new_user_session_path
109 110
  end

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

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

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

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

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

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

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

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

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

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

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

176 177
  def render_403
    head :forbidden
gitlabhq's avatar
gitlabhq committed
178
  end
gitlabhq's avatar
gitlabhq committed
179

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

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

188 189 190 191 192
  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
193

194 195 196
  def default_headers
    headers['X-Frame-Options'] = 'DENY'
    headers['X-XSS-Protection'] = '1; mode=block'
xyb's avatar
xyb committed
197
    headers['X-UA-Compatible'] = 'IE=edge'
198
    headers['X-Content-Type-Options'] = 'nosniff'
199 200 201 202
    # 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
203
  end
204 205

  def add_gon_variables
206 207
    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
208
    gon.default_issues_tracker = Project.new.default_issue_tracker.to_param
209 210 211
    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
212 213 214 215 216

    if current_user
      gon.current_user_id = current_user.id
      gon.api_token = current_user.private_token
    end
217
  end
218

tduehr's avatar
tduehr committed
219 220 221 222 223 224 225 226 227 228 229 230 231 232
  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

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

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

245
  def ldap_security_check
246
    if current_user && current_user.requires_ldap_check?
247 248 249 250
      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
251 252 253 254
      end
    end
  end

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

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

264 265 266 267 268 269 270 271 272 273 274 275 276
  # 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
277 278 279 280 281 282 283 284

  def view_to_html_string(partial)
    render_to_string(
      partial,
      layout: false,
      formats: [:html]
    )
  end
Dmitriy Zaporozhets's avatar
Dmitriy Zaporozhets committed
285 286

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

  def hexdigest(string)
    Digest::SHA1.hexdigest string
  end
293 294 295 296 297 298

  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
299

300
  def set_filters_params
301
    params[:sort] ||= 'id_desc'
302 303 304
    params[:scope] = 'all' if params[:scope].blank?
    params[:state] = 'opened' if params[:state].blank?

305
    @sort = params[:sort]
306
    @filter_params = params.dup
307 308

    if @project
309
      @filter_params[:project_id] = @project.id
310
    elsif @group
311
      @filter_params[:group_id] = @group.id
312
    else
313 314 315 316 317
      # 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
318
    end
319 320

    @filter_params
321 322
  end

323 324
  def get_issues_collection
    set_filters_params
325 326
    @issuable_finder = IssuesFinder.new(current_user, @filter_params)
    @issuable_finder.execute
327 328 329 330
  end

  def get_merge_requests_collection
    set_filters_params
331 332
    @issuable_finder = MergeRequestsFinder.new(current_user, @filter_params)
    @issuable_finder.execute
333
  end
Douwe Maan's avatar
Douwe Maan committed
334

335 336 337 338
  def import_sources_enabled?
    !current_application_settings.import_sources.empty?
  end

Douwe Maan's avatar
Douwe Maan committed
339
  def github_import_enabled?
340 341 342 343
    current_application_settings.import_sources.include?('github')
  end

  def github_import_configured?
344
    Gitlab::OAuth::Provider.enabled?(:github)
Douwe Maan's avatar
Douwe Maan committed
345 346 347
  end

  def gitlab_import_enabled?
348 349 350 351
    request.host != 'gitlab.com' && current_application_settings.import_sources.include?('gitlab')
  end

  def gitlab_import_configured?
352
    Gitlab::OAuth::Provider.enabled?(:gitlab)
Douwe Maan's avatar
Douwe Maan committed
353 354 355
  end

  def bitbucket_import_enabled?
356 357 358 359
    current_application_settings.import_sources.include?('bitbucket')
  end

  def bitbucket_import_configured?
360
    Gitlab::OAuth::Provider.enabled?(:bitbucket) && Gitlab::BitbucketImport.public_key.present?
Douwe Maan's avatar
Douwe Maan committed
361
  end
362 363 364 365 366 367 368 369 370

  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
371 372 373 374
  def fogbugz_import_enabled?
    current_application_settings.import_sources.include?('fogbugz')
  end

375 376 377
  def git_import_enabled?
    current_application_settings.import_sources.include?('git')
  end
378

379 380 381 382
  def two_factor_authentication_required?
    current_application_settings.require_two_factor_authentication
  end

383 384 385 386
  def two_factor_grace_period
    current_application_settings.two_factor_grace_period
  end

387 388
  def two_factor_grace_period_expired?
    date = current_user.otp_grace_period_started_at
389 390 391 392 393 394 395
    date && (date + two_factor_grace_period.hours) < Time.current
  end

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

396 397 398 399 400 401 402 403 404 405 406 407
  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
gitlabhq's avatar
gitlabhq committed
408
end