rails-auth 2.1.2 → 2.2.2

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.
Files changed (58) hide show
  1. checksums.yaml +5 -5
  2. data/.rubocop.yml +14 -1
  3. data/.travis.yml +8 -5
  4. data/BUG-BOUNTY.md +3 -3
  5. data/CHANGES.md +45 -2
  6. data/CONTRIBUTING.md +11 -10
  7. data/Gemfile +6 -5
  8. data/Guardfile +2 -0
  9. data/Rakefile +3 -1
  10. data/lib/rails/auth/acl.rb +4 -0
  11. data/lib/rails/auth/acl/matchers/allow_all.rb +3 -0
  12. data/lib/rails/auth/acl/middleware.rb +3 -0
  13. data/lib/rails/auth/acl/resource.rb +7 -5
  14. data/lib/rails/auth/config_builder.rb +5 -1
  15. data/lib/rails/auth/controller_methods.rb +4 -0
  16. data/lib/rails/auth/credentials.rb +3 -1
  17. data/lib/rails/auth/credentials/injector_middleware.rb +6 -2
  18. data/lib/rails/auth/env.rb +4 -3
  19. data/lib/rails/auth/error_page/debug_middleware.rb +1 -1
  20. data/lib/rails/auth/error_page/middleware.rb +3 -0
  21. data/lib/rails/auth/exceptions.rb +2 -0
  22. data/lib/rails/auth/helpers.rb +3 -1
  23. data/lib/rails/auth/installed_constraint.rb +2 -0
  24. data/lib/rails/auth/monitor/middleware.rb +2 -0
  25. data/lib/rails/auth/rack.rb +1 -0
  26. data/lib/rails/auth/rspec.rb +2 -0
  27. data/lib/rails/auth/rspec/helper_methods.rb +6 -5
  28. data/lib/rails/auth/rspec/matchers/acl_matchers.rb +4 -2
  29. data/lib/rails/auth/version.rb +1 -1
  30. data/lib/rails/auth/x509/certificate.rb +35 -5
  31. data/lib/rails/auth/x509/filter/java.rb +4 -12
  32. data/lib/rails/auth/x509/filter/pem.rb +10 -1
  33. data/lib/rails/auth/x509/matcher.rb +2 -0
  34. data/lib/rails/auth/x509/middleware.rb +11 -9
  35. data/lib/rails/auth/x509/subject_alt_name_extension.rb +29 -0
  36. data/rails-auth.gemspec +5 -4
  37. data/spec/rails/auth/acl/matchers/allow_all_spec.rb +2 -0
  38. data/spec/rails/auth/acl/middleware_spec.rb +2 -0
  39. data/spec/rails/auth/acl/resource_spec.rb +2 -0
  40. data/spec/rails/auth/acl_spec.rb +2 -0
  41. data/spec/rails/auth/controller_methods_spec.rb +2 -0
  42. data/spec/rails/auth/credentials/injector_middleware_spec.rb +15 -0
  43. data/spec/rails/auth/credentials_spec.rb +2 -0
  44. data/spec/rails/auth/env_spec.rb +2 -0
  45. data/spec/rails/auth/error_page/debug_middleware_spec.rb +2 -0
  46. data/spec/rails/auth/error_page/middleware_spec.rb +2 -0
  47. data/spec/rails/auth/monitor/middleware_spec.rb +2 -0
  48. data/spec/rails/auth/rspec/helper_methods_spec.rb +2 -0
  49. data/spec/rails/auth/rspec/matchers/acl_matchers_spec.rb +12 -1
  50. data/spec/rails/auth/x509/certificate_spec.rb +103 -20
  51. data/spec/rails/auth/x509/matcher_spec.rb +2 -0
  52. data/spec/rails/auth/x509/middleware_spec.rb +13 -2
  53. data/spec/rails/auth/x509/subject_alt_name_extension_spec.rb +39 -0
  54. data/spec/rails/auth_spec.rb +2 -0
  55. data/spec/spec_helper.rb +5 -3
  56. data/spec/support/claims_matcher.rb +2 -0
  57. data/spec/support/create_certs.rb +57 -2
  58. metadata +14 -7
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Rails
2
4
  module Auth
3
5
  # Rails constraint to make sure the ACLs have been installed
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Rails
2
4
  module Auth
3
5
  module Monitor
@@ -27,3 +27,4 @@ require "rails/auth/x509/filter/pem"
27
27
  require "rails/auth/x509/filter/java" if defined?(JRUBY_VERSION)
28
28
  require "rails/auth/x509/matcher"
29
29
  require "rails/auth/x509/middleware"
30
+ require "rails/auth/x509/subject_alt_name_extension"
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require "rails/auth/rspec/helper_methods"
2
4
  require "rails/auth/rspec/matchers/acl_matchers"
3
5
 
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Rails
2
4
  module Auth
3
5
  module RSpec
@@ -12,6 +14,7 @@ module Rails
12
14
  # NOTE: Credentials will be *cleared* after the block. Nesting is not allowed.
13
15
  def with_credentials(credentials = {})
14
16
  raise TypeError, "expected Hash of credentials, got #{credentials.class}" unless credentials.is_a?(Hash)
17
+
15
18
  test_credentials.clear
16
19
 
17
20
  credentials.each do |type, value|
@@ -24,8 +27,8 @@ module Rails
24
27
  # Creates an Rails::Auth::X509::Certificate instance double
25
28
  def x509_certificate(cn: nil, ou: nil)
26
29
  subject = ""
27
- subject << "CN=#{cn}" if cn
28
- subject << "OU=#{ou}" if ou
30
+ subject += "CN=#{cn}" if cn
31
+ subject += "OU=#{ou}" if ou
29
32
 
30
33
  instance_double(Rails::Auth::X509::Certificate, subject, cn: cn, ou: ou).tap do |certificate|
31
34
  allow(certificate).to receive(:[]) do |key|
@@ -47,9 +50,7 @@ module Rails
47
50
  path = self.class.description
48
51
 
49
52
  # Warn if methods are improperly used
50
- unless path.chars[0] == "/"
51
- raise ArgumentError, "expected #{path} to start with '/'"
52
- end
53
+ raise ArgumentError, "expected #{path} to start with '/'" unless path.chars[0] == "/"
53
54
 
54
55
  env = {
55
56
  "REQUEST_METHOD" => method,
@@ -1,12 +1,14 @@
1
+ # frozen_string_literal: true
2
+
1
3
  RSpec::Matchers.define(:permit) do |env|
2
4
  description do
3
5
  method = env["REQUEST_METHOD"]
4
6
  credentials = Rails::Auth.credentials(env)
5
7
  message = "allow #{method}s by "
6
8
 
7
- return message << "unauthenticated clients" if credentials.count.zero?
9
+ return message + "unauthenticated clients" if credentials.count.zero?
8
10
 
9
- message << credentials.values.map(&:inspect).join(", ")
11
+ message + credentials.values.map(&:inspect).join(", ")
10
12
  end
11
13
 
12
14
  match { |acl| acl.match(env) }
@@ -3,6 +3,6 @@
3
3
  module Rails
4
4
  # Pluggable authentication and authorization for Rack/Rails
5
5
  module Auth
6
- VERSION = "2.1.2".freeze
6
+ VERSION = "2.2.2"
7
7
  end
8
8
  end
@@ -18,7 +18,8 @@ module Rails
18
18
  @certificate.subject.to_a.each do |name, data, _type|
19
19
  @subject[name.freeze] = data.freeze
20
20
  end
21
-
21
+ @subject_alt_names = SubjectAltNameExtension.new(certificate)
22
+ @subject_alt_names.freeze
22
23
  @subject.freeze
23
24
  end
24
25
 
@@ -27,23 +28,52 @@ module Rails
27
28
  end
28
29
 
29
30
  def cn
30
- @subject["CN".freeze]
31
+ @subject["CN"]
31
32
  end
32
33
  alias common_name cn
33
34
 
35
+ def dns_names
36
+ @subject_alt_names.dns_names
37
+ end
38
+
39
+ def ips
40
+ @subject_alt_names.ips
41
+ end
42
+
34
43
  def ou
35
- @subject["OU".freeze]
44
+ @subject["OU"]
36
45
  end
37
46
  alias organizational_unit ou
38
47
 
48
+ def uris
49
+ @subject_alt_names.uris
50
+ end
51
+
52
+ # According to the SPIFFE standard only one SPIFFE ID can exist in the URI
53
+ # SAN:
54
+ # (https://github.com/spiffe/spiffe/blob/master/standards/X509-SVID.md#2-spiffe-id)
55
+ #
56
+ # @return [String, nil] string containing SPIFFE ID if one is present
57
+ # in the certificate
58
+ def spiffe_id
59
+ uris.detect { |uri| uri.start_with?("spiffe://") }
60
+ end
61
+
39
62
  # Generates inspectable attributes for debugging
40
63
  #
41
64
  # @return [Hash] hash containing parts of the certificate subject (cn, ou)
65
+ # and subject alternative name extension (uris, dns_names) as well
66
+ # as SPIFFE ID (spiffe_id), which is just a convenience since those
67
+ # are already included in the uris
42
68
  def attributes
43
69
  {
44
70
  cn: cn,
45
- ou: ou
46
- }
71
+ dns_names: dns_names,
72
+ ips: ips,
73
+ ou: ou,
74
+ spiffe_id: spiffe_id,
75
+ uris: uris
76
+ }.reject { |_, v| v.nil? || v.empty? }
47
77
  end
48
78
 
49
79
  # Compare ourself to another object by ensuring that it has the same type
@@ -1,23 +1,15 @@
1
- require "java"
2
- require "stringio"
1
+ # frozen_string_literal: true
3
2
 
4
3
  module Rails
5
4
  module Auth
6
5
  module X509
7
6
  module Filter
8
- # Extract OpenSSL::X509::Certificates from Java's sun.security.x509.X509CertImpl
7
+ # Extract OpenSSL::X509::Certificates from java.security.cert.Certificate
9
8
  class Java
10
9
  def call(certs)
11
- return unless certs
12
- OpenSSL::X509::Certificate.new(extract_der(certs[0])).freeze
13
- end
14
-
15
- private
10
+ return if certs.nil? || certs.empty?
16
11
 
17
- def extract_der(cert)
18
- stringio = StringIO.new
19
- cert.derEncode(stringio.to_outputstream)
20
- stringio.string
12
+ OpenSSL::X509::Certificate.new(certs[0].get_encoded).freeze
21
13
  end
22
14
  end
23
15
  end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Rails
2
4
  module Auth
3
5
  module X509
@@ -5,7 +7,14 @@ module Rails
5
7
  # Extract OpenSSL::X509::Certificates from Privacy Enhanced Mail (PEM) certificates
6
8
  class Pem
7
9
  def call(pem)
8
- OpenSSL::X509::Certificate.new(pem.delete("\t")).freeze
10
+ # Normalize the whitespace in the certificate to the exact format
11
+ # certificates are normally formatted in otherwise parsing with fail
12
+ # with a 'nested asn1 error'. split(" ") handles sequential whitespace
13
+ # characters like \t, \n, and space.
14
+ OpenSSL::X509::Certificate.new(pem.split(" ").instance_eval do
15
+ [[self[0], self[1]].join(" "), self[2...-2], [self[-2], self[-1]].join(" ")]
16
+ .flatten.join("\n")
17
+ end).freeze
9
18
  end
10
19
  end
11
20
  end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Rails
2
4
  module Auth
3
5
  module X509
@@ -12,20 +12,21 @@ module Rails
12
12
  # Create a new X.509 Middleware object
13
13
  #
14
14
  # @param [Object] app next app in the Rack middleware chain
15
- # @param [Hash] cert_filters maps Rack environment names to cert extractors
16
15
  # @param [String] ca_file path to the CA bundle to verify client certs with
17
- # @param [OpenSSL::X509::Store] truststore (optional) provide your own truststore (for e.g. CRLs)
16
+ # @param [Hash] cert_filters maps Rack environment names to cert extractors
17
+ # @param [Logger] logger place to log verification successes & failures
18
18
  # @param [Boolean] require_cert causes middleware to raise if certs are unverified
19
+ # @param [OpenSSL::X509::Store] truststore (optional) provide your own truststore (for e.g. CRLs)
19
20
  #
20
21
  # @return [Rails::Auth::X509::Middleware] new X509 middleware instance
21
- def initialize(app, cert_filters: {}, ca_file: nil, truststore: nil, require_cert: false, logger: nil)
22
- raise ArgumentError, "no ca_file given" unless ca_file
22
+ def initialize(app, ca_file: nil, cert_filters: {}, logger: nil, require_cert: false, truststore: nil)
23
+ raise ArgumentError, "no ca_file or truststore given" unless ca_file || truststore
23
24
 
24
25
  @app = app
26
+ @cert_filters = cert_filters
25
27
  @logger = logger
26
- @truststore = truststore || OpenSSL::X509::Store.new.add_file(ca_file)
27
28
  @require_cert = require_cert
28
- @cert_filters = cert_filters
29
+ @truststore = truststore || OpenSSL::X509::Store.new.add_file(ca_file)
29
30
 
30
31
  @cert_filters.each do |key, filter|
31
32
  next unless filter.is_a?(Symbol)
@@ -40,7 +41,7 @@ module Rails
40
41
 
41
42
  def call(env)
42
43
  credential = extract_credential(env)
43
- Rails::Auth.add_credential(env, "x509".freeze, credential.freeze) if credential
44
+ Rails::Auth.add_credential(env, "x509", credential.freeze) if credential
44
45
 
45
46
  @app.call(env)
46
47
  end
@@ -62,6 +63,7 @@ module Rails
62
63
  end
63
64
 
64
65
  raise CertificateVerifyFailed, "no client certificate in request" if @require_cert
66
+
65
67
  nil
66
68
  end
67
69
 
@@ -72,8 +74,8 @@ module Rails
72
74
  end
73
75
 
74
76
  filter.call(raw_cert)
75
- rescue => ex
76
- @logger.debug("rails-auth: Certificate error: #{ex.class}: #{ex.message}") if @logger
77
+ rescue StandardError => e
78
+ @logger.debug("rails-auth: Certificate error: #{e.class}: #{e.message}") if @logger
77
79
  nil
78
80
  end
79
81
 
@@ -0,0 +1,29 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Rails
4
+ module Auth
5
+ module X509
6
+ # Provides convenience methods for subjectAltName extension of X.509 certificates
7
+ class SubjectAltNameExtension
8
+ attr_reader :dns_names, :ips, :uris
9
+
10
+ DNS_REGEX = /^DNS:/i.freeze
11
+ IP_REGEX = /^IP( Address)?:/i.freeze
12
+ URI_REGEX = /^URI:/i.freeze
13
+
14
+ def initialize(certificate)
15
+ unless certificate.is_a?(OpenSSL::X509::Certificate)
16
+ raise TypeError, "expecting OpenSSL::X509::Certificate, got #{certificate.class}"
17
+ end
18
+
19
+ extension = certificate.extensions.detect { |ext| ext.oid == "subjectAltName" }
20
+ values = (extension&.value&.split(",") || []).map(&:strip)
21
+
22
+ @dns_names = values.grep(DNS_REGEX) { |v| v.sub(DNS_REGEX, "") }.freeze
23
+ @ips = values.grep(IP_REGEX) { |v| v.sub(IP_REGEX, "") }.freeze
24
+ @uris = values.grep(URI_REGEX) { |v| v.sub(URI_REGEX, "") }.freeze
25
+ end
26
+ end
27
+ end
28
+ end
29
+ end
@@ -1,5 +1,6 @@
1
- # coding: utf-8
2
- lib = File.expand_path("../lib", __FILE__)
1
+ # frozen_string_literal: true
2
+
3
+ lib = File.expand_path("lib", __dir__)
3
4
  $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
5
  require "rails/auth/version"
5
6
 
@@ -25,10 +26,10 @@ Gem::Specification.new do |spec|
25
26
  spec.bindir = "exe"
26
27
  spec.require_paths = ["lib"]
27
28
 
28
- spec.required_ruby_version = ">= 2.0.0"
29
+ spec.required_ruby_version = ">= 2.3.0"
29
30
 
30
31
  spec.add_runtime_dependency "rack"
31
32
 
32
- spec.add_development_dependency "bundler", "~> 1.10"
33
+ spec.add_development_dependency "bundler", ">= 1.10", "< 3"
33
34
  spec.add_development_dependency "rake", "~> 10.0"
34
35
  end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  RSpec.describe Rails::Auth::ACL::Matchers::AllowAll do
2
4
  let(:matcher) { described_class.new(enabled) }
3
5
  let(:example_env) { env_for(:get, "/") }
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require "logger"
2
4
 
3
5
  RSpec.describe Rails::Auth::ACL::Middleware do
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  RSpec.describe Rails::Auth::ACL::Resource do
2
4
  let(:example_method) { "GET" }
3
5
  let(:another_method) { "POST" }
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  RSpec.describe Rails::Auth::ACL do
2
4
  let(:example_config) { fixture_path("example_acl.yml").read }
3
5
 
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  RSpec.describe Rails::Auth::ControllerMethods do
2
4
  let(:controller_class) do
3
5
  Class.new do
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  RSpec.describe Rails::Auth::Credentials::InjectorMiddleware do
2
4
  let(:request) { Rack::MockRequest.env_for("https://www.example.com") }
3
5
  let(:app) { ->(env) { [200, env, "Hello, world!"] } }
@@ -8,4 +10,17 @@ RSpec.describe Rails::Auth::Credentials::InjectorMiddleware do
8
10
  _response, env = middleware.call(request)
9
11
  expect(env[Rails::Auth::Env::CREDENTIALS_ENV_KEY]).to eq credentials
10
12
  end
13
+
14
+ context "with a proc for credentials" do
15
+ let(:credentials_proc) { instance_double(Proc) }
16
+ let(:middleware) { described_class.new(app, credentials_proc) }
17
+
18
+ it "overrides rails-auth credentials in the rack environment" do
19
+ expect(credentials_proc).to receive(:call).with(request).and_return(credentials)
20
+
21
+ _response, env = middleware.call(request)
22
+
23
+ expect(env[Rails::Auth::Env::CREDENTIALS_ENV_KEY]).to eq credentials
24
+ end
25
+ end
11
26
  end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  RSpec.describe Rails::Auth::Credentials do
2
4
  let(:rack_env) { Rack::MockRequest.env_for("https://www.example.com") }
3
5
 
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  RSpec.describe Rails::Auth::Env do
2
4
  let(:rack_env) { Rack::MockRequest.env_for("https://www.example.com") }
3
5
  let(:example_authority) { "some-authority" }
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  RSpec.describe Rails::Auth::ErrorPage::DebugMiddleware do
2
4
  let(:request) { Rack::MockRequest.env_for("https://www.example.com") }
3
5
 
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  RSpec.describe Rails::Auth::ErrorPage::Middleware do
2
4
  let(:request) { Rack::MockRequest.env_for("https://www.example.com") }
3
5
  let(:error_page) { "<h1> Unauthorized!!! </h1>" }
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  RSpec.describe Rails::Auth::Monitor::Middleware do
2
4
  let(:request) { Rack::MockRequest.env_for("https://www.example.com") }
3
5
 
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require "ostruct"
2
4
 
3
5
  RSpec.describe Rails::Auth::RSpec::HelperMethods, acl_spec: true do
@@ -1,6 +1,8 @@
1
+ # frozen_string_literal: true
2
+
1
3
  RSpec.describe "RSpec ACL matchers", acl_spec: true do
2
- let(:example_certificate) { x509_certificate_hash(ou: "ponycopter") }
3
4
  let(:another_certificate) { x509_certificate_hash(ou: "derpderp") }
5
+ let(:example_certificate) { x509_certificate_hash(ou: "ponycopter") }
4
6
 
5
7
  subject do
6
8
  Rails::Auth::ACL.from_yaml(
@@ -16,5 +18,14 @@ RSpec.describe "RSpec ACL matchers", acl_spec: true do
16
18
  it { is_expected.to permit get_request(credentials: example_certificate) }
17
19
  it { is_expected.not_to permit get_request(credentials: another_certificate) }
18
20
  it { is_expected.not_to permit get_request }
21
+
22
+ it "has the correct description" do
23
+ expect(permit(get_request(credentials: example_certificate)).description)
24
+ .to eq('allow GETs by #<InstanceDouble(Rails::Auth::X509::Certificate) "OU=ponycopter">')
25
+ expect(permit(get_request(credentials: another_certificate)).description)
26
+ .to eq('allow GETs by #<InstanceDouble(Rails::Auth::X509::Certificate) "OU=derpderp">')
27
+ expect(permit(get_request).description)
28
+ .to eq("allow GETs by unauthenticated clients")
29
+ end
19
30
  end
20
31
  end
@@ -1,38 +1,121 @@
1
+ # frozen_string_literal: true
2
+
1
3
  RSpec.describe Rails::Auth::X509::Certificate do
2
4
  let(:example_cert) { OpenSSL::X509::Certificate.new(cert_path("valid.crt").read) }
5
+ let(:example_cert_with_extension) { OpenSSL::X509::Certificate.new(cert_path("valid_with_ext.crt").read) }
3
6
  let(:example_certificate) { described_class.new(example_cert) }
7
+ let(:example_certificate_with_extension) { described_class.new(example_cert_with_extension) }
4
8
 
5
9
  let(:example_cn) { "127.0.0.1" }
10
+ let(:example_dns_names) { %w[example.com exemplar.com somethingelse.com] }
11
+ let(:example_ips) { %w[0.0.0.0 127.0.0.1 192.168.1.1] }
6
12
  let(:example_ou) { "ponycopter" }
13
+ let(:example_spiffe) { "spiffe://example.com/exemplar" }
14
+ let(:example_uris) { [example_spiffe, "https://www.example.com/page1", "https://www.example.com/page2"] }
15
+
16
+ describe "without extensions" do
17
+ describe "#[]" do
18
+ it "allows access to subject components via strings" do
19
+ expect(example_certificate["CN"]).to eq example_cn
20
+ expect(example_certificate["OU"]).to eq example_ou
21
+ end
7
22
 
8
- describe "#[]" do
9
- it "allows access to subject components via strings" do
10
- expect(example_certificate["CN"]).to eq example_cn
11
- expect(example_certificate["OU"]).to eq example_ou
23
+ it "allows access to subject components via symbols" do
24
+ expect(example_certificate[:cn]).to eq example_cn
25
+ expect(example_certificate[:ou]).to eq example_ou
26
+ end
12
27
  end
13
28
 
14
- it "allows access to subject components via symbols" do
15
- expect(example_certificate[:cn]).to eq example_cn
16
- expect(example_certificate[:ou]).to eq example_ou
29
+ it "knows its #cn" do
30
+ expect(example_certificate.cn).to eq example_cn
17
31
  end
18
- end
19
32
 
20
- it "knows its #cn" do
21
- expect(example_certificate.cn).to eq example_cn
22
- end
33
+ it "has no #dns_names" do
34
+ expect(example_certificate.dns_names).to be_empty
35
+ end
23
36
 
24
- it "knows its #ou" do
25
- expect(example_certificate.ou).to eq example_ou
26
- end
37
+ it "has no #ips" do
38
+ expect(example_certificate.ips).to be_empty
39
+ end
40
+
41
+ it "knows its #ou" do
42
+ expect(example_certificate.ou).to eq example_ou
43
+ end
27
44
 
28
- it "knows its attributes" do
29
- expect(example_certificate.attributes).to eq(cn: example_cn, ou: example_ou)
45
+ it "has no #uris" do
46
+ expect(example_certificate.uris).to be_empty
47
+ end
48
+
49
+ it "has no #spiffe_id" do
50
+ expect(example_certificate.spiffe_id).to be_nil
51
+ end
52
+
53
+ it "knows its attributes" do
54
+ expect(example_certificate.attributes).to eq(cn: example_cn, ou: example_ou)
55
+ end
56
+
57
+ it "compares certificate objects by comparing their certificates" do
58
+ second_cert = OpenSSL::X509::Certificate.new(cert_path("valid.crt").read)
59
+ second_certificate = described_class.new(second_cert)
60
+
61
+ expect(example_certificate).to be_eql second_certificate
62
+ end
30
63
  end
31
64
 
32
- it "compares certificate objects by comparing their certificates" do
33
- second_cert = OpenSSL::X509::Certificate.new(cert_path("valid.crt").read)
34
- second_certificate = described_class.new(second_cert)
65
+ describe "with extensions" do
66
+ describe "#[]" do
67
+ it "allows access to subject components via strings" do
68
+ expect(example_certificate_with_extension["CN"]).to eq example_cn
69
+ expect(example_certificate_with_extension["OU"]).to eq example_ou
70
+ end
35
71
 
36
- expect(example_certificate).to be_eql second_certificate
72
+ it "allows access to subject components via symbols" do
73
+ expect(example_certificate_with_extension[:cn]).to eq example_cn
74
+ expect(example_certificate_with_extension[:ou]).to eq example_ou
75
+ end
76
+ end
77
+
78
+ it "knows its #cn" do
79
+ expect(example_certificate_with_extension.cn).to eq example_cn
80
+ end
81
+
82
+ it "knows its #dns_names" do
83
+ expect(example_certificate_with_extension.dns_names).to eq example_dns_names
84
+ end
85
+
86
+ it "knows its #ips" do
87
+ expect(example_certificate_with_extension.ips).to eq example_ips
88
+ end
89
+
90
+ it "knows its #ou" do
91
+ expect(example_certificate_with_extension.ou).to eq example_ou
92
+ end
93
+
94
+ it "knows its #spiffe_id" do
95
+ expect(example_certificate_with_extension.spiffe_id).to eq example_spiffe
96
+ end
97
+
98
+ it "knows its #uris" do
99
+ expect(example_certificate_with_extension.uris).to eq example_uris
100
+ end
101
+
102
+ it "knows its attributes" do
103
+ expected_attrs = {
104
+ cn: example_cn,
105
+ dns_names: example_dns_names,
106
+ ips: example_ips,
107
+ ou: example_ou,
108
+ spiffe_id: example_spiffe,
109
+ uris: example_uris
110
+ }
111
+ expect(example_certificate_with_extension.attributes).to eq(expected_attrs)
112
+ end
113
+
114
+ it "compares certificate objects by comparing their certificates" do
115
+ second_cert = OpenSSL::X509::Certificate.new(cert_path("valid_with_ext.crt").read)
116
+ second_certificate = described_class.new(second_cert)
117
+
118
+ expect(example_certificate_with_extension).to be_eql second_certificate
119
+ end
37
120
  end
38
121
  end