foreman_openscap 0.5.0 → 0.5.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (83) hide show
  1. checksums.yaml +4 -4
  2. data/app/assets/javascripts/foreman_openscap/arf_reports.js +16 -0
  3. data/app/assets/javascripts/foreman_openscap/openscap_proxy.js +52 -0
  4. data/app/assets/javascripts/foreman_openscap/reports.js +6 -0
  5. data/app/assets/stylesheets/foreman_openscap/policy.css +2 -1
  6. data/app/assets/stylesheets/foreman_openscap/reports.css +3 -0
  7. data/app/controllers/api/v2/compliance/arf_reports_controller.rb +2 -2
  8. data/app/controllers/api/v2/compliance/policies_controller.rb +6 -6
  9. data/app/controllers/api/v2/compliance/scap_contents_controller.rb +2 -2
  10. data/app/controllers/arf_reports_controller.rb +41 -5
  11. data/app/controllers/concerns/foreman_openscap/hostgroups_controller_extensions.rb +20 -0
  12. data/app/controllers/concerns/foreman_openscap/hosts_common_controller_extensions.rb +45 -0
  13. data/app/controllers/concerns/foreman_openscap/hosts_controller_extensions.rb +17 -0
  14. data/app/controllers/policies_controller.rb +4 -4
  15. data/app/helpers/arf_report_dashboard_helper.rb +5 -5
  16. data/app/helpers/arf_reports_helper.rb +28 -0
  17. data/app/helpers/compliance_hosts_helper.rb +5 -3
  18. data/app/helpers/policies_helper.rb +3 -3
  19. data/app/helpers/policy_dashboard_helper.rb +2 -2
  20. data/app/lib/proxy_api/available_proxy.rb +7 -7
  21. data/app/lib/proxy_api/openscap.rb +10 -0
  22. data/app/mailers/foreman_openscap/policy_mailer.rb +2 -2
  23. data/app/models/concerns/foreman_openscap/compliance_status_scoped_search.rb +5 -5
  24. data/app/models/concerns/foreman_openscap/host_extensions.rb +22 -14
  25. data/app/models/concerns/foreman_openscap/hostgroup_extensions.rb +12 -0
  26. data/app/models/concerns/foreman_openscap/log_extensions.rb +8 -0
  27. data/app/models/concerns/foreman_openscap/openscap_proxy_core_extensions.rb +55 -0
  28. data/app/models/concerns/foreman_openscap/openscap_proxy_extensions.rb +17 -0
  29. data/app/models/concerns/foreman_openscap/smart_proxy_extensions.rb +23 -0
  30. data/app/models/foreman_openscap/arf_report.rb +42 -21
  31. data/app/models/foreman_openscap/policy.rb +38 -38
  32. data/app/models/foreman_openscap/scap_content.rb +9 -9
  33. data/app/overrides/hostgroups/form/select_openscap_proxy.rb +4 -0
  34. data/app/overrides/hosts/form/select_openscap_proxy.rb +4 -0
  35. data/app/services/foreman_openscap/host_report_dashboard/data.rb +5 -5
  36. data/app/services/foreman_openscap/report_dashboard/data.rb +5 -5
  37. data/app/views/arf_reports/_detailed_message.html.erb +9 -0
  38. data/app/views/arf_reports/_list.html.erb +29 -9
  39. data/app/views/arf_reports/_output.html.erb +12 -8
  40. data/app/views/arf_reports/delete_multiple.html.erb +29 -0
  41. data/app/views/arf_reports/index.html.erb +1 -1
  42. data/app/views/arf_reports/show.html.erb +3 -1
  43. data/app/views/compliance_hosts/_openscap_proxy.html.erb +8 -0
  44. data/app/views/policies/_form.html.erb +1 -1
  45. data/app/views/policies/steps/_schedule_form.html.erb +1 -1
  46. data/config/routes.rb +17 -1
  47. data/db/migrate/20141013172051_create_scaptimony_policies.rb +1 -1
  48. data/db/migrate/20141014105333_create_scaptimony_assets.rb +2 -2
  49. data/db/migrate/20141015092642_create_scaptimony_arf_reports.rb +4 -4
  50. data/db/migrate/20141104164201_create_scaptimony_scap_contents.rb +1 -3
  51. data/db/migrate/20141104171545_create_scaptimony_policy_revisions.rb +2 -2
  52. data/db/migrate/20141105174834_add_columns_to_scaptimony_policies.rb +1 -1
  53. data/db/migrate/20141113221054_create_scaptimony_scap_content_profiles.rb +1 -1
  54. data/db/migrate/20141116171305_add_profile_to_scaptimony_policies.rb +1 -1
  55. data/db/migrate/20141119182606_create_scaptimony_xccdf_rule_results.rb +3 -3
  56. data/db/migrate/20150821100137_migrate_from_scaptimony.rb +2 -2
  57. data/db/migrate/20150929152345_move_arf_reports_to_reports_table.rb +1 -1
  58. data/db/migrate/20151023131950_link_arf_report_directly_to_host.rb +12 -8
  59. data/db/migrate/20151118165125_add_size_to_scap_content.rb +5 -0
  60. data/db/migrate/20151119155419_add_arf_fields_to_message.rb +8 -0
  61. data/db/migrate/20151120090851_add_openscap_proxy_to_host_and_hostgroup.rb +25 -0
  62. data/db/seeds.d/openscap_feature.rb +1 -1
  63. data/db/seeds.d/openscap_scap_default.rb +1 -1
  64. data/lib/foreman_openscap/bulk_upload.rb +11 -12
  65. data/lib/foreman_openscap/engine.rb +22 -11
  66. data/lib/foreman_openscap/helper.rb +1 -1
  67. data/lib/foreman_openscap/version.rb +1 -1
  68. data/lib/tasks/foreman_openscap_tasks.rake +3 -2
  69. data/test/factories/arf_report_factory.rb +2 -1
  70. data/test/factories/compliance_host_factory.rb +1 -0
  71. data/test/factories/scap_content_related.rb +1 -1
  72. data/test/functional/api/v2/compliance/arf_reports_controller_test.rb +3 -3
  73. data/test/functional/api/v2/compliance/policies_controller_test.rb +1 -1
  74. data/test/functional/api/v2/compliance/scap_contents_controller_test.rb +2 -2
  75. data/test/functional/arf_reports_controller_test.rb +21 -0
  76. data/test/lib/foreman_openscap/bulk_upload_test.rb +2 -2
  77. data/test/test_plugin_helper.rb +2 -2
  78. data/test/unit/arf_report_test.rb +12 -1
  79. data/test/unit/compliance_status_test.rb +6 -5
  80. data/test/unit/openscap_host_test.rb +4 -0
  81. data/test/unit/policy_mailer_test.rb +1 -1
  82. data/test/unit/scap_content_test.rb +1 -1
  83. metadata +29 -8
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: f4717f3191e1244335417139e2dddef5297b612d
4
- data.tar.gz: 54ac0f517e808a186d74a6a42a03f07a79565abf
3
+ metadata.gz: 121b364917afa0cdfdc713d1faf79d3ab6ccd3b9
4
+ data.tar.gz: 28b664428cc89f6b6f5f668d94b4d9df84b73ea0
5
5
  SHA512:
6
- metadata.gz: 2dbb8f627fdb059e1099c4f95288bfdc7b4ee9e2eef011cec3c45c9c7419ee8ccf7e99813894d46ba659c8d2cfc88d44cc86ebec0a5e24f91312489478133193
7
- data.tar.gz: c74cd26daf940251810f9aae28728897da0d8d35848939476c2e90a882ae569e5a698835c11b24d669539c187c2bd6526a3c46f6e319bf5b251382190c09ae11
6
+ metadata.gz: d5fe4ad1b975934842292b33f3ccfb147d69054cdfb0c4d65c73c31cd905ebb863ba11cdab59d3b7f5a6dbd2fcbfd3e575ec9d5257d9ce8eccf5c0ffe205d2fb
7
+ data.tar.gz: fad85cd933a8799fd6bb064f816c4770531ccd97e72c2d0e052ddeb63c8f45972adf087f0dc193e90eae6f894fad582043cf31f203b3f4f2d1ef40e4a83ab2f7
@@ -0,0 +1,16 @@
1
+ function buildArfModal(element, url) {
2
+ var url = url + "?" + $.param({arf_report_ids: $.foremanSelectedHosts});
3
+ var title = $(element).attr('data-dialog-title');
4
+ $('#confirmation-modal .modal-header h4').text(title);
5
+ $("#confirmation-modal .modal-body").load(url + " #content",
6
+ function(response, status, xhr) {
7
+ $('#submit_multiple').val('');
8
+ var b = $("#confirmation-modal .btn-primary");
9
+ if ($(response).find('#content form select').length > 0)
10
+ b.addClass("disabled").attr("disabled", true);
11
+ else
12
+ b.removeClass("disabled").attr("disabled", false);
13
+ $('#confirmation-modal').modal();
14
+ });
15
+ return false;
16
+ }
@@ -0,0 +1,52 @@
1
+ function updateOpenscapProxy(element){
2
+ var id = $("form").data('id')
3
+ var url = $(element).attr('data-url');
4
+ var data = $("form").serialize().replace('method=put', 'method=post');
5
+ if (url.match('hostgroups')) {
6
+ data = data + '&hostgroup_id=' + id
7
+ } else {
8
+ data = data + '&host_id=' + id
9
+ }
10
+
11
+ toggleErrorText("");
12
+ $(element).indicator_show();
13
+ $.ajax({
14
+ type: 'post',
15
+ url: url,
16
+ data: data,
17
+ error: function(response) {
18
+ var text = $(response.responseText).find('.form-group.has-error').find('.help-block.help-inline').text();
19
+ addOpenscapProxyError(text);
20
+ $(element).indicator_hide();
21
+ },
22
+ success: function(response) {
23
+ removeOpenscapProxyError();
24
+ $('#puppetclasses_parameters').replaceWith($(response).find("#puppetclasses_parameters"));
25
+ $(element).indicator_hide();
26
+ }
27
+ })
28
+ }
29
+
30
+ function findOpenscapProxyFormGroup(){
31
+ return $('form').find("label[for='openscap_proxy_id']").parents(".form-group").first();
32
+ }
33
+
34
+ function addOpenscapProxyError(text){
35
+ var formGroup = findOpenscapProxyFormGroup();
36
+ $(formGroup).addClass("has-error");
37
+ toggleErrorText(text);
38
+ }
39
+
40
+ function removeOpenscapProxyError(){
41
+ var formGroup = findOpenscapProxyFormGroup();
42
+ $(formGroup).removeClass("has-error");
43
+ toggleErrorText("");
44
+ }
45
+
46
+ function toggleErrorText(text){
47
+ if(text){
48
+ $(findOpenscapProxyFormGroup()).find(".help-block.help-inline").append('<span id="openscap_error">' + text + '</span>');
49
+ } else {
50
+ $(findOpenscapProxyFormGroup()).find("#openscap_error").remove();
51
+ }
52
+ }
@@ -0,0 +1,6 @@
1
+ function showReportDetails(log_id, event) {
2
+ var showDetails = $('#details-' + log_id)
3
+ showDetails.toggle();
4
+ showDetails.is(':visible') ? $(event).find('span').attr('class', 'glyphicon glyphicon-collapse-up') : $(event).find('span').attr('class', 'glyphicon glyphicon-collapse-down');
5
+ }
6
+
@@ -4,4 +4,5 @@
4
4
 
5
5
  .hide-pane {
6
6
  display: none;
7
- }
7
+ }
8
+
@@ -0,0 +1,3 @@
1
+ #report_log a.more-info {
2
+ color: #CAC5C5;
3
+ }
@@ -20,14 +20,14 @@ module Api
20
20
 
21
21
  add_smart_proxy_filters :create, :features => 'Openscap'
22
22
 
23
- before_filter :find_resource, :only => %w{show destroy}
23
+ before_filter :find_resource, :only => %w(show destroy)
24
24
 
25
25
  def resource_name
26
26
  '::ForemanOpenscap::ArfReport'
27
27
  end
28
28
 
29
29
  def get_resource
30
- instance_variable_get :"@arf_report" or raise 'no resource loaded'
30
+ instance_variable_get :"@arf_report" or fail 'no resource loaded'
31
31
  end
32
32
 
33
33
  resource_description do
@@ -5,7 +5,7 @@ module Api::V2
5
5
 
6
6
  add_smart_proxy_filters :content, :features => 'Openscap'
7
7
 
8
- before_filter :find_resource, :except => %w{index create}
8
+ before_filter :find_resource, :except => %w(index create)
9
9
 
10
10
  skip_after_filter :log_response_body, :only => [:content]
11
11
 
@@ -14,7 +14,7 @@ module Api::V2
14
14
  end
15
15
 
16
16
  def get_resource
17
- instance_variable_get :"@policy" or raise 'no resource loaded'
17
+ instance_variable_get :"@policy" or fail 'no resource loaded'
18
18
  end
19
19
 
20
20
  def policy_url(policy = nil)
@@ -94,10 +94,10 @@ module Api::V2
94
94
 
95
95
  def action_permission
96
96
  case params[:action]
97
- when 'content'
98
- :view
99
- else
100
- super
97
+ when 'content'
98
+ :view
99
+ else
100
+ super
101
101
  end
102
102
  end
103
103
  end
@@ -1,14 +1,14 @@
1
1
  module Api::V2
2
2
  module Compliance
3
3
  class ScapContentsController < ::Api::V2::BaseController
4
- before_filter :find_resource, :except => %w{index create}
4
+ before_filter :find_resource, :except => %w(index create)
5
5
 
6
6
  def resource_name
7
7
  '::ForemanOpenscap::ScapContent'
8
8
  end
9
9
 
10
10
  def get_resource
11
- instance_variable_get :"@scap_content" or raise 'no resource loaded'
11
+ instance_variable_get :"@scap_content" or fail 'no resource loaded'
12
12
  end
13
13
 
14
14
  resource_description do
@@ -1,14 +1,15 @@
1
1
  class ArfReportsController < ApplicationController
2
2
  include Foreman::Controller::AutoCompleteSearch
3
3
 
4
- before_filter :find_by_id, :only => [:show, :show_html, :destroy, :parse_html, :parse_bzip]
4
+ before_filter :find_arf_report, :only => [:show, :show_html, :destroy, :parse_html, :parse_bzip]
5
+ before_filter :find_multiple, :only => [:delete_multiple, :submit_delete_multiple]
5
6
 
6
7
  def model_of_controller
7
8
  ::ForemanOpenscap::ArfReport
8
9
  end
9
10
 
10
11
  def index
11
- @arf_reports = resource_base.includes(:asset)
12
+ @arf_reports = resource_base.includes(:host => [:policies, :last_report_object, :host_statuses])
12
13
  .search_for(params[:search], :order => params[:order])
13
14
  .paginate(:page => params[:page], :per_page => params[:per_page])
14
15
  end
@@ -41,20 +42,55 @@ class ArfReportsController < ApplicationController
41
42
  if @arf_report.destroy
42
43
  process_success(:success_msg => (_("Successfully deleted Arf report.")), :success_redirect => arf_reports_path)
43
44
  else
44
- process_error
45
+ process_error(:error_msg => _("Failed to delete Arf Report for host #{@arf_report.host.name} reported at #{@arf_report.reported_at}"))
46
+ end
47
+ end
48
+
49
+ def delete_multiple
50
+ end
51
+
52
+ def submit_delete_multiple
53
+ failed_deletes = @arf_reports.reject(&:destroy).count
54
+ if failed_deletes > 0
55
+ process_error(:error_msg => (_("Failed to delete %s compliance reports") % failed_deletes),
56
+ :error_redirect => arf_reports_path)
57
+ else
58
+ process_success(:success_msg => (_("Successfully deleted %s compliance reports") % @arf_reports.count),
59
+ :success_redirect => arf_reports_path)
45
60
  end
46
61
  end
47
62
 
48
63
  private
49
64
 
50
- def find_by_id
51
- @arf_report = resource_base.find(params[:id])
65
+ def find_arf_report
66
+ @arf_report = resource_base.includes(:logs => [:message, :source]).find(params[:id])
67
+ end
68
+
69
+ def find_multiple
70
+ if params[:arf_report_ids].present?
71
+ @arf_reports = ::ForemanOpenscap::ArfReport.where(:id => params[:arf_report_ids])
72
+ if @arf_reports.empty?
73
+ error _('No complince reports were found.')
74
+ redirect_to(arf_reports_path) and return false
75
+ end
76
+ else
77
+ error _('No complince reports selected')
78
+ redirect_to(arf_reports_path) and return false
79
+ end
80
+ return @arf_reports
81
+ rescue => e
82
+ error _("Something went wrong while selecting compliance reports - %s") % (e)
83
+ logger.debug e.message
84
+ logger.debug e.backtrace.join("\n")
85
+ redirect_to arf_reports_path and return false
52
86
  end
53
87
 
54
88
  def action_permission
55
89
  case params[:action]
56
90
  when 'show_html', 'parse_html', 'parse_bzip'
57
91
  :view
92
+ when 'delete_multiple', 'submit_delete_multiple'
93
+ :destroy
58
94
  else
59
95
  super
60
96
  end
@@ -0,0 +1,20 @@
1
+ module ForemanOpenscap
2
+ module HostgroupsControllerExtensions
3
+ extend ActiveSupport::Concern
4
+
5
+ included do
6
+ alias_method_chain :load_vars_for_ajax, :openscap
7
+ alias_method_chain :inherit_parent_attributes, :openscap
8
+ end
9
+
10
+ def load_vars_for_ajax_with_openscap
11
+ load_vars_for_ajax_without_openscap
12
+ @openscap_proxy = @hostgroup.openscap_proxy
13
+ end
14
+
15
+ def inherit_parent_attributes_with_openscap
16
+ inherit_parent_attributes_without_openscap
17
+ @hostgroup.openscap_proxy ||= @parent.openscap_proxy
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,45 @@
1
+ module ForemanOpenscap
2
+ module HostsCommonControllerExtensions
3
+ extend ActiveSupport::Concern
4
+
5
+ def openscap_proxy_changed
6
+ model = if params[param_id_key].blank?
7
+ controller_name.classify.constantize.new(params[param_model_key])
8
+ else
9
+ resource_base.find(params[param_id_key])
10
+ end
11
+ proxy_id = params[param_model_key][:openscap_proxy_id]
12
+ instance_variable_set("@#{param_model_key}", model)
13
+ if proxy_id.blank?
14
+ render :partial => "form"
15
+ else
16
+ begin
17
+ instance_variable_get("@#{param_model_key}").update_scap_client_params(proxy_id)
18
+ render :partial => "form"
19
+ rescue => e
20
+ instance_variable_get("@#{param_model_key}").errors.add(:openscap_proxy_id, e.message)
21
+ render :partial => "form", :status => 422
22
+ end
23
+ end
24
+ end
25
+
26
+ def action_permission
27
+ case params[:action]
28
+ when 'openscap_proxy_changed'
29
+ :edit
30
+ else
31
+ super
32
+ end
33
+ end
34
+
35
+ private
36
+
37
+ def param_id_key
38
+ "#{param_model_key}_id"
39
+ end
40
+
41
+ def param_model_key
42
+ controller_name.singularize
43
+ end
44
+ end
45
+ end
@@ -0,0 +1,17 @@
1
+ module ForemanOpenscap
2
+ module HostsControllerExtensions
3
+ extend ActiveSupport::Concern
4
+
5
+ included do
6
+ alias_method_chain :process_hostgroup, :openscap
7
+ self::AJAX_REQUESTS << 'openscap_proxy_changed'
8
+ end
9
+
10
+ def process_hostgroup_with_openscap
11
+ @hostgroup = Hostgroup.find(params[:host][:hostgroup_id]) if params[:host][:hostgroup_id].to_i > 0
12
+ return head(:not_found) unless @hostgroup
13
+ @openscap_proxy = @hostgroup.openscap_proxy
14
+ process_hostgroup_without_openscap
15
+ end
16
+ end
17
+ end
@@ -134,10 +134,10 @@ class PoliciesController < ApplicationController
134
134
 
135
135
  def action_permission
136
136
  case params[:action]
137
- when 'parse'
138
- :view
139
- else
140
- super
137
+ when 'parse'
138
+ :view
139
+ else
140
+ super
141
141
  end
142
142
  end
143
143
  end
@@ -1,8 +1,8 @@
1
1
  module ArfReportDashboardHelper
2
2
  COLORS = {
3
- :passed => '#89A54E',
4
- :failed => '#AA4643',
5
- :othered => '#DB843D',
3
+ :passed => '#89A54E',
4
+ :failed => '#AA4643',
5
+ :othered => '#DB843D',
6
6
  }
7
7
 
8
8
  def reports_breakdown_chart(report, options = {})
@@ -10,9 +10,9 @@ module ArfReportDashboardHelper
10
10
  [[:failed, _('Failed')],
11
11
  [:passed, _('Passed')],
12
12
  [:othered, _('Othered')],
13
- ].each { |i|
13
+ ].each do |i|
14
14
  data << {:label => i[1], :data => report[i[0]], :color => COLORS[i[0]]}
15
- }
15
+ end
16
16
  flot_pie_chart 'overview', _('Compliance reports breakdown'), data, options
17
17
  end
18
18
  end
@@ -34,4 +34,32 @@ module ArfReportsHelper
34
34
  end
35
35
  "class='label label-#{tag}'".html_safe
36
36
  end
37
+
38
+ def severity_tag(level)
39
+ tag = case level.downcase.to_sym
40
+ when :low
41
+ "info"
42
+ when :medium
43
+ "warning"
44
+ when :high
45
+ "danger"
46
+ else
47
+ "default"
48
+ end
49
+ "class='label label-#{tag}'".html_safe
50
+ end
51
+
52
+ def multiple_actions_arf_report
53
+ actions = [
54
+ [_('Delete reports'), delete_multiple_arf_reports_path]
55
+ ]
56
+ end
57
+
58
+ def multiple_actions_arf_report_select
59
+ select_action_button(_("Select Action"), {:id => 'submit_multiple'},
60
+ multiple_actions_arf_report.map do |action|
61
+ link_to_function(action[0], "buildArfModal(this, '#{action[1]}')",
62
+ :'data-dialog-title' => _("%s - The following compliance reports are about to be changed") % action[0])
63
+ end.flatten)
64
+ end
37
65
  end
@@ -5,14 +5,16 @@ module ComplianceHostsHelper
5
5
  [[:passed, _('Passed')],
6
6
  [:failed, _('Failed')],
7
7
  [:othered, _('Other')],
8
- ].each { |i|
8
+ ].each do |i|
9
9
  data << {:label => i[1], :data => report[i[0]], :color => ArfReportDashboardHelper::COLORS[i[0]]}
10
- }
10
+ end
11
11
  flot_pie_chart 'overview', _('Compliance reports breakdown'), data, options
12
12
  end
13
13
 
14
14
  def host_arf_reports_chart(policy_id)
15
- passed, failed, othered, = [], [], []
15
+ passed = []
16
+ failed = []
17
+ othered = []
16
18
  @host.arf_reports.of_policy(policy_id).each do |report|
17
19
  passed << [report.created_at.to_i*1000, report.passed]
18
20
  failed << [report.created_at.to_i*1000, report.failed]
@@ -9,7 +9,7 @@ module PoliciesHelper
9
9
  scap_contents = ::ForemanOpenscap::ScapContent.all
10
10
  if scap_contents.length > 1
11
11
  select_f form, :scap_content_id, scap_contents, :id, :title,
12
- {:include_blank => _("Choose existing SCAP Content")} ,
12
+ {:include_blank => _("Choose existing SCAP Content")},
13
13
  {:label => _("SCAP Content"),
14
14
  :onchange => 'scap_content_selected(this);',
15
15
  :'data-url' => method_path('scap_content_selected')}
@@ -44,7 +44,7 @@ module PoliciesHelper
44
44
  content_tag(:div, :class => "form-actions") do
45
45
  text = overwrite ? overwrite : _("Submit")
46
46
  options = {:class => "btn btn-primary"}
47
- options.merge! :'data-id' => form_to_submit_id(form) unless options.has_key?(:'data-id')
47
+ options.merge! :'data-id' => form_to_submit_id(form) unless options.key?(:'data-id')
48
48
  previous = form.object.first_step? ? ' ' : previous_link(form)
49
49
  cancel_and_submit = content_tag(:div, :class => "pull-right") do
50
50
  link_to(_("Cancel"), args[:cancel_path], :class => "btn btn-default") + ' ' +
@@ -67,6 +67,6 @@ module PoliciesHelper
67
67
  end
68
68
 
69
69
  def days_of_week_hash
70
- Hash[*Date::DAYNAMES.map{ |day| [day.downcase, day]}.flatten]
70
+ Hash[*Date::DAYNAMES.map { |day| [day.downcase, day] }.flatten]
71
71
  end
72
72
  end