nm-gigya 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: d1e812bdc1c3eac6a46f6ad74f93e70bfc4337f5
4
+ data.tar.gz: 956a02434949956193e89918da9c01f55d86cb59
5
+ SHA512:
6
+ metadata.gz: aaa472edaecc5856ce1b66a95ba4fa4bd553bc71533201e64a3fceed11a53ec1444e576e38e9bdcbf9e0dcc80aecd145e7259abd65b082e86ad333c72c68c142
7
+ data.tar.gz: 18ff50dc1b21b29e50cbea1d61934574cf47363a0b3f190775e3c6e192f9a7551cd1a9033495f09f1f5cc58b84b972cd304b35691624bd66326deceb36ed7c40
@@ -0,0 +1,311 @@
1
+ require "openssl"
2
+ require "httparty"
3
+ require "jwt"
4
+ require "json"
5
+
6
+ # Required for the dynamic modules
7
+ #require "active_support"
8
+ #require "active_support/core_ext"
9
+
10
+ module Gigya
11
+ class GigyaApiException < StandardError
12
+ def initialize(msg, result)
13
+ super(msg)
14
+ @api_result = result
15
+ end
16
+
17
+ def api_result
18
+ @api_result
19
+ end
20
+ end
21
+
22
+ class GigyaDynamicImplementation
23
+ attr_accessor :connection, :area, :function, :data_results, :data_keys
24
+
25
+ def clone_gdi
26
+ gdi = GigyaDynamicImplementation.new
27
+ gdi.connection = connection
28
+ gdi.area = area
29
+ gdi.function = function
30
+ gdi.data_results = data_results
31
+ gdi.data_keys = (data_keys || []).clone
32
+
33
+ return gdi
34
+ end
35
+
36
+ def gdi_value
37
+ current_result = data_results
38
+ data_keys.each do |k|
39
+ current_result = current_result[k]
40
+ return nil if current_result == nil
41
+ end
42
+ return current_result
43
+ end
44
+
45
+ def to_h
46
+ gdi_value
47
+ end
48
+
49
+ def to_hash
50
+ gdi_value
51
+ end
52
+
53
+ def to_value
54
+ gdi_value
55
+ end
56
+
57
+ def nil?
58
+ to_value.nil?
59
+ end
60
+
61
+ def blank?
62
+ to_value.blank?
63
+ end
64
+
65
+ def empty?
66
+ to_value.empty?
67
+ end
68
+
69
+ def method_missing(name, *args, &block)
70
+ name = name.to_s
71
+ gdi = clone_gdi
72
+
73
+ if data_results == nil
74
+ if(name[-1..-1] == "=" && args.size == 1)
75
+ field_name = name[0..-2]
76
+
77
+ if self.area == nil
78
+ raise "Can't run a setter on #{area}"
79
+ else
80
+ function_name = "set" + field_name.camelize
81
+ return connection.api_get(area, function_name, val)
82
+ end
83
+ elsif args.size == 0
84
+ field_name = name
85
+ function_name = "get" + field_name.camelize
86
+ results = connection.api_get(area, function_name)
87
+ gdi.function = function_name
88
+ gdi.data_results = results
89
+ gdi.data_keys = []
90
+
91
+ return gdi
92
+ end
93
+ else
94
+ field_name = name
95
+ if field_name[-1..-1] == "="
96
+ field_name = field_name[0..-2]
97
+ if field_name[0..1] == "__"
98
+ field_name = field_name[2..-1]
99
+ else
100
+ field_name = field_name.camelize(:lower)
101
+ end
102
+ setter_hash = {}
103
+ cur_hashpoint = setter_hash
104
+ curval = gdi.data_results
105
+ gdi.data_keys.each do |k|
106
+ curval = curval[k]
107
+ if Hash === curval
108
+ cur_hashpoint[k] = {}
109
+ cur_hashpoint = cur_hashpoint[k]
110
+ elsif Array === curval
111
+ cur_hashpoint[k] = []
112
+ cur_hashpoint = cur_hashpoint[k]
113
+ else
114
+ cur_hashpoint[k] = curval
115
+ cur_hashpoint = curval
116
+ end
117
+ end
118
+ cur_hashpoint[field_name] = args[0]
119
+ setter_hash.keys.each do |k|
120
+ val = setter_hash[k]
121
+ if Hash === val || Array === val
122
+ val = val.to_json
123
+ setter_hash[k] = val
124
+ end
125
+ end
126
+
127
+ return connection.api_get(area, function.gsub(/^get/, "set"), setter_hash)
128
+ else
129
+ if field_name[0..1] == "__"
130
+ # This is an escape sequence to maintain capitalization
131
+ field_name = field_name[2..-1]
132
+ else
133
+ field_name = field_name.camelize(:lower)
134
+ end
135
+ gdi.data_keys.push(field_name)
136
+
137
+ val = gdi.to_value
138
+ if Hash === val || Array === val || val == nil
139
+ return gdi
140
+ else
141
+ return val
142
+ end
143
+ end
144
+ end
145
+
146
+ super
147
+ end
148
+
149
+ # Don't know if I should implement this
150
+ # respond_to_missing?(method_name, *args)
151
+ end
152
+
153
+ class Connection
154
+ GIGYA_BASE_URL="gigya.com"
155
+ def self.shared_connection
156
+ @@connection ||= self.new(
157
+ :datacenter => Rails.application.secrets.gigya_datacenter || "us1",
158
+ :api_key => Rails.application.secrets.gigya_api_key,
159
+ :user_key => Rails.application.secrets.gigya_user_key,
160
+ :user_secret => Rails.application.secrets.gigya_user_secret
161
+ )
162
+ end
163
+
164
+ def self.shared_connection=(conn)
165
+ @@connection = conn
166
+ end
167
+
168
+ # The regular URI.encode doesn't do "+"s, so this is a shortcut
169
+ def self.encode(x)
170
+ URI.encode_www_form_component(x)
171
+ end
172
+
173
+ # See here for the reasons for the strange reasons for this strange function:
174
+ # https://developers.gigya.com/display/GD/How+To+Validate+A+Gigya+id_token
175
+ # Seems to apply to some, but not all, pieces of Base64 encoded things.
176
+ def self.strange_munge(x)
177
+ x.gsub("-", "+").gsub("_", "/")
178
+ end
179
+
180
+ # According to https://developers.gigya.com/display/GD/How+To+Validate+A+Gigya+id_token
181
+ # Gigya JWTs are not in the standard format, but must be munged first
182
+ def self.reformat_jwt(jwt_token)
183
+ signable_piece = jwt_token.split(".")[0..1].join(".")
184
+ signature = strange_munge(jwt_token.split(".")[2])
185
+ return [signable_piece, signature].join(".")
186
+ end
187
+
188
+ # Builds an OpenSSL RSA key from a given modulus and exponent.
189
+ # This is because Gigya likes to give us keys like this.
190
+ # https://stackoverflow.com/questions/46121275/ruby-rsa-from-exponent-and-modulus-strings
191
+ def self.build_rsa_key(modulus, exponent)
192
+ k = OpenSSL::PKey::RSA.new
193
+ k.n = OpenSSL::BN.new(Base64.decode64(strange_munge(modulus)), 2)
194
+ k.e = OpenSSL::BN.new(Base64.decode64(exponent), 2)
195
+ return k
196
+ end
197
+
198
+ def initialize(opts = {})
199
+ @opts = opts
200
+ @cached_data = {}
201
+ end
202
+
203
+ def connection_options
204
+ @opts
205
+ end
206
+
207
+ # NOTE - the key_id is here so that, in the future, we might be able
208
+ # to download a specific key. Right now, it is ignored and the
209
+ # most recent one is obtained
210
+ def download_latest_jwt_public_key(key_id = nil)
211
+ keyinfo = api_get("accounts", "getJWTPublicKey")
212
+ keyinfo_id = keyinfo["kid"]
213
+ raise "Unsupported Key Type" if keyinfo["kty"] != "RSA"
214
+ keyinfo_key = self.class.build_rsa_key(keyinfo["n"], keyinfo["e"])
215
+ @cached_data["jwt_public_keys"] ||= {}
216
+ @cached_data["jwt_public_keys"][keyinfo_id] = keyinfo_key
217
+ return keyinfo_key
218
+ end
219
+
220
+ def validate_jwt(jwt_token, gigya_munge = false)
221
+ jwt_token = self.class.reformat_jwt(jwt_token) if gigya_munge
222
+
223
+ user_jwt_info, signing_jwt_info = JWT.decode(jwt_token, nil, false)
224
+ signing_key_id = signing_jwt_info["keyid"]
225
+ @cached_data["jwt_public_keys"] ||= {}
226
+ k = @cached_data["jwt_public_keys"][signing_key_id]
227
+ k = download_latest_jwt_public_key(signing_key_id) if k == nil
228
+ user_jwt_info, signing_jwt_info = JWT.decode(jwt_token, k, true, { :algorithm => signing_jwt_info["alg"] })
229
+ return user_jwt_info
230
+ end
231
+
232
+ def login(username, password)
233
+ user_info = api_get("accounts", "login", {:loginID => username, :password => password, :targetEnv => "mobile"}, :throw_on_error => true)
234
+ uid = user_info["UID"]
235
+ session_token = user_info["sessionToken"]
236
+ session_secret = user_info["sessionSecret"]
237
+ conn = self.class.new(@opts.merge(:session => {:user_id => uid, :profile => user_info["profile"], :token => session_token, :secret => session_secret}))
238
+ return conn
239
+ end
240
+
241
+ def api_get(area, function, params = nil, opts = nil)
242
+ api_call("GET", area, function, params, opts)
243
+ end
244
+
245
+ def api_post(area, function, params = nil, opts = nil)
246
+ api_call("POST", area, function, params, opts)
247
+ end
248
+
249
+ def api_call(http_method, area, function, params = nil, opts = nil)
250
+ params ||= {}
251
+ opts ||= {}
252
+ opts = @opts.merge(opts)
253
+
254
+ base_url = "https://#{area}.#{opts[:datacenter]}.#{GIGYA_BASE_URL}/#{area}.#{function}"
255
+
256
+ params[:apiKey] = opts[:api_key]
257
+ unless opts[:authenticate_app] == false
258
+ params[:secret] = opts[:user_secret]
259
+ params[:userKey] = opts[:user_key]
260
+ end
261
+
262
+ if opts[:session] != nil
263
+ if opts[:session][:user_id] != nil
264
+ unless opts[:ignore_user_id]
265
+ params[:UID] = opts[:session][:user_id]
266
+ end
267
+ end
268
+ end
269
+
270
+ if opts[:debug_connection]
271
+ # FIXME - what to do with logging
272
+ puts "DEBUG CONNECTION SEND: #{http_method} #{base_url} // #{params.inspect}"
273
+ end
274
+ http_response = nil
275
+ response = begin
276
+ http_response = http_method == "GET" ? HTTParty.get(base_url, :query => params) : HTTParty.post(base_url, :body => params)
277
+ JSON.parse(http_response.body)
278
+ rescue
279
+ {"errorCode" => 600, "errorMessage" => "Unknown error", "errorDetail" => "Unable to communicate with authentication server", :http => http_response.inspect}
280
+ end
281
+
282
+ if opts[:debug_connection]
283
+ # FIXME - what to do with logging
284
+ puts "DEBUG CONNECTION RECEIVE: #{response.inspect}"
285
+ end
286
+
287
+ if opts[:throw_on_error]
288
+ if response["statusCode"].to_i >= 400 || response["errorCode"].to_i > 0
289
+ error_msg = "#{response["errorMessage"]}: #{response["errorDetails"]}"
290
+ raise GigyaApiException.new(error_msg, response)
291
+ end
292
+ end
293
+
294
+ return response
295
+ end
296
+
297
+ def method_missing(name, *args, &block)
298
+ if args.size == 0
299
+ gdi = GigyaDynamicImplementation.new
300
+ gdi.connection = self
301
+ gdi.area = name
302
+ return gdi
303
+ else
304
+ super
305
+ end
306
+ end
307
+
308
+ # Don't know if I should implement this
309
+ # respond_to_missing?(method_name, *args)
310
+ end
311
+ end
@@ -0,0 +1,36 @@
1
+ module Gigya
2
+ ### Helper/controller mixins
3
+ module ControllerUtils
4
+ def gigya_user_required
5
+ begin
6
+ render(:json => {:error => "Invalid login"}, :status => 401) if gigya_user_identifier.blank?
7
+ rescue
8
+ render(:json => {:error => "#{$!.message}"}, :status => 401)
9
+ end
10
+ end
11
+
12
+ def interpret_jwt_token
13
+ @token ||= begin
14
+ tmp_token = nil
15
+ authenticate_with_http_token do |token, options|
16
+ tmp_token = token
17
+ end
18
+ tmp_token = params[:token] unless params[:token].blank?
19
+ tmp_token
20
+ end
21
+ @jwt_info ||= Gigya::Connection.shared_connection.validate_jwt(@token)
22
+ end
23
+
24
+ def gigya_user_information
25
+ interpret_jwt_token
26
+ @jwt_info
27
+ end
28
+
29
+ def gigya_user_identifier
30
+ @gigya_user_identifier ||= begin
31
+ interpret_jwt_token
32
+ @jwt_info["sub"]
33
+ end
34
+ end
35
+ end
36
+ end
data/lib/gigya.rb ADDED
@@ -0,0 +1,4 @@
1
+ require 'httparty'
2
+
3
+ require "gigya/connection"
4
+ require "gigya/controller_utils" if defined?(Rails)
metadata ADDED
@@ -0,0 +1,74 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: nm-gigya
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.2
5
+ platform: ruby
6
+ authors:
7
+ - Jonathan Bartlett
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2018-06-13 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: httparty
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: 0.16.2
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: 0.16.2
27
+ - !ruby/object:Gem::Dependency
28
+ name: jwt
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '2.1'
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '2.1'
41
+ description: Utility package for accessing Gigya API
42
+ email: jonathan@newmedio.com
43
+ executables: []
44
+ extensions: []
45
+ extra_rdoc_files: []
46
+ files:
47
+ - lib/gigya.rb
48
+ - lib/gigya/connection.rb
49
+ - lib/gigya/controller_utils.rb
50
+ homepage: http://www.newmedio.com/
51
+ licenses:
52
+ - MIT
53
+ metadata: {}
54
+ post_install_message:
55
+ rdoc_options: []
56
+ require_paths:
57
+ - lib
58
+ required_ruby_version: !ruby/object:Gem::Requirement
59
+ requirements:
60
+ - - ">="
61
+ - !ruby/object:Gem::Version
62
+ version: '0'
63
+ required_rubygems_version: !ruby/object:Gem::Requirement
64
+ requirements:
65
+ - - ">="
66
+ - !ruby/object:Gem::Version
67
+ version: '0'
68
+ requirements: []
69
+ rubyforge_project:
70
+ rubygems_version: 2.4.6
71
+ signing_key:
72
+ specification_version: 4
73
+ summary: Gigya API Utility Package
74
+ test_files: []