Browse Source

Fixes #4471 - Support LDAP interfaces that always need authentication (e.g. OKTA LDAP).

Co-authored-by: Dominik Klein <dk@zammad.com>
Co-authored-by: Florian Liebe <fl@zammad.com>
Florian Liebe 1 year ago
parent
commit
78edc3211c

+ 21 - 1
app/assets/javascripts/app/controllers/_integration/ldap.coffee

@@ -447,6 +447,9 @@ class ConnectionWizard extends App.ControllerWizardModal
           @showAlert('js-discover', data.message)
           return
 
+        if !_.isEmpty(data.error) && data.error is 'disallow-bind-anon'
+          @wizardConfig.disallow_bind_anon = true
+
         @wizardConfig.host       = params.host
         @wizardConfig.ssl        = params.ssl
         @wizardConfig.ssl_verify = params.ssl_verify
@@ -479,7 +482,24 @@ class ConnectionWizard extends App.ControllerWizardModal
 
   bindShow: (alreadyShown) =>
     @showSlide('js-bind') if !alreadyShown
-    @$('.js-bind .js-baseDn').html(@createSelection('base_dn', @wizardConfig.options, @wizardConfig.base_dn || @wizardConfig.option, true))
+
+    if @wizardConfig.disallow_bind_anon
+      baseDnInput = App.UiElement.input.render(
+        name: 'base_dn'
+        id: 'base_dn'
+        display: __('Base DN')
+        tag: 'input'
+        type: 'text'
+        class: 'form-control--small js-baseDn'
+        required: 'required'
+        placeholder: ''
+        value: @wizardConfig.base_dn
+        autocomplete: 'autocomplete="off"'
+      )[0].outerHTML
+      @$('.js-bind .js-baseDn').html(baseDnInput)
+    else
+      @$('.js-bind .js-baseDn').html(@createSelection('base_dn', @wizardConfig.options, @wizardConfig.base_dn || @wizardConfig.option, true))
+
     @$('.js-bind input[name="bind_user"]').val(@wizardConfig.bind_user)
     @$('.js-bind input[name="bind_pw"]').val(@wizardConfig.bind_pw)
 

+ 6 - 6
app/controllers/integration/ldap_controller.rb

@@ -5,6 +5,11 @@ class Integration::LdapController < ApplicationController
 
   prepend_before_action :authenticate_and_authorize!
 
+  EXCEPTIONS_SPECIAL_TREATMENT = {
+    '48, Inappropriate Authentication' => {}, # workaround for issue #1114
+    '53, Unwilling to perform'         => { error: 'disallow-bind-anon' },
+  }.freeze
+
   def discover
     answer_with do
 
@@ -14,12 +19,7 @@ class Integration::LdapController < ApplicationController
         attributes: ldap.preferences
       }
     rescue => e
-      # workaround for issue #1114
-      raise if !e.message.end_with?(', 48, Inappropriate Authentication')
-
-      # return empty result
-      {}
-
+      EXCEPTIONS_SPECIAL_TREATMENT.find { |msg, _| e.message.ends_with?(msg) }&.last || raise
     end
   end
 

+ 1 - 0
i18n/zammad.pot

@@ -1712,6 +1712,7 @@ msgstr ""
 msgid "Base"
 msgstr ""
 
+#: app/assets/javascripts/app/controllers/_integration/ldap.coffee
 #: app/assets/javascripts/app/views/integration/ldap.jst.eco
 #: app/assets/javascripts/app/views/integration/ldap_wizard.jst.eco
 msgid "Base DN"

+ 31 - 0
spec/requests/integration/ldap_spec.rb

@@ -0,0 +1,31 @@
+# Copyright (C) 2012-2023 Zammad Foundation, https://zammad-foundation.org/
+
+require 'rails_helper'
+
+RSpec.describe 'Ldap', type: :request do
+  let!(:admin) do
+    create(:admin, groups: Group.all)
+  end
+
+  describe 'discover' do
+    let(:params) do
+      {
+        name:       'Example LDAP',
+        host:       'example.ldap.okta.com',
+        ssl:        'ssl',
+        ssl_verify: true,
+        active:     'true'
+      }
+    end
+
+    context 'when disallow bin anon is active' do
+      it 'returns special exception treatment for not allowed anonymous bind' do
+        authenticated_as(admin)
+
+        post '/api/v1/integration/ldap/discover', params: params, as: :json
+
+        expect(json_response).to eq('result' => 'ok', 'error' => 'disallow-bind-anon')
+      end
+    end
+  end
+end

+ 36 - 0
spec/system/system/integration/ldap_spec.rb

@@ -0,0 +1,36 @@
+# Copyright (C) 2012-2023 Zammad Foundation, https://zammad-foundation.org/
+
+require 'rails_helper'
+
+RSpec.describe 'Manage > Integration > Ldap', type: :system do
+  before do
+    visit 'system/integration/ldap'
+  end
+
+  def open_ldap_wizard
+    click_on 'New Source'
+
+    modal_ready
+  end
+
+  context 'when new source will be added with the wizard' do
+    context 'when no anonymous bind is allowed' do
+      it 'can insert base dn in normal text field' do
+        open_ldap_wizard
+
+        in_modal do
+          fill_in 'name', with: 'Example LDAP'
+          fill_in 'host', with: 'example.ldap.okta.com'
+
+          click_on 'Connect'
+
+          wait.until { find('input[name="base_dn"]').present? }
+
+          fill_in 'base_dn', with: 'dc=example,dc=okta,dc=com'
+
+          click '.js-close'
+        end
+      end
+    end
+  end
+end