Created
October 31, 2013 13:30
-
-
Save philayres/7249755 to your computer and use it in GitHub Desktop.
Simple One-Time-Token (nonce) generator / validator. Note: one time use of the token in the database is not included in this example, but validation of timeout and URI parameter correctness is
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
require 'digest' | |
class ApiAuth | |
# Pass params as hash, a secret used to salt the hash, action and controller, and any additional options | |
def self.generate_ottoken params, secret, action, controller, options={} | |
uri_set = [] | |
sign_params = params.to_a | |
sign_params.sort! {|x,y| x <=> y } | |
sign_params.each do |k,v| | |
pname = k.to_s | |
# Use a subset of character substitutions for URI encoding | |
pval = v.to_s.gsub('%', '%25').gsub('&', '%26').gsub('=', '%3D').gsub('?', '%3F') | |
uri_set << "#{pname}=#{pval}" | |
end | |
uri_string = "#{secret}#{controller}/#{action}?#{uri_set.join('&')}" | |
options[:uri_string] = uri_string # Allow testing of the result | |
Digest::SHA256.hexdigest(uri_string) | |
end | |
def self.validate_ottoken params, secret, action, controller, options={} | |
ottoken = params[:ottoken] | |
timestamp = params[:timestamp] | |
raise "Incorrect parameters in validate_ottoken" unless ottoken && timestamp && !ottoken.empty? | |
timedout = (timestamp.to_i - current_timestamp.to_i).abs > max_time_difference(options) | |
raise "Request has timed out" if timedout | |
sign_params = params.dup | |
sign_params.delete(:ottoken) | |
otgen = generate_ottoken(sign_params, secret, action, controller, options) | |
return (otgen == ottoken) | |
end | |
def self.current_timestamp | |
Time.new.strftime('%s%3N') | |
end | |
def self.max_time_difference options={} | |
#Timeout in milliseconds | |
options[:max_timeout] || 30000 | |
end | |
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
require './lib/api_server/api_auth' | |
describe ApiAuth do | |
before(:each) do | |
@api_auth = ApiAuth.new | |
end | |
it "should return same one time token" do | |
res = {} | |
# Check correct creation of an ottoken string with character encodings | |
t = Time.new.strftime('%s%3N') | |
params = {b: 123, c: 'abc', adg: 'hello phil', rand: 'this & this = a question?', timestamp: t, client: 'ajksf7862346fffs1'} | |
ot = ApiAuth.generate_ottoken(params, 'supersecret!', 'do', 'some_controller', res) | |
expected_response = 'supersecret!some_controller/do?adg=hello phil&b=123&c=abc&client=ajksf7862346fffs1&rand=this %26 this %3D a question%3F×tamp='+t | |
res[:uri_string].should == expected_response | |
# Check result is repeatable | |
ot2 = ApiAuth.generate_ottoken(params, 'supersecret!', 'do', 'some_controller', res) | |
ot.should == ot2 | |
# Check a timelag produces a different ottoken result | |
sleep(0.1) | |
t2 = Time.new.strftime('%s%3N') | |
params[:timestamp] = t2 | |
ot_test = ApiAuth.generate_ottoken(params, 'supersecret!', 'do', 'some_controller', res) | |
puts res[:uri_string] | |
puts ot_test | |
res[:uri_string].should_not == expected_response | |
# Check timeout allows some lag before rejecting the timeout | |
sleep(1.0) | |
params[:ottoken] = ot_test | |
otres = ApiAuth.validate_ottoken(params, 'supersecret!', 'do', 'some_controller', res) | |
puts res[:uri_string] | |
otres.should == true | |
# Check ottoken timeout with very short max timeout (100ms) | |
res[:max_timeout]=100 | |
expect {otres = ApiAuth.validate_ottoken(params, 'supersecret!', 'do', 'some_controller', res) }.to raise_error | |
end | |
end |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment