firebase-id-token 0.0.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 (3) hide show
  1. checksums.yaml +7 -0
  2. data/lib/firebase-id-token.rb +170 -0
  3. metadata +128 -0
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: d89290464d362ff7e59e9058263abefe82915661
4
+ data.tar.gz: 0fc67807f0f9d7538a64252594cd8476702aba35
5
+ SHA512:
6
+ metadata.gz: 50ebce4492b3eae49194b537839fa4776a28a81422b95c4a9f501cec52285995d683213f2051d99f0fbf1e39dbfb4bc140bab72d59be1715da5b9ba59b18874e
7
+ data.tar.gz: 3986fed0aec5ccae0cf2b0b483c2424a87f702d8294d2261b63165001a9a60974b508899ec43921917b77b8e0aa5295097b465b6c8f32cf62567439e5caaa837
@@ -0,0 +1,170 @@
1
+ # encoding: utf-8
2
+ # Copyright 2012 Google Inc.
3
+ #
4
+ # Licensed under the Apache License, Version 2.0 (the "License");
5
+ # you may not use this file except in compliance with the License.
6
+ # You may obtain a copy of the License at
7
+ #
8
+ # http://www.apache.org/licenses/LICENSE-2.0
9
+ #
10
+ # Unless required by applicable law or agreed to in writing, software
11
+ # distributed under the License is distributed on an "AS IS" BASIS,
12
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+ # See the License for the specific language governing permissions and
14
+ # limitations under the License.
15
+
16
+ ##
17
+ # Validates strings alleged to be ID Tokens issued by Google; if validation
18
+ # succeeds, returns the decoded ID Token as a hash.
19
+ # It's a good idea to keep an instance of this class around for a long time,
20
+ # because it caches the keys, performs validation statically, and only
21
+ # refreshes from Google when required (once per day by default)
22
+ #
23
+ # @author Tim Bray, adapted from code by Bob Aman
24
+
25
+ require 'multi_json'
26
+ require 'jwt'
27
+ require 'openssl'
28
+ require 'net/http'
29
+
30
+ module FirebaseIDToken
31
+ class CertificateError < StandardError; end
32
+ class ValidationError < StandardError; end
33
+ class ExpiredTokenError < ValidationError; end
34
+ class SignatureError < ValidationError; end
35
+ class InvalidIssuerError < ValidationError; end
36
+ class AudienceMismatchError < ValidationError; end
37
+ class ClientIDMismatchError < ValidationError; end
38
+
39
+ class Validator
40
+
41
+ FIREBASE_CERTS_URI = 'https://www.googleapis.com/robot/v1/metadata/x509/securetoken@system.gserviceaccount.com'
42
+ FIREBASE_CERTS_EXPIRY = 86400 # 1 day
43
+
44
+ FIREBASE_ISSUERS_PREFIX = 'https://securetoken.google.com/'
45
+
46
+ def initialize(keyopts = {})
47
+ if keyopts[:x509_cert]
48
+ @certs_mode = :literal
49
+ @certs = { :_ => keyopts[:x509_cert] }
50
+ # elsif keyopts[:jwk_uri] # TODO
51
+ # @certs_mode = :jwk
52
+ # @certs = {}
53
+ else
54
+ @certs_mode = :old_skool
55
+ @certs = {}
56
+ end
57
+
58
+ @certs_expiry = keyopts.fetch(:expiry, FIREBASE_CERTS_EXPIRY)
59
+ end
60
+
61
+ ##
62
+ # If it validates, returns a hash with the JWT payload from the ID Token.
63
+ # You have to provide an "aud" value, which must match the
64
+ # token's field with that name.
65
+ # Furthermore the tokens field "iss" must be
66
+ # "https://securetoken.google.com/<aud>"
67
+ #
68
+ # If something fails, raises an error
69
+ #
70
+ # @param [String] token
71
+ # The string form of the token
72
+ # @param [String] aud
73
+ # The required audience value
74
+ #
75
+ # @return [Hash] The decoded ID token
76
+ def check(token, aud)
77
+ payload = check_cached_certs(token, aud)
78
+
79
+ unless payload
80
+ # no certs worked, might've expired, refresh
81
+ if refresh_certs
82
+ payload = check_cached_certs(token, aud)
83
+
84
+ unless payload
85
+ raise SignatureError, 'Token not verified as issued by Firebase'
86
+ end
87
+ else
88
+ raise CertificateError, 'Unable to retrieve Firebase public keys'
89
+ end
90
+ end
91
+
92
+ payload
93
+ end
94
+
95
+ private
96
+
97
+ # tries to validate the token against each cached cert.
98
+ # Returns the token payload or raises a ValidationError or
99
+ # nil, which means none of the certs validated.
100
+ def check_cached_certs(token, aud)
101
+ payload = nil
102
+
103
+ # find first public key that validates this token
104
+ @certs.detect do |key, cert|
105
+ begin
106
+ public_key = cert.public_key
107
+ decoded_token = JWT.decode(token, public_key, true, { :algorithm => 'RS256' })
108
+ payload = decoded_token.first
109
+
110
+ payload
111
+ rescue JWT::ExpiredSignature
112
+ raise ExpiredTokenError, 'Token signature is expired'
113
+ rescue JWT::DecodeError => e
114
+ nil # go on, try the next cert
115
+ end
116
+ end
117
+
118
+ if payload
119
+ if !(payload.has_key?('aud') && payload['aud'] == aud)
120
+ raise AudienceMismatchError, 'Token audience mismatch'
121
+ end
122
+ if FIREBASE_ISSUERS_PREFIX + aud != payload['iss']
123
+ raise InvalidIssuerError, 'Token issuer mismatch'
124
+ end
125
+ payload
126
+ else
127
+ nil
128
+ end
129
+ end
130
+
131
+ # returns false if there was a problem
132
+ def refresh_certs
133
+ case @certs_mode
134
+ when :literal
135
+ true # no-op
136
+ when :old_skool
137
+ old_skool_refresh_certs
138
+ end
139
+ end
140
+
141
+ def old_skool_refresh_certs
142
+ return true unless certs_cache_expired?
143
+
144
+ uri = URI(FIREBASE_CERTS_URI)
145
+ get = Net::HTTP::Get.new uri.request_uri
146
+ http = Net::HTTP.new(uri.host, uri.port)
147
+ http.use_ssl = true
148
+ res = http.request(get)
149
+
150
+ if res.is_a?(Net::HTTPSuccess)
151
+ new_certs = Hash[MultiJson.load(res.body).map do |key, cert|
152
+ [key, OpenSSL::X509::Certificate.new(cert)]
153
+ end]
154
+ @certs.merge! new_certs
155
+ @certs_last_refresh = Time.now
156
+ true
157
+ else
158
+ false
159
+ end
160
+ end
161
+
162
+ def certs_cache_expired?
163
+ if defined? @certs_last_refresh
164
+ Time.now > @certs_last_refresh + @certs_expiry
165
+ else
166
+ true
167
+ end
168
+ end
169
+ end
170
+ end
metadata ADDED
@@ -0,0 +1,128 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: firebase-id-token
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.2
5
+ platform: ruby
6
+ authors:
7
+ - Daniel Arnreich
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2017-04-15 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: multi_json
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ">="
18
+ - !ruby/object:Gem::Version
19
+ version: '0'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - ">="
25
+ - !ruby/object:Gem::Version
26
+ version: '0'
27
+ - !ruby/object:Gem::Dependency
28
+ name: jwt
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ">="
32
+ - !ruby/object:Gem::Version
33
+ version: '1'
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - ">="
39
+ - !ruby/object:Gem::Version
40
+ version: '1'
41
+ - !ruby/object:Gem::Dependency
42
+ name: fakeweb
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - ">="
46
+ - !ruby/object:Gem::Version
47
+ version: '0'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - ">="
53
+ - !ruby/object:Gem::Version
54
+ version: '0'
55
+ - !ruby/object:Gem::Dependency
56
+ name: rake
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - ">="
60
+ - !ruby/object:Gem::Version
61
+ version: '0'
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - ">="
67
+ - !ruby/object:Gem::Version
68
+ version: '0'
69
+ - !ruby/object:Gem::Dependency
70
+ name: rspec
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - ">="
74
+ - !ruby/object:Gem::Version
75
+ version: '0'
76
+ type: :development
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - ">="
81
+ - !ruby/object:Gem::Version
82
+ version: '0'
83
+ - !ruby/object:Gem::Dependency
84
+ name: openssl
85
+ requirement: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - ">="
88
+ - !ruby/object:Gem::Version
89
+ version: '0'
90
+ type: :development
91
+ prerelease: false
92
+ version_requirements: !ruby/object:Gem::Requirement
93
+ requirements:
94
+ - - ">="
95
+ - !ruby/object:Gem::Version
96
+ version: '0'
97
+ description: Firebase ID Token utilities; currently just a parser/checker
98
+ email: daniel@arnreich.de
99
+ executables: []
100
+ extensions: []
101
+ extra_rdoc_files: []
102
+ files:
103
+ - lib/firebase-id-token.rb
104
+ homepage: https://github.com/darnreich/firebase-id-token
105
+ licenses:
106
+ - APACHE-2.0
107
+ metadata: {}
108
+ post_install_message:
109
+ rdoc_options: []
110
+ require_paths:
111
+ - lib
112
+ required_ruby_version: !ruby/object:Gem::Requirement
113
+ requirements:
114
+ - - ">="
115
+ - !ruby/object:Gem::Version
116
+ version: '0'
117
+ required_rubygems_version: !ruby/object:Gem::Requirement
118
+ requirements:
119
+ - - ">="
120
+ - !ruby/object:Gem::Version
121
+ version: '0'
122
+ requirements: []
123
+ rubyforge_project:
124
+ rubygems_version: 2.5.1
125
+ signing_key:
126
+ specification_version: 4
127
+ summary: Firebase ID Token utilities
128
+ test_files: []