ez_token 1.0.0
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/.gitignore +10 -0
- data/.rspec +2 -0
- data/.travis.yml +5 -0
- data/Gemfile +4 -0
- data/README.md +38 -0
- data/Rakefile +6 -0
- data/bin/console +14 -0
- data/bin/setup +8 -0
- data/ez_token.gemspec +28 -0
- data/lib/ez_token/version.rb +3 -0
- data/lib/ez_token.rb +139 -0
- metadata +110 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 2b13ed8392a2ad5fe9ebacf4572a7f1d7ffbe240
|
4
|
+
data.tar.gz: 7d9b30e3c173720d800d60dbf75290b8fbb6923b
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 2b6e4aca886cb3736c14d9d50bb197c7a91364d3b58bda6c5c656aec143eae6280934bd74e7c20a1f6734c0c9cbf580a2d9e444440f21e96fbafd76227c39dd9
|
7
|
+
data.tar.gz: e91c22e94e74832cea0a210b4cee488d1c299b52a852e5f08782b44dafd08b19637ea39354a0d276803d9beaa7831dbbf89e8e8832e3e43de6eb5ca84981a4dc
|
data/.gitignore
ADDED
data/.rspec
ADDED
data/.travis.yml
ADDED
data/Gemfile
ADDED
data/README.md
ADDED
@@ -0,0 +1,38 @@
|
|
1
|
+
# EzToken
|
2
|
+
|
3
|
+
Generates and verifies SHA1-based Tokens. Automatically converts to/from String, Array and Hash objects.
|
4
|
+
|
5
|
+
## Installation
|
6
|
+
|
7
|
+
Add this line to your application's Gemfile:
|
8
|
+
|
9
|
+
```ruby
|
10
|
+
gem 'ez_token'
|
11
|
+
```
|
12
|
+
|
13
|
+
And then execute:
|
14
|
+
|
15
|
+
$ bundle
|
16
|
+
|
17
|
+
Or install it yourself as:
|
18
|
+
|
19
|
+
$ gem install ez_token
|
20
|
+
|
21
|
+
## Usage
|
22
|
+
|
23
|
+
token = EzToken.generate(['a', {a: 'a'}, 'str'])
|
24
|
+
valid = EzToken.verify?(token)
|
25
|
+
or pass a timestamp
|
26
|
+
valid = EzToken.verify?(token, 300) # is the token more than 300 seconds old
|
27
|
+
original_payload = EzToken.verify(token)
|
28
|
+
|
29
|
+
## Development
|
30
|
+
|
31
|
+
After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake spec` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
|
32
|
+
|
33
|
+
To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and tags, and push the `.gem` file to [rubygems.org](https://rubygems.org).
|
34
|
+
|
35
|
+
## Contributing
|
36
|
+
|
37
|
+
Bug reports and pull requests are welcome on GitHub at https://github.com/devforce/ez_token.
|
38
|
+
|
data/Rakefile
ADDED
data/bin/console
ADDED
@@ -0,0 +1,14 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require 'bundler/setup'
|
4
|
+
require 'ez_token'
|
5
|
+
|
6
|
+
# You can add fixtures and/or initialization code here to make experimenting
|
7
|
+
# with your gem easier. You can also use a different console, if you like.
|
8
|
+
|
9
|
+
# (If you use this, don't forget to add pry to your Gemfile!)
|
10
|
+
# require 'pry'
|
11
|
+
# Pry.start
|
12
|
+
|
13
|
+
require 'irb'
|
14
|
+
IRB.start
|
data/bin/setup
ADDED
data/ez_token.gemspec
ADDED
@@ -0,0 +1,28 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require 'ez_token/version'
|
5
|
+
|
6
|
+
Gem::Specification.new do |spec|
|
7
|
+
spec.name = 'ez_token'
|
8
|
+
spec.version = EzTokenVersion::VERSION
|
9
|
+
spec.authors = ['Mike Papper']
|
10
|
+
spec.email = ['bodaro@gmail.com']
|
11
|
+
|
12
|
+
spec.summary = 'SHA1 Token creation from Ruby Arrays, Hash or Strings.'
|
13
|
+
spec.description = 'Allows Ruby String, Hash and Array objects to be encoded and decoded'
|
14
|
+
spec.homepage = 'https://github.com/devforce/ez-token'
|
15
|
+
|
16
|
+
spec.files = `git ls-files -z`.split("\x0").reject do |f|
|
17
|
+
f.match(%r{^(test|spec|features)/})
|
18
|
+
end
|
19
|
+
spec.bindir = 'exe'
|
20
|
+
spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
|
21
|
+
spec.require_paths = ['lib']
|
22
|
+
|
23
|
+
spec.add_dependency 'type_caster'
|
24
|
+
|
25
|
+
spec.add_development_dependency 'bundler', '~> 1.13'
|
26
|
+
spec.add_development_dependency 'rake', '~> 10.0'
|
27
|
+
spec.add_development_dependency 'rspec', '~> 3.0'
|
28
|
+
end
|
data/lib/ez_token.rb
ADDED
@@ -0,0 +1,139 @@
|
|
1
|
+
require 'ez_token/version'
|
2
|
+
require 'type_caster'
|
3
|
+
|
4
|
+
# This class helps to create and decode SHA1 encoded tokens
|
5
|
+
# utf-8 strings are supported:
|
6
|
+
class EzToken
|
7
|
+
attr_reader :secret
|
8
|
+
|
9
|
+
# pass your secret
|
10
|
+
def initialize(sec)
|
11
|
+
@secret = sec
|
12
|
+
end
|
13
|
+
|
14
|
+
# Return a token representing the params + the timestamp + a digest
|
15
|
+
# the digest is a generate a 40 hex-character (640bit) SHA1 encoding of the params + timestamp + secret
|
16
|
+
# params can be a String, an Array (each item in the array is joined with a &)
|
17
|
+
# or a Hash (each item in the hash is of the form key=value and each of those are joined with &).
|
18
|
+
# We url encode the array items, the hash values and the String value. Array items and Hash values are turned into
|
19
|
+
# their String representation.
|
20
|
+
# If params is a Hash, the keys cannot contain a & or =. Values that are nil will be replaced by the empty
|
21
|
+
# string upon decoding/verify.
|
22
|
+
# For arrays, nil items in an array will be replaced by the string representation of nil which is ''.
|
23
|
+
def generate(params, now = Time.now.to_i)
|
24
|
+
# Note: for oddly utf-8 encoded strings empty? will fail, we circumvent this by testing the length first
|
25
|
+
raise 'EzToken.generate: Params cannot be empty' if params.empty?
|
26
|
+
|
27
|
+
# order hash by keys
|
28
|
+
return get_hash_token(params, now) if params.class == Hash
|
29
|
+
return get_array_token(params, now) if params.class == Array
|
30
|
+
return get_string_token(params, now) if params.class == String
|
31
|
+
raise "EzToken.generate: Params of class #{params.class} is invalid. Must be String, Hash or Array"
|
32
|
+
end
|
33
|
+
|
34
|
+
# Decode token that was originally encoded in the form <payload>&ts=<integer timestamp>&SECRET
|
35
|
+
# where payload can be a string separated by &. Take the <payload> and turn into
|
36
|
+
# a String, Hash or Array depending on its format:
|
37
|
+
# if there are no & and no '=' its a URL encoded String
|
38
|
+
# if there are just & its an Array (separated by &)
|
39
|
+
# otherwise it is a hash of the form key=value&key=value...
|
40
|
+
# If interval is non-nil it represents the length in seconds for the max age of this token
|
41
|
+
# We return false if the decoding didnt work, we return "expired" if the interval is non-nil and
|
42
|
+
# the token has expired
|
43
|
+
# Otherwise we return the original payload encoded in the token.
|
44
|
+
def verify(token, interval = nil, allow_expired = false)
|
45
|
+
payload, ts = decode_token_into_payload(token)
|
46
|
+
return false if payload == false
|
47
|
+
|
48
|
+
is_expired = !interval.nil? && ts + interval < Time.now.to_i
|
49
|
+
return 'expired' if allow_expired && is_expired
|
50
|
+
return false if is_expired
|
51
|
+
|
52
|
+
return TypeCaster.new(payload).uri_decode if payload !~ /[&=]/ # a (url encoded) string
|
53
|
+
return decode_array(payload) if payload !~ /=/ # its an Array - it has a & in it
|
54
|
+
decode_hash(payload)
|
55
|
+
end
|
56
|
+
|
57
|
+
def verify!(token, interval = nil)
|
58
|
+
params = verify(token, interval, true)
|
59
|
+
raise 'EzToken.verify!: Token is invalid' if params == false
|
60
|
+
raise 'EzToken.verify!: Token has expired' if params == 'expired'
|
61
|
+
params
|
62
|
+
end
|
63
|
+
|
64
|
+
def verify?(token, interval = nil)
|
65
|
+
params = verify(token, interval, true)
|
66
|
+
return false if params == false || params == 'expired'
|
67
|
+
true
|
68
|
+
end
|
69
|
+
|
70
|
+
private
|
71
|
+
|
72
|
+
def get_hash_token(params, now)
|
73
|
+
keys = params.keys.sort
|
74
|
+
|
75
|
+
payload = keys.map do |key|
|
76
|
+
msg = 'EzToken.generate: Params for Token generation cannot have & or = characters in the key - please encode this'
|
77
|
+
raise msg if key =~ /[&=]/
|
78
|
+
val = params[key].to_s # get the string representation of the value
|
79
|
+
"#{key}=#{TypeCaster.new(val).uri_encode}&" # FYI: nil is encoded as 'nil'
|
80
|
+
end.join
|
81
|
+
payload.chomp!('&')
|
82
|
+
payload = '=' if payload.empty? # encode so EzToken decode knows it an empty Hash
|
83
|
+
encode_token(payload, now)
|
84
|
+
end
|
85
|
+
|
86
|
+
def get_array_token(params, now)
|
87
|
+
encoded = params.map { |i| TypeCaster.new(i.to_s).uri_encode }
|
88
|
+
payload = encoded.join('&')
|
89
|
+
payload += '&' # a trailing & to ensure the "decoding" of this EzToken sees this as an array (in case there is just 1 item)
|
90
|
+
encode_token(payload, now)
|
91
|
+
end
|
92
|
+
|
93
|
+
def get_string_token(params, now)
|
94
|
+
encode_token(TypeCaster.new(params).uri_encode, now)
|
95
|
+
end
|
96
|
+
|
97
|
+
def encode_token(payload, now)
|
98
|
+
"#{payload}&ts=#{now}&#{Digest::SHA1.hexdigest("#{payload}&ts=#{now}&#{secret}")[0..39]}"
|
99
|
+
end
|
100
|
+
|
101
|
+
def decode_token_into_payload(token)
|
102
|
+
return [false, nil] if token.empty?
|
103
|
+
|
104
|
+
digest_pos = token.rindex('&')
|
105
|
+
return [false, nil] if digest_pos.nil?
|
106
|
+
|
107
|
+
ts_pos = token.rindex('&', digest_pos - 1)
|
108
|
+
return [false, nil] if ts_pos.nil?
|
109
|
+
|
110
|
+
extract_payload(token, ts_pos, digest_pos)
|
111
|
+
end
|
112
|
+
|
113
|
+
# return the payload and tisemstamp from the token.
|
114
|
+
# We expect ts_pos to be the index on the timeytsamp in the token
|
115
|
+
# and digest pos is the index of the digest in the token (the last component of the token)
|
116
|
+
def extract_payload(token, ts_pos, digest_pos)
|
117
|
+
payload = token[0...ts_pos]
|
118
|
+
ts = token[ts_pos + 4...digest_pos].to_i
|
119
|
+
digest = token[digest_pos + 1..-1]
|
120
|
+
calculated_digest = Digest::SHA1.hexdigest("#{payload}&ts=#{ts}&#{secret}")[0..39]
|
121
|
+
# debugging only: puts("EzToken.extract_payload: digest #{digest} and calculated digest #{calculated_digest}")
|
122
|
+
|
123
|
+
return [false, ts] if digest != calculated_digest
|
124
|
+
[payload, ts]
|
125
|
+
end
|
126
|
+
|
127
|
+
def decode_array(payload)
|
128
|
+
payload.split('&').map { |i| TypeCaster.new(i).uri_decode }
|
129
|
+
end
|
130
|
+
|
131
|
+
def decode_hash(payload)
|
132
|
+
return {} if payload == '=' # an empty hash
|
133
|
+
payload.split('&').each_with_object({}) do |item, result|
|
134
|
+
key, value, error = item.split('=')
|
135
|
+
raise "Invalid Hash for payload #{payload}" if key.empty? || error
|
136
|
+
result[key.to_sym] = TypeCaster.new(value).uri_decode || ''
|
137
|
+
end # all symbols
|
138
|
+
end
|
139
|
+
end
|
metadata
ADDED
@@ -0,0 +1,110 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: ez_token
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 1.0.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Mike Papper
|
8
|
+
autorequire:
|
9
|
+
bindir: exe
|
10
|
+
cert_chain: []
|
11
|
+
date: 2017-03-29 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: type_caster
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - ">="
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '0'
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - ">="
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '0'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: bundler
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - "~>"
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '1.13'
|
34
|
+
type: :development
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - "~>"
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '1.13'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: rake
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - "~>"
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '10.0'
|
48
|
+
type: :development
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - "~>"
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '10.0'
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: rspec
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - "~>"
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '3.0'
|
62
|
+
type: :development
|
63
|
+
prerelease: false
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - "~>"
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: '3.0'
|
69
|
+
description: Allows Ruby String, Hash and Array objects to be encoded and decoded
|
70
|
+
email:
|
71
|
+
- bodaro@gmail.com
|
72
|
+
executables: []
|
73
|
+
extensions: []
|
74
|
+
extra_rdoc_files: []
|
75
|
+
files:
|
76
|
+
- ".gitignore"
|
77
|
+
- ".rspec"
|
78
|
+
- ".travis.yml"
|
79
|
+
- Gemfile
|
80
|
+
- README.md
|
81
|
+
- Rakefile
|
82
|
+
- bin/console
|
83
|
+
- bin/setup
|
84
|
+
- ez_token.gemspec
|
85
|
+
- lib/ez_token.rb
|
86
|
+
- lib/ez_token/version.rb
|
87
|
+
homepage: https://github.com/devforce/ez-token
|
88
|
+
licenses: []
|
89
|
+
metadata: {}
|
90
|
+
post_install_message:
|
91
|
+
rdoc_options: []
|
92
|
+
require_paths:
|
93
|
+
- lib
|
94
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
95
|
+
requirements:
|
96
|
+
- - ">="
|
97
|
+
- !ruby/object:Gem::Version
|
98
|
+
version: '0'
|
99
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
100
|
+
requirements:
|
101
|
+
- - ">="
|
102
|
+
- !ruby/object:Gem::Version
|
103
|
+
version: '0'
|
104
|
+
requirements: []
|
105
|
+
rubyforge_project:
|
106
|
+
rubygems_version: 2.5.1
|
107
|
+
signing_key:
|
108
|
+
specification_version: 4
|
109
|
+
summary: SHA1 Token creation from Ruby Arrays, Hash or Strings.
|
110
|
+
test_files: []
|