firebase-ruby 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.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 693b989e038daf52ac6f06a2a978614a3d0fee2b
4
+ data.tar.gz: 4c88d12f7167b180dc8cc5b7707a700e1e803f63
5
+ SHA512:
6
+ metadata.gz: 8a8319b29c0b88f9640d86604fae62bec3a323a243f40caf4a0c32b56db2f5d6914945ed0d3ed80367a5a751cf98565367679cbe55118ffe3680f54c63687b4f
7
+ data.tar.gz: 75677e5b82c80ebf6302abf25a995ba7126c3af2f467db9a59f6605b288bb1434fd9e325b1cd68e61ce66a64acda1fed70e879586a2c2f95f46d86a0615ee93d
data/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2017 Ken J.
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,2 @@
1
+ # firebase-ruby-private
2
+ Pure simple Ruby based Firebase REST library
data/bin/fbrb ADDED
@@ -0,0 +1,51 @@
1
+ #!/usr/bin/env ruby
2
+ require 'firebase-ruby/trollop'
3
+ require 'firebase-ruby'
4
+
5
+
6
+ opts = Trollop::options do
7
+ banner "fbrb [options] <URL>"
8
+ opt :data, 'HTTP POST data', type: :string
9
+ opt :id, 'Project ID', type: :string
10
+ opt :key, 'JSON file with private key', type: :string
11
+ opt :log, 'Log file', type: :string
12
+ opt :path, 'Path', type: :string
13
+ opt :request, 'Specify request command to use', type: :string, short: 'X', default: 'get'
14
+ opt :verbose, 'Verbose mode'
15
+ end
16
+
17
+ if opts[:log_given]
18
+ Firebase.logger = Logger.new(opts[:log])
19
+ Firebase.logger.level = Logger::WARN
20
+ end
21
+
22
+ if opts[:verbose]
23
+ Firebase.logger = Logger.new(STDOUT) unless opts[:log_given]
24
+ Firebase.logger.level = Logger::DEBUG
25
+ end
26
+ log = Firebase.logger
27
+
28
+ log.debug("Command line arguments: #{opts}")
29
+
30
+ path = opts[:path]
31
+ path ||= ARGV.shift
32
+
33
+ Trollop::die :path, "is missing" if path.nil?
34
+
35
+ db = Firebase::Database.new()
36
+ db.set_auth_with_keyfile(opts[:key])
37
+
38
+ method = opts[:request].downcase.to_sym
39
+
40
+ case method
41
+ when :get, :delete
42
+ data = db.public_send(method, path)
43
+ when :put, :patch, :post
44
+ if opts[:data_given]
45
+ data = db.public_send(method, path, opts[:data])
46
+ else
47
+ Trollop::die :data, "is missing"
48
+ end
49
+ end
50
+
51
+ puts data if data
@@ -0,0 +1,18 @@
1
+ $LOAD_PATH.unshift(File.expand_path('../lib', __FILE__))
2
+ require 'firebase-ruby/version'
3
+
4
+
5
+ Gem::Specification.new do |s|
6
+ s.name = 'firebase-ruby'
7
+ s.version = Firebase::Version
8
+ s.authors = ['Ken J.']
9
+ s.email = ['kenjij@gmail.com']
10
+ s.summary = %q{Pure simple Ruby based Firebase REST library}
11
+ s.description = %q{Firebase REST library written in pure Ruby without external dependancy.}
12
+ s.homepage = 'https://github.com/kenjij/firebase-ruby'
13
+ s.license = 'MIT'
14
+
15
+ s.files = `git ls-files`.split($/)
16
+ s.executables = s.files.grep(%r{^bin/}) { |f| File.basename(f) }
17
+ s.require_paths = ['lib']
18
+ end
@@ -0,0 +1,4 @@
1
+ require 'firebase-ruby/logger'
2
+ require 'firebase-ruby/database'
3
+ require 'firebase-ruby/auth'
4
+ require 'firebase-ruby/http'
@@ -0,0 +1,85 @@
1
+ require 'jwt'
2
+
3
+ module Firebase
4
+
5
+ class Auth
6
+
7
+ GOOGLE_JWT_SCOPE = 'https://www.googleapis.com/auth/firebase.database https://www.googleapis.com/auth/userinfo.email'
8
+ GOOGLE_JWT_AUD = 'https://www.googleapis.com/oauth2/v4/token'
9
+ GOOGLE_ALGORITHM = 'RS256'
10
+ GOOGLE_GRANT_TYPE = 'urn:ietf:params:oauth:grant-type:jwt-bearer'
11
+ GOOGLE_TOKEN_URL = 'https://www.googleapis.com/oauth2/v4/token'
12
+
13
+ attr_reader :project_id
14
+ attr_reader :client_email
15
+ attr_reader :access_token
16
+ attr_reader :expires
17
+
18
+ def initialize(path)
19
+ load_privatekeyfile(path)
20
+ end
21
+
22
+ # Return a valid access token; it will retrieve a new token if necessary
23
+ def valid_token
24
+ return access_token if access_token && !expiring?
25
+ return access_token if request_access_token
26
+ return nil
27
+ end
28
+
29
+ # If token is expiring within a minute
30
+ def expiring?
31
+ return true if expires - Time.now < 60
32
+ return false
33
+ end
34
+
35
+ # If token has already expired
36
+ def expired?
37
+ return true if expires - Time.now <= 0
38
+ return false
39
+ end
40
+
41
+ private
42
+
43
+ # @param path [String] JSON file with private key
44
+ def load_privatekeyfile(path)
45
+ raise ArgumentError, 'private key file path missing' unless path
46
+ Firebase.logger.debug("Loading private key file: #{path}")
47
+ cred = JSON.parse(IO.read(path), {symbolize_names: true})
48
+ @private_key = cred[:private_key]
49
+ @project_id = cred[:project_id]
50
+ @client_email = cred[:client_email]
51
+ Firebase.logger.info('Auth.load_privatekeyfile done.')
52
+ end
53
+
54
+ # Request new token from Google
55
+ def request_access_token
56
+ Firebase.logger.info('Requesting access token to Google')
57
+ res = HTTP.post_form(GOOGLE_TOKEN_URL, jwt)
58
+ Firebase.logger.debug("HTTP response code: #{res[:code]}")
59
+ if res.class == Hash && res[:code] == 200
60
+ data = JSON.parse(res[:body], {symbolize_names: true})
61
+ @access_token = data[:access_token]
62
+ @expires = Time.now + data[:expires_in]
63
+ return true
64
+ end
65
+ return false
66
+ end
67
+
68
+ # Generate JWT claim
69
+ def jwt
70
+ pkey = OpenSSL::PKey::RSA.new(@private_key)
71
+ now_ts = Time.now.to_i
72
+ payload = {
73
+ iss: client_email,
74
+ scope: GOOGLE_JWT_SCOPE,
75
+ aud: GOOGLE_JWT_AUD,
76
+ iat: now_ts,
77
+ exp: now_ts + 60
78
+ }
79
+ jwt = JWT.encode payload, pkey, GOOGLE_ALGORITHM
80
+ return {grant_type: GOOGLE_GRANT_TYPE, assertion: jwt}
81
+ end
82
+
83
+ end
84
+
85
+ end
@@ -0,0 +1,86 @@
1
+ module Firebase
2
+
3
+ class Database
4
+
5
+ FIREBASE_URL_TEMPLATE = 'https://%s.firebaseio.com/'
6
+
7
+ attr_accessor :auth, :print, :shallow
8
+
9
+ def initialize()
10
+ end
11
+
12
+ def set_auth_with_keyfile(filepath)
13
+ @auth = Auth.new(filepath)
14
+ end
15
+
16
+ def project_id=(id)
17
+ @project_id = id
18
+ end
19
+
20
+ def project_id
21
+ return @project_id if @project_id
22
+ return auth.project_id if auth
23
+ return nil
24
+ end
25
+
26
+ def get(path)
27
+ return operate(__method__, path)
28
+ end
29
+
30
+ def put(path, data)
31
+ return operate(__method__, path, data)
32
+ end
33
+
34
+ def patch(path, data)
35
+ return operate(__method__, path, data)
36
+ end
37
+
38
+ def post(path, data)
39
+ return operate(__method__, path, data)
40
+ end
41
+
42
+ def delete(path)
43
+ return operate(__method__, path)
44
+ end
45
+
46
+ private
47
+
48
+ def operate(method, path, data = nil)
49
+ case method
50
+ when :get, :delete
51
+ res_data = http.public_send(method, path: format_path(path))
52
+ when :put, :patch, :post
53
+ data = JSON.fast_generate(data) if data.class == Hash
54
+ res_data = http.public_send(method, path: format_path(path), body: data)
55
+ end
56
+ return handle_response_data(res_data)
57
+ end
58
+
59
+ def http
60
+ unless @http
61
+ url = FIREBASE_URL_TEMPLATE % project_id
62
+ headers = {
63
+ 'Authorization' => "Bearer #{auth.valid_token}",
64
+ 'Content-Type' => 'application/json'
65
+ }
66
+ @http = HTTP.new(url, headers)
67
+ end
68
+ return @http
69
+ end
70
+
71
+ def format_path(path)
72
+ path = '/' + path unless path.start_with?('/')
73
+ return path + '.json'
74
+ end
75
+
76
+ def handle_response_data(data)
77
+ if data[:code] != 200
78
+ Firebase.logger.error("HTTP response error: #{data[:code]}\n#{data[:message]}")
79
+ return nil
80
+ end
81
+ return JSON.parse(data[:body], {symbolize_names: true})
82
+ end
83
+
84
+ end
85
+
86
+ end
@@ -0,0 +1,172 @@
1
+ require 'net/http'
2
+ require 'openssl'
3
+
4
+
5
+ module Firebase
6
+
7
+ class HTTP
8
+
9
+ METHOD_HTTP_CLASS = {
10
+ get: Net::HTTP::Get,
11
+ put: Net::HTTP::Put,
12
+ patch: Net::HTTP::Patch,
13
+ post: Net::HTTP::Post,
14
+ delete: Net::HTTP::Delete
15
+ }
16
+
17
+ def self.get(url, params)
18
+ h = HTTP.new(url)
19
+ data = h.get(params: params)
20
+ h.close
21
+ return data
22
+ end
23
+
24
+ def self.post_form(url, params)
25
+ h = HTTP.new(url)
26
+ data = h.post(params: params)
27
+ h.close
28
+ return data
29
+ end
30
+
31
+ attr_reader :init_uri, :http
32
+ attr_accessor :headers
33
+
34
+ def initialize(url, hdrs = nil)
35
+ @init_uri = URI(url)
36
+ raise ArgumentError, 'Invalid URL' unless @init_uri.class <= URI::HTTP
37
+ @http = Net::HTTP.new(init_uri.host, init_uri.port)
38
+ http.use_ssl = init_uri.scheme == 'https'
39
+ http.verify_mode = OpenSSL::SSL::VERIFY_PEER
40
+ self.headers = hdrs
41
+ end
42
+
43
+ def get(path: nil, params: nil, query: nil)
44
+ return operate(__method__, path: path, params: params, query: query)
45
+ end
46
+
47
+ def post(path: nil, params: nil, body: nil, query: nil)
48
+ return operate(__method__, path: path, params: params, body: body, query: query)
49
+ end
50
+
51
+ def put(path: nil, params: nil, body: nil, query: nil)
52
+ return operate(__method__, path: path, params: params, body: body, query: query)
53
+ end
54
+
55
+ def patch(path: nil, params: nil, body: nil, query: nil)
56
+ return operate(__method__, path: path, params: params, body: body, query: query)
57
+ end
58
+
59
+ def delete(path: nil, params: nil, query: nil)
60
+ return operate(__method__, path: path, params: params, query: query)
61
+ end
62
+
63
+ def close
64
+ http.finish if http.started?
65
+ end
66
+
67
+ private
68
+
69
+ def operate(method, path: nil, params: nil, body: nil, query: nil)
70
+ uri = uri_with_path(path)
71
+ case method
72
+ when :get, :delete
73
+ if params
74
+ query = URI.encode_www_form(params)
75
+ Firebase.logger.info('Created urlencoded query from params')
76
+ else
77
+ uri.query = query
78
+ end
79
+ req = METHOD_HTTP_CLASS[method].new(uri)
80
+ when :put, :patch, :post
81
+ uri.query = query if query
82
+ req = METHOD_HTTP_CLASS[method].new(uri)
83
+ if params
84
+ req.form_data = params
85
+ Firebase.logger.info('Created form data from params')
86
+ elsif body
87
+ req.body = body
88
+ end
89
+ else
90
+ return nil
91
+ end
92
+ data = send(req)
93
+ data = redirect(method, uri, params: params, body: body, query: query) if data.class <= URI::HTTP
94
+ return data
95
+ end
96
+
97
+ def uri_with_path(path)
98
+ uri = init_uri.clone
99
+ uri.path = path unless path.nil?
100
+ return uri
101
+ end
102
+
103
+ def send(req)
104
+ inject_headers_to(req)
105
+ unless http.started?
106
+ Firebase.logger.info('HTTP session not started; starting now')
107
+ http.start
108
+ Firebase.logger.debug("Opened connection to #{http.address}:#{http.port}")
109
+ end
110
+ Firebase.logger.debug("Sending HTTP #{req.method} request to #{req.path}")
111
+ Firebase.logger.debug("Body size: #{req.body.length}") if req.request_body_permitted?
112
+ res = http.request(req)
113
+ return handle_response(res)
114
+ end
115
+
116
+ def inject_headers_to(req)
117
+ return if headers.nil?
118
+ headers.each do |k, v|
119
+ req[k] = v
120
+ end
121
+ Firebase.logger.info('Header injected into HTTP request header')
122
+ end
123
+
124
+ def handle_response(res)
125
+ if res.connection_close?
126
+ Firebase.logger.info('HTTP response header says connection close; closing session now')
127
+ close
128
+ end
129
+ case res
130
+ when Net::HTTPRedirection
131
+ Firebase.logger.info('HTTP response was a redirect')
132
+ data = URI(res['Location'])
133
+ if data.class == URI::Generic
134
+ data = uri_with_path(res['Location'])
135
+ Firebase.logger.debug("Full URI object built for local redirect with path: #{data.path}")
136
+ end
137
+ # when Net::HTTPSuccess
138
+ # when Net::HTTPClientError
139
+ # when Net::HTTPServerError
140
+ else
141
+ data = {
142
+ code: res.code.to_i,
143
+ headers: res.to_hash,
144
+ body: res.body,
145
+ message: res.msg
146
+ }
147
+ end
148
+ return data
149
+ end
150
+
151
+ def redirect(method, uri, params: nil, body: nil, query: nil)
152
+ if uri.host == init_uri.host && uri.port == init_uri.port
153
+ Firebase.logger.info("Local #{method.upcase} redirect, reusing HTTP session")
154
+ new_http = http
155
+ else
156
+ Firebase.logger.info("External #{method.upcase} redirect, spawning new HTTP object")
157
+ new_http = HTTP.new("#{uri.scheme}://#{uri.host}#{uri.path}", headers)
158
+ end
159
+ case method
160
+ when :get, :delete
161
+ data = operate(method, uri, params: params, query: query)
162
+ when :put, :patch, :post
163
+ data = new_http.public_send(method, uri, params: params, body: body, query: query)
164
+ else
165
+ data = nil
166
+ end
167
+ return data
168
+ end
169
+
170
+ end
171
+
172
+ end