firebase-id-token 0.0.2

Sign up to get free protection for your applications and to get access to all the features.
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: []