firebase-ruby 0.0.2

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