adding content tag url validation

testplan: check to make sure:
 * nil urls are allowed
 * surrounding whitespace is stripped
 * empty urls are not allowed
 * it parses a wide variety of urls
 * it adds missing schemes
 * it ensures a host exists
 * invalid schemes are rejected

Change-Id: I93e89f8e67d847d488693f7dd4f95e52c898054d
Reviewed-on: https://gerrit.instructure.com/6659
Tested-by: Hudson <hudson@instructure.com>
Reviewed-by: Brian Palmer <brianp@instructure.com>
This commit is contained in:
JT Olds 2011-11-07 10:06:01 -07:00
parent bad37ada4e
commit 3783768a45
6 changed files with 155 additions and 52 deletions

View File

@ -33,10 +33,11 @@ class ContentTag < ActiveRecord::Base
after_save :enforce_unique_in_modules
after_save :touch_context_module
after_save :touch_context_if_learning_outcome
include CustomValidations
validates_as_url :url
attr_accessible :learning_outcome, :context, :tag_type, :mastery_score, :rubric_association, :content_asset_string, :content, :title, :indent, :position, :url, :new_tab
set_policy do
given {|user, session| self.context && self.context.grants_right?(user, session, :manage_content)}
can :delete

View File

@ -39,6 +39,8 @@ class Submission < ActiveRecord::Base
serialize :turnitin_data, Hash
validates_presence_of :assignment_id, :user_id
validates_length_of :body, :maximum => maximum_long_text_length, :allow_nil => true, :allow_blank => true
include CustomValidations
validates_as_url :url
named_scope :with_comments, :include => [:submission_comments ]
named_scope :after, lambda{|date|
@ -208,22 +210,6 @@ class Submission < ActiveRecord::Base
end
end
validates_each(:url, :allow_nil => true) do |record, attr, value|
begin
value = value.strip
raise ArgumentError if value.empty?
uri = URI.parse(value)
unless uri.scheme
value = "http://#{value}"
uri = URI.parse(value)
end
raise ArgumentError unless %w(http https).include?(uri.scheme)
record.url = value
rescue URI::InvalidURIError, ArgumentError
record.errors.add attr, 'is not a valid URL'
end
end
def plaintext_body
self.extend TextHelper
strip_tags((self.body || "").gsub(/\<\s*br\s*\/\>/, "\n<br/>").gsub(/\<\/p\>/, "</p>\n"))

51
lib/custom_validations.rb Normal file
View File

@ -0,0 +1,51 @@
#
# Copyright (C) 2011 Instructure, Inc.
#
# This file is part of Canvas.
#
# Canvas is free software: you can redistribute it and/or modify it under
# the terms of the GNU Affero General Public License as published by the Free
# Software Foundation, version 3 of the License.
#
# Canvas is distributed in the hope that it will be useful, but WITHOUT ANY
# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
# A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
# details.
#
# You should have received a copy of the GNU Affero General Public License along
# with this program. If not, see <http://www.gnu.org/licenses/>.
#
module CustomValidations
module ClassMethods
def validates_as_url(*fields)
fields.each do |field|
validates_each(field.to_sym, :allow_nil => true) do |record, attr, value|
begin
value = value.strip
raise ArgumentError if value.empty?
uri = URI.parse(value)
unless uri.scheme
value = "http://#{value}"
uri = URI.parse(value)
end
raise ArgumentError unless uri.host && %w(http https).include?(uri.scheme.downcase)
record.url = value
rescue URI::InvalidURIError, ArgumentError
record.errors.add attr, 'is not a valid URL'
end
end
end
end
end
def self.included(klass)
if klass < ActiveRecord::Base
klass.send :extend, ClassMethods
end
end
end

View File

@ -0,0 +1,75 @@
#
# Copyright (C) 2011 Instructure, Inc.
#
# This file is part of Canvas.
#
# Canvas is free software: you can redistribute it and/or modify it under
# the terms of the GNU Affero General Public License as published by the Free
# Software Foundation, version 3 of the License.
#
# Canvas is distributed in the hope that it will be useful, but WITHOUT ANY
# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
# A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
# details.
#
# You should have received a copy of the GNU Affero General Public License along
# with this program. If not, see <http://www.gnu.org/licenses/>.
#
require File.expand_path(File.dirname(__FILE__) + '/../spec_helper')
shared_examples_for "url validation tests" do
def test_url_validation(model)
# should add http://
model.url = "example.com"
model.save!
model.errors.length.should == 0
model.url.should == "http://example.com"
# should remove whitespace
model.url = " example.com "
model.save!
model.errors.length.should == 0
model.url.should == "http://example.com"
model.url = " http://www.example.com "
model.save!
model.errors.length.should == 0
model.url.should == "http://www.example.com"
# should not work on invalid urls
["/relativepath",
"",
"invalidscheme://www.example.com",
"not a url"].each do |invalid_url|
model.url = invalid_url
saved = model.save
[model.url, saved].should == [invalid_url, false]
model.errors.length.should == 1
model.errors.first.to_s.should =~ /not a valid URL/
end
# should work on valid urls
["http://www.sub.test.example.tld./thing1?query#hash",
"https://localhost/test/",
"HTTP://localhost/test/",
"http://192.168.24.205:1000",
"http://user:password@host.tld:5555/path/to/thing",
"http://user:password@host.tld/path/to/thing",
"http://" + ("a"*300) + ".com",
"http://www.example.com"].each do |valid_url|
model.url = valid_url
model.save!
model.errors.length.should == 0
model.url.should == valid_url
end
# should support nil urls
model.url = nil
model.save!
model.errors.length.should == 0
model.url.should be_nil
end
end

View File

@ -17,6 +17,7 @@
#
require File.expand_path(File.dirname(__FILE__) + '/../spec_helper.rb')
require File.expand_path(File.dirname(__FILE__) + '/../lib/validates_as_url.rb')
describe ContentTag do
@ -159,4 +160,10 @@ describe ContentTag do
@assignment.reload
@assignment.title.should == 'some assignment (renamed)'
end
it_should_behave_like "url validation tests"
it "should check url validity" do
quiz = course.quizzes.create!
test_url_validation(ContentTag.create!(:content => quiz, :context => @course))
end
end

View File

@ -17,6 +17,7 @@
#
require File.expand_path(File.dirname(__FILE__) + '/../spec_helper.rb')
require File.expand_path(File.dirname(__FILE__) + '/../lib/validates_as_url.rb')
describe Submission do
before(:each) do
@ -38,6 +39,23 @@ describe Submission do
Submission.create!(@valid_attributes)
end
it_should_behave_like "url validation tests"
it "should check url validity" do
test_url_validation(Submission.create!(@valid_attributes))
end
it "should add http:// to the body for long urls, too" do
s = Submission.create!(@valid_attributes)
s.url.should == 'http://www.instructure.com'
long_url = ("a"*300 + ".com")
s.url = long_url
s.save!
s.url.should == "http://#{long_url}"
# make sure it adds the "http://" to the body for long urls, too
s.body.should == "http://#{long_url}"
end
it "should offer the context, if one is available" do
@course = Course.new
@assignment = Assignment.new(:context => @course)
@ -252,41 +270,6 @@ describe Submission do
end
end
context "URL submissions" do
it "should automatically add the 'http://' scheme if none given" do
s = Submission.create!(@valid_attributes)
s.url.should == 'http://www.instructure.com'
long_url = ("a"*300 + ".com")
s.url = long_url
s.save!
s.url.should == "http://#{long_url}"
# make sure it adds the "http://" to the body for long urls, too
s.body.should == "http://#{long_url}"
end
it "should reject invalid urls" do
s = Submission.create(@valid_attributes.merge :url => 'bad url')
s.new_record?.should be_true
s.errors.length.should == 1
s.errors.first.to_s.should match(/not a valid URL/)
end
it "should reject empty urls" do
s = Submission.create(@valid_attributes.merge :url => '')
s.new_record?.should be_true
s.errors.length.should == 1
s.errors.first.to_s.should match(/not a valid URL/)
end
it "should strip leading/trailing whitespace" do
s = Submission.create(@valid_attributes.merge :url => ' http://www.google.com ')
s.new_record?.should be_false
s.errors.should be_empty
s.url.should == 'http://www.google.com'
end
end
context "turnitin" do
before do
@assignment.turnitin_enabled = true