warden_openid_bearer 0.1.0

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
+ SHA256:
3
+ metadata.gz: f228ea62cd8329a39ec230cb8b764c8b83db2557a767c41f0f18b28f35fabe8c
4
+ data.tar.gz: 11272a48dc32c8063f08957d7e409100bfb14818a0795a86acbf9416b88a2b3d
5
+ SHA512:
6
+ metadata.gz: 0602d2ade77d620f6aab62b0f0c55e02f4ff4d7ceb360d33c875a18b1318ec4b8664f3d0b0fc8a6eee8c423d8674dbfe1b3ad28feb80d2638489f013171a252c
7
+ data.tar.gz: dfe6d0e9418db37a762124a8cd94a00ab1127ceef9617acfcbc7bb94683ee92f6f661efde51a570a5b79d0fa21db4f480ac1e868b6ec4250065f73625f419046
data/.rspec ADDED
@@ -0,0 +1,3 @@
1
+ --format documentation
2
+ --color
3
+ --require spec_helper
data/.standard.yml ADDED
@@ -0,0 +1,3 @@
1
+ # For available configuration options, see:
2
+ # https://github.com/testdouble/standard
3
+ ruby_version: 2.6
data/CHANGELOG.md ADDED
@@ -0,0 +1,5 @@
1
+ ## [Unreleased]
2
+
3
+ ## [0.1.0] - 2022-10-07
4
+
5
+ - Initial release
data/Gemfile ADDED
@@ -0,0 +1,14 @@
1
+ # frozen_string_literal: true
2
+
3
+ source "https://rubygems.org"
4
+
5
+ # Specify your gem's dependencies in warden_openid_bearer.gemspec
6
+ gemspec
7
+
8
+ gem "rake", "~> 13.0"
9
+
10
+ gem "rspec", "~> 3.0"
11
+
12
+ gem "standard", "~> 1.3"
13
+
14
+ gem "debug"
data/LICENSE.txt ADDED
@@ -0,0 +1,21 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2022 Dominique Quatravaux
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,51 @@
1
+ # WardenOpenidBearer
2
+
3
+ Welcome to your new gem! In this directory, you'll find the files you need to be able to package up your Ruby library into a gem. Put your Ruby code in the file `lib/warden_openid_bearer`. To experiment with that code, run `bin/console` for an interactive prompt.
4
+
5
+ TODO: Delete this and the text above, and describe your gem
6
+
7
+ ## Installation
8
+
9
+ Install the gem and add to the application's Gemfile by executing:
10
+
11
+ $ bundle add warden_openid_bearer
12
+
13
+ If bundler is not being used to manage dependencies, install the gem by executing:
14
+
15
+ $ gem install warden_openid_bearer
16
+
17
+ ## Usage
18
+
19
+ TODO: Write usage instructions here
20
+
21
+ ## Development
22
+
23
+ After checking out the Git repository, run `bin/setup` to install dependencies. Then, run `bundle exec rake` to run the test suite and linter checks. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
24
+
25
+ ### Debugger
26
+
27
+ The `debugger` gem is a development-time requirement (in the Gemfile). In order to activate it:
28
+
29
+ 1. Uncomment the line that says `require "debug"` in `./spec_helper.rb`
30
+ 1. Stick `debugger` somewhere in the source or test code
31
+ 1. Run the test suite
32
+
33
+ ### Local Install
34
+
35
+ To install this gem onto your local machine, run `bundle exec rake install`.
36
+
37
+ ### Release
38
+
39
+ To release a new version:
40
+ 1. Make sure that the version you want to publish is the current `master` branch on GitHub, and that the tests are green
41
+ 1. Check out the `master` branch in your working directory
42
+ 1. Update the version number in `version.rb`
43
+ 1. Run `bundle exec rake release`, which will create a git tag for the version, push git commits and the created tag, and push the `.gem` file to [rubygems.org](https://rubygems.org)
44
+
45
+ ## Contributing
46
+
47
+ Bug reports and pull requests are welcome on GitHub at https://github.com/epfl-si/warden_openid_bearer.
48
+
49
+ ## License
50
+
51
+ The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
data/Rakefile ADDED
@@ -0,0 +1,10 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "bundler/gem_tasks"
4
+ require "rspec/core/rake_task"
5
+
6
+ RSpec::Core::RakeTask.new(:spec)
7
+
8
+ require "standard/rake"
9
+
10
+ task default: %i[spec standard]
@@ -0,0 +1,33 @@
1
+ module WardenOpenidBearer
2
+ # We don't need an overengineered approach based on the Rails cache.
3
+ # No, really.
4
+ module CacheMixin
5
+ def cached_by(key, &do_it)
6
+ # We could support more complex types (e.g. arrays) as
7
+ # value-type cache keys; but right now, our use cases don't
8
+ # require it:
9
+ is_value_type = key.is_a? String
10
+ cache = if is_value_type
11
+ @__cache_mixin__cache ||= {}
12
+ else
13
+ # Use the ::ObjectSpace::WeakMap private API, because the
14
+ # endeavor of reinventing weak maps on top of (public)
15
+ # WeakRef's would be called an inversion of abstraction and
16
+ # would be considered harmful. Sue me (I have unit tests).
17
+ @__cache_mixin__weakmap_cache ||= ::ObjectSpace::WeakMap.new
18
+ end
19
+
20
+ now = Time.now()
21
+
22
+ if (cached = cache[key])
23
+ unless respond_to?(:cache_timeout) && now - cached[:fetched_at] > cache_timeout
24
+ return cached[:payload]
25
+ end
26
+ end
27
+
28
+ retval = do_it.call
29
+ cache[key] = {payload: retval, fetched_at: now}
30
+ retval
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,42 @@
1
+ module WardenOpenidBearer
2
+ # Cacheable configuration (periodically re-)fetched starting from
3
+ # the OpenID authentication server's “well-known” endpoint
4
+ class DiscoveredConfig
5
+ include CacheMixin
6
+
7
+ def initialize(metadata_uri)
8
+ @metadata_uri = metadata_uri
9
+ end
10
+
11
+ # Called by the CacheMixin.
12
+ def cache_timeout
13
+ @cache_timeout ||= 900
14
+ end
15
+ # Provide a public API for tuning the timeout.
16
+ attr_writer :cache_timeout
17
+
18
+ def jwks
19
+ json(metadata[:jwks_uri])
20
+ end
21
+
22
+ def issuer
23
+ metadata[:issuer]
24
+ end
25
+
26
+ def authorization_algs
27
+ metadata[:authorization_signing_alg_values_supported]
28
+ end
29
+
30
+ private
31
+
32
+ def metadata
33
+ json(@metadata_uri)
34
+ end
35
+
36
+ def json(uri)
37
+ cached_by(uri) do
38
+ JSON.parse(Net::HTTP.get_response(URI(uri)).body, symbolize_names: true)
39
+ end
40
+ end
41
+ end
42
+ end
@@ -0,0 +1,31 @@
1
+ require "warden"
2
+
3
+ module WardenOpenidBearer
4
+ # Add this mixin to your `Warden::Strategies::Base` subclass to
5
+ # streamline the `Warden::Strategies.add()` business.
6
+ #
7
+ # If you mix this into `Your::Class` (or inherit from one that
8
+ # does, such as `OIDCBearer::Strategy`), then you can say
9
+ #
10
+ # manager.default_strategies Your::Class.register!
11
+ #
12
+ module Registerer
13
+ def self.included(klass)
14
+ klass.extend(ClassMethods)
15
+ end
16
+
17
+ module ClassMethods
18
+ def register!(as_symbol = default_registration_symbol)
19
+ return @registered_symbol if @registered_symbol
20
+ Warden::Strategies.add(as_symbol, self)
21
+ @registered_symbol = as_symbol
22
+ end
23
+
24
+ protected
25
+
26
+ def default_registration_symbol
27
+ name.delete(":").sub(/Strategy$/, "").underscore.to_sym
28
+ end
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,143 @@
1
+ # Like `WardenOpenidAuth::Strategy` in
2
+ # `lib/warden_openid_auth/strategy.rb` from the `warden_openid_auth`
3
+ # gem, except done right for a modern, split-backend Web application
4
+ # (in which the browser takes charge of the OAuth2 login dance, and
5
+ # the back-end only checks signatures on the JWT claims).
6
+ #
7
+ # You shoud subclass `WardenOpenidBearer::Strategy` and override the
8
+ # `user_of_claims` protected method if you want `env['warden'].user`
9
+ # to be a “real” user object (instead of just a hash of OIDC claims,
10
+ # which is what happens when using `WardenOpenidBearer::Strategy` directly).
11
+ # If you want your Rails app to support more than one OIDC
12
+ # authentication server, you should also subclass
13
+ # `WardenOpenidBearer::Strategy` and override the `metadata_url` method.
14
+ #
15
+ # This class has a `self.register!` method, which makes things
16
+ # (slightly) easier than calling `Warden::Strategies.add` yourself.
17
+ # See `WardenOpenidBearer::Registerer` for details.
18
+ module WardenOpenidBearer
19
+ class Strategy < Warden::Strategies::Base
20
+ include WardenOpenidBearer::Registerer # Provides self.register!
21
+ include WardenOpenidBearer::CacheMixin
22
+
23
+ def valid?
24
+ return if !token
25
+ # Do the issuer check here, so as to seamlessly support multiple
26
+ # OIDC issuers inside the same app. If a token is not “for us”,
27
+ # we want to defer to the other Warden strategy instances in the
28
+ # stack (one which could typically be another instance of either
29
+ # WardenOpenidBearer::Strategy, or one of its subclasses); therefore, we
30
+ # want to return `false` if issuers don't match.
31
+ untrusted_issuer == config.issuer
32
+ end
33
+
34
+ def authenticate!
35
+ if (c = claims)
36
+ success! user_of_claims(c)
37
+ else
38
+ # Given that `valid?` did return true previously,
39
+ # we know the status with precision:
40
+ fail! "Invalid OIDC bearer token"
41
+ end
42
+ rescue JWT::ExpiredSignature
43
+ fail! "Expired OIDC bearer token"
44
+ end
45
+
46
+ # Overridden to always return false, because we typically *don't*
47
+ # want persistent sessions for an OpenID-Connect resource server —
48
+ # Everything we need to know is in the JWT token.
49
+ def store?
50
+ false
51
+ end
52
+
53
+ # Made public so that one may tune the `strategy.config.cache_timeout`:
54
+ def config
55
+ return @config if @config
56
+ @config = WardenOpenidBearer::DiscoveredConfig.new(metadata_url)
57
+ @config.cache_timeout = cache_timeout
58
+ @config
59
+ end
60
+
61
+ protected
62
+
63
+ # Dummy implementation for applications that don't really care
64
+ # about `env['warden'].user` being an object (or at all). Override
65
+ # in a subclass if you do care.
66
+ def user_of_claims(claims)
67
+ claims
68
+ end
69
+
70
+ # Returns the URL of the OIDC metadata for the authentication server,
71
+ # which typically ends with `/.well-known/openid-configuration`
72
+ #
73
+ # The default implementation obeys the `.openid_metadata_url`
74
+ # setting, as set in a `WardenOpenidBearer.configure` block. Override
75
+ # in a subclass if you don't want all your OIDC claims to be
76
+ # checked against one and the same authentication server. (If you
77
+ # want to support two authentication servers, for instance, you
78
+ # should have two subclasses.)
79
+ def metadata_url
80
+ WardenOpenidBearer.config.openid_metadata_url
81
+ end
82
+
83
+ # Returns the cache timeout for the security data obtained from
84
+ # the authentication server.
85
+ #
86
+ # The default implementation uses a global configuration. Like
87
+ # `metadata_url`, you should override this in multiple subclasses
88
+ # if you want multiple OpenID authentication servers.
89
+ def cache_timeout
90
+ WardenOpenidBearer.config.cache_timeout
91
+ end
92
+
93
+ # Returns the JWT token from `request.headers['Authorization']`
94
+ # (which may or may not be valid)
95
+ def token
96
+ # We call this one quite a lot, so we want some caching. Also,
97
+ # it so happens that Warden only constructs a single instance of
98
+ # this class and re-uses it across requests (see
99
+ # `_fetch_strategy` in `lib/warden/proxy.rb`).
100
+ cached_by(request) do
101
+ puts request.headers
102
+ strategy, token = (request.headers["Authorization"] || "").split(" ")
103
+ token if (strategy || "").downcase == "bearer"
104
+ end
105
+ end
106
+
107
+ # Returns the JWT claims, only if the cryptographic signature and
108
+ # other security requirements (in particular, the expiration
109
+ # timestamp) check out.
110
+ def claims
111
+ JWT.decode(token, nil, true, jwt_decode_opts).first
112
+ end
113
+
114
+ def jwt_decode_opts
115
+ # Note: issuer check was already done in `valid?`, see
116
+ # explanations there; skip it here.
117
+ {
118
+ algorithm: algorithm,
119
+ verify_expiration: true,
120
+ verify_not_before: true,
121
+ verify_iat: true,
122
+ jwks: config.jwks
123
+ }
124
+ end
125
+
126
+ def algorithm
127
+ return untrusted_algorithm if
128
+ config.authorization_algs.member? untrusted_algorithm
129
+ end
130
+
131
+ def untrusted_fields
132
+ JWT.decode(token, nil, false)
133
+ end
134
+
135
+ def untrusted_algorithm
136
+ untrusted_fields.last["alg"]
137
+ end
138
+
139
+ def untrusted_issuer
140
+ untrusted_fields.first["iss"]
141
+ end
142
+ end
143
+ end
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ module WardenOpenidBearer
4
+ VERSION = "0.1.0"
5
+ end
@@ -0,0 +1,16 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "dry/configurable"
4
+
5
+ require_relative "warden_openid_bearer/version"
6
+ require_relative "warden_openid_bearer/registerer"
7
+ require_relative "warden_openid_bearer/cache_mixin"
8
+ require_relative "warden_openid_bearer/discovered_config"
9
+ require_relative "warden_openid_bearer/strategy"
10
+
11
+ module WardenOpenidBearer
12
+ extend Dry::Configurable
13
+
14
+ setting :openid_metadata_url, constructor: ->(url) { URI(url) }
15
+ setting :cache_timeout, default: 900
16
+ end
@@ -0,0 +1,4 @@
1
+ module WardenOpenidBearer
2
+ VERSION: String
3
+ # See the writing guide of rbs: https://github.com/ruby/rbs#guides
4
+ end
@@ -0,0 +1,45 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "lib/warden_openid_bearer/version"
4
+
5
+ Gem::Specification.new do |spec|
6
+ spec.name = "warden_openid_bearer"
7
+ spec.version = WardenOpenidBearer::VERSION
8
+ spec.authors = ["Dominique Quatravaux"]
9
+ spec.email = ["dominique.quatravaux@epfl.ch"]
10
+
11
+ spec.summary = "Warden strategy to validate OpenID-Connect bearer tokens"
12
+ spec.description = <<~END_DESCRIPTION
13
+
14
+ This gem is like the `warden_openid_auth` gem, except that it only
15
+ provides support for the very last step of the OAuth code flow, i.e.
16
+ when the resource server / relying party (your Ruby Web app)
17
+ validates and decodes the JWT token.
18
+
19
+ Use this gem if your client-side Web (or mobile) app will be taking
20
+ care of the rest of the OAuth2 motions, such as redirecting (or
21
+ opening a popup window) to the authentication server at login time,
22
+ managing and refreshing tokens, doing all these unspeakable things
23
+ with iframes, etc.
24
+
25
+ END_DESCRIPTION
26
+ spec.homepage = "https://github.com/epfl-si/warden_openid_bearer"
27
+ spec.license = "MIT"
28
+ spec.required_ruby_version = ">= 2.6.0"
29
+
30
+ spec.metadata["homepage_uri"] = spec.homepage
31
+ spec.metadata["source_code_uri"] = spec.homepage
32
+ spec.metadata["changelog_uri"] = "#{spec.homepage}/blob/master/CHANGELOG.md"
33
+ spec.metadata["my_side_project_has_a_side_project"] = "https://github.com/epfl-si/rails.starterkit"
34
+
35
+ # The `git ls-files -z` loads the files in the RubyGem that have been added into git.
36
+ spec.files = Dir.chdir(__dir__) do
37
+ `git ls-files -z`.split("\x0").reject do |f|
38
+ (f == __FILE__) || f.match(%r{\A(?:(?:bin|test|spec|features)/|\.(?:git|travis|circleci)|appveyor)})
39
+ end
40
+ end
41
+ spec.require_paths = ["lib"]
42
+
43
+ spec.add_dependency "warden", "~> 1.2.0"
44
+ spec.add_dependency "dry-configurable", "~> 0.15.0"
45
+ end
metadata ADDED
@@ -0,0 +1,103 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: warden_openid_bearer
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Dominique Quatravaux
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2022-10-07 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: warden
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: 1.2.0
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: 1.2.0
27
+ - !ruby/object:Gem::Dependency
28
+ name: dry-configurable
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: 0.15.0
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: 0.15.0
41
+ description: |2+
42
+
43
+ This gem is like the `warden_openid_auth` gem, except that it only
44
+ provides support for the very last step of the OAuth code flow, i.e.
45
+ when the resource server / relying party (your Ruby Web app)
46
+ validates and decodes the JWT token.
47
+
48
+ Use this gem if your client-side Web (or mobile) app will be taking
49
+ care of the rest of the OAuth2 motions, such as redirecting (or
50
+ opening a popup window) to the authentication server at login time,
51
+ managing and refreshing tokens, doing all these unspeakable things
52
+ with iframes, etc.
53
+
54
+ email:
55
+ - dominique.quatravaux@epfl.ch
56
+ executables: []
57
+ extensions: []
58
+ extra_rdoc_files: []
59
+ files:
60
+ - ".rspec"
61
+ - ".standard.yml"
62
+ - CHANGELOG.md
63
+ - Gemfile
64
+ - LICENSE.txt
65
+ - README.md
66
+ - Rakefile
67
+ - lib/warden_openid_bearer.rb
68
+ - lib/warden_openid_bearer/cache_mixin.rb
69
+ - lib/warden_openid_bearer/discovered_config.rb
70
+ - lib/warden_openid_bearer/registerer.rb
71
+ - lib/warden_openid_bearer/strategy.rb
72
+ - lib/warden_openid_bearer/version.rb
73
+ - sig/warden_openid_bearer.rbs
74
+ - warden_openid_bearer.gemspec
75
+ homepage: https://github.com/epfl-si/warden_openid_bearer
76
+ licenses:
77
+ - MIT
78
+ metadata:
79
+ homepage_uri: https://github.com/epfl-si/warden_openid_bearer
80
+ source_code_uri: https://github.com/epfl-si/warden_openid_bearer
81
+ changelog_uri: https://github.com/epfl-si/warden_openid_bearer/blob/master/CHANGELOG.md
82
+ my_side_project_has_a_side_project: https://github.com/epfl-si/rails.starterkit
83
+ post_install_message:
84
+ rdoc_options: []
85
+ require_paths:
86
+ - lib
87
+ required_ruby_version: !ruby/object:Gem::Requirement
88
+ requirements:
89
+ - - ">="
90
+ - !ruby/object:Gem::Version
91
+ version: 2.6.0
92
+ required_rubygems_version: !ruby/object:Gem::Requirement
93
+ requirements:
94
+ - - ">="
95
+ - !ruby/object:Gem::Version
96
+ version: '0'
97
+ requirements: []
98
+ rubygems_version: 3.3.11
99
+ signing_key:
100
+ specification_version: 4
101
+ summary: Warden strategy to validate OpenID-Connect bearer tokens
102
+ test_files: []
103
+ ...