nm-gigya 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: 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: []