Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement LDAP authentication for GL3 #5483

Open
wants to merge 4 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions Gemfile
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ gem 'jsbundling-rails', '>= 1.2.2'
gem 'jwt'
gem 'mini_magick', '>= 4.9.5'
gem 'omniauth', '~> 2.1.2'
gem 'omniauth-ldap', git: 'https://github.com/Ithanil/omniauth-ldap'
gem 'omniauth_openid_connect', '>= 0.6.1'
gem 'omniauth-rails_csrf_protection', '~> 1.0.2'
gem 'pagy', '~> 6.0', '>= 6.0.0'
Expand Down
15 changes: 15 additions & 0 deletions Gemfile.lock
Original file line number Diff line number Diff line change
@@ -1,3 +1,13 @@
GIT
remote: https://github.com/Ithanil/omniauth-ldap
revision: 0e8852ecd28b4293245683348333af2fd8a1916f
specs:
omniauth-ldap (2.1.0)
net-ldap (~> 0.16)
omniauth (~> 2.1.0)
pyu-ruby-sasl (~> 0.0.3.3)
rubyntlm (~> 0.6.2)

GEM
remote: https://rubygems.org/
specs:
Expand Down Expand Up @@ -270,6 +280,7 @@ GEM
net-imap (0.4.12)
date
net-protocol
net-ldap (0.18.0)
net-pop (0.1.2)
net-protocol
net-protocol (0.2.2)
Expand Down Expand Up @@ -317,6 +328,7 @@ GEM
public_suffix (5.0.3)
puma (5.6.8)
nio4r (~> 2.0)
pyu-ruby-sasl (0.0.3.3)
racc (1.8.0)
rack (2.2.9)
rack-oauth2 (2.2.0)
Expand Down Expand Up @@ -430,6 +442,8 @@ GEM
ruby-progressbar (1.13.0)
ruby-vips (2.1.4)
ffi (~> 1.12)
ruby2_keywords (0.0.5)
rubyntlm (0.6.3)
rubyzip (2.3.2)
selenium-webdriver (4.8.0)
rexml (~> 3.2, >= 3.2.5)
Expand Down Expand Up @@ -529,6 +543,7 @@ DEPENDENCIES
lograge (~> 0.14.0)
mini_magick (>= 4.9.5)
omniauth (~> 2.1.2)
omniauth-ldap!
omniauth-rails_csrf_protection (~> 1.0.2)
omniauth_openid_connect (>= 0.6.1)
pagy (~> 6.0, >= 6.0.0)
Expand Down
10 changes: 6 additions & 4 deletions app/controllers/api/v1/api_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -89,11 +89,13 @@ def config_sorting(allowed_columns: [])
{ sort_column => sort_direction }
end

# Checks if external authentication is enabled (currently only OIDC is implemented)
# Checks if external authentication is enabled
def external_auth?
return ENV['OPENID_CONNECT_ISSUER'].present? if ENV['LOADBALANCER_ENDPOINT'].blank?

!Tenant.exists?(name: current_provider, client_secret: 'local')
if ENV['LOADBALANCER_ENDPOINT'].blank?
ENV['OPENID_CONNECT_ISSUER'].present? || ENV['LDAP_SERVER'].present?
else
!Tenant.exists?(name: current_provider, client_secret: 'local')
end
end
end
end
Expand Down
6 changes: 6 additions & 0 deletions app/controllers/external_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,12 @@ def create_user
redirect_to root_path(error: Rails.configuration.custom_error_msgs[:external_signup_error])
end

# GET /auth/failure
# Provide the user with a proper error message in case of external authentication failure
def auth_failure
redirect_to root_path(error: Rails.configuration.custom_error_msgs[:external_signup_error])
end

# POST /recording_ready
# Creates the recording in Greenlight using information received from BigBlueButton
def recording_ready
Expand Down
63 changes: 40 additions & 23 deletions config/initializers/omniauth.rb
Original file line number Diff line number Diff line change
Expand Up @@ -17,34 +17,40 @@
# frozen_string_literal: true

Rails.application.config.middleware.use OmniAuth::Builder do
issuer = ENV.fetch('OPENID_CONNECT_ISSUER', '')
oidc_issuer = ENV.fetch('OPENID_CONNECT_ISSUER', '')
ldap_server = ENV.fetch('LDAP_SERVER', '')

lb = ENV.fetch('LOADBALANCER_ENDPOINT', '')

if lb.present?
provider :openid_connect, setup: lambda { |env|
request = Rack::Request.new(env)
current_provider = request.params['current_provider'] || request.host&.split('.')&.first
secret = Tenant.find_by(name: current_provider)&.client_secret
issuer_url = File.join issuer.to_s, "/#{current_provider}"
if oidc_issuer.present?
# OpenID Connect with LB
provider :openid_connect, setup: lambda { |env|
request = Rack::Request.new(env)
current_provider = request.params['current_provider'] || request.host&.split('.')&.first
secret = Tenant.find_by(name: current_provider)&.client_secret
issuer_url = File.join oidc_issuer.to_s, "/#{current_provider}"

env['omniauth.strategy'].options[:issuer] = issuer_url
env['omniauth.strategy'].options[:scope] = %i[openid email profile]
env['omniauth.strategy'].options[:uid_field] = ENV.fetch('OPENID_CONNECT_UID_FIELD', 'sub')
env['omniauth.strategy'].options[:discovery] = true
env['omniauth.strategy'].options[:client_options].identifier = ENV.fetch('OPENID_CONNECT_CLIENT_ID')
env['omniauth.strategy'].options[:client_options].secret = secret
env['omniauth.strategy'].options[:client_options].redirect_uri = File.join(
File.join('https://', "#{current_provider}.#{ENV.fetch('OPENID_CONNECT_REDIRECT', '')}", 'auth', 'openid_connect', 'callback')
)
env['omniauth.strategy'].options[:client_options].authorization_endpoint = File.join(issuer_url, 'protocol', 'openid-connect', 'auth')
env['omniauth.strategy'].options[:client_options].token_endpoint = File.join(issuer_url, 'protocol', 'openid-connect', 'token')
env['omniauth.strategy'].options[:client_options].userinfo_endpoint = File.join(issuer_url, 'protocol', 'openid-connect', 'userinfo')
env['omniauth.strategy'].options[:client_options].jwks_uri = File.join(issuer_url, 'protocol', 'openid-connect', 'certs')
env['omniauth.strategy'].options[:client_options].end_session_endpoint = File.join(issuer_url, 'protocol', 'openid-connect', 'logout')
}
elsif issuer.present?
env['omniauth.strategy'].options[:issuer] = issuer_url
env['omniauth.strategy'].options[:scope] = %i[openid email profile]
env['omniauth.strategy'].options[:uid_field] = ENV.fetch('OPENID_CONNECT_UID_FIELD', 'sub')
env['omniauth.strategy'].options[:discovery] = true
env['omniauth.strategy'].options[:client_options].identifier = ENV.fetch('OPENID_CONNECT_CLIENT_ID')
env['omniauth.strategy'].options[:client_options].secret = secret
env['omniauth.strategy'].options[:client_options].redirect_uri = File.join(
File.join('https://', "#{current_provider}.#{ENV.fetch('OPENID_CONNECT_REDIRECT', '')}", 'auth', 'openid_connect', 'callback')
)
env['omniauth.strategy'].options[:client_options].authorization_endpoint = File.join(issuer_url, 'protocol', 'openid-connect', 'auth')
env['omniauth.strategy'].options[:client_options].token_endpoint = File.join(issuer_url, 'protocol', 'openid-connect', 'token')
env['omniauth.strategy'].options[:client_options].userinfo_endpoint = File.join(issuer_url, 'protocol', 'openid-connect', 'userinfo')
env['omniauth.strategy'].options[:client_options].jwks_uri = File.join(issuer_url, 'protocol', 'openid-connect', 'certs')
env['omniauth.strategy'].options[:client_options].end_session_endpoint = File.join(issuer_url, 'protocol', 'openid-connect', 'logout')
}
end
elsif oidc_issuer.present?
# OpenID Connect
provider :openid_connect,
issuer:,
issuer: oidc_issuer,
scope: %i[openid email profile],
uid_field: ENV.fetch('OPENID_CONNECT_UID_FIELD', 'sub'),
discovery: true,
Expand All @@ -53,5 +59,16 @@
secret: ENV.fetch('OPENID_CONNECT_CLIENT_SECRET'),
redirect_uri: File.join(ENV.fetch('OPENID_CONNECT_REDIRECT', ''), 'auth', 'openid_connect', 'callback')
}
elsif ldap_server.present?
# LDAP
provider :ldap,
host: ldap_server,
title: ENV.fetch('LDAP_TITLE', nil),
port: ENV.fetch('LDAP_PORT', 389),
method: ENV.fetch('LDAP_METHOD', :plain),
base: ENV.fetch('LDAP_BASE', ''),
uid: ENV.fetch('LDAP_UID', ''),
bind_dn: ENV.fetch('LDAP_BIND_DN', ''),
password: ENV.fetch('LDAP_PASSWORD', nil)
end
end
2 changes: 2 additions & 0 deletions config/routes.rb
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,8 @@

# External requests
get '/auth/:provider/callback', to: 'external#create_user'
post '/auth/:provider/callback', to: 'external#create_user'
get '/auth/failure', to: 'external#auth_failure'
get '/meeting_ended', to: 'external#meeting_ended'
post '/recording_ready', to: 'external#recording_ready'

Expand Down
4 changes: 3 additions & 1 deletion esbuild.dev.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@ import * as esbuild from 'esbuild';

// Fetch 'RELATIVE_URL_ROOT' ENV variable value while removing any trailing slashes.
const relativeUrlRoot = (process.env.RELATIVE_URL_ROOT || '').replace(/\/*$/, '');
// Determine whether LDAP is used (OIDC takes precedence)
const useLDAP = (process.env.LDAP_SERVER && !process.env.OPENID_CONNECT_ISSUER);

esbuild.context({
entryPoints: ['app/javascript/main.jsx'],
Expand All @@ -14,7 +16,7 @@ esbuild.context({
},
define: {
'process.env.RELATIVE_URL_ROOT': `"${relativeUrlRoot}"`,
'process.env.OMNIAUTH_PATH': `"${relativeUrlRoot}/auth/openid_connect"`, // currently, only OIDC is implemented
'process.env.OMNIAUTH_PATH': useLDAP ? `"${relativeUrlRoot}/auth/ldap"` : `"${relativeUrlRoot}/auth/openid_connect"`,
},
}).then(context => {
if (process.argv.includes("--watch")) {
Expand Down
4 changes: 3 additions & 1 deletion esbuild.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@ import * as esbuild from 'esbuild';

// Fetch 'RELATIVE_URL_ROOT' ENV variable value while removing any trailing slashes.
const relativeUrlRoot = (process.env.RELATIVE_URL_ROOT || '').replace(/\/*$/, '');
// Determine whether LDAP is used (OIDC takes precedence)
const useLDAP = (process.env.LDAP_SERVER && !process.env.OPENID_CONNECT_ISSUER);

await esbuild.build({
entryPoints: ['app/javascript/main.jsx'],
Expand All @@ -14,7 +16,7 @@ await esbuild.build({
},
define: {
'process.env.RELATIVE_URL_ROOT': `"${relativeUrlRoot}"`,
'process.env.OMNIAUTH_PATH': `"${relativeUrlRoot}/auth/openid_connect"`, // currently, only OIDC is implemented
'process.env.OMNIAUTH_PATH': useLDAP ? `"${relativeUrlRoot}/auth/ldap"` : `"${relativeUrlRoot}/auth/openid_connect"`,
},
});

Expand Down
14 changes: 14 additions & 0 deletions sample.env
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,20 @@ REDIS_URL=
# More information: https://github.com/bigbluebutton/greenlight/issues/5872
#USE_EMAIL_AS_EXTERNAL_ID_FALLBACK=true

# LDAP Login Provider
#
# You can enable LDAP authentication by providing values for the variables below.
# For some documentation, see here: https://github.com/omniauth/omniauth-ldap
# LDAP_TITLE="Example-LDAP"
# LDAP_SERVER=ldap.example.com
# LDAP_PORT=389
# LDAP_METHOD=plain
# LDAP_UID=uid
# LDAP_BASE=dc=example,dc=com
# LDAP_AUTH=simple
# LDAP_BIND_DN=cn=admin,dc=example,dc=com
# LDAP_PASSWORD=password

# To enable hCaptcha on the user sign up and sign in, define these 2 keys
# More information: https://docs.bigbluebutton.org/greenlight/v3/install/#hcaptcha-setup
#HCAPTCHA_SITE_KEY=
Expand Down
Loading