diff --git a/Gemfile b/Gemfile index 33b61aca..2e77d0e3 100644 --- a/Gemfile +++ b/Gemfile @@ -45,8 +45,9 @@ gem 'omniauth' gem 'omniauth-twitter' gem 'omniauth-google-oauth2' gem 'omniauth-bn-office365', git: 'https://github.com/blindsidenetworks/omniauth-bn-office365.git', tag: '0.1.0' -gem 'omniauth-ldap' -gem 'omniauth-bn-launcher', git: 'https://github.com/blindsidenetworks/omniauth-bn-launcher.git', tag: '0.1.0' +gem 'omniauth-bn-launcher', git: 'https://github.com/blindsidenetworks/omniauth-bn-launcher.git', tag: '0.1.1' +gem 'bn-ldap-authentication', git: 'https://github.com/blindsidenetworks/bn-ldap-authentication.git' +gem 'net-ldap' # BigBlueButton API wrapper. gem 'bigbluebutton-api-ruby' diff --git a/Gemfile.lock b/Gemfile.lock index 44e22c68..9bc6b0b0 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -1,9 +1,16 @@ +GIT + remote: https://github.com/blindsidenetworks/bn-ldap-authentication.git + revision: 538132e0df70dbe470120f7bc7a93968c522031f + specs: + bn-ldap-authentication (1.0.0) + net-ldap + GIT remote: https://github.com/blindsidenetworks/omniauth-bn-launcher.git - revision: 0d0597bec9aed63b9c9d44e99e16353c5d587d48 - tag: 0.1.0 + revision: 025785046c3d532ed2252ef4762469c8d08d4839 + tag: 0.1.1 specs: - omniauth-bn-launcher (0.1.0) + omniauth-bn-launcher (0.1.1) omniauth (~> 1.3, >= 1.3.2) omniauth-oauth2 (= 1.5.0) @@ -165,11 +172,6 @@ GEM jwt (>= 2.0) omniauth (>= 1.1.1) omniauth-oauth2 (>= 1.5) - omniauth-ldap (1.0.5) - net-ldap (~> 0.12) - omniauth (~> 1.0) - pyu-ruby-sasl (~> 0.0.3.2) - rubyntlm (~> 0.3.4) omniauth-oauth (1.1.0) oauth omniauth (~> 1.0) @@ -187,7 +189,6 @@ GEM popper_js (1.14.5) public_suffix (3.0.3) puma (3.12.1) - pyu-ruby-sasl (0.0.3.3) rack (2.0.7) rack-test (0.6.3) rack (>= 1.0) @@ -256,7 +257,6 @@ GEM ruby-progressbar (~> 1.7) unicode-display_width (>= 1.4.0, < 1.7) ruby-progressbar (1.10.0) - rubyntlm (0.3.4) safe_yaml (1.0.5) sass (3.7.4) sass-listen (~> 4.0.0) @@ -336,6 +336,7 @@ DEPENDENCIES action-cable-testing bcrypt (~> 3.1.7) bigbluebutton-api-ruby + bn-ldap-authentication! bootstrap (~> 4.3.1) byebug cancancan (~> 2.0) @@ -351,11 +352,11 @@ DEPENDENCIES jquery-rails (~> 4.3.3) listen (~> 3.0.5) mini_racer + net-ldap omniauth omniauth-bn-launcher! omniauth-bn-office365! omniauth-google-oauth2 - omniauth-ldap omniauth-twitter pagy pg (~> 0.18) diff --git a/app/assets/images/ldap-logo.png b/app/assets/images/ldap-logo.png new file mode 100644 index 00000000..f11320ef Binary files /dev/null and b/app/assets/images/ldap-logo.png differ diff --git a/app/assets/stylesheets/main.scss b/app/assets/stylesheets/main.scss index 42e41209..4271ade6 100755 --- a/app/assets/stylesheets/main.scss +++ b/app/assets/stylesheets/main.scss @@ -145,6 +145,17 @@ } } +.customBtn-ldap { + @extend .customBtn; + background: #d61515; + + .customBtn-image { + background: #ffffff image-url("ldap-logo.png") no-repeat left top; + background-size: 18px 18px; + padding:10px 10px 10px 10px; + } +} + .signin-button { font-size: 16px; } diff --git a/app/controllers/sessions_controller.rb b/app/controllers/sessions_controller.rb index 678ea8a0..f4de6416 100644 --- a/app/controllers/sessions_controller.rb +++ b/app/controllers/sessions_controller.rb @@ -19,6 +19,7 @@ class SessionsController < ApplicationController include Registrar include Emailer + include LdapAuthenticator skip_before_action :verify_authenticity_token, only: [:omniauth, :fail] @@ -47,8 +48,65 @@ class SessionsController < ApplicationController # GET/POST /auth/:provider/callback def omniauth + @auth = request.env['omniauth.auth'] + + process_signin + end + + # POST /auth/failure + def omniauth_fail + redirect_to root_path, alert: I18n.t(params[:message], default: I18n.t("omniauth_error")) + end + + # GET /auth/ldap + def ldap + ldap_config = {} + ldap_config[:host] = ENV['LDAP_SERVER'] + ldap_config[:port] = ENV['LDAP_PORT'].to_i != 0 ? ENV['LDAP_PORT'].to_i : 389 + ldap_config[:bind_dn] = ENV['LDAP_BIND_DN'] + ldap_config[:password] = ENV['LDAP_PASSWORD'] + ldap_config[:encryption] = if ENV['LDAP_METHOD'] == 'ssl' + 'simple_tls' + elsif ENV['LDAP_METHOD'] == 'tls' + 'start_tls' + end + ldap_config[:base] = ENV['LDAP_BASE'] + ldap_config[:uid] = ENV['LDAP_UID'] + + result = send_ldap_request(params[:session], ldap_config) + + if result + result = result.first + else + return redirect_to(ldap_signin_path, alert: I18n.t("invalid_credentials")) + end + + @auth = parse_auth(result) + + process_signin + end + + private + + def session_params + params.require(:session).permit(:email, :password) + end + + def check_user_exists + provider = @auth['provider'] == "bn_launcher" ? @auth['info']['customer'] : @auth['provider'] + User.exists?(social_uid: @auth['uid'], provider: provider) + end + + # Check if the user already exists, if not then check for invitation + def passes_invite_reqs + return true if @user_exists + + invitation = check_user_invited("", session[:invite_token], @user_domain) + invitation[:present] + end + + def process_signin begin - @auth = request.env['omniauth.auth'] @user_exists = check_user_exists if !@user_exists && @auth['provider'] == "twitter" @@ -89,28 +147,4 @@ class SessionsController < ApplicationController omniauth_fail end end - - # POST /auth/failure - def omniauth_fail - redirect_to root_path, alert: I18n.t(params[:message], default: I18n.t("omniauth_error")) - end - - private - - def session_params - params.require(:session).permit(:email, :password) - end - - def check_user_exists - provider = @auth['provider'] == "bn_launcher" ? @auth['info']['customer'] : @auth['provider'] - User.exists?(social_uid: @auth['uid'], provider: provider) - end - - # Check if the user already exists, if not then check for invitation - def passes_invite_reqs - return true if @user_exists - - invitation = check_user_invited("", session[:invite_token], @user_domain) - invitation[:present] - end end diff --git a/app/controllers/users_controller.rb b/app/controllers/users_controller.rb index 3883a584..dd89d633 100644 --- a/app/controllers/users_controller.rb +++ b/app/controllers/users_controller.rb @@ -68,6 +68,10 @@ class UsersController < ApplicationController end end + # GET /ldap_signin + def ldap_signin + end + # GET /signup def new return redirect_to root_path unless Rails.configuration.allow_user_signup diff --git a/app/helpers/application_helper.rb b/app/helpers/application_helper.rb index 248ebbf8..eb3ebf65 100644 --- a/app/helpers/application_helper.rb +++ b/app/helpers/application_helper.rb @@ -33,7 +33,7 @@ module ApplicationHelper # Determines which providers can show a login button in the login modal. def iconset_providers - providers = configured_providers & [:google, :twitter, :microsoft_office365] + providers = configured_providers & [:google, :twitter, :microsoft_office365, :ldap] providers.delete(:twitter) if session[:old_twitter_user_id] @@ -42,7 +42,11 @@ module ApplicationHelper # Generates the login URL for a specific provider. def omniauth_login_url(provider) - "#{Rails.configuration.relative_url_root}/auth/#{provider}" + if provider == :ldap + ldap_signin_path + else + "#{Rails.configuration.relative_url_root}/auth/#{provider}" + end end # Determine if Greenlight is configured to allow user signups. diff --git a/app/views/shared/_header.html.erb b/app/views/shared/_header.html.erb index a11211df..1228e2b4 100755 --- a/app/views/shared/_header.html.erb +++ b/app/views/shared/_header.html.erb @@ -72,9 +72,7 @@ <% else %> <% allow_greenlight_accounts = allow_greenlight_accounts? %> - <% if Rails.configuration.omniauth_ldap %> - <%= link_to t("login"), omniauth_login_url(:ldap), :class => "btn btn-outline-primary mx-2 sign-in-button" %> - <% elsif allow_greenlight_accounts %> + <% if allow_greenlight_accounts %> <%= link_to t("login"), signin_path, :class => "btn btn-outline-primary mx-2 sign-in-button" %> <% elsif Rails.configuration.loadbalanced_configuration %> <%= link_to t("login"), omniauth_login_url(:bn_launcher), :class => "btn btn-outline-primary mx-2 sign-in-button" %> diff --git a/app/views/users/ldap_signin.html.erb b/app/views/users/ldap_signin.html.erb new file mode 100644 index 00000000..3d7c301c --- /dev/null +++ b/app/views/users/ldap_signin.html.erb @@ -0,0 +1,34 @@ +
+
+
+
+
+

<%= t("login_title") %>

+
+
+ <%= form_for(:session, url: ldap_callback_path) do |f| %> +
+
+ + + + <%= f.text_field :username, class: "form-control", placeholder: t("administrator.users.table.username"), value: "" %> +
+
+
+
+ + + + <%= f.password_field :password, class: "form-control", placeholder: t("password"), value: "" %> +
+
+
+ <%= f.submit t("login"), class: "btn btn-primary btn-block signin-button" %> +
+ <% end %> +
+
+
+
+
\ No newline at end of file diff --git a/config/initializers/omniauth.rb b/config/initializers/omniauth.rb index d601fb3f..b046590a 100644 --- a/config/initializers/omniauth.rb +++ b/config/initializers/omniauth.rb @@ -15,9 +15,6 @@ Rails.application.config.omniauth_google = ENV['GOOGLE_OAUTH2_ID'].present? && E Rails.application.config.omniauth_office365 = ENV['OFFICE365_KEY'].present? && ENV['OFFICE365_SECRET'].present? -# If LDAP is enabled, override and disable allow_user_signup. -Rails.application.config.allow_user_signup = false if Rails.application.config.omniauth_ldap - SETUP_PROC = lambda do |env| SessionsController.helpers.omniauth_options env end @@ -29,19 +26,9 @@ Rails.application.config.middleware.use OmniAuth::Builder do client_secret: ENV['CLIENT_SECRET'], client_options: { site: ENV['BN_LAUNCHER_URI'] || ENV['BN_LAUNCHER_REDIRECT_URI'] }, setup: SETUP_PROC - elsif Rails.configuration.omniauth_ldap - Rails.application.config.providers << :ldap - - provider :ldap, - host: ENV['LDAP_SERVER'], - port: ENV['LDAP_PORT'] || '389', - method: ENV['LDAP_METHOD'].blank? ? :plain : ENV['LDAP_METHOD'].to_sym, - allow_username_or_email_login: true, - uid: ENV['LDAP_UID'], - base: ENV['LDAP_BASE'], - bind_dn: ENV['LDAP_BIND_DN'], - password: ENV['LDAP_PASSWORD'] else + Rails.application.config.providers << :ldap if Rails.configuration.omniauth_ldap + if Rails.configuration.omniauth_twitter Rails.application.config.providers << :twitter @@ -69,27 +56,3 @@ end OmniAuth.config.on_failure = proc { |env| OmniAuth::FailureEndpoint.new(env).redirect_to_failure } - -# Work around beacuse callback_url option causes -# omniauth.auth to be nil in the authhash when -# authenticating with LDAP. -module OmniAuthLDAPExt - def request_phase - rel_root = ENV['RELATIVE_URL_ROOT'].present? ? ENV['RELATIVE_URL_ROOT'] : '/b' - - @callback_path = nil - path = options[:callback_path] - options[:callback_path] = "#{rel_root if Rails.env == 'production'}/auth/ldap/callback" - form = super - options[:callback_path] = path - form - end -end - -module OmniAuth - module Strategies - class LDAP - prepend OmniAuthLDAPExt - end - end -end diff --git a/config/locales/en.yml b/config/locales/en.yml index f821a047..fe338331 100755 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -302,6 +302,7 @@ en: google: Google office365: Office 365 twitter: Twitter + ldap: LDAP recaptcha: errors: recaptcha_unreachable: Oops, we failed to validate your reCAPTCHA response. Please try again. diff --git a/config/routes.rb b/config/routes.rb index a99d62cb..e0429db7 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -29,6 +29,7 @@ Rails.application.routes.draw do get '/signin', to: 'users#signin', as: :signin get '/signup', to: 'users#new', as: :signup post '/signup', to: 'users#create', as: :create_user + get '/ldap_signin', to: 'users#ldap_signin', as: :ldap_signin # Redirect to terms page match '/terms', to: 'users#terms', via: [:get, :post] @@ -88,6 +89,7 @@ Rails.application.routes.draw do # Handles Omniauth authentication. match '/auth/:provider/callback', to: 'sessions#omniauth', via: [:get, :post], as: :omniauth_session get '/auth/failure', to: 'sessions#omniauth_fail' + post '/auth/ldap', to: 'sessions#ldap', as: :ldap_callback # Room resources. resources :rooms, only: [:create, :show, :destroy], param: :room_uid, path: '/' diff --git a/spec/controllers/sessions_controller_spec.rb b/spec/controllers/sessions_controller_spec.rb index 3ea499c5..d8f2bbc3 100644 --- a/spec/controllers/sessions_controller_spec.rb +++ b/spec/controllers/sessions_controller_spec.rb @@ -336,4 +336,41 @@ describe SessionsController, type: :controller do expect(response).to redirect_to(root_path) end end + + describe "POST #ldap" do + it "should create and login a user with a ldap login" do + entry = Net::LDAP::Entry.new("cn=Test User,ou=people,dc=planetexpress,dc=com") + entry[:cn] = "Test User" + entry[:givenName] = "Test" + entry[:sn] = "User" + entry[:mail] = "test@example.com" + allow_any_instance_of(Net::LDAP).to receive(:bind_as).and_return([entry]) + + post :ldap, params: { + session: { + user: "test", + password: 'password', + }, + } + + u = User.last + expect(u.provider).to eql("ldap") + expect(u.email).to eql("test@example.com") + expect(@request.session[:user_id]).to eql(u.id) + end + + it "should redirect to signin on invalid credentials" do + allow_any_instance_of(Net::LDAP).to receive(:bind_as).and_return(false) + + post :ldap, params: { + session: { + user: "test", + password: 'passwor', + }, + } + + expect(response).to redirect_to(ldap_signin_path) + expect(flash[:alert]).to eq(I18n.t("invalid_credentials")) + end + end end diff --git a/spec/controllers/users_controller_spec.rb b/spec/controllers/users_controller_spec.rb index 5622dcb1..42bc6103 100644 --- a/spec/controllers/users_controller_spec.rb +++ b/spec/controllers/users_controller_spec.rb @@ -387,4 +387,12 @@ describe UsersController, type: :controller do expect(response).to redirect_to(root_path) end end + + context 'GET #ldap_signin' do + it "should render the ldap signin page" do + get :ldap_signin + + expect(response).to render_template(:ldap_signin) + end + end end