Skip to content

Commit

Permalink
Merge pull request #100 from bbc-test/feature/client_certs
Browse files Browse the repository at this point in the history
Client SSL certs
  • Loading branch information
apotonick committed Aug 28, 2014
2 parents ee6b063 + 0727684 commit 90e22dd
Show file tree
Hide file tree
Showing 7 changed files with 228 additions and 60 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -2,3 +2,4 @@ pkg/*
*.gem
.bundle
Gemfile*.lock
.idea
14 changes: 14 additions & 0 deletions README.markdown
Original file line number Diff line number Diff line change
Expand Up @@ -597,8 +597,22 @@ The HTTP verbs allow you to specify credentials for HTTP basic auth.

```ruby
song.get(uri: "http://localhost:4567/songs/1", basic_auth: ["username", "secret_password"])
```

### Client SSL certificates

(Only currently supported with Net:Http)

```ruby
song.get(uri: "http://localhost:4567/songs/1", pem_file: "/path/to/client/cert.pem", ssl_verify_mode: OpenSSL::SSL::VERIFY_PEER)
```

Note: ssl_verify_mode is not required and will default to ```OpenSSL::SSL::VERIFY_PEER)```



### Request customization

All verbs yield the request object before the request is sent, allowing to modify it. It is a `Net::HTTP::Request` instance (unless you use Faraday).
Expand Down
5 changes: 4 additions & 1 deletion lib/roar.rb
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@

module Roar
# Your code goes here...
def self.root
File.expand_path '../..', __FILE__
end
end
60 changes: 1 addition & 59 deletions lib/roar/representer/transport/net_http.rb
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
require "net/http"
require "uri"
require 'roar/representer/transport/net_http/request'

module Roar
module Representer
Expand All @@ -10,63 +9,6 @@ module Transport
#
# The following options are available:
class NetHTTP
class Request # TODO: implement me.
def initialize(options)
@uri = parse_uri(options[:uri]) # TODO: add :uri.
@as = options[:as]
@body = options[:body]
@options = options

@http = Net::HTTP.new(uri.host, uri.port)
end

def call(what)
@req = what.new(uri.request_uri)

# if options[:ssl]
# uri.port = Net::HTTP.https_default_port()
# end
https!
basic_auth!

req.content_type = as
req["accept"] = as # TODO: test me. # DISCUSS: if Accept is not set, rails treats this request as as "text/html".
req.body = body if body

yield req if block_given?

http.request(req).tap do |res|
raise UnauthorizedError if res.is_a?(Net::HTTPUnauthorized) # FIXME: make this better. # DISCUSS: abstract all that crap here?
end
end

def get
call(Net::HTTP::Get)
end

private
attr_reader :uri, :as, :body, :options, :req, :http

def parse_uri(url)
uri = URI(url)
raise "Incorrect URL `#{url}`. Maybe you forgot http://?" if uri.instance_of?(URI::Generic)
uri
end

def https!
return unless uri.scheme == 'https'

@http.use_ssl = true
@http.verify_mode = OpenSSL::SSL::VERIFY_NONE
end

def basic_auth!
return unless options[:basic_auth]

@req.basic_auth(*options[:basic_auth])
end
end


def get_uri(*options, &block)
call(Net::HTTP::Get, *options, &block)
Expand Down
75 changes: 75 additions & 0 deletions lib/roar/representer/transport/net_http/request.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
require "net/http"
require "uri"
require "openssl"

module Roar
module Representer
module Transport
class NetHTTP
class Request # TODO: implement me.
def initialize(options)
@uri = parse_uri(options[:uri]) # TODO: add :uri.
@as = options[:as]
@body = options[:body]
@options = options

@http = Net::HTTP.new(uri.host, uri.port)
unless options[:pem_file].nil?
pem = File.read(options[:pem_file])
@http.use_ssl = true
@http.cert = OpenSSL::X509::Certificate.new(pem)
@http.key = OpenSSL::PKey::RSA.new(pem)
@http.verify_mode = options[:ssl_verify_mode].nil? ? OpenSSL::SSL::VERIFY_PEER : options[:ssl_verify_mode]
end
end

def call(what)
@req = what.new(uri.request_uri)

# if options[:ssl]
# uri.port = Net::HTTP.https_default_port()
# end
https!
basic_auth!

req.content_type = as
req["accept"] = as # TODO: test me. # DISCUSS: if Accept is not set, rails treats this request as as "text/html".
req.body = body if body

yield req if block_given?

http.request(req).tap do |res|
raise UnauthorizedError if res.is_a?(Net::HTTPUnauthorized) # FIXME: make this better. # DISCUSS: abstract all that crap here?
end
end

def get
call(Net::HTTP::Get)
end

private
attr_reader :uri, :as, :body, :options, :req, :http

def parse_uri(url)
uri = URI(url)
raise "Incorrect URL `#{url}`. Maybe you forgot http://?" if uri.instance_of?(URI::Generic)
uri
end

def https!
return unless uri.scheme == 'https'

@http.use_ssl = true
@http.verify_mode = OpenSSL::SSL::VERIFY_NONE
end

def basic_auth!
return unless options[:basic_auth]

@req.basic_auth(*options[:basic_auth])
end
end
end
end
end
end
31 changes: 31 additions & 0 deletions test/fixtures/sample.pem
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
-----BEGIN CERTIFICATE-----
MIICcTCCAdqgAwIBAgIJAMvA30EY1rKtMA0GCSqGSIb3DQEBBQUAMDAxCzAJBgNV
BAYTAlVTMQ0wCwYDVQQKEwRLZWFzMRIwEAYDVQQDEwlsb2NhbGhvc3QwHhcNMDgw
OTA0MTU1MTU5WhcNMTgwOTAyMTU1MTU5WjAwMQswCQYDVQQGEwJVUzENMAsGA1UE
ChMES2VhczESMBAGA1UEAxMJbG9jYWxob3N0MIGfMA0GCSqGSIb3DQEBAQUAA4GN
ADCBiQKBgQDK17rB/KVaK8MVjiEkvA4ZncOOIC3nStZ/erXM+qwkghPM4Tfr2FTU
iTgwwdLdu/ht74oWnppttfaTQ+sVz2rFXnPgfqKTGoJTwWFiuNuZhSRDVssGVnL/
RatZW6wns8UNf+W4hUe6/vGQP6obNTe2T4R+t2hXP51OkOy4BMcq0QIDAQABo4GS
MIGPMB0GA1UdDgQWBBQDIsX7HoSqbxKrCawi64MkXRmtmzBgBgNVHSMEWTBXgBQD
IsX7HoSqbxKrCawi64MkXRmtm6E0pDIwMDELMAkGA1UEBhMCVVMxDTALBgNVBAoT
BEtlYXMxEjAQBgNVBAMTCWxvY2FsaG9zdIIJAMvA30EY1rKtMAwGA1UdEwQFMAMB
Af8wDQYJKoZIhvcNAQEFBQADgYEAW5UBM7EIMpARzQwpQ8N1gyTR/VqJ9fSm4MIw
Y5m90HRgsDcXVbhn0rRfcC8o4EtGDvCjqsFYXy/ImF9tjEiuaysxbqepl+XMszPE
1kO50quWsV1FLSdcJX6t/ofJYOxiQkqPvg9t/ovTnEZ+w4NfPo+0MJgudjJoD2+w
5UTsKtU=
-----END CERTIFICATE-----
-----BEGIN RSA PRIVATE KEY-----
MIICXQIBAAKBgQDK17rB/KVaK8MVjiEkvA4ZncOOIC3nStZ/erXM+qwkghPM4Tfr
2FTUiTgwwdLdu/ht74oWnppttfaTQ+sVz2rFXnPgfqKTGoJTwWFiuNuZhSRDVssG
VnL/RatZW6wns8UNf+W4hUe6/vGQP6obNTe2T4R+t2hXP51OkOy4BMcq0QIDAQAB
AoGAHcDJDx1M784NfoLrj6TZ+J3wik9kDFIo5mgMdLWsPGqsFthOSJTh1I8QI+66
THX++bkyKyE2i7MuKOnEeN2Ezo2jAThF7XoWhm6/+pSXhSqmL1jKr/1CZRaR9jv0
cCVJc3mTuAGH+yFVeGpWNvDaCmOUlD5M48xTROJXteDQ0TECQQDuDM9pmQdqkGIp
dvbIviS8donYn0kJ0TKS14pMtb/C63lcld513rHS43ru3FRY9baR/q5vV9vW5RhH
S7w4cYvVAkEA2iNLsFEAkY88oZJYbdyybeKxZdReyes1/zPe4RYzRdbDHRNAa+zk
mZIZDI820E0Y+DeoT+q3nXkXiiOS/iRNDQJBAKdAvOH2sO1AcJetjArS/cCkkIlw
sMKDB0OAyRzIfekXxPc2HU03oD0Jsy/sAh9W1GWTST/VvRIpeHtvTNljfdkCQF5T
UuBcNoW6zXoEYU6oV1Oi6hjhW1eu6PuAv4jPY754XoiNEZdZqYQqo8BFkWtDW1/C
GXrtQRbMDPzD40UYB2UCQQCmJpJp+u2lHj7zuZikHIHQBNyXyoGnzgNs6XUj1Bs6
Y4vjue8w6RkRLZ1YGP+xqsngVqb9IRygyLDpEgwEnOT4
-----END RSA PRIVATE KEY-----
102 changes: 102 additions & 0 deletions test/ssl_client_certs_test.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
require 'test_helper'

require "roar"
require 'roar/representer/transport/net_http/request'
require 'roar/representer/transport/net_http'


class SslClientCertsTest < MiniTest::Spec

describe Roar::Representer::Transport::NetHTTP do

describe "instance methods" do

let(:url) { "http://www.bbc.co.uk" }
let(:as) { "application/xml" }

let(:transport) { Roar::Representer::Transport::NetHTTP.new }

describe "options passed to the request object (private #call method)" do

let(:options) { { uri: url, as: as, pem_file: "test/fixtures/sample.pem", ssl_verify_mode: "ssl_verify_mode" } }

describe "option handling" do

it "provides all options to the Request object" do

request_mock = MiniTest::Mock.new
request_mock.expect :call, nil, [Net::HTTP::Get]

options_assertions = lambda { |argument_options|
assert_equal argument_options, options
request_mock
}

Roar::Representer::Transport::NetHTTP::Request.stub :new, options_assertions do
transport.get_uri(options)
end
end
end
end
end
end

describe Roar::Representer::Transport::NetHTTP::Request do

describe "instance methods" do
describe "#initialize" do

describe "client certificate configuration" do

let(:uri) { URI.parse("http://www.bbc.co.uk") }
let(:options) { { uri: uri, pem_file: pem_file, ssl_verify_mode: ssl_verify_mode } }

let(:request) { Roar::Representer::Transport::NetHTTP::Request.new(options) }
let(:net_http_instance) { request.instance_variable_get(:@http) }
let(:ssl_verify_mode) { nil }

describe "when a pem file has been provided with the request options" do

let(:pem_file) { File.expand_path("test/fixtures/sample.pem", Roar.root) }

let(:pem) { File.read(pem_file) }
let(:cert) { OpenSSL::X509::Certificate.new(pem) }
let(:key) { OpenSSL::PKey::RSA.new(pem) }

it "sets the client to use an ssl connection" do
assert(net_http_instance.use_ssl?, "Net::HTTP connection uses ssl")
end

it "sets the client cert" do
assert_equal(net_http_instance.cert.to_s, cert.to_s)
end
it "sets the client key" do
assert_equal(net_http_instance.key.to_s, key.to_s)
end
it "defaults the verify mode to OpenSSL::SSL::VERIFY_PEER when no option provided" do
assert_equal(net_http_instance.verify_mode, OpenSSL::SSL::VERIFY_PEER)
end

describe "verify mode is specified" do

let(:ssl_verify_mode) { OpenSSL::SSL::VERIFY_NONE }

it "sets the client verify mode to that option provided" do
assert_equal(net_http_instance.verify_mode, ssl_verify_mode)
end
end
end

describe "when a pem file has not been provided in the request options" do

let(:pem_file) { nil }

it "does not set the client to use an ssl connection" do
refute(net_http_instance.use_ssl?, "Net::HTTP connection do not use SSL")
end
end
end
end
end
end
end

0 comments on commit 90e22dd

Please sign in to comment.