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,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 %>