prx_auth 1.1.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.
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 3e7866c4a21b61c3e43328c528301e6b53aa350efc5caec23f94b2c4acebea30
4
+ data.tar.gz: 4d39f067376023bfa72cdaa2059ea976d5011535e5b766fb5928478508f48e23
5
+ SHA512:
6
+ metadata.gz: 849fe18e035fe9406412cf48da28578d60ecdd5e5ea632b0577d17dcb0b2566e187452313f86d6186529b963a5771275a4968b5ed8bd342a7fea5bfafeb6d34e
7
+ data.tar.gz: 916cc3c3e08829b1bd07f638b853bc4a19b9bf68ce08ec5580df999fda8c712f50046a9e11b77ef4063df7e8bfd1ebf15457439ccc67e2c5ff5089ae9c2c2350
@@ -0,0 +1,16 @@
1
+ /.bundle/
2
+ /.yardoc
3
+ /Gemfile.lock
4
+ /_yardoc/
5
+ /coverage/
6
+ /doc/
7
+ /pkg/
8
+ /spec/reports/
9
+ /tmp/
10
+ /vendor/bundle/
11
+ *.bundle
12
+ *.so
13
+ *.o
14
+ *.a
15
+ mkmf.log
16
+ .ruby-version
@@ -0,0 +1,12 @@
1
+ sudo: false
2
+ language: ruby
3
+ cache: bundler
4
+ rvm:
5
+ - 2.3.7
6
+ - 2.4.4
7
+ - 2.5.1
8
+ - 2.6.3
9
+ before_install:
10
+ - gem install bundler
11
+ script:
12
+ - bundle exec rake test
@@ -0,0 +1,20 @@
1
+ # Change Log
2
+ All notable changes to this project will be documented in this file.
3
+ This project adheres to [Semantic Versioning](http://semver.org/).
4
+
5
+ ## [Unreleased][unreleased]
6
+
7
+ ## [0.0.7] - 2015-06-15
8
+ ### Added
9
+ - This Changelog
10
+ - TokenData#authorized? method
11
+
12
+ ## [0.0.6] - 2015-04-22
13
+ ### Removed
14
+ - Railtie
15
+ ### Fixed
16
+ - Endless loop while fetching afer certificate cache expires
17
+
18
+ [unreleased]: https://github.com/PRX/rack-prx_auth/compare/v0.0.7...HEAD
19
+ [0.0.7]: https://github.com/PRX/rack-prx_auth/compare/v0.0.6...v0.0.7
20
+ [0.0.6]: https://github.com/PRX/rack-prx_auth/compare/v0.0.5...v0.0.6
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in rack-prx_auth.gemspec
4
+ gemspec
@@ -0,0 +1,8 @@
1
+ guard :minitest, all_after_pass: true do
2
+ watch(%r{^test/(.*)\/?test_(.*)\.rb})
3
+ watch(%r{^lib/(.*/)?([^/]+)\.rb}) { |m| "test/#{m[1]}test_#{m[2]}.rb" }
4
+ watch(%r{^lib/(.+)\.rb}) { |m| "test/#{m[1]}_test.rb" }
5
+ watch(%r{^lib/(.+)\.rb}) { |m| "test/#{m[1]}_test.rb" }
6
+ watch(%r{^test/.+_test\.rb})
7
+ watch(%r{^test/test_helper\.rb}) { 'test' }
8
+ end
data/LICENSE ADDED
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2014 PRX, Eve Asher
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,66 @@
1
+ # Rack::PrxAuth
2
+
3
+ [![Gem Version](https://badge.fury.io/rb/rack-prx_auth.svg)](http://badge.fury.io/rb/rack-prx_auth)
4
+ [![Dependency Status](https://gemnasium.com/PRX/rack-prx_auth.svg)](https://gemnasium.com/PRX/rack-prx_auth)
5
+ [![Build Status](https://travis-ci.org/PRX/rack-prx_auth.svg?branch=master)](https://travis-ci.org/PRX/rack-prx_auth)
6
+ [![Code Climate](https://codeclimate.com/github/PRX/rack-prx_auth/badges/gpa.svg)](https://codeclimate.com/github/PRX/rack-prx_auth)
7
+ [![Coverage Status](https://coveralls.io/repos/PRX/rack-prx_auth/badge.svg)](https://coveralls.io/r/PRX/rack-prx_auth)
8
+
9
+ This gem adds middleware to a Rack application that decodes and verified a JSON Web Token (JWT) issued by PRX.org. If the JWT is invalid, the middleware will respond with a 401 Unauthorized. If the JWT was not issued by PRX (or the specified issuer), the request will continue through the middleware stack.
10
+
11
+ ## Installation
12
+
13
+ Add this line to your application's Gemfile:
14
+
15
+ ```ruby
16
+ gem 'rack-prx_auth'
17
+ ```
18
+
19
+ And then execute:
20
+
21
+ $ bundle
22
+
23
+ Or install it yourself as:
24
+
25
+ $ gem install rack-prx_auth
26
+
27
+ In a non-Rails app, add the following to the application's config.ru file:
28
+
29
+ ```ruby
30
+ use Rack::PrxAuth, cert_location: [CERT LOCATION], issuer: [ISSUER]
31
+ ```
32
+ The `cert_location` and `issuer` parameters are optional. See below.
33
+
34
+ ## Usage
35
+
36
+ ### The Request
37
+
38
+ Rack-prx_auth looks for a token in the request's HTTP_AUTHORIZATION header. It expects that the header's content will take the form of 'Bearer <your token>'. If no HTTP_AUTHORIZATION header is present, rack-prx_auth passes the request to the next middleware.
39
+
40
+ We have another application that's in charge of making the token. It's called id.prx.org. Its job is to show a form for a user to enter credentials, validate those credentials, and then generate a JWT using PRX's private key. See http://openid.net/specs/openid-connect-implicit-1_0.html to find out what information is encoded in a JWT. Basically it's a hash containing, among other things, the user's ID, the issuer of the token, and when the token expires.
41
+
42
+ ### Configuration
43
+
44
+ Rack-prx_auth takes two optional parameters, `issuer` and `cert_location`. See Installation for how to specify them.
45
+
46
+ By default, rack-prx_auth will assume that you want to make sure the JWT was issued by PRX. After decoding the JWT, rack-prx_auth checks the `issuer` field to make sure it's id.prx.org. If you want it to check for a different issuer, pass `issuer: <your issuer>` as a parameter.
47
+
48
+ Since the JWT was created using PRX's private key, rack-prx_auth needs to fetch PRX's public key to decode it. It does this by accessing the `cert_location` (default is https://id.prx.org/api/v1/certs), generating an OpenSSL::X509::Certificate based on its contents, and determining the public key from the certificate object. Should you wish to get your public key from a different certificate, you may specify a different endpoint by passing `cert_location: <your cert location>` as a parameter. Keep in mind that unless the certificate matches the private key used to make the JWT, rack-prx_auth will return 401.
49
+
50
+ ### The Response
51
+
52
+ If the token isn't valid, meaning it's expired or it wasn't created using our private key, rack-prx_auth will return 401 Unauthorized.
53
+
54
+ If there's nothing in the HTTP_AUTHORIZATION heading, there's something but JSON::JWT can't decode it, or the issuer field doesn't specify the correct issuer, rack-prx_auth just punts to the next piece of middleware.
55
+
56
+ If all goes well, rack-prx_auth takes the decoded JWT and makes a TokenData object. Then it adds this object to the `env` with the key 'prx.auth'.
57
+
58
+ If you are using rack-prx_auth in a Rails app, you'll have a few handy controller methods available to you. Calling `prx_auth_token` within a controller returns the TokenData object, and `prx_authenticated?` tells you whether a TokenData object is available. Also, if you call `user_id` on the TokenData object, you get the user's ID so you can ask id.prx.org for information about them.
59
+
60
+ ## Contributing
61
+
62
+ 1. Fork it ( https://github.com/PRX/rack-prx_auth/fork )
63
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
64
+ 3. Commit your changes (`git commit -am 'Add some feature'`)
65
+ 4. Push to the branch (`git push origin my-new-feature`)
66
+ 5. Create a new Pull Request
@@ -0,0 +1,10 @@
1
+ require 'bundler/gem_tasks'
2
+ require 'rake'
3
+ require 'rake/testtask'
4
+
5
+ Rake::TestTask.new do |t|
6
+ t.libs << 'test'
7
+ t.pattern = 'test/**/*test.rb'
8
+ end
9
+
10
+ task default: :test
@@ -0,0 +1,3 @@
1
+ require 'prx_auth/resource_map'
2
+ require 'prx_auth/scope_list'
3
+ require 'prx_auth/version'
@@ -0,0 +1,124 @@
1
+ module PrxAuth
2
+ class ResourceMap
3
+ WILDCARD_KEY = '*'
4
+
5
+ def initialize(mapped_values)
6
+ input = mapped_values.clone
7
+ @wildcard = ScopeList.new(input.delete(WILDCARD_KEY)||'')
8
+ @map = Hash[input.map do |(key, values)|
9
+ [key, ScopeList.new(values)]
10
+ end]
11
+ end
12
+
13
+ def contains?(resource, namespace=nil, scope=nil)
14
+ resource = resource.to_s
15
+
16
+ if resource == WILDCARD_KEY
17
+ raise ArgumentError if namespace.nil?
18
+
19
+ @wildcard.contains?(namespace, scope)
20
+ else
21
+ mapped_resource = @map[resource]
22
+
23
+ if mapped_resource && !namespace.nil?
24
+ mapped_resource.contains?(namespace, scope) || @wildcard.contains?(namespace, scope)
25
+ elsif !namespace.nil?
26
+ @wildcard.contains?(namespace, scope)
27
+ else
28
+ !!mapped_resource
29
+ end
30
+ end
31
+ end
32
+
33
+ def condense
34
+ condensed_wildcard = @wildcard.condense
35
+ condensed_map = Hash[@map.map do |resource, list|
36
+ [resource, (list - condensed_wildcard).condense]
37
+ end]
38
+ ResourceMap.new(condensed_map.merge(WILDCARD_KEY => condensed_wildcard))
39
+ end
40
+
41
+ def +(other_map)
42
+ result = {}
43
+ (resources + other_map.resources + [WILDCARD_KEY]).uniq.each do |resource|
44
+ list_a = list_for_resource(resource)
45
+ list_b = other_map.list_for_resource(resource)
46
+ result[resource] = if list_a.nil?
47
+ list_b
48
+ elsif list_b.nil?
49
+ list_a
50
+ else
51
+ list_a + list_b
52
+ end
53
+ end
54
+
55
+ ResourceMap.new(result).condense
56
+ end
57
+
58
+ def -(other_map)
59
+ result = {}
60
+ other_wildcard = other_map.list_for_resource(WILDCARD_KEY) || PrxAuth::ScopeList.new('')
61
+
62
+ resources.each do |resource|
63
+ result[resource] = list_for_resource(resource) - (other_wildcard + other_map.list_for_resource(resource))
64
+ end
65
+
66
+ if @wildcard
67
+ result[WILDCARD_KEY] = @wildcard - other_wildcard
68
+ end
69
+
70
+ ResourceMap.new(result)
71
+ end
72
+
73
+ def &(other_map)
74
+ result = {}
75
+ other_wildcard = other_map.list_for_resource(WILDCARD_KEY)
76
+
77
+ (resources + other_map.resources).uniq.each do |res|
78
+ left = list_for_resource(res)
79
+ right = other_map.list_for_resource(res)
80
+
81
+ result[res] = if left.nil?
82
+ right & @wildcard
83
+ elsif right.nil?
84
+ left & other_wildcard
85
+ else
86
+ (left + @wildcard) & (right + other_wildcard)
87
+ end
88
+ end
89
+
90
+ if @wildcard
91
+ result[WILDCARD_KEY] = @wildcard - (@wildcard - other_wildcard)
92
+ end
93
+
94
+ ResourceMap.new(result).condense
95
+ end
96
+
97
+ def as_json(opts={})
98
+ @map.merge(WILDCARD_KEY => @wildcard).as_json(opts)
99
+ end
100
+
101
+ def freeze
102
+ @map.freeze
103
+ @wildcard.freeze
104
+ self
105
+ end
106
+
107
+ def resources(namespace=nil, scope=nil)
108
+ if namespace.nil?
109
+ @map.keys
110
+ else
111
+ @map.select do |name, list|
112
+ list.contains?(namespace, scope) || @wildcard.contains?(namespace, scope)
113
+ end.map(&:first)
114
+ end
115
+ end
116
+
117
+ protected
118
+
119
+ def list_for_resource(resource)
120
+ return @wildcard if resource.to_s == WILDCARD_KEY
121
+ @map[resource.to_s]
122
+ end
123
+ end
124
+ end
@@ -0,0 +1,138 @@
1
+ module PrxAuth
2
+ class ScopeList
3
+ SCOPE_SEPARATOR = ' '
4
+ NAMESPACE_SEPARATOR = ':'
5
+ NO_NAMESPACE = :_
6
+
7
+ def self.new(list)
8
+ case list
9
+ when PrxAuth::ScopeList then list
10
+ else super(list)
11
+ end
12
+ end
13
+
14
+ def initialize(list)
15
+ @string = list
16
+ end
17
+
18
+ def contains?(namespace, scope=nil)
19
+ scope, namespace = namespace, NO_NAMESPACE if scope.nil?
20
+
21
+ if namespace == NO_NAMESPACE
22
+ map[namespace].include?(symbolize(scope))
23
+ else
24
+ symbolized_scope = symbolize(scope)
25
+ map[symbolize(namespace)].include?(symbolized_scope) || map[NO_NAMESPACE].include?(symbolized_scope)
26
+ end
27
+ end
28
+
29
+ def freeze
30
+ @string.freeze
31
+ self
32
+ end
33
+
34
+ def to_s
35
+ @string
36
+ end
37
+
38
+ def condense
39
+ tripped = false
40
+ result = map[NO_NAMESPACE].clone
41
+ namespaces = map.keys - [NO_NAMESPACE]
42
+
43
+ namespaces.each do |ns|
44
+ map[ns].each do |scope|
45
+ if !contains?(NO_NAMESPACE, scope)
46
+ result << scope_string(ns, scope)
47
+ else
48
+ tripped = true
49
+ end
50
+ end
51
+ end
52
+
53
+ if tripped
54
+ ScopeList.new(result.join(SCOPE_SEPARATOR))
55
+ else
56
+ self
57
+ end
58
+ end
59
+
60
+ def as_json(opts=())
61
+ to_s.as_json(opts)
62
+ end
63
+
64
+ def -(other_scope_list)
65
+ return self if other_scope_list.nil?
66
+
67
+ tripped = false
68
+ result = []
69
+
70
+ map.each do |namespace, scopes|
71
+ scopes.each do |scope|
72
+ if other_scope_list.contains?(namespace, scope)
73
+ tripped = true
74
+ else
75
+ result << scope_string(namespace, scope)
76
+ end
77
+ end
78
+ end
79
+
80
+ if tripped
81
+ ScopeList.new(result.join(SCOPE_SEPARATOR))
82
+ else
83
+ self
84
+ end
85
+ end
86
+
87
+ def +(other_list)
88
+ return self if other_list.nil?
89
+
90
+ ScopeList.new([to_s, other_list.to_s].join(SCOPE_SEPARATOR)).condense
91
+ end
92
+
93
+ def &(other_list)
94
+ return ScopeList.new('') if other_list.nil?
95
+
96
+ self - (self - other_list)
97
+ end
98
+
99
+ private
100
+
101
+ def map
102
+ @parsed_map ||= empty_map.tap do |map|
103
+ @string.split(SCOPE_SEPARATOR).each do |value|
104
+ next if value.length < 1
105
+
106
+ parts = value.split(NAMESPACE_SEPARATOR, 2)
107
+ if parts.length == 2
108
+ map[symbolize(parts[0])] << symbolize(parts[1])
109
+ else
110
+ map[NO_NAMESPACE] << symbolize(parts[0])
111
+ end
112
+ end
113
+ end
114
+ end
115
+
116
+ def scope_string(ns, scope)
117
+ if ns == NO_NAMESPACE
118
+ scope.to_s
119
+ else
120
+ [ns, scope].join(NAMESPACE_SEPARATOR)
121
+ end
122
+ end
123
+
124
+ def empty_map
125
+ @empty_map ||= Hash.new do |hash, key|
126
+ hash[key] = []
127
+ end
128
+ end
129
+
130
+ def symbolize(value)
131
+ case value
132
+ when Symbol then value
133
+ when String then value.downcase.gsub('-', '_').intern
134
+ else symbolize value.to_s
135
+ end
136
+ end
137
+ end
138
+ end