the_signature 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,17 @@
1
+ *.gem
2
+ *.rbc
3
+ .bundle
4
+ .config
5
+ .yardoc
6
+ Gemfile.lock
7
+ InstalledFiles
8
+ _yardoc
9
+ coverage
10
+ doc/
11
+ lib/bundler/man
12
+ pkg
13
+ rdoc
14
+ spec/reports
15
+ test/tmp
16
+ test/version_tmp
17
+ tmp
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in the_signature.gemspec
4
+ gemspec
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2013 Ramario Depass
2
+
3
+ MIT License
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining
6
+ a copy of this software and associated documentation files (the
7
+ "Software"), to deal in the Software without restriction, including
8
+ without limitation the rights to use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and to
10
+ permit persons to whom the Software is furnished to do so, subject to
11
+ the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1,29 @@
1
+ # TheSignature
2
+
3
+ TODO: Write a gem description
4
+
5
+ ## Installation
6
+
7
+ Add this line to your application's Gemfile:
8
+
9
+ gem 'the_signature'
10
+
11
+ And then execute:
12
+
13
+ $ bundle
14
+
15
+ Or install it yourself as:
16
+
17
+ $ gem install the_signature
18
+
19
+ ## Usage
20
+
21
+ TODO: Write usage instructions here
22
+
23
+ ## Contributing
24
+
25
+ 1. Fork it
26
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
27
+ 3. Commit your changes (`git commit -am 'Add some feature'`)
28
+ 4. Push to the branch (`git push origin my-new-feature`)
29
+ 5. Create new Pull Request
@@ -0,0 +1 @@
1
+ require "bundler/gem_tasks"
@@ -0,0 +1,224 @@
1
+ require 'openssl'
2
+
3
+ require 'the_signature/query_encoder'
4
+
5
+ module TheSignature
6
+ class AuthenticationError < RuntimeError; end
7
+
8
+ class Token
9
+ attr_reader :key, :secret
10
+
11
+ def initialize(key, secret)
12
+ @key, @secret = key, secret
13
+ end
14
+
15
+ def sign(request)
16
+ request.sign(self)
17
+ end
18
+ end
19
+
20
+ class Request
21
+ attr_accessor :path, :query_hash
22
+
23
+ include QueryEncoder
24
+
25
+ # http://www.w3.org/TR/NOTE-datetime
26
+ ISO8601 = "%Y-%m-%dT%H:%M:%SZ"
27
+
28
+ def initialize(method, path, query)
29
+ raise ArgumentError, "Expected string" unless path.kind_of?(String)
30
+ raise ArgumentError, "Expected hash" unless query.kind_of?(Hash)
31
+
32
+ query_hash = {}
33
+ auth_hash = {}
34
+ query.each do |key, v|
35
+ k = key.to_s.downcase
36
+ k[0..4] == 'auth_' ? auth_hash[k] = v : query_hash[k] = v
37
+ end
38
+
39
+ @method = method.upcase
40
+ @path, @query_hash, @auth_hash = path, query_hash, auth_hash
41
+ @signed = false
42
+ end
43
+
44
+ # Sign the request with the given token, and return the computed
45
+ # authentication parameters
46
+ #
47
+ def sign(token)
48
+ @auth_hash = {
49
+ :auth_version => "1.0",
50
+ :auth_key => token.key,
51
+ :auth_timestamp => Time.now.to_i.to_s
52
+ }
53
+ @auth_hash[:auth_signature] = signature(token)
54
+
55
+ @signed = true
56
+
57
+ return @auth_hash
58
+ end
59
+
60
+ # Authenticates the request with a token
61
+ #
62
+ # Raises an AuthenticationError if the request is invalid.
63
+ # AuthenticationError exception messages are designed to be exposed to API
64
+ # consumers, and should help them correct errors generating signatures
65
+ #
66
+ # Timestamp: Unless timestamp_grace is set to nil (which allows this check
67
+ # to be skipped), AuthenticationError will be raised if the timestamp is
68
+ # missing or further than timestamp_grace period away from the real time
69
+ # (defaults to 10 minutes)
70
+ #
71
+ # Signature: Raises AuthenticationError if the signature does not match
72
+ # the computed HMAC. The error contains a hint for how to sign.
73
+ #
74
+ def authenticate_by_token!(token, timestamp_grace = 600)
75
+ # Validate that your code has provided a valid token. This does not
76
+ # raise an AuthenticationError since passing tokens with empty secret is
77
+ # a code error which should be fixed, not reported to the API's consumer
78
+ if token.secret.nil? || token.secret.empty?
79
+ raise "Provided token is missing secret"
80
+ end
81
+
82
+ validate_version!
83
+ validate_timestamp!(timestamp_grace)
84
+ validate_signature!(token)
85
+ true
86
+ end
87
+
88
+ # Authenticate the request with a token, but rather than raising an
89
+ # exception if the request is invalid, simply returns false
90
+ #
91
+ def authenticate_by_token(token, timestamp_grace = 600)
92
+ authenticate_by_token!(token, timestamp_grace)
93
+ rescue AuthenticationError
94
+ false
95
+ end
96
+
97
+ # Authenticate a request
98
+ #
99
+ # Takes a block which will be called with the auth_key from the request,
100
+ # and which should return a Signature::Token (or nil if no token can be
101
+ # found for the key)
102
+ #
103
+ # Raises errors in the same way as authenticate_by_token!
104
+ #
105
+ def authenticate(timestamp_grace = 600)
106
+ raise ArgumentError, "Block required" unless block_given?
107
+ key = @auth_hash['auth_key']
108
+ raise AuthenticationError, "Missing parameter: auth_key" unless key
109
+ token = yield key
110
+ unless token
111
+ raise AuthenticationError, "Unknown auth_key"
112
+ end
113
+ authenticate_by_token!(token, timestamp_grace)
114
+ return token
115
+ end
116
+
117
+ # Authenticate a request asynchronously
118
+ #
119
+ # This method is useful it you're running a server inside eventmachine and
120
+ # need to lookup the token asynchronously.
121
+ #
122
+ # The block is passed an auth key and a deferrable which should succeed
123
+ # with the token, or fail if the token cannot be found
124
+ #
125
+ # This method returns a deferrable which succeeds with the valid token, or
126
+ # fails with an AuthenticationError which can be used to pass the error
127
+ # back to the user
128
+ #
129
+ def authenticate_async(timestamp_grace = 600)
130
+ raise ArgumentError, "Block required" unless block_given?
131
+ df = EM::DefaultDeferrable.new
132
+
133
+ key = @auth_hash['auth_key']
134
+
135
+ unless key
136
+ df.fail(AuthenticationError.new("Missing parameter: auth_key"))
137
+ return
138
+ end
139
+
140
+ token_df = yield key
141
+ token_df.callback { |token|
142
+ begin
143
+ authenticate_by_token!(token, timestamp_grace)
144
+ df.succeed(token)
145
+ rescue AuthenticationError => e
146
+ df.fail(e)
147
+ end
148
+ }
149
+ token_df.errback {
150
+ df.fail(AuthenticationError.new("Unknown auth_key"))
151
+ }
152
+ ensure
153
+ return df
154
+ end
155
+
156
+ # Expose the authentication parameters for a signed request
157
+ #
158
+ def auth_hash
159
+ raise "Request not signed" unless @signed
160
+ @auth_hash
161
+ end
162
+
163
+ # Query parameters merged with the computed authentication parameters
164
+ #
165
+ def signed_params
166
+ @query_hash.merge(auth_hash)
167
+ end
168
+
169
+ private
170
+
171
+ def signature(token)
172
+ digest = OpenSSL::Digest::SHA256.new
173
+ OpenSSL::HMAC.hexdigest(digest, token.secret, string_to_sign)
174
+ end
175
+
176
+ def string_to_sign
177
+ [@method, @path, parameter_string].join("\n")
178
+ end
179
+
180
+ def parameter_string
181
+ param_hash = @query_hash.merge(@auth_hash || {})
182
+
183
+ # Convert keys to lowercase strings
184
+ hash = {}; param_hash.each { |k,v| hash[k.to_s.downcase] = v }
185
+
186
+ # Exclude signature from signature generation!
187
+ hash.delete("auth_signature")
188
+
189
+ hash.sort.map do |k, v|
190
+ QueryEncoder.encode_param_without_escaping(k, v)
191
+ end.join('&')
192
+ end
193
+
194
+ def validate_version!
195
+ version = @auth_hash["auth_version"]
196
+ raise AuthenticationError, "Version required" unless version
197
+ raise AuthenticationError, "Version not supported" unless version == '1.0'
198
+ end
199
+
200
+ def validate_timestamp!(grace)
201
+ return true if grace.nil?
202
+
203
+ timestamp = @auth_hash["auth_timestamp"]
204
+ error = (timestamp.to_i - Time.now.to_i).abs
205
+ raise AuthenticationError, "Timestamp required" unless timestamp
206
+ if error >= grace
207
+ raise AuthenticationError, "Timestamp expired: Given timestamp "\
208
+ "(#{Time.at(timestamp.to_i).utc.strftime(ISO8601)}) "\
209
+ "not within #{grace}s of server time "\
210
+ "(#{Time.now.utc.strftime(ISO8601)})"
211
+ end
212
+ return true
213
+ end
214
+
215
+ def validate_signature!(token)
216
+ unless @auth_hash["auth_signature"] == signature(token)
217
+ raise AuthenticationError, "Invalid signature: you should have "\
218
+ "sent HmacSHA256Hex(#{string_to_sign.inspect}, your_secret_key)"\
219
+ ", but you sent #{@auth_hash["auth_signature"].inspect}"
220
+ end
221
+ return true
222
+ end
223
+ end
224
+ end
@@ -0,0 +1,3 @@
1
+ module TheSignature
2
+ VERSION = "0.0.1"
3
+ end
@@ -0,0 +1,20 @@
1
+ # -*- encoding: utf-8 -*-
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'the_signature/version'
5
+
6
+ Gem::Specification.new do |gem|
7
+ gem.name = "the_signature"
8
+ gem.version = TheSignature::VERSION
9
+ gem.platform = Gem::Platform::RUBY
10
+ gem.authors = ["Ramario Depass"]
11
+ gem.email = ["RamarioDepass@gmail.com"]
12
+ gem.description = %q{Simple key/secret based authentication for apis}
13
+ gem.summary = %q{Simple key/secret based authentication for apis}
14
+ gem.homepage = "http://github.com/ramariodepass/the_signature"
15
+
16
+ gem.files = `git ls-files`.split($/)
17
+ gem.executables = gem.files.grep(%r{^bin/}).map{ |f| File.basename(f) }
18
+ gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
19
+ gem.require_paths = ["lib"]
20
+ end
metadata ADDED
@@ -0,0 +1,53 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: the_signature
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ prerelease:
6
+ platform: ruby
7
+ authors:
8
+ - Ramario Depass
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2013-08-24 00:00:00.000000000 Z
13
+ dependencies: []
14
+ description: Simple key/secret based authentication for apis
15
+ email:
16
+ - RamarioDepass@gmail.com
17
+ executables: []
18
+ extensions: []
19
+ extra_rdoc_files: []
20
+ files:
21
+ - .gitignore
22
+ - Gemfile
23
+ - LICENSE.txt
24
+ - README.md
25
+ - Rakefile
26
+ - lib/the_signature.rb
27
+ - lib/the_signature/version.rb
28
+ - the_signature.gemspec
29
+ homepage: http://github.com/ramariodepass/the_signature
30
+ licenses: []
31
+ post_install_message:
32
+ rdoc_options: []
33
+ require_paths:
34
+ - lib
35
+ required_ruby_version: !ruby/object:Gem::Requirement
36
+ none: false
37
+ requirements:
38
+ - - ! '>='
39
+ - !ruby/object:Gem::Version
40
+ version: '0'
41
+ required_rubygems_version: !ruby/object:Gem::Requirement
42
+ none: false
43
+ requirements:
44
+ - - ! '>='
45
+ - !ruby/object:Gem::Version
46
+ version: '0'
47
+ requirements: []
48
+ rubyforge_project:
49
+ rubygems_version: 1.8.25
50
+ signing_key:
51
+ specification_version: 3
52
+ summary: Simple key/secret based authentication for apis
53
+ test_files: []