CVE-2015-10080: Improper Neutralization of Input During Web Page Generation ('Cross-site Scripting') (CWE-79)
value: A vulnerability was found in NREL api-umbrella-web 0.7.1. It has been classified as problematic. This affects an unknown part of the component Admin Data Table Handler. The manipulation leads to cross site scripting. It is possible to initiate the attack remotely. Upgrading to version 0.8.0 is able to address this issue. The patch is named f53a9fb87e10c457f0f3dd4f2af24d3b2f21b3ca. It is recommended to upgrade the affected component. The associated identifier of this vulnerability is VDB-221487.
CWE description: The product does not neutralize or incorrectly neutralizes user-controllable input before it is placed in output that is used as a web page that is served to other users.
CVSS v3 base score: 6.1
Published: 2023-02-20T10:15Z
Repository: https://github.com/NREL/api-umbrella-web
Language: JavaScript
Files changed: app/assets/javascripts/admin/app.js, app/assets/javascripts/admin/views/admin_groups/table_view.js, app/assets/javascripts/admin/views/admins/table_view.js, app/assets/javascripts/admin/views/api_scopes/table_view.js, app/assets/javascripts/admin/views/api_users/table_view.js, app/assets/javascripts/admin/views/apis/table_view.js, app/assets/javascripts/admin/views/stats/logs_table_view.js, app/assets/javascripts/admin/views/stats/users_table_view.js, config/routes.rb, spec/factories/api_users.rb, spec/factories/log_items.rb, spec/features/admin/api_users_spec.rb, spec/features/admin/stats_logs_spec.rb
Commit date: 2015-04-09 22:47:52 -0600
Commit message: Fix XSS escaping issues in the admin data tables.

I mistakenly thought DataTables escaped things by default, but this is
not the case. Escape all data tables fields now and add capybara
integrations tests to ensure this is effective.

Setting up the Capybara tests required a slight change in the analytics
"Filter Logs" table, since the ajax request needs to be performed via
POST instead of GET. I think this is due to the length of the URL
involved in the query. This had worked in most browsers, but I'm
guessing this was broken in IE anyway, so it's probably a good idea
anyway.

Diff: diff --git a/app/assets/javascripts/admin/app.js b/app/assets/javascripts/admin/app.js
index 7c1f803..65225a0 100644
--- a/app/assets/javascripts/admin/app.js
+++ b/app/assets/javascripts/admin/app.js
@@ -361,5 +361,35 @@ Admin.Save = Ember.Mixin.create({
       button.button('reset');
       $.scrollTo('#error_messages', { offset: -50, duration: 200 });
     }, this));
   },
 });
+
+Admin.DataTablesHelpers = {
+  renderEscaped: function(value, type) {
+    if(type === 'display' && value) {
+      return _.escape(value);
+    }
+
+    return value;
+  },
+
+  renderListEscaped: function(value, type) {
+    if(type === 'display' && value) {
+      if(_.isArray(value)) {
+        return _.map(value, function(v) { return _.escape(v); }).join('<br>');
+      } else {
+        return _.escape(value);
+      }
+    }
+
+    return value;
+  },
+
+  renderTime: function(value, type) {
+    if(type === 'display' && value && value !== '-') {
+      return moment(value).format('YYYY-MM-DD HH:mm:ss');
+    }
+
+    return value;
+  },
+};
diff --git a/app/assets/javascripts/admin/views/admin_groups/table_view.js b/app/assets/javascripts/admin/views/admin_groups/table_view.js
index a50b59f..a992d41 100644
--- a/app/assets/javascripts/admin/views/admin_groups/table_view.js
+++ b/app/assets/javascripts/admin/views/admin_groups/table_view.js
@@ -24,29 +24,17 @@ Admin.AdminGroupsTableView = Ember.View.extend({
           }, this),
         },
         {
           data: 'api_scope_display_names',
           title: 'API Scopes',
-          render: function(names, type) {
-            if(type === 'display' && names && names !== '-') {
-              return names.join('<br>');
-            }
-
-            return names;
-          }
+          render: Admin.DataTablesHelpers.renderListEscaped,
         },
         {
           data: 'permission_display_names',
           title: 'Access',
           defaultContent: '-',
-          render: function(names, type) {
-            if(type === 'display' && names && names !== '-') {
-              return names.join('<br>');
-            }
-
-            return names;
-          }
+          render: Admin.DataTablesHelpers.renderListEscaped,
         }
       ]
     });
   },
 });
diff --git a/app/assets/javascripts/admin/views/admins/table_view.js b/app/assets/javascripts/admin/views/admins/table_view.js
index a6d52f4..51de1db 100644
--- a/app/assets/javascripts/admin/views/admins/table_view.js
+++ b/app/assets/javascripts/admin/views/admins/table_view.js
@@ -25,52 +25,36 @@ Admin.AdminsTableView = Ember.View.extend({
         },
         {
           data: 'email',
           title: 'E-mail',
           defaultContent: '-',
+          render: Admin.DataTablesHelpers.renderEscaped,
         },
         {
           data: 'name',
           title: 'Name',
           defaultContent: '-',
+          render: Admin.DataTablesHelpers.renderEscaped,
         },
         {
           data: 'group_names',
           title: 'Groups',
-          render: function(names, type) {
-            if(type === 'display' && names && names !== '-') {
-              return names.join('<br>');
-            }
-
-            return names;
-          }
+          render: Admin.DataTablesHelpers.renderListEscaped,
         },
         {
           data: 'last_sign_in_at',
           type: 'date',
           title: 'Last Signed In',
           defaultContent: '-',
-          render: function(time, type) {
-            if(type === 'display' && time && time !== '-') {
-              return moment(time).format('YYYY-MM-DD HH:mm:ss');
-            }
-
-            return time;
-          },
+          render: Admin.DataTablesHelpers.renderTime,
         },
         {
           data: 'created_at',
           type: 'date',
           title: 'Created',
           defaultContent: '-',
-          render: function(time, type) {
-            if(type === 'display' && time && time !== '-') {
-              return moment(time).format('YYYY-MM-DD HH:mm:ss');
-            }
-
-            return time;
-          },
+          render: Admin.DataTablesHelpers.renderTime,
         }
       ]
     });
   },
 });
diff --git a/app/assets/javascripts/admin/views/api_scopes/table_view.js b/app/assets/javascripts/admin/views/api_scopes/table_view.js
index 7447673..9fbccf2 100644
--- a/app/assets/javascripts/admin/views/api_scopes/table_view.js
+++ b/app/assets/javascripts/admin/views/api_scopes/table_view.js
@@ -25,15 +25,17 @@ Admin.ApiScopesTableView = Ember.View.extend({
         },
         {
           data: 'host',
           title: 'Host',
           defaultContent: '-',
+          render: Admin.DataTablesHelpers.renderEscaped,
         },
         {
           data: 'path_prefix',
           title: 'Path Prefix',
           defaultContent: '-',
+          render: Admin.DataTablesHelpers.renderEscaped,
         }
       ]
     });
   },
 });
diff --git a/app/assets/javascripts/admin/views/api_users/table_view.js b/app/assets/javascripts/admin/views/api_users/table_view.js
index 8519058..d2d1cf2 100644
--- a/app/assets/javascripts/admin/views/api_users/table_view.js
+++ b/app/assets/javascripts/admin/views/api_users/table_view.js
@@ -25,44 +25,43 @@ Admin.ApiUsersTableView = Ember.View.extend({
         },
         {
           data: 'first_name',
           title: 'First Name',
           defaultContent: '-',
+          render: Admin.DataTablesHelpers.renderEscaped,
         },
         {
           data: 'last_name',
           title: 'Last Name',
           defaultContent: '-',
+          render: Admin.DataTablesHelpers.renderEscaped,
         },
         {
           data: 'use_description',
           title: 'Purpose',
           defaultContent: '-',
+          render: Admin.DataTablesHelpers.renderEscaped,
         },
         {
           data: 'created_at',
           type: 'date',
           title: 'Created',
           defaultContent: '-',
-          render: function(time, type) {
-            if(type === 'display' && time && time !== '-') {
-              return moment(time).format('YYYY-MM-DD HH:mm:ss');
-            }
-
-            return time;
-          },
+          render: Admin.DataTablesHelpers.renderTime,
         },
         {
           data: 'registration_source',
           title: 'Registration Source',
           defaultContent: '-',
+          render: Admin.DataTablesHelpers.renderEscaped,
         },
         {
           data: 'api_key_preview',
           title: 'API Key',
           defaultContent: '-',
           orderable: false,
+          render: Admin.DataTablesHelpers.renderEscaped,
         },
       ]
     });
   },
 });
diff --git a/app/assets/javascripts/admin/views/apis/table_view.js b/app/assets/javascripts/admin/views/apis/table_view.js
index ef41289..c1457bd 100644
--- a/app/assets/javascripts/admin/views/apis/table_view.js
+++ b/app/assets/javascripts/admin/views/apis/table_view.js
@@ -37,21 +37,24 @@ Admin.ApisTableView = Ember.View.extend({
         },
         {
           data: 'frontend_host',
           title: 'Host',
           defaultContent: '-',
+          render: Admin.DataTablesHelpers.renderEscaped,
         },
         {
           data: 'frontend_prefixes',
           title: 'Prefixes',
           defaultContent: '-',
+          render: Admin.DataTablesHelpers.renderEscaped,
         },
         {
           data: 'sort_order',
           title: 'Matching Order',
           defaultContent: '-',
           width: 130,
+          render: Admin.DataTablesHelpers.renderEscaped,
         },
         {
           data: null,
           className: 'reorder-handle',
           orderable: false,
diff --git a/app/assets/javascripts/admin/views/stats/logs_table_view.js b/app/assets/javascripts/admin/views/stats/logs_table_view.js
index 1b0e3ba..f62a471 100644
--- a/app/assets/javascripts/admin/views/stats/logs_table_view.js
+++ b/app/assets/javascripts/admin/views/stats/logs_table_view.js
@@ -7,10 +7,13 @@ Admin.LogsTableView = Ember.View.extend({
     this.$().DataTable({
       searching: false,
       serverSide: true,
       ajax: {
         url: '/admin/stats/logs.json',
+        // Use POST for this endpoint, since the URLs can be very long and
+        // exceed URL length limits in IE (and apparently Capybara too).
+        type: 'POST',
         data: _.bind(function(data) {
           var query = this.get('controller.query.params');
           return _.extend({}, data, query);
         }, this)
       },
@@ -41,32 +44,29 @@ Admin.LogsTableView = Ember.View.extend({
         {
           data: 'request_at',
           type: 'date',
           title: 'Time',
           defaultContent: '-',
-          render: function(time, type) {
-            if(type === 'display' && time && time !== '-') {
-              return moment(time).format('YYYY-MM-DD HH:mm:ss');
-            }
-
-            return time;
-          },
+          render: Admin.DataTablesHelpers.renderTime,
         },
         {
           data: 'request_method',
           title: 'Method',
           defaultContent: '-',
+          render: Admin.DataTablesHelpers.renderEscaped,
         },
         {
           data: 'request_host',
           title: 'Host',
           defaultContent: '-',
+          render: Admin.DataTablesHelpers.renderEscaped,
         },
         {
           data: 'request_url',
           title: 'URL',
           defaultContent: '-',
+          render: Admin.DataTablesHelpers.renderEscaped,
         },
         {
           data: 'user_email',
           title: 'User',
           defaultContent: '-',
@@ -84,30 +84,35 @@ Admin.LogsTableView = Ember.View.extend({
         },
         {
           data: 'request_ip',
           title: 'IP Address',
           defaultContent: '-',
+          render: Admin.DataTablesHelpers.renderEscaped,
         },
         {
           data: 'request_ip_country',
           title: 'Country',
           defaultContent: '-',
+          render: Admin.DataTablesHelpers.renderEscaped,
         },
         {
           data: 'request_ip_region',
           title: 'State',
           defaultContent: '-',
+          render: Admin.DataTablesHelpers.renderEscaped,
         },
         {
           data: 'request_ip_city',
           title: 'City',
           defaultContent: '-',
+          render: Admin.DataTablesHelpers.renderEscaped,
         },
         {
           data: 'response_status',
           title: 'Status',
           defaultContent: '-',
+          render: Admin.DataTablesHelpers.renderEscaped,
         },
         {
           data: 'response_time',
           title: 'Response Time',
           defaultContent: '-',
@@ -121,20 +126,23 @@ Admin.LogsTableView = Ember.View.extend({
         },
         {
           data: 'response_content_type',
           title: 'Content Type',
           defaultContent: '-',
+          render: Admin.DataTablesHelpers.renderEscaped,
         },
         {
           data: 'request_accept_encoding',
           title: 'Accept Encoding',
           defaultContent: '-',
+          render: Admin.DataTablesHelpers.renderEscaped,
         },
         {
           data: 'request_user_agent',
           title: 'User Agent',
           defaultContent: '-',
+          render: Admin.DataTablesHelpers.renderEscaped,
         },
       ]
     });
   },
 
diff --git a/app/assets/javascripts/admin/views/stats/users_table_view.js b/app/assets/javascripts/admin/views/stats/users_table_view.js
index d75a574..9623f35 100644
--- a/app/assets/javascripts/admin/views/stats/users_table_view.js
+++ b/app/assets/javascripts/admin/views/stats/users_table_view.js
@@ -34,26 +34,24 @@ Admin.StatsUsersTableView = Ember.View.extend({
         },
         {
           data: 'first_name',
           title: 'First Name',
           defaultContent: '-',
+          render: Admin.DataTablesHelpers.renderEscaped,
         },
         {
           data: 'last_name',
           title: 'Last Name',
           defaultContent: '-',
+          render: Admin.DataTablesHelpers.renderEscaped,
         },
         {
           data: 'created_at',
           type: 'date',
           title: 'Signed Up',
           defaultContent: '-',
-          render: function(time, type) {
-            if(type === 'display' && time && time !== '-') {
-              return moment(time).format('YYYY-MM-DD HH:mm:ss');
-            }
-          },
+          render: Admin.DataTablesHelpers.renderTime,
         },
         {
           data: 'hits',
           title: 'Hits',
           defaultContent: '-',
@@ -68,22 +66,17 @@ Admin.StatsUsersTableView = Ember.View.extend({
         {
           data: 'last_request_at',
           type: 'date',
           title: 'Last Request',
           defaultContent: '-',
-          render: function(time, type) {
-            if(type === 'display' && time && time !== '-') {
-              return moment(time).format('YYYY-MM-DD HH:mm:ss');
-            }
-
-            return time;
-          },
+          render: Admin.DataTablesHelpers.renderTime,
         },
         {
           data: 'use_description',
           title: 'Use Description',
           defaultContent: '-',
+          render: Admin.DataTablesHelpers.renderTime,
         },
       ]
     });
   },
 
diff --git a/config/routes.rb b/config/routes.rb
index c43fb7d..8310258 100644
--- a/config/routes.rb
+++ b/config/routes.rb
@@ -69,10 +69,11 @@ ApiUmbrella::Application.routes.draw do
 
     resources :stats, :only => [:index] do
       collection do
         get "search"
         get "logs"
+        post "logs"
         get "users"
         get "map"
       end
     end
 
diff --git a/spec/factories/api_users.rb b/spec/factories/api_users.rb
index a2c03e7..48068fb 100644
--- a/spec/factories/api_users.rb
+++ b/spec/factories/api_users.rb
@@ -9,7 +9,14 @@ FactoryGirl.define do
     terms_and_conditions "1"
 
     factory :invalid_api_user do
       terms_and_conditions ""
     end
+
+    factory :xss_api_user do
+      first_name '"><script class="xss-test">alert("Hello first_name");</script>'
+      last_name '"><script class="xss-test">alert("Hello last_name");</script>'
+      use_description '"><script class="xss-test">alert("Hello use_description");</script>'
+      registration_source '"><script class="xss-test">alert("Hello registration_source");</script>'
+    end
   end
 end
diff --git a/spec/factories/log_items.rb b/spec/factories/log_items.rb
index b49f282..04c6958 100644
--- a/spec/factories/log_items.rb
+++ b/spec/factories/log_items.rb
@@ -8,15 +8,18 @@ class LogItem
   attribute :api_key, String
   attribute :backend_response_time, Float
   attribute :internal_gatekeeper_time, Float
   attribute :internal_response_time, Float
   attribute :proxy_overhead, Float
-  attribute :request_accept, String
+  attribute :request_accept_encoding, String
   attribute :request_at, Time
   attribute :request_hierarchy, Array
   attribute :request_host, String
   attribute :request_ip, String
+  attribute :request_ip_city, String
+  attribute :request_ip_country, String
+  attribute :request_ip_region, String
   attribute :request_method, String
   attribute :request_path, String
   attribute :request_query, Hash
   attribute :request_scheme, String
   attribute :request_size, Integer
@@ -45,15 +48,18 @@ FactoryGirl.define do
     api_key "UfhkQUBgWQbJ0ZVqnJ4TvA7quGCZHYTFCXwSfOTQ"
     backend_response_time 0
     internal_gatekeeper_time 1.4
     internal_response_time 1.8
     proxy_overhead 3
-    request_accept "*/*"
+    request_accept_encoding "*/*"
     request_at { Time.now }
     request_hierarchy ["0/127.0.0.1/", "1/127.0.0.1/hello"]
     request_host "127.0.0.1"
     request_ip "127.0.0.1"
+    request_ip_city "Golden"
+    request_ip_country "US"
+    request_ip_region "CO"
     request_method "GET"
     request_path "/hello/"
     request_query({ "foo" => "bar" })
     request_scheme "http"
     request_size 140
@@ -69,7 +75,23 @@ FactoryGirl.define do
     response_status 200
     response_time 3
     user_email "test@example.com"
     user_id "4199b260-ae76-463f-8395-d30de09c1540"
     user_registration_source "web_admin"
+
+    factory :xss_log_item do
+      request_accept_encoding '"><script class="xss-test">alert("1");</script>'
+      request_host '"><script class="xss-test">alert("2");</script>'
+      request_ip_city '"><script class="xss-test">alert("3");</script>'
+      request_ip_country '"><script class="xss-test">alert("4");</script>'
+      request_ip_region '"><script class="xss-test">alert("5");</script>'
+      request_path '"><script class="xss-test">alert("6");</script>'
+      request_query({ "foo" => '"><script class="xss-test">alert("7");</script>' })
+      request_url '"><script class="xss-test">alert("8");</script>'
+      request_user_agent '"><script class="xss-test">alert("9");</script>'
+      response_content_type '"><script class="xss-test">alert("10");</script>'
+      response_server '"><script class="xss-test">alert("11");</script>'
+      user_email '"><script class="xss-test">alert("12");</script>'
+      user_registration_source '"><script class="xss-test">alert("13");</script>'
+    end
   end
 end
diff --git a/spec/features/admin/api_users_spec.rb b/spec/features/admin/api_users_spec.rb
index 3da92e7..8867a3e 100644
--- a/spec/features/admin/api_users_spec.rb
+++ b/spec/features/admin/api_users_spec.rb
@@ -1,10 +1,39 @@
 require 'spec_helper'
 
 describe "api users form", :js => true do
   login_admin
 
+  describe "xss" do
+    before(:all) do
+      ApiUser.where(:registration_source.ne => "seed").delete_all
+      @user = FactoryGirl.create(:xss_api_user)
+    end
+
+    it "escapes html entities in the table" do
+      visit "/admin/#/api_users"
+
+      page.should have_content(@user.email)
+      page.should_not have_selector(".xss-test", :visible => :all)
+      page.should have_content(@user.first_name)
+      page.should have_content(@user.last_name)
+      page.should have_content(@user.use_description)
+      page.should have_content(@user.registration_source)
+    end
+
+    it "escapes html entities in the form" do
+      visit "/admin/#/api_users/#{@user.id}/edit"
+
+      find_field("E-mail").value.should eql(@user.email)
+      page.should_not have_selector(".xss-test", :visible => :all)
+      find_field("First Name").value.should eql(@user.first_name)
+      find_field("Last Name").value.should eql(@user.last_name)
+      find_field("Purpose").value.should eql(@user.use_description)
+      page.should have_content(@user.registration_source)
+    end
+  end
+
   describe "api key in the save notification" do
     it "shows the api key when creating a new account" do
       visit "/admin/#/api_users/new"
 
       fill_in "E-mail", :with => "example@example.com"
diff --git a/spec/features/admin/stats_logs_spec.rb b/spec/features/admin/stats_logs_spec.rb
index 3c5de71..0dfe0f5 100644
--- a/spec/features/admin/stats_logs_spec.rb
+++ b/spec/features/admin/stats_logs_spec.rb
@@ -9,10 +9,29 @@ describe "apis", :js => true do
       LogItem.gateway.client.indices.delete :index => LogItem.index_name
     rescue Elasticsearch::Transport::Transport::Errors::NotFound # rubocop:disable Lint/HandleExceptions
     end
   end
 
+  describe "xss" do
+    it "escapes html entities in the table" do
+      log = FactoryGirl.create(:xss_log_item, :request_at => Time.parse("2015-01-16T06:06:28.816Z"), :request_method => "OPTIONS")
+      LogItem.gateway.refresh_index!
+
+      visit "/admin/#/stats/logs/tz=America%2FDenver&search=&start_at=2015-01-12&end_at=2015-01-18&interval=day"
+
+      page.should have_content(log.request_method)
+      page.should_not have_selector(".xss-test", :visible => :all)
+      page.should have_content(log.request_accept_encoding)
+      page.should have_content(log.request_ip_city)
+      page.should have_content(log.request_ip_country)
+      page.should have_content(log.request_ip_region)
+      page.should have_content(log.request_user_agent)
+      page.should have_content(log.response_content_type)
+      page.should have_content(log.user_email)
+    end
+  end
+
   describe "csv download" do
     it "updates the download link as the query parameters change" do
       FactoryGirl.create(:log_item, :request_at => Time.parse("2015-01-16T06:06:28.816Z"))
       LogItem.gateway.refresh_index!
 

Vulnerable code:           render: function(names, type) {
            if(type === 'display' && names && names !== '-') {
              return names.join('<br>');
            }

            return names;
          }
          render: function(names, type) {
            if(type === 'display' && names && names !== '-') {
              return names.join('<br>');
            }

            return names;
          }
          render: function(names, type) {
            if(type === 'display' && names && names !== '-') {
              return names.join('<br>');
            }

            return names;
          }
          render: function(time, type) {
            if(type === 'display' && time && time !== '-') {
              return moment(time).format('YYYY-MM-DD HH:mm:ss');
            }

            return time;
          },
          render: function(time, type) {
            if(type === 'display' && time && time !== '-') {
              return moment(time).format('YYYY-MM-DD HH:mm:ss');
            }

            return time;
          },
          render: function(time, type) {
            if(type === 'display' && time && time !== '-') {
              return moment(time).format('YYYY-MM-DD HH:mm:ss');
            }

            return time;
          },
          render: function(time, type) {
            if(type === 'display' && time && time !== '-') {
              return moment(time).format('YYYY-MM-DD HH:mm:ss');
            }

            return time;
          },
          render: function(time, type) {
            if(type === 'display' && time && time !== '-') {
              return moment(time).format('YYYY-MM-DD HH:mm:ss');
            }
          },
          render: function(time, type) {
            if(type === 'display' && time && time !== '-') {
              return moment(time).format('YYYY-MM-DD HH:mm:ss');
            }

            return time;
          },
  attribute :request_accept, String
    request_accept "*/*"
Fixed code: 
Admin.DataTablesHelpers = {
  renderEscaped: function(value, type) {
    if(type === 'display' && value) {
      return _.escape(value);
    }

    return value;
  },

  renderListEscaped: function(value, type) {
    if(type === 'display' && value) {
      if(_.isArray(value)) {
        return _.map(value, function(v) { return _.escape(v); }).join('<br>');
      } else {
        return _.escape(value);
      }
    }

    return value;
  },

  renderTime: function(value, type) {
    if(type === 'display' && value && value !== '-') {
      return moment(value).format('YYYY-MM-DD HH:mm:ss');
    }

    return value;
  },
};
          render: Admin.DataTablesHelpers.renderListEscaped,
          render: Admin.DataTablesHelpers.renderListEscaped,
          render: Admin.DataTablesHelpers.renderEscaped,
          render: Admin.DataTablesHelpers.renderEscaped,
          render: Admin.DataTablesHelpers.renderListEscaped,
          render: Admin.DataTablesHelpers.renderTime,
          render: Admin.DataTablesHelpers.renderTime,
          render: Admin.DataTablesHelpers.renderEscaped,
          render: Admin.DataTablesHelpers.renderEscaped,
          render: Admin.DataTablesHelpers.renderEscaped,
          render: Admin.DataTablesHelpers.renderEscaped,
          render: Admin.DataTablesHelpers.renderEscaped,
          render: Admin.DataTablesHelpers.renderTime,
          render: Admin.DataTablesHelpers.renderEscaped,
          render: Admin.DataTablesHelpers.renderEscaped,
          render: Admin.DataTablesHelpers.renderEscaped,
          render: Admin.DataTablesHelpers.renderEscaped,
          render: Admin.DataTablesHelpers.renderEscaped,
        // Use POST for this endpoint, since the URLs can be very long and
        // exceed URL length limits in IE (and apparently Capybara too).
        type: 'POST',
          render: Admin.DataTablesHelpers.renderTime,
          render: Admin.DataTablesHelpers.renderEscaped,
          render: Admin.DataTablesHelpers.renderEscaped,
          render: Admin.DataTablesHelpers.renderEscaped,
          render: Admin.DataTablesHelpers.renderEscaped,
          render: Admin.DataTablesHelpers.renderEscaped,
          render: Admin.DataTablesHelpers.renderEscaped,
          render: Admin.DataTablesHelpers.renderEscaped,
          render: Admin.DataTablesHelpers.renderEscaped,
          render: Admin.DataTablesHelpers.renderEscaped,
          render: Admin.DataTablesHelpers.renderEscaped,
          render: Admin.DataTablesHelpers.renderEscaped,
          render: Admin.DataTablesHelpers.renderEscaped,
          render: Admin.DataTablesHelpers.renderEscaped,
          render: Admin.DataTablesHelpers.renderTime,
          render: Admin.DataTablesHelpers.renderTime,
          render: Admin.DataTablesHelpers.renderTime,
        post "logs"

    factory :xss_api_user do
      first_name '"><script class="xss-test">alert("Hello first_name");</script>'
      last_name '"><script class="xss-test">alert("Hello last_name");</script>'
      use_description '"><script class="xss-test">alert("Hello use_description");</script>'
      registration_source '"><script class="xss-test">alert("Hello registration_source");</script>'
    end
  attribute :request_accept_encoding, String
  attribute :request_ip_city, String
  attribute :request_ip_country, String
  attribute :request_ip_region, String
    request_accept_encoding "*/*"
    request_ip_city "Golden"
    request_ip_country "US"
    request_ip_region "CO"

    factory :xss_log_item do
      request_accept_encoding '"><script class="xss-test">alert("1");</script>'
      request_host '"><script class="xss-test">alert("2");</script>'
      request_ip_city '"><script class="xss-test">alert("3");</script>'
      request_ip_country '"><script class="xss-test">alert("4");</script>'
      request_ip_region '"><script class="xss-test">alert("5");</script>'
      request_path '"><script class="xss-test">alert("6");</script>'
      request_query({ "foo" => '"><script class="xss-test">alert("7");</script>' })
      request_url '"><script class="xss-test">alert("8");</script>'
      request_user_agent '"><script class="xss-test">alert("9");</script>'
      response_content_type '"><script class="xss-test">alert("10");</script>'
      response_server '"><script class="xss-test">alert("11");</script>'
      user_email '"><script class="xss-test">alert("12");</script>'
      user_registration_source '"><script class="xss-test">alert("13");</script>'
    end
  describe "xss" do
    before(:all) do
      ApiUser.where(:registration_source.ne => "seed").delete_all
      @user = FactoryGirl.create(:xss_api_user)
    end

    it "escapes html entities in the table" do
      visit "/admin/#/api_users"

      page.should have_content(@user.email)
      page.should_not have_selector(".xss-test", :visible => :all)
      page.should have_content(@user.first_name)
      page.should have_content(@user.last_name)
      page.should have_content(@user.use_description)
      page.should have_content(@user.registration_source)
    end

    it "escapes html entities in the form" do
      visit "/admin/#/api_users/#{@user.id}/edit"

      find_field("E-mail").value.should eql(@user.email)
      page.should_not have_selector(".xss-test", :visible => :all)
      find_field("First Name").value.should eql(@user.first_name)
      find_field("Last Name").value.should eql(@user.last_name)
      find_field("Purpose").value.should eql(@user.use_description)
      page.should have_content(@user.registration_source)
    end
  end

  describe "xss" do
    it "escapes html entities in the table" do
      log = FactoryGirl.create(:xss_log_item, :request_at => Time.parse("2015-01-16T06:06:28.816Z"), :request_method => "OPTIONS")
      LogItem.gateway.refresh_index!

      visit "/admin/#/stats/logs/tz=America%2FDenver&search=&start_at=2015-01-12&end_at=2015-01-18&interval=day"

      page.should have_content(log.request_method)
      page.should_not have_selector(".xss-test", :visible => :all)
      page.should have_content(log.request_accept_encoding)
      page.should have_content(log.request_ip_city)
      page.should have_content(log.request_ip_country)
      page.should have_content(log.request_ip_region)
      page.should have_content(log.request_user_agent)
      page.should have_content(log.response_content_type)
      page.should have_content(log.user_email)
    end
  end