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 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
@@ -0,0 +1,10 @@
1
+ /.bundle/
2
+ /.yardoc
3
+ /Gemfile.lock
4
+ /_yardoc/
5
+ /coverage/
6
+ /doc/
7
+ /pkg/
8
+ /spec/reports/
9
+ /tmp/
10
+ /log/*
data/.rspec ADDED
@@ -0,0 +1,2 @@
1
+ --format documentation
2
+ --color
data/.travis.yml ADDED
@@ -0,0 +1,5 @@
1
+ sudo: false
2
+ language: ruby
3
+ rvm:
4
+ - 2.3.0
5
+ before_install: gem install bundler -v 1.13.6
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in ez_token.gemspec
4
+ gemspec
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
@@ -0,0 +1,6 @@
1
+ require 'bundler/gem_tasks'
2
+ require 'rspec/core/rake_task'
3
+
4
+ RSpec::Core::RakeTask.new(:spec)
5
+
6
+ task :default => :spec
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
@@ -0,0 +1,8 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+ IFS=$'\n\t'
4
+ set -vx
5
+
6
+ bundle install
7
+
8
+ # Do any other automated setup that you need to do here
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
@@ -0,0 +1,3 @@
1
+ module EzTokenVersion
2
+ VERSION = '1.0.0'.freeze
3
+ 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: []