# frozen_string_literal: true module LdapLogin::Login require 'net/ldap' require 'resolv' LDAP_DOMAIN = 'CTX10.NFU.EDU.TW' LDAP_SRV = '_ldap._tcp.dc._msdcs.ctx10.nfu.edu.tw' def ldap_login_auth(user, request, session, flash, params) Rails.logger.info '[LDAP] ===== login start =====' ldap_hosts = fetch_ldap_hosts ldap_user = params[:user_name].to_s.strip ldap_pass = params[:password].to_s error = '' login_flag = false url = '/' url_method = 'redirect_to' Rails.logger.info "[LDAP] login user=#{ldap_user}" if ldap_pass.blank? error = '請輸入密碼' else ldap_hosts.each do |ldap_host| Rails.logger.info "[LDAP] try host=#{ldap_host}" begin ldap = Net::LDAP.new( host: ldap_host, port: 389, auth: { method: :simple, username: "#{ldap_user}@#{LDAP_DOMAIN}", password: ldap_pass } ) if ldap.bind Rails.logger.info "[LDAP] bind success (#{ldap_host})" if user.present? session[:user_id] = user.id session[:login_referer] = nil if params[:referer_url].present? url = URI.parse(params[:referer_url]).path else url = admin_dashboards_path end login_flag = true break else error = I18n.t('devise.failure.ldap_pass_but_account_not_in_orbit') break end else Rails.logger.warn "[LDAP] bind failed (#{ldap_host})" error = ldap_error_message(ldap.get_operation_result) # ⚠️ 不 break,繼續嘗試下一台 DC next end rescue Net::LDAP::ConnectionError => e Rails.logger.error "[LDAP] connection error (#{ldap_host}) #{e.message}" error = '無法連線至 AD 伺服器' next rescue => e Rails.logger.error "[LDAP] unknown error #{e.class}: #{e.message}" error = '發生不可預知的錯誤' break end end end unless login_flag flash.now.alert = error.presence || 'AD 驗證失敗' url = 'new' url_method = 'render' end [login_flag, session, flash, url, url_method] end private # 透過 SRV 取得 AD DC 清單 def fetch_ldap_hosts Rails.logger.info "[LDAP] fetch SRV record #{LDAP_SRV}" records = Resolv::DNS.open do |dns| dns.getresources(LDAP_SRV, Resolv::DNS::Resource::IN::SRV) end hosts = records.map { |r| r.target.to_s.chomp('.') } Rails.logger.info "[LDAP] SRV result #{hosts}" hosts.shuffle rescue => e Rails.logger.error "[LDAP] SRV lookup failed #{e.message}" [] end # AD / LDAP 錯誤碼轉換 def ldap_error_message(result) return 'AD 驗證失敗' if result.nil? msg = [ result.error_message, result.message ].compact.join(' ') Rails.logger.info "[LDAP ERROR RAW] #{msg}" if msg =~ /data\s+([0-9a-fA-F]{3})/i case Regexp.last_match(1).downcase when '530' then '不允許在此時間登入' when '701' then '帳號已過期' when '533' then '帳號停用中!' when '532' then '密碼過期!' when '52e' then '帳號或密碼錯誤' when '525' then '帳號不存在' when '775' then '帳號已被鎖定(輸入錯誤超過次數)' when '773' then '使用者密碼必須重置' else 'AD 驗證失敗' end else 'AD 驗證失敗' end end end