Class: FirebaseIdToken::Certificates

Inherits:
Object
  • Object
show all
Defined in:
lib/firebase_id_token/certificates.rb

Overview

Manage download and access of Google's x509 certificates. Keeps certificates on an ActiveSupport cache.

Download & Access Certificates

It describes two ways to download it: Certificates.request and Certificates.request!. The first will only do something when the certificates cache is empty, the second one will always request a new download to Google's API and override the database with the response.

It's important to note that when saving a set of certificates, it will also set a expiration time to match Google's API header expires. After this time went out, the cache will no longer provide those certificates.

To know how many seconds left until the expiration you can use Certificates.ttl.

When comes to accessing it, you can either use Certificates.present? to check if there's any data inside the cache or Certificates.all to obtain an Array of current certificates.

Examples:

.request will only download once

FirebaseIdToken::Certificates.request # Downloads certificates.
FirebaseIdToken::Certificates.request # Won't do anything.
FirebaseIdToken::Certificates.request # Won't do anything either.

.request! will download always

FirebaseIdToken::Certificates.request # Downloads certificates.
FirebaseIdToken::Certificates.request! # Downloads certificates.
FirebaseIdToken::Certificates.request! # Downloads certificates.

Defined Under Namespace

Classes: ActiveSupport, Redis

Constant Summary collapse

URL =

Google's x509 certificates API URL for ID Tokens.

'https://www.googleapis.com/robot/v1/metadata/x509/'\
'securetoken@system.gserviceaccount.com'
'https://www.googleapis.com/identitytoolkit/v3/'\
'relyingparty/publicKeys'
URLS =

Certificates API URL of each source.

{ id_token: URL, session_cookie: SESSION_COOKIE_URL }.freeze
CACHE_KEYS =

Cache key of each source, so both certificate sets live side by side.

{
  id_token: 'certificates',
  session_cookie: 'session_cookie_certificates'
}.freeze

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(source: :id_token) ⇒ Certificates

Sets two instance attributes: :cach_store and :local_certs. Those are respectively a cache instance from FirebaseIdToken::Configuration and the certificates in it.

Raises:

  • (NotImplementedError)


181
182
183
184
# File 'lib/firebase_id_token/certificates.rb', line 181

def initialize(source: :id_token)
  # this should not be called directly. Call a child class
  raise NotImplementedError
end

Instance Attribute Details

#local_certsObject (readonly)

Certificates saved in the cache (JSON String or nil).



34
35
36
# File 'lib/firebase_id_token/certificates.rb', line 34

def local_certs
  @local_certs
end

#sourceObject (readonly)

Certificate source (:id_token or :session_cookie).



37
38
39
# File 'lib/firebase_id_token/certificates.rb', line 37

def source
  @source
end

Class Method Details

.all(source: :id_token) ⇒ Array

Returns an array of hashes, each hash is a single {key => value} pair containing the certificate KID String as key and a OpenSSL::X509::Certificate object of the respective certificate as value. Returns a empty Array when there's no certificates data in the cache.

Examples:

FirebaseIdToken::Certificates.request
certs = FirebaseIdToken::Certificates.all
certs.first #=> {"1d6d01c7[...]" => #<OpenSSL::X509::Certificate[...]}

Returns:

  • (Array)


115
116
117
118
# File 'lib/firebase_id_token/certificates.rb', line 115

def self.all(source: :id_token)
  new_child(source: source).local_certs.map { |kid, cert|
    { kid => OpenSSL::X509::Certificate.new(cert) } }
end

.find(kid, raise_error: false, source: :id_token) ⇒ nil, OpenSSL::X509::Certificate

Returns a OpenSSL::X509::Certificate object of the requested Key ID (KID) if there's one. Returns nil otherwise.

It will raise a Exceptions::NoCertificatesError if the certificates cache is empty.

Examples:

FirebaseIdToken::Certificates.request
cert = FirebaseIdToken::Certificates.find "1d6d01f4w7d54c7[...]"
#=> <OpenSSL::X509::Certificate: subject=#<OpenSSL [...]

Parameters:

  • kid (String)

    Key ID

Returns:

  • (nil, OpenSSL::X509::Certificate)

Raises:



131
132
133
134
135
136
137
138
139
140
141
# File 'lib/firebase_id_token/certificates.rb', line 131

def self.find(kid, raise_error: false, source: :id_token)
  certs = new_child(source: source).local_certs
  raise Exceptions::NoCertificatesError if certs.empty?

  return OpenSSL::X509::Certificate.new certs[kid] if certs[kid]

  return unless raise_error

  raise Exceptions::CertificateNotFound,
    "Unable to find a certificate with `#{kid}`."
end

.find!(kid, source: :id_token) ⇒ OpenSSL::X509::Certificate

Returns a OpenSSL::X509::Certificate object of the requested Key ID (KID) if there's one.

is empty.

Examples:

FirebaseIdToken::Certificates.request
cert = FirebaseIdToken::Certificates.find! "1d6d01f4w7d54c7[...]"
#=> <OpenSSL::X509::Certificate: subject=#<OpenSSL [...]

Parameters:

  • kid (String)

    Key ID

Returns:

  • (OpenSSL::X509::Certificate)

Raises:



157
158
159
# File 'lib/firebase_id_token/certificates.rb', line 157

def self.find!(kid, source: :id_token)
  find(kid, raise_error: true, source: source)
end

.new_child(source: :id_token) ⇒ Object

When called on Certificates itself, picks the store class from the configuration. When called on a subclass, instantiates that subclass.



172
173
174
175
176
# File 'lib/firebase_id_token/certificates.rb', line 172

def self.new_child(source: :id_token)
  return new(source: source) unless self == Certificates

  FirebaseIdToken.configuration.klass.new(source: source)
end

.present?(source: :id_token) ⇒ Boolean

Returns true if there's certificates data in the cache, false otherwise.

Returns:

  • (Boolean)


101
102
103
# File 'lib/firebase_id_token/certificates.rb', line 101

def self.present?(source: :id_token)
  ! new_child(source: source).local_certs.empty?
end

.request(source: :id_token) ⇒ nil, Hash

Calls request! only if there are no certificates in the cache. It will return nil otherwise.

It will raise Exceptions::CertificatesRequestError if the request fails or Exceptions::CertificatesTtlError when Google responds with a low TTL, check out request! for more info.

Parameters:

  • source (Symbol) (defaults to: :id_token)

    :id_token (default) or :session_cookie

Returns:

  • (nil, Hash)

See Also:



67
68
69
# File 'lib/firebase_id_token/certificates.rb', line 67

def self.request(source: :id_token)
  new_child(source: source).request
end

.request!(source: :id_token) ⇒ Hash

Triggers a HTTPS request to Google's x509 certificates API. If it responds with a status 200 OK, saves the request body into the cache and returns it as a Hash.

Otherwise it will raise a Exceptions::CertificatesRequestError.

This is really rare to happen, but Google may respond with a low TTL certificate. This is a SecurityError and will raise a Exceptions::CertificatesTtlError. You are mostly like to never face it.

Parameters:

  • source (Symbol) (defaults to: :id_token)

    :id_token (default) or :session_cookie

Returns:

  • (Hash)


82
83
84
# File 'lib/firebase_id_token/certificates.rb', line 82

def self.request!(source: :id_token)
  new_child(source: source).request!
end

.request_anywayObject

Deprecated.

Use only request! in favor of Ruby conventions.

It will raise a warning. Kept for compatibility.

See Also:



89
90
91
92
93
94
# File 'lib/firebase_id_token/certificates.rb', line 89

def self.request_anyway
  warn 'WARNING: FirebaseIdToken::Certificates.request_anyway is '\
    'deprecated. Use FirebaseIdToken::Certificates.request! instead.'

  new_child.request!
end

.ttl(source: :id_token) ⇒ Fixnum

Returns the current certificates TTL (Time-To-Live) in seconds. Zero meaning no certificates. It's the same as the certificates expiration time, use it to know when to request again.

Returns:

  • (Fixnum)


165
166
167
168
# File 'lib/firebase_id_token/certificates.rb', line 165

def self.ttl(source: :id_token)
  # call a child class based on the configuration
  FirebaseIdToken.configuration.klass.ttl(source: source)
end

Instance Method Details

#requestObject

See Also:



187
188
189
# File 'lib/firebase_id_token/certificates.rb', line 187

def request
  request! if @local_certs.empty?
end

#request!Object

See Also:



192
193
194
195
196
197
198
199
200
# File 'lib/firebase_id_token/certificates.rb', line 192

def request!
  @request = HTTParty.get URLS.fetch(@source)
  code = @request.code
  if code == 200
    save_certificates
  else
    raise Exceptions::CertificatesRequestError.new(code)
  end
end