incline 0.1.5

Sign up to get free protection for your applications and to get access to all the features.
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