2018-12-05 23:21:45 +05:30
# frozen_string_literal: true
2014-09-02 18:07:02 +05:30
require 'gon'
2015-09-25 12:07:36 +05:30
require 'fogbugz'
2014-09-02 18:07:02 +05:30
class ApplicationController < ActionController :: Base
2016-06-02 11:05:42 +05:30
include Gitlab :: GonHelper
2015-04-26 12:48:37 +05:30
include GitlabRoutingHelper
2015-09-11 14:41:01 +05:30
include PageLayoutHelper
2018-06-27 16:04:02 +05:30
include SafeParamsHelper
2016-06-16 23:09:34 +05:30
include WorkhorseHelper
2017-08-17 22:00:37 +05:30
include EnforcesTwoFactorAuthentication
2017-09-10 17:25:29 +05:30
include WithPerformanceBar
2018-11-29 20:51:05 +05:30
include SessionlessAuthentication
2015-04-26 12:48:37 +05:30
2015-09-11 14:41:01 +05:30
before_action :authenticate_user!
2018-10-15 14:42:47 +05:30
before_action :enforce_terms! , if : :should_enforce_terms?
2016-01-14 18:37:52 +05:30
before_action :validate_user_service_ticket!
2015-09-11 14:41:01 +05:30
before_action :check_password_expiration
before_action :ldap_security_check
2016-06-02 11:05:42 +05:30
before_action :sentry_context
2015-09-11 14:41:01 +05:30
before_action :default_headers
2018-11-18 11:00:15 +05:30
before_action :add_gon_variables , unless : [ :peek_request? , :json_request? ]
2015-09-11 14:41:01 +05:30
before_action :configure_permitted_parameters , if : :devise_controller?
before_action :require_email , unless : :devise_controller?
2018-11-20 20:47:30 +05:30
before_action :set_usage_stats_consent_flag
2019-02-15 15:39:39 +05:30
before_action :check_impersonation_availability
2014-09-02 18:07:02 +05:30
2017-08-17 22:00:37 +05:30
around_action :set_locale
2018-11-18 11:00:15 +05:30
after_action :set_page_title_header , if : :json_request?
after_action :limit_unauthenticated_session_times
2018-03-17 18:26:18 +05:30
2018-11-08 19:23:39 +05:30
protect_from_forgery with : :exception , prepend : true
2014-09-02 18:07:02 +05:30
2018-03-17 18:26:18 +05:30
helper_method :can?
2018-11-18 11:00:15 +05:30
helper_method :import_sources_enabled? , :github_import_enabled? ,
:gitea_import_enabled? , :github_import_configured? ,
:gitlab_import_enabled? , :gitlab_import_configured? ,
:bitbucket_import_enabled? , :bitbucket_import_configured? ,
:bitbucket_server_import_enabled? ,
:google_code_import_enabled? , :fogbugz_import_enabled? ,
:git_import_enabled? , :gitlab_project_import_enabled? ,
:manifest_import_enabled?
DEFAULT_GITLAB_CACHE_CONTROL = " #{ ActionDispatch :: Http :: Cache :: Response :: DEFAULT_CACHE_CONTROL } , no-store " . freeze
2014-09-02 18:07:02 +05:30
rescue_from Encoding :: CompatibilityError do | exception |
log_exception ( exception )
render " errors/encoding " , layout : " errors " , status : 500
end
rescue_from ActiveRecord :: RecordNotFound do | exception |
log_exception ( exception )
2015-10-24 18:46:33 +05:30
render_404
end
2017-09-10 17:25:29 +05:30
rescue_from ( ActionController :: UnknownFormat ) do
render_404
end
2016-08-24 12:49:21 +05:30
rescue_from Gitlab :: Access :: AccessDeniedError do | exception |
render_403
end
2017-08-17 22:00:37 +05:30
rescue_from Gitlab :: Auth :: TooManyIps do | e |
head :forbidden , retry_after : Gitlab :: Auth :: UniqueIpsLimiter . config . unique_ips_limit_time_window
end
2018-12-13 13:39:08 +05:30
rescue_from GRPC :: Unavailable , Gitlab :: Git :: CommandError do | exception |
2017-09-10 17:25:29 +05:30
log_exception ( exception )
headers [ 'Retry-After' ] = exception . retry_after if exception . respond_to? ( :retry_after )
render_503
end
2015-10-24 18:46:33 +05:30
def redirect_back_or_default ( default : root_path , options : { } )
2019-02-15 15:39:39 +05:30
redirect_back ( fallback_location : default , ** options )
2014-09-02 18:07:02 +05:30
end
2016-11-03 12:29:30 +05:30
def not_found
render_404
end
2017-08-17 22:00:37 +05:30
def route_not_found
if current_user
not_found
else
authenticate_user!
end
end
2018-11-18 11:00:15 +05:30
# By default, all sessions are given the same expiration time configured in
# the session store (e.g. 1 week). However, unauthenticated users can
# generate a lot of sessions, primarily for CSRF verification. It makes
# sense to reduce the TTL for unauthenticated to something much lower than
# the default (e.g. 1 hour) to limit Redis memory. In addition, Rails
# creates a new session after login, so the short TTL doesn't even need to
# be extended.
def limit_unauthenticated_session_times
return if current_user
# Rack sets this header, but not all tests may have it: https://github.com/rack/rack/blob/fdcd03a3c5a1c51d1f96fc97f9dfa1a9deac0c77/lib/rack/session/abstract/id.rb#L251-L259
return unless request . env [ 'rack.session.options' ]
# This works because Rack uses these options every time a request is handled:
# https://github.com/rack/rack/blob/fdcd03a3c5a1c51d1f96fc97f9dfa1a9deac0c77/lib/rack/session/abstract/id.rb#L342
request . env [ 'rack.session.options' ] [ :expire_after ] = Settings . gitlab [ 'unauthenticated_session_expire_delay' ]
end
2018-12-05 23:21:45 +05:30
def render ( * args )
super . tap do
# Set a header for custom error pages to prevent them from being intercepted by gitlab-workhorse
if response . content_type == 'text/html' && ( 400 .. 599 ) . cover? ( response . status )
response . headers [ 'X-GitLab-Custom-Error' ] = '1'
end
end
end
2014-09-02 18:07:02 +05:30
protected
2017-09-10 17:25:29 +05:30
def append_info_to_payload ( payload )
super
2018-11-18 11:00:15 +05:30
2018-11-20 20:47:30 +05:30
payload [ :ua ] = request . env [ " HTTP_USER_AGENT " ]
2017-09-10 17:25:29 +05:30
payload [ :remote_ip ] = request . remote_ip
2019-02-15 15:39:39 +05:30
payload [ Gitlab :: CorrelationId :: LOG_KEY ] = Gitlab :: CorrelationId . current_id
2017-09-10 17:25:29 +05:30
2018-03-17 18:26:18 +05:30
logged_user = auth_user
if logged_user . present?
payload [ :user_id ] = logged_user . try ( :id )
payload [ :username ] = logged_user . try ( :username )
2017-09-10 17:25:29 +05:30
end
2018-11-08 19:23:39 +05:30
if response . status == 422 && response . body . present? && response . content_type == 'application/json' . freeze
payload [ :response ] = response . body
end
2017-09-10 17:25:29 +05:30
end
2018-11-18 11:00:15 +05:30
##
2018-03-17 18:26:18 +05:30
# Controllers such as GitHttpController may use alternative methods
2018-11-18 11:00:15 +05:30
# (e.g. tokens) to authenticate the user, whereas Devise sets current_user.
#
2018-03-17 18:26:18 +05:30
def auth_user
2018-11-18 11:00:15 +05:30
if user_signed_in?
current_user
else
try ( :authenticated_user )
end
2017-09-10 17:25:29 +05:30
end
2014-09-02 18:07:02 +05:30
def log_exception ( exception )
2019-02-15 15:39:39 +05:30
Gitlab :: Sentry . track_acceptable_exception ( exception )
2017-09-10 17:25:29 +05:30
2019-02-15 15:39:39 +05:30
backtrace_cleaner = request . env [ " action_dispatch.backtrace_cleaner " ]
2018-10-15 14:42:47 +05:30
application_trace = ActionDispatch :: ExceptionWrapper . new ( backtrace_cleaner , exception ) . application_trace
2018-03-17 18:26:18 +05:30
application_trace . map! { | t | " #{ t } \n " }
2014-09-02 18:07:02 +05:30
logger . error " \n #{ exception . class . name } ( #{ exception . message } ): \n #{ application_trace . join } "
end
2015-04-26 12:48:37 +05:30
def after_sign_in_path_for ( resource )
2017-08-17 22:00:37 +05:30
stored_location_for ( :redirect ) || stored_location_for ( resource ) || root_path
2014-09-02 18:07:02 +05:30
end
2015-09-11 14:41:01 +05:30
def after_sign_out_path_for ( resource )
2018-03-17 18:26:18 +05:30
Gitlab :: CurrentSettings . after_sign_out_path . presence || new_user_session_path
2015-09-11 14:41:01 +05:30
end
2017-08-17 22:00:37 +05:30
def can? ( object , action , subject = :global )
2016-09-29 09:46:39 +05:30
Ability . allowed? ( object , action , subject )
2014-09-02 18:07:02 +05:30
end
2019-02-15 15:39:39 +05:30
def access_denied! ( message = nil , status = nil )
2018-10-15 14:42:47 +05:30
# If we display a custom access denied message to the user, we don't want to
# hide existence of the resource, rather tell them they cannot access it using
# the provided message
2019-02-15 15:39:39 +05:30
status || = message . present? ? :forbidden : :not_found
2019-03-02 22:35:43 +05:30
template =
if status == :not_found
" errors/not_found "
else
" errors/access_denied "
end
2018-10-15 14:42:47 +05:30
2017-08-17 22:00:37 +05:30
respond_to do | format |
2018-10-15 14:42:47 +05:30
format . any { head status }
2018-03-27 19:54:05 +05:30
format . html do
2019-03-02 22:35:43 +05:30
render template ,
2018-03-27 19:54:05 +05:30
layout : " errors " ,
2018-10-15 14:42:47 +05:30
status : status ,
2018-03-27 19:54:05 +05:30
locals : { message : message }
end
2017-08-17 22:00:37 +05:30
end
2014-09-02 18:07:02 +05:30
end
def git_not_found!
2016-04-02 18:10:28 +05:30
render " errors/git_not_found.html " , layout : " errors " , status : 404
2014-09-02 18:07:02 +05:30
end
def render_403
2018-11-08 19:23:39 +05:30
respond_to do | format |
format . any { head :forbidden }
format . html { render " errors/access_denied " , layout : " errors " , status : 403 }
end
2014-09-02 18:07:02 +05:30
end
def render_404
2016-11-03 12:29:30 +05:30
respond_to do | format |
2018-11-08 19:23:39 +05:30
format . html { render " errors/not_found " , layout : " errors " , status : 404 }
2018-03-17 18:26:18 +05:30
# Prevent the Rails CSRF protector from thinking a missing .js file is a JavaScript file
format . js { render json : '' , status : :not_found , content_type : 'application/json' }
2016-11-03 12:29:30 +05:30
format . any { head :not_found }
end
2014-09-02 18:07:02 +05:30
end
2017-08-17 22:00:37 +05:30
def respond_422
head :unprocessable_entity
end
2017-09-10 17:25:29 +05:30
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
2014-09-02 18:07:02 +05:30
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
def default_headers
headers [ 'X-Frame-Options' ] = 'DENY'
headers [ 'X-XSS-Protection' ] = '1; mode=block'
headers [ 'X-UA-Compatible' ] = 'IE=edge'
headers [ 'X-Content-Type-Options' ] = 'nosniff'
2018-11-18 11:00:15 +05:30
if current_user
# Adds `no-store` to the DEFAULT_CACHE_CONTROL, to prevent security
# concerns due to caching private data.
headers [ 'Cache-Control' ] = DEFAULT_GITLAB_CACHE_CONTROL
headers [ " Pragma " ] = " no-cache " # HTTP 1.0 compatibility
end
2014-09-02 18:07:02 +05:30
end
2016-01-14 18:37:52 +05:30
def validate_user_service_ticket!
return unless signed_in? && session [ :service_tickets ]
valid = session [ :service_tickets ] . all? do | provider , ticket |
2018-03-27 19:54:05 +05:30
Gitlab :: Auth :: OAuth :: Session . valid? ( provider , ticket )
2016-01-14 18:37:52 +05:30
end
unless valid
session [ :service_tickets ] = nil
sign_out current_user
redirect_to new_user_session_path
end
end
2014-09-02 18:07:02 +05:30
def check_password_expiration
2018-03-17 18:26:18 +05:30
return if session [ :impersonator_id ] || ! current_user & . allow_password_authentication?
password_expires_at = current_user & . password_expires_at
if password_expires_at && password_expires_at < Time . now
2017-08-17 22:00:37 +05:30
return redirect_to new_profile_password_path
2016-01-14 18:37:52 +05:30
end
end
2014-09-02 18:07:02 +05:30
def ldap_security_check
if current_user && current_user . requires_ldap_check?
2016-04-02 18:10:28 +05:30
return unless current_user . try_obtain_ldap_lease
2018-03-27 19:54:05 +05:30
unless Gitlab :: Auth :: LDAP :: Access . allowed? ( current_user )
2014-09-02 18:07:02 +05:30
sign_out current_user
flash [ :alert ] = " Access denied for your LDAP account. "
redirect_to new_user_session_path
end
end
end
def event_filter
2018-12-05 23:21:45 +05:30
@event_filter || =
EventFilter . new ( params [ :event_filter ] . presence || cookies [ :event_filter ] ) . tap do | new_event_filter |
cookies [ :event_filter ] = new_event_filter . filter
end
2014-09-02 18:07:02 +05:30
end
# JSON for infinite scroll via Pager object
2016-11-24 13:41:30 +05:30
def pager_json ( partial , count , locals = { } )
2014-09-02 18:07:02 +05:30
html = render_to_string (
partial ,
2016-11-24 13:41:30 +05:30
locals : locals ,
2014-09-02 18:07:02 +05:30
layout : false ,
formats : [ :html ]
)
render json : {
html : html ,
count : count
}
end
2016-04-02 18:10:28 +05:30
def view_to_html_string ( partial , locals = { } )
2014-09-02 18:07:02 +05:30
render_to_string (
partial ,
2016-04-02 18:10:28 +05:30
locals : locals ,
2014-09-02 18:07:02 +05:30
layout : false ,
formats : [ :html ]
)
end
def configure_permitted_parameters
2016-06-16 23:09:34 +05:30
devise_parameter_sanitizer . permit ( :sign_in , keys : [ :username , :email , :password , :login , :remember_me , :otp_attempt ] )
2014-09-02 18:07:02 +05:30
end
def hexdigest ( string )
Digest :: SHA1 . hexdigest string
end
def require_email
2017-08-17 22:00:37 +05:30
if current_user && current_user . temp_oauth_email? && session [ :impersonator_id ] . nil?
return redirect_to profile_path , notice : 'Please complete your profile with email address'
2014-09-02 18:07:02 +05:30
end
end
2015-04-26 12:48:37 +05:30
2018-10-15 14:42:47 +05:30
def enforce_terms!
return unless current_user
return if current_user . terms_accepted?
2018-11-08 19:23:39 +05:30
message = _ ( " Please accept the Terms of Service before continuing. " )
2018-10-15 14:42:47 +05:30
if sessionless_user?
2018-11-08 19:23:39 +05:30
access_denied! ( message )
2018-10-15 14:42:47 +05:30
else
# Redirect to the destination if the request is a get.
# Redirect to the source if it was a post, so the user can re-submit after
# accepting the terms.
redirect_path = if request . get?
request . fullpath
else
URI ( request . referer ) . path if request . referer
end
2018-11-08 19:23:39 +05:30
flash [ :notice ] = message
2018-10-15 14:42:47 +05:30
redirect_to terms_path ( redirect : redirect_path ) , status : :found
end
end
2015-09-25 12:07:36 +05:30
def import_sources_enabled?
2018-03-17 18:26:18 +05:30
! Gitlab :: CurrentSettings . import_sources . empty?
2015-09-25 12:07:36 +05:30
end
2018-11-18 11:00:15 +05:30
def bitbucket_server_import_enabled?
Gitlab :: CurrentSettings . import_sources . include? ( 'bitbucket_server' )
end
2015-04-26 12:48:37 +05:30
def github_import_enabled?
2018-03-17 18:26:18 +05:30
Gitlab :: CurrentSettings . import_sources . include? ( 'github' )
2015-09-25 12:07:36 +05:30
end
2017-08-17 22:00:37 +05:30
def gitea_import_enabled?
2018-03-17 18:26:18 +05:30
Gitlab :: CurrentSettings . import_sources . include? ( 'gitea' )
2017-08-17 22:00:37 +05:30
end
2015-09-25 12:07:36 +05:30
def github_import_configured?
2018-03-27 19:54:05 +05:30
Gitlab :: Auth :: OAuth :: Provider . enabled? ( :github )
2015-04-26 12:48:37 +05:30
end
def gitlab_import_enabled?
2018-03-17 18:26:18 +05:30
request . host != 'gitlab.com' && Gitlab :: CurrentSettings . import_sources . include? ( 'gitlab' )
2015-09-25 12:07:36 +05:30
end
def gitlab_import_configured?
2018-03-27 19:54:05 +05:30
Gitlab :: Auth :: OAuth :: Provider . enabled? ( :gitlab )
2015-04-26 12:48:37 +05:30
end
def bitbucket_import_enabled?
2018-03-17 18:26:18 +05:30
Gitlab :: CurrentSettings . import_sources . include? ( 'bitbucket' )
2015-09-25 12:07:36 +05:30
end
def bitbucket_import_configured?
2018-03-27 19:54:05 +05:30
Gitlab :: Auth :: OAuth :: Provider . enabled? ( :bitbucket )
2015-04-26 12:48:37 +05:30
end
2015-09-25 12:07:36 +05:30
def google_code_import_enabled?
2018-03-17 18:26:18 +05:30
Gitlab :: CurrentSettings . import_sources . include? ( 'google_code' )
2015-09-25 12:07:36 +05:30
end
def fogbugz_import_enabled?
2018-03-17 18:26:18 +05:30
Gitlab :: CurrentSettings . import_sources . include? ( 'fogbugz' )
2015-09-25 12:07:36 +05:30
end
def git_import_enabled?
2018-03-17 18:26:18 +05:30
Gitlab :: CurrentSettings . import_sources . include? ( 'git' )
2015-09-25 12:07:36 +05:30
end
2015-11-26 14:37:03 +05:30
2016-06-22 15:30:34 +05:30
def gitlab_project_import_enabled?
2018-03-17 18:26:18 +05:30
Gitlab :: CurrentSettings . import_sources . include? ( 'gitlab_project' )
2016-06-22 15:30:34 +05:30
end
2018-11-18 11:00:15 +05:30
def manifest_import_enabled?
2019-02-15 15:39:39 +05:30
Group . supports_nested_objects? && Gitlab :: CurrentSettings . import_sources . include? ( 'manifest' )
2018-11-18 11:00:15 +05:30
end
2016-06-16 23:09:34 +05:30
# U2F (universal 2nd factor) devices need a unique identifier for the application
# to perform authentication.
# https://developers.yubico.com/U2F/App_ID.html
def u2f_app_id
request . base_url
end
2017-08-17 22:00:37 +05:30
2017-09-10 17:25:29 +05:30
def set_locale ( & block )
Gitlab :: I18n . with_user_locale ( current_user , & block )
end
2017-08-17 22:00:37 +05:30
2018-03-17 18:26:18 +05:30
def set_page_title_header
# Per https://tools.ietf.org/html/rfc5987, headers need to be ISO-8859-1, not UTF-8
response . headers [ 'Page-Title' ] = URI . escape ( page_title ( 'GitLab' ) )
end
2018-10-15 14:42:47 +05:30
def peek_request?
request . path . start_with? ( '/-/peek' )
end
2018-11-18 11:00:15 +05:30
def json_request?
request . format . json?
end
2018-10-15 14:42:47 +05:30
def should_enforce_terms?
return false unless Gitlab :: CurrentSettings . current_application_settings . enforce_terms
! ( peek_request? || devise_controller? )
end
2018-11-20 20:47:30 +05:30
def set_usage_stats_consent_flag
return unless current_user
return if sessionless_user?
return if session . has_key? ( :ask_for_usage_stats_consent )
session [ :ask_for_usage_stats_consent ] = current_user . requires_usage_stats_consent?
if session [ :ask_for_usage_stats_consent ]
disable_usage_stats
end
end
def disable_usage_stats
application_setting_params = {
usage_ping_enabled : false ,
version_check_enabled : false ,
skip_usage_stats_user : true
}
settings = Gitlab :: CurrentSettings . current_application_settings
ApplicationSettings :: UpdateService
. new ( settings , current_user , application_setting_params )
. execute
end
2019-02-15 15:39:39 +05:30
def check_impersonation_availability
return unless session [ :impersonator_id ]
unless Gitlab . config . gitlab . impersonation_enabled
stop_impersonation
access_denied! _ ( 'Impersonation has been disabled' )
end
end
def stop_impersonation
impersonated_user = current_user
Gitlab :: AppLogger . info ( " User #{ impersonator . username } has stopped impersonating #{ impersonated_user . username } " )
warden . set_user ( impersonator , scope : :user )
session [ :impersonator_id ] = nil
impersonated_user
end
def impersonator
@impersonator || = User . find ( session [ :impersonator_id ] ) if session [ :impersonator_id ]
end
def sentry_context
Gitlab :: Sentry . context ( current_user )
end
2014-09-02 18:07:02 +05:30
end