From b529d061c05205239ae9136c76e68e39fcd9830a Mon Sep 17 00:00:00 2001 From: Peter Boling Date: Wed, 25 Sep 2024 13:18:40 -0600 Subject: [PATCH] =?UTF-8?q?=F0=9F=94=96=20Prepare=20for=20release=20v2.0.2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .envrc | 1 + .simplecov | 4 +- CHANGELOG.md | 11 +- Gemfile | 2 - Gemfile.lock | 63 ++- README.md | 68 +++ Rakefile | 7 +- lib/rack/openid/version.rb | 2 +- rack-openid2.gemspec | 12 +- test/{test_helper.rb => support/config.rb} | 20 +- test/test_integration.rb | 566 +++++++-------------- test/test_openid_headers.rb | 46 ++ test/test_rack_openid.rb | 2 +- test/test_simple_auth.rb | 37 ++ 14 files changed, 426 insertions(+), 415 deletions(-) rename test/{test_helper.rb => support/config.rb} (56%) create mode 100644 test/test_openid_headers.rb create mode 100644 test/test_simple_auth.rb diff --git a/.envrc b/.envrc index ec029d8..b56b211 100644 --- a/.envrc +++ b/.envrc @@ -27,6 +27,7 @@ export MAX_ROWS=1 # Setting for simplecov-console gem for tty output, limits to # Internal Debugging Controls export DEBUG=false # do not allow byebug statements (override in .env.local) +export REQUIRE_BENCH=false # set to true in .env.local to turn on require_bench # .env would override anything in this file, if `dotenv` is uncommented below. # .env is a DOCKER standard, and if we use it, it would be in deployed, or DOCKER, environments, diff --git a/.simplecov b/.simplecov index bfe90c0..b540b12 100644 --- a/.simplecov +++ b/.simplecov @@ -1,3 +1,5 @@ require "kettle/soup/cover/config" -SimpleCov.start +SimpleCov.start do + add_filter "test/**/*" +end diff --git a/CHANGELOG.md b/CHANGELOG.md index 8dc6e04..e23e6f5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,9 +10,18 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Fixed ### Removed -## 2.0.2 - 2024-09-20 +## 2.0.2 - 2024-09-25 +- COVERAGE: 97.72% -- 214/219 lines in 4 files +- BRANCH COVERAGE: 86.00% -- 43/50 branches in 4 files +- 39.13% documented ### Added - Improved integration with direnv for development +- Better test coverage +- More tests +- rots v1.0.0 for tests +### Changed +- Upgraded to ruby-openid2 v3.1.0 +- Moved to oauth-xx organization ## 2.0.1 - 2024-09-05 ### Added diff --git a/Gemfile b/Gemfile index e4a17b2..a2b5f72 100644 --- a/Gemfile +++ b/Gemfile @@ -14,5 +14,3 @@ platform :mri do # Debugging gem "byebug", ">= 11" end - -gem "rots", github: "oauth-xx/rots" diff --git a/Gemfile.lock b/Gemfile.lock index a2ccd6c..10ef762 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -1,19 +1,9 @@ -GIT - remote: https://github.com/oauth-xx/rots - revision: caeacbf72b3b5966c3a299391911557d5771ceee - specs: - rots (0.2.2) - net-http - rack (>= 2.0) - ruby-openid2 (~> 3.0) - yaml - PATH remote: . specs: - rack-openid2 (2.0.1) + rack-openid2 (2.0.2) rack (>= 2.2) - ruby-openid2 (~> 3.0, >= 3.0.1) + ruby-openid2 (~> 3.1, >= 3.1.0) version_gem (~> 1.1, >= 1.1.4) GEM @@ -23,6 +13,7 @@ GEM ast (2.4.2) backports (3.25.0) byebug (11.1.3) + date (3.3.4) diff-lcs (1.5.1) diffy (3.4.2) docile (1.4.1) @@ -38,32 +29,54 @@ GEM version_gem (~> 1.1, >= 1.1.4) language_server-protocol (3.17.0.3) lint_roller (1.1.0) + logger (1.6.1) minitest (5.25.1) + minitest-focus (1.4.0) + minitest (>= 4, < 6) minitest-rg (5.3.0) minitest (~> 5.0) - net-http (0.4.1) - uri + openssl (3.2.0) + optparse (0.5.0) ostruct (0.6.0) parallel (1.26.3) parser (3.3.5.0) ast (~> 2.4.1) racc + psych (5.1.2) + stringio racc (1.8.1) rack (3.1.7) rack-session (2.0.0) rack (>= 3.0.0) + rackup (2.1.0) + rack (>= 3) + webrick (~> 1.8) rainbow (3.1.1) rake (13.2.1) regexp_parser (2.9.2) + require_bench (1.0.4) + version_gem (>= 1.1.3, < 4) rexml (3.3.7) + rots (1.0.0) + date + openssl + optparse + psych (~> 5.1) + rack (>= 2) + rackup (>= 2) + ruby-openid2 (~> 3.1, >= 3.1.0) + stringio + version_gem (~> 1.1, >= 1.1.4) + webrick + yaml (~> 0.3) rspec-block_is_expected (1.0.6) - rubocop (1.64.1) + rubocop (1.65.1) json (~> 2.3) language_server-protocol (>= 3.17.0) parallel (~> 1.10) parser (>= 3.3.0.2) rainbow (>= 2.2.2, < 4.0) - regexp_parser (>= 1.8, < 3.0) + regexp_parser (>= 2.4, < 3.0) rexml (>= 3.2.5, < 4.0) rubocop-ast (>= 1.31.1, < 2.0) ruby-progressbar (~> 1.7) @@ -104,9 +117,7 @@ GEM rubocop (~> 1.51) rubocop-thread_safety (0.5.1) rubocop (>= 0.90.0) - ruby-openid2 (3.0.1) - net-http (~> 0.4, >= 0.4.1) - rexml (~> 3.3, >= 3.3.7) + ruby-openid2 (3.1.0) version_gem (~> 1.1, >= 1.1.4) ruby-progressbar (1.13.0) simplecov (0.22.0) @@ -125,10 +136,10 @@ GEM simplecov-rcov (0.3.7) simplecov (>= 0.4.1) simplecov_json_formatter (0.1.4) - standard (1.37.0) + standard (1.40.0) language_server-protocol (~> 3.17.0.2) lint_roller (~> 1.0) - rubocop (~> 1.64.0) + rubocop (~> 1.65.0) standard-custom (~> 1.0.0) standard-performance (~> 1.4) standard-custom (1.0.2) @@ -143,11 +154,12 @@ GEM standard-custom (>= 1.0.2, < 2) standard-performance (>= 1.3.1, < 2) version_gem (>= 1.1.4, < 3) + stringio (3.1.1) terminal-table (3.0.2) unicode-display_width (>= 1.1.1, < 3) unicode-display_width (2.6.0) - uri (0.13.1) version_gem (1.1.4) + webrick (1.8.2) yaml (0.3.0) yard (0.9.37) yard-junk (0.0.10) @@ -162,12 +174,15 @@ PLATFORMS DEPENDENCIES byebug (>= 11) kettle-soup-cover (~> 1.0, >= 1.0.2) - minitest (>= 5) + logger (~> 1.6, >= 1.6.1) + minitest (>= 5, < 6) + minitest-focus (~> 1.4) minitest-rg (>= 5) rack-openid2! rack-session (>= 2) rake (>= 13) - rots! + require_bench (~> 1.0, >= 1.0.4) + rots (~> 1.0) rubocop-lts (~> 18.2, >= 18.2.1) rubocop-minitest (~> 0.36) rubocop-packaging (~> 0.5, >= 0.5.2) diff --git a/README.md b/README.md index 7bc0ec8..81f2e4c 100644 --- a/README.md +++ b/README.md @@ -2,6 +2,8 @@
+[![Version](https://img.shields.io/gem/v/rack-openid2.svg)](https://rubygems.org/gems/rack-openid2) +[![Downloads Today](https://img.shields.io/gem/rd/rack-openid2.svg)](https://github.com/oauth-xx/rack-openid2) [![CI Supported Build][🚎s-wfi]][🚎s-wf] [![CI Unsupported Build][🚎us-wfi]][🚎us-wf] [![CI Style Build][🚎st-wfi]][🚎st-wf] @@ -19,10 +21,46 @@ [🚎hd-wf]: https://github.com/oauth-xx/rack-openid2/actions/workflows/heads.yml [🚎hd-wfi]: https://github.com/oauth-xx/rack-openid2/actions/workflows/heads.yml/badge.svg +----- + +
+ +[![Liberapay Patrons][⛳liberapay-img]][⛳liberapay] +[![Sponsor Me on Github][🖇sponsor-img]][🖇sponsor] +[![Polar Shield][🖇polar-img]][🖇polar] +[![Donate to my FLOSS or refugee efforts at ko-fi.com][🖇kofi-img]][🖇kofi] +[![Donate to my FLOSS or refugee efforts using Patreon][🖇patreon-img]][🖇patreon] + +[⛳liberapay-img]: https://img.shields.io/liberapay/patrons/pboling.svg?logo=liberapay +[⛳liberapay]: https://liberapay.com/pboling/donate +[🖇sponsor-img]: https://img.shields.io/badge/Sponsor_Me!-pboling.svg?style=social&logo=github +[🖇sponsor]: https://github.com/sponsors/pboling +[🖇polar-img]: https://polar.sh/embed/seeks-funding-shield.svg?org=pboling +[🖇polar]: https://polar.sh/pboling +[🖇kofi-img]: https://img.shields.io/badge/buy%20me%20coffee-donate-yellow.svg +[🖇kofi]: https://ko-fi.com/O5O86SNP4 +[🖇patreon-img]: https://img.shields.io/badge/patreon-donate-yellow.svg +[🖇patreon]: https://patreon.com/galtzo + + + + + +
Provides a more HTTPish API around the ruby-openid library. +## Installation + +Install the gem and add to the application's Gemfile by executing: + + $ bundle add rack-openid2 + +If bundler is not being used to manage dependencies, install the gem by executing: + + $ gem install rack-openid2 + ## Usage You trigger an OpenID request similar to HTTP authentication. From your app, return a "401 Unauthorized" and a "WWW-Authenticate" header with the identifier you would like to validate. @@ -94,6 +132,36 @@ __END__ ``` +## General Info + +| Primary Namespace | `Rack::OpenID` | +|-------------------|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| gem name | [ruby-openid2](https://rubygems.org/gems/rack-openid2) | +| code triage | [![Open Source Helpers](https://www.codetriage.com/oauth-xx/rack-openid2/badges/users.svg)](https://www.codetriage.com/oauth-xx/rack-openid2) | +| documentation | [on Github.com][homepage], [on Rdoc.info][documentation] | +| expert support | [![Get help on Codementor](https://cdn.codementor.io/badges/get_help_github.svg)](https://www.codementor.io/peterboling?utm_source=github&utm_medium=button&utm_term=peterboling&utm_campaign=github) | +| `...` 💖 | [![Liberapay Patrons][⛳liberapay-img]][⛳liberapay] [![Sponsor Me][🖇sponsor-img]][🖇sponsor] [![Follow Me on LinkedIn][🖇linkedin-img]][🖇linkedin] [![Find Me on WellFound:][✌️wellfound-img]][✌️wellfound] [![Find Me on CrunchBase][💲crunchbase-img]][💲crunchbase] [![My LinkTree][🌳linktree-img]][🌳linktree] [![Follow Me on Ruby.Social][🐘ruby-mast-img]][🐘ruby-mast] [![Tweet @ Peter][🐦tweet-img]][🐦tweet] [💻][coderme] [🌏][aboutme] | + + +[🐦tweet-img]: https://img.shields.io/twitter/follow/galtzo.svg?style=social&label=Follow%20%40galtzo +[🐦tweet]: http://twitter.com/galtzo +[🚎blog]: http://www.railsbling.com/tags/rack-openid2/ +[🚎blog-img]: https://img.shields.io/badge/blog-railsbling-brightgreen.svg?style=flat +[🖇linkedin]: http://www.linkedin.com/in/peterboling +[🖇linkedin-img]: https://img.shields.io/badge/PeterBoling-blue?style=plastic&logo=linkedin +[✌️wellfound]: https://angel.co/u/peter-boling +[✌️wellfound-img]: https://img.shields.io/badge/peter--boling-orange?style=plastic&logo=wellfound +[💲crunchbase]: https://www.crunchbase.com/person/peter-boling +[💲crunchbase-img]: https://img.shields.io/badge/peter--boling-purple?style=plastic&logo=crunchbase +[🐘ruby-mast]: https://ruby.social/@galtzo +[🐘ruby-mast-img]: https://img.shields.io/mastodon/follow/109447111526622197?domain=https%3A%2F%2Fruby.social&style=plastic&logo=mastodon&label=Ruby%20%40galtzo +[🌳linktree]: https://linktr.ee/galtzo +[🌳linktree-img]: https://img.shields.io/badge/galtzo-purple?style=plastic&logo=linktree + + +[aboutme]: https://about.me/peter.boling +[coderme]: https://coderwall.com/Peter%20Boling + ## TODO - 1 failing test (skipped) diff --git a/Rakefile b/Rakefile index fc0e04d..3bb2d6f 100644 --- a/Rakefile +++ b/Rakefile @@ -2,11 +2,10 @@ require "bundler/gem_tasks" require "rake/testtask" +require "require_bench" if ENV.fetch("REQUIRE_BENCH").casecmp?("true") + desc "Run tests" Rake::TestTask.new("test") do |t| - t.libs << "lib" - t.libs << "test" - t.test_files = FileList["test/**/test_*.rb"] t.verbose = false end @@ -40,4 +39,4 @@ rescue LoadError end end -task default: %i[test rubocop_gradual yard yard:junk] +task default: %i[test rubocop_gradual:autocorrect yard yard:junk] diff --git a/lib/rack/openid/version.rb b/lib/rack/openid/version.rb index c8225fa..f6d2c07 100644 --- a/lib/rack/openid/version.rb +++ b/lib/rack/openid/version.rb @@ -1,7 +1,7 @@ module Rack class OpenID module Version - VERSION = "2.0.1" + VERSION = "2.0.2" end end end diff --git a/rack-openid2.gemspec b/rack-openid2.gemspec index 6522efc..c5749ee 100644 --- a/rack-openid2.gemspec +++ b/rack-openid2.gemspec @@ -42,18 +42,26 @@ Gem::Specification.new do |spec| spec.metadata["rubygems_mfa_required"] = "true" spec.add_dependency("rack", ">= 2.2") - spec.add_dependency("ruby-openid2", "~> 3.0", ">= 3.0.1") + spec.add_dependency("ruby-openid2", "~> 3.1", ">= 3.1.0") spec.add_dependency("version_gem", "~> 1.1", ">= 1.1.4") # Testing - spec.add_development_dependency("minitest", ">= 5") + spec.add_development_dependency("minitest", ">= 5", "< 6") # Use assert_nil if expecting nil + spec.add_development_dependency("minitest-focus", "~> 1.4") spec.add_development_dependency("minitest-rg", ">= 5") spec.add_development_dependency("rack-session", ">= 2") spec.add_development_dependency("rake", ">= 13") + spec.add_development_dependency("rots", "~> 1.0") + + # Test Logging + spec.add_development_dependency("logger", "~> 1.6", ">= 1.6.1") # Coverage spec.add_development_dependency("kettle-soup-cover", "~> 1.0", ">= 1.0.2") + # Debugging + spec.add_development_dependency("require_bench", "~> 1.0", ">= 1.0.4") + # Linting spec.add_development_dependency("rubocop-lts", "~> 18.2", ">= 18.2.1") spec.add_development_dependency("rubocop-minitest", "~> 0.36") diff --git a/test/test_helper.rb b/test/support/config.rb similarity index 56% rename from test/test_helper.rb rename to test/support/config.rb index 7807194..14f5ac9 100644 --- a/test/test_helper.rb +++ b/test/support/config.rb @@ -1,14 +1,15 @@ # External dependencies +require "require_bench" if ENV.fetch("REQUIRE_BENCH").casecmp?("true") require "byebug" if ENV.fetch("DEBUG", "false").casecmp?("true") require "net/http" require "rack" require "rack/session" -# testing libraries +# External testing libraries require "minitest/rg" # Test support -require "support/logging" +require_relative "logging" ## Last thing before loading this gem is to setup code coverage begin @@ -22,9 +23,18 @@ # Testing libraries that need to load after simplecov require "minitest/autorun" +require "minitest/focus" -# Internal dependencies & mixins -require "rack/openid" -require "rack/openid/simple_auth" +# rots depends on this library, but the tests here also depend on it, +# so it needs to load after simplecov, in order to get accurate coverage of this gem, +# since this gem is loaded by rots. +require "rots" +require "rots/mocks" +require "rots/test" OpenID::Util.logger = TestLogging::LOGGER +OpenID.fetcher = Rots::Mocks::Fetcher.new(Rots::Mocks::RotsServer.new) + +# This library +require "rack-openid2" +require "rack/openid/simple_auth" diff --git a/test/test_integration.rb b/test/test_integration.rb index b36b71f..128a2a6 100644 --- a/test/test_integration.rb +++ b/test/test_integration.rb @@ -1,441 +1,259 @@ -# External libraries -require "rots" +require_relative "support/config" -require_relative "test_helper" +describe "openid integration" do + include Rots::Test::RackTestHelpers -describe "integration" do - class MockFetcher - def initialize(app) - @app = app - end - - def fetch(url, body = nil, headers = nil, limit = nil) - opts = (headers || {}).dup - opts[:input] = body - opts[:method] = "POST" if body - env = Rack::MockRequest.env_for(url, opts) - - status, headers, body = @app.call(env) - - buf = [] - buf << "HTTP/1.1 #{status} #{Rack::Utils::HTTP_STATUS_CODES[status]}" - headers.each { |header, value| buf << "#{header}: #{value}" } - buf << "" - body.each { |part| buf << part } + it "with_get" do + app = app({}) + mock_openid_request(app, "/", method: "GET") + follow_openid_redirect!(app) - io = Net::BufferedIO.new(StringIO.new(buf.join("\n"))) - res = Net::HTTPResponse.read_new(io) - res.reading_body(io, true) {} - OpenID::HTTPResponse._from_net_response(res, url) - end + assert_equal 200, @response.status + assert_equal "GET", @response.headers["X-Method"] + assert_equal "/", @response.headers["X-Path"] + assert_equal "success", @response.body end - ROTS_SERVER_URL = "http://localhost:9292" - - RotsApp = Rack::Builder.new do - config = { - "identity" => "john.doe", - "sreg" => { - "nickname" => "jdoe", - "fullname" => "John Doe", - "email" => "jhon@doe.com", - "dob" => Date.parse("1985-09-21"), - "gender" => "M", - }, - } - - map("/%s" % config["identity"]) do - run(Rots::IdentityPageApp.new(config, {})) - end + it "with_deprecated_identity" do + app = app({}) + mock_openid_request(app, "/", method: "GET", identity: "#{Rots::Mocks::RotsServer::SERVER_URL}/john.doe?openid.success=true") + follow_openid_redirect!(app) - map("/server") do - run(Rots::ServerApp.new(config, storage: Dir.tmpdir)) - end + assert_equal 200, @response.status + assert_equal "GET", @response.headers["X-Method"] + assert_equal "/", @response.headers["X-Path"] + assert_equal "success", @response.body end - OpenID.fetcher = MockFetcher.new(RotsApp) + it "with_post_method" do + app = app({}) + mock_openid_request(app, "/", method: "POST") + follow_openid_redirect!(app) - module RackTestHelpers - private - - def process(*args) - env = Rack::MockRequest.env_for(*args) - @response = Rack::MockResponse.new(*@app.call(env)) - end - - def follow_redirect! - assert(@response) - assert_equal(303, @response.status) - - env = Rack::MockRequest.env_for(@response.headers["Location"]) - _status, headers, _body = RotsApp.call(env) - - uri = URI(headers["Location"]) - process("#{uri.path}?#{uri.query}") - end + assert_equal 200, @response.status + assert_equal "POST", @response.headers["X-Method"] + assert_equal "/", @response.headers["X-Path"] + assert_equal "success", @response.body end - describe "headers" do - it "builds header" do - assert_equal 'OpenID identity="http://example.com/"', - Rack::OpenID.build_header(identity: "http://example.com/") - assert_equal 'OpenID identity="http://example.com/?foo=bar"', - Rack::OpenID.build_header(identity: "http://example.com/?foo=bar") + it "with_custom_return_to" do + app = app(return_to: "http://example.org/complete") + mock_openid_request(app, "/", method: "GET") + follow_openid_redirect!(app) - header = Rack::OpenID.build_header(identity: "http://example.com/", return_to: "http://example.org/") - - assert_match(/OpenID /, header) - assert_match(/identity="http:\/\/example\.com\/"/, header) - assert_match(/return_to="http:\/\/example\.org\/"/, header) - - header = Rack::OpenID.build_header(identity: "http://example.com/", required: ["nickname", "email"]) - - assert_match(/OpenID /, header) - assert_match(/identity="http:\/\/example\.com\/"/, header) - assert_match(/required="nickname,email"/, header) - end - - it "parses header" do - assert_equal( - {"identity" => "http://example.com/"}, - Rack::OpenID.parse_header('OpenID identity="http://example.com/"'), - ) - assert_equal( - {"identity" => "http://example.com/?foo=bar"}, - Rack::OpenID.parse_header('OpenID identity="http://example.com/?foo=bar"'), - ) - assert_equal( - {"identity" => "http://example.com/", "return_to" => "http://example.org/"}, - Rack::OpenID.parse_header('OpenID identity="http://example.com/", return_to="http://example.org/"'), - ) - assert_equal( - {"identity" => "http://example.com/", "required" => ["nickname", "email"]}, - Rack::OpenID.parse_header('OpenID identity="http://example.com/", required="nickname,email"'), - ) - - # ensure we don't break standard HTTP basic auth - assert_empty( - Rack::OpenID.parse_header('Realm="Example"'), - ) - end + assert_equal 200, @response.status + assert_equal "GET", @response.headers["X-Method"] + assert_equal "/complete", @response.headers["X-Path"] + assert_equal "success", @response.body end - describe "openid" do - include RackTestHelpers - - it "with_get" do - @app = app({}) - process("/", method: "GET") - follow_redirect! - - assert_equal 200, @response.status - assert_equal "GET", @response.headers["X-Method"] - assert_equal "/", @response.headers["X-Path"] - assert_equal "success", @response.body - end - - it "with_deprecated_identity" do - @app = app({}) - process("/", method: "GET", identity: "#{ROTS_SERVER_URL}/john.doe?openid.success=true") - follow_redirect! - - assert_equal 200, @response.status - assert_equal "GET", @response.headers["X-Method"] - assert_equal "/", @response.headers["X-Path"] - assert_equal "success", @response.body - end + it "with_get_nested_params_custom_return_to" do + url = "http://example.org/complete?user[remember_me]=true" + app = app(return_to: url) + mock_openid_request(app, "/", method: "GET") + follow_openid_redirect!(app) + + assert_equal 200, @response.status + assert_equal "GET", @response.headers["X-Method"] + assert_equal "/complete", @response.headers["X-Path"] + assert_equal "success", @response.body + assert_match(/remember_me/, @response.headers["X-Query-String"]) + end - it "with_post_method" do - @app = app({}) - process("/", method: "POST") - follow_redirect! + it "with_post_nested_params_custom_return_to" do + url = "http://example.org/complete?user[remember_me]=true" + app = app(return_to: url) + mock_openid_request(app, "/", method: "POST") - assert_equal 200, @response.status - assert_equal "POST", @response.headers["X-Method"] - assert_equal "/", @response.headers["X-Path"] - assert_equal "success", @response.body - end + assert_equal 303, @response.status + env = Rack::MockRequest.env_for(@response.headers["Location"]) + _status, headers, _body = Rots::Mocks::RotsServer.new.call(env) - it "with_custom_return_to" do - @app = app(return_to: "http://example.org/complete") - process("/", method: "GET") - follow_redirect! + _uri, input = headers["Location"].split("?", 2) + mock_openid_request(app, "http://example.org/complete?user[remember_me]=true", method: "POST", input: input) - assert_equal 200, @response.status - assert_equal "GET", @response.headers["X-Method"] - assert_equal "/complete", @response.headers["X-Path"] - assert_equal "success", @response.body - end - - it "with_get_nested_params_custom_return_to" do - url = "http://example.org/complete?user[remember_me]=true" - @app = app(return_to: url) - process("/", method: "GET") - follow_redirect! + assert_equal 200, @response.status + assert_equal "POST", @response.headers["X-Method"] + assert_equal "/complete", @response.headers["X-Path"] + assert_equal "success", @response.body + assert_match(/remember_me/, @response.headers["X-Query-String"]) + end - assert_equal 200, @response.status - assert_equal "GET", @response.headers["X-Method"] - assert_equal "/complete", @response.headers["X-Path"] - assert_equal "success", @response.body - assert_match(/remember_me/, @response.headers["X-Query-String"]) - end + it "with_post_method_custom_return_to" do + app = app(return_to: "http://example.org/complete") + mock_openid_request(app, "/", method: "POST") + follow_openid_redirect!(app) - it "with_post_nested_params_custom_return_to" do - url = "http://example.org/complete?user[remember_me]=true" - @app = app(return_to: url) - process("/", method: "POST") + assert_equal 200, @response.status + assert_equal "GET", @response.headers["X-Method"] + assert_equal "/complete", @response.headers["X-Path"] + assert_equal "success", @response.body + end - assert_equal 303, @response.status - env = Rack::MockRequest.env_for(@response.headers["Location"]) - _status, headers, _body = RotsApp.call(env) + it "with_custom_return_method" do + app = app(method: "put") + mock_openid_request(app, "/", method: "GET") + follow_openid_redirect!(app) - _uri, input = headers["Location"].split("?", 2) - process("http://example.org/complete?user[remember_me]=true", method: "POST", input: input) + assert_equal 200, @response.status + assert_equal "PUT", @response.headers["X-Method"] + assert_equal "/", @response.headers["X-Path"] + assert_equal "success", @response.body + end - assert_equal 200, @response.status - assert_equal "POST", @response.headers["X-Method"] - assert_equal "/complete", @response.headers["X-Path"] - assert_equal "success", @response.body - assert_match(/remember_me/, @response.headers["X-Query-String"]) - end + it "with_simple_registration_fields" do + app = app(required: ["nickname", "email"], optional: "fullname") + mock_openid_request(app, "/", method: "GET") + follow_openid_redirect!(app) - it "with_post_method_custom_return_to" do - @app = app(return_to: "http://example.org/complete") - process("/", method: "POST") - follow_redirect! + assert_equal 200, @response.status + assert_equal "GET", @response.headers["X-Method"] + assert_equal "/", @response.headers["X-Path"] + assert_equal "success", @response.body + end - assert_equal 200, @response.status - assert_equal "GET", @response.headers["X-Method"] - assert_equal "/complete", @response.headers["X-Path"] - assert_equal "success", @response.body - end + it "with_attribute_exchange" do + app = app( + required: ["http://axschema.org/namePerson/friendly", "http://axschema.org/contact/email"], + optional: "http://axschema.org/namePerson", + ) + mock_openid_request(app, "/", method: "GET") + follow_openid_redirect!(app) + + assert_equal 200, @response.status + assert_equal "GET", @response.headers["X-Method"] + assert_equal "/", @response.headers["X-Path"] + assert_equal "success", @response.body + end - it "with_custom_return_method" do - @app = app(method: "put") - process("/", method: "GET") - follow_redirect! + it "with_oauth" do + app = app( + "oauth[consumer]": "www.example.com", + "oauth[scope]": ["http://docs.google.com/feeds/", "http://spreadsheets.google.com/feeds/"], + ) + mock_openid_request(app, "/", method: "GET") - assert_equal 200, @response.status - assert_equal "PUT", @response.headers["X-Method"] - assert_equal "/", @response.headers["X-Path"] - assert_equal "success", @response.body - end + location = @response.headers["Location"] - it "with_simple_registration_fields" do - @app = app(required: ["nickname", "email"], optional: "fullname") - process("/", method: "GET") - follow_redirect! + assert_match(/openid.oauth.consumer/, location) + assert_match(/openid.oauth.scope/, location) - assert_equal 200, @response.status - assert_equal "GET", @response.headers["X-Method"] - assert_equal "/", @response.headers["X-Path"] - assert_equal "success", @response.body - end + follow_openid_redirect!(app) - it "with_attribute_exchange" do - @app = app( - required: ["http://axschema.org/namePerson/friendly", "http://axschema.org/contact/email"], - optional: "http://axschema.org/namePerson", - ) - process("/", method: "GET") - follow_redirect! - - assert_equal 200, @response.status - assert_equal "GET", @response.headers["X-Method"] - assert_equal "/", @response.headers["X-Path"] - assert_equal "success", @response.body - end + assert_equal 200, @response.status + assert_equal "GET", @response.headers["X-Method"] + assert_equal "/", @response.headers["X-Path"] + assert_equal "success", @response.body + end - it "with_oauth" do - @app = app( - "oauth[consumer]": "www.example.com", - "oauth[scope]": ["http://docs.google.com/feeds/", "http://spreadsheets.google.com/feeds/"], - ) - process("/", method: "GET") + it "with_pape" do + app = app( + "pape[preferred_auth_policies]": ["test_policy1", "test_policy2"], + "pape[max_auth_age]": 600, + ) + mock_openid_request(app, "/", method: "GET") - location = @response.headers["Location"] + location = @response.headers["Location"] - assert_match(/openid.oauth.consumer/, location) - assert_match(/openid.oauth.scope/, location) + assert_match(/pape\.preferred_auth_policies=test_policy1\+test_policy2/, location) + assert_match(/pape\.max_auth_age=600/, location) - follow_redirect! + follow_openid_redirect!(app) - assert_equal 200, @response.status - assert_equal "GET", @response.headers["X-Method"] - assert_equal "/", @response.headers["X-Path"] - assert_equal "success", @response.body - end + assert_equal 200, @response.status + assert_equal "GET", @response.headers["X-Method"] + assert_equal "/", @response.headers["X-Path"] + assert_equal "success", @response.body + end - it "with_pape" do - @app = app( - "pape[preferred_auth_policies]": ["test_policy1", "test_policy2"], - "pape[max_auth_age]": 600, - ) - process("/", method: "GET") + it "with_immediate_mode_setup_needed" do + skip("because failing, and not enough time to fix all the things") do + app = app(identifier: "#{Rots::Mocks::RotsServer::SERVER_URL}/john.doe?openid.success=false", immediate: true) + mock_openid_request(app, "/", method: "GET") location = @response.headers["Location"] - assert_match(/pape\.preferred_auth_policies=test_policy1\+test_policy2/, location) - assert_match(/pape\.max_auth_age=600/, location) + assert_match(/openid.mode=checkid_immediate/, location) - follow_redirect! + follow_openid_redirect!(app) - assert_equal 200, @response.status + assert_equal 307, @response.status assert_equal "GET", @response.headers["X-Method"] assert_equal "/", @response.headers["X-Path"] - assert_equal "success", @response.body + assert_equal Rots::Mocks::RotsServer::SERVER_URL, @response.headers["Location"] + assert_equal "setup_needed", @response.body end + end - it "with_immediate_mode_setup_needed" do - skip("because failing, and not enough time to fix all the things") do - @app = app(identifier: "#{ROTS_SERVER_URL}/john.doe?openid.success=false", immediate: true) - process("/", method: "GET") - - location = @response.headers["Location"] - - assert_match(/openid.mode=checkid_immediate/, location) - - follow_redirect! - - assert_equal 307, @response.status - assert_equal "GET", @response.headers["X-Method"] - assert_equal "/", @response.headers["X-Path"] - assert_equal ROTS_SERVER_URL, @response.headers["Location"] - assert_equal "setup_needed", @response.body - end - end - - it "with_realm_wildcard" do - @app = app( - realm_domain: "*.example.org", - ) - process("/", method: "GET") - - location = @response.headers["Location"] - - assert_match(/openid.realm=http%3A%2F%2F%2A.example.org/, location) - - follow_redirect! - - assert_equal 200, @response.status - end - - it "with_inferred_realm" do - @app = app({}) - process("/", method: "GET") - - location = @response.headers["Location"] - - assert_match(/openid.realm=http%3A%2F%2Fexample.org/, location) - - follow_redirect! - - assert_equal 200, @response.status - end + it "with_realm_wildcard" do + app = app( + realm_domain: "*.example.org", + ) + mock_openid_request(app, "/", method: "GET") - it "with_missing_id" do - @app = app(identifier: "#{ROTS_SERVER_URL}/john.doe") - process("/", method: "GET") - follow_redirect! + location = @response.headers["Location"] - assert_equal 400, @response.status - assert_equal "GET", @response.headers["X-Method"] - assert_equal "/", @response.headers["X-Path"] - assert_equal "cancel", @response.body - end + assert_match(/openid.realm=http%3A%2F%2F%2A.example.org/, location) - it "with_timeout" do - @app = app(identifier: ROTS_SERVER_URL) - process("/", method: "GET") + follow_openid_redirect!(app) - assert_equal 400, @response.status - assert_equal "GET", @response.headers["X-Method"] - assert_equal "/", @response.headers["X-Path"] - assert_equal "missing", @response.body - end + assert_equal 200, @response.status + end - it "sanitize_query_string" do - @app = app({}) - process("/", method: "GET") - follow_redirect! + it "with_inferred_realm" do + app = app({}) + mock_openid_request(app, "/", method: "GET") - assert_equal 200, @response.status - assert_equal "/", @response.headers["X-Path"] - assert_equal "", @response.headers["X-Query-String"] - end + location = @response.headers["Location"] - it "passthrough_standard_http_basic_auth" do - @app = app({}) - process("/", :method => "GET", "MOCK_HTTP_BASIC_AUTH" => "1") + assert_match(/openid.realm=http%3A%2F%2Fexample.org/, location) - assert_equal 401, @response.status - end + follow_openid_redirect!(app) - private - - def app(options = {}) - options[:identifier] ||= "#{ROTS_SERVER_URL}/john.doe?openid.success=true" - - rack_app = lambda { |env| - if (resp = env[Rack::OpenID::RESPONSE]) - headers = { - "X-Path" => env["PATH_INFO"], - "X-Method" => env["REQUEST_METHOD"], - "X-Query-String" => env["QUERY_STRING"], - } - if resp.status == :success - [200, headers, [resp.status.to_s]] - elsif resp.status == :setup_needed - headers["Location"] = ROTS_SERVER_URL # TODO update Rots to properly send user_setup_url. This should come from resp. - [307, headers, [resp.status.to_s]] - else - [400, headers, [resp.status.to_s]] - end - elsif env["MOCK_HTTP_BASIC_AUTH"] - [401, {Rack::OpenID::AUTHENTICATE_HEADER => 'Realm="Example"'}, []] - else - [401, {Rack::OpenID::AUTHENTICATE_HEADER => Rack::OpenID.build_header(options)}, []] - end - } - Rack::Session::Pool.new(Rack::OpenID.new(rack_app)) - end + assert_equal 200, @response.status end - describe "simple auth" do - include RackTestHelpers - - it "can login" do - @app = simple_app("#{ROTS_SERVER_URL}/john.doe?openid.success=true") + it "with_missing_id" do + app = app(identifier: "#{Rots::Mocks::RotsServer::SERVER_URL}/john.doe") + mock_openid_request(app, "/", method: "GET") + follow_openid_redirect!(app) - process "/dashboard" - follow_redirect! + assert_equal 400, @response.status + assert_equal "GET", @response.headers["X-Method"] + assert_equal "/", @response.headers["X-Path"] + assert_equal "cancel", @response.body + end - assert_equal 303, @response.status - assert_equal "http://example.org/dashboard", @response.headers["Location"] + it "with_timeout" do + app = app(identifier: Rots::Mocks::RotsServer::SERVER_URL) + mock_openid_request(app, "/", method: "GET") - cookie = @response.headers["Set-Cookie"].split(";").first - process "/dashboard", "HTTP_COOKIE" => cookie + assert_equal 400, @response.status + assert_equal "GET", @response.headers["X-Method"] + assert_equal "/", @response.headers["X-Path"] + assert_equal "missing", @response.body + end - assert_equal 200, @response.status - assert_equal "Hello", @response.body - end + it "sanitize_query_string" do + app = app({}) + mock_openid_request(app, "/", method: "GET") + follow_openid_redirect!(app) - it "fails login" do - @app = simple_app("#{ROTS_SERVER_URL}/john.doe") - @app = simple_app("#{ROTS_SERVER_URL}/john.doe") + assert_equal 200, @response.status + assert_equal "/", @response.headers["X-Path"] + assert_equal "", @response.headers["X-Query-String"] + end - process "/dashboard" - follow_redirect! + it "passthrough_standard_http_basic_auth" do + app = app({}) + mock_openid_request(app, "/", :method => "GET", "MOCK_HTTP_BASIC_AUTH" => "1") - assert_match ROTS_SERVER_URL, @response.headers["Location"] - end + assert_equal 401, @response.status + end - private + private - def simple_app(identifier) - rack_app = lambda { |env| [200, {"Content-Type" => "text/html"}, ["Hello"]] } - rack_app = Rack::OpenID::SimpleAuth.new(rack_app, identifier) - Rack::Session::Pool.new(rack_app) - end + def app(options = {}) + Rots::Mocks::ClientApp.new(**options) end end diff --git a/test/test_openid_headers.rb b/test/test_openid_headers.rb new file mode 100644 index 0000000..47d0e66 --- /dev/null +++ b/test/test_openid_headers.rb @@ -0,0 +1,46 @@ +require_relative "support/config" + +describe "openid headers" do + it "builds header" do + assert_equal 'OpenID identity="http://example.com/"', + Rack::OpenID.build_header(identity: "http://example.com/") + assert_equal 'OpenID identity="http://example.com/?foo=bar"', + Rack::OpenID.build_header(identity: "http://example.com/?foo=bar") + + header = Rack::OpenID.build_header(identity: "http://example.com/", return_to: "http://example.org/") + + assert_match(/OpenID /, header) + assert_match(/identity="http:\/\/example\.com\/"/, header) + assert_match(/return_to="http:\/\/example\.org\/"/, header) + + header = Rack::OpenID.build_header(identity: "http://example.com/", required: ["nickname", "email"]) + + assert_match(/OpenID /, header) + assert_match(/identity="http:\/\/example\.com\/"/, header) + assert_match(/required="nickname,email"/, header) + end + + it "parses header" do + assert_equal( + {"identity" => "http://example.com/"}, + Rack::OpenID.parse_header('OpenID identity="http://example.com/"'), + ) + assert_equal( + {"identity" => "http://example.com/?foo=bar"}, + Rack::OpenID.parse_header('OpenID identity="http://example.com/?foo=bar"'), + ) + assert_equal( + {"identity" => "http://example.com/", "return_to" => "http://example.org/"}, + Rack::OpenID.parse_header('OpenID identity="http://example.com/", return_to="http://example.org/"'), + ) + assert_equal( + {"identity" => "http://example.com/", "required" => ["nickname", "email"]}, + Rack::OpenID.parse_header('OpenID identity="http://example.com/", required="nickname,email"'), + ) + + # ensure we don't break standard HTTP basic auth + assert_empty( + Rack::OpenID.parse_header('Realm="Example"'), + ) + end +end diff --git a/test/test_rack_openid.rb b/test/test_rack_openid.rb index 80d5679..4c87d28 100644 --- a/test/test_rack_openid.rb +++ b/test/test_rack_openid.rb @@ -1,4 +1,4 @@ -require_relative "test_helper" +require_relative "support/config" describe Rack::OpenID do describe ".sanitize_params!" do diff --git a/test/test_simple_auth.rb b/test/test_simple_auth.rb new file mode 100644 index 0000000..cd316c2 --- /dev/null +++ b/test/test_simple_auth.rb @@ -0,0 +1,37 @@ +require_relative "support/config" + +describe "simple auth" do + include Rots::Test::RackTestHelpers + + it "can login" do + app = simple_app("#{Rots::Mocks::RotsServer::SERVER_URL}/john.doe?openid.success=true") + mock_openid_request app, "/dashboard" + follow_openid_redirect!(app) + + assert_equal 303, @response.status + assert_equal "http://example.org/dashboard", @response.headers["Location"] + + cookie = @response.headers["Set-Cookie"].split(";").first + mock_openid_request app, "/dashboard", "HTTP_COOKIE" => cookie + + assert_equal 200, @response.status + assert_equal "Hello", @response.body + end + + it "fails login" do + app = simple_app("#{Rots::Mocks::RotsServer::SERVER_URL}/john.doe") + + mock_openid_request app, "/dashboard" + follow_openid_redirect!(app) + + assert_match Rots::Mocks::RotsServer::SERVER_URL, @response.headers["Location"] + end + + private + + def simple_app(identifier) + rack_app = lambda { |env| [200, {"Content-Type" => "text/html"}, ["Hello"]] } + rack_app = Rack::OpenID::SimpleAuth.new(rack_app, identifier) + Rack::Session::Pool.new(rack_app) + end +end