Commit 27e2d103 authored by Dmitriy Zaporozhets's avatar Dmitriy Zaporozhets

Merge branch 'gitlab-geo' into 'master'

Gitlab Geo: Authentication

Initial internal API for discovery and authentication for the gitlab-org/gitlab-ee#76

See merge request !112
parents aa6ac83e b816969d
......@@ -100,13 +100,17 @@ class ApplicationController < ActionController::Base
flash[:alert] = "Your account is blocked. Retry when an admin has unblocked it."
new_user_session_path
else
stored_location_for(:redirect) || stored_location_for(resource) || root_path
stored_location_for(:geo_node) || stored_location_for(:redirect) || stored_location_for(resource) || root_path
end
end
def after_sign_out_path_for(resource)
if Gitlab::Geo.readonly?
Gitlab::Geo.primary_node.url
else
current_application_settings.after_sign_out_path || new_user_session_path
end
end
def abilities
Ability.abilities
......
......@@ -4,6 +4,7 @@ class SessionsController < Devise::SessionsController
prepend_before_action :authenticate_with_two_factor, only: [:create]
prepend_before_action :store_redirect_path, only: [:new]
before_action :gitlab_geo_login, only: [:new]
before_action :auto_sign_in_with_provider, only: [:new]
before_action :load_recaptcha
......@@ -44,23 +45,25 @@ class SessionsController < Devise::SessionsController
end
def store_redirect_path
redirect_path =
redirect_uri =
if request.referer.present? && (params['redirect_to_referer'] == 'yes')
referer_uri = URI(request.referer)
if referer_uri.host == Gitlab.config.gitlab.host
referer_uri.path
URI(request.referer)
else
request.fullpath
end
else
request.fullpath
URI(request.url)
end
# 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
store_location_for(:redirect, redirect_path)
if redirect_uri.path == new_user_session_path
return true
elsif redirect_uri.host == Gitlab.config.gitlab.host && redirect_uri.port == Gitlab.config.gitlab.port
redirect_to = redirect_uri.to_s
elsif Gitlab::Geo.geo_node?(host: redirect_uri.host, port: redirect_uri.port)
redirect_to = redirect_uri.to_s
end
@redirect_to = redirect_to
store_location_for(:redirect, redirect_to)
end
def authenticate_with_two_factor
......@@ -85,6 +88,17 @@ class SessionsController < Devise::SessionsController
end
end
def gitlab_geo_login
if !signed_in? && Gitlab::Geo.enabled? && Gitlab::Geo.readonly?
# share full url with primary node by shared session
user_return_to = URI.join(root_url, session[:user_return_to]).to_s
session[:geo_node_return_to] = @redirect_to || user_return_to
login_uri = URI.join(Gitlab::Geo.primary_node.url, new_session_path(:user)).to_s
redirect_to login_uri
end
end
def auto_sign_in_with_provider
provider = Gitlab.config.omniauth.auto_sign_in_with_provider
return unless provider.present?
......
# == Schema Information
#
# Table name: geo_nodes
#
# id :integer not null, primary key
# schema :string
# host :string
# port :integer
# relative_url_root :string
# primary :boolean
#
class GeoNode < ActiveRecord::Base
default_value_for :schema, 'http'
default_value_for :port, 80
default_value_for :relative_url_root, ''
default_value_for :primary, false
validates :host, host: true, presence: true, uniqueness: { case_sensitive: false, scope: :port }
validates :primary, uniqueness: { message: 'primary node already exists' }, if: :primary
validates :schema, inclusion: %w(http https)
def uri
URI.parse("#{schema}://#{host}:#{port}/#{relative_url_root}")
end
def url
uri.to_s
end
end
# HostnameValidator
#
# Custom validator for Hosts.
#
# This is similar to an URI validator, but will make sure no schema or
# path is present, only the domain part.
#
class HostValidator < ActiveModel::EachValidator
def validate_each(record, attribute, value)
unless valid_host?(value)
record.errors.add(attribute, 'must be a valid hostname!')
end
end
private
def valid_host?(value)
URI.parse("http://#{value}").host == value
rescue URI::InvalidURIError
false
end
end
class CreateGeoNodes < ActiveRecord::Migration
def change
create_table :geo_nodes do |t|
t.string :schema
t.string :host, index: true
t.integer :port
t.string :relative_url_root
t.boolean :primary, index: true
end
end
end
......@@ -404,6 +404,16 @@ ActiveRecord::Schema.define(version: 20160120172143) do
add_index "forked_project_links", ["forked_to_project_id"], name: "index_forked_project_links_on_forked_to_project_id", unique: true, using: :btree
create_table "geo_nodes", force: :cascade do |t|
t.string "schema"
t.string "host"
t.integer "port"
t.string "relative_url_root"
t.boolean "primary"
end
add_index "geo_nodes", ["host"], name: "index_geo_nodes_on_host", using: :btree
add_index "geo_nodes", ["primary"], name: "index_geo_nodes_on_primary", using: :btree
create_table "git_hooks", force: :cascade do |t|
t.string "force_push_regex"
t.string "delete_branch_regex"
......
module Gitlab
module Geo
def self.current_node
RequestStore.store[:geo_node_current] ||= begin
GeoNode.find_by(host: Gitlab.config.gitlab.host,
port: Gitlab.config.gitlab.port,
relative_url_root: Gitlab.config.gitlab.relative_url_root)
end
end
def self.primary_node
RequestStore.store[:geo_node_primary] ||= GeoNode.find_by(primary: true)
end
def self.enabled?
RequestStore.store[:geo_node_enabled] ||= GeoNode.exists?
end
def self.readonly?
RequestStore.store[:geo_node_readonly] ||= self.enabled? && !self.current_node.primary?
end
def self.geo_node?(host:, port:)
GeoNode.where(host: host, port: port).exists?
end
end
end
......@@ -244,4 +244,14 @@ FactoryGirl.define do
factory :license do
data { build(:gitlab_license).export }
end
factory :geo_node do
host { Gitlab.config.gitlab.host }
sequence(:port) {|n| n}
trait :primary do
primary true
port { Gitlab.config.gitlab.port }
end
end
end
require 'spec_helper'
describe Gitlab::Geo, lib: true do
let(:primary_node) { FactoryGirl.create(:geo_node, :primary) }
let(:secondary_node) { FactoryGirl.create(:geo_node) }
describe 'current_node' do
before(:each) { primary_node }
it 'returns a GeoNode instance' do
expect(described_class.current_node).to eq(primary_node)
end
end
describe 'primary_node' do
before(:each) do
primary_node
secondary_node
end
it 'returns a GeoNode primary instance' do
expect(described_class.current_node).to eq(primary_node)
end
end
describe 'enabled?' do
context 'when any GeoNode exists' do
before(:each) { secondary_node }
it 'returns true' do
expect(described_class.enabled?).to be_truthy
end
end
context 'when no GeoNode exists' do
it 'returns false' do
expect(described_class.enabled?).to be_falsey
end
end
end
describe 'readonly?' do
context 'when current node is secondary' do
before(:each) { secondary_node }
it 'returns true' do
expect(described_class).to receive(:current_node) { secondary_node }
expect(described_class.readonly?).to be_truthy
end
end
context 'current node is primary' do
before(:each) { primary_node }
it 'returns false when ' do
expect(described_class).to receive(:current_node) { primary_node }
expect(described_class.readonly?).to be_falsey
end
end
end
describe 'geo_node?' do
it 'returns true if a node with specific host and port exists' do
expect(described_class.geo_node?(host: primary_node.host, port: primary_node.port)).to be_truthy
end
it 'returns false if specified host and port doesnt match any existing node' do
expect(described_class.geo_node?(host: 'inexistent', port: 1234)).to be_falsey
end
end
end
RSpec.configure do |config|
config.before(:each) do
RequestStore.clear!
end
end
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