Commit bbfce29b authored by Lin Jen-Shin's avatar Lin Jen-Shin

Use a controller to hold request values

So that we don't need to hold env after the request.
This makes it much harder to test, especially Rails session is
acting weirdly, so we need `dig('flash', 'flashes', 'alert')`
to dig the actual flash value.
parent d4d564c8
...@@ -5,86 +5,95 @@ module Gitlab ...@@ -5,86 +5,95 @@ module Gitlab
APPLICATION_JSON = 'application/json'.freeze APPLICATION_JSON = 'application/json'.freeze
API_VERSIONS = (3..4) API_VERSIONS = (3..4)
def initialize(app) class Controller
@app = app def initialize(app, env)
@whitelisted = internal_routes @app = app
end @env = env
end
def call(env)
@env = env
@route_hash = nil
if disallowed_request? && Gitlab::Database.read_only? def call
Rails.logger.debug('GitLab ReadOnly: preventing possible non read-only operation') if disallowed_request? && Gitlab::Database.read_only?
error_message = 'You cannot do writing operations on a read-only GitLab instance' Rails.logger.debug('GitLab ReadOnly: preventing possible non read-only operation')
error_message = 'You cannot do writing operations on a read-only GitLab instance'
if json_request? if json_request?
return [403, { 'Content-Type' => 'application/json' }, [{ 'message' => error_message }.to_json]] return [403, { 'Content-Type' => 'application/json' }, [{ 'message' => error_message }.to_json]]
else else
rack_flash.alert = error_message rack_flash.alert = error_message
rack_session['flash'] = rack_flash.to_session_value rack_session['flash'] = rack_flash.to_session_value
return [301, { 'Location' => last_visited_url }, []] return [301, { 'Location' => last_visited_url }, []]
end
end end
@app.call(@env)
end end
@app.call(env).tap { @env = nil } private
end
private def disallowed_request?
DISALLOWED_METHODS.include?(@env['REQUEST_METHOD']) &&
!whitelisted_routes
end
def internal_routes def json_request?
API_VERSIONS.flat_map { |version| "api/v#{version}/internal" } request.media_type == APPLICATION_JSON
end end
def disallowed_request? def rack_flash
DISALLOWED_METHODS.include?(@env['REQUEST_METHOD']) && !whitelisted_routes @rack_flash ||= ActionDispatch::Flash::FlashHash.from_session_value(rack_session)
end end
def json_request? def rack_session
request.media_type == APPLICATION_JSON @env['rack.session']
end end
def rack_flash def request
@rack_flash ||= ActionDispatch::Flash::FlashHash.from_session_value(rack_session) @env['rack.request'] ||= Rack::Request.new(@env)
end end
def rack_session def last_visited_url
@env['rack.session'] @env['HTTP_REFERER'] || rack_session['user_return_to'] || Gitlab::Routing.url_helpers.root_url
end end
def request def route_hash
@env['rack.request'] ||= Rack::Request.new(@env) @route_hash ||= Rails.application.routes.recognize_path(request.url, { method: request.request_method }) rescue {}
end end
def last_visited_url def whitelisted_routes
@env['HTTP_REFERER'] || rack_session['user_return_to'] || Gitlab::Routing.url_helpers.root_url grack_route || ReadOnly.internal_routes.any? { |path| request.path.include?(path) } || lfs_route || sidekiq_route
end end
def route_hash def sidekiq_route
@route_hash ||= Rails.application.routes.recognize_path(request.url, { method: request.request_method }) rescue {} request.path.start_with?('/admin/sidekiq')
end end
def whitelisted_routes def grack_route
grack_route || @whitelisted.any? { |path| request.path.include?(path) } || lfs_route || sidekiq_route # Calling route_hash may be expensive. Only do it if we think there's a possible match
end return false unless request.path.end_with?('.git/git-upload-pack')
def sidekiq_route route_hash[:controller] == 'projects/git_http' && route_hash[:action] == 'git_upload_pack'
request.path.start_with?('/admin/sidekiq') end
end
def lfs_route
# Calling route_hash may be expensive. Only do it if we think there's a possible match
return false unless request.path.end_with?('/info/lfs/objects/batch')
def grack_route route_hash[:controller] == 'projects/lfs_api' && route_hash[:action] == 'batch'
# Calling route_hash may be expensive. Only do it if we think there's a possible match end
return false unless request.path.end_with?('.git/git-upload-pack') end
route_hash[:controller] == 'projects/git_http' && route_hash[:action] == 'git_upload_pack' def self.internal_routes
@internal_routes ||=
API_VERSIONS.map { |version| "api/v#{version}/internal" }
end end
def lfs_route def initialize(app)
# Calling route_hash may be expensive. Only do it if we think there's a possible match @app = app
return false unless request.path.end_with?('/info/lfs/objects/batch') end
route_hash[:controller] == 'projects/lfs_api' && route_hash[:action] == 'batch' def call(env)
Controller.new(@app, env).call
end end
end end
end end
......
...@@ -11,8 +11,10 @@ describe Gitlab::Middleware::ReadOnly do ...@@ -11,8 +11,10 @@ describe Gitlab::Middleware::ReadOnly do
RSpec::Matchers.define :disallow_request do RSpec::Matchers.define :disallow_request do
match do |middleware| match do |middleware|
flash = middleware.send(:rack_flash) alert = middleware.env['rack.session'].to_hash
flash['alert'] && flash['alert'].include?('You cannot do writing operations') .dig('flash', 'flashes', 'alert')
alert&.include?('You cannot do writing operations')
end end
end end
...@@ -34,7 +36,22 @@ describe Gitlab::Middleware::ReadOnly do ...@@ -34,7 +36,22 @@ describe Gitlab::Middleware::ReadOnly do
rack.to_app rack.to_app
end end
subject { described_class.new(fake_app) } let(:observe_env) do
Module.new do
attr_reader :env
def call(env)
@env = env
super
end
end
end
subject do
app = described_class.new(fake_app)
app.extend(observe_env)
app
end
let(:request) { Rack::MockRequest.new(rack_stack) } let(:request) { Rack::MockRequest.new(rack_stack) }
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment