activeldap3 1.2.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/.gitignore +6 -0
- data/CHANGES +691 -0
- data/COPYING +340 -0
- data/LICENSE +58 -0
- data/README +154 -0
- data/Rakefile +121 -0
- data/TODO +32 -0
- data/activeldap3.gemspec +18 -0
- data/benchmark/bench-al.rb +263 -0
- data/benchmark/config.yaml.sample +5 -0
- data/examples/al-admin/README +182 -0
- data/examples/al-admin/Rakefile +16 -0
- data/examples/al-admin/app/controllers/account_controller.rb +60 -0
- data/examples/al-admin/app/controllers/application_controller.rb +46 -0
- data/examples/al-admin/app/controllers/attributes_controller.rb +17 -0
- data/examples/al-admin/app/controllers/directory_controller.rb +49 -0
- data/examples/al-admin/app/controllers/object_classes_controller.rb +17 -0
- data/examples/al-admin/app/controllers/syntaxes_controller.rb +17 -0
- data/examples/al-admin/app/controllers/users_controller.rb +66 -0
- data/examples/al-admin/app/controllers/welcome_controller.rb +12 -0
- data/examples/al-admin/app/helpers/account_helper.rb +2 -0
- data/examples/al-admin/app/helpers/application_helper.rb +46 -0
- data/examples/al-admin/app/helpers/attributes_helper.rb +15 -0
- data/examples/al-admin/app/helpers/directory_helper.rb +7 -0
- data/examples/al-admin/app/helpers/object_classes_helper.rb +10 -0
- data/examples/al-admin/app/helpers/syntaxes_helper.rb +10 -0
- data/examples/al-admin/app/helpers/url_helper.rb +17 -0
- data/examples/al-admin/app/helpers/users_helper.rb +17 -0
- data/examples/al-admin/app/helpers/welcome_helper.rb +2 -0
- data/examples/al-admin/app/models/entry.rb +23 -0
- data/examples/al-admin/app/models/ldap_user.rb +58 -0
- data/examples/al-admin/app/models/user.rb +91 -0
- data/examples/al-admin/app/views/_entry/_attributes_information.html.erb +29 -0
- data/examples/al-admin/app/views/_entry/_entry.html.erb +15 -0
- data/examples/al-admin/app/views/_schema/_aliases.html.erb +7 -0
- data/examples/al-admin/app/views/_switcher/_after.html.erb +2 -0
- data/examples/al-admin/app/views/_switcher/_before.html.erb +5 -0
- data/examples/al-admin/app/views/account/login.html.erb +26 -0
- data/examples/al-admin/app/views/account/sign_up.html.erb +29 -0
- data/examples/al-admin/app/views/attributes/_attributes.html.erb +19 -0
- data/examples/al-admin/app/views/attributes/_detail.html.erb +29 -0
- data/examples/al-admin/app/views/attributes/index.html.erb +3 -0
- data/examples/al-admin/app/views/attributes/show.html.erb +31 -0
- data/examples/al-admin/app/views/directory/_tree.html.erb +10 -0
- data/examples/al-admin/app/views/directory/_tree_view_js.html.erb +25 -0
- data/examples/al-admin/app/views/directory/index.html.erb +13 -0
- data/examples/al-admin/app/views/directory/populate.html.erb +2 -0
- data/examples/al-admin/app/views/layouts/_footer.html.erb +11 -0
- data/examples/al-admin/app/views/layouts/_header_menu.html.erb +10 -0
- data/examples/al-admin/app/views/layouts/_main_menu.html.erb +18 -0
- data/examples/al-admin/app/views/layouts/application.html.erb +57 -0
- data/examples/al-admin/app/views/object_classes/_attributes.html.erb +28 -0
- data/examples/al-admin/app/views/object_classes/_object_classes.html.erb +19 -0
- data/examples/al-admin/app/views/object_classes/index.html.erb +3 -0
- data/examples/al-admin/app/views/object_classes/show.html.erb +39 -0
- data/examples/al-admin/app/views/syntaxes/_detail.html.erb +14 -0
- data/examples/al-admin/app/views/syntaxes/_syntaxes.html.erb +19 -0
- data/examples/al-admin/app/views/syntaxes/index.html.erb +3 -0
- data/examples/al-admin/app/views/syntaxes/show.html.erb +22 -0
- data/examples/al-admin/app/views/users/_attributes_update_form.html.erb +42 -0
- data/examples/al-admin/app/views/users/_form.html.erb +13 -0
- data/examples/al-admin/app/views/users/_object_classes_update_form.html.erb +46 -0
- data/examples/al-admin/app/views/users/_password_change_form.html.erb +20 -0
- data/examples/al-admin/app/views/users/edit.html.erb +15 -0
- data/examples/al-admin/app/views/users/index.html.erb +10 -0
- data/examples/al-admin/app/views/users/show.html.erb +11 -0
- data/examples/al-admin/app/views/welcome/index.html.erb +13 -0
- data/examples/al-admin/config.ru +7 -0
- data/examples/al-admin/config/boot.rb +110 -0
- data/examples/al-admin/config/database.yml.example +19 -0
- data/examples/al-admin/config/environment.rb +73 -0
- data/examples/al-admin/config/environments/development.rb +17 -0
- data/examples/al-admin/config/environments/production.rb +24 -0
- data/examples/al-admin/config/environments/test.rb +22 -0
- data/examples/al-admin/config/initializers/exception_notifier.rb +2 -0
- data/examples/al-admin/config/initializers/fast_gettext.rb +3 -0
- data/examples/al-admin/config/initializers/inflections.rb +10 -0
- data/examples/al-admin/config/initializers/mime_types.rb +5 -0
- data/examples/al-admin/config/initializers/session_store.rb +23 -0
- data/examples/al-admin/config/ldap.yml.example +21 -0
- data/examples/al-admin/config/routes.rb +58 -0
- data/examples/al-admin/db/migrate/001_create_users.rb +16 -0
- data/examples/al-admin/doc/README_FOR_APP +2 -0
- data/examples/al-admin/lib/authenticated_system.rb +124 -0
- data/examples/al-admin/lib/authenticated_test_helper.rb +113 -0
- data/examples/al-admin/lib/ldap_test_helper.rb +38 -0
- data/examples/al-admin/lib/tasks/testing.rake +10 -0
- data/examples/al-admin/po/en/al-admin.po +343 -0
- data/examples/al-admin/po/ja/al-admin.po +343 -0
- data/examples/al-admin/po/nl/al-admin.po +380 -0
- data/examples/al-admin/public/.htaccess +40 -0
- data/examples/al-admin/public/404.html +30 -0
- data/examples/al-admin/public/500.html +30 -0
- data/examples/al-admin/public/dispatch.cgi +10 -0
- data/examples/al-admin/public/dispatch.fcgi +24 -0
- data/examples/al-admin/public/dispatch.rb +10 -0
- data/examples/al-admin/public/favicon.ico +0 -0
- data/examples/al-admin/public/images/active-ldap.svg +6351 -0
- data/examples/al-admin/public/images/al-admin.png +0 -0
- data/examples/al-admin/public/images/al-admin.svg +6371 -0
- data/examples/al-admin/public/images/header-background.png +0 -0
- data/examples/al-admin/public/images/header-background.svg +99 -0
- data/examples/al-admin/public/images/rails.png +0 -0
- data/examples/al-admin/public/images/spinelz/accordion_tab_left_active.gif +0 -0
- data/examples/al-admin/public/images/spinelz/accordion_tab_left_inactive.gif +0 -0
- data/examples/al-admin/public/images/spinelz/accordion_tab_middle_active.gif +0 -0
- data/examples/al-admin/public/images/spinelz/accordion_tab_middle_inactive.gif +0 -0
- data/examples/al-admin/public/images/spinelz/accordion_tab_right_active.gif +0 -0
- data/examples/al-admin/public/images/spinelz/accordion_tab_right_inactive.gif +0 -0
- data/examples/al-admin/public/images/spinelz/balloon_back.gif +0 -0
- data/examples/al-admin/public/images/spinelz/balloon_bottom_left.gif +0 -0
- data/examples/al-admin/public/images/spinelz/balloon_bottom_middle.gif +0 -0
- data/examples/al-admin/public/images/spinelz/balloon_bottom_right.gif +0 -0
- data/examples/al-admin/public/images/spinelz/balloon_left_down_arrow.gif +0 -0
- data/examples/al-admin/public/images/spinelz/balloon_left_up_arrow.gif +0 -0
- data/examples/al-admin/public/images/spinelz/balloon_middle_left.gif +0 -0
- data/examples/al-admin/public/images/spinelz/balloon_middle_right.gif +0 -0
- data/examples/al-admin/public/images/spinelz/balloon_right_down_arrow.gif +0 -0
- data/examples/al-admin/public/images/spinelz/balloon_right_up_arrow.gif +0 -0
- data/examples/al-admin/public/images/spinelz/balloon_top_left.gif +0 -0
- data/examples/al-admin/public/images/spinelz/balloon_top_middle.gif +0 -0
- data/examples/al-admin/public/images/spinelz/balloon_top_right.gif +0 -0
- data/examples/al-admin/public/images/spinelz/barchart_h.gif +0 -0
- data/examples/al-admin/public/images/spinelz/barchart_v.gif +0 -0
- data/examples/al-admin/public/images/spinelz/button.gif +0 -0
- data/examples/al-admin/public/images/spinelz/calendar_default_handler.gif +0 -0
- data/examples/al-admin/public/images/spinelz/calendar_delete.gif +0 -0
- data/examples/al-admin/public/images/spinelz/calendar_next.gif +0 -0
- data/examples/al-admin/public/images/spinelz/calendar_next_second.gif +0 -0
- data/examples/al-admin/public/images/spinelz/calendar_pre.gif +0 -0
- data/examples/al-admin/public/images/spinelz/calendar_pre_second.gif +0 -0
- data/examples/al-admin/public/images/spinelz/calendar_private_icon.gif +0 -0
- data/examples/al-admin/public/images/spinelz/calendar_schedule.gif +0 -0
- data/examples/al-admin/public/images/spinelz/calender_back.gif +0 -0
- data/examples/al-admin/public/images/spinelz/calender_back_second.gif +0 -0
- data/examples/al-admin/public/images/spinelz/datepicker2_back.gif +0 -0
- data/examples/al-admin/public/images/spinelz/datepicker2_back_second.gif +0 -0
- data/examples/al-admin/public/images/spinelz/datepicker2_next.gif +0 -0
- data/examples/al-admin/public/images/spinelz/datepicker2_next_second.gif +0 -0
- data/examples/al-admin/public/images/spinelz/datepicker2_pre.gif +0 -0
- data/examples/al-admin/public/images/spinelz/datepicker2_pre_second.gif +0 -0
- data/examples/al-admin/public/images/spinelz/datepicker_back.gif +0 -0
- data/examples/al-admin/public/images/spinelz/datepicker_back_second.gif +0 -0
- data/examples/al-admin/public/images/spinelz/datepicker_next.gif +0 -0
- data/examples/al-admin/public/images/spinelz/datepicker_next_second.gif +0 -0
- data/examples/al-admin/public/images/spinelz/datepicker_pre.gif +0 -0
- data/examples/al-admin/public/images/spinelz/datepicker_pre_second.gif +0 -0
- data/examples/al-admin/public/images/spinelz/grid_down.gif +0 -0
- data/examples/al-admin/public/images/spinelz/grid_state.gif +0 -0
- data/examples/al-admin/public/images/spinelz/grid_up.gif +0 -0
- data/examples/al-admin/public/images/spinelz/icon_day.gif +0 -0
- data/examples/al-admin/public/images/spinelz/icon_month.gif +0 -0
- data/examples/al-admin/public/images/spinelz/icon_week.gif +0 -0
- data/examples/al-admin/public/images/spinelz/menubar_back.gif +0 -0
- data/examples/al-admin/public/images/spinelz/menubar_subcontents_back.gif +0 -0
- data/examples/al-admin/public/images/spinelz/navPanel_tab_left_active.gif +0 -0
- data/examples/al-admin/public/images/spinelz/navPanel_tab_left_inactive.gif +0 -0
- data/examples/al-admin/public/images/spinelz/navPanel_tab_middle_active.gif +0 -0
- data/examples/al-admin/public/images/spinelz/navPanel_tab_middle_inactive.gif +0 -0
- data/examples/al-admin/public/images/spinelz/navPanel_tab_right_active.gif +0 -0
- data/examples/al-admin/public/images/spinelz/navPanel_tab_right_inactive.gif +0 -0
- data/examples/al-admin/public/images/spinelz/select_date.gif +0 -0
- data/examples/al-admin/public/images/spinelz/selectabletable_selected.gif +0 -0
- data/examples/al-admin/public/images/spinelz/sideBarBox_about.gif +0 -0
- data/examples/al-admin/public/images/spinelz/sideBarBox_menu.gif +0 -0
- data/examples/al-admin/public/images/spinelz/sideBarBox_sample.gif +0 -0
- data/examples/al-admin/public/images/spinelz/sideBarBox_tabBottomActive.gif +0 -0
- data/examples/al-admin/public/images/spinelz/sideBarBox_tabBottomInactive.gif +0 -0
- data/examples/al-admin/public/images/spinelz/sideBarBox_tabMiddleActive.gif +0 -0
- data/examples/al-admin/public/images/spinelz/sideBarBox_tabMiddleActive2.gif +0 -0
- data/examples/al-admin/public/images/spinelz/sideBarBox_tabMiddleInactive.gif +0 -0
- data/examples/al-admin/public/images/spinelz/sideBarBox_tabMiddleInactive2.gif +0 -0
- data/examples/al-admin/public/images/spinelz/sideBarBox_tabTopActive.gif +0 -0
- data/examples/al-admin/public/images/spinelz/sideBarBox_tabTopInactive.gif +0 -0
- data/examples/al-admin/public/images/spinelz/sideBarBox_tabbar.gif +0 -0
- data/examples/al-admin/public/images/spinelz/sortableTable_down.gif +0 -0
- data/examples/al-admin/public/images/spinelz/sortableTable_normal.gif +0 -0
- data/examples/al-admin/public/images/spinelz/sortableTable_up.gif +0 -0
- data/examples/al-admin/public/images/spinelz/switcher_close.gif +0 -0
- data/examples/al-admin/public/images/spinelz/switcher_open.gif +0 -0
- data/examples/al-admin/public/images/spinelz/tabBox_close.gif +0 -0
- data/examples/al-admin/public/images/spinelz/tabBox_tabLeftActive.gif +0 -0
- data/examples/al-admin/public/images/spinelz/tabBox_tabLeftInactive.gif +0 -0
- data/examples/al-admin/public/images/spinelz/tabBox_tabMiddleActive.gif +0 -0
- data/examples/al-admin/public/images/spinelz/tabBox_tabMiddleInactive.gif +0 -0
- data/examples/al-admin/public/images/spinelz/tabBox_tabRightActive.gif +0 -0
- data/examples/al-admin/public/images/spinelz/tabBox_tabRightInactive.gif +0 -0
- data/examples/al-admin/public/images/spinelz/tab_bar.gif +0 -0
- data/examples/al-admin/public/images/spinelz/table_back.gif +0 -0
- data/examples/al-admin/public/images/spinelz/timepicker_clock.gif +0 -0
- data/examples/al-admin/public/images/spinelz/toolbar_close.gif +0 -0
- data/examples/al-admin/public/images/spinelz/toolbar_left.gif +0 -0
- data/examples/al-admin/public/images/spinelz/toolbar_max.gif +0 -0
- data/examples/al-admin/public/images/spinelz/toolbar_middle.gif +0 -0
- data/examples/al-admin/public/images/spinelz/toolbar_min.gif +0 -0
- data/examples/al-admin/public/images/spinelz/toolbar_next.gif +0 -0
- data/examples/al-admin/public/images/spinelz/toolbar_right.gif +0 -0
- data/examples/al-admin/public/images/spinelz/treeview_dir.gif +0 -0
- data/examples/al-admin/public/images/spinelz/treeview_file.gif +0 -0
- data/examples/al-admin/public/images/spinelz/treeview_group.gif +0 -0
- data/examples/al-admin/public/images/spinelz/treeview_group_special.gif +0 -0
- data/examples/al-admin/public/images/spinelz/treeview_state.gif +0 -0
- data/examples/al-admin/public/images/spinelz/treeview_user.gif +0 -0
- data/examples/al-admin/public/images/spinelz/window_bottom_left.gif +0 -0
- data/examples/al-admin/public/images/spinelz/window_bottom_middle.gif +0 -0
- data/examples/al-admin/public/images/spinelz/window_bottom_right.gif +0 -0
- data/examples/al-admin/public/images/spinelz/window_close.gif +0 -0
- data/examples/al-admin/public/images/spinelz/window_max.gif +0 -0
- data/examples/al-admin/public/images/spinelz/window_middle_left.gif +0 -0
- data/examples/al-admin/public/images/spinelz/window_middle_right.gif +0 -0
- data/examples/al-admin/public/images/spinelz/window_min.gif +0 -0
- data/examples/al-admin/public/images/spinelz/window_top_left.gif +0 -0
- data/examples/al-admin/public/images/spinelz/window_top_middle.gif +0 -0
- data/examples/al-admin/public/images/spinelz/window_top_right.gif +0 -0
- data/examples/al-admin/public/javascripts/application.js +2 -0
- data/examples/al-admin/public/javascripts/controls.js +963 -0
- data/examples/al-admin/public/javascripts/dragdrop.js +973 -0
- data/examples/al-admin/public/javascripts/effects.js +1128 -0
- data/examples/al-admin/public/javascripts/prototype.js +4320 -0
- data/examples/al-admin/public/javascripts/spinelz/accordion.js +185 -0
- data/examples/al-admin/public/javascripts/spinelz/ajaxHistory.js +157 -0
- data/examples/al-admin/public/javascripts/spinelz/balloon.js +287 -0
- data/examples/al-admin/public/javascripts/spinelz/barchart.js +524 -0
- data/examples/al-admin/public/javascripts/spinelz/calendar.js +3012 -0
- data/examples/al-admin/public/javascripts/spinelz/colorpicker.js +128 -0
- data/examples/al-admin/public/javascripts/spinelz/datepicker.js +438 -0
- data/examples/al-admin/public/javascripts/spinelz/grid.js +1391 -0
- data/examples/al-admin/public/javascripts/spinelz/grid_resizeEx.js +100 -0
- data/examples/al-admin/public/javascripts/spinelz/grid_sortabletableEx.js +129 -0
- data/examples/al-admin/public/javascripts/spinelz/inplaceEditorEx.js +148 -0
- data/examples/al-admin/public/javascripts/spinelz/menubar.js +232 -0
- data/examples/al-admin/public/javascripts/spinelz/navPanel.js +170 -0
- data/examples/al-admin/public/javascripts/spinelz/selectableTable.js +433 -0
- data/examples/al-admin/public/javascripts/spinelz/sideBarBox.js +282 -0
- data/examples/al-admin/public/javascripts/spinelz/sideBarBox_effects.js +83 -0
- data/examples/al-admin/public/javascripts/spinelz/sortableTable.js +270 -0
- data/examples/al-admin/public/javascripts/spinelz/switcher.js +78 -0
- data/examples/al-admin/public/javascripts/spinelz/tabBox.js +469 -0
- data/examples/al-admin/public/javascripts/spinelz/timepicker.js +384 -0
- data/examples/al-admin/public/javascripts/spinelz/toolbar.js +152 -0
- data/examples/al-admin/public/javascripts/spinelz/treeview.js +703 -0
- data/examples/al-admin/public/javascripts/spinelz/window.js +641 -0
- data/examples/al-admin/public/javascripts/spinelz/window_resizeEx.js +130 -0
- data/examples/al-admin/public/javascripts/spinelz_lib/builder.js +131 -0
- data/examples/al-admin/public/javascripts/spinelz_lib/controls.js +835 -0
- data/examples/al-admin/public/javascripts/spinelz_lib/dragdrop.js +944 -0
- data/examples/al-admin/public/javascripts/spinelz_lib/effects.js +1090 -0
- data/examples/al-admin/public/javascripts/spinelz_lib/json.js +139 -0
- data/examples/al-admin/public/javascripts/spinelz_lib/prototype.js +2515 -0
- data/examples/al-admin/public/javascripts/spinelz_lib/resize.js +215 -0
- data/examples/al-admin/public/javascripts/spinelz_lib/scriptaculous.js +51 -0
- data/examples/al-admin/public/javascripts/spinelz_lib/slider.js +278 -0
- data/examples/al-admin/public/javascripts/spinelz_lib/spinelz_util.js +1266 -0
- data/examples/al-admin/public/javascripts/spinelz_lib/unittest.js +564 -0
- data/examples/al-admin/public/robots.txt +1 -0
- data/examples/al-admin/public/stylesheets/account.css +41 -0
- data/examples/al-admin/public/stylesheets/attributes.css +1 -0
- data/examples/al-admin/public/stylesheets/base.css +104 -0
- data/examples/al-admin/public/stylesheets/detail.css +36 -0
- data/examples/al-admin/public/stylesheets/directory.css +22 -0
- data/examples/al-admin/public/stylesheets/object-classes.css +6 -0
- data/examples/al-admin/public/stylesheets/rails.css +35 -0
- data/examples/al-admin/public/stylesheets/spinelz/accordion.css +59 -0
- data/examples/al-admin/public/stylesheets/spinelz/balloon.css +151 -0
- data/examples/al-admin/public/stylesheets/spinelz/calendar.css +564 -0
- data/examples/al-admin/public/stylesheets/spinelz/datepicker.css +175 -0
- data/examples/al-admin/public/stylesheets/spinelz/grid.css +137 -0
- data/examples/al-admin/public/stylesheets/spinelz/menubar.css +78 -0
- data/examples/al-admin/public/stylesheets/spinelz/modal.css +22 -0
- data/examples/al-admin/public/stylesheets/spinelz/navPanel.css +58 -0
- data/examples/al-admin/public/stylesheets/spinelz/selectableTable.css +28 -0
- data/examples/al-admin/public/stylesheets/spinelz/sideBarBox.css +82 -0
- data/examples/al-admin/public/stylesheets/spinelz/sortableTable.css +51 -0
- data/examples/al-admin/public/stylesheets/spinelz/switcher.css +23 -0
- data/examples/al-admin/public/stylesheets/spinelz/tabBox.css +94 -0
- data/examples/al-admin/public/stylesheets/spinelz/timepicker.css +508 -0
- data/examples/al-admin/public/stylesheets/spinelz/toolbar.css +82 -0
- data/examples/al-admin/public/stylesheets/spinelz/treeview.css +121 -0
- data/examples/al-admin/public/stylesheets/spinelz/window.css +140 -0
- data/examples/al-admin/public/stylesheets/structure.css +81 -0
- data/examples/al-admin/public/stylesheets/syntaxes.css +1 -0
- data/examples/al-admin/public/stylesheets/users.css +13 -0
- data/examples/al-admin/public/stylesheets/welcome.css +0 -0
- data/examples/al-admin/script/about +3 -0
- data/examples/al-admin/script/console +3 -0
- data/examples/al-admin/script/dbconsole +3 -0
- data/examples/al-admin/script/destroy +3 -0
- data/examples/al-admin/script/generate +3 -0
- data/examples/al-admin/script/performance/benchmarker +3 -0
- data/examples/al-admin/script/performance/profiler +3 -0
- data/examples/al-admin/script/performance/request +3 -0
- data/examples/al-admin/script/plugin +3 -0
- data/examples/al-admin/script/process/inspector +3 -0
- data/examples/al-admin/script/process/reaper +3 -0
- data/examples/al-admin/script/process/spawner +3 -0
- data/examples/al-admin/script/runner +3 -0
- data/examples/al-admin/script/server +3 -0
- data/examples/al-admin/test/fixtures/users.yml +9 -0
- data/examples/al-admin/test/functional/account_controller_test.rb +12 -0
- data/examples/al-admin/test/functional/attributes_controller_test.rb +8 -0
- data/examples/al-admin/test/functional/directory_controller_test.rb +8 -0
- data/examples/al-admin/test/functional/object_classes_controller_test.rb +8 -0
- data/examples/al-admin/test/functional/syntaxes_controller_test.rb +8 -0
- data/examples/al-admin/test/functional/users_controller_test.rb +8 -0
- data/examples/al-admin/test/functional/welcome_controller_test.rb +8 -0
- data/examples/al-admin/test/integration/sign_up_test.rb +44 -0
- data/examples/al-admin/test/run-test.sh +3 -0
- data/examples/al-admin/test/test_helper.rb +52 -0
- data/examples/al-admin/test/unit/user_test.rb +12 -0
- data/examples/al-admin/vendor/plugins/exception_notification/README +111 -0
- data/examples/al-admin/vendor/plugins/exception_notification/init.rb +1 -0
- data/examples/al-admin/vendor/plugins/exception_notification/lib/exception_notifiable.rb +99 -0
- data/examples/al-admin/vendor/plugins/exception_notification/lib/exception_notifier.rb +66 -0
- data/examples/al-admin/vendor/plugins/exception_notification/lib/exception_notifier_helper.rb +78 -0
- data/examples/al-admin/vendor/plugins/exception_notification/test/exception_notifier_helper_test.rb +61 -0
- data/examples/al-admin/vendor/plugins/exception_notification/test/test_helper.rb +7 -0
- data/examples/al-admin/vendor/plugins/exception_notification/views/exception_notifier/_backtrace.rhtml +1 -0
- data/examples/al-admin/vendor/plugins/exception_notification/views/exception_notifier/_environment.rhtml +7 -0
- data/examples/al-admin/vendor/plugins/exception_notification/views/exception_notifier/_inspect_model.rhtml +16 -0
- data/examples/al-admin/vendor/plugins/exception_notification/views/exception_notifier/_request.rhtml +4 -0
- data/examples/al-admin/vendor/plugins/exception_notification/views/exception_notifier/_session.rhtml +2 -0
- data/examples/al-admin/vendor/plugins/exception_notification/views/exception_notifier/_title.rhtml +3 -0
- data/examples/al-admin/vendor/plugins/exception_notification/views/exception_notifier/exception_notification.rhtml +6 -0
- data/examples/config.yaml.example +5 -0
- data/examples/example.der +0 -0
- data/examples/example.jpg +0 -0
- data/examples/groupadd +41 -0
- data/examples/groupdel +35 -0
- data/examples/groupls +49 -0
- data/examples/groupmod +42 -0
- data/examples/lpasswd +55 -0
- data/examples/objects/group.rb +13 -0
- data/examples/objects/ou.rb +4 -0
- data/examples/objects/user.rb +20 -0
- data/examples/ouadd +38 -0
- data/examples/useradd +45 -0
- data/examples/useradd-binary +53 -0
- data/examples/userdel +34 -0
- data/examples/userls +50 -0
- data/examples/usermod +42 -0
- data/examples/usermod-binary-add +50 -0
- data/examples/usermod-binary-add-time +54 -0
- data/examples/usermod-binary-del +48 -0
- data/examples/usermod-lang-add +43 -0
- data/lib/active_ldap.rb +105 -0
- data/lib/active_ldap/action_controller/ldap_benchmarking.rb +55 -0
- data/lib/active_ldap/acts/tree.rb +75 -0
- data/lib/active_ldap/adapter/base.rb +705 -0
- data/lib/active_ldap/adapter/jndi.rb +184 -0
- data/lib/active_ldap/adapter/jndi_connection.rb +185 -0
- data/lib/active_ldap/adapter/ldap.rb +290 -0
- data/lib/active_ldap/adapter/ldap_ext.rb +105 -0
- data/lib/active_ldap/adapter/net_ldap.rb +309 -0
- data/lib/active_ldap/adapter/net_ldap_ext.rb +23 -0
- data/lib/active_ldap/association/belongs_to.rb +47 -0
- data/lib/active_ldap/association/belongs_to_many.rb +58 -0
- data/lib/active_ldap/association/children.rb +21 -0
- data/lib/active_ldap/association/collection.rb +105 -0
- data/lib/active_ldap/association/has_many.rb +31 -0
- data/lib/active_ldap/association/has_many_utils.rb +44 -0
- data/lib/active_ldap/association/has_many_wrap.rb +62 -0
- data/lib/active_ldap/association/proxy.rb +107 -0
- data/lib/active_ldap/associations.rb +202 -0
- data/lib/active_ldap/attributes.rb +184 -0
- data/lib/active_ldap/base.rb +1594 -0
- data/lib/active_ldap/callbacks.rb +52 -0
- data/lib/active_ldap/command.rb +49 -0
- data/lib/active_ldap/compatible.rb +44 -0
- data/lib/active_ldap/configuration.rb +147 -0
- data/lib/active_ldap/connection.rb +294 -0
- data/lib/active_ldap/distinguished_name.rb +291 -0
- data/lib/active_ldap/entry_attribute.rb +78 -0
- data/lib/active_ldap/escape.rb +12 -0
- data/lib/active_ldap/get_text.rb +9 -0
- data/lib/active_ldap/get_text/parser.rb +161 -0
- data/lib/active_ldap/get_text_fallback.rb +60 -0
- data/lib/active_ldap/get_text_support.rb +20 -0
- data/lib/active_ldap/helper.rb +92 -0
- data/lib/active_ldap/human_readable.rb +132 -0
- data/lib/active_ldap/ldap_error.rb +74 -0
- data/lib/active_ldap/ldif.rb +930 -0
- data/lib/active_ldap/object_class.rb +95 -0
- data/lib/active_ldap/operations.rb +610 -0
- data/lib/active_ldap/populate.rb +53 -0
- data/lib/active_ldap/railtie.rb +31 -0
- data/lib/active_ldap/schema.rb +699 -0
- data/lib/active_ldap/schema/syntaxes.rb +417 -0
- data/lib/active_ldap/timeout.rb +75 -0
- data/lib/active_ldap/timeout_stub.rb +17 -0
- data/lib/active_ldap/user_password.rb +92 -0
- data/lib/active_ldap/validations.rb +229 -0
- data/lib/active_ldap/xml.rb +122 -0
- data/po/en/active-ldap.po +4029 -0
- data/po/ja/active-ldap.po +4060 -0
- data/rails_generators/model_active_ldap/USAGE +17 -0
- data/rails_generators/model_active_ldap/model_active_ldap_generator.rb +69 -0
- data/rails_generators/model_active_ldap/templates/model_active_ldap.rb +3 -0
- data/rails_generators/model_active_ldap/templates/unit_test.rb +8 -0
- data/rails_generators/scaffold_active_ldap/scaffold_active_ldap_generator.rb +7 -0
- data/rails_generators/scaffold_active_ldap/templates/ldap.yml +18 -0
- data/test/al-test-utils.rb +439 -0
- data/test/command.rb +112 -0
- data/test/config.yaml.sample +6 -0
- data/test/fixtures/lower_case_object_class_schema.rb +802 -0
- data/test/run-test.rb +44 -0
- data/test/test_acts_as_tree.rb +60 -0
- data/test/test_adapter.rb +121 -0
- data/test/test_associations.rb +664 -0
- data/test/test_attributes.rb +117 -0
- data/test/test_base.rb +1177 -0
- data/test/test_base_per_instance.rb +61 -0
- data/test/test_bind.rb +62 -0
- data/test/test_callback.rb +37 -0
- data/test/test_configuration.rb +40 -0
- data/test/test_connection.rb +82 -0
- data/test/test_connection_per_class.rb +112 -0
- data/test/test_connection_per_dn.rb +112 -0
- data/test/test_dn.rb +172 -0
- data/test/test_find.rb +176 -0
- data/test/test_groupadd.rb +50 -0
- data/test/test_groupdel.rb +46 -0
- data/test/test_groupls.rb +107 -0
- data/test/test_groupmod.rb +51 -0
- data/test/test_ldif.rb +1891 -0
- data/test/test_load.rb +133 -0
- data/test/test_lpasswd.rb +75 -0
- data/test/test_object_class.rb +74 -0
- data/test/test_reflection.rb +182 -0
- data/test/test_schema.rb +559 -0
- data/test/test_syntax.rb +383 -0
- data/test/test_user.rb +217 -0
- data/test/test_user_password.rb +101 -0
- data/test/test_useradd-binary.rb +62 -0
- data/test/test_useradd.rb +57 -0
- data/test/test_userdel.rb +48 -0
- data/test/test_userls.rb +91 -0
- data/test/test_usermod-binary-add-time.rb +65 -0
- data/test/test_usermod-binary-add.rb +64 -0
- data/test/test_usermod-binary-del.rb +66 -0
- data/test/test_usermod-lang-add.rb +60 -0
- data/test/test_usermod.rb +58 -0
- data/test/test_validation.rb +274 -0
- metadata +502 -0
|
@@ -0,0 +1,184 @@
|
|
|
1
|
+
module ActiveLdap
|
|
2
|
+
module Attributes
|
|
3
|
+
def self.included(base)
|
|
4
|
+
base.class_eval do
|
|
5
|
+
extend(ClassMethods)
|
|
6
|
+
extend(Normalizable)
|
|
7
|
+
include(Normalizable)
|
|
8
|
+
end
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
module ClassMethods
|
|
12
|
+
def attr_protected(*attributes)
|
|
13
|
+
targets = attributes.collect {|attr| attr.to_s} - protected_attributes
|
|
14
|
+
instance_variable_set("@attr_protected", targets)
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def protected_attributes
|
|
18
|
+
ancestors[0..(ancestors.index(Base))].inject([]) do |result, ancestor|
|
|
19
|
+
result + ancestor.instance_eval {@attr_protected ||= []}
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def blank_value?(value)
|
|
24
|
+
case value
|
|
25
|
+
when Hash
|
|
26
|
+
value.values.all? {|val| blank_value?(val)}
|
|
27
|
+
when Array
|
|
28
|
+
value.all? {|val| blank_value?(val)}
|
|
29
|
+
when String
|
|
30
|
+
/\A\s*\z/ =~ value
|
|
31
|
+
when nil
|
|
32
|
+
true
|
|
33
|
+
else
|
|
34
|
+
value.blank?
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
def remove_blank_value(value)
|
|
39
|
+
case value
|
|
40
|
+
when Hash
|
|
41
|
+
result = {}
|
|
42
|
+
value.each do |k, v|
|
|
43
|
+
v = remove_blank_value(v)
|
|
44
|
+
next if v.nil?
|
|
45
|
+
result[k] = v
|
|
46
|
+
end
|
|
47
|
+
result = nil if result.blank?
|
|
48
|
+
result
|
|
49
|
+
when Array
|
|
50
|
+
result = []
|
|
51
|
+
value.each do |v|
|
|
52
|
+
v = remove_blank_value(v)
|
|
53
|
+
next if v.nil?
|
|
54
|
+
result << v
|
|
55
|
+
end
|
|
56
|
+
result = nil if result.blank?
|
|
57
|
+
result
|
|
58
|
+
when String
|
|
59
|
+
if /\A\s*\z/ =~ value
|
|
60
|
+
nil
|
|
61
|
+
else
|
|
62
|
+
value
|
|
63
|
+
end
|
|
64
|
+
else
|
|
65
|
+
value
|
|
66
|
+
end
|
|
67
|
+
end
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
module Normalizable
|
|
71
|
+
def normalize_attribute_name(name)
|
|
72
|
+
name.to_s.downcase
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
# Enforce typing:
|
|
76
|
+
# Hashes are for subtypes
|
|
77
|
+
# Arrays are for multiple entries
|
|
78
|
+
def normalize_attribute(name, value)
|
|
79
|
+
if name.nil?
|
|
80
|
+
raise RuntimeError, _('The first argument, name, must not be nil. ' \
|
|
81
|
+
'Please report this as a bug!')
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
name = normalize_attribute_name(name)
|
|
85
|
+
[name, schema.attribute(name).normalize_value(value)]
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
def unnormalize_attributes(attributes)
|
|
89
|
+
result = {}
|
|
90
|
+
attributes.each do |name, values|
|
|
91
|
+
unnormalize_attribute(name, values, result)
|
|
92
|
+
end
|
|
93
|
+
result
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
def unnormalize_attribute(name, values, result={})
|
|
97
|
+
if values.empty?
|
|
98
|
+
result[name] = []
|
|
99
|
+
else
|
|
100
|
+
values.each do |value|
|
|
101
|
+
if value.is_a?(Hash)
|
|
102
|
+
suffix, real_value = unnormalize_attribute_options(value)
|
|
103
|
+
new_name = name + suffix
|
|
104
|
+
unnormalize_attribute(new_name, real_value, result)
|
|
105
|
+
else
|
|
106
|
+
result[name] ||= []
|
|
107
|
+
if value.is_a?(DN)
|
|
108
|
+
result[name] << value.to_s
|
|
109
|
+
else
|
|
110
|
+
result[name] << value.dup
|
|
111
|
+
end
|
|
112
|
+
end
|
|
113
|
+
end
|
|
114
|
+
end
|
|
115
|
+
result
|
|
116
|
+
end
|
|
117
|
+
|
|
118
|
+
# normalize_attribute_options
|
|
119
|
+
#
|
|
120
|
+
# Makes the Hashized value from the full attribute name
|
|
121
|
+
# e.g. userCertificate;binary => "some_bin"
|
|
122
|
+
# becomes userCertificate => {"binary" => "some_bin"}
|
|
123
|
+
def normalize_attribute_options(attr, value)
|
|
124
|
+
return [attr, value] unless attr.match(/;/)
|
|
125
|
+
|
|
126
|
+
ret_attr, *options = attr.split(/;/)
|
|
127
|
+
[ret_attr,
|
|
128
|
+
[options.reverse.inject(value) {|result, option| {option => result}}]]
|
|
129
|
+
end
|
|
130
|
+
|
|
131
|
+
# unnormalize_attribute_options
|
|
132
|
+
#
|
|
133
|
+
# Unnormalizes all of the subtypes from a given set of nested hashes
|
|
134
|
+
# and returns the attribute suffix and the final true value
|
|
135
|
+
def unnormalize_attribute_options(value)
|
|
136
|
+
options = ''
|
|
137
|
+
ret_val = value
|
|
138
|
+
if value.class == Hash
|
|
139
|
+
options = ';' + value.keys[0]
|
|
140
|
+
ret_val = value[value.keys[0]]
|
|
141
|
+
if ret_val.class == Hash
|
|
142
|
+
sub_options, ret_val = unnormalize_attribute_options(ret_val)
|
|
143
|
+
options += sub_options
|
|
144
|
+
end
|
|
145
|
+
end
|
|
146
|
+
ret_val = [ret_val] unless ret_val.class == Array
|
|
147
|
+
[options, ret_val]
|
|
148
|
+
end
|
|
149
|
+
end
|
|
150
|
+
|
|
151
|
+
private
|
|
152
|
+
def remove_attributes_protected_from_mass_assignment(targets)
|
|
153
|
+
needless_attributes = {}
|
|
154
|
+
(attributes_protected_by_default +
|
|
155
|
+
(self.class.protected_attributes || [])).each do |name|
|
|
156
|
+
needless_attributes[to_real_attribute_name(name)] = true
|
|
157
|
+
end
|
|
158
|
+
|
|
159
|
+
_dn_attribute = nil
|
|
160
|
+
begin
|
|
161
|
+
_dn_attribute = dn_attribute_with_fallback
|
|
162
|
+
rescue DistinguishedNameInvalid
|
|
163
|
+
end
|
|
164
|
+
targets.collect do |key, value|
|
|
165
|
+
key = _dn_attribute if ["id", "dn"].include?(key.to_s)
|
|
166
|
+
[to_real_attribute_name(key) || key, value]
|
|
167
|
+
end.reject do |key, value|
|
|
168
|
+
needless_attributes[key]
|
|
169
|
+
end
|
|
170
|
+
end
|
|
171
|
+
|
|
172
|
+
def attributes_protected_by_default
|
|
173
|
+
begin
|
|
174
|
+
_dn_attribute = dn_attribute_with_fallback
|
|
175
|
+
rescue DistinguishedNameInvalid
|
|
176
|
+
end
|
|
177
|
+
[_dn_attribute, 'objectClass'].compact
|
|
178
|
+
end
|
|
179
|
+
|
|
180
|
+
def normalize_attribute_name(name)
|
|
181
|
+
self.class.normalize_attribute_name(name)
|
|
182
|
+
end
|
|
183
|
+
end
|
|
184
|
+
end
|
|
@@ -0,0 +1,1594 @@
|
|
|
1
|
+
# === activeldap - an OO-interface to LDAP objects inspired by ActiveRecord
|
|
2
|
+
# Author: Will Drewry <will@alum.bu.edu>
|
|
3
|
+
# License: See LICENSE and COPYING.txt
|
|
4
|
+
# Copyright 2004-2006 Will Drewry <will@alum.bu.edu>
|
|
5
|
+
# Some portions Copyright 2006 Google Inc
|
|
6
|
+
#
|
|
7
|
+
# == Summary
|
|
8
|
+
# ActiveLdap lets you read and update LDAP entries in a completely object
|
|
9
|
+
# oriented fashion, even handling attributes with multiple names seamlessly.
|
|
10
|
+
# It was inspired by ActiveRecord so extending it to deal with custom
|
|
11
|
+
# LDAP schemas is as effortless as knowing the 'ou' of the objects, and the
|
|
12
|
+
# primary key. (fix this up some)
|
|
13
|
+
#
|
|
14
|
+
# == Example
|
|
15
|
+
# irb> require 'active_ldap'
|
|
16
|
+
# > true
|
|
17
|
+
# irb> user = ActiveLdap::User.new("drewry")
|
|
18
|
+
# > #<ActiveLdap::User:0x402e...
|
|
19
|
+
# irb> user.cn
|
|
20
|
+
# > "foo"
|
|
21
|
+
# irb> user.common_name
|
|
22
|
+
# > "foo"
|
|
23
|
+
# irb> user.cn = "Will Drewry"
|
|
24
|
+
# > "Will Drewry"
|
|
25
|
+
# irb> user.cn
|
|
26
|
+
# > "Will Drewry"
|
|
27
|
+
# irb> user.save
|
|
28
|
+
#
|
|
29
|
+
#
|
|
30
|
+
|
|
31
|
+
require 'English'
|
|
32
|
+
require 'thread'
|
|
33
|
+
require 'erb'
|
|
34
|
+
|
|
35
|
+
module ActiveLdap
|
|
36
|
+
# OO-interface to LDAP assuming pam/nss_ldap-style organization with
|
|
37
|
+
# Active specifics
|
|
38
|
+
# Each subclass does a ldapsearch for the matching entry.
|
|
39
|
+
# If no exact match, raise an error.
|
|
40
|
+
# If match, change all LDAP attributes in accessor attributes on the object.
|
|
41
|
+
# -- these are ACTUALLY populated from schema - see active_ldap/schema.rb
|
|
42
|
+
# example
|
|
43
|
+
# -- extract objectClasses from match and populate
|
|
44
|
+
# Multiple entries become lists.
|
|
45
|
+
# If this isn't read-only then lists become multiple entries, etc.
|
|
46
|
+
|
|
47
|
+
class << self
|
|
48
|
+
include GetTextSupport
|
|
49
|
+
include ActiveRecord::Persistence
|
|
50
|
+
def const_missing(id)
|
|
51
|
+
case id
|
|
52
|
+
when :ConnectionNotEstablished
|
|
53
|
+
message =
|
|
54
|
+
_("ActiveLdap::ConnectionNotEstablished has been deprecated " \
|
|
55
|
+
"since 1.1.0. " \
|
|
56
|
+
"Please use ActiveLdap::ConnectionNotSetup instead.")
|
|
57
|
+
ActiveSupport::Deprecation.warn(message)
|
|
58
|
+
const_set("ConnectionNotEstablished", ConnectionNotSetup)
|
|
59
|
+
ConnectionNotEstablished
|
|
60
|
+
else
|
|
61
|
+
super
|
|
62
|
+
end
|
|
63
|
+
end
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
class Error < StandardError
|
|
67
|
+
include GetTextSupport
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
# ConfigurationError
|
|
71
|
+
#
|
|
72
|
+
# An exception raised when there is a problem with Base.connect arguments
|
|
73
|
+
class ConfigurationError < Error
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
# DeleteError
|
|
77
|
+
#
|
|
78
|
+
# An exception raised when an ActiveLdap delete action fails
|
|
79
|
+
class DeleteError < Error
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
# SaveError
|
|
83
|
+
#
|
|
84
|
+
# An exception raised when an ActiveLdap save action failes
|
|
85
|
+
class SaveError < Error
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
# AuthenticationError
|
|
89
|
+
#
|
|
90
|
+
# An exception raised when user authentication fails
|
|
91
|
+
class AuthenticationError < Error
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
# ConnectionError
|
|
95
|
+
#
|
|
96
|
+
# An exception raised when the LDAP conenction fails
|
|
97
|
+
class ConnectionError < Error
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
# ObjectClassError
|
|
101
|
+
#
|
|
102
|
+
# An exception raised when an objectClass is not defined in the schema
|
|
103
|
+
class ObjectClassError < Error
|
|
104
|
+
end
|
|
105
|
+
|
|
106
|
+
# AttributeAssignmentError
|
|
107
|
+
#
|
|
108
|
+
# An exception raised when there is an issue assigning a value to
|
|
109
|
+
# an attribute
|
|
110
|
+
class AttributeAssignmentError < Error
|
|
111
|
+
end
|
|
112
|
+
|
|
113
|
+
# TimeoutError
|
|
114
|
+
#
|
|
115
|
+
# An exception raised when a connection action fails due to a timeout
|
|
116
|
+
class TimeoutError < Error
|
|
117
|
+
end
|
|
118
|
+
|
|
119
|
+
class EntryNotFound < Error
|
|
120
|
+
end
|
|
121
|
+
|
|
122
|
+
class EntryAlreadyExist < Error
|
|
123
|
+
end
|
|
124
|
+
|
|
125
|
+
class StrongAuthenticationRequired < Error
|
|
126
|
+
end
|
|
127
|
+
|
|
128
|
+
class DistinguishedNameInputInvalid < Error
|
|
129
|
+
attr_reader :input
|
|
130
|
+
def initialize(input=nil)
|
|
131
|
+
@input = input
|
|
132
|
+
super(_("invalid distinguished name (DN) to parse: %s") % @input.inspect)
|
|
133
|
+
end
|
|
134
|
+
end
|
|
135
|
+
|
|
136
|
+
class DistinguishedNameInvalid < Error
|
|
137
|
+
attr_reader :dn, :reason
|
|
138
|
+
def initialize(dn, reason=nil)
|
|
139
|
+
@dn = dn
|
|
140
|
+
@reason = reason
|
|
141
|
+
if @reason
|
|
142
|
+
message = _("%s is invalid distinguished name (DN): %s") % [@dn, @reason]
|
|
143
|
+
else
|
|
144
|
+
message = _("%s is invalid distinguished name (DN)") % @dn
|
|
145
|
+
end
|
|
146
|
+
super(message)
|
|
147
|
+
end
|
|
148
|
+
end
|
|
149
|
+
|
|
150
|
+
class DistinguishedNameNotSetError < Error
|
|
151
|
+
end
|
|
152
|
+
|
|
153
|
+
class LdifInvalid < Error
|
|
154
|
+
attr_reader :ldif, :reason, :line, :column, :nearest
|
|
155
|
+
def initialize(ldif, reason=nil, line=nil, column=nil)
|
|
156
|
+
@ldif = ldif
|
|
157
|
+
@reason = reason
|
|
158
|
+
@line = line
|
|
159
|
+
@column = column
|
|
160
|
+
@nearest = nil
|
|
161
|
+
if @reason
|
|
162
|
+
message = _("invalid LDIF: %s:") % @reason
|
|
163
|
+
else
|
|
164
|
+
message = _("invalid LDIF:")
|
|
165
|
+
end
|
|
166
|
+
if @line and @column
|
|
167
|
+
@nearest = detect_nearest(@line, @column)
|
|
168
|
+
snippet = generate_snippet
|
|
169
|
+
message << "\n#{snippet}\n"
|
|
170
|
+
end
|
|
171
|
+
super("#{message}\n#{numbered_ldif}")
|
|
172
|
+
end
|
|
173
|
+
|
|
174
|
+
NEAREST_MARK = "|@|"
|
|
175
|
+
private
|
|
176
|
+
def detect_nearest(line, column)
|
|
177
|
+
lines = Compatible.string_to_lines(@ldif).to_a
|
|
178
|
+
nearest = lines[line - 1] || ""
|
|
179
|
+
if column - 1 == nearest.size # for JRuby 1.0.2 :<
|
|
180
|
+
nearest << NEAREST_MARK
|
|
181
|
+
else
|
|
182
|
+
nearest[column - 1, 0] = NEAREST_MARK
|
|
183
|
+
end
|
|
184
|
+
if nearest == NEAREST_MARK
|
|
185
|
+
nearest = "#{lines[line - 2]}#{nearest}"
|
|
186
|
+
end
|
|
187
|
+
nearest
|
|
188
|
+
end
|
|
189
|
+
|
|
190
|
+
def generate_snippet
|
|
191
|
+
nearest = @nearest.chomp
|
|
192
|
+
column_column = ":#{@column}"
|
|
193
|
+
target_position_info = "#{@line}#{column_column}: "
|
|
194
|
+
if /\n/ =~ nearest
|
|
195
|
+
snippet = "%#{Math.log10(@line).truncate}d" % (@line - 1)
|
|
196
|
+
snippet << " " * column_column.size
|
|
197
|
+
snippet << ": "
|
|
198
|
+
snippet << nearest.gsub(/\n/, "\n#{target_position_info}")
|
|
199
|
+
else
|
|
200
|
+
snippet = "#{target_position_info}#{nearest}"
|
|
201
|
+
end
|
|
202
|
+
snippet
|
|
203
|
+
end
|
|
204
|
+
|
|
205
|
+
def numbered_ldif
|
|
206
|
+
return @ldif if @ldif.blank?
|
|
207
|
+
lines = Compatible.string_to_lines(@ldif)
|
|
208
|
+
format = "%#{Math.log10(lines.size).truncate + 1}d: %s"
|
|
209
|
+
i = 0
|
|
210
|
+
lines.collect do |line|
|
|
211
|
+
i += 1
|
|
212
|
+
format % [i, line]
|
|
213
|
+
end.join
|
|
214
|
+
end
|
|
215
|
+
end
|
|
216
|
+
|
|
217
|
+
class EntryNotSaved < Error
|
|
218
|
+
end
|
|
219
|
+
|
|
220
|
+
class RequiredObjectClassMissed < Error
|
|
221
|
+
end
|
|
222
|
+
|
|
223
|
+
class RequiredAttributeMissed < Error
|
|
224
|
+
end
|
|
225
|
+
|
|
226
|
+
class EntryInvalid < Error
|
|
227
|
+
end
|
|
228
|
+
|
|
229
|
+
class OperationNotPermitted < Error
|
|
230
|
+
end
|
|
231
|
+
|
|
232
|
+
class ConnectionNotSetup < Error
|
|
233
|
+
end
|
|
234
|
+
|
|
235
|
+
class AdapterNotSpecified < Error
|
|
236
|
+
end
|
|
237
|
+
|
|
238
|
+
class AdapterNotFound < Error
|
|
239
|
+
attr_reader :adapter
|
|
240
|
+
def initialize(adapter)
|
|
241
|
+
@adapter = adapter
|
|
242
|
+
super(_("LDAP configuration specifies nonexistent %s adapter") % adapter)
|
|
243
|
+
end
|
|
244
|
+
end
|
|
245
|
+
|
|
246
|
+
class UnknownAttribute < Error
|
|
247
|
+
attr_reader :name
|
|
248
|
+
def initialize(name)
|
|
249
|
+
@name = name
|
|
250
|
+
super(_("%s is unknown attribute") % @name)
|
|
251
|
+
end
|
|
252
|
+
end
|
|
253
|
+
|
|
254
|
+
class AttributeValueInvalid < Error
|
|
255
|
+
attr_reader :attribute, :value
|
|
256
|
+
def initialize(attribute, value, message)
|
|
257
|
+
@attribute = attribute
|
|
258
|
+
@value = value
|
|
259
|
+
super(message)
|
|
260
|
+
end
|
|
261
|
+
end
|
|
262
|
+
|
|
263
|
+
class NotImplemented < Error
|
|
264
|
+
attr_reader :target
|
|
265
|
+
def initialize(target)
|
|
266
|
+
@target = target
|
|
267
|
+
super(_("not implemented: %s") % @target)
|
|
268
|
+
end
|
|
269
|
+
end
|
|
270
|
+
|
|
271
|
+
# Base
|
|
272
|
+
#
|
|
273
|
+
# Base is the primary class which contains all of the core
|
|
274
|
+
# ActiveLdap functionality. It is meant to only ever be subclassed
|
|
275
|
+
# by extension classes.
|
|
276
|
+
class Base
|
|
277
|
+
include GetTextSupport
|
|
278
|
+
public :_
|
|
279
|
+
|
|
280
|
+
if Object.const_defined?(:Reloadable)
|
|
281
|
+
if Reloadable.const_defined?(:Deprecated)
|
|
282
|
+
include Reloadable::Deprecated
|
|
283
|
+
else
|
|
284
|
+
include Reloadable::Subclasses
|
|
285
|
+
end
|
|
286
|
+
end
|
|
287
|
+
|
|
288
|
+
cattr_accessor :colorize_logging, :instance_writer => false
|
|
289
|
+
@@colorize_logging = true
|
|
290
|
+
|
|
291
|
+
VALID_LDAP_MAPPING_OPTIONS = [:dn_attribute, :prefix, :scope,
|
|
292
|
+
:classes, :recommended_classes,
|
|
293
|
+
:excluded_classes, :sort_by, :order]
|
|
294
|
+
|
|
295
|
+
cattr_accessor :logger
|
|
296
|
+
cattr_accessor :configurations
|
|
297
|
+
@@configurations = {}
|
|
298
|
+
|
|
299
|
+
def self.class_local_attr_accessor(search_ancestors, *syms)
|
|
300
|
+
syms.flatten.each do |sym|
|
|
301
|
+
class_eval(<<-EOS, __FILE__, __LINE__ + 1)
|
|
302
|
+
def self.#{sym}(search_superclasses=#{search_ancestors})
|
|
303
|
+
@#{sym} ||= nil
|
|
304
|
+
return @#{sym} if @#{sym}
|
|
305
|
+
if search_superclasses
|
|
306
|
+
target = superclass
|
|
307
|
+
value = nil
|
|
308
|
+
loop do
|
|
309
|
+
break nil unless target.respond_to?(:#{sym})
|
|
310
|
+
value = target.#{sym}
|
|
311
|
+
break if value
|
|
312
|
+
target = target.superclass
|
|
313
|
+
end
|
|
314
|
+
value
|
|
315
|
+
else
|
|
316
|
+
nil
|
|
317
|
+
end
|
|
318
|
+
end
|
|
319
|
+
def #{sym}; self.class.#{sym}; end
|
|
320
|
+
def self.#{sym}=(value); @#{sym} = value; end
|
|
321
|
+
EOS
|
|
322
|
+
end
|
|
323
|
+
end
|
|
324
|
+
|
|
325
|
+
class_local_attr_accessor false, :inheritable_prefix, :inheritable_base
|
|
326
|
+
class_local_attr_accessor true, :dn_attribute, :scope, :sort_by, :order
|
|
327
|
+
class_local_attr_accessor true, :required_classes, :recommended_classes
|
|
328
|
+
class_local_attr_accessor true, :excluded_classes
|
|
329
|
+
|
|
330
|
+
class << self
|
|
331
|
+
# Hide new in Base
|
|
332
|
+
private :new
|
|
333
|
+
|
|
334
|
+
def inherited(sub_class)
|
|
335
|
+
super
|
|
336
|
+
sub_class.module_eval do
|
|
337
|
+
include GetTextSupport
|
|
338
|
+
end
|
|
339
|
+
end
|
|
340
|
+
|
|
341
|
+
# Connect and bind to LDAP creating a class variable for use by
|
|
342
|
+
# all ActiveLdap objects.
|
|
343
|
+
#
|
|
344
|
+
# == +config+
|
|
345
|
+
# +config+ must be a hash that may contain any of the following fields:
|
|
346
|
+
# :password_block, :logger, :host, :port, :base, :bind_dn,
|
|
347
|
+
# :try_sasl, :allow_anonymous
|
|
348
|
+
# :bind_dn specifies the DN to bind with.
|
|
349
|
+
# :password_block specifies a Proc object that will yield a String to
|
|
350
|
+
# be used as the password when called.
|
|
351
|
+
# :logger specifies a logger object (Logger, Log4r::Logger and s on)
|
|
352
|
+
# :host sets the LDAP server hostname
|
|
353
|
+
# :port sets the LDAP server port
|
|
354
|
+
# :base overwrites Base.base - this affects EVERYTHING
|
|
355
|
+
# :try_sasl indicates that a SASL bind should be attempted when binding
|
|
356
|
+
# to the server (default: false)
|
|
357
|
+
# :sasl_mechanisms is an array of SASL mechanism to try
|
|
358
|
+
# (default: ["GSSAPI", "CRAM-MD5", "EXTERNAL"])
|
|
359
|
+
# :allow_anonymous indicates that a true anonymous bind is allowed when
|
|
360
|
+
# trying to bind to the server (default: true)
|
|
361
|
+
# :retries - indicates the number of attempts to reconnect that will be
|
|
362
|
+
# undertaken when a stale connection occurs. -1 means infinite.
|
|
363
|
+
# :sasl_quiet - if true, sets @sasl_quiet on the Ruby/LDAP connection
|
|
364
|
+
# :method - whether to use :ssl, :tls, or :plain (unencrypted)
|
|
365
|
+
# :retry_wait - seconds to wait before retrying a connection
|
|
366
|
+
# :scope - dictates how to find objects. ONELEVEL by default to
|
|
367
|
+
# avoid dn_attr collisions across OUs. Think before changing.
|
|
368
|
+
# :timeout - time in seconds - defaults to disabled. This CAN interrupt
|
|
369
|
+
# search() requests. Be warned.
|
|
370
|
+
# :retry_on_timeout - whether to reconnect when timeouts occur. Defaults
|
|
371
|
+
# to true
|
|
372
|
+
# See lib/active_ldap/configuration.rb for defaults for each option
|
|
373
|
+
def setup_connection(config=nil)
|
|
374
|
+
super
|
|
375
|
+
ensure_logger
|
|
376
|
+
nil
|
|
377
|
+
end
|
|
378
|
+
|
|
379
|
+
# establish_connection is deprecated since 1.1.0. Please use
|
|
380
|
+
# setup_connection() instead.
|
|
381
|
+
def establish_connection(config=nil)
|
|
382
|
+
message =
|
|
383
|
+
_("ActiveLdap::Base.establish_connection has been deprecated " \
|
|
384
|
+
"since 1.1.0. " \
|
|
385
|
+
"Please use ActiveLdap::Base.setup_connection instead.")
|
|
386
|
+
ActiveSupport::Deprecation.warn(message)
|
|
387
|
+
setup_connection(config)
|
|
388
|
+
end
|
|
389
|
+
|
|
390
|
+
def create(attributes=nil, &block)
|
|
391
|
+
if attributes.is_a?(Array)
|
|
392
|
+
attributes.collect {|attrs| create(attrs, &block)}
|
|
393
|
+
else
|
|
394
|
+
object = new(attributes, &block)
|
|
395
|
+
object.save
|
|
396
|
+
object
|
|
397
|
+
end
|
|
398
|
+
end
|
|
399
|
+
|
|
400
|
+
# This class function is used to setup all mappings between the subclass
|
|
401
|
+
# and ldap for use in activeldap
|
|
402
|
+
#
|
|
403
|
+
# Example:
|
|
404
|
+
# ldap_mapping :dn_attribute => 'uid', :prefix => 'ou=People',
|
|
405
|
+
# :classes => ['top', 'posixAccount'],
|
|
406
|
+
# :scope => :sub
|
|
407
|
+
def ldap_mapping(options={})
|
|
408
|
+
options = options.symbolize_keys
|
|
409
|
+
validate_ldap_mapping_options(options)
|
|
410
|
+
|
|
411
|
+
self.dn_attribute = options[:dn_attribute] || default_dn_attribute
|
|
412
|
+
self.dn_attribute = dn_attribute.to_s if dn_attribute.is_a?(Symbol)
|
|
413
|
+
self.prefix = options[:prefix] || default_prefix
|
|
414
|
+
self.scope = options[:scope]
|
|
415
|
+
self.required_classes = options[:classes]
|
|
416
|
+
self.recommended_classes = options[:recommended_classes]
|
|
417
|
+
self.excluded_classes = options[:excluded_classes]
|
|
418
|
+
self.sort_by = options[:sort_by]
|
|
419
|
+
self.order = options[:order]
|
|
420
|
+
|
|
421
|
+
public_class_method :new
|
|
422
|
+
end
|
|
423
|
+
|
|
424
|
+
# Base.base
|
|
425
|
+
#
|
|
426
|
+
# This method when included into Base provides
|
|
427
|
+
# an inheritable, overwritable configuration setting
|
|
428
|
+
#
|
|
429
|
+
# This should be a string with the base of the
|
|
430
|
+
# ldap server such as 'dc=example,dc=com', and
|
|
431
|
+
# it should be overwritten by including
|
|
432
|
+
# configuration.rb into this class.
|
|
433
|
+
# When subclassing, the specified prefix will be concatenated.
|
|
434
|
+
def base
|
|
435
|
+
@base ||= compute_base
|
|
436
|
+
end
|
|
437
|
+
alias_method :parsed_base, :base # for backward compatibility
|
|
438
|
+
|
|
439
|
+
def base=(value)
|
|
440
|
+
self.inheritable_base = value
|
|
441
|
+
@base = nil
|
|
442
|
+
end
|
|
443
|
+
|
|
444
|
+
def prefix
|
|
445
|
+
@prefix ||= inheritable_prefix and DN.parse(inheritable_prefix)
|
|
446
|
+
end
|
|
447
|
+
|
|
448
|
+
def prefix=(value)
|
|
449
|
+
self.inheritable_prefix = value
|
|
450
|
+
@prefix = nil
|
|
451
|
+
@base = nil
|
|
452
|
+
end
|
|
453
|
+
|
|
454
|
+
alias_method :scope_without_validation=, :scope=
|
|
455
|
+
def scope=(scope)
|
|
456
|
+
validate_scope(scope)
|
|
457
|
+
self.scope_without_validation = scope
|
|
458
|
+
end
|
|
459
|
+
|
|
460
|
+
def validate_scope(scope)
|
|
461
|
+
scope = scope.to_sym if scope.is_a?(String)
|
|
462
|
+
return if scope.nil? or scope.is_a?(Symbol)
|
|
463
|
+
raise ConfigurationError,
|
|
464
|
+
_("scope '%s' must be a Symbol") % scope.inspect
|
|
465
|
+
end
|
|
466
|
+
|
|
467
|
+
def base_class
|
|
468
|
+
if self == Base or superclass == Base
|
|
469
|
+
self
|
|
470
|
+
else
|
|
471
|
+
superclass.base_class
|
|
472
|
+
end
|
|
473
|
+
end
|
|
474
|
+
|
|
475
|
+
def default_search_attribute
|
|
476
|
+
dn_attribute
|
|
477
|
+
end
|
|
478
|
+
|
|
479
|
+
def inspect
|
|
480
|
+
if self == Base
|
|
481
|
+
super
|
|
482
|
+
elsif abstract_class?
|
|
483
|
+
"#{super}(abstract)"
|
|
484
|
+
else
|
|
485
|
+
detail = nil
|
|
486
|
+
begin
|
|
487
|
+
must = []
|
|
488
|
+
may = []
|
|
489
|
+
class_names = classes.collect do |object_class|
|
|
490
|
+
must.concat(object_class.must)
|
|
491
|
+
may.concat(object_class.may)
|
|
492
|
+
object_class.name
|
|
493
|
+
end
|
|
494
|
+
detail = ["objectClass:<#{class_names.join(', ')}>",
|
|
495
|
+
"must:<#{inspect_attributes(must)}>",
|
|
496
|
+
"may:<#{inspect_attributes(may)}>"].join(", ")
|
|
497
|
+
rescue ActiveLdap::ConnectionNotSetup
|
|
498
|
+
detail = "not-connected"
|
|
499
|
+
rescue ActiveLdap::Error
|
|
500
|
+
detail = "connection-failure"
|
|
501
|
+
end
|
|
502
|
+
"#{super}(#{detail})"
|
|
503
|
+
end
|
|
504
|
+
end
|
|
505
|
+
|
|
506
|
+
attr_accessor :abstract_class
|
|
507
|
+
def abstract_class?
|
|
508
|
+
defined?(@abstract_class) && @abstract_class
|
|
509
|
+
end
|
|
510
|
+
|
|
511
|
+
def class_of_active_ldap_descendant(klass)
|
|
512
|
+
if klass.superclass == Base or klass.superclass.abstract_class?
|
|
513
|
+
klass
|
|
514
|
+
elsif klass.superclass.nil?
|
|
515
|
+
raise Error, _("%s doesn't belong in a hierarchy descending " \
|
|
516
|
+
"from ActiveLdap") % (name || to_s)
|
|
517
|
+
else
|
|
518
|
+
class_of_active_ldap_descendant(klass.superclass)
|
|
519
|
+
end
|
|
520
|
+
end
|
|
521
|
+
|
|
522
|
+
def self_and_descendants_from_active_ldap
|
|
523
|
+
klass = self
|
|
524
|
+
classes = [klass]
|
|
525
|
+
while klass != klass.base_class
|
|
526
|
+
classes << klass = klass.superclass
|
|
527
|
+
end
|
|
528
|
+
classes
|
|
529
|
+
rescue
|
|
530
|
+
[self]
|
|
531
|
+
end
|
|
532
|
+
if ActiveRecord::Base.respond_to?(:self_and_descendents_from_active_record)
|
|
533
|
+
# ActiveRecord 2.2.2 has a typo. :<
|
|
534
|
+
alias_method(:self_and_descendents_from_active_record,
|
|
535
|
+
:self_and_descendants_from_active_ldap)
|
|
536
|
+
else
|
|
537
|
+
alias_method(:self_and_descendants_from_active_record,
|
|
538
|
+
:self_and_descendants_from_active_ldap)
|
|
539
|
+
end
|
|
540
|
+
|
|
541
|
+
def human_name(options={})
|
|
542
|
+
defaults = self_and_descendants_from_active_ldap.collect do |klass|
|
|
543
|
+
if klass.name.blank?
|
|
544
|
+
nil
|
|
545
|
+
else
|
|
546
|
+
:"#{klass.name.underscore}"
|
|
547
|
+
end
|
|
548
|
+
end
|
|
549
|
+
defaults << name.humanize
|
|
550
|
+
defaults = defaults.compact
|
|
551
|
+
defaults.first || name || to_s
|
|
552
|
+
end
|
|
553
|
+
|
|
554
|
+
private
|
|
555
|
+
def inspect_attributes(attributes)
|
|
556
|
+
inspected_attribute_names = {}
|
|
557
|
+
attributes.collect do |attribute|
|
|
558
|
+
if inspected_attribute_names.has_key?(attribute.name)
|
|
559
|
+
nil
|
|
560
|
+
else
|
|
561
|
+
inspected_attribute_names[attribute.name] = true
|
|
562
|
+
inspect_attribute(attribute)
|
|
563
|
+
end
|
|
564
|
+
end.compact.join(', ')
|
|
565
|
+
end
|
|
566
|
+
|
|
567
|
+
def inspect_attribute(attribute)
|
|
568
|
+
syntax = attribute.syntax
|
|
569
|
+
result = "#{attribute.name}"
|
|
570
|
+
if syntax and !syntax.description.blank?
|
|
571
|
+
result << ": #{syntax.description}"
|
|
572
|
+
end
|
|
573
|
+
properties = []
|
|
574
|
+
properties << "read-only" if attribute.read_only?
|
|
575
|
+
properties << "binary" if attribute.binary?
|
|
576
|
+
properties << "binary-required" if attribute.binary_required?
|
|
577
|
+
result << "(#{properties.join(', ')})" unless properties.empty?
|
|
578
|
+
result
|
|
579
|
+
end
|
|
580
|
+
|
|
581
|
+
def validate_ldap_mapping_options(options)
|
|
582
|
+
options.assert_valid_keys(VALID_LDAP_MAPPING_OPTIONS)
|
|
583
|
+
end
|
|
584
|
+
|
|
585
|
+
def ensure_logger
|
|
586
|
+
@@logger ||= configuration[:logger]
|
|
587
|
+
# Setup default logger to console
|
|
588
|
+
if @@logger.nil?
|
|
589
|
+
require 'logger'
|
|
590
|
+
@@logger = Logger.new(STDERR)
|
|
591
|
+
@@logger.progname = 'ActiveLdap'
|
|
592
|
+
@@logger.level = Logger::ERROR
|
|
593
|
+
end
|
|
594
|
+
configuration[:logger] ||= @@logger
|
|
595
|
+
end
|
|
596
|
+
|
|
597
|
+
def instantiate(args)
|
|
598
|
+
dn, attributes, options = args
|
|
599
|
+
options ||= {}
|
|
600
|
+
if self.class == Class
|
|
601
|
+
klass = self.ancestors[0].to_s.split(':').last
|
|
602
|
+
real_klass = self.ancestors[0]
|
|
603
|
+
else
|
|
604
|
+
klass = self.class.to_s.split(':').last
|
|
605
|
+
real_klass = self.class
|
|
606
|
+
end
|
|
607
|
+
|
|
608
|
+
obj = real_klass.allocate
|
|
609
|
+
conn = options[:connection] || connection
|
|
610
|
+
obj.connection = conn if conn != connection
|
|
611
|
+
obj.instance_eval do
|
|
612
|
+
initialize_by_ldap_data(dn, attributes)
|
|
613
|
+
end
|
|
614
|
+
obj
|
|
615
|
+
end
|
|
616
|
+
|
|
617
|
+
def default_dn_attribute
|
|
618
|
+
dn_attribute = nil
|
|
619
|
+
parent_class = ancestors[1]
|
|
620
|
+
if parent_class.respond_to?(:dn_attribute)
|
|
621
|
+
dn_attribute = parent_class.dn_attribute
|
|
622
|
+
end
|
|
623
|
+
dn_attribute || "cn"
|
|
624
|
+
end
|
|
625
|
+
|
|
626
|
+
def default_prefix
|
|
627
|
+
if name.blank?
|
|
628
|
+
nil
|
|
629
|
+
else
|
|
630
|
+
"ou=#{name.demodulize.pluralize}"
|
|
631
|
+
end
|
|
632
|
+
end
|
|
633
|
+
|
|
634
|
+
def compute_base
|
|
635
|
+
_base = inheritable_base
|
|
636
|
+
_base = configuration[:base] if _base.nil? and configuration
|
|
637
|
+
if _base.nil?
|
|
638
|
+
target = superclass
|
|
639
|
+
loop do
|
|
640
|
+
break unless target.respond_to?(:base)
|
|
641
|
+
_base = target.base
|
|
642
|
+
break if _base
|
|
643
|
+
target = target.superclass
|
|
644
|
+
end
|
|
645
|
+
end
|
|
646
|
+
_prefix = prefix
|
|
647
|
+
|
|
648
|
+
_base ||= connection.naming_contexts.first
|
|
649
|
+
return _prefix if _base.blank?
|
|
650
|
+
|
|
651
|
+
_base = DN.parse(_base)
|
|
652
|
+
_base = _prefix + _base if _prefix
|
|
653
|
+
_base
|
|
654
|
+
end
|
|
655
|
+
end
|
|
656
|
+
|
|
657
|
+
self.scope = :sub
|
|
658
|
+
self.required_classes = ['top']
|
|
659
|
+
self.recommended_classes = []
|
|
660
|
+
self.excluded_classes = []
|
|
661
|
+
|
|
662
|
+
include Enumerable
|
|
663
|
+
|
|
664
|
+
### All instance methods, etc
|
|
665
|
+
|
|
666
|
+
# new
|
|
667
|
+
#
|
|
668
|
+
# Creates a new instance of Base initializing all class and all
|
|
669
|
+
# initialization. Defines local defaults. See examples If multiple values
|
|
670
|
+
# exist for dn_attribute, the first one put here will be authoritative
|
|
671
|
+
def initialize(attributes=nil)
|
|
672
|
+
init_base
|
|
673
|
+
@new_entry = true
|
|
674
|
+
initial_classes = required_classes | recommended_classes
|
|
675
|
+
case attributes
|
|
676
|
+
when nil
|
|
677
|
+
self.classes = initial_classes
|
|
678
|
+
when String, Array, DN
|
|
679
|
+
self.classes = initial_classes
|
|
680
|
+
self.dn = attributes
|
|
681
|
+
when Hash
|
|
682
|
+
classes, attributes = extract_object_class(attributes)
|
|
683
|
+
self.classes = classes | initial_classes
|
|
684
|
+
normalized_attributes = {}
|
|
685
|
+
attributes.each do |key, value|
|
|
686
|
+
real_key = to_real_attribute_name(key) || key
|
|
687
|
+
normalized_attributes[real_key] = value
|
|
688
|
+
end
|
|
689
|
+
self.dn = normalized_attributes.delete(dn_attribute)
|
|
690
|
+
self.attributes = normalized_attributes
|
|
691
|
+
else
|
|
692
|
+
format = _("'%s' must be either nil, DN value as ActiveLdap::DN, " \
|
|
693
|
+
"String or Array or attributes as Hash")
|
|
694
|
+
raise ArgumentError, format % attributes.inspect
|
|
695
|
+
end
|
|
696
|
+
yield self if block_given?
|
|
697
|
+
end
|
|
698
|
+
|
|
699
|
+
# Returns true if the +comparison_object+ is the same object, or is of
|
|
700
|
+
# the same type and has the same dn.
|
|
701
|
+
def ==(comparison_object)
|
|
702
|
+
comparison_object.equal?(self) or
|
|
703
|
+
(comparison_object.instance_of?(self.class) and
|
|
704
|
+
comparison_object.dn == dn and
|
|
705
|
+
!comparison_object.new_entry?)
|
|
706
|
+
end
|
|
707
|
+
|
|
708
|
+
# Delegates to ==
|
|
709
|
+
def eql?(comparison_object)
|
|
710
|
+
self == (comparison_object)
|
|
711
|
+
end
|
|
712
|
+
|
|
713
|
+
# Delegates to id in order to allow two records of the same type and id
|
|
714
|
+
# to work with something like:
|
|
715
|
+
# [ User.find("a"), User.find("b"), User.find("c") ] &
|
|
716
|
+
# [ User.find("a"), User.find("d") ] # => [ User.find("a") ]
|
|
717
|
+
def hash
|
|
718
|
+
return super if @_hashing # workaround for GetText :<
|
|
719
|
+
_dn = nil
|
|
720
|
+
begin
|
|
721
|
+
@_hashing = true
|
|
722
|
+
_dn = dn
|
|
723
|
+
rescue DistinguishedNameInvalid, DistinguishedNameNotSetError
|
|
724
|
+
return super
|
|
725
|
+
ensure
|
|
726
|
+
@_hashing = false
|
|
727
|
+
end
|
|
728
|
+
_dn.hash
|
|
729
|
+
end
|
|
730
|
+
|
|
731
|
+
def may
|
|
732
|
+
entry_attribute.may
|
|
733
|
+
end
|
|
734
|
+
|
|
735
|
+
def must
|
|
736
|
+
entry_attribute.must
|
|
737
|
+
end
|
|
738
|
+
|
|
739
|
+
# attributes
|
|
740
|
+
#
|
|
741
|
+
# Return attribute methods so that a program can determine available
|
|
742
|
+
# attributes dynamically without schema awareness
|
|
743
|
+
def attribute_names(normalize=false)
|
|
744
|
+
entry_attribute.names(normalize)
|
|
745
|
+
end
|
|
746
|
+
|
|
747
|
+
def attribute_present?(name)
|
|
748
|
+
values = get_attribute(name, true)
|
|
749
|
+
!values.empty? or values.any? {|x| !(x and x.empty?)}
|
|
750
|
+
end
|
|
751
|
+
|
|
752
|
+
# exist?
|
|
753
|
+
#
|
|
754
|
+
# Return whether the entry exists in LDAP or not
|
|
755
|
+
def exist?
|
|
756
|
+
self.class.exists?(dn)
|
|
757
|
+
end
|
|
758
|
+
alias_method(:exists?, :exist?)
|
|
759
|
+
|
|
760
|
+
# new_entry?
|
|
761
|
+
#
|
|
762
|
+
# Return whether the entry is new entry in LDAP or not
|
|
763
|
+
def new_entry?
|
|
764
|
+
@new_entry
|
|
765
|
+
end
|
|
766
|
+
|
|
767
|
+
# dn
|
|
768
|
+
#
|
|
769
|
+
# Return the authoritative dn
|
|
770
|
+
def dn
|
|
771
|
+
@dn ||= compute_dn
|
|
772
|
+
end
|
|
773
|
+
|
|
774
|
+
def id
|
|
775
|
+
get_attribute(dn_attribute_with_fallback)
|
|
776
|
+
end
|
|
777
|
+
|
|
778
|
+
def to_param
|
|
779
|
+
id
|
|
780
|
+
end
|
|
781
|
+
|
|
782
|
+
def dn=(value)
|
|
783
|
+
set_attribute(dn_attribute_with_fallback, value)
|
|
784
|
+
end
|
|
785
|
+
alias_method(:id=, :dn=)
|
|
786
|
+
|
|
787
|
+
alias_method(:dn_attribute_of_class, :dn_attribute)
|
|
788
|
+
def dn_attribute
|
|
789
|
+
ensure_update_dn
|
|
790
|
+
_dn_attribute = @dn_attribute || dn_attribute_of_class
|
|
791
|
+
to_real_attribute_name(_dn_attribute) || _dn_attribute
|
|
792
|
+
end
|
|
793
|
+
|
|
794
|
+
def default_search_attribute
|
|
795
|
+
self.class.default_search_attribute
|
|
796
|
+
end
|
|
797
|
+
|
|
798
|
+
# destroy
|
|
799
|
+
#
|
|
800
|
+
# Delete this entry from LDAP
|
|
801
|
+
def destroy
|
|
802
|
+
self.class.delete(dn)
|
|
803
|
+
@new_entry = true
|
|
804
|
+
end
|
|
805
|
+
|
|
806
|
+
def delete(options={})
|
|
807
|
+
super(dn, options)
|
|
808
|
+
end
|
|
809
|
+
|
|
810
|
+
# save
|
|
811
|
+
#
|
|
812
|
+
# Save and validate this object into LDAP
|
|
813
|
+
# either adding or replacing attributes
|
|
814
|
+
# TODO: Relative DN support
|
|
815
|
+
def save
|
|
816
|
+
create_or_update
|
|
817
|
+
end
|
|
818
|
+
|
|
819
|
+
def save!
|
|
820
|
+
unless create_or_update
|
|
821
|
+
raise EntryNotSaved, _("entry %s can't be saved") % dn
|
|
822
|
+
end
|
|
823
|
+
end
|
|
824
|
+
|
|
825
|
+
# method_missing
|
|
826
|
+
#
|
|
827
|
+
# If a given method matches an attribute or an attribute alias
|
|
828
|
+
# then call the appropriate method.
|
|
829
|
+
# TODO: Determine if it would be better to define each allowed method
|
|
830
|
+
# using class_eval instead of using method_missing. This would
|
|
831
|
+
# give tab completion in irb.
|
|
832
|
+
def method_missing(name, *args, &block)
|
|
833
|
+
key = name.to_s
|
|
834
|
+
case key
|
|
835
|
+
when /=$/
|
|
836
|
+
real_key = $PREMATCH
|
|
837
|
+
if have_attribute?(real_key, ['objectClass'])
|
|
838
|
+
if args.size != 1
|
|
839
|
+
raise ArgumentError,
|
|
840
|
+
_("wrong number of arguments (%d for 1)") % args.size
|
|
841
|
+
end
|
|
842
|
+
return set_attribute(real_key, *args, &block)
|
|
843
|
+
end
|
|
844
|
+
when /(?:(_before_type_cast)|(\?))?$/
|
|
845
|
+
real_key = $PREMATCH
|
|
846
|
+
before_type_cast = !$1.nil?
|
|
847
|
+
query = !$2.nil?
|
|
848
|
+
if have_attribute?(real_key, ['objectClass'])
|
|
849
|
+
if args.size > 1
|
|
850
|
+
raise ArgumentError,
|
|
851
|
+
_("wrong number of arguments (%d for 1)") % args.size
|
|
852
|
+
end
|
|
853
|
+
if before_type_cast
|
|
854
|
+
return get_attribute_before_type_cast(real_key, *args)[1]
|
|
855
|
+
elsif query
|
|
856
|
+
return get_attribute_as_query(real_key, *args)
|
|
857
|
+
else
|
|
858
|
+
return get_attribute(real_key, *args)
|
|
859
|
+
end
|
|
860
|
+
end
|
|
861
|
+
end
|
|
862
|
+
super
|
|
863
|
+
end
|
|
864
|
+
|
|
865
|
+
# Add available attributes to the methods
|
|
866
|
+
def methods(inherited_too=true)
|
|
867
|
+
target_names = entry_attribute.all_names
|
|
868
|
+
target_names -= ['objectClass', 'objectClass'.underscore]
|
|
869
|
+
super + target_names.uniq.collect do |x|
|
|
870
|
+
[x, "#{x}=", "#{x}?", "#{x}_before_type_cast"]
|
|
871
|
+
end.flatten
|
|
872
|
+
end
|
|
873
|
+
|
|
874
|
+
alias_method :respond_to_without_attributes?, :respond_to?
|
|
875
|
+
def respond_to?(name, include_priv=false)
|
|
876
|
+
return true if super
|
|
877
|
+
|
|
878
|
+
name = name.to_s
|
|
879
|
+
return true if have_attribute?(name, ["objectClass"])
|
|
880
|
+
return false if /(?:=|\?|_before_type_cast)$/ !~ name
|
|
881
|
+
have_attribute?($PREMATCH, ["objectClass"])
|
|
882
|
+
end
|
|
883
|
+
|
|
884
|
+
# Updates a given attribute and saves immediately
|
|
885
|
+
def update_attribute(name, value)
|
|
886
|
+
send("#{name}=", value)
|
|
887
|
+
save
|
|
888
|
+
end
|
|
889
|
+
|
|
890
|
+
# This performs a bulk update of attributes and immediately
|
|
891
|
+
# calls #save.
|
|
892
|
+
def update_attributes(attrs)
|
|
893
|
+
self.attributes = attrs
|
|
894
|
+
save
|
|
895
|
+
end
|
|
896
|
+
|
|
897
|
+
def update_attributes!(attrs)
|
|
898
|
+
self.attributes = attrs
|
|
899
|
+
save!
|
|
900
|
+
end
|
|
901
|
+
|
|
902
|
+
# This returns the key value pairs in @data with all values
|
|
903
|
+
# cloned
|
|
904
|
+
def attributes
|
|
905
|
+
@simplified_data ||= simplify_data(@data)
|
|
906
|
+
@simplified_data.clone
|
|
907
|
+
end
|
|
908
|
+
|
|
909
|
+
# This allows a bulk update to the attributes of a record
|
|
910
|
+
# without forcing an immediate save or validation.
|
|
911
|
+
#
|
|
912
|
+
# It is unwise to attempt objectClass updates this way.
|
|
913
|
+
# Also be sure to only pass in key-value pairs of your choosing.
|
|
914
|
+
# Do not let URL/form hackers supply the keys.
|
|
915
|
+
def attributes=(new_attributes)
|
|
916
|
+
return if new_attributes.blank?
|
|
917
|
+
_schema = _local_entry_attribute = nil
|
|
918
|
+
targets = remove_attributes_protected_from_mass_assignment(new_attributes)
|
|
919
|
+
targets.each do |key, value|
|
|
920
|
+
setter = "#{key}="
|
|
921
|
+
unless respond_to?(setter)
|
|
922
|
+
_schema ||= schema
|
|
923
|
+
attribute = _schema.attribute(key)
|
|
924
|
+
next if attribute.id.nil?
|
|
925
|
+
_local_entry_attribute ||= local_entry_attribute
|
|
926
|
+
_local_entry_attribute.register(attribute)
|
|
927
|
+
end
|
|
928
|
+
send(setter, value)
|
|
929
|
+
end
|
|
930
|
+
end
|
|
931
|
+
|
|
932
|
+
def to_ldif_record
|
|
933
|
+
super(dn, normalize_data(@data))
|
|
934
|
+
end
|
|
935
|
+
|
|
936
|
+
def to_ldif
|
|
937
|
+
Ldif.new([to_ldif_record]).to_s
|
|
938
|
+
end
|
|
939
|
+
|
|
940
|
+
def to_xml(options={})
|
|
941
|
+
options = options.dup
|
|
942
|
+
options[:root] ||= (self.class.name || '').underscore
|
|
943
|
+
options[:root] = 'anonymous' if options[:root].blank?
|
|
944
|
+
except = options[:except]
|
|
945
|
+
if except
|
|
946
|
+
options[:except] = except.collect do |name|
|
|
947
|
+
if name.to_s.downcase == "dn"
|
|
948
|
+
"dn"
|
|
949
|
+
else
|
|
950
|
+
to_real_attribute_name(name)
|
|
951
|
+
end
|
|
952
|
+
end.compact
|
|
953
|
+
end
|
|
954
|
+
XML.new(dn, normalize_data(@data), schema).to_s(options)
|
|
955
|
+
end
|
|
956
|
+
|
|
957
|
+
def to_s
|
|
958
|
+
to_ldif
|
|
959
|
+
end
|
|
960
|
+
|
|
961
|
+
def have_attribute?(name, except=[])
|
|
962
|
+
real_name = to_real_attribute_name(name)
|
|
963
|
+
!real_name.nil? and !except.include?(real_name)
|
|
964
|
+
end
|
|
965
|
+
alias_method :has_attribute?, :have_attribute?
|
|
966
|
+
|
|
967
|
+
def reload
|
|
968
|
+
clear_association_cache
|
|
969
|
+
_, attributes = search(:value => id).find do |_dn, _attributes|
|
|
970
|
+
dn == _dn
|
|
971
|
+
end
|
|
972
|
+
if attributes.nil?
|
|
973
|
+
raise EntryNotFound, _("Can't find DN '%s' to reload") % dn
|
|
974
|
+
end
|
|
975
|
+
|
|
976
|
+
@ldap_data.update(attributes)
|
|
977
|
+
classes, attributes = extract_object_class(attributes)
|
|
978
|
+
self.classes = classes
|
|
979
|
+
self.attributes = attributes
|
|
980
|
+
@new_entry = false
|
|
981
|
+
self
|
|
982
|
+
end
|
|
983
|
+
|
|
984
|
+
def [](name, force_array=false)
|
|
985
|
+
if name == "dn"
|
|
986
|
+
array_of(dn, force_array)
|
|
987
|
+
else
|
|
988
|
+
get_attribute(name, force_array)
|
|
989
|
+
end
|
|
990
|
+
end
|
|
991
|
+
|
|
992
|
+
def []=(name, value)
|
|
993
|
+
set_attribute(name, value)
|
|
994
|
+
end
|
|
995
|
+
|
|
996
|
+
def each
|
|
997
|
+
@data.each do |key, values|
|
|
998
|
+
yield(key.dup, values.dup)
|
|
999
|
+
end
|
|
1000
|
+
end
|
|
1001
|
+
|
|
1002
|
+
def bind(config_or_password={}, config_or_ignore=nil, &block)
|
|
1003
|
+
if config_or_password.is_a?(String)
|
|
1004
|
+
config = (config_or_ignore || {}).merge(:password => config_or_password)
|
|
1005
|
+
else
|
|
1006
|
+
config = config_or_password
|
|
1007
|
+
end
|
|
1008
|
+
config = {:bind_dn => dn, :allow_anonymous => false}.merge(config)
|
|
1009
|
+
config[:password_block] ||= block if block_given?
|
|
1010
|
+
setup_connection(config)
|
|
1011
|
+
|
|
1012
|
+
before_connection = @connection
|
|
1013
|
+
begin
|
|
1014
|
+
@connection = nil
|
|
1015
|
+
connection.connect
|
|
1016
|
+
@connection = connection
|
|
1017
|
+
clear_connection_based_cache
|
|
1018
|
+
clear_association_cache
|
|
1019
|
+
rescue ActiveLdap::Error
|
|
1020
|
+
remove_connection
|
|
1021
|
+
@connection = before_connection
|
|
1022
|
+
raise
|
|
1023
|
+
end
|
|
1024
|
+
true
|
|
1025
|
+
end
|
|
1026
|
+
|
|
1027
|
+
def clear_connection_based_cache
|
|
1028
|
+
@schema = nil
|
|
1029
|
+
@local_entry_attribute = nil
|
|
1030
|
+
clear_object_class_based_cache
|
|
1031
|
+
end
|
|
1032
|
+
|
|
1033
|
+
def clear_object_class_based_cache
|
|
1034
|
+
@entry_attribute = nil
|
|
1035
|
+
@real_names = {}
|
|
1036
|
+
end
|
|
1037
|
+
|
|
1038
|
+
def schema
|
|
1039
|
+
@schema ||= super
|
|
1040
|
+
end
|
|
1041
|
+
|
|
1042
|
+
def base
|
|
1043
|
+
@base ||= compute_base
|
|
1044
|
+
end
|
|
1045
|
+
|
|
1046
|
+
def base=(object_local_base)
|
|
1047
|
+
ensure_update_dn
|
|
1048
|
+
@dn = nil
|
|
1049
|
+
@base = nil
|
|
1050
|
+
@base_value = object_local_base
|
|
1051
|
+
end
|
|
1052
|
+
|
|
1053
|
+
alias_method :scope_of_class, :scope
|
|
1054
|
+
def scope
|
|
1055
|
+
@scope || scope_of_class
|
|
1056
|
+
end
|
|
1057
|
+
|
|
1058
|
+
def scope=(scope)
|
|
1059
|
+
self.class.validate_scope(scope)
|
|
1060
|
+
@scope = scope
|
|
1061
|
+
end
|
|
1062
|
+
|
|
1063
|
+
def delete_all(options={})
|
|
1064
|
+
super({:base => dn}.merge(options || {}))
|
|
1065
|
+
end
|
|
1066
|
+
|
|
1067
|
+
def destroy_all(options={})
|
|
1068
|
+
super({:base => dn}.merge(options || {}))
|
|
1069
|
+
end
|
|
1070
|
+
|
|
1071
|
+
def inspect
|
|
1072
|
+
object_classes = entry_attribute.object_classes
|
|
1073
|
+
inspected_object_classes = object_classes.collect do |object_class|
|
|
1074
|
+
object_class.name
|
|
1075
|
+
end.join(', ')
|
|
1076
|
+
must_attributes = must.collect(&:name).sort.join(', ')
|
|
1077
|
+
may_attributes = may.collect(&:name).sort.join(', ')
|
|
1078
|
+
inspected_attributes = attribute_names.sort.collect do |name|
|
|
1079
|
+
inspect_attribute(name)
|
|
1080
|
+
end.join(', ')
|
|
1081
|
+
result = "\#<#{self.class} objectClass:<#{inspected_object_classes}>, "
|
|
1082
|
+
result << "must:<#{must_attributes}>, may:<#{may_attributes}>, "
|
|
1083
|
+
result << "#{inspected_attributes}>"
|
|
1084
|
+
result
|
|
1085
|
+
end
|
|
1086
|
+
|
|
1087
|
+
private
|
|
1088
|
+
def dn_attribute_with_fallback
|
|
1089
|
+
begin
|
|
1090
|
+
dn_attribute
|
|
1091
|
+
rescue DistinguishedNameInvalid
|
|
1092
|
+
_dn_attribute = @dn_attribute || dn_attribute_of_class
|
|
1093
|
+
_dn_attribute = to_real_attribute_name(_dn_attribute) || _dn_attribute
|
|
1094
|
+
raise if _dn_attribute.nil?
|
|
1095
|
+
_dn_attribute
|
|
1096
|
+
end
|
|
1097
|
+
end
|
|
1098
|
+
|
|
1099
|
+
def inspect_attribute(name)
|
|
1100
|
+
values = get_attribute(name, true)
|
|
1101
|
+
values.collect do |value|
|
|
1102
|
+
if value.is_a?(String) and value.length > 50
|
|
1103
|
+
"#{value[0, 50]}...".inspect
|
|
1104
|
+
elsif value.is_a?(Date) || value.is_a?(Time)
|
|
1105
|
+
"#{value.to_s(:db)}"
|
|
1106
|
+
else
|
|
1107
|
+
value.inspect
|
|
1108
|
+
end
|
|
1109
|
+
end
|
|
1110
|
+
"#{name}: #{values.inspect}"
|
|
1111
|
+
end
|
|
1112
|
+
|
|
1113
|
+
def find_object_class_values(data)
|
|
1114
|
+
data["objectClass"] || data["objectclass"]
|
|
1115
|
+
end
|
|
1116
|
+
|
|
1117
|
+
def attribute_name_resolvable_without_connection?
|
|
1118
|
+
@entry_attribute and @local_entry_attribute
|
|
1119
|
+
end
|
|
1120
|
+
|
|
1121
|
+
def entry_attribute
|
|
1122
|
+
@entry_attribute ||=
|
|
1123
|
+
connection.entry_attribute(find_object_class_values(@data) || [])
|
|
1124
|
+
end
|
|
1125
|
+
|
|
1126
|
+
def local_entry_attribute
|
|
1127
|
+
@local_entry_attribute ||= connection.entry_attribute([])
|
|
1128
|
+
end
|
|
1129
|
+
|
|
1130
|
+
def extract_object_class(attributes)
|
|
1131
|
+
classes = []
|
|
1132
|
+
attrs = {}
|
|
1133
|
+
attributes.each do |key, value|
|
|
1134
|
+
key = key.to_s
|
|
1135
|
+
if /\Aobject_?class\z/i =~ key
|
|
1136
|
+
classes.concat(value.to_a)
|
|
1137
|
+
else
|
|
1138
|
+
attrs[key] = value
|
|
1139
|
+
end
|
|
1140
|
+
end
|
|
1141
|
+
[classes, attributes]
|
|
1142
|
+
end
|
|
1143
|
+
|
|
1144
|
+
def init_base
|
|
1145
|
+
init_instance_variables
|
|
1146
|
+
end
|
|
1147
|
+
|
|
1148
|
+
def initialize_by_ldap_data(dn, attributes)
|
|
1149
|
+
init_base
|
|
1150
|
+
dn = Compatible.convert_to_utf8_encoded_object(dn)
|
|
1151
|
+
attributes = Compatible.convert_to_utf8_encoded_object(attributes)
|
|
1152
|
+
@original_dn = dn.clone
|
|
1153
|
+
@dn = dn
|
|
1154
|
+
@base = nil
|
|
1155
|
+
@base_value = nil
|
|
1156
|
+
@new_entry = false
|
|
1157
|
+
@dn_is_base = false
|
|
1158
|
+
@ldap_data = attributes
|
|
1159
|
+
classes, attributes = extract_object_class(attributes)
|
|
1160
|
+
self.classes = classes
|
|
1161
|
+
self.dn = dn
|
|
1162
|
+
self.attributes = attributes
|
|
1163
|
+
yield self if block_given?
|
|
1164
|
+
end
|
|
1165
|
+
|
|
1166
|
+
def instantiate(args)
|
|
1167
|
+
dn, attributes, options = args
|
|
1168
|
+
options ||= {}
|
|
1169
|
+
|
|
1170
|
+
obj = self.class.allocate
|
|
1171
|
+
obj.connection = options[:connection] || @connection
|
|
1172
|
+
obj.instance_eval do
|
|
1173
|
+
initialize_by_ldap_data(dn, attributes)
|
|
1174
|
+
end
|
|
1175
|
+
obj
|
|
1176
|
+
end
|
|
1177
|
+
|
|
1178
|
+
def to_real_attribute_name(name, allow_normalized_name=true)
|
|
1179
|
+
return name if name.nil?
|
|
1180
|
+
if allow_normalized_name
|
|
1181
|
+
entry_attribute.normalize(name, allow_normalized_name) ||
|
|
1182
|
+
local_entry_attribute.normalize(name, allow_normalized_name)
|
|
1183
|
+
else
|
|
1184
|
+
@real_names[name] ||=
|
|
1185
|
+
entry_attribute.normalize(name, false) ||
|
|
1186
|
+
local_entry_attribute.normalize(name, false)
|
|
1187
|
+
end
|
|
1188
|
+
end
|
|
1189
|
+
|
|
1190
|
+
# enforce_type
|
|
1191
|
+
#
|
|
1192
|
+
# enforce_type applies your changes without attempting to write to LDAP.
|
|
1193
|
+
# This means that if you set userCertificate to somebinary value, it will
|
|
1194
|
+
# wrap it up correctly.
|
|
1195
|
+
def enforce_type(key, value)
|
|
1196
|
+
# Enforce attribute value formatting
|
|
1197
|
+
normalize_attribute(key, value)[1]
|
|
1198
|
+
end
|
|
1199
|
+
|
|
1200
|
+
def init_instance_variables
|
|
1201
|
+
@mutex = Mutex.new
|
|
1202
|
+
@data = {} # where the r/w entry data is stored
|
|
1203
|
+
@ldap_data = {} # original ldap entry data
|
|
1204
|
+
@dn_attribute = nil
|
|
1205
|
+
@base = nil
|
|
1206
|
+
@scope = nil
|
|
1207
|
+
@dn = nil
|
|
1208
|
+
@dn_is_base = false
|
|
1209
|
+
@dn_split_value = nil
|
|
1210
|
+
@connection ||= nil
|
|
1211
|
+
@_hashing = false
|
|
1212
|
+
clear_connection_based_cache
|
|
1213
|
+
end
|
|
1214
|
+
|
|
1215
|
+
# get_attribute
|
|
1216
|
+
#
|
|
1217
|
+
# Return the value of the attribute called by method_missing?
|
|
1218
|
+
def get_attribute(name, force_array=false)
|
|
1219
|
+
name, value = get_attribute_before_type_cast(name, force_array)
|
|
1220
|
+
return value if name.nil?
|
|
1221
|
+
attribute = schema.attribute(name)
|
|
1222
|
+
type_cast(attribute, value)
|
|
1223
|
+
end
|
|
1224
|
+
|
|
1225
|
+
def type_cast(attribute, value)
|
|
1226
|
+
case value
|
|
1227
|
+
when Hash
|
|
1228
|
+
result = {}
|
|
1229
|
+
value.each do |option, val|
|
|
1230
|
+
result[option] = type_cast(attribute, val)
|
|
1231
|
+
end
|
|
1232
|
+
if result.size == 1 and result.has_key?("binary")
|
|
1233
|
+
result["binary"]
|
|
1234
|
+
else
|
|
1235
|
+
result
|
|
1236
|
+
end
|
|
1237
|
+
when Array
|
|
1238
|
+
value.collect do |val|
|
|
1239
|
+
type_cast(attribute, val)
|
|
1240
|
+
end
|
|
1241
|
+
else
|
|
1242
|
+
attribute.type_cast(value)
|
|
1243
|
+
end
|
|
1244
|
+
end
|
|
1245
|
+
|
|
1246
|
+
def get_attribute_before_type_cast(name, force_array=false)
|
|
1247
|
+
name = to_real_attribute_name(name)
|
|
1248
|
+
|
|
1249
|
+
value = @data[name]
|
|
1250
|
+
value = [] if value.nil?
|
|
1251
|
+
[name, array_of(value, force_array)]
|
|
1252
|
+
end
|
|
1253
|
+
|
|
1254
|
+
def get_attribute_as_query(name, force_array=false)
|
|
1255
|
+
name, value = get_attribute_before_type_cast(name, force_array)
|
|
1256
|
+
if force_array
|
|
1257
|
+
value.collect {|x| !false_value?(x)}
|
|
1258
|
+
else
|
|
1259
|
+
!false_value?(value)
|
|
1260
|
+
end
|
|
1261
|
+
end
|
|
1262
|
+
|
|
1263
|
+
def false_value?(value)
|
|
1264
|
+
value.nil? or value == false or value == [] or
|
|
1265
|
+
value == "false" or value == "FALSE" or value == ""
|
|
1266
|
+
end
|
|
1267
|
+
|
|
1268
|
+
# set_attribute
|
|
1269
|
+
#
|
|
1270
|
+
# Set the value of the attribute called by method_missing?
|
|
1271
|
+
def set_attribute(name, value)
|
|
1272
|
+
real_name = to_real_attribute_name(name)
|
|
1273
|
+
_dn_attribute = nil
|
|
1274
|
+
valid_dn_attribute = true
|
|
1275
|
+
begin
|
|
1276
|
+
_dn_attribute = dn_attribute
|
|
1277
|
+
rescue DistinguishedNameInvalid
|
|
1278
|
+
valid_dn_attribute = false
|
|
1279
|
+
end
|
|
1280
|
+
if valid_dn_attribute and real_name == _dn_attribute
|
|
1281
|
+
real_name, value = register_new_dn_attribute(real_name, value)
|
|
1282
|
+
end
|
|
1283
|
+
raise UnknownAttribute.new(name) if real_name.nil?
|
|
1284
|
+
|
|
1285
|
+
@data[real_name] = value
|
|
1286
|
+
@simplified_data = nil
|
|
1287
|
+
end
|
|
1288
|
+
|
|
1289
|
+
def register_new_dn_attribute(name, value)
|
|
1290
|
+
@dn = nil
|
|
1291
|
+
@dn_is_base = false
|
|
1292
|
+
if value.blank?
|
|
1293
|
+
@dn_split_value = nil
|
|
1294
|
+
[name, nil]
|
|
1295
|
+
else
|
|
1296
|
+
new_name, new_value, raw_new_value, new_bases = split_dn_value(value)
|
|
1297
|
+
@dn_split_value = [new_name, new_value, new_bases]
|
|
1298
|
+
if new_name.nil? and new_value.nil?
|
|
1299
|
+
new_name, raw_new_value = new_bases[0].to_a[0]
|
|
1300
|
+
end
|
|
1301
|
+
[to_real_attribute_name(new_name) || name,
|
|
1302
|
+
raw_new_value || value]
|
|
1303
|
+
end
|
|
1304
|
+
end
|
|
1305
|
+
|
|
1306
|
+
def update_dn(new_name, new_value, bases)
|
|
1307
|
+
if new_name.nil? and new_value.nil?
|
|
1308
|
+
@dn_is_base = true
|
|
1309
|
+
@base = nil
|
|
1310
|
+
@base_value = nil
|
|
1311
|
+
attr, value = bases[0].to_a[0]
|
|
1312
|
+
@dn_attribute = attr
|
|
1313
|
+
else
|
|
1314
|
+
new_name ||= @dn_attribute || dn_attribute_of_class
|
|
1315
|
+
new_name = to_real_attribute_name(new_name)
|
|
1316
|
+
if new_name.nil?
|
|
1317
|
+
new_name = @dn_attribute || dn_attribute_of_class
|
|
1318
|
+
new_name = to_real_attribute_name(new_name)
|
|
1319
|
+
end
|
|
1320
|
+
new_bases = bases.empty? ? nil : DN.new(*bases).to_s
|
|
1321
|
+
dn_components = ["#{new_name}=#{new_value}",
|
|
1322
|
+
new_bases,
|
|
1323
|
+
self.class.base.to_s]
|
|
1324
|
+
dn_components = dn_components.find_all {|component| !component.blank?}
|
|
1325
|
+
DN.parse(dn_components.join(','))
|
|
1326
|
+
@base = nil
|
|
1327
|
+
@base_value = new_bases
|
|
1328
|
+
@dn_attribute = new_name
|
|
1329
|
+
end
|
|
1330
|
+
end
|
|
1331
|
+
|
|
1332
|
+
def split_dn_value(value)
|
|
1333
|
+
dn_value = relative_dn_value = nil
|
|
1334
|
+
begin
|
|
1335
|
+
dn_value = value if value.is_a?(DN)
|
|
1336
|
+
dn_value ||= DN.parse(value)
|
|
1337
|
+
rescue DistinguishedNameInvalid
|
|
1338
|
+
begin
|
|
1339
|
+
dn_value = DN.parse("#{dn_attribute}=#{value}")
|
|
1340
|
+
rescue DistinguishedNameInvalid
|
|
1341
|
+
return [nil, value, value, []]
|
|
1342
|
+
end
|
|
1343
|
+
end
|
|
1344
|
+
|
|
1345
|
+
val = bases = nil
|
|
1346
|
+
begin
|
|
1347
|
+
relative_dn_value = dn_value
|
|
1348
|
+
base_of_class = self.class.base
|
|
1349
|
+
relative_dn_value -= base_of_class if base_of_class
|
|
1350
|
+
if relative_dn_value.rdns.empty?
|
|
1351
|
+
val = []
|
|
1352
|
+
bases = dn_value.rdns
|
|
1353
|
+
else
|
|
1354
|
+
val, *bases = relative_dn_value.rdns
|
|
1355
|
+
end
|
|
1356
|
+
rescue ArgumentError
|
|
1357
|
+
val, *bases = dn_value.rdns
|
|
1358
|
+
end
|
|
1359
|
+
|
|
1360
|
+
dn_attribute_name, dn_attribute_value = val.to_a[0]
|
|
1361
|
+
escaped_dn_attribute_value = nil
|
|
1362
|
+
unless dn_attribute_value.nil?
|
|
1363
|
+
escaped_dn_attribute_value = DN.escape_value(dn_attribute_value)
|
|
1364
|
+
end
|
|
1365
|
+
[dn_attribute_name, escaped_dn_attribute_value,
|
|
1366
|
+
dn_attribute_value, bases]
|
|
1367
|
+
end
|
|
1368
|
+
|
|
1369
|
+
def need_update_dn?
|
|
1370
|
+
not @dn_split_value.nil?
|
|
1371
|
+
end
|
|
1372
|
+
|
|
1373
|
+
def ensure_update_dn
|
|
1374
|
+
return unless need_update_dn?
|
|
1375
|
+
@mutex.synchronize do
|
|
1376
|
+
if @dn_split_value
|
|
1377
|
+
update_dn(*@dn_split_value)
|
|
1378
|
+
@dn_split_value = nil
|
|
1379
|
+
end
|
|
1380
|
+
end
|
|
1381
|
+
end
|
|
1382
|
+
|
|
1383
|
+
def compute_dn
|
|
1384
|
+
return base if @dn_is_base
|
|
1385
|
+
|
|
1386
|
+
ensure_update_dn
|
|
1387
|
+
dn_value = id
|
|
1388
|
+
if dn_value.nil?
|
|
1389
|
+
format = _("%s's DN attribute (%s) isn't set")
|
|
1390
|
+
message = format % [self.inspect, dn_attribute]
|
|
1391
|
+
raise DistinguishedNameNotSetError.new, message
|
|
1392
|
+
end
|
|
1393
|
+
dn_value = DN.escape_value(dn_value.to_s)
|
|
1394
|
+
_base = base
|
|
1395
|
+
_base = nil if _base.blank?
|
|
1396
|
+
DN.parse(["#{dn_attribute}=#{dn_value}", _base].compact.join(","))
|
|
1397
|
+
end
|
|
1398
|
+
|
|
1399
|
+
def compute_base
|
|
1400
|
+
base_of_class = self.class.base
|
|
1401
|
+
if @base_value.nil?
|
|
1402
|
+
base_of_class
|
|
1403
|
+
else
|
|
1404
|
+
base_of_object = DN.parse(@base_value)
|
|
1405
|
+
base_of_object += base_of_class if base_of_class
|
|
1406
|
+
base_of_object
|
|
1407
|
+
end
|
|
1408
|
+
end
|
|
1409
|
+
|
|
1410
|
+
# array_of
|
|
1411
|
+
#
|
|
1412
|
+
# Returns the array form of a value, or not an array if
|
|
1413
|
+
# false is passed in.
|
|
1414
|
+
def array_of(value, to_a=true)
|
|
1415
|
+
case value
|
|
1416
|
+
when Array
|
|
1417
|
+
if to_a or value.size > 1
|
|
1418
|
+
value.collect {|v| array_of(v, false)}.compact
|
|
1419
|
+
else
|
|
1420
|
+
if value.empty?
|
|
1421
|
+
nil
|
|
1422
|
+
else
|
|
1423
|
+
array_of(value.first, to_a)
|
|
1424
|
+
end
|
|
1425
|
+
end
|
|
1426
|
+
when Hash
|
|
1427
|
+
if to_a
|
|
1428
|
+
[value]
|
|
1429
|
+
else
|
|
1430
|
+
result = {}
|
|
1431
|
+
value.each {|k, v| result[k] = array_of(v, to_a)}
|
|
1432
|
+
result
|
|
1433
|
+
end
|
|
1434
|
+
else
|
|
1435
|
+
to_a ? [value] : value
|
|
1436
|
+
end
|
|
1437
|
+
end
|
|
1438
|
+
|
|
1439
|
+
def normalize_data(data, except=[])
|
|
1440
|
+
_schema = schema
|
|
1441
|
+
result = {}
|
|
1442
|
+
data.each do |key, values|
|
|
1443
|
+
next if except.include?(key)
|
|
1444
|
+
real_name = to_real_attribute_name(key)
|
|
1445
|
+
next if real_name and except.include?(real_name)
|
|
1446
|
+
real_name ||= key
|
|
1447
|
+
next if _schema.attribute(real_name).id.nil?
|
|
1448
|
+
result[real_name] ||= []
|
|
1449
|
+
result[real_name].concat(enforce_type(real_name, values))
|
|
1450
|
+
end
|
|
1451
|
+
result
|
|
1452
|
+
end
|
|
1453
|
+
|
|
1454
|
+
def simplify_data(data)
|
|
1455
|
+
_schema = schema
|
|
1456
|
+
result = {}
|
|
1457
|
+
data.each do |key, values|
|
|
1458
|
+
attribute = _schema.attribute(key)
|
|
1459
|
+
if attribute.single_value? and values.is_a?(Array) and values.size == 1
|
|
1460
|
+
values = values[0]
|
|
1461
|
+
end
|
|
1462
|
+
result[key] = type_cast(attribute, values)
|
|
1463
|
+
end
|
|
1464
|
+
result
|
|
1465
|
+
end
|
|
1466
|
+
|
|
1467
|
+
def collect_modified_attributes(ldap_data, data)
|
|
1468
|
+
klass = self.class
|
|
1469
|
+
_dn_attribute = dn_attribute
|
|
1470
|
+
new_dn_value = nil
|
|
1471
|
+
attributes = []
|
|
1472
|
+
|
|
1473
|
+
# Now that all the options will be treated as unique attributes
|
|
1474
|
+
# we can see what's changed and add anything that is brand-spankin'
|
|
1475
|
+
# new.
|
|
1476
|
+
ldap_data.each do |k, v|
|
|
1477
|
+
value = data[k] || []
|
|
1478
|
+
|
|
1479
|
+
next if v == value
|
|
1480
|
+
|
|
1481
|
+
value = klass.remove_blank_value(value) || []
|
|
1482
|
+
next if v == value
|
|
1483
|
+
|
|
1484
|
+
if klass.blank_value?(value) and
|
|
1485
|
+
schema.attribute(k).binary_required?
|
|
1486
|
+
value = [{'binary' => []}]
|
|
1487
|
+
end
|
|
1488
|
+
if k == _dn_attribute
|
|
1489
|
+
new_dn_value = value[0]
|
|
1490
|
+
else
|
|
1491
|
+
attributes.push([:replace, k, value])
|
|
1492
|
+
end
|
|
1493
|
+
end
|
|
1494
|
+
|
|
1495
|
+
data.each do |k, v|
|
|
1496
|
+
value = v || []
|
|
1497
|
+
next if ldap_data.has_key?(k)
|
|
1498
|
+
|
|
1499
|
+
value = klass.remove_blank_value(value) || []
|
|
1500
|
+
next if klass.blank_value?(value)
|
|
1501
|
+
|
|
1502
|
+
# Detect subtypes and account for them
|
|
1503
|
+
# REPLACE will function like ADD, but doesn't hit EQUALITY problems
|
|
1504
|
+
# TODO: Added equality(attr) to Schema
|
|
1505
|
+
attributes.push([:replace, k, value])
|
|
1506
|
+
end
|
|
1507
|
+
|
|
1508
|
+
[new_dn_value, attributes]
|
|
1509
|
+
end
|
|
1510
|
+
|
|
1511
|
+
def collect_all_attributes(data)
|
|
1512
|
+
dn_attr = dn_attribute
|
|
1513
|
+
dn_value = data[dn_attr]
|
|
1514
|
+
|
|
1515
|
+
attributes = []
|
|
1516
|
+
attributes.push([dn_attr, dn_value])
|
|
1517
|
+
|
|
1518
|
+
oc_value = data['objectClass']
|
|
1519
|
+
attributes.push(['objectClass', oc_value])
|
|
1520
|
+
except_keys = ['objectClass', dn_attr].collect(&:downcase)
|
|
1521
|
+
data.each do |key, value|
|
|
1522
|
+
next if except_keys.include?(key.downcase)
|
|
1523
|
+
value = self.class.remove_blank_value(value)
|
|
1524
|
+
next if self.class.blank_value?(value)
|
|
1525
|
+
|
|
1526
|
+
attributes.push([key, value])
|
|
1527
|
+
end
|
|
1528
|
+
|
|
1529
|
+
attributes
|
|
1530
|
+
end
|
|
1531
|
+
|
|
1532
|
+
def create_or_update
|
|
1533
|
+
new_entry? ? create : update
|
|
1534
|
+
end
|
|
1535
|
+
|
|
1536
|
+
def prepare_data_for_saving
|
|
1537
|
+
# Expand subtypes to real ldap_data attributes
|
|
1538
|
+
# We can't reuse @ldap_data because an exception would leave
|
|
1539
|
+
# an object in an unknown state
|
|
1540
|
+
ldap_data = normalize_data(@ldap_data)
|
|
1541
|
+
|
|
1542
|
+
# Expand subtypes to real data attributes, but leave @data alone
|
|
1543
|
+
object_classes = find_object_class_values(@ldap_data) || []
|
|
1544
|
+
original_attributes =
|
|
1545
|
+
connection.entry_attribute(object_classes).names
|
|
1546
|
+
bad_attrs = original_attributes - entry_attribute.names
|
|
1547
|
+
data = normalize_data(@data, bad_attrs)
|
|
1548
|
+
|
|
1549
|
+
success = yield(data, ldap_data)
|
|
1550
|
+
|
|
1551
|
+
if success
|
|
1552
|
+
@ldap_data = data.clone
|
|
1553
|
+
# Delete items disallowed by objectclasses.
|
|
1554
|
+
# They should have been removed from ldap.
|
|
1555
|
+
bad_attrs.each do |remove_me|
|
|
1556
|
+
@ldap_data.delete(remove_me)
|
|
1557
|
+
end
|
|
1558
|
+
@original_dn = dn.clone
|
|
1559
|
+
end
|
|
1560
|
+
|
|
1561
|
+
success
|
|
1562
|
+
end
|
|
1563
|
+
|
|
1564
|
+
def create
|
|
1565
|
+
prepare_data_for_saving do |data, ldap_data|
|
|
1566
|
+
attributes = collect_all_attributes(data)
|
|
1567
|
+
add_entry(dn, attributes)
|
|
1568
|
+
@new_entry = false
|
|
1569
|
+
true
|
|
1570
|
+
end
|
|
1571
|
+
end
|
|
1572
|
+
|
|
1573
|
+
def update
|
|
1574
|
+
prepare_data_for_saving do |data, ldap_data|
|
|
1575
|
+
new_dn_value, attributes = collect_modified_attributes(ldap_data, data)
|
|
1576
|
+
modify_entry(@original_dn, attributes)
|
|
1577
|
+
if new_dn_value
|
|
1578
|
+
old_dn_base = DN.parse(@original_dn).parent
|
|
1579
|
+
new_dn_base = dn.clone.parent
|
|
1580
|
+
if old_dn_base == new_dn_base
|
|
1581
|
+
new_superior = nil
|
|
1582
|
+
else
|
|
1583
|
+
new_superior = new_dn_base
|
|
1584
|
+
end
|
|
1585
|
+
modify_rdn_entry(@original_dn,
|
|
1586
|
+
"#{dn_attribute}=#{DN.escape_value(new_dn_value)}",
|
|
1587
|
+
true,
|
|
1588
|
+
new_superior)
|
|
1589
|
+
end
|
|
1590
|
+
true
|
|
1591
|
+
end
|
|
1592
|
+
end
|
|
1593
|
+
end # Base
|
|
1594
|
+
end # ActiveLdap
|