incline 0.1.5

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 (303) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +13 -0
  3. data/Gemfile +17 -0
  4. data/Gemfile.lock +186 -0
  5. data/MIT-LICENSE +20 -0
  6. data/README.rdoc +208 -0
  7. data/Rakefile +37 -0
  8. data/app/assets/fonts/incline/.keep +0 -0
  9. data/app/assets/images/incline/.keep +0 -0
  10. data/app/assets/images/incline/barcode-B.svg +181 -0
  11. data/app/assets/javascripts/incline/activate_classed_items.js +11 -0
  12. data/app/assets/javascripts/incline/application.js +30 -0
  13. data/app/assets/javascripts/incline/bootstrap-datepicker.js +1800 -0
  14. data/app/assets/javascripts/incline/datatables.js +22193 -0
  15. data/app/assets/javascripts/incline/escapeHtml.js +10 -0
  16. data/app/assets/javascripts/incline/inline_actions.js +479 -0
  17. data/app/assets/javascripts/incline/jquery.doubleScroll.js +112 -0
  18. data/app/assets/javascripts/incline/jquery.number.js +764 -0
  19. data/app/assets/javascripts/incline/regexMask.js +27 -0
  20. data/app/assets/javascripts/incline/select2/i18n/en.js +3 -0
  21. data/app/assets/javascripts/incline/select2/select2.full.js +6436 -0
  22. data/app/assets/stylesheets/incline/application.css +18 -0
  23. data/app/assets/stylesheets/incline/bootstrap-theme.min.css +5 -0
  24. data/app/assets/stylesheets/incline/custom.scss +279 -0
  25. data/app/assets/stylesheets/incline/datatables.css +494 -0
  26. data/app/assets/stylesheets/incline/datepicker3.css +790 -0
  27. data/app/assets/stylesheets/incline/select2.css +484 -0
  28. data/app/controllers/incline/access_groups_controller.rb +127 -0
  29. data/app/controllers/incline/access_test_controller.rb +30 -0
  30. data/app/controllers/incline/account_activations_controller.rb +28 -0
  31. data/app/controllers/incline/application_controller.rb +11 -0
  32. data/app/controllers/incline/contact_controller.rb +34 -0
  33. data/app/controllers/incline/password_resets_controller.rb +113 -0
  34. data/app/controllers/incline/security_controller.rb +100 -0
  35. data/app/controllers/incline/sessions_controller.rb +50 -0
  36. data/app/controllers/incline/users_controller.rb +304 -0
  37. data/app/controllers/incline/welcome_controller.rb +19 -0
  38. data/app/helpers/incline/.keep +0 -0
  39. data/app/mailers/incline/application_mailer_base.rb +11 -0
  40. data/app/mailers/incline/contact_form.rb +19 -0
  41. data/app/mailers/incline/user_mailer.rb +45 -0
  42. data/app/models/incline/access_group.rb +121 -0
  43. data/app/models/incline/access_group_group_member.rb +12 -0
  44. data/app/models/incline/access_group_user_member.rb +10 -0
  45. data/app/models/incline/action_group.rb +12 -0
  46. data/app/models/incline/action_security.rb +222 -0
  47. data/app/models/incline/contact_message.rb +37 -0
  48. data/app/models/incline/disable_info.rb +20 -0
  49. data/app/models/incline/password_reset.rb +14 -0
  50. data/app/models/incline/password_reset_request.rb +14 -0
  51. data/app/models/incline/user.rb +437 -0
  52. data/app/models/incline/user_login_history.rb +30 -0
  53. data/app/views/incline/access_groups/_details.json.jbuilder +10 -0
  54. data/app/views/incline/access_groups/_form.html.erb +19 -0
  55. data/app/views/incline/access_groups/_list.html.erb +60 -0
  56. data/app/views/incline/access_groups/_messages.json.jbuilder +6 -0
  57. data/app/views/incline/access_groups/edit.html.erb +2 -0
  58. data/app/views/incline/access_groups/index.html.erb +6 -0
  59. data/app/views/incline/access_groups/index.json.jbuilder +16 -0
  60. data/app/views/incline/access_groups/new.html.erb +2 -0
  61. data/app/views/incline/access_groups/show.html.erb +9 -0
  62. data/app/views/incline/access_groups/show.json.jbuilder +11 -0
  63. data/app/views/incline/contact/new.html.erb +22 -0
  64. data/app/views/incline/contact_form/contact.html.erb +16 -0
  65. data/app/views/incline/contact_form/contact.text.erb +13 -0
  66. data/app/views/incline/password_resets/edit.html.erb +16 -0
  67. data/app/views/incline/password_resets/new.html.erb +12 -0
  68. data/app/views/incline/security/_details.json.jbuilder +7 -0
  69. data/app/views/incline/security/_form.html.erb +20 -0
  70. data/app/views/incline/security/_list.html.erb +89 -0
  71. data/app/views/incline/security/_messages.json.jbuilder +6 -0
  72. data/app/views/incline/security/edit.html.erb +2 -0
  73. data/app/views/incline/security/index.html.erb +6 -0
  74. data/app/views/incline/security/index.json.jbuilder +16 -0
  75. data/app/views/incline/security/show.html.erb +31 -0
  76. data/app/views/incline/security/show.json.jbuilder +11 -0
  77. data/app/views/incline/sessions/new.html.erb +26 -0
  78. data/app/views/incline/user_mailer/account_activation.html.erb +7 -0
  79. data/app/views/incline/user_mailer/account_activation.text.erb +6 -0
  80. data/app/views/incline/user_mailer/invalid_password_reset.html.erb +3 -0
  81. data/app/views/incline/user_mailer/invalid_password_reset.text.erb +5 -0
  82. data/app/views/incline/user_mailer/password_reset.html.erb +8 -0
  83. data/app/views/incline/user_mailer/password_reset.text.erb +7 -0
  84. data/app/views/incline/users/_details.json.jbuilder +32 -0
  85. data/app/views/incline/users/_form.html.erb +21 -0
  86. data/app/views/incline/users/_list.html.erb +102 -0
  87. data/app/views/incline/users/_messages.json.jbuilder +6 -0
  88. data/app/views/incline/users/disable_confirm.html.erb +19 -0
  89. data/app/views/incline/users/edit.html.erb +5 -0
  90. data/app/views/incline/users/index.html.erb +6 -0
  91. data/app/views/incline/users/index.json.jbuilder +16 -0
  92. data/app/views/incline/users/new.html.erb +5 -0
  93. data/app/views/incline/users/show.html.erb +12 -0
  94. data/app/views/incline/users/show.json.jbuilder +11 -0
  95. data/app/views/incline/welcome/home.html.erb +5 -0
  96. data/app/views/layouts/application.html.erb +1 -0
  97. data/app/views/layouts/incline/_account_menu.html.erb +18 -0
  98. data/app/views/layouts/incline/_app_menu_anon.html.erb +1 -0
  99. data/app/views/layouts/incline/_app_menu_authenticated.html.erb +1 -0
  100. data/app/views/layouts/incline/_footer.html.erb +13 -0
  101. data/app/views/layouts/incline/_header.html.erb +21 -0
  102. data/app/views/layouts/incline/_html_mailer.html.erb +5 -0
  103. data/app/views/layouts/incline/_incline_app.html.erb +25 -0
  104. data/app/views/layouts/incline/_messages.html.erb +3 -0
  105. data/app/views/layouts/incline/_shim.html.erb +3 -0
  106. data/app/views/layouts/incline/_text_mailer.text.erb +1 -0
  107. data/app/views/layouts/incline/application.html.erb +1 -0
  108. data/app/views/layouts/mailer.html.erb +2 -0
  109. data/app/views/layouts/mailer.text.erb +2 -0
  110. data/bin/rails +12 -0
  111. data/bin/test_scaffold.sh +10 -0
  112. data/config/routes.rb +61 -0
  113. data/db/migrate/20170511230126_create_incline_users.rb +26 -0
  114. data/db/migrate/20170515003052_create_incline_access_groups.rb +10 -0
  115. data/db/migrate/20170515003221_create_incline_user_login_histories.rb +12 -0
  116. data/db/migrate/20170515150908_create_incline_access_group_user_members.rb +11 -0
  117. data/db/migrate/20170515151058_create_incline_access_group_group_members.rb +11 -0
  118. data/db/migrate/20170517193432_add_comments_to_incline_user.rb +5 -0
  119. data/db/migrate/20170622132700_create_incline_action_securities.rb +16 -0
  120. data/db/migrate/20170622172712_create_incline_action_groups.rb +11 -0
  121. data/db/migrate/20170622195742_add_non_standard_to_action_security.rb +5 -0
  122. data/db/migrate/20170622230422_add_visible_to_action_security.rb +5 -0
  123. data/db/seeds.rb +81 -0
  124. data/exe/new_incline_app +42 -0
  125. data/lib/generators/incline/install_generator.rb +259 -0
  126. data/lib/generators/incline/templates/_app_menu_anon.html.erb +1 -0
  127. data/lib/generators/incline/templates/_app_menu_authenticated.html.erb +1 -0
  128. data/lib/generators/incline/templates/incline_application.css +17 -0
  129. data/lib/generators/incline/templates/incline_application.html.erb +1 -0
  130. data/lib/generators/incline/templates/incline_application.js +12 -0
  131. data/lib/generators/incline/templates/incline_database.yml +25 -0
  132. data/lib/generators/incline/templates/incline_email.yml +20 -0
  133. data/lib/generators/incline/templates/incline_mailer.html.erb +2 -0
  134. data/lib/generators/incline/templates/incline_mailer.text.erb +2 -0
  135. data/lib/generators/incline/templates/incline_users.yml +64 -0
  136. data/lib/generators/incline/templates/incline_version.rb +3 -0
  137. data/lib/incline/auth_engine_base.rb +52 -0
  138. data/lib/incline/data_tables_request.rb +336 -0
  139. data/lib/incline/date_time_formats.rb +6 -0
  140. data/lib/incline/engine.rb +212 -0
  141. data/lib/incline/errors.rb +15 -0
  142. data/lib/incline/extensions/action_controller_base.rb +526 -0
  143. data/lib/incline/extensions/action_mailer_base.rb +66 -0
  144. data/lib/incline/extensions/action_view_base.rb +489 -0
  145. data/lib/incline/extensions/active_record_base.rb +308 -0
  146. data/lib/incline/extensions/application.rb +137 -0
  147. data/lib/incline/extensions/application_configuration.rb +50 -0
  148. data/lib/incline/extensions/connection_adapter.rb +55 -0
  149. data/lib/incline/extensions/date_time_value.rb +123 -0
  150. data/lib/incline/extensions/date_value.rb +77 -0
  151. data/lib/incline/extensions/decimal_value.rb +55 -0
  152. data/lib/incline/extensions/erb_scaffold_generator.rb +31 -0
  153. data/lib/incline/extensions/float_value.rb +59 -0
  154. data/lib/incline/extensions/form_builder.rb +617 -0
  155. data/lib/incline/extensions/integer_value.rb +54 -0
  156. data/lib/incline/extensions/jbuilder_generator.rb +38 -0
  157. data/lib/incline/extensions/jbuilder_template.rb +39 -0
  158. data/lib/incline/extensions/main_app.rb +40 -0
  159. data/lib/incline/extensions/numeric.rb +63 -0
  160. data/lib/incline/extensions/object.rb +31 -0
  161. data/lib/incline/extensions/resource_route_generator.rb +53 -0
  162. data/lib/incline/extensions/session.rb +113 -0
  163. data/lib/incline/extensions/string.rb +50 -0
  164. data/lib/incline/extensions/test_case.rb +764 -0
  165. data/lib/incline/extensions/time_zone_converter.rb +40 -0
  166. data/lib/incline/global_status.rb +236 -0
  167. data/lib/incline/helpers/route_hash_formatter.rb +46 -0
  168. data/lib/incline/json_log_formatter.rb +96 -0
  169. data/lib/incline/json_logger.rb +17 -0
  170. data/lib/incline/log.rb +153 -0
  171. data/lib/incline/number_formats.rb +17 -0
  172. data/lib/incline/recaptcha.rb +346 -0
  173. data/lib/incline/user_manager.rb +212 -0
  174. data/lib/incline/validators/email_validator.rb +45 -0
  175. data/lib/incline/validators/ip_address_validator.rb +32 -0
  176. data/lib/incline/validators/recaptcha_validator.rb +37 -0
  177. data/lib/incline/validators/safe_name_validator.rb +31 -0
  178. data/lib/incline/version.rb +3 -0
  179. data/lib/incline/work_path.rb +75 -0
  180. data/lib/incline.rb +197 -0
  181. data/lib/tasks/incline_tasks.rake +4 -0
  182. data/lib/templates/erb/scaffold/_form.html.erb +43 -0
  183. data/lib/templates/erb/scaffold/_list.html.erb +81 -0
  184. data/lib/templates/erb/scaffold/edit.html.erb +1 -0
  185. data/lib/templates/erb/scaffold/index.html.erb +6 -0
  186. data/lib/templates/erb/scaffold/new.html.erb +1 -0
  187. data/lib/templates/erb/scaffold/show.html.erb +34 -0
  188. data/lib/templates/jbuilder/scaffold/_details.json.jbuilder +20 -0
  189. data/lib/templates/jbuilder/scaffold/index.json.jbuilder +16 -0
  190. data/lib/templates/jbuilder/scaffold/show.json.jbuilder +16 -0
  191. data/lib/templates/rails/scaffold_controller/controller.rb +128 -0
  192. data/test/controllers/incline/access_groups_controller_test.rb +65 -0
  193. data/test/controllers/incline/access_test_controller_test.rb +53 -0
  194. data/test/controllers/incline/contact_controller_test.rb +32 -0
  195. data/test/controllers/incline/security_controller_test.rb +39 -0
  196. data/test/controllers/incline/welcome_controller_test.rb +16 -0
  197. data/test/dummy/README.rdoc +28 -0
  198. data/test/dummy/Rakefile +6 -0
  199. data/test/dummy/app/assets/images/.keep +0 -0
  200. data/test/dummy/app/assets/javascripts/application.js +12 -0
  201. data/test/dummy/app/assets/stylesheets/application.css +17 -0
  202. data/test/dummy/app/controllers/application_controller.rb +5 -0
  203. data/test/dummy/app/controllers/concerns/.keep +0 -0
  204. data/test/dummy/app/helpers/application_helper.rb +2 -0
  205. data/test/dummy/app/mailers/.keep +0 -0
  206. data/test/dummy/app/models/.keep +0 -0
  207. data/test/dummy/app/models/concerns/.keep +0 -0
  208. data/test/dummy/app/views/layouts/application.html.erb +1 -0
  209. data/test/dummy/app/views/layouts/incline/_app_menu_anon.html.erb +1 -0
  210. data/test/dummy/app/views/layouts/incline/_app_menu_authenticated.html.erb +1 -0
  211. data/test/dummy/app/views/layouts/mailer.html.erb +2 -0
  212. data/test/dummy/app/views/layouts/mailer.text.erb +2 -0
  213. data/test/dummy/bin/bundle +3 -0
  214. data/test/dummy/bin/rails +4 -0
  215. data/test/dummy/bin/rake +4 -0
  216. data/test/dummy/bin/setup +29 -0
  217. data/test/dummy/config/application.rb +38 -0
  218. data/test/dummy/config/boot.rb +5 -0
  219. data/test/dummy/config/database.yml +34 -0
  220. data/test/dummy/config/email.yml +24 -0
  221. data/test/dummy/config/environment.rb +5 -0
  222. data/test/dummy/config/environments/development.rb +45 -0
  223. data/test/dummy/config/environments/production.rb +85 -0
  224. data/test/dummy/config/environments/test.rb +44 -0
  225. data/test/dummy/config/initializers/assets.rb +11 -0
  226. data/test/dummy/config/initializers/backtrace_silencers.rb +7 -0
  227. data/test/dummy/config/initializers/cookies_serializer.rb +3 -0
  228. data/test/dummy/config/initializers/filter_parameter_logging.rb +4 -0
  229. data/test/dummy/config/initializers/inflections.rb +16 -0
  230. data/test/dummy/config/initializers/mime_types.rb +4 -0
  231. data/test/dummy/config/initializers/session_store.rb +3 -0
  232. data/test/dummy/config/initializers/to_time_preserves_timezone.rb +10 -0
  233. data/test/dummy/config/initializers/wrap_parameters.rb +14 -0
  234. data/test/dummy/config/locales/en.yml +23 -0
  235. data/test/dummy/config/routes.rb +6 -0
  236. data/test/dummy/config.ru +4 -0
  237. data/test/dummy/db/schema.rb +108 -0
  238. data/test/dummy/lib/assets/.keep +0 -0
  239. data/test/dummy/log/.keep +0 -0
  240. data/test/dummy/public/404.html +67 -0
  241. data/test/dummy/public/422.html +67 -0
  242. data/test/dummy/public/500.html +66 -0
  243. data/test/dummy/public/favicon.ico +0 -0
  244. data/test/extensions/action_controller_base_extensions_test.rb +21 -0
  245. data/test/extensions/action_mailer_base_extensions_test.rb +20 -0
  246. data/test/extensions/action_view_base_extensions_test.rb +267 -0
  247. data/test/extensions/active_record_extensions_test.rb +173 -0
  248. data/test/extensions/application_configuration_extensions_test.rb +46 -0
  249. data/test/extensions/application_extensions_test.rb +23 -0
  250. data/test/extensions/connection_adapter_extensions_test.rb +54 -0
  251. data/test/extensions/date_time_value_extensions_test.rb +104 -0
  252. data/test/extensions/date_value_extensions_test.rb +102 -0
  253. data/test/extensions/decimal_value_extensions_test.rb +85 -0
  254. data/test/extensions/erb_scaffold_generator_extensions_test.rb +17 -0
  255. data/test/extensions/float_value_extensions_test.rb +78 -0
  256. data/test/extensions/form_builder_extensions_test.rb +28 -0
  257. data/test/extensions/integer_value_extensions_test.rb +78 -0
  258. data/test/extensions/jbuilder_generator_extensions_test.rb +21 -0
  259. data/test/extensions/jbuilder_template_extensions_test.rb +47 -0
  260. data/test/extensions/main_app_extensions_test.rb +55 -0
  261. data/test/extensions/numeric_extensions_test.rb +76 -0
  262. data/test/extensions/object_extensions_test.rb +104 -0
  263. data/test/extensions/session_extensions_test.rb +69 -0
  264. data/test/extensions/string_extensions_test.rb +32 -0
  265. data/test/extensions/test_case_extensions_test.rb +538 -0
  266. data/test/extensions/time_zone_converter_extensions_test.rb +10 -0
  267. data/test/fixtures/incline/access_group_group_members.yml +1 -0
  268. data/test/fixtures/incline/access_group_user_members.yml +1 -0
  269. data/test/fixtures/incline/access_groups.yml +13 -0
  270. data/test/fixtures/incline/action_groups.yml +6 -0
  271. data/test/fixtures/incline/action_securities.yml +18 -0
  272. data/test/fixtures/incline/user_login_histories.yml +1 -0
  273. data/test/fixtures/incline/users.yml +64 -0
  274. data/test/incline_test.rb +63 -0
  275. data/test/integration/incline/users_edit_test.rb +180 -0
  276. data/test/integration/incline/users_login_test.rb +105 -0
  277. data/test/integration/incline/users_signup_test.rb +147 -0
  278. data/test/integration/navigation_test.rb +11 -0
  279. data/test/lib/data_tables_request_test.rb +245 -0
  280. data/test/lib/date_time_formats_test.rb +111 -0
  281. data/test/lib/global_status_test.rb +89 -0
  282. data/test/lib/json_log_formatter_test.rb +43 -0
  283. data/test/lib/log_test.rb +36 -0
  284. data/test/lib/recaptcha_test.rb +75 -0
  285. data/test/lib/user_manager_test.rb +47 -0
  286. data/test/lib/work_path_test.rb +18 -0
  287. data/test/models/incline/access_group_group_member_test.rb +30 -0
  288. data/test/models/incline/access_group_test.rb +60 -0
  289. data/test/models/incline/access_group_user_member_test.rb +29 -0
  290. data/test/models/incline/action_group_test.rb +27 -0
  291. data/test/models/incline/action_security_test.rb +176 -0
  292. data/test/models/incline/contact_message_test.rb +66 -0
  293. data/test/models/incline/disable_info_test.rb +29 -0
  294. data/test/models/incline/password_reset_request_test.rb +35 -0
  295. data/test/models/incline/password_reset_test.rb +51 -0
  296. data/test/models/incline/user_login_history_test.rb +31 -0
  297. data/test/models/incline/user_test.rb +91 -0
  298. data/test/test_helper.rb +42 -0
  299. data/test/validators/email_validator_test.rb +102 -0
  300. data/test/validators/ip_address_validator_test.rb +107 -0
  301. data/test/validators/recaptcha_validator_test.rb +57 -0
  302. data/test/validators/safe_name_validator_test.rb +101 -0
  303. metadata +584 -0
@@ -0,0 +1,346 @@
1
+ require 'cgi/util'
2
+ require 'net/http'
3
+ require 'action_view'
4
+
5
+ module Incline
6
+ ##
7
+ # A helper class for reCAPTCHA.
8
+ #
9
+ # To use reCAPTCHA, you will need to define +recaptcha_public+ and +recaptcha_private+ in your 'config/secrets.yml'.
10
+ # If you need to use a proxy server, you will need to configure the proxy settings as well.
11
+ #
12
+ # # config/secrets.yml
13
+ # default: &default
14
+ # recaptcha_public: SomeBase64StringFromGoogle
15
+ # recaptcha_private: AnotherBase64StringFromGoogle
16
+ # recaptcha_proxy:
17
+ # host: 10.10.10.10
18
+ # port: 1000
19
+ # user: username
20
+ # password: top_secret
21
+ #
22
+ class Recaptcha
23
+
24
+ ##
25
+ # Defines a reCAPTCHA tag that can be used to supply a field in a model with a hash of values.
26
+ #
27
+ # Basically we define two fields for the model attribute, one for :remote_ip and one for :response.
28
+ # The :remote_ip field is set automatically and shouldn't be changed.
29
+ # The :response field is set when the user completes the challenge.
30
+ #
31
+ # Incline::Recaptcha::Tag.new(my_model, :is_robot).render
32
+ #
33
+ # <input type="hidden" name="my_model[is_robot][remote_ip]" id="my_model_is_robot_remote_ip" value="10.11.12.13">
34
+ # <input type="hidden" name="my_model[is_robot][response]" id="my_model_is_robot_response" value="">
35
+ #
36
+ # Incline::Recaptcha::verify model: my_model, attribute: :is_robot
37
+ class Tag < ActionView::Helpers::Tags::Base
38
+
39
+ ##
40
+ # Generates the reCAPTCHA data.
41
+ def render
42
+ remote_ip =
43
+ if @template_object&.respond_to?(:request) && @template_object.send(:request)&.respond_to?(:remote_ip)
44
+ @template_object.request.remote_ip
45
+ else
46
+ ENV['REMOTE_ADDR']
47
+ end
48
+
49
+ if Incline::Recaptcha::disabled?
50
+ # very simple, if recaptcha is disabled, send the IP and 'disabled' to the form.
51
+ # for validation, recaptcha must still be disabled or it will fail.
52
+ return tag('input', type: 'hidden', id: tag_id, name: tag_name, value: "#{remote_ip}|disabled")
53
+ end
54
+
55
+ # reCAPTCHA is not disabled, so put everything we need into the form.
56
+ ret = tag('input', type: 'hidden', id: tag_id, name: tag_name, value: remote_ip)
57
+ ret += "\n"
58
+
59
+ div_id = tag_id + '_div'
60
+
61
+ ret += tag('div', { class: 'form-group' }, true)
62
+ ret += tag('div', { id: div_id }, true)
63
+ ret += "</div></div>\n".html_safe
64
+
65
+ sitekey = CGI::escape_html(Incline::Recaptcha::public_key)
66
+ onload = 'onload_' + tag_id
67
+ callback = 'update_' + tag_id
68
+ tabindex = @options[:tab_index].to_s.to_i
69
+ theme = make_valid(@options[:theme], VALID_THEMES, :light).to_s
70
+ type = make_valid(@options[:type], VALID_TYPES, :image).to_s
71
+ size = make_valid(@options[:size], VALID_SIZES, :normal).to_s
72
+
73
+
74
+ ret += <<-EOS.html_safe
75
+ <script type="text/javascript">
76
+ // <![CDATA[
77
+ function #{onload}() {
78
+ grecaptcha.render(#{div_id.inspect}, {
79
+ "sitekey" : #{sitekey.inspect},
80
+ "callback" : #{callback.inspect},
81
+ "tabindex" : #{tabindex},
82
+ "theme" : #{theme.inspect},
83
+ "type" : #{type.inspect},
84
+ "size" : #{size.inspect}
85
+ });
86
+ }
87
+ function #{callback}(response) {
88
+ var fld = $('##{tag_id}');
89
+ var val = fld.val();
90
+ if (val) { val = val.split('|'); val = val[0]; } else { val = ''; }
91
+ fld.val(val + '|' + response);
92
+ }
93
+ // ]]>
94
+ </script>
95
+ EOS
96
+
97
+ Incline::Recaptcha::onload_callbacks << onload
98
+
99
+ ret.html_safe
100
+ end
101
+
102
+ private
103
+
104
+ def make_valid(value, valid, default)
105
+ return default if value.blank?
106
+ value = value.to_sym
107
+ return default unless valid.include?(value)
108
+ value
109
+ end
110
+
111
+ end
112
+
113
+ ##
114
+ # Gets the valid themes for the reCAPTCHA field.
115
+ VALID_THEMES = [ :dark, :light ]
116
+
117
+ ##
118
+ # Gets the valid types for the reCAPTCHA field.
119
+ VALID_TYPES = [ :audio, :image ]
120
+
121
+ ##
122
+ # Gets the valid sizes for the reCAPTCHA field.
123
+ VALID_SIZES = [ :compact, :normal ]
124
+
125
+ ##
126
+ # A string that will validated when reCAPTCHA is disabled.
127
+ DISABLED = '0.0.0.0|disabled'
128
+
129
+ ##
130
+ # Determines if recaptcha is disabled either due to a test environment or because :recaptcha_public or :recaptcha_private is not defined in +secrets.yml+.
131
+ def self.disabled?
132
+ temp_lock || public_key.blank? || private_key.blank? || (Rails.env.test? && !enabled_for_testing?)
133
+ end
134
+
135
+ ##
136
+ # Gets the public key.
137
+ def self.public_key
138
+ @public_key ||= Rails.application.secrets[:recaptcha_public].to_s.strip
139
+ end
140
+
141
+ ##
142
+ # Gets the private key.
143
+ def self.private_key
144
+ @private_key ||= Rails.application.secrets[:recaptcha_private].to_s.strip
145
+ end
146
+
147
+ ##
148
+ # Gets the proxy configuration (if any).
149
+ def self.proxy
150
+ @proxy ||= (Rails.application.secrets[:recaptcha_proxy] || {}).symbolize_keys
151
+ end
152
+
153
+
154
+ ##
155
+ # Generates the bare minimum code needed to include a reCAPTCHA challenge in a form.
156
+ def self.add
157
+ unless disabled?
158
+ "<div class=\"g-recaptcha\" data-sitekey=\"#{CGI::escape_html(public_key)}\"></div>\n<script src=\"https://www.google.com/recaptcha/api.js\"></script><br>".html_safe
159
+ end
160
+ end
161
+
162
+ ##
163
+ # Verifies the response from a reCAPTCHA challenge.
164
+ #
165
+ # Valid options:
166
+ # model::
167
+ # Sets the model that this challenge is verifying.
168
+ # attribute::
169
+ # If a model is provided, you can supply an attribute to retrieve the response data from.
170
+ # This attribute should return a hash with :response and :remote_ip keys.
171
+ # If this is provided, then the remaining options are ignored.
172
+ # response::
173
+ # If specified, defines the response from the reCAPTCHA challenge that we want to verify.
174
+ # If not specified, then the request parameters (if any) are searched for the "g-recaptcha-response" value.
175
+ # remote_ip::
176
+ # If specified, defines the remote IP of the user that was challenged.
177
+ # If not specified, then the remote IP from the request (if any) is used.
178
+ # request::
179
+ # Specifies the request to use for information.
180
+ # This must be provided unless :response and :remote_ip are both specified.
181
+ # This is the default option if an object other than a Hash is provided to #verify.
182
+ #
183
+ # Returns true on success, or false on failure.
184
+ #
185
+ def self.verify(options = {})
186
+ return true if temp_lock
187
+
188
+ options = { request: options } unless options.is_a?(::Hash)
189
+
190
+ model = options[:model]
191
+
192
+ response =
193
+ if model && options[:attribute] && model.respond_to?(options[:attribute])
194
+ model.send(options[:attribute])
195
+ else
196
+ nil
197
+ end
198
+
199
+ remote_ip = nil
200
+
201
+ if response.is_a?(::Hash)
202
+ remote_ip = response[:remote_ip]
203
+ response = response[:response]
204
+ end
205
+
206
+ # model must respond to the 'errors' message and the result of that must respond to 'add'
207
+ if !model || !model.respond_to?('errors') || !model.send('errors').respond_to?('add')
208
+ model = nil
209
+ end
210
+
211
+ response ||= options[:response]
212
+ remote_ip ||= options[:remote_ip]
213
+
214
+ if response.blank? || remote_ip.blank?
215
+ request = options[:request]
216
+ raise ArgumentError, 'Either :request must be specified or both :response and :remote_ip must be specified.' unless request
217
+ response = request.params['g-recaptcha-response']
218
+ remote_ip = request.respond_to?(:remote_ip) ? request.send(:remote_ip) : ENV['REMOTE_ADDR']
219
+ end
220
+
221
+ if disabled?
222
+ # In tests or environments where reCAPTCHA is disabled,
223
+ # the response should be 'disabled' to verify successfully.
224
+ return response == 'disabled'
225
+ else
226
+ begin
227
+ if proxy.blank?
228
+ http = Net::HTTP
229
+ else
230
+ http = Net::HTTP::Proxy(proxy.host, proxy.port, proxy.user, proxy.password)
231
+ end
232
+
233
+ verify_hash = {
234
+ secret: private_key,
235
+ remoteip: remote_ip,
236
+ response: response
237
+ }
238
+ recaptcha = nil
239
+ Timeout::timeout(5) do
240
+ uri = URI.parse('https://www.google.com/recaptcha/api/siteverify')
241
+ http_instance = http.new(uri.host, uri.port)
242
+ if uri.port == 443
243
+ http_instance.use_ssl = true
244
+ end
245
+ request = Net::HTTP::Post.new(uri.request_uri)
246
+ request.set_form_data(verify_hash)
247
+ recaptcha = http_instance.request(request)
248
+ end
249
+ answer = JSON.parse(recaptcha.body)
250
+
251
+ unless answer['success'].to_s.downcase == 'true'
252
+ if model
253
+ model.errors.add(options[:attribute] || :base, 'Recaptcha verification failed.')
254
+ end
255
+ return false
256
+ end
257
+
258
+ return true
259
+ rescue Timeout::Error
260
+ if model
261
+ model.errors.add(:base, 'Recaptcha unreachable.')
262
+ end
263
+ end
264
+ end
265
+
266
+ false
267
+ end
268
+
269
+
270
+
271
+ ##
272
+ # Contains a collection of onload callbacks for explicit reCAPTCHA fields.
273
+ #
274
+ # Used by the Incline::Recaptcha::Tag helper.
275
+ def self.onload_callbacks
276
+ # FIXME: Should probably move this to the session.
277
+ @onload_callbacks ||= []
278
+ end
279
+
280
+ ##
281
+ # Generates a script block to load reCAPTCHA and activate any reCAPTCHA fields.
282
+ def self.script_block
283
+ if onload_callbacks.any?
284
+ ret = "<script type=\"text/javascript\">\n// <![CDATA[\nfunction recaptcha_onload() { "
285
+ onload_callbacks.each { |onload| ret += CGI::escape_html(onload) + '(); ' }
286
+ ret += "}\n// ]]>\n</script>\n<script type=\"text/javascript\" src=\"https://www.google.com/recaptcha/api.js?onload=recaptcha_onload&amp;render=explicit\" async defer></script>"
287
+
288
+ # clear the cache.
289
+ onload_callbacks.clear
290
+
291
+ ret.html_safe
292
+ end
293
+ end
294
+
295
+ ##
296
+ # Pauses reCAPTCHA validation for the specified block of code.
297
+ def self.pause_for(&block)
298
+ # already paused, so just call the block.
299
+ return block.call if paused?
300
+
301
+ # otherwise pause and then call the block.
302
+ self.temp_lock = true
303
+ begin
304
+ return block.call
305
+ ensure
306
+ self.temp_lock = false
307
+ end
308
+ end
309
+
310
+ ##
311
+ # Determines if reCAPTCHA validation is currently paused.
312
+ def self.paused?
313
+ temp_lock
314
+ end
315
+
316
+ private
317
+
318
+ def self.enable_for_testing(pub_key = nil, priv_key = nil)
319
+ raise 'This method is only valid when testing.' unless Rails.env.test?
320
+
321
+ @enabled_for_testing = true
322
+ @public_key = pub_key
323
+ @private_key = priv_key
324
+ begin
325
+ yield if block_given?
326
+ ensure
327
+ @private_key = nil
328
+ @public_key = nil
329
+ @enabled_for_testing = false
330
+ end
331
+ end
332
+
333
+ def self.enabled_for_testing?
334
+ @enabled_for_testing ||= false
335
+ end
336
+
337
+ def self.temp_lock
338
+ @temp_lock ||= false
339
+ end
340
+
341
+ def self.temp_lock=(bool)
342
+ @temp_lock = bool
343
+ end
344
+
345
+ end
346
+ end
@@ -0,0 +1,212 @@
1
+
2
+ module Incline
3
+ ##
4
+ # Handles the user management tasks between an authentication system and the database.
5
+ #
6
+ # The default authentication system is the database, but other systems are supported.
7
+ # Out of the box we support LDAP, but the class can be extended to add other functionality.
8
+ #
9
+ class UserManager < AuthEngineBase
10
+
11
+ ##
12
+ # Creates a new user manager.
13
+ #
14
+ # The user manager itself takes no options, however options will be passed to
15
+ # any registered authentication engines when they are instantiated.
16
+ #
17
+ # The options can be used to pre-register engines and provide configuration for them.
18
+ # The engines will have specific configurations, but the UserManager class recognizes
19
+ # the 'engines' key.
20
+ #
21
+ # {
22
+ # :engines => {
23
+ # 'example.com' => {
24
+ # :engine => MySuperAuthEngine.new(...)
25
+ # },
26
+ # 'example.org' => {
27
+ # :engine => 'incline_ldap/auth_engine',
28
+ # :config => {
29
+ # :host => 'ldap.example.org',
30
+ # :port => 636,
31
+ # :base_dn => 'DC=ldap,DC=example,DC=org'
32
+ # }
33
+ # }
34
+ # }
35
+ # }
36
+ #
37
+ # When an 'engines' key is processed, the configuration options for the engines are pulled
38
+ # from the subkeys. Once the processing of the 'engines' key is complete, it will be removed
39
+ # from the options hash so any engines registered in the future will not receive the extra options.
40
+ def initialize(options = {})
41
+ @options = (options || {}).deep_symbolize_keys
42
+ Incline::User.ensure_admin_exists!
43
+
44
+ if @options[:engines].is_a?(::Hash)
45
+ @options[:engines].each do |domain_name, domain_config|
46
+ if domain_config[:engine].blank?
47
+ ::Incline::Log::info "Domain #{domain_name} is missing an engine definition and will not be registered."
48
+ elsif domain_config[:engine].is_a?(::Incline::AuthEngineBase)
49
+ ::Incline::Log::info "Using supplied auth engine for #{domain_name}."
50
+ register_auth_engine domain_config[:engine], domain_name
51
+ else
52
+ engine =
53
+ begin
54
+ domain_config[:engine].to_s.classify.constantize
55
+ rescue NameError
56
+ nil
57
+ end
58
+
59
+ if engine
60
+ engine = engine.new(domain_config[:config] || {})
61
+ if engine.is_a?(::Incline::AuthEngineBase)
62
+ ::Incline::Log::info "Using newly created auth engine for #{domain_name}."
63
+ register_auth_engine engine, domain_name
64
+ else
65
+ ::Incline::Log::warn "Object created for #{domain_name} does not inherit from Incline::AuthEngineBase."
66
+ end
67
+ else
68
+ ::Incline::Log::warn "Failed to create auth engine for #{domain_name}."
69
+ end
70
+ end
71
+ end
72
+ end
73
+
74
+ @options.delete(:engines)
75
+
76
+ end
77
+
78
+ ##
79
+ # Attempts to authenticate the user and returns the model on success.
80
+ def authenticate(email, password, client_ip)
81
+ return nil unless Incline::EmailValidator.valid?(email)
82
+ email = email.downcase
83
+
84
+ # If an engine is registered for the email domain, then use it.
85
+ engine = get_auth_engine(email)
86
+ if engine
87
+ return engine.authenticate(email, password, client_ip)
88
+ end
89
+
90
+ # Otherwise we will be using the database.
91
+ user = User.find_by(email: email)
92
+ if user
93
+ # user must be enabled and the password must match.
94
+ unless user.enabled?
95
+ add_failure_to user, '(DB) account disabled', client_ip
96
+ return nil
97
+ end
98
+ if user.authenticate(password)
99
+ add_success_to user, '(DB)', client_ip
100
+ return user
101
+ else
102
+ add_failure_to user, '(DB) invalid password', client_ip
103
+ return nil
104
+ end
105
+ end
106
+ add_failure_to email, 'invalid email', client_ip
107
+ nil
108
+ end
109
+
110
+ ##
111
+ # Attempts to authenticate the user and returns the model on success.
112
+ def self.authenticate(email, password, client_ip)
113
+ default.authenticate email, password, client_ip
114
+ end
115
+
116
+ ##
117
+ # Registers an authentication engine for one or more domains.
118
+ #
119
+ # The +engine+ passed in should take an options hash as the only argument to +initialize+
120
+ # and should provide an +authenticate+ method that takes the +email+, +password+, and
121
+ # +client_ip+.
122
+ #
123
+ # The +authenticate+ method of the engine should return an Incline::User object on success or nil on failure.
124
+ #
125
+ # class MyAuthEngine
126
+ # def initialize(options = {})
127
+ # ...
128
+ # end
129
+ #
130
+ # def authenticate(email, password, client_ip)
131
+ # ...
132
+ # end
133
+ # end
134
+ #
135
+ # Incline::UserManager.register_auth_engine(MyAuthEngine, 'example.com', 'example.net', 'example.org')
136
+ #
137
+ def register_auth_engine(engine, *domains)
138
+ unless engine.nil?
139
+ unless engine.is_a?(::Incline::AuthEngineBase)
140
+ raise ArgumentError, "The 'engine' parameter must be an instance of an auth engine or a class defining an auth engine." unless engine.is_a?(::Class)
141
+ engine = engine.new(@options)
142
+ raise ArgumentError, "The 'engine' parameter must be an instance of an auth engine or a class defining an auth engine." unless engine.is_a?(::Incline::AuthEngineBase)
143
+ end
144
+ end
145
+ domains.map do |dom|
146
+ dom.to_s.downcase.strip
147
+ raise ArgumentError, "The domain #{dom.inspect} does not appear to be a valid domain." unless dom =~ /\A[a-z0-9]+(?:[-.][a-z0-9]+)*\.[a-z]+\Z/
148
+ dom
149
+ end.each do |dom|
150
+ auth_engines[dom] = engine
151
+ end
152
+ end
153
+
154
+ ##
155
+ # Registers an authentication engine for one or more domains.
156
+ #
157
+ # The +engine+ passed in should take an options hash as the only argument to +initialize+
158
+ # and should provide an +authenticate+ method that takes the +email+, +password+, and
159
+ # +client_ip+.
160
+ #
161
+ # The +authenticate+ method of the engine should return an Incline::User object on success or nil on failure.
162
+ def self.register_auth_engine(engine, *domains)
163
+ default.register_auth_engine(engine, *domains)
164
+ end
165
+
166
+ ##
167
+ # Clears any registered authentication engine for one or more domains.
168
+ def clear_auth_engine(*domains)
169
+ register_auth_engine(nil, *domains)
170
+ end
171
+
172
+ ##
173
+ # Clears any registered authentication engine for one or more domains.
174
+ def self.clear_auth_engine(*domains)
175
+ default.clear_auth_engine(*domains)
176
+ end
177
+
178
+ private
179
+
180
+ def auth_engines
181
+ @auth_engines ||= { }
182
+ end
183
+
184
+ def get_auth_engine(email)
185
+ dom = email.partition('@')[2].downcase
186
+ auth_engines[dom]
187
+ end
188
+
189
+ def self.auth_config
190
+ @auth_config ||=
191
+ begin
192
+ cfg = Rails.root.join('config','auth.yml')
193
+ if File.exist?(cfg)
194
+ cfg = YAML.load_file(cfg)
195
+ if cfg.is_a?(::Hash)
196
+ cfg = cfg[Rails.env]
197
+ (cfg || {}).symbolize_keys
198
+ else
199
+ {}
200
+ end
201
+ else
202
+ {}
203
+ end
204
+ end
205
+ end
206
+
207
+ def self.default
208
+ @default ||= UserManager.new(auth_config)
209
+ end
210
+
211
+ end
212
+ end
@@ -0,0 +1,45 @@
1
+
2
+ module Incline
3
+ ##
4
+ # Validates a string to ensure it contains a valid email address.
5
+ #
6
+ # validates :email_address, 'incline/email' => true
7
+ #
8
+ class EmailValidator < ActiveModel::EachValidator
9
+
10
+ INTERNAL_DOM_REGEX = '[a-z\d]+(?:-+[a-z\d]+)*(?:\.[a-z\d]+(?:-+[a-z\d]+)*)*\.[a-z]+'
11
+ private_constant :INTERNAL_DOM_REGEX
12
+
13
+ ##
14
+ # This regular expression should validate 99.9% of common email addresses.
15
+ #
16
+ # There are some weird rules that it doesn't account for, but they should be rare.
17
+ #
18
+ VALID_EMAIL_REGEX = /\A[\w+\-.]+@#{INTERNAL_DOM_REGEX}\z/i
19
+
20
+ ##
21
+ # This regular expression should validate any domain.
22
+ VALID_DOMAIN_REGEX = /\A#{INTERNAL_DOM_REGEX}\z/i
23
+
24
+
25
+ ##
26
+ # Validates attributes to determine if they contain valid email addresses.
27
+ #
28
+ # Does not perform an in depth check, but does verify that the format is valid.
29
+ def validate_each(record, attribute, value)
30
+ unless value.blank?
31
+ record.errors[attribute] << (options[:message] || 'is not a valid email address') unless value =~ VALID_EMAIL_REGEX
32
+ end
33
+ end
34
+
35
+ ##
36
+ # Validates that an email address is valid based on format.
37
+ def self.valid?(email)
38
+ return false if email.blank?
39
+ !!(email =~ VALID_EMAIL_REGEX)
40
+ end
41
+
42
+ end
43
+ end
44
+
45
+
@@ -0,0 +1,32 @@
1
+ require 'ipaddr'
2
+
3
+ module Incline
4
+ ##
5
+ # Validates a string contains a valid IP address.
6
+ class IpAddressValidator < ActiveModel::EachValidator
7
+
8
+ ##
9
+ # Validates attributes to determine if the values contain valid IP addresses.
10
+ #
11
+ # Set the :no_mask option to restrict the IP address to singular addresses only.
12
+ def validate_each(record, attribute, value)
13
+ begin
14
+ unless value.blank?
15
+ IPAddr.new(value)
16
+ if options[:no_mask]
17
+ if value =~ /\//
18
+ record.errors[attribute] << (options[:message] || 'must not contain a mask')
19
+ end
20
+ elsif options[:require_mask]
21
+ unless value =~ /\//
22
+ record.errors[attribute] << (options[:message] || 'must contain a mask')
23
+ end
24
+ end
25
+ end
26
+ rescue IPAddr::InvalidAddressError
27
+ record.errors[attribute] << (options[:message] || 'is not a valid IP address')
28
+ end
29
+ end
30
+ end
31
+
32
+ end
@@ -0,0 +1,37 @@
1
+
2
+ module Incline
3
+ ##
4
+ # Validates a reCAPTCHA attribute.
5
+ class RecaptchaValidator < ActiveModel::EachValidator
6
+
7
+ ##
8
+ # Validates a reCAPTCHA attribute.
9
+ #
10
+ # The value of the attribute should be a hash with two keys: :response, :remote_ip
11
+ def validate_each(record, attribute, value)
12
+ # Do NOT raise an error if nil.
13
+ return if value.blank?
14
+
15
+ # Make sure the response only gets processed once.
16
+ return if value == :verified
17
+
18
+ # Automatically skip validation if paused.
19
+ return if Incline::Recaptcha::paused?
20
+
21
+ # If the user form includes the recaptcha field, then something will come in
22
+ # and then we want to check it.
23
+ remote_ip, _, response = value.partition('|')
24
+ if remote_ip.blank? || response.blank?
25
+ record.errors[:base] << (options[:message] || 'Requires reCAPTCHA challenge to be completed')
26
+ else
27
+ if Incline::Recaptcha::verify(response: response, remote_ip: remote_ip)
28
+ record.send "#{attribute}=", :verified
29
+ else
30
+ record.errors[:base] << (options[:message] || 'Invalid response from reCAPTCHA challenge')
31
+ end
32
+ end
33
+
34
+ end
35
+
36
+ end
37
+ end
@@ -0,0 +1,31 @@
1
+
2
+ module Incline
3
+ ##
4
+ # Validates a string value to ensure it is a safe name.
5
+ #
6
+ # A safe name is one that only contains letters, numbers, and underscore.
7
+ # It must also start with a letter and cannot end with an underscore.
8
+ class SafeNameValidator < ActiveModel::EachValidator
9
+
10
+ ##
11
+ # Validates a string to ensure it is a safe name.
12
+ VALID_MASK = /\A[a-z](?:_*[a-z0-9]+)*\z/i
13
+
14
+ ##
15
+ # Validates attributes to determine if the values match the requirements of a safe name.
16
+ def validate_each(record, attribute, value)
17
+ unless value.blank?
18
+ unless value =~ VALID_MASK
19
+ if value =~ /\A[^a-z]/i
20
+ record.errors[attribute] << (options[:message] || 'must start with a letter')
21
+ elsif value =~ /_\z/
22
+ record.errors[attribute] << (options[:message] || 'must not end with an underscore')
23
+ else
24
+ record.errors[attribute] << (options[:message] || 'must contain only letters, numbers, and underscore')
25
+ end
26
+ end
27
+ end
28
+ end
29
+
30
+ end
31
+ end