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.
- checksums.yaml +7 -0
- data/.gitignore +13 -0
- data/Gemfile +17 -0
- data/Gemfile.lock +186 -0
- data/MIT-LICENSE +20 -0
- data/README.rdoc +208 -0
- data/Rakefile +37 -0
- data/app/assets/fonts/incline/.keep +0 -0
- data/app/assets/images/incline/.keep +0 -0
- data/app/assets/images/incline/barcode-B.svg +181 -0
- data/app/assets/javascripts/incline/activate_classed_items.js +11 -0
- data/app/assets/javascripts/incline/application.js +30 -0
- data/app/assets/javascripts/incline/bootstrap-datepicker.js +1800 -0
- data/app/assets/javascripts/incline/datatables.js +22193 -0
- data/app/assets/javascripts/incline/escapeHtml.js +10 -0
- data/app/assets/javascripts/incline/inline_actions.js +479 -0
- data/app/assets/javascripts/incline/jquery.doubleScroll.js +112 -0
- data/app/assets/javascripts/incline/jquery.number.js +764 -0
- data/app/assets/javascripts/incline/regexMask.js +27 -0
- data/app/assets/javascripts/incline/select2/i18n/en.js +3 -0
- data/app/assets/javascripts/incline/select2/select2.full.js +6436 -0
- data/app/assets/stylesheets/incline/application.css +18 -0
- data/app/assets/stylesheets/incline/bootstrap-theme.min.css +5 -0
- data/app/assets/stylesheets/incline/custom.scss +279 -0
- data/app/assets/stylesheets/incline/datatables.css +494 -0
- data/app/assets/stylesheets/incline/datepicker3.css +790 -0
- data/app/assets/stylesheets/incline/select2.css +484 -0
- data/app/controllers/incline/access_groups_controller.rb +127 -0
- data/app/controllers/incline/access_test_controller.rb +30 -0
- data/app/controllers/incline/account_activations_controller.rb +28 -0
- data/app/controllers/incline/application_controller.rb +11 -0
- data/app/controllers/incline/contact_controller.rb +34 -0
- data/app/controllers/incline/password_resets_controller.rb +113 -0
- data/app/controllers/incline/security_controller.rb +100 -0
- data/app/controllers/incline/sessions_controller.rb +50 -0
- data/app/controllers/incline/users_controller.rb +304 -0
- data/app/controllers/incline/welcome_controller.rb +19 -0
- data/app/helpers/incline/.keep +0 -0
- data/app/mailers/incline/application_mailer_base.rb +11 -0
- data/app/mailers/incline/contact_form.rb +19 -0
- data/app/mailers/incline/user_mailer.rb +45 -0
- data/app/models/incline/access_group.rb +121 -0
- data/app/models/incline/access_group_group_member.rb +12 -0
- data/app/models/incline/access_group_user_member.rb +10 -0
- data/app/models/incline/action_group.rb +12 -0
- data/app/models/incline/action_security.rb +222 -0
- data/app/models/incline/contact_message.rb +37 -0
- data/app/models/incline/disable_info.rb +20 -0
- data/app/models/incline/password_reset.rb +14 -0
- data/app/models/incline/password_reset_request.rb +14 -0
- data/app/models/incline/user.rb +437 -0
- data/app/models/incline/user_login_history.rb +30 -0
- data/app/views/incline/access_groups/_details.json.jbuilder +10 -0
- data/app/views/incline/access_groups/_form.html.erb +19 -0
- data/app/views/incline/access_groups/_list.html.erb +60 -0
- data/app/views/incline/access_groups/_messages.json.jbuilder +6 -0
- data/app/views/incline/access_groups/edit.html.erb +2 -0
- data/app/views/incline/access_groups/index.html.erb +6 -0
- data/app/views/incline/access_groups/index.json.jbuilder +16 -0
- data/app/views/incline/access_groups/new.html.erb +2 -0
- data/app/views/incline/access_groups/show.html.erb +9 -0
- data/app/views/incline/access_groups/show.json.jbuilder +11 -0
- data/app/views/incline/contact/new.html.erb +22 -0
- data/app/views/incline/contact_form/contact.html.erb +16 -0
- data/app/views/incline/contact_form/contact.text.erb +13 -0
- data/app/views/incline/password_resets/edit.html.erb +16 -0
- data/app/views/incline/password_resets/new.html.erb +12 -0
- data/app/views/incline/security/_details.json.jbuilder +7 -0
- data/app/views/incline/security/_form.html.erb +20 -0
- data/app/views/incline/security/_list.html.erb +89 -0
- data/app/views/incline/security/_messages.json.jbuilder +6 -0
- data/app/views/incline/security/edit.html.erb +2 -0
- data/app/views/incline/security/index.html.erb +6 -0
- data/app/views/incline/security/index.json.jbuilder +16 -0
- data/app/views/incline/security/show.html.erb +31 -0
- data/app/views/incline/security/show.json.jbuilder +11 -0
- data/app/views/incline/sessions/new.html.erb +26 -0
- data/app/views/incline/user_mailer/account_activation.html.erb +7 -0
- data/app/views/incline/user_mailer/account_activation.text.erb +6 -0
- data/app/views/incline/user_mailer/invalid_password_reset.html.erb +3 -0
- data/app/views/incline/user_mailer/invalid_password_reset.text.erb +5 -0
- data/app/views/incline/user_mailer/password_reset.html.erb +8 -0
- data/app/views/incline/user_mailer/password_reset.text.erb +7 -0
- data/app/views/incline/users/_details.json.jbuilder +32 -0
- data/app/views/incline/users/_form.html.erb +21 -0
- data/app/views/incline/users/_list.html.erb +102 -0
- data/app/views/incline/users/_messages.json.jbuilder +6 -0
- data/app/views/incline/users/disable_confirm.html.erb +19 -0
- data/app/views/incline/users/edit.html.erb +5 -0
- data/app/views/incline/users/index.html.erb +6 -0
- data/app/views/incline/users/index.json.jbuilder +16 -0
- data/app/views/incline/users/new.html.erb +5 -0
- data/app/views/incline/users/show.html.erb +12 -0
- data/app/views/incline/users/show.json.jbuilder +11 -0
- data/app/views/incline/welcome/home.html.erb +5 -0
- data/app/views/layouts/application.html.erb +1 -0
- data/app/views/layouts/incline/_account_menu.html.erb +18 -0
- data/app/views/layouts/incline/_app_menu_anon.html.erb +1 -0
- data/app/views/layouts/incline/_app_menu_authenticated.html.erb +1 -0
- data/app/views/layouts/incline/_footer.html.erb +13 -0
- data/app/views/layouts/incline/_header.html.erb +21 -0
- data/app/views/layouts/incline/_html_mailer.html.erb +5 -0
- data/app/views/layouts/incline/_incline_app.html.erb +25 -0
- data/app/views/layouts/incline/_messages.html.erb +3 -0
- data/app/views/layouts/incline/_shim.html.erb +3 -0
- data/app/views/layouts/incline/_text_mailer.text.erb +1 -0
- data/app/views/layouts/incline/application.html.erb +1 -0
- data/app/views/layouts/mailer.html.erb +2 -0
- data/app/views/layouts/mailer.text.erb +2 -0
- data/bin/rails +12 -0
- data/bin/test_scaffold.sh +10 -0
- data/config/routes.rb +61 -0
- data/db/migrate/20170511230126_create_incline_users.rb +26 -0
- data/db/migrate/20170515003052_create_incline_access_groups.rb +10 -0
- data/db/migrate/20170515003221_create_incline_user_login_histories.rb +12 -0
- data/db/migrate/20170515150908_create_incline_access_group_user_members.rb +11 -0
- data/db/migrate/20170515151058_create_incline_access_group_group_members.rb +11 -0
- data/db/migrate/20170517193432_add_comments_to_incline_user.rb +5 -0
- data/db/migrate/20170622132700_create_incline_action_securities.rb +16 -0
- data/db/migrate/20170622172712_create_incline_action_groups.rb +11 -0
- data/db/migrate/20170622195742_add_non_standard_to_action_security.rb +5 -0
- data/db/migrate/20170622230422_add_visible_to_action_security.rb +5 -0
- data/db/seeds.rb +81 -0
- data/exe/new_incline_app +42 -0
- data/lib/generators/incline/install_generator.rb +259 -0
- data/lib/generators/incline/templates/_app_menu_anon.html.erb +1 -0
- data/lib/generators/incline/templates/_app_menu_authenticated.html.erb +1 -0
- data/lib/generators/incline/templates/incline_application.css +17 -0
- data/lib/generators/incline/templates/incline_application.html.erb +1 -0
- data/lib/generators/incline/templates/incline_application.js +12 -0
- data/lib/generators/incline/templates/incline_database.yml +25 -0
- data/lib/generators/incline/templates/incline_email.yml +20 -0
- data/lib/generators/incline/templates/incline_mailer.html.erb +2 -0
- data/lib/generators/incline/templates/incline_mailer.text.erb +2 -0
- data/lib/generators/incline/templates/incline_users.yml +64 -0
- data/lib/generators/incline/templates/incline_version.rb +3 -0
- data/lib/incline/auth_engine_base.rb +52 -0
- data/lib/incline/data_tables_request.rb +336 -0
- data/lib/incline/date_time_formats.rb +6 -0
- data/lib/incline/engine.rb +212 -0
- data/lib/incline/errors.rb +15 -0
- data/lib/incline/extensions/action_controller_base.rb +526 -0
- data/lib/incline/extensions/action_mailer_base.rb +66 -0
- data/lib/incline/extensions/action_view_base.rb +489 -0
- data/lib/incline/extensions/active_record_base.rb +308 -0
- data/lib/incline/extensions/application.rb +137 -0
- data/lib/incline/extensions/application_configuration.rb +50 -0
- data/lib/incline/extensions/connection_adapter.rb +55 -0
- data/lib/incline/extensions/date_time_value.rb +123 -0
- data/lib/incline/extensions/date_value.rb +77 -0
- data/lib/incline/extensions/decimal_value.rb +55 -0
- data/lib/incline/extensions/erb_scaffold_generator.rb +31 -0
- data/lib/incline/extensions/float_value.rb +59 -0
- data/lib/incline/extensions/form_builder.rb +617 -0
- data/lib/incline/extensions/integer_value.rb +54 -0
- data/lib/incline/extensions/jbuilder_generator.rb +38 -0
- data/lib/incline/extensions/jbuilder_template.rb +39 -0
- data/lib/incline/extensions/main_app.rb +40 -0
- data/lib/incline/extensions/numeric.rb +63 -0
- data/lib/incline/extensions/object.rb +31 -0
- data/lib/incline/extensions/resource_route_generator.rb +53 -0
- data/lib/incline/extensions/session.rb +113 -0
- data/lib/incline/extensions/string.rb +50 -0
- data/lib/incline/extensions/test_case.rb +764 -0
- data/lib/incline/extensions/time_zone_converter.rb +40 -0
- data/lib/incline/global_status.rb +236 -0
- data/lib/incline/helpers/route_hash_formatter.rb +46 -0
- data/lib/incline/json_log_formatter.rb +96 -0
- data/lib/incline/json_logger.rb +17 -0
- data/lib/incline/log.rb +153 -0
- data/lib/incline/number_formats.rb +17 -0
- data/lib/incline/recaptcha.rb +346 -0
- data/lib/incline/user_manager.rb +212 -0
- data/lib/incline/validators/email_validator.rb +45 -0
- data/lib/incline/validators/ip_address_validator.rb +32 -0
- data/lib/incline/validators/recaptcha_validator.rb +37 -0
- data/lib/incline/validators/safe_name_validator.rb +31 -0
- data/lib/incline/version.rb +3 -0
- data/lib/incline/work_path.rb +75 -0
- data/lib/incline.rb +197 -0
- data/lib/tasks/incline_tasks.rake +4 -0
- data/lib/templates/erb/scaffold/_form.html.erb +43 -0
- data/lib/templates/erb/scaffold/_list.html.erb +81 -0
- data/lib/templates/erb/scaffold/edit.html.erb +1 -0
- data/lib/templates/erb/scaffold/index.html.erb +6 -0
- data/lib/templates/erb/scaffold/new.html.erb +1 -0
- data/lib/templates/erb/scaffold/show.html.erb +34 -0
- data/lib/templates/jbuilder/scaffold/_details.json.jbuilder +20 -0
- data/lib/templates/jbuilder/scaffold/index.json.jbuilder +16 -0
- data/lib/templates/jbuilder/scaffold/show.json.jbuilder +16 -0
- data/lib/templates/rails/scaffold_controller/controller.rb +128 -0
- data/test/controllers/incline/access_groups_controller_test.rb +65 -0
- data/test/controllers/incline/access_test_controller_test.rb +53 -0
- data/test/controllers/incline/contact_controller_test.rb +32 -0
- data/test/controllers/incline/security_controller_test.rb +39 -0
- data/test/controllers/incline/welcome_controller_test.rb +16 -0
- data/test/dummy/README.rdoc +28 -0
- data/test/dummy/Rakefile +6 -0
- data/test/dummy/app/assets/images/.keep +0 -0
- data/test/dummy/app/assets/javascripts/application.js +12 -0
- data/test/dummy/app/assets/stylesheets/application.css +17 -0
- data/test/dummy/app/controllers/application_controller.rb +5 -0
- data/test/dummy/app/controllers/concerns/.keep +0 -0
- data/test/dummy/app/helpers/application_helper.rb +2 -0
- data/test/dummy/app/mailers/.keep +0 -0
- data/test/dummy/app/models/.keep +0 -0
- data/test/dummy/app/models/concerns/.keep +0 -0
- data/test/dummy/app/views/layouts/application.html.erb +1 -0
- data/test/dummy/app/views/layouts/incline/_app_menu_anon.html.erb +1 -0
- data/test/dummy/app/views/layouts/incline/_app_menu_authenticated.html.erb +1 -0
- data/test/dummy/app/views/layouts/mailer.html.erb +2 -0
- data/test/dummy/app/views/layouts/mailer.text.erb +2 -0
- data/test/dummy/bin/bundle +3 -0
- data/test/dummy/bin/rails +4 -0
- data/test/dummy/bin/rake +4 -0
- data/test/dummy/bin/setup +29 -0
- data/test/dummy/config/application.rb +38 -0
- data/test/dummy/config/boot.rb +5 -0
- data/test/dummy/config/database.yml +34 -0
- data/test/dummy/config/email.yml +24 -0
- data/test/dummy/config/environment.rb +5 -0
- data/test/dummy/config/environments/development.rb +45 -0
- data/test/dummy/config/environments/production.rb +85 -0
- data/test/dummy/config/environments/test.rb +44 -0
- data/test/dummy/config/initializers/assets.rb +11 -0
- data/test/dummy/config/initializers/backtrace_silencers.rb +7 -0
- data/test/dummy/config/initializers/cookies_serializer.rb +3 -0
- data/test/dummy/config/initializers/filter_parameter_logging.rb +4 -0
- data/test/dummy/config/initializers/inflections.rb +16 -0
- data/test/dummy/config/initializers/mime_types.rb +4 -0
- data/test/dummy/config/initializers/session_store.rb +3 -0
- data/test/dummy/config/initializers/to_time_preserves_timezone.rb +10 -0
- data/test/dummy/config/initializers/wrap_parameters.rb +14 -0
- data/test/dummy/config/locales/en.yml +23 -0
- data/test/dummy/config/routes.rb +6 -0
- data/test/dummy/config.ru +4 -0
- data/test/dummy/db/schema.rb +108 -0
- data/test/dummy/lib/assets/.keep +0 -0
- data/test/dummy/log/.keep +0 -0
- data/test/dummy/public/404.html +67 -0
- data/test/dummy/public/422.html +67 -0
- data/test/dummy/public/500.html +66 -0
- data/test/dummy/public/favicon.ico +0 -0
- data/test/extensions/action_controller_base_extensions_test.rb +21 -0
- data/test/extensions/action_mailer_base_extensions_test.rb +20 -0
- data/test/extensions/action_view_base_extensions_test.rb +267 -0
- data/test/extensions/active_record_extensions_test.rb +173 -0
- data/test/extensions/application_configuration_extensions_test.rb +46 -0
- data/test/extensions/application_extensions_test.rb +23 -0
- data/test/extensions/connection_adapter_extensions_test.rb +54 -0
- data/test/extensions/date_time_value_extensions_test.rb +104 -0
- data/test/extensions/date_value_extensions_test.rb +102 -0
- data/test/extensions/decimal_value_extensions_test.rb +85 -0
- data/test/extensions/erb_scaffold_generator_extensions_test.rb +17 -0
- data/test/extensions/float_value_extensions_test.rb +78 -0
- data/test/extensions/form_builder_extensions_test.rb +28 -0
- data/test/extensions/integer_value_extensions_test.rb +78 -0
- data/test/extensions/jbuilder_generator_extensions_test.rb +21 -0
- data/test/extensions/jbuilder_template_extensions_test.rb +47 -0
- data/test/extensions/main_app_extensions_test.rb +55 -0
- data/test/extensions/numeric_extensions_test.rb +76 -0
- data/test/extensions/object_extensions_test.rb +104 -0
- data/test/extensions/session_extensions_test.rb +69 -0
- data/test/extensions/string_extensions_test.rb +32 -0
- data/test/extensions/test_case_extensions_test.rb +538 -0
- data/test/extensions/time_zone_converter_extensions_test.rb +10 -0
- data/test/fixtures/incline/access_group_group_members.yml +1 -0
- data/test/fixtures/incline/access_group_user_members.yml +1 -0
- data/test/fixtures/incline/access_groups.yml +13 -0
- data/test/fixtures/incline/action_groups.yml +6 -0
- data/test/fixtures/incline/action_securities.yml +18 -0
- data/test/fixtures/incline/user_login_histories.yml +1 -0
- data/test/fixtures/incline/users.yml +64 -0
- data/test/incline_test.rb +63 -0
- data/test/integration/incline/users_edit_test.rb +180 -0
- data/test/integration/incline/users_login_test.rb +105 -0
- data/test/integration/incline/users_signup_test.rb +147 -0
- data/test/integration/navigation_test.rb +11 -0
- data/test/lib/data_tables_request_test.rb +245 -0
- data/test/lib/date_time_formats_test.rb +111 -0
- data/test/lib/global_status_test.rb +89 -0
- data/test/lib/json_log_formatter_test.rb +43 -0
- data/test/lib/log_test.rb +36 -0
- data/test/lib/recaptcha_test.rb +75 -0
- data/test/lib/user_manager_test.rb +47 -0
- data/test/lib/work_path_test.rb +18 -0
- data/test/models/incline/access_group_group_member_test.rb +30 -0
- data/test/models/incline/access_group_test.rb +60 -0
- data/test/models/incline/access_group_user_member_test.rb +29 -0
- data/test/models/incline/action_group_test.rb +27 -0
- data/test/models/incline/action_security_test.rb +176 -0
- data/test/models/incline/contact_message_test.rb +66 -0
- data/test/models/incline/disable_info_test.rb +29 -0
- data/test/models/incline/password_reset_request_test.rb +35 -0
- data/test/models/incline/password_reset_test.rb +51 -0
- data/test/models/incline/user_login_history_test.rb +31 -0
- data/test/models/incline/user_test.rb +91 -0
- data/test/test_helper.rb +42 -0
- data/test/validators/email_validator_test.rb +102 -0
- data/test/validators/ip_address_validator_test.rb +107 -0
- data/test/validators/recaptcha_validator_test.rb +57 -0
- data/test/validators/safe_name_validator_test.rb +101 -0
- 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&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
|