nm-gigya 0.0.2
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/lib/gigya/connection.rb +311 -0
- data/lib/gigya/controller_utils.rb +36 -0
- data/lib/gigya.rb +4 -0
- metadata +74 -0
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
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: []
|