touchpass 0.0.8.1
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.
- data/.gitignore +3 -0
- data/.rspec +2 -0
- data/Gemfile +16 -0
- data/Gemfile.lock +38 -0
- data/Guardfile +9 -0
- data/Rakefile +1 -0
- data/app/controllers/touchpass/verifications_controller.rb +30 -0
- data/bin/tpcli.rb +248 -0
- data/config/routes.rb +8 -0
- data/lib/engine.rb +4 -0
- data/lib/generators/templates/config/initializers/devise.rb +204 -0
- data/lib/generators/templates/config/initializers/touchpass.rb +24 -0
- data/lib/generators/templates/config/locales/devise.en.yml +53 -0
- data/lib/generators/touchpass/install_generator.rb +36 -0
- data/lib/touchpass/client.rb +431 -0
- data/lib/touchpass/crypt.rb +51 -0
- data/lib/touchpass/device.rb +18 -0
- data/lib/touchpass/key_file_creator.rb +56 -0
- data/lib/touchpass/prp.rb +256 -0
- data/lib/touchpass/verification.rb +112 -0
- data/lib/touchpass/version.rb +3 -0
- data/lib/touchpass.rb +53 -0
- data/spec/bounding_box_spec.rb +26 -0
- data/spec/geocode_spec.rb +22 -0
- data/spec/helpers/client_spec_helper.rb +1 -0
- data/spec/helpers/gtp_spec_helper.rb +98 -0
- data/spec/key_file_creator_spec.rb +30 -0
- data/spec/proximity_spec.rb +283 -0
- data/spec/prp_spec.rb +86 -0
- data/spec/resolution_spec.rb +46 -0
- data/spec/spec_helper.rb +24 -0
- data/spec/touchpass_client_spec.rb +319 -0
- data/touchpass.gemspec +27 -0
- metadata +146 -0
@@ -0,0 +1,36 @@
|
|
1
|
+
require 'rails/generators'
|
2
|
+
require 'rails/generators/migration'
|
3
|
+
|
4
|
+
module Touchpass
|
5
|
+
module Generators
|
6
|
+
class InstallGenerator < Rails::Generators::Base
|
7
|
+
include Rails::Generators::Migration
|
8
|
+
source_root File.expand_path("../../templates", __FILE__)
|
9
|
+
|
10
|
+
desc "Install Touchpass files (migrations, initializers, locales)."
|
11
|
+
|
12
|
+
def create_migration_file
|
13
|
+
# no db config for relying partiues
|
14
|
+
end
|
15
|
+
|
16
|
+
def copy_initializer_files
|
17
|
+
copy_file "config/initializers/devise.rb", "config/initializers/devise.rb"
|
18
|
+
copy_file "config/initializers/touchpass.rb", "config/initializers/touchpass.rb"
|
19
|
+
end
|
20
|
+
|
21
|
+
def copy_locale_files
|
22
|
+
copy_file "config/locales/devise.en.yml", "config/locales/devise.en.yml"
|
23
|
+
end
|
24
|
+
|
25
|
+
private
|
26
|
+
def self.next_migration_number(dirname)
|
27
|
+
if ActiveRecord::Base.timestamped_migrations
|
28
|
+
Time.new.utc.strftime("%Y%m%d%H%M%S")
|
29
|
+
else
|
30
|
+
"%.3d" % (current_migration_number(dirname) + 1)
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
@@ -0,0 +1,431 @@
|
|
1
|
+
# Geodica Touchpass
|
2
|
+
# (C) Copyright 2009-2012 Geodica, a Carpadium Pty Ltd Venture
|
3
|
+
# All rights reserved
|
4
|
+
|
5
|
+
require 'rubygems'
|
6
|
+
require 'thor'
|
7
|
+
require 'httparty'
|
8
|
+
require 'openssl'
|
9
|
+
require 'fileutils'
|
10
|
+
require 'base64'
|
11
|
+
require 'pp'
|
12
|
+
require 'cgi'
|
13
|
+
require 'json'
|
14
|
+
|
15
|
+
module Touchpass
|
16
|
+
|
17
|
+
# Provide a simple Ruby interface to a Touchpass verification server
|
18
|
+
class Client
|
19
|
+
|
20
|
+
attr_accessor :hostname, :debug, :api_key
|
21
|
+
|
22
|
+
API_KEY_HEADER = "X-TouchPass-ApiKey"
|
23
|
+
ENVIRONMENT = ENV['RAILS_ENV']
|
24
|
+
|
25
|
+
## Host and port to use for service invocations, default to the main Touchpass server
|
26
|
+
DFT_HOSTNAME = ENVIRONMENT == 'production' ? "https://touchpass.geodica.com" : "https://localhost:3000"
|
27
|
+
|
28
|
+
# Default response output to json
|
29
|
+
DFT_OUTPUT = "json"
|
30
|
+
|
31
|
+
# Default debug state (on: dev|test, off: prod)
|
32
|
+
DFT_DEBUG = ENV['TPC_DEBUG'] ? true : false
|
33
|
+
|
34
|
+
# Create a new Touchpass client instance and default the hostname and output
|
35
|
+
def initialize(hostname=DFT_HOSTNAME, debug=DFT_DEBUG)
|
36
|
+
@hostname = hostname.nil? ? DFT_HOSTNAME : hostname
|
37
|
+
@debug = debug
|
38
|
+
@output = DFT_OUTPUT # always use json internally
|
39
|
+
end
|
40
|
+
|
41
|
+
# Register a new relying party with the Touchpass server
|
42
|
+
# needs: :email, :username, :password
|
43
|
+
def register_party(params)
|
44
|
+
http_options = standard_http_options(params, %W{email username password})
|
45
|
+
response = submit_request("post", "#{@hostname}/parties.#{@output}", http_options)
|
46
|
+
set_api_key_from_response(response)
|
47
|
+
response
|
48
|
+
end
|
49
|
+
|
50
|
+
# Authenticate a party to obtain the API key
|
51
|
+
# needs: :login, :password
|
52
|
+
def authenticate_party(params)
|
53
|
+
http_options = standard_http_options(params, %W{login password})
|
54
|
+
|
55
|
+
response = submit_request(
|
56
|
+
"post",
|
57
|
+
"#{@hostname}/parties/authenticate.#{@output}",
|
58
|
+
http_options)
|
59
|
+
set_api_key_from_response(response)
|
60
|
+
response
|
61
|
+
end
|
62
|
+
|
63
|
+
# Get details for a party
|
64
|
+
# needs: :api_key; uses: :id OR :username
|
65
|
+
def get_party(params)
|
66
|
+
http_options = standard_http_options(params)
|
67
|
+
|
68
|
+
if !params[:username].nil?
|
69
|
+
response = submit_request("get", "#{@hostname}/#{params[:username]}.#{@output}", http_options)
|
70
|
+
elsif !params[:id].nil?
|
71
|
+
response = submit_request("get", "#{@hostname}/parties/#{params[:id]}.#{@output}", http_options)
|
72
|
+
else
|
73
|
+
response = format_response({"error" => "Either username or id must be provided."})
|
74
|
+
end
|
75
|
+
response
|
76
|
+
end
|
77
|
+
|
78
|
+
# Update details for a party
|
79
|
+
# needs: :api_key; uses: :username, :id, :email, :first_name, :last_name
|
80
|
+
def update_party(params)
|
81
|
+
id = params[:id]
|
82
|
+
username = params[:username]
|
83
|
+
http_options = standard_http_options(params, %W{username email first_name last_name})
|
84
|
+
|
85
|
+
if !username.nil?
|
86
|
+
response = submit_request("put", "#{@hostname}/#{username}.#{@output}", http_options)
|
87
|
+
elsif !id.nil?
|
88
|
+
response = submit_request("put", "#{@hostname}/parties/#{id}.#{@output}", http_options)
|
89
|
+
else
|
90
|
+
response = format_response({"error" => "Either username or id must be provided."})
|
91
|
+
end
|
92
|
+
response
|
93
|
+
end
|
94
|
+
|
95
|
+
# Register a new device for a party
|
96
|
+
# needs: :api_key, :username, :udid, :devicename; uses: :messaging_type, :messaging_value
|
97
|
+
def register_device(params)
|
98
|
+
|
99
|
+
# Generate private & public key for this device
|
100
|
+
crypt = KeyFileCreator.new(params[:udid])
|
101
|
+
crypt.generate_keys
|
102
|
+
|
103
|
+
# Collect attributes of new device
|
104
|
+
device_attributes = params.merge({
|
105
|
+
:udid => params[:udid],
|
106
|
+
:name => params[:name],
|
107
|
+
:pub_key => crypt.public_key,
|
108
|
+
:messaging_type => params[:messaging_type],
|
109
|
+
:messaging_value => params[:messaging_value],
|
110
|
+
})
|
111
|
+
http_options = standard_http_options(device_attributes, %W{udid name pub_key messaging_type messaging_value})
|
112
|
+
|
113
|
+
# Provision the new device with the Touchpass server
|
114
|
+
new_device = submit_request("post", "#{@hostname}/#{params[:username]}/devices.#{@output}", http_options)
|
115
|
+
|
116
|
+
# We made the key files for the new device earlier (see
|
117
|
+
# KeyFileCreator above) but at that stage we didn't know the
|
118
|
+
# :deviceid of the newly provisioned device. So now we need to
|
119
|
+
# move the cert directory around based on the :deviceid returned
|
120
|
+
# from TouchPass.
|
121
|
+
begin
|
122
|
+
devices = submit_request("post", "#{@hostname}/#{params[:username]}/devices.json", http_options)
|
123
|
+
raise devices["error"] if devices["error"]
|
124
|
+
raise "unknown device id" unless devices["id"]
|
125
|
+
device_id = devices["id"].to_s
|
126
|
+
|
127
|
+
orig_file = crypt.keys_path
|
128
|
+
crypt.id = device_id
|
129
|
+
new_file = crypt.keys_path
|
130
|
+
puts "Renaming directory #{orig_file} to #{new_file}" if @debug
|
131
|
+
result = File.rename(orig_file, new_file)
|
132
|
+
rescue Exception => e
|
133
|
+
puts "Exception: #{e.message}"
|
134
|
+
puts "error: updating key file location based on device id: udid:#{params[:udid]}, device_id:#{device_id}"
|
135
|
+
end
|
136
|
+
|
137
|
+
# Return details of the newly provisioned device
|
138
|
+
new_device
|
139
|
+
end
|
140
|
+
|
141
|
+
# Get all devices for a party
|
142
|
+
# needs: :username, :api_key
|
143
|
+
def get_devices(params)
|
144
|
+
# Just return the device list for the specified user
|
145
|
+
http_options = standard_http_options(params)
|
146
|
+
devices = submit_request("get", "#{@hostname}/#{params[:username]}/devices.#{@output}", http_options)
|
147
|
+
|
148
|
+
# Look for errors, and just build an empty device array hashed from 'devices'
|
149
|
+
if !devices['devices'].nil? or !devices['error'].nil?
|
150
|
+
devices
|
151
|
+
else
|
152
|
+
{ 'devices' => [] }
|
153
|
+
end
|
154
|
+
end
|
155
|
+
|
156
|
+
# Get details for a specific device, returns an 'errors' hash if the device cannot be found
|
157
|
+
# needs: :username, :api_key, :id
|
158
|
+
def get_device(params)
|
159
|
+
# Return the device for the specified user and device id
|
160
|
+
http_options = standard_http_options(params)
|
161
|
+
submit_request("get", "#{@hostname}/#{params[:username]}/devices/#{params[:id]}.#{@output}", http_options)
|
162
|
+
end
|
163
|
+
|
164
|
+
# Update details of a device, returns an 'errors' hash if the device cannot be found
|
165
|
+
# needs: :username, :id, api_key; uses: :name, :messaging_type, :messaging_value, :update_pub_key
|
166
|
+
def update_device(params)
|
167
|
+
update_pub_key = params[:update_pub_key] # TODO: Regenerate and update pub_key
|
168
|
+
|
169
|
+
http_options = standard_http_options(params, %W{name messaging_type messaging_value})
|
170
|
+
|
171
|
+
submit_request("put", "#{@hostname}/#{params[:username]}/devices/#{params[:id]}.#{@output}", http_options)
|
172
|
+
end
|
173
|
+
|
174
|
+
# Remove device, returns an 'errors' hash if the device cannot be found
|
175
|
+
# needs: :username, :id, :api_key
|
176
|
+
def remove_device(params)
|
177
|
+
http_options = standard_http_options(params)
|
178
|
+
submit_request("delete", "#{@hostname}/#{params[:username]}/devices/#{params[:id]}.#{@output}", http_options)
|
179
|
+
end
|
180
|
+
|
181
|
+
# Create a new verification
|
182
|
+
# needs: :to_party, :api_key; uses: :message, :address (for LV), :resolution (for LV)
|
183
|
+
def create_verification(params)
|
184
|
+
http_options = standard_http_options(params, %W{to_party})
|
185
|
+
message = params[:message]
|
186
|
+
address = params[:address]
|
187
|
+
|
188
|
+
# Get the list of verifying party devices so we can encrypt data for them using each device's public key
|
189
|
+
response = submit_request("get", "#{@hostname}/#{params[:to_party]}/devices.json",
|
190
|
+
standard_http_options(params))
|
191
|
+
vp_devices = response["devices"]
|
192
|
+
|
193
|
+
# Generate token
|
194
|
+
token = Crypt.salt
|
195
|
+
hashed_token = Crypt.encrypt(token)
|
196
|
+
http_options[:body][:claimed_token] = hashed_token
|
197
|
+
|
198
|
+
if !vp_devices.nil? and (vp_devices.is_a? Array and !vp_devices.empty?)
|
199
|
+
|
200
|
+
# Encrypt message using each verifying party's (to_party) device public keys
|
201
|
+
if !message.nil?
|
202
|
+
vp_devices.each_with_index do |device, index|
|
203
|
+
device_id = device["id"]
|
204
|
+
pub_key_str = device["pub_key"]
|
205
|
+
public_key = Crypt.read_key(pub_key_str)
|
206
|
+
crypted_message = Base64.encode64(public_key.public_encrypt(message))
|
207
|
+
http_options[:body]["crypted_messages[#{index}][device_id]"] = device_id
|
208
|
+
http_options[:body]["crypted_messages[#{index}][value]"] = crypted_message
|
209
|
+
end
|
210
|
+
end
|
211
|
+
|
212
|
+
# Encrypt token using each verifying party's (to_party) device public keys
|
213
|
+
crypted_tokens = []
|
214
|
+
vp_devices.each_with_index do |device, index|
|
215
|
+
device_id = device["id"]
|
216
|
+
pub_key_str = device["pub_key"]
|
217
|
+
public_key = Crypt.read_key(pub_key_str)
|
218
|
+
crypted_token = Base64.encode64(public_key.public_encrypt(token)) # per-transaction token generated by Crypt.salt above
|
219
|
+
http_options[:body]["crypted_tokens[#{index}][device_id]"] = device_id
|
220
|
+
http_options[:body]["crypted_tokens[#{index}][value]"] = crypted_token
|
221
|
+
end
|
222
|
+
end
|
223
|
+
|
224
|
+
# Encrypt prp-hash using verifying_party (to_party) devices
|
225
|
+
if !address.nil? # For location verification
|
226
|
+
http_options[:body][:location_verification] = true
|
227
|
+
|
228
|
+
# Convert address to PRP
|
229
|
+
resolution = options[:resolution] || "LOCAL" # Resolution used to calculate PRP
|
230
|
+
http_options[:body][:resolution] = resolution
|
231
|
+
prp = Proximity::PRP.new(:address => params[:address], :resolution => resolution)
|
232
|
+
# puts prp.print_bbox # for debugging
|
233
|
+
|
234
|
+
# Generate random salt
|
235
|
+
salt = Crypt.salt
|
236
|
+
# puts "Using salt: #{salt}" # for debugging
|
237
|
+
|
238
|
+
# Encrypt PRP using salt
|
239
|
+
claimed_prp = prp.encrypt(salt)
|
240
|
+
http_options[:body][:claimed_prp] = claimed_prp
|
241
|
+
|
242
|
+
# Encrypt salt using verifiying_party (to_party) devices
|
243
|
+
crypted_salts = []
|
244
|
+
vp_devices.each_with_index do |device, index|
|
245
|
+
device_id = device["id"]
|
246
|
+
pub_key_str = device["pub_key"]
|
247
|
+
public_key = Crypt.read_key(pub_key_str)
|
248
|
+
crypted_salt = Base64.encode64(public_key.public_encrypt(salt))
|
249
|
+
http_options[:body]["crypted_salts[#{index}][device_id]"] = device_id
|
250
|
+
http_options[:body]["crypted_salts[#{index}][value]"] = crypted_salt
|
251
|
+
end
|
252
|
+
end
|
253
|
+
|
254
|
+
submit_request("post", "#{@hostname}/verifications.#{@output}", http_options)
|
255
|
+
end
|
256
|
+
|
257
|
+
# Get a page of verifications for a particular party
|
258
|
+
# needs: :api_key; uses: :username, :party_id
|
259
|
+
def get_verifications(params)
|
260
|
+
http_options = standard_http_options(params)
|
261
|
+
response = nil
|
262
|
+
if !params[:username].nil?
|
263
|
+
response = submit_request("get", "#{@hostname}/#{params[:username]}/verifications.#{@output}", http_options)
|
264
|
+
elsif !params[:party_id].nil?
|
265
|
+
http_options[:body][:party_id] = params[:party_id]
|
266
|
+
response = submit_request("get", "#{@hostname}/verifications.#{@output}", http_options)
|
267
|
+
else
|
268
|
+
response = {"error" => "Either username or id must be provided."}
|
269
|
+
end
|
270
|
+
response
|
271
|
+
end
|
272
|
+
|
273
|
+
# Get details of a specific verification, will include decrypted message if :device_id is provided
|
274
|
+
# needs: :api_key, :id; uses: :device_id
|
275
|
+
def get_verification(params)
|
276
|
+
http_options = standard_http_options(params)
|
277
|
+
response = submit_request("get", "#{@hostname}/verifications/#{params[:id]}.json", http_options)
|
278
|
+
|
279
|
+
# TODO: as this stands, it will not decrypt the message unless a :device_id is provided. However,
|
280
|
+
# there is currently no way to pass in the keys to use - it will just try to use the local keys
|
281
|
+
# stored in #{local_key_path}/:device_id. So, we need to fix this up at some point.
|
282
|
+
decrypted_message = nil
|
283
|
+
if response.has_key?("crypted_messages") and !response["crypted_messages"].empty?
|
284
|
+
if !params[:device_id].nil?
|
285
|
+
decrypted_message = decrypt_salt(response["crypted_messages"], params[:device_id], self.api_key)
|
286
|
+
end
|
287
|
+
end
|
288
|
+
# return verification plus decrypted message (if available)
|
289
|
+
response[:decrypted_message] = decrypted_message
|
290
|
+
response
|
291
|
+
end
|
292
|
+
|
293
|
+
# Update Verification
|
294
|
+
# needs: :api_key, :id, :device_id; uses: :address
|
295
|
+
def update_verification(params)
|
296
|
+
# Call show verification to get the crypted salts and resolution for the verification
|
297
|
+
# Note: crypted salts and resolution are also returned in the list verifications response
|
298
|
+
http_options = standard_http_options(params)
|
299
|
+
|
300
|
+
verification = submit_request("get", "#{@hostname}/verifications/#{params[:id]}.json", http_options)
|
301
|
+
crypted_tokens = verification["crypted_tokens"]
|
302
|
+
crypted_salts = verification["crypted_salts"]
|
303
|
+
resolution = verification["resolution"]
|
304
|
+
|
305
|
+
token = decrypt_salt(crypted_tokens, params[:device_id], self.api_key)
|
306
|
+
hashed_token = Crypt.encrypt(token)
|
307
|
+
|
308
|
+
http_options = standard_http_options(params, %W{id})
|
309
|
+
http_options[:body][:verified_token] = hashed_token
|
310
|
+
|
311
|
+
# Generate verified PRP for location verification
|
312
|
+
if !params[:address].nil?
|
313
|
+
prp = Proximity::PRP.new(:address => params[:address], :resolution => resolution)
|
314
|
+
# puts prp.print_bbox # for debugging
|
315
|
+
|
316
|
+
salt = decrypt_salt(crypted_salts, params[:device_id], self.api_key)
|
317
|
+
# puts "Using salt: #{salt}"
|
318
|
+
verified_prp = prp.encrypt(salt)
|
319
|
+
|
320
|
+
http_options[:body][:verified_prp] = verified_prp
|
321
|
+
# puts "Verified PRP: #{verified_prp}"
|
322
|
+
end
|
323
|
+
submit_request("put", "#{@hostname}/verifications/#{params[:id]}.#{@output}",
|
324
|
+
http_options)
|
325
|
+
end
|
326
|
+
|
327
|
+
# Cancel an existing verification
|
328
|
+
# needs: :api_key, :id
|
329
|
+
def cancel_verification(params)
|
330
|
+
submit_request("put", "#{@hostname}/verifications/#{params[:id]}/cancel.#{@output}",
|
331
|
+
standard_http_options(params))
|
332
|
+
end
|
333
|
+
|
334
|
+
# Reject a verification
|
335
|
+
# needs: :api_keu, :id
|
336
|
+
def reject_verification(params)
|
337
|
+
submit_request("put", "#{@hostname}/verifications/#{params[:id]}/reject.#{@output}",
|
338
|
+
standard_http_options(params))
|
339
|
+
end
|
340
|
+
|
341
|
+
private
|
342
|
+
|
343
|
+
# Decrypt salt for a specific device and api_key
|
344
|
+
def decrypt_salt(crypted_salts, device_id, api_key)
|
345
|
+
return nil if crypted_salts.nil?
|
346
|
+
|
347
|
+
# Find crypted salt associated with the device
|
348
|
+
crypted_salt = crypted_salts.detect{|cs| cs["device_id"] == device_id.to_i}
|
349
|
+
return nil if crypted_salt.nil?
|
350
|
+
device_id = crypted_salt["device_id"].to_s
|
351
|
+
ct = crypted_salt["value"]
|
352
|
+
|
353
|
+
# Load the private key
|
354
|
+
crypt = KeyFileCreator.new(device_id)
|
355
|
+
private_key = crypt.private_key()
|
356
|
+
|
357
|
+
# Decrypt the crypted salt
|
358
|
+
salt = private_key.private_decrypt(Base64.decode64(ct))
|
359
|
+
return salt
|
360
|
+
end
|
361
|
+
|
362
|
+
# Returns true if the repsonse contains errors
|
363
|
+
def errors?(response)
|
364
|
+
response.nil? || response['error'] || response['errors']
|
365
|
+
end
|
366
|
+
|
367
|
+
def error_message(response)
|
368
|
+
msg = ""
|
369
|
+
if response['error']
|
370
|
+
msg = response['error']
|
371
|
+
elsif response['errors'].kind_of(Array)
|
372
|
+
# TODO
|
373
|
+
msg = response['errors'].inspect
|
374
|
+
end
|
375
|
+
msg
|
376
|
+
end
|
377
|
+
|
378
|
+
def standard_http_options(params, body_params = [])
|
379
|
+
http_options = { :body => {}, :headers => api_key_header(params) }
|
380
|
+
|
381
|
+
body_params.each do |param_name|
|
382
|
+
param_name = param_name.to_sym
|
383
|
+
next if param_name == :api_key # this is now sent as a header field
|
384
|
+
http_options[:body][param_name] = params[param_name] unless params[param_name].nil?
|
385
|
+
end
|
386
|
+
|
387
|
+
http_options
|
388
|
+
end
|
389
|
+
|
390
|
+
def set_api_key_from_response(response)
|
391
|
+
self.api_key = response["api_key"] if response && response["api_key"]
|
392
|
+
end
|
393
|
+
|
394
|
+
def api_key_header(params)
|
395
|
+
self.api_key ? { API_KEY_HEADER => self.api_key } : {}
|
396
|
+
end
|
397
|
+
|
398
|
+
# Submit the request to the server and process the response (for errors)
|
399
|
+
def submit_request(http_verb, request, params)
|
400
|
+
|
401
|
+
if @debug
|
402
|
+
puts "DEBUG: "
|
403
|
+
puts "DEBUG: hostname: #{@hostname}"
|
404
|
+
puts "DEBUG: output: #{@output}"
|
405
|
+
puts "DEBUG: request: #{http_verb.upcase} #{request}"
|
406
|
+
puts "DEBUG: params: #{params}"
|
407
|
+
end
|
408
|
+
|
409
|
+
case http_verb
|
410
|
+
when "put"
|
411
|
+
response = HTTParty.put(request, params)
|
412
|
+
when "delete"
|
413
|
+
response = HTTParty.delete(request, params)
|
414
|
+
when "post"
|
415
|
+
response = HTTParty.post(request, params)
|
416
|
+
else
|
417
|
+
response = HTTParty.get(request, :query => params[:body],
|
418
|
+
:headers => params[:headers])
|
419
|
+
end
|
420
|
+
|
421
|
+
if @debug
|
422
|
+
puts "DEBUG: response: #{response}"
|
423
|
+
puts "DEBUG: "
|
424
|
+
end
|
425
|
+
|
426
|
+
response.to_hash
|
427
|
+
end
|
428
|
+
|
429
|
+
end # class Touchpass::Client
|
430
|
+
|
431
|
+
end # module Touchpass
|
@@ -0,0 +1,51 @@
|
|
1
|
+
|
2
|
+
module Touchpass
|
3
|
+
class Crypt
|
4
|
+
# Encryption method
|
5
|
+
# Options: :md5, :sha1, :sha256, :sha512
|
6
|
+
CRYPTO_PROVIDER = :sha256
|
7
|
+
|
8
|
+
def self.salt(length=10)
|
9
|
+
seeds = ('a'..'z').to_a
|
10
|
+
seeds.concat( ('A'..'Z').to_a )
|
11
|
+
seeds.concat( (0..9).to_a )
|
12
|
+
seeds.concat ['/', '.']
|
13
|
+
seeds.compact!
|
14
|
+
|
15
|
+
salt_string = '$1$'
|
16
|
+
length.times { salt_string << seeds[ rand(seeds.size) ].to_s }
|
17
|
+
|
18
|
+
salt_string
|
19
|
+
end
|
20
|
+
|
21
|
+
def self.encrypt(string)
|
22
|
+
crypto_provider = nil
|
23
|
+
case CRYPTO_PROVIDER
|
24
|
+
when :md5
|
25
|
+
require 'digest/md5' unless defined?(Digest::MD5)
|
26
|
+
crypto_provider = Digest::MD5
|
27
|
+
when :sha1
|
28
|
+
require 'digest/sha1' unless defined?(Digest::SHA1)
|
29
|
+
crypto_provider = Digest::SHA1
|
30
|
+
when :sha256
|
31
|
+
require 'digest/sha2' unless defined?(Digest::SHA2)
|
32
|
+
crypto_provider = Digest::SHA256
|
33
|
+
when :sha512
|
34
|
+
require 'digest/sha2' unless defined?(Digest::SHA2)
|
35
|
+
crypto_provider = Digest::SHA512
|
36
|
+
end
|
37
|
+
return crypto_provider.hexdigest(string)
|
38
|
+
end
|
39
|
+
|
40
|
+
def self.read_key(pem)
|
41
|
+
begin
|
42
|
+
return OpenSSL::PKey::RSA.new(pem)
|
43
|
+
rescue
|
44
|
+
puts "Unable to read key."
|
45
|
+
end
|
46
|
+
return nil
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
end
|
51
|
+
|
@@ -0,0 +1,18 @@
|
|
1
|
+
module Touchpass
|
2
|
+
module Rp
|
3
|
+
class Device
|
4
|
+
include HTTParty
|
5
|
+
#debug_output
|
6
|
+
|
7
|
+
def initialize
|
8
|
+
self.class.base_uri Touchpass.base_uri
|
9
|
+
end
|
10
|
+
|
11
|
+
def get_all(user)
|
12
|
+
http_options = { :body => {}, :headers => { Touchpass::Client::API_KEY_HEADER => Touchpass.api_key } }
|
13
|
+
response = self.class.get("/#{user}/devices.json", http_options)
|
14
|
+
return response.parsed_response["devices"]
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
@@ -0,0 +1,56 @@
|
|
1
|
+
module Touchpass
|
2
|
+
|
3
|
+
# Used to help with creation of public / private key files for a device (by clients)
|
4
|
+
class KeyFileCreator
|
5
|
+
|
6
|
+
DEFAULT_KEYS_PATH = File.join(ENV['HOME'], ".touchpass", "certs")
|
7
|
+
PRIVATE_KEY = "private.pem"
|
8
|
+
PUBLIC_KEY = "public.pem"
|
9
|
+
|
10
|
+
class << self
|
11
|
+
attr_accessor :keys_path
|
12
|
+
end
|
13
|
+
|
14
|
+
attr_accessor :public_key, :private_key, :id
|
15
|
+
|
16
|
+
def initialize(id)
|
17
|
+
@id = id.to_s
|
18
|
+
end
|
19
|
+
|
20
|
+
# Use openssl command to generate key pair.
|
21
|
+
# RSA public key PEM generated by Ruby’s OpenSSL::PKey::RSA are unreadable by OpenSSL.
|
22
|
+
# See: http://barelyenough.org/blog/2008/04/fun-with-public-keys/
|
23
|
+
def generate_keys
|
24
|
+
directory = keys_path
|
25
|
+
prepare_directories directory
|
26
|
+
`openssl genrsa -out #{directory}/#{PRIVATE_KEY} 1024 > /dev/null 2>&1`
|
27
|
+
`openssl rsa -in #{directory}/#{PRIVATE_KEY} -out #{directory}/#{PUBLIC_KEY} -outform PEM -pubout > /dev/null 2>&1`
|
28
|
+
end
|
29
|
+
|
30
|
+
def private_key
|
31
|
+
pem_file = File.join(keys_path, PRIVATE_KEY)
|
32
|
+
return self.read_key(pem_file)
|
33
|
+
end
|
34
|
+
|
35
|
+
def public_key
|
36
|
+
pem_file = File.join(keys_path, PUBLIC_KEY)
|
37
|
+
return self.read_key(pem_file)
|
38
|
+
end
|
39
|
+
|
40
|
+
def read_key(pem_file)
|
41
|
+
return OpenSSL::PKey::RSA.new(File.read(pem_file))
|
42
|
+
end
|
43
|
+
|
44
|
+
def keys_path
|
45
|
+
File.join(self.class.keys_path, @id)
|
46
|
+
end
|
47
|
+
|
48
|
+
private
|
49
|
+
# Create target directory
|
50
|
+
def prepare_directories(dir)
|
51
|
+
FileUtils.makedirs dir
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
Touchpass::KeyFileCreator.keys_path = Touchpass::KeyFileCreator::DEFAULT_KEYS_PATH
|