ey-hmac 0.0.5 → 0.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/lib/ey-hmac/adapter.rb +23 -20
- data/lib/ey-hmac/adapter/faraday.rb +3 -3
- data/lib/ey-hmac/adapter/rack.rb +1 -1
- data/lib/ey-hmac/rack.rb +2 -1
- data/lib/ey-hmac/version.rb +1 -1
- data/spec/faraday_spec.rb +41 -1
- data/spec/rack_spec.rb +40 -3
- metadata +9 -16
    
        checksums.yaml
    ADDED
    
    | @@ -0,0 +1,7 @@ | |
| 1 | 
            +
            ---
         | 
| 2 | 
            +
            SHA1:
         | 
| 3 | 
            +
              metadata.gz: d66a44d088fc9384dc6e9058dad00bb4264dcbb6
         | 
| 4 | 
            +
              data.tar.gz: 0cc4a68974d4f73b2f3081c2d58e59d235456445
         | 
| 5 | 
            +
            SHA512:
         | 
| 6 | 
            +
              metadata.gz: c3dcc933b1577141c16c55e861fc09bedba9ed6558b517a91e2d75f9a8882b3473b45222473029d4931aa2948e96b11d7396ee2e078adf108f6f4957b3952643
         | 
| 7 | 
            +
              data.tar.gz: 2aa4b84886e85a7be13db759a22aa337fa566b0ca1cfd9a5f1372872b583e91d15b89179cd3b7ac2dd9779ec0e74ec29b676b17a5e1c01d40613cd66bc14eea8
         | 
    
        data/lib/ey-hmac/adapter.rb
    CHANGED
    
    | @@ -6,7 +6,7 @@ class Ey::Hmac::Adapter | |
| 6 6 | 
             
              autoload :Rack, "ey-hmac/adapter/rack"
         | 
| 7 7 | 
             
              autoload :Faraday, "ey-hmac/adapter/faraday"
         | 
| 8 8 |  | 
| 9 | 
            -
              attr_reader :request, :options, :authorization_header, :service
         | 
| 9 | 
            +
              attr_reader :request, :options, :authorization_header, :service, :sign_with, :accept_digests
         | 
| 10 10 |  | 
| 11 11 | 
             
              # @param [Object] request signer-specific request implementation
         | 
| 12 12 | 
             
              # @option options [Integer] :version signature version
         | 
| @@ -16,7 +16,9 @@ class Ey::Hmac::Adapter | |
| 16 16 | 
             
                @request, @options = request, options
         | 
| 17 17 |  | 
| 18 18 | 
             
                @authorization_header = options[:authorization_header] || 'Authorization'
         | 
| 19 | 
            -
                @service | 
| 19 | 
            +
                @service              = options[:service] || 'EyHmac'
         | 
| 20 | 
            +
                @sign_with            = options[:sign_with] || :sha256
         | 
| 21 | 
            +
                @accept_digests       = Array(options[:accept_digests] || :sha256)
         | 
| 20 22 | 
             
              end
         | 
| 21 23 |  | 
| 22 24 | 
             
              # In order for the server to correctly authorize the request, the client and server MUST AGREE on this format
         | 
| @@ -28,17 +30,17 @@ class Ey::Hmac::Adapter | |
| 28 30 | 
             
              end
         | 
| 29 31 |  | 
| 30 32 | 
             
              # @param [String] key_secret private HMAC key
         | 
| 33 | 
            +
              # @param [String] signature digest hash function. Defaults to #sign_with
         | 
| 31 34 | 
             
              # @return [String] HMAC signature of {#request}
         | 
| 32 | 
            -
               | 
| 33 | 
            -
             | 
| 34 | 
            -
                Base64.encode64(OpenSSL::HMAC.digest(OpenSSL::Digest::Digest.new('sha1'), key_secret, canonicalize)).strip
         | 
| 35 | 
            +
              def signature(key_secret, digest = self.sign_with)
         | 
| 36 | 
            +
                Base64.encode64(OpenSSL::HMAC.digest(OpenSSL::Digest::Digest.new(digest.to_s), key_secret, canonicalize)).strip
         | 
| 35 37 | 
             
              end
         | 
| 36 38 |  | 
| 37 39 | 
             
              # @param [String] key_id public HMAC key
         | 
| 38 40 | 
             
              # @param [String] key_secret private HMAC key
         | 
| 39 41 | 
             
              # @return [String] HMAC header value of {#request}
         | 
| 40 42 | 
             
              def authorization(key_id, key_secret)
         | 
| 41 | 
            -
                "#{service} #{key_id}:#{signature(key_secret)}"
         | 
| 43 | 
            +
                "#{service} #{key_id}:#{signature(key_secret, sign_with)}"
         | 
| 42 44 | 
             
              end
         | 
| 43 45 |  | 
| 44 46 | 
             
              # @abstract
         | 
| @@ -55,7 +57,6 @@ class Ey::Hmac::Adapter | |
| 55 57 |  | 
| 56 58 | 
             
              # @abstract
         | 
| 57 59 | 
             
              # Digest of body. Default is MD5.
         | 
| 58 | 
            -
              # @todo support explicit digest methods
         | 
| 59 60 | 
             
              # @return [String] digest of body
         | 
| 60 61 | 
             
              def content_digest
         | 
| 61 62 | 
             
                raise NotImplementedError
         | 
| @@ -99,7 +100,7 @@ class Ey::Hmac::Adapter | |
| 99 100 | 
             
              # @yieldparam key_id [String] public HMAC key
         | 
| 100 101 | 
             
              # @return [Boolean] true if block yields matching private key and signature matches, else false
         | 
| 101 102 | 
             
              # @see #authenticated!
         | 
| 102 | 
            -
              def authenticated?(&block)
         | 
| 103 | 
            +
              def authenticated?(options={}, &block)
         | 
| 103 104 | 
             
                authenticated!(&block)
         | 
| 104 105 | 
             
              rescue Ey::Hmac::Error
         | 
| 105 106 | 
             
                false
         | 
| @@ -107,20 +108,22 @@ class Ey::Hmac::Adapter | |
| 107 108 |  | 
| 108 109 | 
             
              # @see Ey::Hmac#authenticate!
         | 
| 109 110 | 
             
              def authenticated!(&block)
         | 
| 110 | 
            -
                 | 
| 111 | 
            -
                  key_id          = authorization_match[1]
         | 
| 112 | 
            -
                  signature_value = authorization_match[2]
         | 
| 113 | 
            -
             | 
| 114 | 
            -
                  if key_secret = block.call(key_id)
         | 
| 115 | 
            -
                    calculated_signature = signature(key_secret)
         | 
| 116 | 
            -
                    if secure_compare(signature_value, calculated_signature)
         | 
| 117 | 
            -
                    else raise(Ey::Hmac::SignatureMismatch, "Calculated siganature #{signature_value} does not match #{calculated_signature} using #{canonicalize.inspect}")
         | 
| 118 | 
            -
                    end
         | 
| 119 | 
            -
                  else raise(Ey::Hmac::MissingSecret, "Failed to find secret matching #{key_id.inspect}")
         | 
| 120 | 
            -
                  end
         | 
| 121 | 
            -
                else
         | 
| 111 | 
            +
                unless authorization_match = AUTHORIZATION_REGEXP.match(authorization_signature)
         | 
| 122 112 | 
             
                  raise(Ey::Hmac::MissingAuthorization, "Failed to parse authorization_signature #{authorization_signature}")
         | 
| 123 113 | 
             
                end
         | 
| 114 | 
            +
             | 
| 115 | 
            +
                key_id          = authorization_match[1]
         | 
| 116 | 
            +
                signature_value = authorization_match[2]
         | 
| 117 | 
            +
             | 
| 118 | 
            +
                unless key_secret = block.call(key_id)
         | 
| 119 | 
            +
                  raise(Ey::Hmac::MissingSecret, "Failed to find secret matching #{key_id.inspect}")
         | 
| 120 | 
            +
                end
         | 
| 121 | 
            +
             | 
| 122 | 
            +
                calculated_signatures = self.accept_digests.map { |ad| signature(key_secret, ad) }
         | 
| 123 | 
            +
             | 
| 124 | 
            +
                unless calculated_signatures.any? { |cs| secure_compare(signature_value, cs) }
         | 
| 125 | 
            +
                  raise(Ey::Hmac::SignatureMismatch, "Calculated siganature #{signature_value} does not match #{calculated_signatures.inspect} using #{canonicalize.inspect}")
         | 
| 126 | 
            +
                end
         | 
| 124 127 | 
             
                true
         | 
| 125 128 | 
             
              end
         | 
| 126 129 | 
             
              alias authenticate! authenticated!
         | 
| @@ -4,11 +4,11 @@ class Ey::Hmac::Adapter::Faraday < Ey::Hmac::Adapter | |
| 4 4 | 
             
              end
         | 
| 5 5 |  | 
| 6 6 | 
             
              def content_type
         | 
| 7 | 
            -
                %w[CONTENT-TYPE CONTENT_TYPE Content-Type Content_Type].inject(nil){|r, | 
| 7 | 
            +
                %w[CONTENT-TYPE CONTENT_TYPE Content-Type Content_Type].inject(nil) { |r,h| r || request[:request_headers][h] }
         | 
| 8 8 | 
             
              end
         | 
| 9 9 |  | 
| 10 10 | 
             
              def content_digest
         | 
| 11 | 
            -
                if existing = %w[CONTENT-DIGEST CONTENT_DIGEST Content-Digest Content_Digest].inject(nil){|r,h| r || request[:request_headers][h]}
         | 
| 11 | 
            +
                if existing = %w[CONTENT-DIGEST CONTENT_DIGEST Content-Digest Content_Digest].inject(nil) { |r,h| r || request[:request_headers][h] }
         | 
| 12 12 | 
             
                  existing
         | 
| 13 13 | 
             
                elsif digestable = body && Digest::MD5.hexdigest(body)
         | 
| 14 14 | 
             
                  request[:request_headers]['Content-Digest'] = digestable
         | 
| @@ -24,7 +24,7 @@ class Ey::Hmac::Adapter::Faraday < Ey::Hmac::Adapter | |
| 24 24 | 
             
              end
         | 
| 25 25 |  | 
| 26 26 | 
             
              def date
         | 
| 27 | 
            -
                existing = %w[DATE Date].inject(nil){|r,h| r || request[h]}
         | 
| 27 | 
            +
                existing = %w[DATE Date].inject(nil) { |r,h| r || request[h] }
         | 
| 28 28 | 
             
                existing || (request[:request_headers]['Date'] = Time.now.httpdate)
         | 
| 29 29 | 
             
              end
         | 
| 30 30 |  | 
    
        data/lib/ey-hmac/adapter/rack.rb
    CHANGED
    
    
    
        data/lib/ey-hmac/rack.rb
    CHANGED
    
    | @@ -9,7 +9,8 @@ class Ey::Hmac::Rack | |
| 9 9 | 
             
              end
         | 
| 10 10 |  | 
| 11 11 | 
             
              def call(env)
         | 
| 12 | 
            -
                Ey::Hmac.sign!(env, key_id, key_secret, {adapter: Ey::Hmac::Adapter::Rack}.merge(options))
         | 
| 12 | 
            +
                Ey::Hmac.sign!(env, key_id, key_secret, { adapter: Ey::Hmac::Adapter::Rack }.merge(options))
         | 
| 13 | 
            +
             | 
| 13 14 | 
             
                @app.call(env)
         | 
| 14 15 | 
             
              end
         | 
| 15 16 | 
             
            end
         | 
    
        data/lib/ey-hmac/version.rb
    CHANGED
    
    
    
        data/spec/faraday_spec.rb
    CHANGED
    
    | @@ -97,7 +97,27 @@ describe "faraday" do | |
| 97 97 | 
             
              end
         | 
| 98 98 |  | 
| 99 99 | 
             
              describe "middleware" do
         | 
| 100 | 
            -
                it "should  | 
| 100 | 
            +
                it "should accept a SHA1 signature" do
         | 
| 101 | 
            +
                  require 'ey-hmac/faraday'
         | 
| 102 | 
            +
                  Bundler.require(:rack)
         | 
| 103 | 
            +
             | 
| 104 | 
            +
                  app = lambda do |env|
         | 
| 105 | 
            +
                    authenticated = Ey::Hmac.authenticated?(env, accept_digests: :sha1, adapter: Ey::Hmac::Adapter::Rack) do |auth_id|
         | 
| 106 | 
            +
                      (auth_id == key_id) && key_secret
         | 
| 107 | 
            +
                    end
         | 
| 108 | 
            +
                    [(authenticated ? 200 : 401), {"Content-Type" => "text/plain"}, []]
         | 
| 109 | 
            +
                  end
         | 
| 110 | 
            +
             | 
| 111 | 
            +
                  request_env = nil
         | 
| 112 | 
            +
                  connection = Faraday.new do |c|
         | 
| 113 | 
            +
                    c.request :hmac, key_id, key_secret, sign_with: :sha1
         | 
| 114 | 
            +
                    c.adapter(:rack, app)
         | 
| 115 | 
            +
                  end
         | 
| 116 | 
            +
             | 
| 117 | 
            +
                  connection.get("/resources").status.should == 200
         | 
| 118 | 
            +
                end
         | 
| 119 | 
            +
             | 
| 120 | 
            +
                it "should accept a SHA256 signature" do # default
         | 
| 101 121 | 
             
                  require 'ey-hmac/faraday'
         | 
| 102 122 | 
             
                  Bundler.require(:rack)
         | 
| 103 123 |  | 
| @@ -117,6 +137,26 @@ describe "faraday" do | |
| 117 137 | 
             
                  connection.get("/resources").status.should == 200
         | 
| 118 138 | 
             
                end
         | 
| 119 139 |  | 
| 140 | 
            +
                it "should accept multiple digest signatures" do # default
         | 
| 141 | 
            +
                  require 'ey-hmac/faraday'
         | 
| 142 | 
            +
                  Bundler.require(:rack)
         | 
| 143 | 
            +
             | 
| 144 | 
            +
                  app = lambda do |env|
         | 
| 145 | 
            +
                    authenticated = Ey::Hmac.authenticated?(env, accept_digests: [:sha1, :sha256], adapter: Ey::Hmac::Adapter::Rack) do |auth_id|
         | 
| 146 | 
            +
                      (auth_id == key_id) && key_secret
         | 
| 147 | 
            +
                    end
         | 
| 148 | 
            +
                    [(authenticated ? 200 : 401), {"Content-Type" => "text/plain"}, []]
         | 
| 149 | 
            +
                  end
         | 
| 150 | 
            +
             | 
| 151 | 
            +
                  request_env = nil
         | 
| 152 | 
            +
                  connection = Faraday.new do |c|
         | 
| 153 | 
            +
                    c.request :hmac, key_id, key_secret
         | 
| 154 | 
            +
                    c.adapter(:rack, app)
         | 
| 155 | 
            +
                  end
         | 
| 156 | 
            +
             | 
| 157 | 
            +
                  connection.get("/resources").status.should == 200
         | 
| 158 | 
            +
                end
         | 
| 159 | 
            +
             | 
| 120 160 | 
             
                it "should sign empty request" do
         | 
| 121 161 | 
             
                  require 'ey-hmac/faraday'
         | 
| 122 162 | 
             
                  Bundler.require(:rack)
         | 
    
        data/spec/rack_spec.rb
    CHANGED
    
    | @@ -3,8 +3,8 @@ require 'spec_helper' | |
| 3 3 | 
             
            describe "rack" do
         | 
| 4 4 | 
             
              before(:all) { Bundler.require(:rack) }
         | 
| 5 5 |  | 
| 6 | 
            -
              let!(:key_id)     { ( | 
| 7 | 
            -
              let!(:key_secret) { ( | 
| 6 | 
            +
              let!(:key_id)     { SecureRandom.hex(8) }
         | 
| 7 | 
            +
              let!(:key_secret) { SecureRandom.hex(16) }
         | 
| 8 8 |  | 
| 9 9 | 
             
              describe "adapter" do
         | 
| 10 10 | 
             
                let(:adapter)     { Ey::Hmac::Adapter::Rack }
         | 
| @@ -89,7 +89,24 @@ describe "rack" do | |
| 89 89 | 
             
              end
         | 
| 90 90 |  | 
| 91 91 | 
             
              describe "middleware" do
         | 
| 92 | 
            -
                it "should  | 
| 92 | 
            +
                it "should accept a SHA1 signature" do
         | 
| 93 | 
            +
                  app = lambda do |env|
         | 
| 94 | 
            +
                    authenticated = Ey::Hmac.authenticated?(env, accept_digests: [:sha1, :sha256], adapter: Ey::Hmac::Adapter::Rack) do |auth_id|
         | 
| 95 | 
            +
                      (auth_id == key_id) && key_secret
         | 
| 96 | 
            +
                    end
         | 
| 97 | 
            +
                    [(authenticated ? 200 : 401), {"Content-Type" => "text/plain"}, []]
         | 
| 98 | 
            +
                  end
         | 
| 99 | 
            +
             | 
| 100 | 
            +
                  _key_id, _key_secret = key_id, key_secret
         | 
| 101 | 
            +
                  client = Rack::Client.new do
         | 
| 102 | 
            +
                    use Ey::Hmac::Rack, _key_id, _key_secret, sign_with: :sha1
         | 
| 103 | 
            +
                    run app
         | 
| 104 | 
            +
                  end
         | 
| 105 | 
            +
             | 
| 106 | 
            +
                  client.get("/resource").status.should == 200
         | 
| 107 | 
            +
                end
         | 
| 108 | 
            +
             | 
| 109 | 
            +
                it "should accept a SHA256 signature" do # default
         | 
| 93 110 | 
             
                  app = lambda do |env|
         | 
| 94 111 | 
             
                    authenticated = Ey::Hmac.authenticated?(env, adapter: Ey::Hmac::Adapter::Rack) do |auth_id|
         | 
| 95 112 | 
             
                      (auth_id == key_id) && key_secret
         | 
| @@ -105,5 +122,25 @@ describe "rack" do | |
| 105 122 |  | 
| 106 123 | 
             
                  client.get("/resource").status.should == 200
         | 
| 107 124 | 
             
                end
         | 
| 125 | 
            +
             | 
| 126 | 
            +
                it "should accept multiple digest signatures" do # default
         | 
| 127 | 
            +
                  require 'ey-hmac/faraday'
         | 
| 128 | 
            +
                  Bundler.require(:rack)
         | 
| 129 | 
            +
             | 
| 130 | 
            +
                  app = lambda do |env|
         | 
| 131 | 
            +
                    authenticated = Ey::Hmac.authenticated?(env, adapter: Ey::Hmac::Adapter::Rack) do |auth_id|
         | 
| 132 | 
            +
                      (auth_id == key_id) && key_secret
         | 
| 133 | 
            +
                    end
         | 
| 134 | 
            +
                    [(authenticated ? 200 : 401), {"Content-Type" => "text/plain"}, []]
         | 
| 135 | 
            +
                  end
         | 
| 136 | 
            +
             | 
| 137 | 
            +
                  request_env = nil
         | 
| 138 | 
            +
                  connection = Faraday.new do |c|
         | 
| 139 | 
            +
                    c.request :hmac, key_id, key_secret, digest: [:sha1, :sha256]
         | 
| 140 | 
            +
                    c.adapter(:rack, app)
         | 
| 141 | 
            +
                  end
         | 
| 142 | 
            +
             | 
| 143 | 
            +
                  connection.get("/resources").status.should == 200
         | 
| 144 | 
            +
                end
         | 
| 108 145 | 
             
              end
         | 
| 109 146 | 
             
            end
         | 
    
        metadata
    CHANGED
    
    | @@ -1,36 +1,32 @@ | |
| 1 1 | 
             
            --- !ruby/object:Gem::Specification
         | 
| 2 2 | 
             
            name: ey-hmac
         | 
| 3 3 | 
             
            version: !ruby/object:Gem::Version
         | 
| 4 | 
            -
              version: 0. | 
| 5 | 
            -
              prerelease: 
         | 
| 4 | 
            +
              version: 0.1.1
         | 
| 6 5 | 
             
            platform: ruby
         | 
| 7 6 | 
             
            authors:
         | 
| 8 7 | 
             
            - Josh Lane & Jason Hansen
         | 
| 9 8 | 
             
            autorequire: 
         | 
| 10 9 | 
             
            bindir: bin
         | 
| 11 10 | 
             
            cert_chain: []
         | 
| 12 | 
            -
            date:  | 
| 11 | 
            +
            date: 2014-02-19 00:00:00.000000000 Z
         | 
| 13 12 | 
             
            dependencies:
         | 
| 14 13 | 
             
            - !ruby/object:Gem::Dependency
         | 
| 15 14 | 
             
              name: rake
         | 
| 16 15 | 
             
              requirement: !ruby/object:Gem::Requirement
         | 
| 17 | 
            -
                none: false
         | 
| 18 16 | 
             
                requirements:
         | 
| 19 | 
            -
                - -  | 
| 17 | 
            +
                - - '>='
         | 
| 20 18 | 
             
                  - !ruby/object:Gem::Version
         | 
| 21 19 | 
             
                    version: '0'
         | 
| 22 20 | 
             
              type: :development
         | 
| 23 21 | 
             
              prerelease: false
         | 
| 24 22 | 
             
              version_requirements: !ruby/object:Gem::Requirement
         | 
| 25 | 
            -
                none: false
         | 
| 26 23 | 
             
                requirements:
         | 
| 27 | 
            -
                - -  | 
| 24 | 
            +
                - - '>='
         | 
| 28 25 | 
             
                  - !ruby/object:Gem::Version
         | 
| 29 26 | 
             
                    version: '0'
         | 
| 30 27 | 
             
            - !ruby/object:Gem::Dependency
         | 
| 31 28 | 
             
              name: bundler
         | 
| 32 29 | 
             
              requirement: !ruby/object:Gem::Requirement
         | 
| 33 | 
            -
                none: false
         | 
| 34 30 | 
             
                requirements:
         | 
| 35 31 | 
             
                - - ~>
         | 
| 36 32 | 
             
                  - !ruby/object:Gem::Version
         | 
| @@ -38,7 +34,6 @@ dependencies: | |
| 38 34 | 
             
              type: :development
         | 
| 39 35 | 
             
              prerelease: false
         | 
| 40 36 | 
             
              version_requirements: !ruby/object:Gem::Requirement
         | 
| 41 | 
            -
                none: false
         | 
| 42 37 | 
             
                requirements:
         | 
| 43 38 | 
             
                - - ~>
         | 
| 44 39 | 
             
                  - !ruby/object:Gem::Version
         | 
| @@ -71,31 +66,29 @@ files: | |
| 71 66 | 
             
            - spec/spec_helper.rb
         | 
| 72 67 | 
             
            homepage: ''
         | 
| 73 68 | 
             
            licenses: []
         | 
| 69 | 
            +
            metadata: {}
         | 
| 74 70 | 
             
            post_install_message: 
         | 
| 75 71 | 
             
            rdoc_options: []
         | 
| 76 72 | 
             
            require_paths:
         | 
| 77 73 | 
             
            - lib
         | 
| 78 74 | 
             
            required_ruby_version: !ruby/object:Gem::Requirement
         | 
| 79 | 
            -
              none: false
         | 
| 80 75 | 
             
              requirements:
         | 
| 81 | 
            -
              - -  | 
| 76 | 
            +
              - - '>='
         | 
| 82 77 | 
             
                - !ruby/object:Gem::Version
         | 
| 83 78 | 
             
                  version: '0'
         | 
| 84 79 | 
             
            required_rubygems_version: !ruby/object:Gem::Requirement
         | 
| 85 | 
            -
              none: false
         | 
| 86 80 | 
             
              requirements:
         | 
| 87 | 
            -
              - -  | 
| 81 | 
            +
              - - '>='
         | 
| 88 82 | 
             
                - !ruby/object:Gem::Version
         | 
| 89 83 | 
             
                  version: '0'
         | 
| 90 84 | 
             
            requirements: []
         | 
| 91 85 | 
             
            rubyforge_project: 
         | 
| 92 | 
            -
            rubygems_version:  | 
| 86 | 
            +
            rubygems_version: 2.0.3
         | 
| 93 87 | 
             
            signing_key: 
         | 
| 94 | 
            -
            specification_version:  | 
| 88 | 
            +
            specification_version: 4
         | 
| 95 89 | 
             
            summary: Lightweight HMAC signing libraries and middleware for Farday and Rack
         | 
| 96 90 | 
             
            test_files:
         | 
| 97 91 | 
             
            - spec/faraday_spec.rb
         | 
| 98 92 | 
             
            - spec/rack_spec.rb
         | 
| 99 93 | 
             
            - spec/shared/authenticated.rb
         | 
| 100 94 | 
             
            - spec/spec_helper.rb
         | 
| 101 | 
            -
            has_rdoc: 
         |