prx_auth 1.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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