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,437 @@
1
+ require_relative './access_group'
2
+ require_relative './user_login_history'
3
+
4
+ module Incline
5
+
6
+ ##
7
+ # This class represents an application user.
8
+ class User < ActiveRecord::Base
9
+
10
+ ANONYMOUS_EMAIL = 'anonymous@server.local'
11
+
12
+ has_many :login_histories, class_name: 'Incline::UserLoginHistory'
13
+
14
+ has_many :access_group_user_members, class_name: 'Incline::AccessGroupUserMember', foreign_key: 'member_id'
15
+ private :access_group_user_members, :access_group_user_members=
16
+
17
+ has_many :groups, class_name: 'Incline::AccessGroup', through: :access_group_user_members
18
+
19
+
20
+ before_save :downcase_email
21
+ before_create :create_activation_digest
22
+ after_save :refresh_comments
23
+
24
+ attr_accessor :recaptcha
25
+
26
+ attr_accessor :remember_token
27
+ attr_accessor :activation_token
28
+ attr_accessor :reset_token
29
+
30
+ search_attribute :email
31
+
32
+ has_secure_password
33
+
34
+ validates :name,
35
+ presence: true,
36
+ length: { maximum: 100 }
37
+
38
+ validates :email,
39
+ presence: true,
40
+ length: { maximum: 250 },
41
+ uniqueness: { case_sensitive: false },
42
+ 'incline/email' => true
43
+
44
+ validates :password,
45
+ presence: true,
46
+ length: { minimum: 8 },
47
+ allow_nil: true
48
+
49
+ validates :disabled_by,
50
+ length: { maximum: 250 }
51
+
52
+ validates :disabled_reason,
53
+ length: { maximum: 200 }
54
+
55
+ validates :last_login_ip,
56
+ length: { maximum: 64 },
57
+ 'incline/ip_address' => { no_mask: true }
58
+
59
+ validates :password_digest,
60
+ :activation_digest,
61
+ :remember_digest,
62
+ :reset_digest,
63
+ length: { maximum: 100 }
64
+
65
+ # recaptcha is only required when creating a new record.
66
+ validates :recaptcha,
67
+ presence: true,
68
+ 'incline/recaptcha' => true,
69
+ on: :create
70
+
71
+
72
+ ##
73
+ # Gets all known users.
74
+ scope :known, ->{ where.not(email: ANONYMOUS_EMAIL) }
75
+
76
+ ##
77
+ # Gets all of the currently enabled users.
78
+ scope :enabled, ->{ where(enabled: true, activated: true) }
79
+
80
+ ##
81
+ # Sorts the users by name.
82
+ scope :sorted, ->{ order(name: :asc) }
83
+
84
+ ##
85
+ # Gets the email address in a partially obfuscated fashion.
86
+ def partial_email
87
+ @partial_email ||=
88
+ begin
89
+ uid,_,domain = email.partition('@')
90
+ if uid.length < 4
91
+ uid = '*' * uid.length
92
+ elsif uid.length < 8
93
+ uid = uid[0..2] + ('*' * (uid.length - 3))
94
+ else
95
+ uid = uid[0..2] + ('*' * (uid.length - 6)) + uid[-3..-1]
96
+ end
97
+ "#{uid}@#{domain}"
98
+ end
99
+ end
100
+
101
+ ##
102
+ # Gets the email formatted with the name.
103
+ def formatted_email
104
+ "#{name} <#{email}>"
105
+ end
106
+
107
+ ##
108
+ # Gets the IDs for the groups that the user explicitly belongs to.
109
+ def group_ids
110
+ groups.map{|g| g.id}
111
+ end
112
+
113
+ ##
114
+ # Sets the IDs for the groups that the user explicitly belongs to.
115
+ def group_ids=(values)
116
+ values ||= []
117
+ values = [ values ] unless values.is_a?(::Array)
118
+ values = values.reject{|v| v.blank?}.map{|v| v.to_i}
119
+ self.groups = Incline::AccessGroup.where(id: values).to_a
120
+ end
121
+
122
+ ##
123
+ # Gets the effective group membership of this user.
124
+ def effective_groups(refresh = false)
125
+ @effective_groups = nil if refresh
126
+ @effective_groups ||= if system_admin?
127
+ AccessGroup.all.map{ |g| g.to_s.upcase }
128
+ else
129
+ groups
130
+ .collect{ |g| g.effective_groups }
131
+ .flatten
132
+ end
133
+ .map{ |g| g.to_s.upcase }
134
+ .uniq
135
+ .sort
136
+ end
137
+
138
+ ##
139
+ # Does this user have the equivalent of one or more of these groups?
140
+ def has_any_group?(*group_list)
141
+ return :system_admin if system_admin?
142
+ return false if anonymous?
143
+
144
+ r = group_list.select{|g| effective_groups.include?(g.upcase)}
145
+
146
+ r.blank? ? false : r
147
+ end
148
+
149
+ ##
150
+ # Generates a remember token and saves the digest to the user model.
151
+ def remember
152
+ self.remember_token = Incline::User::new_token
153
+ update_attribute(:remember_digest, Incline::User::digest(self.remember_token))
154
+ end
155
+
156
+ ##
157
+ # Removes the remember digest from the user model.
158
+ def forget
159
+ update_attribute(:remember_digest, nil)
160
+ end
161
+
162
+ ##
163
+ # Determines if the supplied token digests to the stored digest in the user model.
164
+ def authenticated?(attribute, token)
165
+ return false unless respond_to?("#{attribute}_digest")
166
+ digest = send("#{attribute}_digest")
167
+ return false if digest.blank?
168
+ BCrypt::Password.new(digest).is_password?(token)
169
+ end
170
+
171
+ ##
172
+ # Disables the user.
173
+ #
174
+ # The +other_user+ is required, cannot be the current user, and must be a system administrator.
175
+ # The +reason+ is technically optional, but should be provided.
176
+ def disable(other_user, reason)
177
+ return false unless other_user&.system_admin?
178
+ return false if other_user == self
179
+
180
+ update_columns(
181
+ disabled_by: other_user.email,
182
+ disabled_at: Time.now,
183
+ disabled_reason: reason,
184
+ enabled: false
185
+ ) && refresh_comments
186
+ end
187
+
188
+ ##
189
+ # Enables the user and removes any previous disable information.
190
+ def enable
191
+ update_columns(
192
+ disabled_by: nil,
193
+ disabled_at: nil,
194
+ disabled_reason: nil,
195
+ enabled: true
196
+ ) && refresh_comments
197
+ end
198
+
199
+ ##
200
+ # Marks the user as activated and removes the activation digest from the user model.
201
+ def activate
202
+ update_columns(
203
+ activated: true,
204
+ activated_at: Time.now,
205
+ activation_digest: nil
206
+ ) && refresh_comments
207
+ end
208
+
209
+ ##
210
+ # Sends the activation email to the user.
211
+ def send_activation_email(client_ip = '0.0.0.0')
212
+ Incline::UserMailer.account_activation(user: self, client_ip: client_ip).deliver_now
213
+ end
214
+
215
+ ##
216
+ # Creates a reset token and stores the digest to the user model.
217
+ def create_reset_digest
218
+ self.reset_token = Incline::User::new_token
219
+ update_columns(
220
+ reset_digest: Incline::User::digest(reset_token),
221
+ reset_sent_at: Time.now
222
+ )
223
+ end
224
+
225
+ ##
226
+ # Was the password reset requested more than 2 hours ago?
227
+ def password_reset_expired?
228
+ reset_sent_at.nil? || reset_sent_at < 2.hours.ago
229
+ end
230
+
231
+ ##
232
+ # Is this the anonymous user?
233
+ def anonymous?
234
+ email == ANONYMOUS_EMAIL
235
+ end
236
+
237
+ ##
238
+ # Gets the last successful login for this user.
239
+ def last_successful_login
240
+ @last_successful_login ||= login_histories.where(successful: true).order(created_at: :desc).first
241
+ end
242
+
243
+ ##
244
+ # Gets the last failed login for this user.
245
+ def last_failed_login
246
+ @last_failed_login ||= login_histories.where.not(successful: true).order(created_at: :desc).first
247
+ end
248
+
249
+ ##
250
+ # Gets the failed logins for a user since the last successful login.
251
+ def failed_login_streak
252
+ @failed_login_streak ||=
253
+ begin
254
+ results = login_histories.where.not(successful: true)
255
+ if last_successful_login
256
+ results = results.where('created_at > ?', last_successful_login.created_at)
257
+ end
258
+ results.order(created_at: :desc)
259
+ end
260
+ end
261
+
262
+ ##
263
+ # Generates some brief comments about the user account and stores them in the comments attribute.
264
+ #
265
+ # This gets updated automatically on every login attempt.
266
+ def refresh_comments
267
+ update_columns :comments => generate_comments
268
+ comments
269
+ end
270
+
271
+ ##
272
+ # Sends the password reset email to the user.
273
+ def send_password_reset_email(client_ip = '0.0.0.0')
274
+ Incline::UserMailer.password_reset(user: self, client_ip: client_ip).deliver_now
275
+ end
276
+
277
+ ##
278
+ # Sends a missing account message when a user requests a password reset.
279
+ def self.send_missing_reset_email(email, client_ip = '0.0.0.0')
280
+ Incline::UserMailer::invalid_password_reset(email: email, client_ip: client_ip).deliver_now
281
+ end
282
+
283
+ ##
284
+ # Sends a disabled account message when a user requests a password reset.
285
+ def self.send_disabled_reset_email(email, client_ip = '0.0.0.0')
286
+ Incline::UserMailer::invalid_password_reset(email: email, message: 'The account attached to this email address has been disabled.', client_ip: client_ip).deliver_now
287
+ end
288
+
289
+ ##
290
+ # Sends a non-activated account message when a user requests a password reset.
291
+ def self.send_inactive_reset_email(email, client_ip = '0.0.0.0')
292
+ Incline::UserMailer::invalid_password_reset(email: email, message: 'The account attached to this email has not yet been activated.', client_ip: client_ip).deliver_now
293
+ end
294
+
295
+ ##
296
+ # Returns a hash digest of the given string.
297
+ def self.digest(string)
298
+ cost = ActiveModel::SecurePassword.min_cost ? BCrypt::Engine::MIN_COST : BCrypt::Engine.cost
299
+ BCrypt::Password.create(string, cost: cost)
300
+ end
301
+
302
+ ##
303
+ # Generates a new random token in (url safe) base64.
304
+ def self.new_token
305
+ SecureRandom.urlsafe_base64(32)
306
+ end
307
+
308
+ ##
309
+ # Generates the necessary system administrator account.
310
+ #
311
+ # When the database is initially seeded, the only user is the system administrator.
312
+ #
313
+ # The absolute default is **admin@barkerest.com** with a password of **Password1**.
314
+ # These values will be used if they are not overridden for the current environment.
315
+ #
316
+ # You can override this by setting the +default_admin+ property in "config/secrets.yml".
317
+ #
318
+ # # config/secrets.yml
319
+ # development:
320
+ # default_admin:
321
+ # email: admin@barkerest.com
322
+ # password: Password1
323
+ #
324
+ # Regardless of whether you use the absolute defaults or create your own, you will want
325
+ # to change the password on first login.
326
+ #
327
+ def self.ensure_admin_exists!
328
+ unless where(system_admin: true, enabled: true).count > 0
329
+
330
+ msg = "Creating/reactivating default administrator...\n"
331
+ if Rails.application.running?
332
+ Rails.logger.info msg
333
+ else
334
+ print msg
335
+ end
336
+
337
+ def_adm = (Rails.application.secrets[:default_admin] || {}).symbolize_keys
338
+
339
+ def_adm_email = def_adm[:email] || 'admin@barkerest.com'
340
+ def_adm_pass = def_adm[:password] || 'Password1'
341
+
342
+ user = Incline::Recaptcha::pause_for do
343
+ User
344
+ .where(
345
+ email: def_adm_email
346
+ )
347
+ .first_or_create!(
348
+ name: 'Default Administrator',
349
+ email: def_adm_email,
350
+ password: def_adm_pass,
351
+ password_confirmation: def_adm_pass,
352
+ enabled: true,
353
+ system_admin: true,
354
+ activated: true,
355
+ activated_at: Time.now,
356
+ recaptcha: 'na'
357
+ )
358
+ end
359
+
360
+
361
+ unless user.activated? && user.enabled? && user.system_admin?
362
+ user.password = def_adm_pass
363
+ user.password_confirmation = def_adm_pass
364
+ user.enabled = true
365
+ user.system_admin = true
366
+ user.activated = true
367
+ user.activated_at = Time.now
368
+ user.save!
369
+ end
370
+ end
371
+ end
372
+
373
+ ##
374
+ # Gets a generic anonymous user.
375
+ def self.anonymous
376
+ @anonymous = nil if Rails.env.test? # always start fresh in test environment.
377
+ @anonymous ||=
378
+ Incline::Recaptcha::pause_for do
379
+ pwd = new_token
380
+ User
381
+ .where(email: ANONYMOUS_EMAIL)
382
+ .first_or_create!(
383
+ email: ANONYMOUS_EMAIL,
384
+ name: 'Anonymous',
385
+ enabled: false,
386
+ activated: true,
387
+ activated_at: Time.now,
388
+ password: pwd,
389
+ password_confirmation: pwd,
390
+ recaptcha: 'na'
391
+ )
392
+ end
393
+ end
394
+
395
+ ##
396
+ # Gets the formatted email for this user.
397
+ def to_s
398
+ formatted_email
399
+ end
400
+
401
+ private
402
+
403
+ def generate_comments
404
+ (system_admin? ? "{ADMIN}\n" : '') +
405
+ if enabled?
406
+ if activated?
407
+ if failed_login_streak.count > 1
408
+ "Failed Login Streak: #{failed_login_streak.count}\nMost Recent Attempt: #{last_failed_login.date_and_ip}\n"
409
+ elsif failed_login_streak.count == 1
410
+ "Failed Login Attempt: #{last_failed_login.date_and_ip}\n"
411
+ else
412
+ ''
413
+ end +
414
+ if last_successful_login
415
+ "Most Recent Login: #{last_successful_login.date_and_ip}"
416
+ else
417
+ 'Most Recent Login: Never'
418
+ end
419
+ else
420
+ 'Not Activated'
421
+ end
422
+ else
423
+ "Disabled #{disabled_at ? disabled_at.in_time_zone.strftime('%m/%d/%Y') : 'some time in the past'} by #{disabled_by.blank? ? 'somebody' : disabled_by}.\n#{disabled_reason}"
424
+ end
425
+ end
426
+
427
+ def downcase_email
428
+ email.downcase!
429
+ end
430
+
431
+ def create_activation_digest
432
+ self.activation_token = Incline::User::new_token
433
+ self.activation_digest = Incline::User::digest(activation_token)
434
+ end
435
+
436
+ end
437
+ end
@@ -0,0 +1,30 @@
1
+ module Incline
2
+ class UserLoginHistory < ActiveRecord::Base
3
+
4
+ belongs_to :user
5
+ after_save :update_user_comments
6
+
7
+ validates :user, presence: true
8
+ validates :ip_address, presence: true, length: { maximum: 64 }, 'incline/ip_address' => { no_mask: true }
9
+ validates :message, length: { maximum: 200 }
10
+
11
+ def time_and_ip
12
+ "#{created_at.in_time_zone.strftime('%m/%d/%Y %H:%M')} from #{ip_address}"
13
+ end
14
+
15
+ def date_and_ip
16
+ "#{created_at.in_time_zone.strftime('%m/%d/%Y')} from #{ip_address}"
17
+ end
18
+
19
+ def to_s
20
+ "Login #{successful ? 'succeeded' : 'failed'} on #{time_and_ip}"
21
+ end
22
+
23
+ private
24
+
25
+ def update_user_comments
26
+ user.refresh_comments
27
+ end
28
+
29
+ end
30
+ end
@@ -0,0 +1,10 @@
1
+ unless access_group.new_record?
2
+ json.set! 'DT_RowId', "access_group_#{access_group.id}"
3
+ json.set! 'DT_Path', access_group_path(access_group)
4
+ if access_group.destroyed?
5
+ json.set! 'DT_RowAction', 'remove'
6
+ end
7
+ end
8
+ json.set! 'name', h(access_group.name)
9
+ json.set! 'created_at', access_group.created_at
10
+ json.set! 'updated_at', access_group.updated_at
@@ -0,0 +1,19 @@
1
+ <%= error_summary(@access_group) %>
2
+ <div class="col-md-4 col-md-offset-4">
3
+ <div class="panel panel-primary">
4
+ <div class="panel-heading">
5
+ <h4 class="panel-title"><%= @access_group.new_record? ? 'Create' : 'Update' %> Access Group</h4>
6
+ </div>
7
+ <div class="panel-body">
8
+ <%= form_for(@access_group) do |f| %>
9
+ <%= f.text_form_group :name %>
10
+ <%= f.select_form_group :user_ids, Incline::User.where.not(id: current_user.id).sorted, :id, :to_s, label_text: 'Users belonging to this group', field_class: 'form-control select2', field_multiple: true %>
11
+ <%= f.select_form_group :group_ids, Incline::AccessGroup.where.not(id: @access_group.id).sorted, :id, :to_s, label_text: 'Groups belonging to this group', field_class: 'form-control select2', field_multiple: true %>
12
+
13
+ <%= f.submit class: 'btn btn-primary' %>
14
+ <%= link_to 'Cancel', access_groups_url, class: 'btn btn-default' %>
15
+ <% end %>
16
+
17
+ </div>
18
+ </div>
19
+ </div>
@@ -0,0 +1,60 @@
1
+ <table id="dt-access_groups" class="table" style="width: 100%;">
2
+ <thead>
3
+ <tr>
4
+ <th>Name</th>
5
+ <th class="text-right"><%= link_to 'New', new_access_group_path, class: 'btn btn-success btn-xs inline_form' %></th>
6
+ </tr>
7
+ </thead>
8
+ </table>
9
+
10
+ <% provide :scripts do %>
11
+ <script type="text/javascript">
12
+ //<![CDATA[
13
+ $(function() {
14
+ $('#dt-access_groups').DataTable({
15
+ dom: '<"panel-body"<"col-sm-6 col-sm-offset-6"fr>>t<"panel-body"<"col-sm-6"i><"col-sm-6"p>>',
16
+ ajax: {
17
+ url: '<%= api_path %>',
18
+ type: 'POST'
19
+ },
20
+ columns: [
21
+ {
22
+ // the data to display.
23
+ data: 'name',
24
+ // can this column be used for sorting?
25
+ orderable: true,
26
+ // can this column be used for searching?
27
+ searchable: true
28
+ },
29
+
30
+ {
31
+ orderable: false,
32
+ searchable: false,
33
+ data: function (row, type, set, meta) {
34
+ if (type === 'display') {
35
+ var ret = '<div class="text-right">';
36
+
37
+ // the show icon.
38
+ ret += '<a href="javascript:inlineForm(\'' + row.DT_Path + '\')" title="Details" class="btn btn-default btn-xs"><i class="glyphicon glyphicon-zoom-in"></i></a>';
39
+
40
+ // the edit icon.
41
+ ret += '<a href="javascript:inlineForm(\'' + row.DT_Path + '/edit\')" title="Edit" class="btn btn-default btn-xs"><i class="glyphicon glyphicon-pencil"></i></a>';
42
+
43
+ // the delete icon.
44
+ ret += '<a href="javascript:inlineAction(\'' + row.DT_Path + '\',\'delete\')" title="Remove" class="btn btn-danger btn-xs" data-confirm="Are you sure you want to remove this access group?"><i class="glyphicon glyphicon-trash"></i></a>';
45
+
46
+ ret += '</div>';
47
+ return ret;
48
+ } else {
49
+ return row.DT_Path;
50
+ }
51
+ }
52
+ }
53
+ ],
54
+ responsive: true,
55
+ serverSide: true
56
+ });
57
+ });
58
+ //]]>
59
+ </script>
60
+ <% end %>
@@ -0,0 +1,6 @@
1
+ json.messages do
2
+ json.array! flash.discard do |type,message|
3
+ json.set! 'type', type
4
+ json.set! 'text', message
5
+ end
6
+ end
@@ -0,0 +1,2 @@
1
+ <%= render 'form' %>
2
+
@@ -0,0 +1,6 @@
1
+ <div class="panel panel-primary">
2
+ <div class="panel-heading">
3
+ <h4 class="panel-title">Access Groups</h4>
4
+ </div>
5
+ <%= render partial: 'list', locals: { api_path: api_access_groups_path } %>
6
+ </div>
@@ -0,0 +1,16 @@
1
+ if @dt_request&.provided?
2
+ json.set! 'draw', @dt_request.draw
3
+ json.set! 'recordsTotal', @dt_request.records_total
4
+ json.set! 'recordsFiltered', @dt_request.records_filtered
5
+ json.data do
6
+ json.array!(@dt_request.records) do |access_group|
7
+ json.partial! 'details', locals: { access_group: access_group }
8
+ end
9
+ end
10
+ if @dt_request.error?
11
+ json.set! 'error', @dt_request.error
12
+ end
13
+ else
14
+ json.set! 'error', 'No data tables request received.'
15
+ end
16
+ json.set! 'appInfo', h(Rails.application.app_info)
@@ -0,0 +1,2 @@
1
+ <%= render 'form' %>
2
+
@@ -0,0 +1,9 @@
1
+ <h1><%= @access_group.name %></h1>
2
+
3
+ <p>
4
+ <h4>Direct and indirect members:</h4>
5
+ <%= @access_group.members.any? ? @access_group.members.map{|u| h(u.to_s)}.join('<br>').html_safe : 'None' %>
6
+ </p>
7
+ <br>
8
+
9
+ <%= link_to 'Cancel', access_groups_path, class: 'btn btn-default' %>
@@ -0,0 +1,11 @@
1
+ json.partial! 'messages'
2
+
3
+ if @access_group.errors.any?
4
+ json.api_errors! 'access_group', @access_group.errors
5
+ else
6
+ json.data do
7
+ json.array! [ @access_group ] do |access_group|
8
+ json.partial! 'details', locals: { access_group: access_group }
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,22 @@
1
+ <% provide :title, 'Contact Form' %>
2
+
3
+ <%= error_summary(@msg) %>
4
+ <div class="col-md-6 col-md-offset-3">
5
+ <div class="panel panel-primary">
6
+ <div class="panel-heading">
7
+ <h4 class="panel-title">Contact Form</h4>
8
+ </div>
9
+ <div class="panel-body">
10
+ <%= form_for @msg, url: contact_url, method: :post do |f| %>
11
+ <%= f.text_form_group :your_name %>
12
+ <%= f.text_form_group :your_email, field_type: 'email' %>
13
+ <%= f.select_form_group :related_to, [ 'Program Error', 'Broken Link', 'Feature Request', 'Other' ] %>
14
+ <%= f.text_form_group :subject %>
15
+ <%= f.textarea_form_group :body %>
16
+ <%= f.recaptcha :recaptcha %>
17
+ <%= f.submit 'Send', class: 'btn btn-success' %>
18
+ <%= link_to 'Cancel', root_url, class: 'btn btn-default' %>
19
+ <% end %>
20
+ </div>
21
+ </div>
22
+ </div>
@@ -0,0 +1,16 @@
1
+ <pre>
2
+ From: <%= @data[:msg].your_name %> &lt;<%= @data[:msg].your_email %>&gt;
3
+ IP Address: <%= @data[:msg].remote_ip %>
4
+ Date/time: <%= Time.zone.now %>
5
+
6
+ Application:
7
+ <%= Rails.application.app_name %> (<%= Rails.application.app_version %>)
8
+ Key Gems:
9
+ <% @data[:gems].each do |name,ver| %>
10
+ <%= name %> (<%= ver %>)
11
+ <% end %>
12
+
13
+ Message:
14
+ <%= @data[:msg].body %>
15
+
16
+ </pre>
@@ -0,0 +1,13 @@
1
+ From: <%= @data[:msg].your_name %> <<%= @data[:msg].your_email %>>
2
+ IP Address: <%= @data[:msg].remote_ip %>
3
+ Date/time: <%= Time.zone.now %>
4
+
5
+ Application:
6
+ <%= Rails.application.app_info %>
7
+ Key Gems:
8
+ <% @data[:gems].each do |name,ver| %>
9
+ <%= name %> (<%= ver %>)
10
+ <% end %>
11
+
12
+ Message:
13
+ <%= @data[:msg].body %>