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 +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: []
|