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.
Files changed (443) hide show
  1. data/.gitignore +6 -0
  2. data/CHANGES +691 -0
  3. data/COPYING +340 -0
  4. data/LICENSE +58 -0
  5. data/README +154 -0
  6. data/Rakefile +121 -0
  7. data/TODO +32 -0
  8. data/activeldap3.gemspec +18 -0
  9. data/benchmark/bench-al.rb +263 -0
  10. data/benchmark/config.yaml.sample +5 -0
  11. data/examples/al-admin/README +182 -0
  12. data/examples/al-admin/Rakefile +16 -0
  13. data/examples/al-admin/app/controllers/account_controller.rb +60 -0
  14. data/examples/al-admin/app/controllers/application_controller.rb +46 -0
  15. data/examples/al-admin/app/controllers/attributes_controller.rb +17 -0
  16. data/examples/al-admin/app/controllers/directory_controller.rb +49 -0
  17. data/examples/al-admin/app/controllers/object_classes_controller.rb +17 -0
  18. data/examples/al-admin/app/controllers/syntaxes_controller.rb +17 -0
  19. data/examples/al-admin/app/controllers/users_controller.rb +66 -0
  20. data/examples/al-admin/app/controllers/welcome_controller.rb +12 -0
  21. data/examples/al-admin/app/helpers/account_helper.rb +2 -0
  22. data/examples/al-admin/app/helpers/application_helper.rb +46 -0
  23. data/examples/al-admin/app/helpers/attributes_helper.rb +15 -0
  24. data/examples/al-admin/app/helpers/directory_helper.rb +7 -0
  25. data/examples/al-admin/app/helpers/object_classes_helper.rb +10 -0
  26. data/examples/al-admin/app/helpers/syntaxes_helper.rb +10 -0
  27. data/examples/al-admin/app/helpers/url_helper.rb +17 -0
  28. data/examples/al-admin/app/helpers/users_helper.rb +17 -0
  29. data/examples/al-admin/app/helpers/welcome_helper.rb +2 -0
  30. data/examples/al-admin/app/models/entry.rb +23 -0
  31. data/examples/al-admin/app/models/ldap_user.rb +58 -0
  32. data/examples/al-admin/app/models/user.rb +91 -0
  33. data/examples/al-admin/app/views/_entry/_attributes_information.html.erb +29 -0
  34. data/examples/al-admin/app/views/_entry/_entry.html.erb +15 -0
  35. data/examples/al-admin/app/views/_schema/_aliases.html.erb +7 -0
  36. data/examples/al-admin/app/views/_switcher/_after.html.erb +2 -0
  37. data/examples/al-admin/app/views/_switcher/_before.html.erb +5 -0
  38. data/examples/al-admin/app/views/account/login.html.erb +26 -0
  39. data/examples/al-admin/app/views/account/sign_up.html.erb +29 -0
  40. data/examples/al-admin/app/views/attributes/_attributes.html.erb +19 -0
  41. data/examples/al-admin/app/views/attributes/_detail.html.erb +29 -0
  42. data/examples/al-admin/app/views/attributes/index.html.erb +3 -0
  43. data/examples/al-admin/app/views/attributes/show.html.erb +31 -0
  44. data/examples/al-admin/app/views/directory/_tree.html.erb +10 -0
  45. data/examples/al-admin/app/views/directory/_tree_view_js.html.erb +25 -0
  46. data/examples/al-admin/app/views/directory/index.html.erb +13 -0
  47. data/examples/al-admin/app/views/directory/populate.html.erb +2 -0
  48. data/examples/al-admin/app/views/layouts/_footer.html.erb +11 -0
  49. data/examples/al-admin/app/views/layouts/_header_menu.html.erb +10 -0
  50. data/examples/al-admin/app/views/layouts/_main_menu.html.erb +18 -0
  51. data/examples/al-admin/app/views/layouts/application.html.erb +57 -0
  52. data/examples/al-admin/app/views/object_classes/_attributes.html.erb +28 -0
  53. data/examples/al-admin/app/views/object_classes/_object_classes.html.erb +19 -0
  54. data/examples/al-admin/app/views/object_classes/index.html.erb +3 -0
  55. data/examples/al-admin/app/views/object_classes/show.html.erb +39 -0
  56. data/examples/al-admin/app/views/syntaxes/_detail.html.erb +14 -0
  57. data/examples/al-admin/app/views/syntaxes/_syntaxes.html.erb +19 -0
  58. data/examples/al-admin/app/views/syntaxes/index.html.erb +3 -0
  59. data/examples/al-admin/app/views/syntaxes/show.html.erb +22 -0
  60. data/examples/al-admin/app/views/users/_attributes_update_form.html.erb +42 -0
  61. data/examples/al-admin/app/views/users/_form.html.erb +13 -0
  62. data/examples/al-admin/app/views/users/_object_classes_update_form.html.erb +46 -0
  63. data/examples/al-admin/app/views/users/_password_change_form.html.erb +20 -0
  64. data/examples/al-admin/app/views/users/edit.html.erb +15 -0
  65. data/examples/al-admin/app/views/users/index.html.erb +10 -0
  66. data/examples/al-admin/app/views/users/show.html.erb +11 -0
  67. data/examples/al-admin/app/views/welcome/index.html.erb +13 -0
  68. data/examples/al-admin/config.ru +7 -0
  69. data/examples/al-admin/config/boot.rb +110 -0
  70. data/examples/al-admin/config/database.yml.example +19 -0
  71. data/examples/al-admin/config/environment.rb +73 -0
  72. data/examples/al-admin/config/environments/development.rb +17 -0
  73. data/examples/al-admin/config/environments/production.rb +24 -0
  74. data/examples/al-admin/config/environments/test.rb +22 -0
  75. data/examples/al-admin/config/initializers/exception_notifier.rb +2 -0
  76. data/examples/al-admin/config/initializers/fast_gettext.rb +3 -0
  77. data/examples/al-admin/config/initializers/inflections.rb +10 -0
  78. data/examples/al-admin/config/initializers/mime_types.rb +5 -0
  79. data/examples/al-admin/config/initializers/session_store.rb +23 -0
  80. data/examples/al-admin/config/ldap.yml.example +21 -0
  81. data/examples/al-admin/config/routes.rb +58 -0
  82. data/examples/al-admin/db/migrate/001_create_users.rb +16 -0
  83. data/examples/al-admin/doc/README_FOR_APP +2 -0
  84. data/examples/al-admin/lib/authenticated_system.rb +124 -0
  85. data/examples/al-admin/lib/authenticated_test_helper.rb +113 -0
  86. data/examples/al-admin/lib/ldap_test_helper.rb +38 -0
  87. data/examples/al-admin/lib/tasks/testing.rake +10 -0
  88. data/examples/al-admin/po/en/al-admin.po +343 -0
  89. data/examples/al-admin/po/ja/al-admin.po +343 -0
  90. data/examples/al-admin/po/nl/al-admin.po +380 -0
  91. data/examples/al-admin/public/.htaccess +40 -0
  92. data/examples/al-admin/public/404.html +30 -0
  93. data/examples/al-admin/public/500.html +30 -0
  94. data/examples/al-admin/public/dispatch.cgi +10 -0
  95. data/examples/al-admin/public/dispatch.fcgi +24 -0
  96. data/examples/al-admin/public/dispatch.rb +10 -0
  97. data/examples/al-admin/public/favicon.ico +0 -0
  98. data/examples/al-admin/public/images/active-ldap.svg +6351 -0
  99. data/examples/al-admin/public/images/al-admin.png +0 -0
  100. data/examples/al-admin/public/images/al-admin.svg +6371 -0
  101. data/examples/al-admin/public/images/header-background.png +0 -0
  102. data/examples/al-admin/public/images/header-background.svg +99 -0
  103. data/examples/al-admin/public/images/rails.png +0 -0
  104. data/examples/al-admin/public/images/spinelz/accordion_tab_left_active.gif +0 -0
  105. data/examples/al-admin/public/images/spinelz/accordion_tab_left_inactive.gif +0 -0
  106. data/examples/al-admin/public/images/spinelz/accordion_tab_middle_active.gif +0 -0
  107. data/examples/al-admin/public/images/spinelz/accordion_tab_middle_inactive.gif +0 -0
  108. data/examples/al-admin/public/images/spinelz/accordion_tab_right_active.gif +0 -0
  109. data/examples/al-admin/public/images/spinelz/accordion_tab_right_inactive.gif +0 -0
  110. data/examples/al-admin/public/images/spinelz/balloon_back.gif +0 -0
  111. data/examples/al-admin/public/images/spinelz/balloon_bottom_left.gif +0 -0
  112. data/examples/al-admin/public/images/spinelz/balloon_bottom_middle.gif +0 -0
  113. data/examples/al-admin/public/images/spinelz/balloon_bottom_right.gif +0 -0
  114. data/examples/al-admin/public/images/spinelz/balloon_left_down_arrow.gif +0 -0
  115. data/examples/al-admin/public/images/spinelz/balloon_left_up_arrow.gif +0 -0
  116. data/examples/al-admin/public/images/spinelz/balloon_middle_left.gif +0 -0
  117. data/examples/al-admin/public/images/spinelz/balloon_middle_right.gif +0 -0
  118. data/examples/al-admin/public/images/spinelz/balloon_right_down_arrow.gif +0 -0
  119. data/examples/al-admin/public/images/spinelz/balloon_right_up_arrow.gif +0 -0
  120. data/examples/al-admin/public/images/spinelz/balloon_top_left.gif +0 -0
  121. data/examples/al-admin/public/images/spinelz/balloon_top_middle.gif +0 -0
  122. data/examples/al-admin/public/images/spinelz/balloon_top_right.gif +0 -0
  123. data/examples/al-admin/public/images/spinelz/barchart_h.gif +0 -0
  124. data/examples/al-admin/public/images/spinelz/barchart_v.gif +0 -0
  125. data/examples/al-admin/public/images/spinelz/button.gif +0 -0
  126. data/examples/al-admin/public/images/spinelz/calendar_default_handler.gif +0 -0
  127. data/examples/al-admin/public/images/spinelz/calendar_delete.gif +0 -0
  128. data/examples/al-admin/public/images/spinelz/calendar_next.gif +0 -0
  129. data/examples/al-admin/public/images/spinelz/calendar_next_second.gif +0 -0
  130. data/examples/al-admin/public/images/spinelz/calendar_pre.gif +0 -0
  131. data/examples/al-admin/public/images/spinelz/calendar_pre_second.gif +0 -0
  132. data/examples/al-admin/public/images/spinelz/calendar_private_icon.gif +0 -0
  133. data/examples/al-admin/public/images/spinelz/calendar_schedule.gif +0 -0
  134. data/examples/al-admin/public/images/spinelz/calender_back.gif +0 -0
  135. data/examples/al-admin/public/images/spinelz/calender_back_second.gif +0 -0
  136. data/examples/al-admin/public/images/spinelz/datepicker2_back.gif +0 -0
  137. data/examples/al-admin/public/images/spinelz/datepicker2_back_second.gif +0 -0
  138. data/examples/al-admin/public/images/spinelz/datepicker2_next.gif +0 -0
  139. data/examples/al-admin/public/images/spinelz/datepicker2_next_second.gif +0 -0
  140. data/examples/al-admin/public/images/spinelz/datepicker2_pre.gif +0 -0
  141. data/examples/al-admin/public/images/spinelz/datepicker2_pre_second.gif +0 -0
  142. data/examples/al-admin/public/images/spinelz/datepicker_back.gif +0 -0
  143. data/examples/al-admin/public/images/spinelz/datepicker_back_second.gif +0 -0
  144. data/examples/al-admin/public/images/spinelz/datepicker_next.gif +0 -0
  145. data/examples/al-admin/public/images/spinelz/datepicker_next_second.gif +0 -0
  146. data/examples/al-admin/public/images/spinelz/datepicker_pre.gif +0 -0
  147. data/examples/al-admin/public/images/spinelz/datepicker_pre_second.gif +0 -0
  148. data/examples/al-admin/public/images/spinelz/grid_down.gif +0 -0
  149. data/examples/al-admin/public/images/spinelz/grid_state.gif +0 -0
  150. data/examples/al-admin/public/images/spinelz/grid_up.gif +0 -0
  151. data/examples/al-admin/public/images/spinelz/icon_day.gif +0 -0
  152. data/examples/al-admin/public/images/spinelz/icon_month.gif +0 -0
  153. data/examples/al-admin/public/images/spinelz/icon_week.gif +0 -0
  154. data/examples/al-admin/public/images/spinelz/menubar_back.gif +0 -0
  155. data/examples/al-admin/public/images/spinelz/menubar_subcontents_back.gif +0 -0
  156. data/examples/al-admin/public/images/spinelz/navPanel_tab_left_active.gif +0 -0
  157. data/examples/al-admin/public/images/spinelz/navPanel_tab_left_inactive.gif +0 -0
  158. data/examples/al-admin/public/images/spinelz/navPanel_tab_middle_active.gif +0 -0
  159. data/examples/al-admin/public/images/spinelz/navPanel_tab_middle_inactive.gif +0 -0
  160. data/examples/al-admin/public/images/spinelz/navPanel_tab_right_active.gif +0 -0
  161. data/examples/al-admin/public/images/spinelz/navPanel_tab_right_inactive.gif +0 -0
  162. data/examples/al-admin/public/images/spinelz/select_date.gif +0 -0
  163. data/examples/al-admin/public/images/spinelz/selectabletable_selected.gif +0 -0
  164. data/examples/al-admin/public/images/spinelz/sideBarBox_about.gif +0 -0
  165. data/examples/al-admin/public/images/spinelz/sideBarBox_menu.gif +0 -0
  166. data/examples/al-admin/public/images/spinelz/sideBarBox_sample.gif +0 -0
  167. data/examples/al-admin/public/images/spinelz/sideBarBox_tabBottomActive.gif +0 -0
  168. data/examples/al-admin/public/images/spinelz/sideBarBox_tabBottomInactive.gif +0 -0
  169. data/examples/al-admin/public/images/spinelz/sideBarBox_tabMiddleActive.gif +0 -0
  170. data/examples/al-admin/public/images/spinelz/sideBarBox_tabMiddleActive2.gif +0 -0
  171. data/examples/al-admin/public/images/spinelz/sideBarBox_tabMiddleInactive.gif +0 -0
  172. data/examples/al-admin/public/images/spinelz/sideBarBox_tabMiddleInactive2.gif +0 -0
  173. data/examples/al-admin/public/images/spinelz/sideBarBox_tabTopActive.gif +0 -0
  174. data/examples/al-admin/public/images/spinelz/sideBarBox_tabTopInactive.gif +0 -0
  175. data/examples/al-admin/public/images/spinelz/sideBarBox_tabbar.gif +0 -0
  176. data/examples/al-admin/public/images/spinelz/sortableTable_down.gif +0 -0
  177. data/examples/al-admin/public/images/spinelz/sortableTable_normal.gif +0 -0
  178. data/examples/al-admin/public/images/spinelz/sortableTable_up.gif +0 -0
  179. data/examples/al-admin/public/images/spinelz/switcher_close.gif +0 -0
  180. data/examples/al-admin/public/images/spinelz/switcher_open.gif +0 -0
  181. data/examples/al-admin/public/images/spinelz/tabBox_close.gif +0 -0
  182. data/examples/al-admin/public/images/spinelz/tabBox_tabLeftActive.gif +0 -0
  183. data/examples/al-admin/public/images/spinelz/tabBox_tabLeftInactive.gif +0 -0
  184. data/examples/al-admin/public/images/spinelz/tabBox_tabMiddleActive.gif +0 -0
  185. data/examples/al-admin/public/images/spinelz/tabBox_tabMiddleInactive.gif +0 -0
  186. data/examples/al-admin/public/images/spinelz/tabBox_tabRightActive.gif +0 -0
  187. data/examples/al-admin/public/images/spinelz/tabBox_tabRightInactive.gif +0 -0
  188. data/examples/al-admin/public/images/spinelz/tab_bar.gif +0 -0
  189. data/examples/al-admin/public/images/spinelz/table_back.gif +0 -0
  190. data/examples/al-admin/public/images/spinelz/timepicker_clock.gif +0 -0
  191. data/examples/al-admin/public/images/spinelz/toolbar_close.gif +0 -0
  192. data/examples/al-admin/public/images/spinelz/toolbar_left.gif +0 -0
  193. data/examples/al-admin/public/images/spinelz/toolbar_max.gif +0 -0
  194. data/examples/al-admin/public/images/spinelz/toolbar_middle.gif +0 -0
  195. data/examples/al-admin/public/images/spinelz/toolbar_min.gif +0 -0
  196. data/examples/al-admin/public/images/spinelz/toolbar_next.gif +0 -0
  197. data/examples/al-admin/public/images/spinelz/toolbar_right.gif +0 -0
  198. data/examples/al-admin/public/images/spinelz/treeview_dir.gif +0 -0
  199. data/examples/al-admin/public/images/spinelz/treeview_file.gif +0 -0
  200. data/examples/al-admin/public/images/spinelz/treeview_group.gif +0 -0
  201. data/examples/al-admin/public/images/spinelz/treeview_group_special.gif +0 -0
  202. data/examples/al-admin/public/images/spinelz/treeview_state.gif +0 -0
  203. data/examples/al-admin/public/images/spinelz/treeview_user.gif +0 -0
  204. data/examples/al-admin/public/images/spinelz/window_bottom_left.gif +0 -0
  205. data/examples/al-admin/public/images/spinelz/window_bottom_middle.gif +0 -0
  206. data/examples/al-admin/public/images/spinelz/window_bottom_right.gif +0 -0
  207. data/examples/al-admin/public/images/spinelz/window_close.gif +0 -0
  208. data/examples/al-admin/public/images/spinelz/window_max.gif +0 -0
  209. data/examples/al-admin/public/images/spinelz/window_middle_left.gif +0 -0
  210. data/examples/al-admin/public/images/spinelz/window_middle_right.gif +0 -0
  211. data/examples/al-admin/public/images/spinelz/window_min.gif +0 -0
  212. data/examples/al-admin/public/images/spinelz/window_top_left.gif +0 -0
  213. data/examples/al-admin/public/images/spinelz/window_top_middle.gif +0 -0
  214. data/examples/al-admin/public/images/spinelz/window_top_right.gif +0 -0
  215. data/examples/al-admin/public/javascripts/application.js +2 -0
  216. data/examples/al-admin/public/javascripts/controls.js +963 -0
  217. data/examples/al-admin/public/javascripts/dragdrop.js +973 -0
  218. data/examples/al-admin/public/javascripts/effects.js +1128 -0
  219. data/examples/al-admin/public/javascripts/prototype.js +4320 -0
  220. data/examples/al-admin/public/javascripts/spinelz/accordion.js +185 -0
  221. data/examples/al-admin/public/javascripts/spinelz/ajaxHistory.js +157 -0
  222. data/examples/al-admin/public/javascripts/spinelz/balloon.js +287 -0
  223. data/examples/al-admin/public/javascripts/spinelz/barchart.js +524 -0
  224. data/examples/al-admin/public/javascripts/spinelz/calendar.js +3012 -0
  225. data/examples/al-admin/public/javascripts/spinelz/colorpicker.js +128 -0
  226. data/examples/al-admin/public/javascripts/spinelz/datepicker.js +438 -0
  227. data/examples/al-admin/public/javascripts/spinelz/grid.js +1391 -0
  228. data/examples/al-admin/public/javascripts/spinelz/grid_resizeEx.js +100 -0
  229. data/examples/al-admin/public/javascripts/spinelz/grid_sortabletableEx.js +129 -0
  230. data/examples/al-admin/public/javascripts/spinelz/inplaceEditorEx.js +148 -0
  231. data/examples/al-admin/public/javascripts/spinelz/menubar.js +232 -0
  232. data/examples/al-admin/public/javascripts/spinelz/navPanel.js +170 -0
  233. data/examples/al-admin/public/javascripts/spinelz/selectableTable.js +433 -0
  234. data/examples/al-admin/public/javascripts/spinelz/sideBarBox.js +282 -0
  235. data/examples/al-admin/public/javascripts/spinelz/sideBarBox_effects.js +83 -0
  236. data/examples/al-admin/public/javascripts/spinelz/sortableTable.js +270 -0
  237. data/examples/al-admin/public/javascripts/spinelz/switcher.js +78 -0
  238. data/examples/al-admin/public/javascripts/spinelz/tabBox.js +469 -0
  239. data/examples/al-admin/public/javascripts/spinelz/timepicker.js +384 -0
  240. data/examples/al-admin/public/javascripts/spinelz/toolbar.js +152 -0
  241. data/examples/al-admin/public/javascripts/spinelz/treeview.js +703 -0
  242. data/examples/al-admin/public/javascripts/spinelz/window.js +641 -0
  243. data/examples/al-admin/public/javascripts/spinelz/window_resizeEx.js +130 -0
  244. data/examples/al-admin/public/javascripts/spinelz_lib/builder.js +131 -0
  245. data/examples/al-admin/public/javascripts/spinelz_lib/controls.js +835 -0
  246. data/examples/al-admin/public/javascripts/spinelz_lib/dragdrop.js +944 -0
  247. data/examples/al-admin/public/javascripts/spinelz_lib/effects.js +1090 -0
  248. data/examples/al-admin/public/javascripts/spinelz_lib/json.js +139 -0
  249. data/examples/al-admin/public/javascripts/spinelz_lib/prototype.js +2515 -0
  250. data/examples/al-admin/public/javascripts/spinelz_lib/resize.js +215 -0
  251. data/examples/al-admin/public/javascripts/spinelz_lib/scriptaculous.js +51 -0
  252. data/examples/al-admin/public/javascripts/spinelz_lib/slider.js +278 -0
  253. data/examples/al-admin/public/javascripts/spinelz_lib/spinelz_util.js +1266 -0
  254. data/examples/al-admin/public/javascripts/spinelz_lib/unittest.js +564 -0
  255. data/examples/al-admin/public/robots.txt +1 -0
  256. data/examples/al-admin/public/stylesheets/account.css +41 -0
  257. data/examples/al-admin/public/stylesheets/attributes.css +1 -0
  258. data/examples/al-admin/public/stylesheets/base.css +104 -0
  259. data/examples/al-admin/public/stylesheets/detail.css +36 -0
  260. data/examples/al-admin/public/stylesheets/directory.css +22 -0
  261. data/examples/al-admin/public/stylesheets/object-classes.css +6 -0
  262. data/examples/al-admin/public/stylesheets/rails.css +35 -0
  263. data/examples/al-admin/public/stylesheets/spinelz/accordion.css +59 -0
  264. data/examples/al-admin/public/stylesheets/spinelz/balloon.css +151 -0
  265. data/examples/al-admin/public/stylesheets/spinelz/calendar.css +564 -0
  266. data/examples/al-admin/public/stylesheets/spinelz/datepicker.css +175 -0
  267. data/examples/al-admin/public/stylesheets/spinelz/grid.css +137 -0
  268. data/examples/al-admin/public/stylesheets/spinelz/menubar.css +78 -0
  269. data/examples/al-admin/public/stylesheets/spinelz/modal.css +22 -0
  270. data/examples/al-admin/public/stylesheets/spinelz/navPanel.css +58 -0
  271. data/examples/al-admin/public/stylesheets/spinelz/selectableTable.css +28 -0
  272. data/examples/al-admin/public/stylesheets/spinelz/sideBarBox.css +82 -0
  273. data/examples/al-admin/public/stylesheets/spinelz/sortableTable.css +51 -0
  274. data/examples/al-admin/public/stylesheets/spinelz/switcher.css +23 -0
  275. data/examples/al-admin/public/stylesheets/spinelz/tabBox.css +94 -0
  276. data/examples/al-admin/public/stylesheets/spinelz/timepicker.css +508 -0
  277. data/examples/al-admin/public/stylesheets/spinelz/toolbar.css +82 -0
  278. data/examples/al-admin/public/stylesheets/spinelz/treeview.css +121 -0
  279. data/examples/al-admin/public/stylesheets/spinelz/window.css +140 -0
  280. data/examples/al-admin/public/stylesheets/structure.css +81 -0
  281. data/examples/al-admin/public/stylesheets/syntaxes.css +1 -0
  282. data/examples/al-admin/public/stylesheets/users.css +13 -0
  283. data/examples/al-admin/public/stylesheets/welcome.css +0 -0
  284. data/examples/al-admin/script/about +3 -0
  285. data/examples/al-admin/script/console +3 -0
  286. data/examples/al-admin/script/dbconsole +3 -0
  287. data/examples/al-admin/script/destroy +3 -0
  288. data/examples/al-admin/script/generate +3 -0
  289. data/examples/al-admin/script/performance/benchmarker +3 -0
  290. data/examples/al-admin/script/performance/profiler +3 -0
  291. data/examples/al-admin/script/performance/request +3 -0
  292. data/examples/al-admin/script/plugin +3 -0
  293. data/examples/al-admin/script/process/inspector +3 -0
  294. data/examples/al-admin/script/process/reaper +3 -0
  295. data/examples/al-admin/script/process/spawner +3 -0
  296. data/examples/al-admin/script/runner +3 -0
  297. data/examples/al-admin/script/server +3 -0
  298. data/examples/al-admin/test/fixtures/users.yml +9 -0
  299. data/examples/al-admin/test/functional/account_controller_test.rb +12 -0
  300. data/examples/al-admin/test/functional/attributes_controller_test.rb +8 -0
  301. data/examples/al-admin/test/functional/directory_controller_test.rb +8 -0
  302. data/examples/al-admin/test/functional/object_classes_controller_test.rb +8 -0
  303. data/examples/al-admin/test/functional/syntaxes_controller_test.rb +8 -0
  304. data/examples/al-admin/test/functional/users_controller_test.rb +8 -0
  305. data/examples/al-admin/test/functional/welcome_controller_test.rb +8 -0
  306. data/examples/al-admin/test/integration/sign_up_test.rb +44 -0
  307. data/examples/al-admin/test/run-test.sh +3 -0
  308. data/examples/al-admin/test/test_helper.rb +52 -0
  309. data/examples/al-admin/test/unit/user_test.rb +12 -0
  310. data/examples/al-admin/vendor/plugins/exception_notification/README +111 -0
  311. data/examples/al-admin/vendor/plugins/exception_notification/init.rb +1 -0
  312. data/examples/al-admin/vendor/plugins/exception_notification/lib/exception_notifiable.rb +99 -0
  313. data/examples/al-admin/vendor/plugins/exception_notification/lib/exception_notifier.rb +66 -0
  314. data/examples/al-admin/vendor/plugins/exception_notification/lib/exception_notifier_helper.rb +78 -0
  315. data/examples/al-admin/vendor/plugins/exception_notification/test/exception_notifier_helper_test.rb +61 -0
  316. data/examples/al-admin/vendor/plugins/exception_notification/test/test_helper.rb +7 -0
  317. data/examples/al-admin/vendor/plugins/exception_notification/views/exception_notifier/_backtrace.rhtml +1 -0
  318. data/examples/al-admin/vendor/plugins/exception_notification/views/exception_notifier/_environment.rhtml +7 -0
  319. data/examples/al-admin/vendor/plugins/exception_notification/views/exception_notifier/_inspect_model.rhtml +16 -0
  320. data/examples/al-admin/vendor/plugins/exception_notification/views/exception_notifier/_request.rhtml +4 -0
  321. data/examples/al-admin/vendor/plugins/exception_notification/views/exception_notifier/_session.rhtml +2 -0
  322. data/examples/al-admin/vendor/plugins/exception_notification/views/exception_notifier/_title.rhtml +3 -0
  323. data/examples/al-admin/vendor/plugins/exception_notification/views/exception_notifier/exception_notification.rhtml +6 -0
  324. data/examples/config.yaml.example +5 -0
  325. data/examples/example.der +0 -0
  326. data/examples/example.jpg +0 -0
  327. data/examples/groupadd +41 -0
  328. data/examples/groupdel +35 -0
  329. data/examples/groupls +49 -0
  330. data/examples/groupmod +42 -0
  331. data/examples/lpasswd +55 -0
  332. data/examples/objects/group.rb +13 -0
  333. data/examples/objects/ou.rb +4 -0
  334. data/examples/objects/user.rb +20 -0
  335. data/examples/ouadd +38 -0
  336. data/examples/useradd +45 -0
  337. data/examples/useradd-binary +53 -0
  338. data/examples/userdel +34 -0
  339. data/examples/userls +50 -0
  340. data/examples/usermod +42 -0
  341. data/examples/usermod-binary-add +50 -0
  342. data/examples/usermod-binary-add-time +54 -0
  343. data/examples/usermod-binary-del +48 -0
  344. data/examples/usermod-lang-add +43 -0
  345. data/lib/active_ldap.rb +105 -0
  346. data/lib/active_ldap/action_controller/ldap_benchmarking.rb +55 -0
  347. data/lib/active_ldap/acts/tree.rb +75 -0
  348. data/lib/active_ldap/adapter/base.rb +705 -0
  349. data/lib/active_ldap/adapter/jndi.rb +184 -0
  350. data/lib/active_ldap/adapter/jndi_connection.rb +185 -0
  351. data/lib/active_ldap/adapter/ldap.rb +290 -0
  352. data/lib/active_ldap/adapter/ldap_ext.rb +105 -0
  353. data/lib/active_ldap/adapter/net_ldap.rb +309 -0
  354. data/lib/active_ldap/adapter/net_ldap_ext.rb +23 -0
  355. data/lib/active_ldap/association/belongs_to.rb +47 -0
  356. data/lib/active_ldap/association/belongs_to_many.rb +58 -0
  357. data/lib/active_ldap/association/children.rb +21 -0
  358. data/lib/active_ldap/association/collection.rb +105 -0
  359. data/lib/active_ldap/association/has_many.rb +31 -0
  360. data/lib/active_ldap/association/has_many_utils.rb +44 -0
  361. data/lib/active_ldap/association/has_many_wrap.rb +62 -0
  362. data/lib/active_ldap/association/proxy.rb +107 -0
  363. data/lib/active_ldap/associations.rb +202 -0
  364. data/lib/active_ldap/attributes.rb +184 -0
  365. data/lib/active_ldap/base.rb +1594 -0
  366. data/lib/active_ldap/callbacks.rb +52 -0
  367. data/lib/active_ldap/command.rb +49 -0
  368. data/lib/active_ldap/compatible.rb +44 -0
  369. data/lib/active_ldap/configuration.rb +147 -0
  370. data/lib/active_ldap/connection.rb +294 -0
  371. data/lib/active_ldap/distinguished_name.rb +291 -0
  372. data/lib/active_ldap/entry_attribute.rb +78 -0
  373. data/lib/active_ldap/escape.rb +12 -0
  374. data/lib/active_ldap/get_text.rb +9 -0
  375. data/lib/active_ldap/get_text/parser.rb +161 -0
  376. data/lib/active_ldap/get_text_fallback.rb +60 -0
  377. data/lib/active_ldap/get_text_support.rb +20 -0
  378. data/lib/active_ldap/helper.rb +92 -0
  379. data/lib/active_ldap/human_readable.rb +132 -0
  380. data/lib/active_ldap/ldap_error.rb +74 -0
  381. data/lib/active_ldap/ldif.rb +930 -0
  382. data/lib/active_ldap/object_class.rb +95 -0
  383. data/lib/active_ldap/operations.rb +610 -0
  384. data/lib/active_ldap/populate.rb +53 -0
  385. data/lib/active_ldap/railtie.rb +31 -0
  386. data/lib/active_ldap/schema.rb +699 -0
  387. data/lib/active_ldap/schema/syntaxes.rb +417 -0
  388. data/lib/active_ldap/timeout.rb +75 -0
  389. data/lib/active_ldap/timeout_stub.rb +17 -0
  390. data/lib/active_ldap/user_password.rb +92 -0
  391. data/lib/active_ldap/validations.rb +229 -0
  392. data/lib/active_ldap/xml.rb +122 -0
  393. data/po/en/active-ldap.po +4029 -0
  394. data/po/ja/active-ldap.po +4060 -0
  395. data/rails_generators/model_active_ldap/USAGE +17 -0
  396. data/rails_generators/model_active_ldap/model_active_ldap_generator.rb +69 -0
  397. data/rails_generators/model_active_ldap/templates/model_active_ldap.rb +3 -0
  398. data/rails_generators/model_active_ldap/templates/unit_test.rb +8 -0
  399. data/rails_generators/scaffold_active_ldap/scaffold_active_ldap_generator.rb +7 -0
  400. data/rails_generators/scaffold_active_ldap/templates/ldap.yml +18 -0
  401. data/test/al-test-utils.rb +439 -0
  402. data/test/command.rb +112 -0
  403. data/test/config.yaml.sample +6 -0
  404. data/test/fixtures/lower_case_object_class_schema.rb +802 -0
  405. data/test/run-test.rb +44 -0
  406. data/test/test_acts_as_tree.rb +60 -0
  407. data/test/test_adapter.rb +121 -0
  408. data/test/test_associations.rb +664 -0
  409. data/test/test_attributes.rb +117 -0
  410. data/test/test_base.rb +1177 -0
  411. data/test/test_base_per_instance.rb +61 -0
  412. data/test/test_bind.rb +62 -0
  413. data/test/test_callback.rb +37 -0
  414. data/test/test_configuration.rb +40 -0
  415. data/test/test_connection.rb +82 -0
  416. data/test/test_connection_per_class.rb +112 -0
  417. data/test/test_connection_per_dn.rb +112 -0
  418. data/test/test_dn.rb +172 -0
  419. data/test/test_find.rb +176 -0
  420. data/test/test_groupadd.rb +50 -0
  421. data/test/test_groupdel.rb +46 -0
  422. data/test/test_groupls.rb +107 -0
  423. data/test/test_groupmod.rb +51 -0
  424. data/test/test_ldif.rb +1891 -0
  425. data/test/test_load.rb +133 -0
  426. data/test/test_lpasswd.rb +75 -0
  427. data/test/test_object_class.rb +74 -0
  428. data/test/test_reflection.rb +182 -0
  429. data/test/test_schema.rb +559 -0
  430. data/test/test_syntax.rb +383 -0
  431. data/test/test_user.rb +217 -0
  432. data/test/test_user_password.rb +101 -0
  433. data/test/test_useradd-binary.rb +62 -0
  434. data/test/test_useradd.rb +57 -0
  435. data/test/test_userdel.rb +48 -0
  436. data/test/test_userls.rb +91 -0
  437. data/test/test_usermod-binary-add-time.rb +65 -0
  438. data/test/test_usermod-binary-add.rb +64 -0
  439. data/test/test_usermod-binary-del.rb +66 -0
  440. data/test/test_usermod-lang-add.rb +60 -0
  441. data/test/test_usermod.rb +58 -0
  442. data/test/test_validation.rb +274 -0
  443. 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