jwt_claims 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: a503400ce945a893631a78c8c38af743587ce810
4
+ data.tar.gz: 3c7ecfee679bd06e177ef4ca881dbb091d524547
5
+ SHA512:
6
+ metadata.gz: 7e70c10bdb07d77aa1958744d53588eeb6caca3de41eb4a8959cd77d0dfe4151a7d40c393a3437cf8b9e521e04f1e082a2d2cba04a022bd9d2762f37aec30e45
7
+ data.tar.gz: b7ac29a45cfb4992b1077d076815c8ebf098a7ad37fd90e3172504f833be6693b198e3f1063fa2c5dc06184f777d6f3cb6a01c6b8303507156ab2bbc598fa10d
data/.gitignore ADDED
@@ -0,0 +1,35 @@
1
+ # https://raw.githubusercontent.com/github/gitignore/master/Ruby.gitignore
2
+
3
+ *.gem
4
+ *.rbc
5
+ /.config
6
+ /coverage/
7
+ /InstalledFiles
8
+ /pkg/
9
+ /spec/reports/
10
+ /test/tmp/
11
+ /test/version_tmp/
12
+ /tmp/
13
+
14
+ ## Documentation cache and generated files:
15
+ /.yardoc/
16
+ /_yardoc/
17
+ /doc/
18
+ /rdoc/
19
+
20
+ ## Environment normalisation:
21
+ /.bundle/
22
+ /vendor/bundle
23
+ /lib/bundler/man/
24
+
25
+ # for a library or gem, you might want to ignore these files since the code is
26
+ # intended to run in multiple environments; otherwise, check them in:
27
+ Gemfile.lock
28
+ .ruby-version
29
+ .ruby-gemset
30
+
31
+ # unless supporting rvm < 1.11.0 or doing something fancy, ignore this:
32
+ .rvmrc
33
+
34
+ # custom
35
+ /spec/examples.txt
data/.rspec ADDED
@@ -0,0 +1,2 @@
1
+ --color
2
+ --require spec_helper
data/Gemfile ADDED
@@ -0,0 +1,7 @@
1
+ source 'https://rubygems.org'
2
+
3
+ gemspec
4
+
5
+ gem 'pry-byebug', '~> 3.1', require: false
6
+ gem 'simplecov', '~> 0.10', require: false
7
+ gem 'yard', '~> 0.8', require: false
data/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2015 Gary Fleshman
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in
13
+ all copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
+ THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,46 @@
1
+ # JWT Claims
2
+
3
+ ## Verification of a JWT (JSON Web Token) Claims Set for Ruby
4
+
5
+ ### Description
6
+
7
+ A Ruby implementation of the JSON Web Token (JWT) registered claims, [RFC 7519][rfc7519]
8
+
9
+ ## Installation
10
+ gem install jwt_claims
11
+
12
+ ## Usage
13
+
14
+ ### JwtClaims.verify(jwt, options)
15
+
16
+ Returns a hash, either:
17
+ * \{:ok, claims\}, a JWT claims set map, if the JWT Message Authentication Code (MAC), or signature, is verified and the registered claims are also verified
18
+ * \{:error, [rejected_claims]\}, a list of any registered claims that fail validation, if the JWT MAC is verified
19
+ * \{:error, 'invalid JWT'\} if the JWT MAC is not verified
20
+ * \{:error, 'invalid input'\} otherwise
21
+
22
+ `jwt` (required) is a JSON web token string
23
+
24
+ `options` (required) map
25
+
26
+ * **alg** (optional, default: `'HS256'`)
27
+ * **key** (required unless alg is 'none')
28
+
29
+ Please refer to the [JSON Web Token][json_web_token] gem for additional guidance regarding JWT options
30
+
31
+ Example
32
+
33
+ ```ruby
34
+
35
+ secure_jwt_example = 'eyJ0eXAiOiJKV1QiLA0KICJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJqb2UiLA0KICJleHAiOjEzMDA4MTkzODAsDQogImh0dHA6Ly9leGFt.cGxlLmNvbS9pc19yb290Ijp0cnVlfQ.dBjftJeZ4CVP-mB92K27uhbUJU1p1r_wW1gFWFOEjXk'
36
+
37
+ # verify with default algorithm, HMAC SHA256
38
+ {:ok, verified_claims} = JwtClaims.verify(secure_jwt_example, key: 'gZH75aKtMN3Yj0iPS4hcgUuTwjAzZr9C')
39
+
40
+ ```
41
+
42
+ ### Supported Ruby versions
43
+ Ruby 2.0.0 and up
44
+
45
+ [rfc7519]: http://tools.ietf.org/html/rfc7519
46
+ [json_web_token]: https://github.com/garyf/json_web_token
@@ -0,0 +1,21 @@
1
+ # -*- encoding: utf-8 -*-
2
+ $:.push File.expand_path("../lib", __FILE__)
3
+ require 'jwt_claims/version'
4
+
5
+ Gem::Specification.new do |s|
6
+ s.author = 'Gary Fleshman'
7
+ s.email = 'gfleshman@newforge-tech.com'
8
+ s.files = `git ls-files`.split("\n")
9
+ s.homepage = 'https://github.com/garyf/jwt_claims'
10
+ s.name = 'jwt_claims'
11
+ s.platform = Gem::Platform::RUBY
12
+ s.summary = 'JSON Web Token (JWT) Claims for Ruby'
13
+ s.version = JwtClaims::VERSION
14
+ # recommended
15
+ s.license = 'MIT'
16
+ # optional
17
+ s.add_runtime_dependency('json_web_token', '~> 0.3')
18
+ s.add_development_dependency('rspec', '~> 3.3')
19
+ s.description = 'Modular implementation of JSON Web Token (JWT) Claims'
20
+ s.required_ruby_version = '>= 2.0.0'
21
+ end
@@ -0,0 +1,28 @@
1
+ require 'jwt_claims/string_or_uri'
2
+
3
+ module JwtClaims
4
+ module Claim
5
+ # Audience
6
+ # @see https://tools.ietf.org/html/rfc7519#section-4.1.3
7
+ module Aud
8
+
9
+ module_function
10
+
11
+ # @param aud [Array, String] the intended recipients of the JWT
12
+ # @param options [Hash] (key aud:) expected audience (or recipient) to match with claim
13
+ # @return [true, false] whether to reject the claim
14
+ def reject?(aud, options = {})
15
+ audience = aud.is_a?(Array) ? aud : [aud]
16
+ expected_recipient = options.fetch(:aud, nil)
17
+ !present_and_member?(audience, expected_recipient)
18
+ end
19
+
20
+ def present_and_member?(collection, value)
21
+ StringOrUri.present?(value) &&
22
+ collection.include?(value)
23
+ end
24
+
25
+ private_class_method :present_and_member?
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,18 @@
1
+ module JwtClaims
2
+ module Claim
3
+ # Expiration time
4
+ # @see https://tools.ietf.org/html/rfc7519#section-4.1.4
5
+ module Exp
6
+
7
+ module_function
8
+
9
+ # @param numeric_date [Numeric] the number of seconds from 1970-01-01T00:00:00Z UTC
10
+ # until the specified UTC date/time; non-integer values may be used
11
+ # @return [true, false] whether to reject the claim
12
+ def reject?(numeric_date, options = {})
13
+ return true unless numeric_date.is_a?(Numeric)
14
+ numeric_date <= Time.now.to_i
15
+ end
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,19 @@
1
+ module JwtClaims
2
+ module Claim
3
+ # Issued at
4
+ # @see https://tools.ietf.org/html/rfc7519#section-4.1.6
5
+ module Iat
6
+
7
+ module_function
8
+
9
+ # @param numeric_date [Numeric] the number of seconds from 1970-01-01T00:00:00Z UTC
10
+ # until the specified UTC date/time, and non-integer values may be used
11
+ # @param options [Hash] optional, ignored
12
+ # @return [true, false] whether to reject the claim
13
+ def reject?(numeric_date, _options = {})
14
+ return true unless numeric_date.is_a?(Numeric)
15
+ numeric_date >= Time.now.to_i
16
+ end
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,20 @@
1
+ require 'jwt_claims/string_or_uri'
2
+
3
+ module JwtClaims
4
+ module Claim
5
+ # Issuer
6
+ # @see https://tools.ietf.org/html/rfc7519#section-4.1.1
7
+ module Iss
8
+
9
+ module_function
10
+
11
+ # @param iss [String] the principal that issued the JWT
12
+ # @param options [Hash] (key iss:) expected issuer to match with claim
13
+ # @return [true, false] whether to reject the claim
14
+ def reject?(iss, options = {})
15
+ expected_issuer = options.fetch(:iss, nil)
16
+ !StringOrUri.present_and_equal?(iss, expected_issuer)
17
+ end
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,20 @@
1
+ require 'jwt_claims/string_or_uri'
2
+
3
+ module JwtClaims
4
+ module Claim
5
+ # JWT ID
6
+ # @see https://tools.ietf.org/html/rfc7519#section-4.1.7
7
+ module Jti
8
+
9
+ module_function
10
+
11
+ # @param jti [String] a unique identifier for the JWT
12
+ # @param options [Hash] (key jti:) expected JWT ID to match with claim
13
+ # @return [true, false] whether to reject the claim
14
+ def reject?(jti, options = {})
15
+ expected_jti = options.fetch(:jti, nil)
16
+ !StringOrUri.present_and_equal?(jti, expected_jti)
17
+ end
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,18 @@
1
+ module JwtClaims
2
+ module Claim
3
+ # Not before
4
+ # @see https://tools.ietf.org/html/rfc7519#section-4.1.5
5
+ module Nbf
6
+
7
+ module_function
8
+
9
+ # @param numeric_date [Numeric] the number of seconds from 1970-01-01T00:00:00Z UTC
10
+ # until the specified UTC date/time; non-integer values may be used
11
+ # @return [true, false] whether to reject the claim
12
+ def reject?(numeric_date, options = {})
13
+ return true unless numeric_date.is_a?(Numeric)
14
+ numeric_date > Time.now.to_i
15
+ end
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,20 @@
1
+ require 'jwt_claims/string_or_uri'
2
+
3
+ module JwtClaims
4
+ module Claim
5
+ # Subject
6
+ # @see https://tools.ietf.org/html/rfc7519#section-4.1.2
7
+ module Sub
8
+
9
+ module_function
10
+
11
+ # @param sub [String] the principal that is the subject of the JWT
12
+ # @param options [Hash] (key sub:) expected subject to match with claim
13
+ # @return [true, false] whether to reject the claim
14
+ def reject?(sub, options = {})
15
+ expected_subject = options.fetch(:sub, nil)
16
+ !StringOrUri.present_and_equal?(sub, expected_subject)
17
+ end
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,43 @@
1
+ module JwtClaims
2
+ # Validation helpers
3
+ module StringOrUri
4
+
5
+ BLANK_STRING_RE = /\A[[:space:]]*\z/
6
+
7
+ module_function
8
+
9
+ # A predicate that compares two strings for equality
10
+ #
11
+ # @param a [String]
12
+ # @param b [String]
13
+ # @return [true, false]
14
+ def present_and_equal?(a, b)
15
+ present?(a) &&
16
+ present?(b) &&
17
+ a == b
18
+ end
19
+
20
+ # A string is present if it is not blank
21
+ #
22
+ # @param a [String]
23
+ # @return [true, false]
24
+ def present?(a)
25
+ !blank?(a)
26
+ end
27
+
28
+ # A string is blank if it is empty or contains whitespaces only
29
+ #
30
+ # blank?('') # => true
31
+ # blank?(' ') # => true
32
+ # blank?("\t\n\r") # => true
33
+ # blank?('foo ') # => false
34
+ #
35
+ # @param a [String]
36
+ # @return [true, false]
37
+ # @see cf. rails activesupport/lib/active_support/core_ext/object/blank.rb
38
+ def blank?(a)
39
+ return true unless a
40
+ BLANK_STRING_RE === a
41
+ end
42
+ end
43
+ end
@@ -0,0 +1,39 @@
1
+ module JwtClaims
2
+ # Validate registered claims
3
+ # @see http://tools.ietf.org/html/rfc7519#section-4.1
4
+ module Validation
5
+
6
+ module_function
7
+
8
+ # @param claims [Hash] JWT claims
9
+ # @param options [Hash] expected values for certain claims
10
+ # @return [Array] symbols of the registered claims that fail validation
11
+ def rejected(claims, options = {})
12
+ claims.each_with_object([]) do |claim, memo|
13
+ sym = reject(*claim, options)
14
+ memo << sym if sym
15
+ end
16
+ end
17
+
18
+ def reject(key, val, options)
19
+ return unless reg_claim = registered_claim(key)
20
+ key if reg_claim.reject?(val, options)
21
+ end
22
+
23
+ def registered_claim(sym)
24
+ case sym
25
+ when :aud then Claim::Aud
26
+ when :exp then Claim::Exp
27
+ when :iat then Claim::Iat
28
+ when :iss then Claim::Iss
29
+ when :jti then Claim::Jti
30
+ when :nbf then Claim::Nbf
31
+ when :sub then Claim::Sub
32
+ else nil # custom claim
33
+ end
34
+ end
35
+
36
+ private_class_method :reject,
37
+ :registered_claim
38
+ end
39
+ end
@@ -0,0 +1,3 @@
1
+ module JwtClaims
2
+ VERSION = '0.0.1'
3
+ end
data/lib/jwt_claims.rb ADDED
@@ -0,0 +1,25 @@
1
+ require 'json_web_token'
2
+ require 'jwt_claims/validation'
3
+
4
+ module JwtClaims
5
+
6
+ module_function
7
+
8
+ # @param jwt [String] JSON web token
9
+ # @param options [Hash] expected values for certain claims;
10
+ # optional keys include: :aud, :iss, :jti, :sub
11
+ # @return [Hash] { ok: { the jwt claims set hash } }, or { error: [symbols of all rejected claims] }
12
+ def verify(jwt, options)
13
+ hsh = JsonWebToken.verify(jwt, options)
14
+ return {error: 'invalid JWT'} if hsh[:error]
15
+ claims = hsh[:ok]
16
+ return {error: 'invalid input'} unless claims
17
+ verified(claims, options)
18
+ end
19
+
20
+ def verified(claims, options)
21
+ rejected_claims = Validation.rejected(claims, options)
22
+ return {ok: claims} if rejected_claims.empty?
23
+ {error: rejected_claims}
24
+ end
25
+ end
@@ -0,0 +1,79 @@
1
+ require 'jwt_claims/claim/aud'
2
+
3
+ module JwtClaims
4
+ module Claim
5
+ describe Aud do
6
+ context '#reject?' do
7
+ context 'w :aud claim an array' do
8
+ let(:recipient) { 'recipient' }
9
+ let(:uri) { 'http://www.example.com' }
10
+ let(:aud) { [uri, recipient] }
11
+ describe 'w match' do
12
+ it 'string returns false' do
13
+ expect(Aud.reject? aud, {aud: recipient}).to be false
14
+ end
15
+
16
+ it 'uri returns false' do
17
+ expect(Aud.reject? aud, {aud: uri}).to be false
18
+ end
19
+ end
20
+
21
+ it 'w/o match returns true' do
22
+ expect(Aud.reject? aud, {aud: 'not recipient'}).to be true
23
+ end
24
+
25
+ it 'w/o options[:aud] returns true' do
26
+ expect(Aud.reject? aud, {iss: 'recipient'}).to be true
27
+ end
28
+ end
29
+
30
+ describe 'w :aud claim a string' do
31
+ let(:recipient) { 'recipient' }
32
+ let(:aud) { recipient }
33
+ it 'w match returns false' do
34
+ expect(Aud.reject? aud, {aud: recipient}).to be false
35
+ end
36
+
37
+ it 'w/o match returns true' do
38
+ expect(Aud.reject? aud, {aud: 'not recipient'}).to be true
39
+ end
40
+
41
+ it 'w/o options[:aud] returns true' do
42
+ expect(Aud.reject? aud, {iss: 'recipient'}).to be true
43
+ end
44
+ end
45
+
46
+ shared_examples_for 'a blank :aud claim' do
47
+ let(:options) { {aud: 'recipient'} }
48
+ it 'returns true' do
49
+ expect(Aud.reject? aud, options).to be true
50
+ end
51
+ end
52
+
53
+ describe 'w :aud an empty array' do
54
+ let(:aud) { [] }
55
+ it_behaves_like 'a blank :aud claim'
56
+ end
57
+
58
+ describe 'w :aud an array w an empty string' do
59
+ let(:aud) { [''] }
60
+ it_behaves_like 'a blank :aud claim'
61
+ end
62
+
63
+ describe 'w :aud an empty string' do
64
+ let(:aud) { '' }
65
+ it_behaves_like 'a blank :aud claim'
66
+ end
67
+
68
+ describe 'w options[:aud] an empty string' do
69
+ let(:aud) { 'recipient' }
70
+ let(:options) { {aud: ''} }
71
+ it 'returns truthy' do
72
+ expect(Aud.reject? aud, options).to be true
73
+ end
74
+ end
75
+ end
76
+ end
77
+ end
78
+ end
79
+
@@ -0,0 +1,23 @@
1
+ require 'jwt_claims/claim/exp'
2
+
3
+ module JwtClaims
4
+ module Claim
5
+ describe Exp do
6
+ let(:after_now) { Time.now.to_i + 1 }
7
+ describe '#reject?' do
8
+ it 'w numeric_date after now returns false' do
9
+ expect(Exp.reject? after_now).to be false
10
+ end
11
+
12
+ it 'w numeric_date now returns true' do
13
+ expect(Exp.reject? Time.now.to_i).to be true
14
+ end
15
+
16
+ it 'w/o numeric numeric_date returns true' do
17
+ expect(Exp.reject? after_now.to_s).to be true
18
+ end
19
+ end
20
+ end
21
+ end
22
+ end
23
+
@@ -0,0 +1,23 @@
1
+ require 'jwt_claims/claim/iat'
2
+
3
+ module JwtClaims
4
+ module Claim
5
+ describe Iat do
6
+ let(:before_now) { Time.now.to_i - 1 }
7
+ describe '#reject?' do
8
+ it 'w numeric_date now returns false' do
9
+ expect(Iat.reject? before_now).to be false
10
+ end
11
+
12
+ it 'w numeric_date now returns true' do
13
+ expect(Iat.reject? Time.now.to_i).to be true
14
+ end
15
+
16
+ it 'w/o numeric claimed_time returns true' do
17
+ expect(Iat.reject? before_now.to_s).to be true
18
+ end
19
+ end
20
+ end
21
+ end
22
+ end
23
+
@@ -0,0 +1,30 @@
1
+ require 'jwt_claims/claim/iss'
2
+
3
+ module JwtClaims
4
+ module Claim
5
+ describe Iss do
6
+ context '#reject?' do
7
+ let(:issuer) { 'issuer' }
8
+ describe 'w an :iss claim' do
9
+ let(:iss) { issuer }
10
+ it 'w match returns false' do
11
+ expect(Iss.reject? iss, {iss: issuer}).to be false
12
+ end
13
+
14
+ it 'w/o match returns true' do
15
+ expect(Iss.reject? iss, {iss: 'not issuer'}).to be true
16
+ end
17
+
18
+ it 'w/o options[:iss] returns true' do
19
+ expect(Iss.reject? iss, {aud: 'issuer'}).to be true
20
+ end
21
+ end
22
+
23
+ it 'w a blank :iss claim returns true' do
24
+ expect(Iss.reject? '', {iss: issuer}).to be true
25
+ end
26
+ end
27
+ end
28
+ end
29
+ end
30
+
@@ -0,0 +1,30 @@
1
+ require 'jwt_claims/claim/jti'
2
+
3
+ module JwtClaims
4
+ module Claim
5
+ describe Jti do
6
+ context '#reject?' do
7
+ let(:jwt_id) { 'jwt_id' }
8
+ describe 'w a :jti claim' do
9
+ let(:jti) { jwt_id }
10
+ it 'w match returns false' do
11
+ expect(Jti.reject? jti, {jti: jwt_id}).to be false
12
+ end
13
+
14
+ it 'w/o match returns true' do
15
+ expect(Jti.reject? jti, {jti: 'not jwt_id'}).to be true
16
+ end
17
+
18
+ it 'w/o options[:jti] returns true' do
19
+ expect(Jti.reject? jti, {aud: jwt_id}).to be true
20
+ end
21
+ end
22
+
23
+ it 'w a blank :jti claim returns true' do
24
+ expect(Jti.reject? '', {jti: jwt_id}).to be true
25
+ end
26
+ end
27
+ end
28
+ end
29
+ end
30
+
@@ -0,0 +1,23 @@
1
+ require 'jwt_claims/claim/nbf'
2
+
3
+ module JwtClaims
4
+ module Claim
5
+ describe Nbf do
6
+ let(:after_now) { Time.now.to_i + 1 }
7
+ describe '#reject?' do
8
+ it 'w numeric_date now returns false' do
9
+ expect(Nbf.reject? Time.now.to_i).to be false
10
+ end
11
+
12
+ it 'w numeric_date after now returns true' do
13
+ expect(Nbf.reject? after_now).to be true
14
+ end
15
+
16
+ it 'w/o numeric claimed_time returns true' do
17
+ expect(Nbf.reject? after_now.to_s).to be true
18
+ end
19
+ end
20
+ end
21
+ end
22
+ end
23
+
@@ -0,0 +1,30 @@
1
+ require 'jwt_claims/claim/sub'
2
+
3
+ module JwtClaims
4
+ module Claim
5
+ describe Sub do
6
+ context '#reject?' do
7
+ let(:subject) { 'subject' }
8
+ describe 'w a :sub claim' do
9
+ let(:sub) { subject }
10
+ it 'w match returns false' do
11
+ expect(Sub.reject? sub, {sub: subject}).to be false
12
+ end
13
+
14
+ it 'w/o match returns true' do
15
+ expect(Sub.reject? sub, {sub: 'not subject'}).to be true
16
+ end
17
+
18
+ it 'w/o options[:sub] returns true' do
19
+ expect(Sub.reject? sub, {aud: subject}).to be true
20
+ end
21
+ end
22
+
23
+ it 'w a blank :sub claim returns true' do
24
+ expect(Sub.reject? '', {sub: subject}).to be true
25
+ end
26
+ end
27
+ end
28
+ end
29
+ end
30
+
@@ -0,0 +1,48 @@
1
+ require 'jwt_claims/string_or_uri'
2
+
3
+ module JwtClaims
4
+ describe StringOrUri do
5
+ context '#present_and_equal?' do
6
+ it 'w equality, returns true' do
7
+ expect(StringOrUri.present_and_equal? 'foo', 'foo').to be true
8
+ end
9
+
10
+ shared_examples_for 'a blank value or not equal' do
11
+ it 'returns false' do
12
+ expect(StringOrUri.present_and_equal? a, b).to be false
13
+ end
14
+ end
15
+
16
+ describe 'w 1st argument blank' do
17
+ let(:a) { '' }
18
+ let(:b) { 'bar' }
19
+ it_behaves_like 'a blank value or not equal'
20
+ end
21
+
22
+ describe 'w 2nd argument blank' do
23
+ let(:a) { 'foo' }
24
+ let(:b) { '' }
25
+ it_behaves_like 'a blank value or not equal'
26
+ end
27
+
28
+ describe 'w/o equality' do
29
+ let(:a) { 'foo' }
30
+ let(:b) { 'bar' }
31
+ it_behaves_like 'a blank value or not equal'
32
+ end
33
+ end
34
+
35
+ describe '#blank?' do
36
+ it 'classifies nil, empty, or exclusively whitespace strings' do
37
+ expect(StringOrUri.blank? nil).to be true
38
+ expect(StringOrUri.blank? '').to be true
39
+ expect(StringOrUri.blank? ' ').to be true
40
+ expect(StringOrUri.blank? "\t\n\r").to be true
41
+
42
+ expect(StringOrUri.blank? 'foo').to be false
43
+ expect(StringOrUri.blank? 'bar ').to be false
44
+ end
45
+ end
46
+ end
47
+ end
48
+
@@ -0,0 +1,69 @@
1
+ require 'jwt_claims/validation'
2
+
3
+ module JwtClaims
4
+ describe Validation do
5
+ context 'example data' do
6
+ let(:uri) { 'http://www.example.com' }
7
+ let(:recipient) { 'recipient' }
8
+ let(:aud) { [uri, recipient] }
9
+
10
+ let(:after_now) { Time.now.to_i + 1 }
11
+ let(:before_now) { Time.now.to_i - 1 }
12
+
13
+ let(:issuer) { 'issuer' }
14
+ let(:jwt_id) { 'jwt_id' }
15
+ let(:subject) { 'subject' }
16
+
17
+ let(:default_options) do
18
+ {
19
+ aud: uri,
20
+ iss: issuer,
21
+ jti: jwt_id,
22
+ sub: subject
23
+ }
24
+ end
25
+ let(:default_claims) do
26
+ {
27
+ aud: [uri, recipient],
28
+ exp: after_now,
29
+ iat: before_now,
30
+ iss: issuer,
31
+ jti: jwt_id,
32
+ nbf: before_now,
33
+ sub: subject
34
+ }
35
+ end
36
+ context '#rejected' do
37
+ it 'w valid claims, returns empty array' do
38
+ expect(Validation.rejected default_claims, default_options).to eql []
39
+ end
40
+
41
+ describe 'w all registered claims invalid' do
42
+ let(:invalid_claims) do
43
+ {
44
+ aud: ['http://www.other.com', 'other recipient'],
45
+ exp: before_now,
46
+ iat: after_now,
47
+ iss: 'other issuer',
48
+ jti: 'other jwt_id',
49
+ nbf: after_now,
50
+ sub: 'other subject'
51
+ }
52
+ end
53
+ it 'returns array of failed claims' do
54
+ expect(Validation.rejected invalid_claims, default_options)
55
+ .to include(
56
+ :aud,
57
+ :exp,
58
+ :iat,
59
+ :iss,
60
+ :jti,
61
+ :nbf,
62
+ :sub
63
+ )
64
+ end
65
+ end
66
+ end
67
+ end
68
+ end
69
+ end
@@ -0,0 +1,50 @@
1
+ require 'jwt_claims'
2
+
3
+ describe JwtClaims do
4
+ let(:after_now) { Time.now.to_i + 1 }
5
+ let(:before_now) { Time.now.to_i - 1 }
6
+ context '#verify' do
7
+ describe 'JsonWebToken integration w :exp claim before now' do
8
+ let(:jwt_w_exp_before_now_w_iss_joe) do
9
+ "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJqb2UiLCJodHRwOi8vZXhhbXBsZS5jb20vaXNfcm9vdCI6dHJ1ZSwiZXhwIjoxMzAwODE5MzgwfQ.Ktfu3EdLz0SpuTIMpMoRZMtZsCATWJHeDEBGrsZE6LI"
10
+ end
11
+ let(:hs256_key) { "gZH75aKtMN3Yj0iPS4hcgUuTwjAzZr9C" }
12
+ it 'w :iss claim matching' do
13
+ options = {iss: 'joe', key: hs256_key}
14
+ rejected_claims = JwtClaims.verify(jwt_w_exp_before_now_w_iss_joe, options)[:error]
15
+ expect(rejected_claims.length).to eql 1
16
+ expect(rejected_claims).to include(:exp)
17
+ end
18
+
19
+ it 'w/o :iss claim matching' do
20
+ options = {iss: 'mary', key: hs256_key}
21
+ rejected_claims = JwtClaims.verify(jwt_w_exp_before_now_w_iss_joe, options)[:error]
22
+ expect(rejected_claims.length).to eql 2
23
+ expect(rejected_claims).to include(:exp, :iss)
24
+ end
25
+ end
26
+
27
+ describe 'w invalid JWT' do
28
+ let(:invalid_jwt) do
29
+ "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJqb2UiLCJodHRwOi8vZXhhbXBsZS5jb20vaXNfcm9vdCI6dHJ1ZSwiZXhwIjoxMzAwODE5MzgwfQ.Ktfu3EdLz0SpuTIMpMoRZMtZsCATWJHeDEBGrsZE6LX"
30
+ end
31
+ it 'returns error' do
32
+ options = {exp: after_now}
33
+ error = JwtClaims.verify(invalid_jwt, options)[:error]
34
+ expect(error).to eql 'invalid JWT'
35
+ end
36
+ end
37
+ end
38
+
39
+ describe '#verified' do
40
+ it 'w rejected_claims' do
41
+ claims = {exp: before_now}
42
+ expect(JwtClaims.verified claims, {}).to include(error: [:exp])
43
+ end
44
+
45
+ it 'w/o rejected_claims' do
46
+ claims = {exp: after_now}
47
+ expect(JwtClaims.verified claims, {}).to include(ok: claims)
48
+ end
49
+ end
50
+ end
@@ -0,0 +1,85 @@
1
+ require 'simplecov'
2
+ SimpleCov.start
3
+
4
+ # Conventionally, all specs live under a `spec` directory, which RSpec adds to
5
+ # the `$LOAD_PATH`. The generated `.rspec` file contains `--require spec_helper`
6
+ # which will cause this file to always be loaded, without a need to explicitly
7
+ # require it in any files.
8
+ #
9
+ # Given that it is always loaded, you are encouraged to keep this file as
10
+ # light-weight as possible. Requiring heavyweight dependencies from this file
11
+ # will add to the boot time of your test suite on EVERY test run, even for an
12
+ # individual file that may not need all of that loaded. Instead, consider
13
+ # making a separate helper file that requires the additional dependencies and
14
+ # performs the additional setup, and require it from the spec files that
15
+ # actually need it.
16
+ #
17
+ # The `.rspec` file also contains a few flags that are not defaults but that
18
+ # users commonly want.
19
+ #
20
+ # See http://rubydoc.info/gems/rspec-core/RSpec/Core/Configuration
21
+ RSpec.configure do |config|
22
+ config.expect_with :rspec do |expectations|
23
+ # This option will default to `true` in RSpec 4. It makes the `description`
24
+ # and `failure_message` of custom matchers include text for helper methods
25
+ # defined using `chain`, e.g.:
26
+ # be_bigger_than(2).and_smaller_than(4).description
27
+ # # => "be bigger than 2 and smaller than 4"
28
+ # ...rather than:
29
+ # # => "be bigger than 2"
30
+ expectations.include_chain_clauses_in_custom_matcher_descriptions = true
31
+ end
32
+
33
+ config.mock_with :rspec do |mocks|
34
+ # Prevents you from mocking or stubbing a method that does not exist on
35
+ # a real object. This is generally recommended, and will default to
36
+ # `true` in RSpec 4.
37
+ mocks.verify_partial_doubles = true
38
+ end
39
+
40
+ # The settings below are suggested to provide a good initial experience
41
+ # with RSpec, but feel free to customize to your heart's content.
42
+
43
+ # These two settings work together to allow you to limit a spec run to
44
+ # individual examples or groups you care about by tagging them with `:focus`
45
+ # metadata. When nothing is tagged with `:focus`, all examples get run.
46
+ config.filter_run :focus
47
+ config.run_all_when_everything_filtered = true
48
+
49
+ # Allows RSpec to persist some state between runs in order to support the
50
+ # `--only-failures` and `--next-failure` CLI options. We recommend you
51
+ # configure your source control system to ignore this file.
52
+ config.example_status_persistence_file_path = "spec/examples.txt"
53
+
54
+ # Limits the available syntax to the non-monkey patched syntax that is
55
+ # recommended. For more details, see:
56
+ # - http://myronmars.to/n/dev-blog/2012/06/rspecs-new-expectation-syntax
57
+ # - http://www.teaisaweso.me/blog/2013/05/27/rspecs-new-message-expectation-syntax/
58
+ # - http://myronmars.to/n/dev-blog/2014/05/notable-changes-in-rspec-3#new__config_option_to_disable_rspeccore_monkey_patching
59
+ # config.disable_monkey_patching!
60
+
61
+ # Many RSpec users commonly either run the entire suite or an individual
62
+ # file, and it's useful to allow more verbose output when running an
63
+ # individual spec file.
64
+ if config.files_to_run.one?
65
+ # Use the documentation formatter for detailed output, unless a formatter
66
+ # has already been configured (e.g. via a command-line flag)
67
+ config.default_formatter = 'doc'
68
+ end
69
+
70
+ # Print the 10 slowest examples and example groups at the end of the spec
71
+ # run, to help surface which specs are running particularly slowly.
72
+ # config.profile_examples = 10
73
+
74
+ # Run specs in random order to surface order dependencies. If you find an
75
+ # order dependency and want to debug it, you can fix the order by providing
76
+ # the seed, which is printed after each run.
77
+ # --seed 1234
78
+ config.order = :random
79
+
80
+ # Seed global randomization in this process using the `--seed` CLI option.
81
+ # Setting this allows you to use `--seed` to deterministically reproduce
82
+ # test failures related to randomization by passing the same `--seed` value
83
+ # as the one that triggered the failure.
84
+ Kernel.srand config.seed
85
+ end
metadata ADDED
@@ -0,0 +1,100 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: jwt_claims
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ platform: ruby
6
+ authors:
7
+ - Gary Fleshman
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2015-08-29 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: json_web_token
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '0.3'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '0.3'
27
+ - !ruby/object:Gem::Dependency
28
+ name: rspec
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '3.3'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '3.3'
41
+ description: Modular implementation of JSON Web Token (JWT) Claims
42
+ email: gfleshman@newforge-tech.com
43
+ executables: []
44
+ extensions: []
45
+ extra_rdoc_files: []
46
+ files:
47
+ - ".gitignore"
48
+ - ".rspec"
49
+ - Gemfile
50
+ - LICENSE
51
+ - README.md
52
+ - jwt_claims.gemspec
53
+ - lib/jwt_claims.rb
54
+ - lib/jwt_claims/claim/aud.rb
55
+ - lib/jwt_claims/claim/exp.rb
56
+ - lib/jwt_claims/claim/iat.rb
57
+ - lib/jwt_claims/claim/iss.rb
58
+ - lib/jwt_claims/claim/jti.rb
59
+ - lib/jwt_claims/claim/nbf.rb
60
+ - lib/jwt_claims/claim/sub.rb
61
+ - lib/jwt_claims/string_or_uri.rb
62
+ - lib/jwt_claims/validation.rb
63
+ - lib/jwt_claims/version.rb
64
+ - spec/jwt_claims/claim/aud_spec.rb
65
+ - spec/jwt_claims/claim/exp_spec.rb
66
+ - spec/jwt_claims/claim/iat_spec.rb
67
+ - spec/jwt_claims/claim/iss_spec.rb
68
+ - spec/jwt_claims/claim/jti_spec.rb
69
+ - spec/jwt_claims/claim/nbf_spec.rb
70
+ - spec/jwt_claims/claim/sub_spec.rb
71
+ - spec/jwt_claims/string_or_uri_spec.rb
72
+ - spec/jwt_claims/validation_spec.rb
73
+ - spec/jwt_claims_spec.rb
74
+ - spec/spec_helper.rb
75
+ homepage: https://github.com/garyf/jwt_claims
76
+ licenses:
77
+ - MIT
78
+ metadata: {}
79
+ post_install_message:
80
+ rdoc_options: []
81
+ require_paths:
82
+ - lib
83
+ required_ruby_version: !ruby/object:Gem::Requirement
84
+ requirements:
85
+ - - ">="
86
+ - !ruby/object:Gem::Version
87
+ version: 2.0.0
88
+ required_rubygems_version: !ruby/object:Gem::Requirement
89
+ requirements:
90
+ - - ">="
91
+ - !ruby/object:Gem::Version
92
+ version: '0'
93
+ requirements: []
94
+ rubyforge_project:
95
+ rubygems_version: 2.4.8
96
+ signing_key:
97
+ specification_version: 4
98
+ summary: JSON Web Token (JWT) Claims for Ruby
99
+ test_files: []
100
+ has_rdoc: