Commit e3bd17a7 authored by Dmitriy Zaporozhets's avatar Dmitriy Zaporozhets

Merge branch 'feature-multiple-ldap-servers' into 'master'

Feature multiple ldap servers

Update the code so Gitlab-EE can support multiple LDAP servers

See merge request !1172
parents 4bebdc09 b4f7b387
......@@ -24,7 +24,7 @@ class OmniauthCallbacksController < Devise::OmniauthCallbacksController
gl_user.remember_me = true if @user.persisted?
# Do additional LDAP checks for the user filter and EE features
if Gitlab::LDAP::Access.allowed?(gl_user)
if @user.allowed?
sign_in_and_redirect(gl_user)
else
flash[:alert] = "Access denied for your LDAP account."
......
......@@ -18,6 +18,10 @@ class SessionsController < Devise::SessionsController
store_location_for(:redirect, redirect_path)
end
if Gitlab.config.ldap.enabled
@ldap_servers = Gitlab::LDAP::Config.servers
end
super
end
......
module OauthHelper
def ldap_enabled?
Devise.omniauth_providers.include?(:ldap)
Gitlab.config.ldap.enabled
end
def default_providers
......
......@@ -178,8 +178,7 @@ class User < ActiveRecord::Base
scope :not_in_team, ->(team){ where('users.id NOT IN (:ids)', ids: team.member_ids) }
scope :not_in_project, ->(project) { project.users.present? ? where("id not in (:ids)", ids: project.users.map(&:id) ) : all }
scope :without_projects, -> { where('id NOT IN (SELECT DISTINCT(user_id) FROM members)') }
scope :ldap, -> { where(provider: 'ldap') }
scope :ldap, -> { where('provider LIKE ?', 'ldap%') }
scope :potential_team_members, ->(team) { team.members.any? ? active.not_in_team(team) : active }
#
......@@ -407,7 +406,7 @@ class User < ActiveRecord::Base
end
def ldap_user?
extern_uid && provider == 'ldap'
extern_uid && provider.start_with?('ldap')
end
def accessible_deploy_keys
......
= form_tag(user_omniauth_callback_path(:ldap), id: 'new_ldap_user' ) do
= form_tag(user_omniauth_callback_path(provider), id: 'new_ldap_user' ) do
= text_field_tag :username, nil, {class: "form-control top", placeholder: "LDAP Login", autofocus: "autofocus"}
= password_field_tag :password, nil, {class: "form-control bottom", placeholder: "Password"}
%br/
......
......@@ -4,20 +4,22 @@
.login-body
- if ldap_enabled? && gitlab_config.signin_enabled
%ul.nav.nav-tabs
%li.active
= link_to 'LDAP', '#tab-ldap', 'data-toggle' => 'tab'
- @ldap_servers.each_with_index do |server, i|
%li{class: (:active if i==0)}
= link_to server['label'], "#tab-#{server['provider_name']}", 'data-toggle' => 'tab'
%li
= link_to 'Standard', '#tab-signin', 'data-toggle' => 'tab'
.tab-content
%div#tab-ldap.tab-pane.active
= render partial: 'devise/sessions/new_ldap'
- @ldap_servers.each_with_index do |server,i|
%div.tab-pane{id: "tab-#{server['provider_name']}", class: (:active if i==0)}
= render 'devise/sessions/new_ldap', provider: server['provider_name']
%div#tab-signin.tab-pane
= render partial: 'devise/sessions/new_base'
= render 'devise/sessions/new_base'
- elsif ldap_enabled?
= render partial: 'devise/sessions/new_ldap'
= render 'devise/sessions/new_ldap', ldap_servers: @ldap_servers
- elsif gitlab_config.signin_enabled
= render partial: 'devise/sessions/new_base'
= render 'devise/sessions/new_base'
- else
%div
No authentication methods configured.
......@@ -36,7 +38,6 @@
%span.light Did not receive confirmation email?
= link_to "Send again", new_confirmation_path(resource_name)
- if extra_config.has_key?('sign_in_text')
%hr
= markdown(extra_config.sign_in_text)
......@@ -135,6 +135,16 @@ production: &base
# bundle exec rake gitlab:ldap:check RAILS_ENV=production
ldap:
enabled: false
servers:
main: # 'main' is the GitLab 'provider ID' of this LDAP server
## label
#
# A human-friendly name for your LDAP server. It is OK to change the label later,
# for instance if you find out it is too large to fit on the web page.
#
# Example: 'Paris' or 'Acme, Ltd.'
label: 'LDAP'
host: '_your_ldap_server'
port: 636
uid: 'sAMAccountName'
......@@ -173,6 +183,14 @@ production: &base
#
user_filter: ''
# GitLab EE only: add more LDAP servers
# Choose an ID made of a-z and 0-9 . This ID will be stored in the database
# so that GitLab can remember which LDAP server a user belongs to.
# uswest2:
# label:
# host:
# ....
## OmniAuth settings
omniauth:
......@@ -300,6 +318,20 @@ test:
project_url: "http://redmine/projects/:issues_tracker_id"
issues_url: "http://redmine/:project_id/:issues_tracker_id/:id"
new_issue_url: "http://redmine/projects/:issues_tracker_id/issues/new"
ldap:
enabled: false
servers:
main:
label: ldap
host: 127.0.0.1
port: 3890
uid: 'uid'
method: 'plain' # "tls" or "ssl" or "plain"
base: 'dc=example,dc=com'
user_filter: ''
group_base: 'ou=groups,dc=example,dc=com'
admin_group: ''
sync_ssh_keys: false
staging:
<<: *base
......@@ -56,9 +56,25 @@ end
# Default settings
Settings['ldap'] ||= Settingslogic.new({})
Settings.ldap['enabled'] = false if Settings.ldap['enabled'].nil?
Settings.ldap['allow_username_or_email_login'] = false if Settings.ldap['allow_username_or_email_login'].nil?
Settings.ldap['active_directory'] = true if Settings.ldap['active_directory'].nil?
# backwards compatibility, we only have one host
if Settings.ldap['enabled'] || Rails.env.test?
if Settings.ldap['host'].present?
server = Settings.ldap.except('sync_time')
server['label'] = 'LDAP'
server['provider_name'] = 'ldap'
Settings.ldap['servers'] = {
'ldap' => server
}
end
Settings.ldap['servers'].each do |key, server|
server['allow_username_or_email_login'] = false if server['allow_username_or_email_login'].nil?
server['active_directory'] = true if server['active_directory'].nil?
server['provider_name'] ||= "ldap#{key}".downcase
server['provider_class'] = OmniAuth::Utils.camelize(server['provider_name'])
end
end
Settings['omniauth'] ||= Settingslogic.new({})
Settings.omniauth['enabled'] = false if Settings.omniauth['enabled'].nil?
......
module OmniAuth::Strategies
server = Gitlab.config.ldap.servers.values.first
const_set(server['provider_class'], Class.new(LDAP))
end
OmniauthCallbacksController.class_eval do
server = Gitlab.config.ldap.servers.values.first
alias_method server['provider_name'], :ldap
end
\ No newline at end of file
......@@ -205,22 +205,24 @@ Devise.setup do |config|
# end
if Gitlab.config.ldap.enabled
if Gitlab.config.ldap.allow_username_or_email_login
Gitlab.config.ldap.servers.values.each do |server|
if server['allow_username_or_email_login']
email_stripping_proc = ->(name) {name.gsub(/@.*$/,'')}
else
email_stripping_proc = ->(name) {name}
end
config.omniauth :ldap,
host: Gitlab.config.ldap['host'],
base: Gitlab.config.ldap['base'],
uid: Gitlab.config.ldap['uid'],
port: Gitlab.config.ldap['port'],
method: Gitlab.config.ldap['method'],
bind_dn: Gitlab.config.ldap['bind_dn'],
password: Gitlab.config.ldap['password'],
config.omniauth server['provider_name'],
host: server['host'],
base: server['base'],
uid: server['uid'],
port: server['port'],
method: server['method'],
bind_dn: server['bind_dn'],
password: server['password'],
name_proc: email_stripping_proc
end
end
Gitlab.config.omniauth.providers.each do |provider|
provider_arguments = []
......
......@@ -3,22 +3,16 @@ module Gitlab
def find(login, password)
user = User.find_by(email: login) || User.find_by(username: login)
# If no user is found, or it's an LDAP server, try LDAP.
# LDAP users are only authenticated via LDAP
if user.nil? || user.ldap_user?
# Second chance - try LDAP authentication
return nil unless ldap_conf.enabled
return nil unless Gitlab::LDAP::Config.enabled?
Gitlab::LDAP::User.authenticate(login, password)
Gitlab::LDAP::Authentication.login(login, password)
else
user if user.valid_password?(password)
end
end
def log
Gitlab::AppLogger
end
def ldap_conf
@ldap_conf ||= Gitlab.config.ldap
end
end
end
# LDAP authorization model
#
# * Check if we are allowed access (not blocked)
#
module Gitlab
module LDAP
class Access
attr_reader :adapter
attr_reader :adapter, :provider, :user
def self.open(&block)
Gitlab::LDAP::Adapter.open do |adapter|
block.call(self.new(adapter))
def self.open(user, &block)
Gitlab::LDAP::Adapter.open(user.provider) do |adapter|
block.call(self.new(user, adapter))
end
end
def self.allowed?(user)
self.open do |access|
if access.allowed?(user)
# GitLab EE LDAP code goes here
self.open(user) do |access|
if access.allowed?
user.last_credential_check_at = Time.now
user.save
true
......@@ -22,21 +25,30 @@ module Gitlab
end
end
def initialize(adapter=nil)
def initialize(user, adapter=nil)
@adapter = adapter
@user = user
@provider = user.provider
end
def allowed?(user)
def allowed?
if Gitlab::LDAP::Person.find_by_dn(user.extern_uid, adapter)
if Gitlab.config.ldap.active_directory
return true unless ldap_config.active_directory
!Gitlab::LDAP::Person.disabled_via_active_directory?(user.extern_uid, adapter)
end
else
false
end
rescue
false
end
def adapter
@adapter ||= Gitlab::LDAP::Adapter.new(provider)
end
def ldap_config
Gitlab::LDAP::Config.new(provider)
end
end
end
end
module Gitlab
module LDAP
class Adapter
attr_reader :ldap
attr_reader :provider, :ldap
def self.open(&block)
Net::LDAP.open(adapter_options) do |ldap|
block.call(self.new(ldap))
def self.open(provider, &block)
Net::LDAP.open(config(provider).adapter_options) do |ldap|
block.call(self.new(provider, ldap))
end
end
def self.config
Gitlab.config.ldap
def self.config(provider)
Gitlab::LDAP::Config.new(provider)
end
def self.adapter_options
encryption =
case config['method'].to_s
when 'ssl'
:simple_tls
when 'tls'
:start_tls
else
nil
end
options = {
host: config['host'],
port: config['port'],
encryption: encryption
}
auth_options = {
auth: {
method: :simple,
username: config['bind_dn'],
password: config['password']
}
}
if config['password'] || config['bind_dn']
options.merge!(auth_options)
end
options
def initialize(provider, ldap=nil)
@provider = provider
@ldap = ldap || Net::LDAP.new(config.adapter_options)
end
def initialize(ldap=nil)
@ldap = ldap || Net::LDAP.new(self.class.adapter_options)
def config
Gitlab::LDAP::Config.new(provider)
end
def users(field, value)
......@@ -57,13 +30,13 @@ module Gitlab
}
else
options = {
base: config['base'],
base: config.base,
filter: Net::LDAP::Filter.eq(field, value)
}
end
if config['user_filter'].present?
user_filter = Net::LDAP::Filter.construct(config['user_filter'])
if config.user_filter.present?
user_filter = Net::LDAP::Filter.construct(config.user_filter)
options[:filter] = if options[:filter]
Net::LDAP::Filter.join(options[:filter], user_filter)
......@@ -77,7 +50,7 @@ module Gitlab
end
entries.map do |entry|
Gitlab::LDAP::Person.new(entry)
Gitlab::LDAP::Person.new(entry, provider)
end
end
......@@ -105,12 +78,6 @@ module Gitlab
results
end
end
private
def config
@config ||= self.class.config
end
end
end
end
# This calls helps to authenticate to LDAP by providing username and password
#
# Since multiple LDAP servers are supported, it will loop through all of them
# until a valid bind is found
#
module Gitlab
module LDAP
class Authentication
def self.login(login, password)
return unless Gitlab::LDAP::Config.enabled?
return unless login.present? && password.present?
auth = nil
# loop through providers until valid bind
providers.find do |provider|
auth = new(provider)
auth.login(login, password) # true will exit the loop
end
# If (login, password) was invalid for all providers, the value of auth is now the last
# Gitlab::LDAP::Authentication instance we tried.
auth.user
end
def self.providers
Gitlab::LDAP::Config.providers
end
attr_accessor :provider, :ldap_user
def initialize(provider)
@provider = provider
end
def login(login, password)
@ldap_user = adapter.bind_as(
filter: user_filter(login),
size: 1,
password: password
)
end
def adapter
OmniAuth::LDAP::Adaptor.new(config.options)
end
def config
Gitlab::LDAP::Config.new(provider)
end
def user_filter(login)
filter = Net::LDAP::Filter.eq(config.uid, login)
# Apply LDAP user filter if present
if config.user_filter.present?
filter = Net::LDAP::Filter.join(
filter,
Net::LDAP::Filter.construct(config.user_filter)
)
end
filter
end
def user
return nil unless ldap_user
Gitlab::LDAP::User.find_by_uid_and_provider(ldap_user.dn, provider)
end
end
end
end
\ No newline at end of file
# Load a specific server configuration
module Gitlab
module LDAP
class Config
attr_accessor :provider, :options
def self.enabled?
Gitlab.config.ldap.enabled
end
def self.servers
Gitlab.config.ldap.servers.values
end
def self.providers
servers.map {|server| server['provider_name'] }
end
def initialize(provider)
@provider = provider
invalid_provider unless valid_provider?
@options = config_for(provider)
end
def enabled?
base_config.enabled
end
def adapter_options
{
host: options['host'],
port: options['port'],
encryption: encryption
}.tap do |options|
options.merge!(auth_options) if has_auth?
end
end
def base
options['base']
end
def uid
options['uid']
end
def sync_ssh_keys?
sync_ssh_keys.present?
end
# The LDAP attribute in which the ssh keys are stored
def sync_ssh_keys
options['sync_ssh_keys']
end
def user_filter
options['user_filter']
end
def group_base
options['group_base']
end
def admin_group
options['admin_group']
end
def active_directory
options['active_directory']
end
protected
def base_config
Gitlab.config.ldap
end
def config_for(provider)
base_config.servers.values.find { |server| server['provider_name'] == provider }
end
def encryption
case options['method'].to_s
when 'ssl'
:simple_tls
when 'tls'
:start_tls
else
nil
end
end
def valid_provider?
self.class.providers.include?(provider)
end
def invalid_provider
raise "Unknown provider (#{provider}). Available providers: #{self.class.providers}"
end
def auth_options
{
auth: {
method: :simple,
username: options['bind_dn'],
password: options['password']
}
}
end
def has_auth?
options['password'] || options['bind_dn']
end
end
end
end
......@@ -6,24 +6,24 @@ module Gitlab
# Source: http://ctogonewild.com/2009/09/03/bitmask-searches-in-ldap/
AD_USER_DISABLED = Net::LDAP::Filter.ex("userAccountControl:1.2.840.113556.1.4.803", "2")
def self.find_by_uid(uid, adapter=nil)
adapter ||= Gitlab::LDAP::Adapter.new
adapter.user(config.uid, uid)
attr_accessor :entry, :provider
def self.find_by_uid(uid, adapter)
adapter.user(adapter.config.uid, uid)
end
def self.find_by_dn(dn, adapter=nil)
adapter ||= Gitlab::LDAP::Adapter.new
def self.find_by_dn(dn, adapter)
adapter.user('dn', dn)
end
def self.disabled_via_active_directory?(dn, adapter=nil)
adapter ||= Gitlab::LDAP::Adapter.new
def self.disabled_via_active_directory?(dn, adapter)
adapter.dn_matches_filter?(dn, AD_USER_DISABLED)
end
def initialize(entry)
def initialize(entry, provider)
Rails.logger.debug { "Instantiating #{self.class.name} with LDIF:\n#{entry.to_ldif}" }
@entry = entry
@provider = provider
end
def name
......@@ -38,6 +38,10 @@ module Gitlab
uid
end
def email
entry.try(:mail)
end
def dn
entry.dn
end
......@@ -48,12 +52,8 @@ module Gitlab
@entry
end
def adapter
@adapter ||= Gitlab::LDAP::Adapter.new
end
def config
@config ||= Gitlab.config.ldap
@config ||= Gitlab::LDAP::Config.new(provider)
end
end
end
......
......@@ -10,45 +10,11 @@ module Gitlab
module LDAP
class User < Gitlab::OAuth::User
class << self
def authenticate(login, password)
# Check user against LDAP backend if user is not authenticated
# Only check with valid login and password to prevent anonymous bind results
return nil unless ldap_conf.enabled && login.present? && password.present?
ldap_user = adapter.bind_as(
filter: user_filter(login),
size: 1,
password: password
)
find_by_uid(ldap_user.dn) if ldap_user
end
def adapter
@adapter ||= OmniAuth::LDAP::Adaptor.new(ldap_conf)
end
def user_filter(login)
filter = Net::LDAP::Filter.eq(adapter.uid, login)
# Apply LDAP user filter if present
if ldap_conf['user_filter'].present?
user_filter = Net::LDAP::Filter.construct(ldap_conf['user_filter'])
filter = Net::LDAP::Filter.join(filter, user_filter)
end
filter
end
def ldap_conf
Gitlab.config.ldap
end
def find_by_uid(uid)
def find_by_uid_and_provider(uid, provider)
# LDAP distinguished name is case-insensitive
model.where("provider = ? and lower(extern_uid) = ?", provider, uid.downcase).last
end
def provider
'ldap'
::User.
where(provider: [provider, :ldap]).
where('lower(extern_uid) = ?', uid.downcase).last
end
end
......@@ -63,10 +29,8 @@ module Gitlab
end
def find_by_uid_and_provider
# LDAP distinguished name is case-insensitive
model.
where(provider: auth_hash.provider).
where('lower(extern_uid) = ?', auth_hash.uid.downcase).last
self.class.find_by_uid_and_provider(
auth_hash.uid.downcase, auth_hash.provider)
end
def find_by_email
......@@ -88,6 +52,10 @@ module Gitlab
def needs_blocking?
false
end
def allowed?
Gitlab::LDAP::Access.allowed?(gl_user)
end
end
end
end
......@@ -70,10 +70,6 @@ module Gitlab
Gitlab::AppLogger
end
def raise_error(message)
raise OmniAuth::Error, "(OAuth) " + message
end
def needs_blocking?
Gitlab.config.omniauth['block_auto_created_users']
end
......
......@@ -24,6 +24,11 @@ FactoryGirl.define do
admin true
end
trait :ldap do
provider 'ldapmain'
extern_uid 'my-ldap-id'
end
factory :admin, traits: [:admin]
end
......
......@@ -28,17 +28,16 @@ describe Gitlab::Auth do
end
context "with ldap enabled" do
before { Gitlab.config.ldap['enabled'] = true }
after { Gitlab.config.ldap['enabled'] = false }
before { Gitlab::LDAP::Config.stub(enabled?: true) }
it "tries to autheticate with db before ldap" do
expect(Gitlab::LDAP::User).not_to receive(:authenticate)
expect(Gitlab::LDAP::Authentication).not_to receive(:login)
gl_auth.find(username, password)
end
it "uses ldap as fallback to for authentication" do
expect(Gitlab::LDAP::User).to receive(:authenticate)
expect(Gitlab::LDAP::Authentication).to receive(:login)
gl_auth.find('ldap_user', 'password')
end
......
require 'spec_helper'
describe Gitlab::LDAP::Access do
let(:access) { Gitlab::LDAP::Access.new }
let(:user) { create(:user) }
let(:access) { Gitlab::LDAP::Access.new user }
let(:user) { create(:user, :ldap) }
describe :allowed? do
subject { access.allowed?(user) }
subject { access.allowed? }
context 'when the user cannot be found' do
before { Gitlab::LDAP::Person.stub(find_by_dn: nil) }
......@@ -28,19 +28,13 @@ describe Gitlab::LDAP::Access do
it { should be_true }
end
context 'and has no disabled flag in active diretory' do
before {
Gitlab::LDAP::Person.stub(disabled_via_active_directory?: false)
Gitlab.config.ldap['enabled'] = true
Gitlab.config.ldap['active_directory'] = false
}
after {
Gitlab.config.ldap['enabled'] = false
Gitlab.config.ldap['active_directory'] = true
}
context 'without ActiveDirectory enabled' do
before do
Gitlab::LDAP::Config.stub(enabled?: true)
Gitlab::LDAP::Config.any_instance.stub(active_directory: false)
end
it { should be_false }
it { should be_true }
end
end
end
......
require 'spec_helper'
describe Gitlab::LDAP::Adapter do
let(:adapter) { Gitlab::LDAP::Adapter.new }
let(:adapter) { Gitlab::LDAP::Adapter.new 'ldapmain' }
describe :dn_matches_filter? do
let(:ldap) { double(:ldap) }
......
require 'spec_helper'
describe Gitlab::LDAP::Authentication do
let(:klass) { Gitlab::LDAP::Authentication }
let(:user) { create(:user, :ldap, extern_uid: dn) }
let(:dn) { 'uid=john,ou=people,dc=example,dc=com' }
let(:login) { 'john' }
let(:password) { 'password' }
describe :login do
let(:adapter) { double :adapter }
before do
Gitlab::LDAP::Config.stub(enabled?: true)
end
it "finds the user if authentication is successful" do
user
# try only to fake the LDAP call
klass.any_instance.stub(adapter: double(:adapter,
bind_as: double(:ldap_user, dn: dn)
))
expect(klass.login(login, password)).to be_true
end
it "is false if the user does not exist" do
# try only to fake the LDAP call
klass.any_instance.stub(adapter: double(:adapter,
bind_as: double(:ldap_user, dn: dn)
))
expect(klass.login(login, password)).to be_false
end
it "is false if authentication fails" do
user
# try only to fake the LDAP call
klass.any_instance.stub(adapter: double(:adapter, bind_as: nil))
expect(klass.login(login, password)).to be_false
end
it "fails if ldap is disabled" do
Gitlab::LDAP::Config.stub(enabled?: false)
expect(klass.login(login, password)).to be_false
end
it "fails if no login is supplied" do
expect(klass.login('', password)).to be_false
end
it "fails if no password is supplied" do
expect(klass.login(login, '')).to be_false
end
end
end
\ No newline at end of file
require 'spec_helper'
describe Gitlab::LDAP::Config do
let(:config) { Gitlab::LDAP::Config.new provider }
let(:provider) { 'ldapmain' }
describe :initalize do
it 'requires a provider' do
expect{ Gitlab::LDAP::Config.new }.to raise_error ArgumentError
end
it "works" do
expect(config).to be_a described_class
end
it "raises an error if a unknow provider is used" do
expect{ Gitlab::LDAP::Config.new 'unknown' }.to raise_error
end
end
end
\ No newline at end of file
......@@ -10,12 +10,12 @@ describe Gitlab::LDAP::User do
}
end
let(:auth_hash) do
double(uid: 'my-uid', provider: 'ldap', info: double(info))
double(uid: 'my-uid', provider: 'ldapmain', info: double(info))
end
describe :find_or_create do
it "finds the user if already existing" do
existing_user = create(:user, extern_uid: 'my-uid', provider: 'ldap')
existing_user = create(:user, extern_uid: 'my-uid', provider: 'ldapmain')
expect{ gl_user.save }.to_not change{ User.count }
end
......@@ -26,27 +26,11 @@ describe Gitlab::LDAP::User do
existing_user.reload
expect(existing_user.extern_uid).to eql 'my-uid'
expect(existing_user.provider).to eql 'ldap'
expect(existing_user.provider).to eql 'ldapmain'
end
it "creates a new user if not found" do
expect{ gl_user.save }.to change{ User.count }.by(1)
end
end
describe "authenticate" do
let(:login) { 'john' }
let(:password) { 'my-secret' }
before {
Gitlab.config.ldap['enabled'] = true
Gitlab.config.ldap['user_filter'] = 'employeeType=developer'
}
after { Gitlab.config.ldap['enabled'] = false }
it "send an authentication request to ldap" do
expect( Gitlab::LDAP::User.adapter ).to receive(:bind_as)
Gitlab::LDAP::User.authenticate(login, password)
end
end
end
......@@ -346,6 +346,25 @@ describe User do
end
end
describe :ldap_user? do
let(:user) { build(:user, :ldap) }
it "is true if provider name starts with ldap" do
user.provider = 'ldapmain'
expect( user.ldap_user? ).to be_true
end
it "is false for other providers" do
user.provider = 'other-provider'
expect( user.ldap_user? ).to be_false
end
it "is false if no extern_uid is provided" do
user.extern_uid = nil
expect( user.ldap_user? ).to be_false
end
end
describe '#full_website_url' do
let(:user) { create(:user) }
......
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