Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP

Loading…

Ignore SSL errors #176

Closed
wants to merge 6 commits into from

6 participants

@rectalogic

Ignore SSL errors, e.g. when testing against a staging server with self-signed certs etc.

@niklasb

I'd very much like to see this as an optional feature, but as I understand it, you disable certificate validation permanently by default, which is to be considered very bad in regards of security (and actually makes the use of SSL completely useless). Have I missed something?

@colinramsay

I agree with @niklasb, this should be optional. I would love this feature though, it's actually stopping me from using capybara-webkit with a current project. Based on another similar discussion (#152) this feature would need an accompanying test to be accepted.

@tobowers

since this is a testing framework... I wouldn't consider it "very bad" or even "making ssl useless." It still allows you to test your production or development environments for acceptance.

@niklasb

@tobowers That's true, for that (main) purpose it's not that much of a security problem. Capybara can however also be used for screen scraping, where I consider certificate validation to be important.
Don't get me wrong, I definitely love this feature, but IMHO ignoring SSL validation errors should neither be the only option, nor should it be the default. Rather, there should be some IgnoreSSLErrors command that sets an internal flag that is checked when an SSL error occurs.
@rectalogic If you want, I can write an integration test for this feature.

@rectalogic rectalogic Make ignoring SSL errors an option.
webkit_server takes an --ignore-ssl-errors argument.
87d5a52
@rectalogic

OK, so I added a new command line option to webkit_server, --ignore-ssl-errors. So by default this won't be specified and we won't ignore errors.

What needs to be done now (and I don't have time to do this) is to expose this through the ruby API, e.g. add :ignore_ssl_errors to the options for Capybara::Driver::Webkit::Browser and it can popen webkit_server with --ignore-ssl-errors or not based on the options.

@niklasb

@rectalogic as soon as I find the time, I can add the option to Capybara::Driver::Webkit::Browser and write a test for your new functionality. I'll let you know when I got this finished so you can merge it.

@niklasb

@rectalogic I pushed some fixes, can you also include them?

@jferris
Owner

I get two test failures after merging this:

Failures:

  1) Capybara::Driver::Webkit::Browser handling of SSL validation errors doesn't accept a self-signed certificate by default
     Failure/Error: @server.shutdown
     Errno::ENOTCONN:
       Socket is not connected
     # ./spec/browser_spec.rb:71:in `shutdown'
     # ./spec/browser_spec.rb:71

  2) Capybara::Driver::Webkit::Browser handling of SSL validation errors accepts a self-signed certificate if configured to do so
     Failure/Error: @server.shutdown
     Errno::ENOTCONN:
       Socket is not connected
     # ./spec/browser_spec.rb:71:in `shutdown'
     # ./spec/browser_spec.rb:71

Finished in 169.71 seconds
614 examples, 2 failures

I used Ruby 1.8.7-p334.

@niklasb

I only tested on Ruby 1.9.1 but will fix it for Ruby 1.8.7

@niklasb

It passes for me on Ruby 1.8.7 if I make a little change in browser.rb (will push it to ignore-ssl-errors). I cannot reproduce the exception you describe on Ruby 1.8.7-p352 [x86_64]. Can you debug this on your machine? The actual specs seem to pass, only the destruction of the temporary SSL-enabled server fails.

@niklasb niklasb referenced this pull request from a commit in niklasb/capybara-webkit
@niklasb niklasb IO.popen doesn't take Array argument in Ruby 1.8.7
refs #176
e196fe8
@niklasb

@jferris: can you try whether the specs pass if you replace shutdown with close?

@niklasb niklasb referenced this pull request from a commit in niklasb/capybara-webkit
@niklasb niklasb fixes wrongly used call to `Socket#shutdown`
refs #176
b14881c
@halogenandtoast

I might be misinterpreting the difference between shutdown and close and I wanted to clarify. #shutdown sends the shutdown(2) signal which will cause the full-duplex connection to shut down on both ends. We're currently running on OSX so not sure why ENOTCONN is being raised. However I don't see why you'd have both a shutdown and a close. If shutdown isn't supported won't close go through each of the file descriptors for the duplex and close them?

@niklasb

@halogenandtoast I noticed that the listening port cannot be listened upon right after simply closing the socket. As I am however sure that the server and its connections will not be used any longer after the tests are run, I thought it might be better to shutdown the socket before closing, so that there are no dangling connections. Most of the information that I got about this is from http://stackoverflow.com/questions/409783/socket-shutdown-vs-socket-close . I am open to suggestions here.

Quote from the thread regarding shutdown:
"However, it does not deallocate the socket and you still need to call close afterward."
Robert S. Barnes

@halogenandtoast

Since OSX doesn't seem to work with the shutdown, I just want to confirm that #shutdown followed by #close doesn't error out with something like IOError: closed stream

@niklasb niklasb referenced this pull request from a commit in niklasb/capybara-webkit
@niklasb niklasb remove call to `Socket#shutdown`
refs #176
e5e2a57
@niklasb

I removed the questionable code. Even if connections are dangling, the specs should always run without problems. Current code lives in branch ignore-ssl-errors

@halogenandtoast

Merged

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Commits on Sep 27, 2011
  1. @rectalogic

    Ignore SSL errors.

    rectalogic authored
  2. @rectalogic

    Fix slot signature.

    rectalogic authored
Commits on Sep 28, 2011
  1. @rectalogic

    Make ignoring SSL errors an option.

    rectalogic authored
    webkit_server takes an --ignore-ssl-errors argument.
  2. @niklasb
Commits on Sep 29, 2011
  1. @niklasb
  2. @niklasb
This page is out of date. Refresh to see the latest.
View
3  lib/capybara/driver/webkit.rb
@@ -17,7 +17,8 @@ def initialize(app, options={})
@options = options
@rack_server = Capybara::Server.new(@app)
@rack_server.boot if Capybara.run_server
- @browser = options[:browser] || Browser.new
+ @browser = options[:browser] || Browser.new(
+ :ignore_ssl_errors => options[:ignore_ssl_errors])
end
def current_url
View
5 lib/capybara/driver/webkit/browser.rb
@@ -12,6 +12,7 @@ def initialize(options = {})
@stdout = options.has_key?(:stdout) ?
options[:stdout] :
$stdout
+ @ignore_ssl_errors = options[:ignore_ssl_errors]
start_server
connect
end
@@ -108,7 +109,9 @@ def fork_server
end
def server_pipe_and_pid(server_path)
- pipe = IO.popen(server_path)
+ cmdline = [server_path]
+ cmdline << "--ignore-ssl-errors" if @ignore_ssl_errors
+ pipe = IO.popen(cmdline)
[pipe, pipe.pid]
end
View
47 spec/browser_spec.rb
@@ -1,10 +1,14 @@
require 'spec_helper'
+require 'self_signed_ssl_cert'
require 'stringio'
require 'capybara/driver/webkit/browser'
describe Capybara::Driver::Webkit::Browser do
let(:browser) { Capybara::Driver::Webkit::Browser.new }
+ let(:browser_ignore_ssl_err) {
+ Capybara::Driver::Webkit::Browser.new(:ignore_ssl_errors => true)
+ }
describe '#server_port' do
subject { browser.server_port }
@@ -32,4 +36,47 @@
io.string.should == "hello world\n"
end
+ context 'handling of SSL validation errors' do
+ before do
+ # set up minimal HTTPS server
+ @host = "127.0.0.1"
+ @server = TCPServer.new(@host, 0)
+ @port = @server.addr[1]
+
+ # set up SSL layer
+ ssl_serv = OpenSSL::SSL::SSLServer.new(@server, $openssl_self_signed_ctx)
+
+ @server_thread = Thread.new(ssl_serv) do |serv|
+ while conn = serv.accept do
+ # read request
+ request = []
+ until (line = conn.readline.strip).empty?
+ request << line
+ end
+
+ # write response
+ html = "<html><body>D'oh!</body></html>"
+ conn.write "HTTP/1.1 200 OK\r\n"
+ conn.write "Content-Type:text/html\r\n"
+ conn.write "Content-Length: %i\r\n" % html.size
+ conn.write "\r\n"
+ conn.write html
+ conn.close
+ end
+ end
+ end
+
+ after do
+ @server_thread.kill
+ @server.shutdown
+ end
+
+ it "doesn't accept a self-signed certificate by default" do
+ lambda { browser.visit "https://#{@host}:#{@port}/" }.should raise_error
+ end
+
+ it 'accepts a self-signed certificate if configured to do so' do
+ browser_ignore_ssl_err.visit "https://#{@host}:#{@port}/"
+ end
+ end
end
View
42 spec/self_signed_ssl_cert.rb
@@ -0,0 +1,42 @@
+require 'openssl'
+
+pem = <<-PEM_ENCODED
+-----BEGIN PRIVATE KEY-----
+MIICdgIBADANBgkqhkiG9w0BAQEFAASCAmAwggJcAgEAAoGBAKW+grT6YW3gv79y
+P9JkQDtm3cDSUhAhd/TEyRBt/pSKz3pNSygsleBJl2d7g8k0fteec95a7YnYRKGH
+XhIpUOvl/3uaV2NVipqxwB+Z+0M+7HegxL3e4unaRFy9kf9/UXJzmuA9BTMLrm/w
+IoW17f+dz7BIFZhCvRurkrmvzraNAgMBAAECgYBv4uB3bYJx20N16Jk+3OAjeXh/
+Hzu4me9Rc7pLdgVinyYaaK0wrJBsfSFRASdgnyh1RAjx9K3f3PfPlwMg/XUbA6Yd
+YOYlMnBUwCJX09TH8RFFCzJzbBylpk/sTF1geICln2O2BloT2cM24PlEPvyz1xLa
+XvxCOnJJfgNU1K6kvQJBANcEVyOMJ9RBfI8gj1o7S70J9yJRI4jvqxuvcOxJBCi6
+CDatkh/getHswsE3sLj25VhrNsi6UQcN8Bjm8Yjt8BsCQQDFVe0uCwobseprMOuW
+dPU4+saN1cFnIT5Gp0iwYRPinjUlkh6H/AuUtUulKFXVmxk1KElpp1E3bxpCDgCp
+oe53AkArO1Mt8Ys8kSIzQO+xy8RRsQRAoSHM8atsuJyy1YeBjM4D+GguApuPQ9Rw
+tvrQZcv9OCleuJ98FKBW0XB1AKpLAkEAmOR3bJofDdAuWTjA/4TEzo32MsRwIZBv
+KNzJg+bjOkzrzp1EzIVrD5/b6S20O1j9EeOR5as+UN3jEVS6DLQrBwJAe5OTrDiQ
+vKtUaAwquC4f4Ia05KwJw+vFrPVaOqgc4QLdxRwx4PfV/Uw3OOqMolpPAvpUi9JI
+LAwIaTtCvo18OQ==
+-----END PRIVATE KEY-----
+-----BEGIN CERTIFICATE-----
+MIICgDCCAemgAwIBAgIJANWcyeZB2ql1MA0GCSqGSIb3DQEBBQUAMFkxCzAJBgNV
+BAYTAkFVMRMwEQYDVQQIDApTb21lLVN0YXRlMSEwHwYDVQQKDBhJbnRlcm5ldCBX
+aWRnaXRzIFB0eSBMdGQxEjAQBgNVBAMMCWxvY2FsaG9zdDAeFw0xMTA5MjgyMDIx
+MTFaFw0xMjA5MjcyMDIxMTFaMFkxCzAJBgNVBAYTAkFVMRMwEQYDVQQIDApTb21l
+LVN0YXRlMSEwHwYDVQQKDBhJbnRlcm5ldCBXaWRnaXRzIFB0eSBMdGQxEjAQBgNV
+BAMMCWxvY2FsaG9zdDCBnzANBgkqhkiG9w0BAQEFAAOBjQAwgYkCgYEApb6CtPph
+beC/v3I/0mRAO2bdwNJSECF39MTJEG3+lIrPek1LKCyV4EmXZ3uDyTR+155z3lrt
+idhEoYdeEilQ6+X/e5pXY1WKmrHAH5n7Qz7sd6DEvd7i6dpEXL2R/39RcnOa4D0F
+Mwuub/AihbXt/53PsEgVmEK9G6uSua/Oto0CAwEAAaNQME4wHQYDVR0OBBYEFBAm
+x19zpY8M8FEcMXB4WQeUhqIiMB8GA1UdIwQYMBaAFBAmx19zpY8M8FEcMXB4WQeU
+hqIiMAwGA1UdEwQFMAMBAf8wDQYJKoZIhvcNAQEFBQADgYEAcznCusWS5Ws77IUl
+b87vdfCPVphICyfGHGWhHp3BZ3WLOphauAMdOYIiJGtPExyWr4DYpzbvx0+Ljg7G
+2FNaI0QRXqbR5ccpvwm6KELmU0XDhykNaXiXSdnvIanr3z/hZ5e03mXAywo+nGQj
+UYTVCb6g/uHVNzXq1NgHGuqogKY=
+-----END CERTIFICATE-----
+PEM_ENCODED
+
+$openssl_self_signed_key = key = OpenSSL::PKey::RSA.new(pem)
+$openssl_self_signed_cert = cert = OpenSSL::X509::Certificate.new(pem)
+$openssl_self_signed_ctx = ssl_ctx = OpenSSL::SSL::SSLContext.new
+ssl_ctx.key = key
+ssl_ctx.cert = cert
View
3  src/Server.cpp
@@ -4,9 +4,10 @@
#include <QTcpServer>
-Server::Server(QObject *parent) : QObject(parent) {
+Server::Server(QObject *parent, bool ignoreSslErrors) : QObject(parent) {
m_tcp_server = new QTcpServer(this);
m_page = new WebPage(this);
+ m_page->setIgnoreSslErrors(ignoreSslErrors);
}
bool Server::start() {
View
2  src/Server.h
@@ -7,7 +7,7 @@ class Server : public QObject {
Q_OBJECT
public:
- Server(QObject *parent = 0);
+ Server(QObject *parent, bool ignoreSslErrors);
bool start();
quint16 server_port() const;
View
15 src/WebPage.cpp
@@ -25,6 +25,7 @@ void WebPage::setCustomNetworkAccessManager() {
NetworkAccessManager *manager = new NetworkAccessManager();
this->setNetworkAccessManager(manager);
connect(manager, SIGNAL(finished(QNetworkReply *)), this, SLOT(replyFinished(QNetworkReply *)));
+ connect(manager, SIGNAL(sslErrors(QNetworkReply *, QList<QSslError>)), this, SLOT(ignoreSslErrors(QNetworkReply *, QList<QSslError>)));
}
void WebPage::loadJavascript() {
@@ -190,6 +191,20 @@ void WebPage::replyFinished(QNetworkReply *reply) {
}
}
+void WebPage::ignoreSslErrors(QNetworkReply *reply, const QList<QSslError> &errors) {
+ if (m_ignoreSslErrors)
+ reply->ignoreSslErrors(errors);
+}
+
+void WebPage::setIgnoreSslErrors(bool ignore) {
+ m_ignoreSslErrors = ignore;
+}
+
+bool WebPage::ignoreSslErrors() {
+ return m_ignoreSslErrors;
+}
+
+
int WebPage::getLastStatus() {
return m_lastStatus;
}
View
4 src/WebPage.h
@@ -15,6 +15,8 @@ class WebPage : public QWebPage {
void setCustomNetworkAccessManager();
bool render(const QString &fileName);
virtual bool extension (Extension extension, const ExtensionOption *option=0, ExtensionReturn *output=0);
+ void setIgnoreSslErrors(bool ignore);
+ bool ignoreSslErrors();
public slots:
bool shouldInterruptJavaScript();
@@ -25,6 +27,7 @@ class WebPage : public QWebPage {
QString pageHeaders();
void frameCreated(QWebFrame *);
void replyFinished(QNetworkReply *reply);
+ void ignoreSslErrors(QNetworkReply *reply, const QList<QSslError> &);
void handleUnsupportedContent(QNetworkReply *reply);
signals:
@@ -46,5 +49,6 @@ class WebPage : public QWebPage {
void setUserStylesheet();
int m_lastStatus;
QString m_pageHeaders;
+ bool m_ignoreSslErrors;
};
View
5 src/main.cpp
@@ -18,7 +18,10 @@ int main(int argc, char **argv) {
app.setOrganizationName("thoughtbot, inc");
app.setOrganizationDomain("thoughtbot.com");
- Server server;
+ QStringList args = app.arguments();
+ bool ignoreSslErrors = args.contains("--ignore-ssl-errors");
+
+ Server server(0, ignoreSslErrors);
if (server.start()) {
std::cout << "Capybara-webkit server started, listening on port: " << server.server_port() << std::endl;
Something went wrong with that request. Please try again.