diff --git a/.envrc b/.envrc new file mode 100644 index 0000000..b56b211 --- /dev/null +++ b/.envrc @@ -0,0 +1,38 @@ +# Run any command in this library's bin/ without the bin/ prefix! +PATH_add bin + +# Only add things to this file that should be shared with the team. + +# **dotenv** (See end of file for .env.local integration) +# .env would override anything in this file, if enabled. +# .env is a DOCKER standard, and if we use it, it would be in deployed, or DOCKER, environments. +# Override and customize anything below in your own .env.local +# If you are using dotenv and not direnv, +# copy the following `export` statements to your own .env file. + +### General Ruby ### +# Turn off Ruby Warnings about deprecated code +# export RUBYOPT="-W0" + +### External Testing Controls +export K_SOUP_COV_DO=true # Means you want code coverage +# Available formats are html, xml, rcov, lcov, json, tty +export K_SOUP_COV_COMMAND_NAME="MiniTest Coverage" +export K_SOUP_COV_FORMATTERS="html,tty" +export K_SOUP_COV_MIN_BRANCH=86 # Means you want to enforce X% branch coverage +export K_SOUP_COV_MIN_LINE=97 # Means you want to enforce X% line coverage +export K_SOUP_COV_MIN_HARD=true # Means you want the build to fail if the coverage thresholds are not met +export K_SOUP_COV_MULTI_FORMATTERS=true +export MAX_ROWS=1 # Setting for simplecov-console gem for tty output, limits to the worst N rows of bad coverage + +# 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, +# and that is why we generally want to leave it commented out. +# dotenv + +# .env.local will override anything in this file. +dotenv_if_exists .env.local diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml new file mode 100644 index 0000000..5527dc1 --- /dev/null +++ b/.github/FUNDING.yml @@ -0,0 +1,13 @@ +# These are supported funding model platforms + +buy_me_a_coffee: pboling +community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry +github: [pboling] # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2] +issuehunt: pboling # Replace with a single IssueHunt username +ko_fi: pboling # Replace with a single Ko-fi username +liberapay: pboling # Replace with a single Liberapay username +open_collective: # Replace with a single Open Collective username +patreon: galtzo # Replace with a single Patreon username +polar: pboling +thanks_dev: u/gh/pboling +tidelift: rubygems/rack-openid2 # Replace with a single Tidelift platform-name/package-name e.g., npm/babel \ No newline at end of file diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 0000000..46f1c90 --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,13 @@ +version: 2 +updates: + - package-ecosystem: bundler + directory: "/" + schedule: + interval: "daily" + open-pull-requests-limit: 10 + ignore: + - dependency-name: "rubocop-lts" + - package-ecosystem: "github-actions" + directory: "/" + schedule: + interval: "daily" diff --git a/.github/workflows/coverage.yml b/.github/workflows/coverage.yml new file mode 100644 index 0000000..1a18505 --- /dev/null +++ b/.github/workflows/coverage.yml @@ -0,0 +1,85 @@ +name: Ruby - Coverage + +env: + K_SOUP_COV_MIN_BRANCH: 86 + K_SOUP_COV_MIN_LINE: 97 + K_SOUP_COV_MIN_HARD: true + K_SOUP_COV_DO: true + K_SOUP_COV_COMMAND_NAME: "MiniTest Coverage" + +on: + push: + branches: + - 'main' + tags: + - '!*' # Do not execute on tags + pull_request: + branches: + - '*' + # Allow manually triggering the workflow. + workflow_dispatch: + +permissions: + contents: read + +# Cancels all previous workflow runs for the same branch that have not yet completed. +concurrency: + # The concurrency group contains the workflow name and the branch name. + group: "${{ github.workflow }}-${{ github.ref }}" + cancel-in-progress: true + +jobs: + test: + name: Specs with Coverage - Ruby ${{ matrix.ruby }} ${{ matrix.name_extra || '' }} + if: "!contains(github.event.commits[0].message, '[ci skip]') && !contains(github.event.commits[0].message, '[skip ci]')" + env: # $BUNDLE_GEMFILE must be set at the job level, so it is set for all steps + BUNDLE_GEMFILE: ${{ github.workspace }}/gemfiles/${{ matrix.gemfile }}.gemfile + runs-on: ubuntu-latest + strategy: + matrix: + rubygems: + - latest + bundler: + - latest + gemfile: + - coverage + ruby: + - '3.1' + + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Setup Ruby & RubyGems + uses: ruby/setup-ruby@v1 + with: + ruby-version: "${{ matrix.ruby }}" + rubygems: "${{ matrix.rubygems }}" + bundler: "${{ matrix.bundler }}" + bundler-cache: true # runs 'bundle install' and caches installed gems automatically + + - name: Run tests + run: bundle exec rake test + + - name: Code Coverage Summary Report + uses: irongut/CodeCoverageSummary@v1.3.0 + if: ${{ github.event_name == 'pull_request' }} + with: + filename: ./coverage/coverage.xml + badge: true + fail_below_min: true + format: markdown + hide_branch_rate: false + hide_complexity: true + indicators: true + output: both + thresholds: '97 86' + continue-on-error: ${{ matrix.experimental != 'false' }} + + - name: Add Coverage PR Comment + uses: marocchino/sticky-pull-request-comment@v2 + if: ${{ github.event_name == 'pull_request' }} + with: + recreate: true + path: code-coverage-results.md + continue-on-error: ${{ matrix.experimental != 'false' }} diff --git a/.github/workflows/heads.yml b/.github/workflows/heads.yml new file mode 100644 index 0000000..de2d274 --- /dev/null +++ b/.github/workflows/heads.yml @@ -0,0 +1,60 @@ +name: Ruby Heads Matrix + +env: + K_SOUP_COV_DO: false + +on: + push: + branches: + - 'main' + tags: + - '!*' # Do not execute on tags + pull_request: + branches: + - '*' + # Allow manually triggering the workflow. + workflow_dispatch: + +permissions: + contents: read + +# Cancels all previous workflow runs for the same branch that have not yet completed. +concurrency: + # The concurrency group contains the workflow name and the branch name. + group: "${{ github.workflow }}-${{ github.ref }}" + cancel-in-progress: true + +jobs: + test: + name: Specs - Ruby ${{ matrix.ruby }} ${{ matrix.name_extra || '' }} + if: "!contains(github.event.commits[0].message, '[ci skip]') && !contains(github.event.commits[0].message, '[skip ci]')" + env: # $BUNDLE_GEMFILE must be set at the job level, so it is set for all steps + BUNDLE_GEMFILE: ${{ github.workspace }}/gemfiles/${{ matrix.gemfile }}.gemfile + runs-on: ubuntu-latest + continue-on-error: ${{ matrix.experimental || endsWith(matrix.ruby, 'head') }} + strategy: + fail-fast: true + matrix: + rubygems: + - latest + bundler: + - latest + gemfile: + - vanilla + ruby: + - head + + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Setup Ruby & RubyGems + uses: ruby/setup-ruby@v1 + with: + ruby-version: "${{ matrix.ruby }}" + rubygems: "${{ matrix.rubygems }}" + bundler: "${{ matrix.bundler }}" + bundler-cache: true # runs 'bundle install' and caches installed gems automatically + + - name: Run tests + run: bundle exec rake test diff --git a/.github/workflows/style.yml b/.github/workflows/style.yml new file mode 100644 index 0000000..26b1f14 --- /dev/null +++ b/.github/workflows/style.yml @@ -0,0 +1,42 @@ +name: Ruby - Style + +on: + push: + branches: + - 'main' + tags: + - '!*' # Do not execute on tags + pull_request: + branches: + - '*' + +jobs: + rubocop: + name: RuboCop + strategy: + fail-fast: false + matrix: + rubygems: + - latest + bundler: + - latest + gemfile: + - style + ruby: + - "3.2" + runs-on: ubuntu-latest + continue-on-error: ${{ matrix.experimental || endsWith(matrix.ruby, 'head') }} + env: # $BUNDLE_GEMFILE must be set at the job level, so it is set for all steps + BUNDLE_GEMFILE: ${{ github.workspace }}/gemfiles/${{ matrix.gemfile }}.gemfile + steps: + - name: Checkout + uses: actions/checkout@v4 + - name: Setup Ruby & RubyGems + uses: ruby/setup-ruby@v1 + with: + ruby-version: ${{ matrix.ruby }} + rubygems: ${{ matrix.rubygems }} + bundler: ${{ matrix.bundler }} + bundler-cache: true + - name: Run RuboCop + run: bundle exec rake rubocop_gradual:check diff --git a/.github/workflows/supported.yml b/.github/workflows/supported.yml new file mode 100644 index 0000000..aadd9af --- /dev/null +++ b/.github/workflows/supported.yml @@ -0,0 +1,57 @@ +name: Supported Ruby Matrix + +env: + K_SOUP_COV_DO: false + +on: + push: + branches: + - 'main' + tags: + - '!*' # Do not execute on tags + pull_request: + branches: + - '*' + # Allow manually triggering the workflow. + workflow_dispatch: + +permissions: + contents: read + +# Cancels all previous workflow runs for the same branch that have not yet completed. +concurrency: + # The concurrency group contains the workflow name and the branch name. + group: "${{ github.workflow }}-${{ github.ref }}" + cancel-in-progress: true + +jobs: + test: + name: Specs - Ruby ${{ matrix.ruby }}${{ matrix.name_extra || '' }} + if: "!contains(github.event.commits[0].message, '[ci skip]') && !contains(github.event.commits[0].message, '[skip ci]')" + env: # $BUNDLE_GEMFILE must be set at the job level, so it is set for all steps + BUNDLE_GEMFILE: ${{ github.workspace }}/gemfiles/${{ matrix.gemfile }}.gemfile + runs-on: ubuntu-latest + strategy: + matrix: + include: + - ruby: "3.3" + rubygems: latest + bundler: latest + gemfile: vanilla + - ruby: "3.2" + rubygems: latest + bundler: latest + gemfile: vanilla + #- Ruby 3.1 tests are run by coverage.yml + steps: + - name: Checkout + uses: actions/checkout@v4 + - name: Setup Ruby & RubyGems + uses: ruby/setup-ruby@v1 + with: + ruby-version: "${{ matrix.ruby }}" + rubygems: "${{ matrix.rubygems }}" + bundler: "${{ matrix.bundler }}" + bundler-cache: true # runs 'bundle install' and caches installed gems automatically + - name: Run tests + run: bundle exec rake test diff --git a/.github/workflows/unsupported.yml b/.github/workflows/unsupported.yml new file mode 100644 index 0000000..8d53dd7 --- /dev/null +++ b/.github/workflows/unsupported.yml @@ -0,0 +1,55 @@ +name: Unsupported (EOL) Ruby Matrix + +env: + K_SOUP_COV_DO: false + +on: + push: + branches: + - 'main' + tags: + - '!*' # Do not execute on tags + pull_request: + branches: + - '*' + # Allow manually triggering the workflow. + workflow_dispatch: + +# Cancels all previous workflow runs for the same branch that have not yet completed. +concurrency: + # The concurrency group contains the workflow name and the branch name. + group: "${{ github.workflow }}-${{ github.ref }}" + cancel-in-progress: true + +jobs: + test: + name: Specs - Ruby ${{ matrix.ruby }}${{ matrix.name_extra || '' }} + if: "!contains(github.event.commits[0].message, '[ci skip]') && !contains(github.event.commits[0].message, '[skip ci]')" + env: # $BUNDLE_GEMFILE must be set at the job level, so it is set for all steps + BUNDLE_GEMFILE: ${{ github.workspace }}/gemfiles/${{ matrix.gemfile }}.gemfile + strategy: + fail-fast: false + matrix: + include: + - ruby: "3.0" + rubygems: "3.3.27" + bundler: none + gemfile: vanilla + - ruby: "2.7" + rubygems: "3.3.27" + bundler: none + gemfile: vanilla + runs-on: ubuntu-20.04 + continue-on-error: ${{ matrix.experimental || endsWith(matrix.ruby, 'head') }} + steps: + - name: Checkout + uses: actions/checkout@v4 + - name: Setup Ruby & RubyGems + uses: ruby/setup-ruby@v1 + with: + ruby-version: "${{ matrix.ruby }}" + rubygems: "${{ matrix.rubygems }}" + bundler: "${{ matrix.bundler }}" + bundler-cache: true # runs 'bundle install' and caches installed gems automatically + - name: Run tests + run: bundle exec rake test diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..2e23cbd --- /dev/null +++ b/.gitignore @@ -0,0 +1,7 @@ +/doc/ +.env.local +.yardoc/ +coverage/ +pkg/ +.bundle/ +.byebug_history diff --git a/.rubocop.yml b/.rubocop.yml new file mode 100644 index 0000000..ee7c8d5 --- /dev/null +++ b/.rubocop.yml @@ -0,0 +1,8 @@ +inherit_gem: + rubocop-lts: config/rubygem.yml + +require: + - rubocop-minitest + +Style/EmptyElse: + Enabled: false diff --git a/.rubocop_gradual.lock b/.rubocop_gradual.lock new file mode 100644 index 0000000..19e9e56 --- /dev/null +++ b/.rubocop_gradual.lock @@ -0,0 +1,10 @@ +{ + "bin/bundle:3870166066": [ + [33, 24, 58, "Performance/ConstantRegexp: Extract this regexp into a constant, memoize it, or append an `/o` option to its options.", 2287896868], + [59, 40, 62, "Performance/ConstantRegexp: Extract this regexp into a constant, memoize it, or append an `/o` option to its options.", 2593609308], + [64, 5, 20, "ThreadSafety/InstanceVariableInClassMethod: Avoid instance variables in class methods.", 2485198147], + [86, 7, 34, "Style/MethodCallWithArgsParentheses: Use parentheses for method calls with arguments.", 991972134], + [93, 5, 194, "Style/MethodCallWithArgsParentheses: Use parentheses for method calls with arguments.", 1374511802], + [94, 5, 7, "Style/MethodCallWithArgsParentheses: Use parentheses for method calls with arguments.", 285434243] + ] +} diff --git a/.simplecov b/.simplecov new file mode 100644 index 0000000..b540b12 --- /dev/null +++ b/.simplecov @@ -0,0 +1,5 @@ +require "kettle/soup/cover/config" + +SimpleCov.start do + add_filter "test/**/*" +end diff --git a/.tool-versions b/.tool-versions new file mode 100644 index 0000000..1dd1998 --- /dev/null +++ b/.tool-versions @@ -0,0 +1 @@ +ruby 3.3.5 diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index 18700f6..0000000 --- a/.travis.yml +++ /dev/null @@ -1,6 +0,0 @@ -language: ruby -bundler_args: "" -rvm: - - ree - - 1.9.3 - - 2.0.0 diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..a14567b --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,54 @@ +# Changelog +All notable changes to this project will be documented in this file. + +Since version 2.0.0, the format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), +and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). + +## [Unreleased] +### Added +### Changed +### Fixed +### Removed + +## 2.0.3 - 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 Documentation +### Fixed +- Documentation typos +- Documentation in Yard (on RubyDoc.info) + +## 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 +- CHANGELOG.md +- CODE_OF_CONDUCT.md +- CONTRIBUTING.md +- SECURITY.md +### Fixed +- Typo in gemspec description + +## 2.0.0 - 2024-09-04 +- COVERAGE: 97.72% -- 214/219 lines in 3 files +- BRANCH COVERAGE: 86.00% -- 43/50 branches in 3 branches +### Changed +- Upgraded to rack v2+ +- Switched ruby-openid => ruby-openid2 +### Fixed +- Compatibility with Ruby 2.7+ +### Removed +- Support for Ruby < 2.7 diff --git a/CNAME b/CNAME new file mode 100644 index 0000000..953691e --- /dev/null +++ b/CNAME @@ -0,0 +1 @@ +rack-openid2.galtzo.com \ No newline at end of file diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md new file mode 100755 index 0000000..c4880b6 --- /dev/null +++ b/CODE_OF_CONDUCT.md @@ -0,0 +1,84 @@ +# Contributor Covenant Code of Conduct + +## Our Pledge + +We as members, contributors, and leaders pledge to make participation in our community a harassment-free experience for everyone, regardless of age, body size, visible or invisible disability, ethnicity, sex characteristics, gender identity and expression, level of experience, education, socio-economic status, nationality, personal appearance, race, religion, or sexual identity and orientation. + +We pledge to act and interact in ways that contribute to an open, welcoming, diverse, inclusive, and healthy community. + +## Our Standards + +Examples of behavior that contributes to a positive environment for our community include: + +* Demonstrating empathy and kindness toward other people +* Being respectful of differing opinions, viewpoints, and experiences +* Giving and gracefully accepting constructive feedback +* Accepting responsibility and apologizing to those affected by our mistakes, and learning from the experience +* Focusing on what is best not just for us as individuals, but for the overall community + +Examples of unacceptable behavior include: + +* The use of sexualized language or imagery, and sexual attention or + advances of any kind +* Trolling, insulting or derogatory comments, and personal or political attacks +* Public or private harassment +* Publishing others' private information, such as a physical or email + address, without their explicit permission +* Other conduct which could reasonably be considered inappropriate in a + professional setting + +## Enforcement Responsibilities + +Community leaders are responsible for clarifying and enforcing our standards of acceptable behavior and will take appropriate and fair corrective action in response to any behavior that they deem inappropriate, threatening, offensive, or harmful. + +Community leaders have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, and will communicate reasons for moderation decisions when appropriate. + +## Scope + +This Code of Conduct applies within all community spaces, and also applies when an individual is officially representing the community in public spaces. Examples of representing our community include using an official e-mail address, posting via an official social media account, or acting as an appointed representative at an online or offline event. + +## Enforcement + +Instances of abusive, harassing, or otherwise unacceptable behavior may be reported to the community leaders responsible for enforcement at peter.boling@gmail.com. All complaints will be reviewed and investigated promptly and fairly. + +All community leaders are obligated to respect the privacy and security of the reporter of any incident. + +## Enforcement Guidelines + +Community leaders will follow these Community Impact Guidelines in determining the consequences for any action they deem in violation of this Code of Conduct: + +### 1. Correction + +**Community Impact**: Use of inappropriate language or other behavior deemed unprofessional or unwelcome in the community. + +**Consequence**: A private, written warning from community leaders, providing clarity around the nature of the violation and an explanation of why the behavior was inappropriate. A public apology may be requested. + +### 2. Warning + +**Community Impact**: A violation through a single incident or series of actions. + +**Consequence**: A warning with consequences for continued behavior. No interaction with the people involved, including unsolicited interaction with those enforcing the Code of Conduct, for a specified period of time. This includes avoiding interactions in community spaces as well as external channels like social media. Violating these terms may lead to a temporary or permanent ban. + +### 3. Temporary Ban + +**Community Impact**: A serious violation of community standards, including sustained inappropriate behavior. + +**Consequence**: A temporary ban from any sort of interaction or public communication with the community for a specified period of time. No public or private interaction with the people involved, including unsolicited interaction with those enforcing the Code of Conduct, is allowed during this period. Violating these terms may lead to a permanent ban. + +### 4. Permanent Ban + +**Community Impact**: Demonstrating a pattern of violation of community standards, including sustained inappropriate behavior, harassment of an individual, or aggression toward or disparagement of classes of individuals. + +**Consequence**: A permanent ban from any sort of public interaction within the community. + +## Attribution + +This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 2.0, +available at https://www.contributor-covenant.org/version/2/0/code_of_conduct.html. + +Community Impact Guidelines were inspired by [Mozilla's code of conduct enforcement ladder](https://github.com/mozilla/diversity). + +[homepage]: https://www.contributor-covenant.org + +For answers to common questions about this code of conduct, see the FAQ at +https://www.contributor-covenant.org/faq. Translations are available at https://www.contributor-covenant.org/translations. diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100755 index 0000000..94658ce --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,55 @@ +## Contributing + +Bug reports and pull requests are welcome on GitHub at [https://github.com/oauth-xx/rack-openid2][🚎src-main] +. This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to +the [code of conduct][🤝conduct]. + +To submit a patch, please fork the project and create a patch with tests. +Once you're happy with it send a pull request. + +## Release + +### One-time, Per-developer, Setup + +**IMPORTANT**: Your public key for signing gems will need to be picked up by the line in the +`gemspec` defining the `spec.cert_chain` (check the relevant ENV variables there), +in order to sign the new release. +See: [RubyGems Security Guide][🔒️rubygems-security-guide] + +### To release a new version: + +1. Run `bin/setup && bin/rake` as a tests, coverage, & linting sanity check +2. Update the version number in `version.rb` +3. Run `bin/setup && bin/rake` again as a secondary check, and to update `Gemfile.lock` +4. Run `git commit -am "🔖 Prepare release v"` to commit the changes +5. Run `git push` to trigger the final CI pipeline before release, & merge PRs + - NOTE: Remember to [check the build][🧪build]! +6. Run `export GIT_TRUNK_BRANCH_NAME="$(git remote show origin | grep 'HEAD branch' | cut -d ' ' -f5)" && echo $GIT_TRUNK_BRANCH_NAME` +7. Run `git checkout $GIT_TRUNK_BRANCH_NAME` +8. Run `git pull origin $GIT_TRUNK_BRANCH_NAME` to ensure you will release the latest trunk code +9. Set `SOURCE_DATE_EPOCH` so `rake build` and `rake release` use same timestamp, and generate same checksums + - Run `export SOURCE_DATE_EPOCH=$EPOCHSECONDS && echo $SOURCE_DATE_EPOCH` + - If the echo above has no output, then it didn't work. + - Note that you'll need the `zsh/datetime` module, if running `zsh`. + - In `bash` you can use `date +%s` instead, i.e. `export SOURCE_DATE_EPOCH=$(date +%s) && echo $SOURCE_DATE_EPOCH` +10. Run `bundle exec rake build` +11. Run `bin/checksums` (more [context][🔒️rubygems-checksums-pr]) to create SHA-256 and SHA-512 checksums + - Checksums will be committed automatically by the script, but not pushed +12. Run `bundle exec rake release` which will create a git tag for the version, + push git commits and tags, and push the `.gem` file to [rubygems.org][💎rubygems] + +## Contributors + +[![Contributors][🖐contributors-img]][🖐contributors] + +Made with [contributors-img][🖐contrib-rocks]. + +[🧪build]: https://github.com/oauth-xx/rack-openid2/actions +[🤝conduct]: https://github.com/oauth-xx/rack-openid2/blob/main/CODE_OF_CONDUCT.md +[🖐contrib-rocks]: https://contrib.rocks +[🖐contributors]: https://github.com/oauth-xx/rack-openid2/graphs/contributors +[🖐contributors-img]: https://contrib.rocks/image?repo=oauth-xx/rack-openid2 +[💎rubygems]: https://rubygems.org +[🔒️rubygems-security-guide]: https://guides.rubygems.org/security/#building-gems +[🔒️rubygems-checksums-pr]: https://github.com/rubygems/guides/pull/325 +[🚎src-main]: https://github.com/oauth-xx/rack-openid2 diff --git a/Gemfile b/Gemfile index 8f4f412..a2b5f72 100644 --- a/Gemfile +++ b/Gemfile @@ -1,9 +1,16 @@ -source 'https://rubygems.org' +# frozen_string_literal: true + +#### IMPORTANT ####################################################### +# Gemfile is for local development ONLY; Gemfile is NOT loaded in CI # +####################################################### IMPORTANT #### + +source "https://rubygems.org" + +git_source(:github) { |repo_name| "https://github.com/#{repo_name}" } gemspec -gem 'minitest' -gem 'minitest-rg' -gem 'rake' -gem 'bump' -gem 'rots', :git => 'git://github.com/roman/rots.git' +platform :mri do + # Debugging + gem "byebug", ">= 11" +end diff --git a/Gemfile.lock b/Gemfile.lock index aeb44e1..b2dedf0 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -1,34 +1,203 @@ -GIT - remote: git://github.com/roman/rots.git - revision: babb5559aae8914556da528147b6649b9c48f48c - specs: - rots (0.2.1) - PATH remote: . specs: - rack-openid (1.4.2) - rack (>= 1.1.0) - ruby-openid (>= 2.1.8) + rack-openid2 (2.0.3) + rack (>= 2.2) + ruby-openid2 (~> 3.1, >= 3.1.0) + version_gem (~> 1.1, >= 1.1.4) GEM remote: https://rubygems.org/ specs: - bump (0.4.3) - minitest (5.0.8) - minitest-rg (5.0.0) + ansi (1.5.0) + ast (2.4.3) + backports (3.25.0) + base64 (0.2.0) + byebug (12.0.0) + date (3.3.4) + diff-lcs (1.5.1) + diffy (3.4.2) + docile (1.4.1) + json (2.12.2) + kettle-soup-cover (1.0.9) + simplecov (~> 0.22) + simplecov-cobertura (~> 2.1) + simplecov-console (~> 0.9, >= 0.9.1) + simplecov-html (~> 0.12) + simplecov-lcov (~> 0.8) + simplecov-rcov (~> 0.3, >= 0.3.3) + simplecov_json_formatter (~> 0.1, >= 0.1.4) + version_gem (~> 1.1, >= 1.1.7) + language_server-protocol (3.17.0.5) + lint_roller (1.1.0) + logger (1.7.0) + minitest (5.25.5) + minitest-focus (1.4.0) + minitest (>= 4, < 6) + minitest-rg (5.3.0) minitest (~> 5.0) - rack (1.5.2) - rake (10.1.0) - ruby-openid (2.3.0) + openssl (3.2.0) + optparse (0.5.0) + ostruct (0.6.0) + parallel (1.27.0) + parser (3.3.8.0) + ast (~> 2.4.1) + racc + prism (1.4.0) + psych (5.1.2) + stringio + racc (1.8.1) + rack (3.1.16) + rack-session (2.1.1) + base64 (>= 0.1.0) + rack (>= 3.0.0) + rackup (2.1.0) + rack (>= 3) + webrick (~> 1.8) + rainbow (3.1.1) + rake (13.3.0) + regexp_parser (2.10.0) + require_bench (1.0.4) + version_gem (>= 1.1.3, < 4) + rexml (3.4.1) + 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.75.8) + json (~> 2.3) + language_server-protocol (~> 3.17.0.2) + lint_roller (~> 1.1.0) + parallel (~> 1.10) + parser (>= 3.3.0.2) + rainbow (>= 2.2.2, < 4.0) + regexp_parser (>= 2.9.3, < 3.0) + rubocop-ast (>= 1.44.0, < 2.0) + ruby-progressbar (~> 1.7) + unicode-display_width (>= 2.4.0, < 4.0) + rubocop-ast (1.44.1) + parser (>= 3.3.7.2) + prism (~> 1.4) + rubocop-gradual (0.3.6) + diff-lcs (>= 1.2.0, < 2.0) + diffy (~> 3.0) + parallel (~> 1.10) + rainbow (>= 2.2.2, < 4.0) + rubocop (~> 1.0) + rubocop-lts (18.2.1) + rubocop-ruby2_7 (>= 2.0.4, < 3) + standard-rubocop-lts (>= 1.0.3, < 3) + version_gem (>= 1.1.2, < 3) + rubocop-md (1.2.3) + rubocop (>= 1.45) + rubocop-minitest (0.38.1) + lint_roller (~> 1.1) + rubocop (>= 1.75.0, < 2.0) + rubocop-ast (>= 1.38.0, < 2.0) + rubocop-packaging (0.6.0) + lint_roller (~> 1.1.0) + rubocop (>= 1.72.1, < 2.0) + rubocop-performance (1.25.0) + lint_roller (~> 1.1) + rubocop (>= 1.75.0, < 2.0) + rubocop-ast (>= 1.38.0, < 2.0) + rubocop-rake (0.6.0) + rubocop (~> 1.0) + rubocop-ruby2_7 (2.0.6) + rubocop-gradual (~> 0.3, >= 0.3.1) + rubocop-md (~> 1.2) + rubocop-rake (~> 0.6) + rubocop-shopify (~> 2.14) + rubocop-thread_safety (~> 0.5, >= 0.5.1) + standard-rubocop-lts (~> 1.0, >= 1.0.7) + version_gem (>= 1.1.3, < 3) + rubocop-shopify (2.15.1) + rubocop (~> 1.51) + rubocop-thread_safety (0.5.1) + rubocop (>= 0.90.0) + ruby-openid2 (3.1.0) + version_gem (~> 1.1, >= 1.1.4) + ruby-progressbar (1.13.0) + simplecov (0.22.0) + docile (~> 1.1) + simplecov-html (~> 0.11) + simplecov_json_formatter (~> 0.1) + simplecov-cobertura (2.1.0) + rexml + simplecov (~> 0.19) + simplecov-console (0.9.3) + ansi + simplecov + terminal-table + simplecov-html (0.13.1) + simplecov-lcov (0.8.0) + simplecov-rcov (0.3.7) + simplecov (>= 0.4.1) + simplecov_json_formatter (0.1.4) + standard (1.50.0) + language_server-protocol (~> 3.17.0.2) + lint_roller (~> 1.0) + rubocop (~> 1.75.5) + standard-custom (~> 1.0.0) + standard-performance (~> 1.8) + standard-custom (1.0.2) + lint_roller (~> 1.0) + rubocop (~> 1.50) + standard-performance (1.8.0) + lint_roller (~> 1.1) + rubocop-performance (~> 1.25.0) + standard-rubocop-lts (1.0.10) + rspec-block_is_expected (~> 1.0, >= 1.0.5) + standard (>= 1.35.1, < 2) + standard-custom (>= 1.0.2, < 2) + standard-performance (>= 1.3.1, < 2) + version_gem (>= 1.1.4, < 3) + stringio (3.1.1) + terminal-table (4.0.0) + unicode-display_width (>= 1.1.1, < 4) + unicode-display_width (3.1.4) + unicode-emoji (~> 4.0, >= 4.0.4) + unicode-emoji (4.0.4) + version_gem (1.1.8) + webrick (1.8.2) + yaml (0.3.0) + yard (0.9.37) + yard-junk (0.0.10) + backports (>= 3.18) + ostruct + rainbow + yard PLATFORMS ruby DEPENDENCIES - bump - minitest - minitest-rg - rack-openid! - rake - rots! + byebug (>= 11) + kettle-soup-cover (~> 1.0, >= 1.0.2) + logger (~> 1.6, >= 1.6.1) + minitest (>= 5, < 6) + minitest-focus (~> 1.4) + minitest-rg (>= 5) + rack-openid2! + rack-session (>= 2) + rake (>= 13) + 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) + standard (>= 1.35.1) + yard (~> 0.9, >= 0.9.34) + yard-junk (~> 0.0) + +BUNDLED WITH + 2.4.22 diff --git a/LICENSE b/LICENSE.txt similarity index 97% rename from LICENSE rename to LICENSE.txt index 4eb22c4..2c544db 100644 --- a/LICENSE +++ b/LICENSE.txt @@ -1,4 +1,5 @@ Copyright (c) 2010 Joshua Peek +Copyright (c) 2024 Peter Boling Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the diff --git a/README.md b/README.md new file mode 100644 index 0000000..1dbc7bc --- /dev/null +++ b/README.md @@ -0,0 +1,242 @@ +# Rack::OpenID + +[![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] +[![CI Coverage Build][🚎cov-wfi]][🚎cov-wf] +[![CI Heads Build][🚎hd-wfi]][🚎hd-wf] + +[🚎s-wf]: https://github.com/oauth-xx/rack-openid2/actions/workflows/supported.yml +[🚎s-wfi]: https://github.com/oauth-xx/rack-openid2/actions/workflows/supported.yml/badge.svg +[🚎us-wf]: https://github.com/oauth-xx/rack-openid2/actions/workflows/unsupported.yml +[🚎us-wfi]: https://github.com/oauth-xx/rack-openid2/actions/workflows/unsupported.yml/badge.svg +[🚎st-wf]: https://github.com/oauth-xx/rack-openid2/actions/workflows/style.yml +[🚎st-wfi]: https://github.com/oauth-xx/rack-openid2/actions/workflows/style.yml/badge.svg +[🚎cov-wf]: https://github.com/oauth-xx/rack-openid2/actions/workflows/coverage.yml +[🚎cov-wfi]: https://github.com/oauth-xx/rack-openid2/actions/workflows/coverage.yml/badge.svg +[🚎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 Goal Progress][⛳liberapay-img]][⛳liberapay] [![Sponsor Me on Github][🖇sponsor-img]][🖇sponsor] [![Buy me a coffee][🖇buyme-small-img]][🖇buyme] [![Donate on Polar][🖇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/goal/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://img.shields.io/badge/polar-donate-yellow.svg +[🖇polar]: https://polar.sh/pboling +[🖇kofi-img]: https://img.shields.io/badge/a_more_different_coffee-✓-yellow.svg +[🖇kofi]: https://ko-fi.com/O5O86SNP4 +[🖇patreon-img]: https://img.shields.io/badge/patreon-donate-yellow.svg +[🖇patreon]: https://patreon.com/galtzo +[🖇buyme-small-img]: https://img.shields.io/badge/buy_me_a_coffee-✓-yellow.svg?style=flat + +Provides a more HTTP-ish 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. + +On competition, the OpenID response is automatically verified and assigned to `env["rack.openid.response"]`. + +### Rack Example + +```Ruby +MyApp = lambda do |env| + if resp = env["rack.openid.response"] + case resp.status + when :success + ... + when :failure + ... + else + [401, {"WWW-Authenticate" => 'OpenID identifier="http://example.com/"'}, []] + end + end +end + +use Rack::OpenID +run MyApp +``` + +### Sinatra Example + +```Ruby +# Session needs to be before Rack::OpenID +use Rack::Session::Cookie + +require 'rack/openid' +use Rack::OpenID + +get '/login' do + erb :login +end + +post '/login' do + if resp = request.env["rack.openid.response"] + if resp.status == :success + "Welcome: #{resp.display_identifier}" + else + "Error: #{resp.status}" + end + else + headers 'WWW-Authenticate' => Rack::OpenID.build_header( + :identifier => params["openid_identifier"] + ) + throw :halt, [401, 'got openid?'] + end +end + +enable :inline_templates + +__END__ + +@@ login +
+

+ + +

+ +

+ +

+
+``` + +## 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 rubydoc.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 +[documentation]: https://rubydoc.info/github/oauth-xx/rack-openid2 +[homepage]: https://github.com/oauth-xx/rack-openid2 + + +[aboutme]: https://about.me/peter.boling +[coderme]: https://coderwall.com/Peter%20Boling + +## TODO + +- 1 failing test (skipped) +- rewrite tests with minitest/spec + +## 🌈 Contributors + +Current maintainer(s): + +- [Peter Boling](https://github.com/pboling) + +Special thanks to: +- [Joshua Peek](https://github.com/josh) author of original `rack-openid` +- [Michael Grosser](http://grosser.it) maintainer of original `rack-openid` + +and contributors to original `rack-openid`: +- [Kenny Buckler](https://github.com/kbuckler) +- [Mike Dillon](https://github.com/md5) +- [Richard Wilson](https://github.com/Senjai) + +[![Contributors][🖐contributors-img]][🖐contributors] + +Made with [contributors-img][🖐contrib-rocks]. + +[🖐contrib-rocks]: https://contrib.rocks +[🖐contributors]: https://github.com/oauth-xx/rack-openid2/graphs/contributors +[🖐contributors-img]: https://contrib.rocks/image?repo=oauth-xx/rack-openid2 + +## 🪇 Code of Conduct + +Everyone interacting in this project's codebases, issue trackers, +chat rooms and mailing lists is expected to follow the [code of conduct][🪇conduct]. + +[🪇conduct]: CODE_OF_CONDUCT.md + +## 📌 Versioning + +This Library adheres to [Semantic Versioning 2.0.0][📌semver]. +Violations of this scheme should be reported as bugs. +Specifically, if a minor or patch version is released that breaks backward compatibility, +a new version should be immediately released that restores compatibility. +Breaking changes to the public API will only be introduced with new major versions. + +To get a better understanding of how SemVer is intended to work over a project's lifetime, +read this article from the creator of SemVer: + +- ["Major Version Numbers are Not Sacred"][📌major-versions-not-sacred] + +As a result of this policy, you can (and should) specify a dependency on these libraries using +the [Pessimistic Version Constraint][📌pvc] with two digits of precision. + +For example: + +```ruby +spec.add_dependency("rack-openid2", "~> 2.0") +``` + +See [CHANGELOG.md][📌changelog] for list of releases. + +[comment]: <> ( 📌 VERSIONING LINKS ) + +[📌pvc]: http://guides.rubygems.org/patterns/#pessimistic-version-constraint +[📌semver]: http://semver.org/ +[📌major-versions-not-sacred]: https://tom.preston-werner.com/2022/05/23/major-version-numbers-are-not-sacred.html +[📌changelog]: CHANGELOG.md + +## 📄 License + +The gem is available as open source under the terms of +the [MIT License][📄license] [![License: MIT][📄license-img]][📄license-ref]. + +See [LICENSE.txt][📄license] for the official [Copyright Notice][📄copyright-notice-explainer]. + +[comment]: <> ( 📄 LEGAL LINKS ) + +[📄copyright-notice-explainer]: https://opensource.stackexchange.com/questions/5778/why-do-licenses-such-as-the-mit-license-specify-a-single-year +[📄license]: LICENSE.txt +[📄license-ref]: https://opensource.org/licenses/MIT +[📄license-img]: https://img.shields.io/badge/License-MIT-green.svg + +## 🤑 One more thing + +You made it to the bottom of the page, so perhaps you'll indulge me for another 20 seconds. I maintain many dozens of gems, including this one, because I want Ruby to be a great place for people to solve problems, big and small. Please consider supporting my efforts via the giant yellow link below, or one of the others at the head of this README. + +[![Buy me a latte][🖇buyme-img]][🖇buyme] + +[🖇buyme-img]: https://img.buymeacoffee.com/button-api/?text=Buy%20me%20a%20latte&emoji=&slug=pboling&button_colour=FFDD00&font_colour=000000&font_family=Cookie&outline_colour=000000&coffee_colour=ffffff +[🖇buyme]: https://www.buymeacoffee.com/pboling diff --git a/Rakefile b/Rakefile index 3df9fc3..1c2a162 100644 --- a/Rakefile +++ b/Rakefile @@ -1,6 +1,42 @@ -require 'bundler/setup' -require 'bundler/gem_tasks' -require 'bump/tasks' -require 'rake/testtask' +require "bundler/gem_tasks" -Rake::TestTask.new(:default) +require "rake/testtask" + +require "require_bench" if ENV.fetch("REQUIRE_BENCH", "false").casecmp?("true") + +desc "Run tests" +Rake::TestTask.new("test") do |t| + t.verbose = false +end + +begin + require "rubocop/lts" + Rubocop::Lts.install_tasks +rescue LoadError + task(:rubocop_gradual) do + warn("RuboCop (Gradual) is disabled") + end +end + +begin + require "ostruct" # until https://github.com/zverok/yard-junk/pull/42 is merged! + require "yard-junk/rake" + + YardJunk::Rake.define_task +rescue LoadError + task("yard:junk") do + warn("yard:junk is disabled") + end +end + +begin + require "yard" + + YARD::Rake::YardocTask.new(:yard) +rescue LoadError + task(:yard) do + warn("yard is disabled") + end +end + +task default: %i[test rubocop_gradual:autocorrect yard yard:junk] diff --git a/Readme.md b/Readme.md deleted file mode 100644 index fb395fa..0000000 --- a/Readme.md +++ /dev/null @@ -1,96 +0,0 @@ -# Rack::OpenID - -Provides a more HTTPish API around the ruby-openid library. - -# 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. - -On competition, the OpenID response is automatically verified and assigned to `env["rack.openid.response"]`. - -### Rack Example - -```Ruby -MyApp = lambda do |env| - if resp = env["rack.openid.response"] - case resp.status - when :success - ... - when :failure - ... - else - [401, {"WWW-Authenticate" => 'OpenID identifier="http://example.com/"'}, []] - end -end - -use Rack::OpenID -run MyApp -``` - -### Sinatra Example - -```Ruby -# Session needs to be before Rack::OpenID -use Rack::Session::Cookie - -require 'rack/openid' -use Rack::OpenID - -get '/login' do - erb :login -end - -post '/login' do - if resp = request.env["rack.openid.response"] - if resp.status == :success - "Welcome: #{resp.display_identifier}" - else - "Error: #{resp.status}" - end - else - headers 'WWW-Authenticate' => Rack::OpenID.build_header( - :identifier => params["openid_identifier"] - ) - throw :halt, [401, 'got openid?'] - end -end - -enable :inline_templates - -__END__ - -@@ login -
-

- - -

- -

- -

-
-``` - - -TODO -==== - - 1 failing test (skipped) - - rewrite tests with minitest/spec - -Authors -======= - -[Joshua Peek](https://github.com/josh) original author - -### [Contributors](https://github.com/grosser/rack-openid/contributors) - - [Kenny Buckler](https://github.com/kbuckler) - - [Mike Dillon](https://github.com/md5) - - [Richard Wilson](https://github.com/Senjai) - -[Michael Grosser](http://grosser.it)
-michael@grosser.it
-License: MIT
-[![Build Status](https://travis-ci.org/grosser/rack-openid.png)](https://travis-ci.org/grosser/rack-openid) - - diff --git a/SECURITY.md b/SECURITY.md new file mode 100755 index 0000000..c81e245 --- /dev/null +++ b/SECURITY.md @@ -0,0 +1,14 @@ +# Security Policy + +## Supported Versions + +| Version | Supported | +|---------|-----------| +| 2.x | ✅ | +| 1.x | ❌ | + +## Reporting a Vulnerability + +Peter Boling is the primary maintainer of this gem. Please find a way +to [contact him directly](https://railsbling.com/contact) to report the issue. Include as much relevant information as +possible. diff --git a/bin/bundle b/bin/bundle new file mode 100755 index 0000000..42c7fd7 --- /dev/null +++ b/bin/bundle @@ -0,0 +1,109 @@ +#!/usr/bin/env ruby +# frozen_string_literal: true + +# +# This file was generated by Bundler. +# +# The application 'bundle' is installed as part of a gem, and +# this file is here to facilitate running it. +# + +require "rubygems" + +m = Module.new do + module_function + + def invoked_as_script? + File.expand_path($0) == File.expand_path(__FILE__) + end + + def env_var_version + ENV["BUNDLER_VERSION"] + end + + def cli_arg_version + return unless invoked_as_script? # don't want to hijack other binstubs + return unless "update".start_with?(ARGV.first || " ") # must be running `bundle update` + bundler_version = nil + update_index = nil + ARGV.each_with_index do |a, i| + if update_index && update_index.succ == i && a =~ Gem::Version::ANCHORED_VERSION_PATTERN + bundler_version = a + end + next unless a =~ /\A--bundler(?:[= ](#{Gem::Version::VERSION_PATTERN}))?\z/ + bundler_version = $1 + update_index = i + end + bundler_version + end + + def gemfile + gemfile = ENV["BUNDLE_GEMFILE"] + return gemfile if gemfile && !gemfile.empty? + + File.expand_path("../Gemfile", __dir__) + end + + def lockfile + lockfile = + case File.basename(gemfile) + when "gems.rb" then gemfile.sub(/\.rb$/, ".locked") + else "#{gemfile}.lock" + end + File.expand_path(lockfile) + end + + def lockfile_version + return unless File.file?(lockfile) + lockfile_contents = File.read(lockfile) + return unless lockfile_contents =~ /\n\nBUNDLED WITH\n\s{2,}(#{Gem::Version::VERSION_PATTERN})\n/ + Regexp.last_match(1) + end + + def bundler_requirement + @bundler_requirement ||= + env_var_version || + cli_arg_version || + bundler_requirement_for(lockfile_version) + end + + def bundler_requirement_for(version) + return "#{Gem::Requirement.default}.a" unless version + + bundler_gem_version = Gem::Version.new(version) + + bundler_gem_version.approximate_recommendation + end + + def load_bundler! + ENV["BUNDLE_GEMFILE"] ||= gemfile + + activate_bundler + end + + def activate_bundler + gem_error = activation_error_handling do + gem "bundler", bundler_requirement + end + return if gem_error.nil? + require_error = activation_error_handling do + require "bundler/version" + end + return if require_error.nil? && Gem::Requirement.new(bundler_requirement).satisfied_by?(Gem::Version.new(Bundler::VERSION)) + warn "Activating bundler (#{bundler_requirement}) failed:\n#{gem_error.message}\n\nTo install the version of bundler this project requires, run `gem install bundler -v '#{bundler_requirement}'`" + exit 42 + end + + def activation_error_handling + yield + nil + rescue StandardError, LoadError => e + e + end +end + +m.load_bundler! + +if m.invoked_as_script? + load Gem.bin_path("bundler", "bundle") +end diff --git a/bin/byebug b/bin/byebug new file mode 100755 index 0000000..abc90db --- /dev/null +++ b/bin/byebug @@ -0,0 +1,27 @@ +#!/usr/bin/env ruby +# frozen_string_literal: true + +# +# This file was generated by Bundler. +# +# The application 'byebug' is installed as part of a gem, and +# this file is here to facilitate running it. +# + +ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../Gemfile", __dir__) + +bundle_binstub = File.expand_path("bundle", __dir__) + +if File.file?(bundle_binstub) + if File.read(bundle_binstub, 300).include?("This file was generated by Bundler") + load(bundle_binstub) + else + abort("Your `bin/bundle` was not generated by Bundler, so this binstub cannot run. +Replace `bin/bundle` by running `bundle binstubs bundler --force`, then run this command again.") + end +end + +require "rubygems" +require "bundler/setup" + +load Gem.bin_path("byebug", "byebug") diff --git a/bin/checksums b/bin/checksums new file mode 100755 index 0000000..5498152 --- /dev/null +++ b/bin/checksums @@ -0,0 +1,67 @@ +#!/usr/bin/env ruby + +# Script from https://github.com/rubygems/guides/pull/325 +require "digest/sha2" + +# Final clause of Regex `(?=\.gem)` is a positive lookahead assertion +# See: https://learnbyexample.github.io/Ruby_Regexp/lookarounds.html#positive-lookarounds +# Used to pattern match against a gem package name, which always ends with .gem. +# The positive lookahead ensures it is present, and prevents it from being captured. +VERSION_REGEX = /((\d+\.\d+\.\d+)([-.][0-9A-Za-z-]+)*)(?=\.gem)/ + +gem_path_parts = ARGV.first&.split("/") + +if gem_path_parts&.any? + gem_name = gem_path_parts.last + gem_pkg = File.join(gem_path_parts) + puts "Looking for: #{gem_pkg.inspect}" + gems = Dir[gem_pkg] + puts "Found: #{gems.inspect}" +else + gem_pkgs = File.join("pkg", "*.gem") + puts "Looking for: #{gem_pkgs.inspect}" + gems = Dir[gem_pkgs] + raise "Unable to find gems #{gem_pkgs}" if gems.empty? + + # Sort by newest last + # [ "my_gem-2.3.9.gem", "my_gem-2.3.11.pre.alpha.4.gem", "my_gem-2.3.15.gem", ... ] + gems.sort_by! { |gem| Gem::Version.new(gem[VERSION_REGEX]) } + gem_pkg = gems.last + gem_path_parts = gem_pkg.split("/") + gem_name = gem_path_parts.last + puts "Found: #{gems.length} gems; latest is #{gem_name}" +end + +checksum512 = Digest::SHA512.new.hexdigest(File.read(gem_pkg)) +checksum512_path = "checksums/#{gem_name}.sha512" +File.write(checksum512_path, checksum512) + +checksum256 = Digest::SHA256.new.hexdigest(File.read(gem_pkg)) +checksum256_path = "checksums/#{gem_name}.sha256" +File.write(checksum256_path, checksum256) + +version = gem_name[VERSION_REGEX] + +git_cmd = <<~GIT_MSG + git add checksums/* && \ + git commit -m "🔒️ Checksums for v#{version}" +GIT_MSG + +puts <<~RESULTS + [ GEM: #{gem_name} ] + [ VERSION: #{version} ] + [ GEM PKG LOCATION: #{gem_pkg} ] + [ CHECKSUM SHA-256: #{checksum256} ] + [ CHECKSUM SHA-512: #{checksum512} ] + [ CHECKSUM SHA-256 PATH: #{checksum256_path} ] + [ CHECKSUM SHA-512 PATH: #{checksum512_path} ] + + ... Running ... + + #{git_cmd} +RESULTS + +# This will replace the current process with the git process, and exit. +# Any command placed after this will not be run: +# See: https://www.akshaykhot.com/call-shell-commands-in-ruby +exec(git_cmd) diff --git a/bin/htmldiff b/bin/htmldiff new file mode 100755 index 0000000..0aeaec8 --- /dev/null +++ b/bin/htmldiff @@ -0,0 +1,27 @@ +#!/usr/bin/env ruby +# frozen_string_literal: true + +# +# This file was generated by Bundler. +# +# The application 'htmldiff' is installed as part of a gem, and +# this file is here to facilitate running it. +# + +ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../Gemfile", __dir__) + +bundle_binstub = File.expand_path("bundle", __dir__) + +if File.file?(bundle_binstub) + if File.read(bundle_binstub, 300).include?("This file was generated by Bundler") + load(bundle_binstub) + else + abort("Your `bin/bundle` was not generated by Bundler, so this binstub cannot run. +Replace `bin/bundle` by running `bundle binstubs bundler --force`, then run this command again.") + end +end + +require "rubygems" +require "bundler/setup" + +load Gem.bin_path("diff-lcs", "htmldiff") diff --git a/bin/ldiff b/bin/ldiff new file mode 100755 index 0000000..8173ede --- /dev/null +++ b/bin/ldiff @@ -0,0 +1,27 @@ +#!/usr/bin/env ruby +# frozen_string_literal: true + +# +# This file was generated by Bundler. +# +# The application 'ldiff' is installed as part of a gem, and +# this file is here to facilitate running it. +# + +ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../Gemfile", __dir__) + +bundle_binstub = File.expand_path("bundle", __dir__) + +if File.file?(bundle_binstub) + if File.read(bundle_binstub, 300).include?("This file was generated by Bundler") + load(bundle_binstub) + else + abort("Your `bin/bundle` was not generated by Bundler, so this binstub cannot run. +Replace `bin/bundle` by running `bundle binstubs bundler --force`, then run this command again.") + end +end + +require "rubygems" +require "bundler/setup" + +load Gem.bin_path("diff-lcs", "ldiff") diff --git a/bin/racc b/bin/racc new file mode 100755 index 0000000..8190015 --- /dev/null +++ b/bin/racc @@ -0,0 +1,27 @@ +#!/usr/bin/env ruby +# frozen_string_literal: true + +# +# This file was generated by Bundler. +# +# The application 'racc' is installed as part of a gem, and +# this file is here to facilitate running it. +# + +ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../Gemfile", __dir__) + +bundle_binstub = File.expand_path("bundle", __dir__) + +if File.file?(bundle_binstub) + if File.read(bundle_binstub, 300).include?("This file was generated by Bundler") + load(bundle_binstub) + else + abort("Your `bin/bundle` was not generated by Bundler, so this binstub cannot run. +Replace `bin/bundle` by running `bundle binstubs bundler --force`, then run this command again.") + end +end + +require "rubygems" +require "bundler/setup" + +load Gem.bin_path("racc", "racc") diff --git a/bin/rackup b/bin/rackup new file mode 100755 index 0000000..6408c79 --- /dev/null +++ b/bin/rackup @@ -0,0 +1,27 @@ +#!/usr/bin/env ruby +# frozen_string_literal: true + +# +# This file was generated by Bundler. +# +# The application 'rackup' is installed as part of a gem, and +# this file is here to facilitate running it. +# + +ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../Gemfile", __dir__) + +bundle_binstub = File.expand_path("bundle", __dir__) + +if File.file?(bundle_binstub) + if File.read(bundle_binstub, 300).include?("This file was generated by Bundler") + load(bundle_binstub) + else + abort("Your `bin/bundle` was not generated by Bundler, so this binstub cannot run. +Replace `bin/bundle` by running `bundle binstubs bundler --force`, then run this command again.") + end +end + +require "rubygems" +require "bundler/setup" + +load Gem.bin_path("rackup", "rackup") diff --git a/bin/rake b/bin/rake new file mode 100755 index 0000000..4eb7d7b --- /dev/null +++ b/bin/rake @@ -0,0 +1,27 @@ +#!/usr/bin/env ruby +# frozen_string_literal: true + +# +# This file was generated by Bundler. +# +# The application 'rake' is installed as part of a gem, and +# this file is here to facilitate running it. +# + +ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../Gemfile", __dir__) + +bundle_binstub = File.expand_path("bundle", __dir__) + +if File.file?(bundle_binstub) + if File.read(bundle_binstub, 300).include?("This file was generated by Bundler") + load(bundle_binstub) + else + abort("Your `bin/bundle` was not generated by Bundler, so this binstub cannot run. +Replace `bin/bundle` by running `bundle binstubs bundler --force`, then run this command again.") + end +end + +require "rubygems" +require "bundler/setup" + +load Gem.bin_path("rake", "rake") diff --git a/bin/rots b/bin/rots new file mode 100755 index 0000000..2ad9295 --- /dev/null +++ b/bin/rots @@ -0,0 +1,27 @@ +#!/usr/bin/env ruby +# frozen_string_literal: true + +# +# This file was generated by Bundler. +# +# The application 'rots' is installed as part of a gem, and +# this file is here to facilitate running it. +# + +ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../Gemfile", __dir__) + +bundle_binstub = File.expand_path("bundle", __dir__) + +if File.file?(bundle_binstub) + if File.read(bundle_binstub, 300).include?("This file was generated by Bundler") + load(bundle_binstub) + else + abort("Your `bin/bundle` was not generated by Bundler, so this binstub cannot run. +Replace `bin/bundle` by running `bundle binstubs bundler --force`, then run this command again.") + end +end + +require "rubygems" +require "bundler/setup" + +load Gem.bin_path("rots", "rots") diff --git a/bin/rubocop b/bin/rubocop new file mode 100755 index 0000000..369a05b --- /dev/null +++ b/bin/rubocop @@ -0,0 +1,27 @@ +#!/usr/bin/env ruby +# frozen_string_literal: true + +# +# This file was generated by Bundler. +# +# The application 'rubocop' is installed as part of a gem, and +# this file is here to facilitate running it. +# + +ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../Gemfile", __dir__) + +bundle_binstub = File.expand_path("bundle", __dir__) + +if File.file?(bundle_binstub) + if File.read(bundle_binstub, 300).include?("This file was generated by Bundler") + load(bundle_binstub) + else + abort("Your `bin/bundle` was not generated by Bundler, so this binstub cannot run. +Replace `bin/bundle` by running `bundle binstubs bundler --force`, then run this command again.") + end +end + +require "rubygems" +require "bundler/setup" + +load Gem.bin_path("rubocop", "rubocop") diff --git a/bin/rubocop-gradual b/bin/rubocop-gradual new file mode 100755 index 0000000..0752005 --- /dev/null +++ b/bin/rubocop-gradual @@ -0,0 +1,27 @@ +#!/usr/bin/env ruby +# frozen_string_literal: true + +# +# This file was generated by Bundler. +# +# The application 'rubocop-gradual' is installed as part of a gem, and +# this file is here to facilitate running it. +# + +ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../Gemfile", __dir__) + +bundle_binstub = File.expand_path("bundle", __dir__) + +if File.file?(bundle_binstub) + if File.read(bundle_binstub, 300).include?("This file was generated by Bundler") + load(bundle_binstub) + else + abort("Your `bin/bundle` was not generated by Bundler, so this binstub cannot run. +Replace `bin/bundle` by running `bundle binstubs bundler --force`, then run this command again.") + end +end + +require "rubygems" +require "bundler/setup" + +load Gem.bin_path("rubocop-gradual", "rubocop-gradual") diff --git a/bin/ruby-parse b/bin/ruby-parse new file mode 100755 index 0000000..d8ebc68 --- /dev/null +++ b/bin/ruby-parse @@ -0,0 +1,27 @@ +#!/usr/bin/env ruby +# frozen_string_literal: true + +# +# This file was generated by Bundler. +# +# The application 'ruby-parse' is installed as part of a gem, and +# this file is here to facilitate running it. +# + +ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../Gemfile", __dir__) + +bundle_binstub = File.expand_path("bundle", __dir__) + +if File.file?(bundle_binstub) + if File.read(bundle_binstub, 300).include?("This file was generated by Bundler") + load(bundle_binstub) + else + abort("Your `bin/bundle` was not generated by Bundler, so this binstub cannot run. +Replace `bin/bundle` by running `bundle binstubs bundler --force`, then run this command again.") + end +end + +require "rubygems" +require "bundler/setup" + +load Gem.bin_path("parser", "ruby-parse") diff --git a/bin/ruby-rewrite b/bin/ruby-rewrite new file mode 100755 index 0000000..b4574ab --- /dev/null +++ b/bin/ruby-rewrite @@ -0,0 +1,27 @@ +#!/usr/bin/env ruby +# frozen_string_literal: true + +# +# This file was generated by Bundler. +# +# The application 'ruby-rewrite' is installed as part of a gem, and +# this file is here to facilitate running it. +# + +ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../Gemfile", __dir__) + +bundle_binstub = File.expand_path("bundle", __dir__) + +if File.file?(bundle_binstub) + if File.read(bundle_binstub, 300).include?("This file was generated by Bundler") + load(bundle_binstub) + else + abort("Your `bin/bundle` was not generated by Bundler, so this binstub cannot run. +Replace `bin/bundle` by running `bundle binstubs bundler --force`, then run this command again.") + end +end + +require "rubygems" +require "bundler/setup" + +load Gem.bin_path("parser", "ruby-rewrite") diff --git a/bin/setup b/bin/setup new file mode 100755 index 0000000..dce67d8 --- /dev/null +++ b/bin/setup @@ -0,0 +1,8 @@ +#!/usr/bin/env bash +set -euo pipefail +IFS=$'\n\t' +set -vx + +bundle install + +# Do any other automated setup that you need to do here diff --git a/bin/standardrb b/bin/standardrb new file mode 100755 index 0000000..b329561 --- /dev/null +++ b/bin/standardrb @@ -0,0 +1,27 @@ +#!/usr/bin/env ruby +# frozen_string_literal: true + +# +# This file was generated by Bundler. +# +# The application 'standardrb' is installed as part of a gem, and +# this file is here to facilitate running it. +# + +ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../Gemfile", __dir__) + +bundle_binstub = File.expand_path("bundle", __dir__) + +if File.file?(bundle_binstub) + if File.read(bundle_binstub, 300).include?("This file was generated by Bundler") + load(bundle_binstub) + else + abort("Your `bin/bundle` was not generated by Bundler, so this binstub cannot run. +Replace `bin/bundle` by running `bundle binstubs bundler --force`, then run this command again.") + end +end + +require "rubygems" +require "bundler/setup" + +load Gem.bin_path("standard", "standardrb") diff --git a/bin/yard b/bin/yard new file mode 100755 index 0000000..ea9daf5 --- /dev/null +++ b/bin/yard @@ -0,0 +1,27 @@ +#!/usr/bin/env ruby +# frozen_string_literal: true + +# +# This file was generated by Bundler. +# +# The application 'yard' is installed as part of a gem, and +# this file is here to facilitate running it. +# + +ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../Gemfile", __dir__) + +bundle_binstub = File.expand_path("bundle", __dir__) + +if File.file?(bundle_binstub) + if File.read(bundle_binstub, 300).include?("This file was generated by Bundler") + load(bundle_binstub) + else + abort("Your `bin/bundle` was not generated by Bundler, so this binstub cannot run. +Replace `bin/bundle` by running `bundle binstubs bundler --force`, then run this command again.") + end +end + +require "rubygems" +require "bundler/setup" + +load Gem.bin_path("yard", "yard") diff --git a/bin/yard-junk b/bin/yard-junk new file mode 100755 index 0000000..be420a5 --- /dev/null +++ b/bin/yard-junk @@ -0,0 +1,27 @@ +#!/usr/bin/env ruby +# frozen_string_literal: true + +# +# This file was generated by Bundler. +# +# The application 'yard-junk' is installed as part of a gem, and +# this file is here to facilitate running it. +# + +ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../Gemfile", __dir__) + +bundle_binstub = File.expand_path("bundle", __dir__) + +if File.file?(bundle_binstub) + if File.read(bundle_binstub, 300).include?("This file was generated by Bundler") + load(bundle_binstub) + else + abort("Your `bin/bundle` was not generated by Bundler, so this binstub cannot run. +Replace `bin/bundle` by running `bundle binstubs bundler --force`, then run this command again.") + end +end + +require "rubygems" +require "bundler/setup" + +load Gem.bin_path("yard-junk", "yard-junk") diff --git a/bin/yardoc b/bin/yardoc new file mode 100755 index 0000000..e1324dc --- /dev/null +++ b/bin/yardoc @@ -0,0 +1,27 @@ +#!/usr/bin/env ruby +# frozen_string_literal: true + +# +# This file was generated by Bundler. +# +# The application 'yardoc' is installed as part of a gem, and +# this file is here to facilitate running it. +# + +ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../Gemfile", __dir__) + +bundle_binstub = File.expand_path("bundle", __dir__) + +if File.file?(bundle_binstub) + if File.read(bundle_binstub, 300).include?("This file was generated by Bundler") + load(bundle_binstub) + else + abort("Your `bin/bundle` was not generated by Bundler, so this binstub cannot run. +Replace `bin/bundle` by running `bundle binstubs bundler --force`, then run this command again.") + end +end + +require "rubygems" +require "bundler/setup" + +load Gem.bin_path("yard", "yardoc") diff --git a/bin/yri b/bin/yri new file mode 100755 index 0000000..f968fde --- /dev/null +++ b/bin/yri @@ -0,0 +1,27 @@ +#!/usr/bin/env ruby +# frozen_string_literal: true + +# +# This file was generated by Bundler. +# +# The application 'yri' is installed as part of a gem, and +# this file is here to facilitate running it. +# + +ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../Gemfile", __dir__) + +bundle_binstub = File.expand_path("bundle", __dir__) + +if File.file?(bundle_binstub) + if File.read(bundle_binstub, 300).include?("This file was generated by Bundler") + load(bundle_binstub) + else + abort("Your `bin/bundle` was not generated by Bundler, so this binstub cannot run. +Replace `bin/bundle` by running `bundle binstubs bundler --force`, then run this command again.") + end +end + +require "rubygems" +require "bundler/setup" + +load Gem.bin_path("yard", "yri") diff --git a/certs/pboling.pem b/certs/pboling.pem new file mode 100644 index 0000000..b33ee80 --- /dev/null +++ b/certs/pboling.pem @@ -0,0 +1,27 @@ +-----BEGIN CERTIFICATE----- +MIIEgDCCAuigAwIBAgIBATANBgkqhkiG9w0BAQsFADBDMRUwEwYDVQQDDAxwZXRl +ci5ib2xpbmcxFTATBgoJkiaJk/IsZAEZFgVnbWFpbDETMBEGCgmSJomT8ixkARkW +A2NvbTAeFw0yNDA5MjAwODU4NDJaFw0yNTA5MjAwODU4NDJaMEMxFTATBgNVBAMM +DHBldGVyLmJvbGluZzEVMBMGCgmSJomT8ixkARkWBWdtYWlsMRMwEQYKCZImiZPy +LGQBGRYDY29tMIIBojANBgkqhkiG9w0BAQEFAAOCAY8AMIIBigKCAYEAjrxsKObI +rFQjBpzvVfqnT6JlF8/pkpgEEjFh7ex3zIerfuHzZvSrx+sRDGxQ8koWWG0Wjx8s +wkBZ5dIqvl0g3sWP5asa28u/09opxkQTC1Ao77iYxcBcwoCe/Dpf1m4Q/m6oH0kL +2AZVNJQL3UkqAcLS0tsj/s/jAKnVlsaZZE5gQiIIi8HtkvSsajtx+Cq2AxDvcWvV +/CliD+pmzYkTjvjwGm8yeyFGGGgrisJMryiZdZlkTwrQSjCzudIKbLeuG8Se4JTD +TAcT+rPubr27v1jwmtIjtiot3rf4nof7LHLb122a/0VR7cC7xPLnXw0Cq1BShvoq +/GKRdSwMNinTOGkFTK1gKnjN+3iD4zyXU3XO3CXoTr+Ju8fXPN1x4tpOMgbv8dme +WbcQMOH9ZjmA5w0bSVRL1c3NhRRpUzrKTNXBEvqOyWjUnintxWKj+cRXx+z+dUgI +dL3kj68fcsiTgl75In3C485pnCMmq1eLuVoiy3jkLNOn2lHeLt9ZK63LAgMBAAGj +fzB9MAkGA1UdEwQCMAAwCwYDVR0PBAQDAgSwMB0GA1UdDgQWBBRhfc+2UaVYd74p +yJ1JclGiUYN8+jAhBgNVHREEGjAYgRZwZXRlci5ib2xpbmdAZ21haWwuY29tMCEG +A1UdEgQaMBiBFnBldGVyLmJvbGluZ0BnbWFpbC5jb20wDQYJKoZIhvcNAQELBQAD +ggGBAA4fLU2+mQ++jBhVM2IeyvQdw1nm+0thkH4Ldv8ZOBm5ZxCPGIMoYliDDzg4 +4JDFxZR1wR4sdrz/K5tWtEkN23SKzopwbNb1NIQRSLQ7nOoc+4bkuz9xwKinmIvF +D+5qsl2S27WLKFreMDtGoh0CREIMBUxU4rGTh0gtzmweGR+fnOShg4Jo0kxrjU5h +uYk/uVE+bn/jOEGs43GvKXZLyshpBrZjQ+ArbvxDht5t35zbSxerbUxUPZUbXUCW +tTyh38a9UYjAAHvnh6Y4Fi9wd4/pGNsektrzB3z/zlVj4YF2TMLX9XfNJWEGRGpO +sSkLYdtEX1WQAmuZtActVW2cL3HdQaRbiv7VbfpA0eSk0ZdZHvBCl516ZZu10uX6 +82W1mg6fuezdpeBOiXwrEbZSt/oGiF4V511F6nd55p0okwHc/6nS10F/3aKJ4gwC +I5o+DRfXQHqKucx1ldFHvI2rE/kSCWqGTHN2eyu1sqCPeOoIMxrltJhaejKPkxqj +zaF9Og== +-----END CERTIFICATE----- diff --git a/checksums/rack-openid2-2.0.0.gem.sha256 b/checksums/rack-openid2-2.0.0.gem.sha256 new file mode 100644 index 0000000..2756171 --- /dev/null +++ b/checksums/rack-openid2-2.0.0.gem.sha256 @@ -0,0 +1 @@ +0ec1f8381e87d63c621db6fa080d30908fd64ce9a72da75fc4e72c7a16e06092 \ No newline at end of file diff --git a/checksums/rack-openid2-2.0.0.gem.sha512 b/checksums/rack-openid2-2.0.0.gem.sha512 new file mode 100644 index 0000000..2617b61 --- /dev/null +++ b/checksums/rack-openid2-2.0.0.gem.sha512 @@ -0,0 +1 @@ +36ca13aed126604789b33e5072fda02a2f6a3cbf651f31802e391e0a37d95db1f454478bceb12e6aa5af7d62b63f4d8e397908963cba271c556c747a7a501a49 \ No newline at end of file diff --git a/checksums/rack-openid2-2.0.1.gem.sha256 b/checksums/rack-openid2-2.0.1.gem.sha256 new file mode 100644 index 0000000..f0af4db --- /dev/null +++ b/checksums/rack-openid2-2.0.1.gem.sha256 @@ -0,0 +1 @@ +1d34f3cc39bb662298176b03a77aab07d1152d1f699d039b2455915f4d87aef3 \ No newline at end of file diff --git a/checksums/rack-openid2-2.0.1.gem.sha512 b/checksums/rack-openid2-2.0.1.gem.sha512 new file mode 100644 index 0000000..40cabb1 --- /dev/null +++ b/checksums/rack-openid2-2.0.1.gem.sha512 @@ -0,0 +1 @@ +96004d0d0b77d64070a3bee66cc23ca9135b16eb24c1561ad530f985c6ab715a1010ca6ecd52821d280abb14003a93d60629579cad14b404116ae7419940b713 \ No newline at end of file diff --git a/checksums/rack-openid2-2.0.2.gem.sha256 b/checksums/rack-openid2-2.0.2.gem.sha256 new file mode 100644 index 0000000..a600fe7 --- /dev/null +++ b/checksums/rack-openid2-2.0.2.gem.sha256 @@ -0,0 +1 @@ +2653d209fa12547a129a514e97d3ac29677f3f1198982d9a07b03c6c186d674a \ No newline at end of file diff --git a/checksums/rack-openid2-2.0.2.gem.sha512 b/checksums/rack-openid2-2.0.2.gem.sha512 new file mode 100644 index 0000000..ca7db93 --- /dev/null +++ b/checksums/rack-openid2-2.0.2.gem.sha512 @@ -0,0 +1 @@ +561a500d7b67326bb834d4f8b3a1afc35053d7a546b1611714a6df0710d93fafdf0dc3c40662924b084eb78499a1b75e2eba2a5b67920772824b3587c4a87eb7 \ No newline at end of file diff --git a/checksums/rack-openid2-2.0.3.gem.sha256 b/checksums/rack-openid2-2.0.3.gem.sha256 new file mode 100644 index 0000000..4ffa08e --- /dev/null +++ b/checksums/rack-openid2-2.0.3.gem.sha256 @@ -0,0 +1 @@ +38e39be5c98ff90824fe7dd1f4eb7ce1232c11ce5048b982e9ff9012d5ec90a1 \ No newline at end of file diff --git a/checksums/rack-openid2-2.0.3.gem.sha512 b/checksums/rack-openid2-2.0.3.gem.sha512 new file mode 100644 index 0000000..b700622 --- /dev/null +++ b/checksums/rack-openid2-2.0.3.gem.sha512 @@ -0,0 +1 @@ +fd93bc46b3ad616911148040ad3a7697b39cf925f82e001c3cfd9ec1d3a79bb3d91df88b566a87f297ccb73ff99b7ff9d9cd2fbeffecf19a3af492c8aa24c72e \ No newline at end of file diff --git a/gem-public_cert.pem b/gem-public_cert.pem deleted file mode 100644 index 2d03746..0000000 --- a/gem-public_cert.pem +++ /dev/null @@ -1,20 +0,0 @@ ------BEGIN CERTIFICATE----- -MIIDMjCCAhqgAwIBAgIBADANBgkqhkiG9w0BAQUFADA/MRAwDgYDVQQDDAdtaWNo -YWVsMRcwFQYKCZImiZPyLGQBGRYHZ3Jvc3NlcjESMBAGCgmSJomT8ixkARkWAml0 -MB4XDTEzMDIwMzE4MTMxMVoXDTE0MDIwMzE4MTMxMVowPzEQMA4GA1UEAwwHbWlj -aGFlbDEXMBUGCgmSJomT8ixkARkWB2dyb3NzZXIxEjAQBgoJkiaJk/IsZAEZFgJp -dDCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAMorXo/hgbUq97+kII9H -MsQcLdC/7wQ1ZP2OshVHPkeP0qH8MBHGg6eYisOX2ubNagF9YTCZWnhrdKrwpLOO -cPLaZbjUjljJ3cQR3B8Yn1veV5IhG86QseTBjymzJWsLpqJ1UZGpfB9tXcsFtuxO -6vHvcIHdzvc/OUkICttLbH+1qb6rsHUceqh+JrH4GrsJ5H4hAfIdyS2XMK7YRKbh -h+IBu6dFWJJByzFsYmV1PDXln3UBmgAt65cmCu4qPfThioCGDzbSJrGDGLmw/pFX -FPpVCm1zgYSb1v6Qnf3cgXa2f2wYGm17+zAVyIDpwryFru9yF/jJxE38z/DRsd9R -/88CAwEAAaM5MDcwCQYDVR0TBAIwADAdBgNVHQ4EFgQUsiNnXHtKeMYYcr4yJVmQ -WONL+IwwCwYDVR0PBAQDAgSwMA0GCSqGSIb3DQEBBQUAA4IBAQAlyN7kKo/NQCQ0 -AOzZLZ3WAePvStkCFIJ53tsv5Kyo4pMAllv+BgPzzBt7qi605mFSL6zBd9uLou+W -Co3s48p1dy7CjjAfVQdmVNHF3MwXtfC2OEyvSQPi4xKR8iba8wa3xp9LVo1PuLpw -/6DsrChWw74HfsJN6qJOK684hJeT8lBYAUfiC3wD0owoPSg+XtyAAddisR+KV5Y1 -NmVHuLtQcNTZy+gRht3ahJRMuC6QyLmkTsf+6MaenwAMkAgHdswGsJztOnNnBa3F -y0kCSWmK6D+x/SbfS6r7Ke07MRqziJdB9GuE1+0cIRuFh8EQ+LN6HXCKM5pon/GU -ycwMXfl0 ------END CERTIFICATE----- diff --git a/gemfiles/coverage.gemfile b/gemfiles/coverage.gemfile new file mode 100644 index 0000000..fa8c11a --- /dev/null +++ b/gemfiles/coverage.gemfile @@ -0,0 +1,12 @@ +git_source(:github) { |repo_name| "https://github.com/#{repo_name}" } + +source "https://rubygems.org" + +# Root Gemfile is only for local development only. It is not loaded on CI. +# On CI we only need the gemspecs' dependencies (including development dependencies). +# Exceptions, if any, will be found in gemfiles/*.gemfile +gem "kettle-soup-cover", "~> 1.0", ">= 1.0.2" + +gem "rots", github: "oauth-xx/rots" + +gemspec path: "../" diff --git a/gemfiles/style.gemfile b/gemfiles/style.gemfile new file mode 100644 index 0000000..827e16b --- /dev/null +++ b/gemfiles/style.gemfile @@ -0,0 +1,10 @@ +git_source(:github) { |repo_name| "https://github.com/#{repo_name}" } + +source "https://rubygems.org" + +# Root Gemfile is only for local development only. It is not loaded on CI. +# On CI we only need the gemspecs' dependencies (including development dependencies). +# Exceptions, if any, will be found in gemfiles/*.gemfile +gem "rubocop-packaging", "~> 0.5", ">= 0.5.2" + +gemspec path: "../" diff --git a/gemfiles/vanilla.gemfile b/gemfiles/vanilla.gemfile new file mode 100644 index 0000000..833c07a --- /dev/null +++ b/gemfiles/vanilla.gemfile @@ -0,0 +1,11 @@ +git_source(:github) { |repo_name| "https://github.com/#{repo_name}" } + +source "https://rubygems.org" + +# Root Gemfile is only for local development only. It is not loaded on CI. +# On CI we only need the gemspecs' dependencies (including development dependencies). +# Exceptions, if any, will be found in gemfiles/*.gemfile + +gem "rots", github: "oauth-xx/rots" + +gemspec path: "../" diff --git a/lib/rack-openid2.rb b/lib/rack-openid2.rb new file mode 100644 index 0000000..f7bc2ea --- /dev/null +++ b/lib/rack-openid2.rb @@ -0,0 +1,2 @@ +# For loading by bundler +require_relative "rack/openid" diff --git a/lib/rack/openid.rb b/lib/rack/openid.rb index 3784498..02ff4b7 100644 --- a/lib/rack/openid.rb +++ b/lib/rack/openid.rb @@ -1,12 +1,17 @@ -require 'rack/request' -require 'rack/utils' - -require 'openid' -require 'openid/consumer' -require 'openid/extensions/sreg' -require 'openid/extensions/ax' -require 'openid/extensions/oauth' -require 'openid/extensions/pape' +# External Libraries +require "version_gem" +require "rack/request" +require "rack/utils" +# Require ruby-openid2 and some of its extensions +require "openid" +require "openid/consumer" +require "openid/extensions/sreg" +require "openid/extensions/ax" +require "openid/extensions/oauth" +require "openid/extensions/pape" + +# This gem +require_relative "openid/version" module Rack # A Rack middleware that provides a more HTTPish API around the @@ -19,38 +24,40 @@ module Rack # On competition, the OpenID response is automatically verified and # assigned to env["rack.openid.response"]. class OpenID - # Helper method for building the "WWW-Authenticate" header value. - # - # Rack::OpenID.build_header(:identifier => "http://josh.openid.com/") - # #=> OpenID identifier="http://josh.openid.com/" - def self.build_header(params = {}) - 'OpenID ' + params.map { |key, value| - if value.is_a?(Array) - "#{key}=\"#{value.join(',')}\"" - else - "#{key}=\"#{value}\"" - end - }.join(', ') - end + class << self + # Helper method for building the "WWW-Authenticate" header value. + # + # Rack::OpenID.build_header(:identifier => "http://josh.openid.com/") + # #=> OpenID identifier="http://josh.openid.com/" + def build_header(params = {}) + "OpenID " + params.map { |key, value| + if value.is_a?(Array) + "#{key}=\"#{value.join(",")}\"" + else + "#{key}=\"#{value}\"" + end + }.join(", ") + end - # Helper method for parsing "WWW-Authenticate" header values into - # a hash. - # - # Rack::OpenID.parse_header("OpenID identifier='http://josh.openid.com/'") - # #=> {:identifier => "http://josh.openid.com/"} - def self.parse_header(str) - params = {} - if str =~ AUTHENTICATE_REGEXP - str = str.gsub(/#{AUTHENTICATE_REGEXP}\s+/, '') - str.split(', ').each { |pair| - key, *value = pair.split('=') - value = value.join('=') - value.gsub!(/^\"/, '').gsub!(/\"$/, "") - value = value.split(',') - params[key] = value.length > 1 ? value : value.first - } + # Helper method for parsing "WWW-Authenticate" header values into + # a hash. + # + # Rack::OpenID.parse_header("OpenID identifier='http://josh.openid.com/'") + # #=> {:identifier => "http://josh.openid.com/"} + def parse_header(str) + params = {} + if AUTHENTICATE_REGEXP.match?(str) + str = str.gsub(/#{AUTHENTICATE_REGEXP}\s+/o, "") + str.split(", ").each { |pair| + key, *value = pair.split("=") + value = value.join("=") + value.gsub!(/^\"/, "").gsub!(/\"$/, "") + value = value.split(",") + params[key] = (value.length > 1) ? value : value.first + } + end + params end - params end class TimeoutResponse @@ -108,8 +115,8 @@ def call(env) private def sanitize_params!(params) - ['openid.sig', 'openid.response_nonce'].each do |param| - (params[param] || '').gsub!(' ', '+') + ["openid.sig", "openid.response_nonce"].each do |param| + (params[param] || "").tr!(" ", "+") end end @@ -119,11 +126,11 @@ def begin_authentication(env, qs) session = env["rack.session"] unless session - raise RuntimeError, "Rack::OpenID requires a session" + raise "Rack::OpenID requires a session" end - consumer = ::OpenID::Consumer.new(session, @store) - identifier = params['identifier'] || params['identity'] + consumer = ::OpenID::Consumer.new(session, @store) + identifier = params["identifier"] || params["identity"] begin oidreq = consumer.begin(identifier) @@ -133,10 +140,10 @@ def begin_authentication(env, qs) add_pape_fields(oidreq, params) url = open_id_redirect_url(req, oidreq, params) - return redirect_to(url) - rescue ::OpenID::OpenIDError, Timeout::Error => e + redirect_to(url) + rescue ::OpenID::OpenIDError, ::Timeout::Error env[RESPONSE] = MissingResponse.new - return @app.call(env) + @app.call(env) end end @@ -145,7 +152,7 @@ def complete_authentication(env) session = env["rack.session"] unless session - raise RuntimeError, "Rack::OpenID requires a session" + raise "Rack::OpenID requires a session" end oidresp = timeout_protection_from_identity_server { @@ -208,7 +215,6 @@ def realm(req, domain = nil) else scheme_with_host_and_port(req) end - end def request_url(req) @@ -225,11 +231,11 @@ def redirect_to(url) def open_id_redirect_url(req, oidreq, options) trust_root = options["trust_root"] - return_to = options["return_to"] - method = options["method"] - immediate = options["immediate"] == "true" + return_to = options["return_to"] + method = options["method"] + immediate = options["immediate"] == "true" - realm = realm(req, options["realm_domain"]) + realm = realm(req, options["realm_domain"]) request_url = request_url(req) if return_to @@ -240,20 +246,20 @@ def open_id_redirect_url(req, oidreq, options) end method = method.to_s.downcase - oidreq.return_to_args['_method'] = method unless method == "get" + oidreq.return_to_args["_method"] = method unless method == "get" oidreq.redirect_url(trust_root || realm, return_to || request_url, immediate) end def add_simple_registration_fields(oidreq, fields) sregreq = ::OpenID::SReg::Request.new - required = Array(fields['required']).reject(&URL_FIELD_SELECTOR) + required = Array(fields["required"]).reject(&URL_FIELD_SELECTOR) sregreq.request_fields(required, true) if required.any? - optional = Array(fields['optional']).reject(&URL_FIELD_SELECTOR) + optional = Array(fields["optional"]).reject(&URL_FIELD_SELECTOR) sregreq.request_fields(optional, false) if optional.any? - policy_url = fields['policy_url'] + policy_url = fields["policy_url"] sregreq.policy_url = policy_url if policy_url oidreq.add_extension(sregreq) @@ -262,8 +268,8 @@ def add_simple_registration_fields(oidreq, fields) def add_attribute_exchange_fields(oidreq, fields) axreq = ::OpenID::AX::FetchRequest.new - required = Array(fields['required']).select(&URL_FIELD_SELECTOR) - optional = Array(fields['optional']).select(&URL_FIELD_SELECTOR) + required = Array(fields["required"]).select(&URL_FIELD_SELECTOR) + optional = Array(fields["optional"]).select(&URL_FIELD_SELECTOR) if required.any? || optional.any? required.each do |field| @@ -279,15 +285,15 @@ def add_attribute_exchange_fields(oidreq, fields) end def add_oauth_fields(oidreq, fields) - if (consumer = fields['oauth[consumer]']) && (scope = fields['oauth[scope]']) - oauthreq = ::OpenID::OAuth::Request.new(consumer, Array(scope).join(' ')) + if (consumer = fields["oauth[consumer]"]) && (scope = fields["oauth[scope]"]) + oauthreq = ::OpenID::OAuth::Request.new(consumer, Array(scope).join(" ")) oidreq.add_extension(oauthreq) end end def add_pape_fields(oidreq, fields) - preferred_auth_policies = fields['pape[preferred_auth_policies]'] - max_auth_age = fields['pape[max_auth_age]'] + preferred_auth_policies = fields["pape[preferred_auth_policies]"] + max_auth_age = fields["pape[max_auth_age]"] if preferred_auth_policies || max_auth_age preferred_auth_policies = preferred_auth_policies.split if preferred_auth_policies.is_a?(String) pape_request = ::OpenID::PAPE::Request.new(preferred_auth_policies || [], max_auth_age) @@ -296,14 +302,18 @@ def add_pape_fields(oidreq, fields) end def default_store - require 'openid/store/memory' + require "openid/store/memory" ::OpenID::Store::Memory.new end def timeout_protection_from_identity_server yield - rescue Timeout::Error + rescue ::Timeout::Error TimeoutResponse.new end end end + +Rack::OpenID::Version.class_eval do + extend VersionGem::Basic +end diff --git a/lib/rack/openid/simple_auth.rb b/lib/rack/openid/simple_auth.rb index 11e2098..9848015 100644 --- a/lib/rack/openid/simple_auth.rb +++ b/lib/rack/openid/simple_auth.rb @@ -1,5 +1,5 @@ -require 'rack/openid' -require 'rack/request' +require "rack/openid" +require "rack/request" module Rack class OpenID @@ -11,14 +11,16 @@ class OpenID # SimpleAuth will automatically insert the required Rack::OpenID # middleware, so use Rack::OpenID is unnecessary. class SimpleAuth - def self.new(*args) - Rack::OpenID.new(super) + class << self + def new(*args) + Rack::OpenID.new(super) + end end attr_reader :app, :identifier def initialize(app, identifier) - @app = app + @app = app @identifier = identifier end @@ -27,7 +29,7 @@ def call(env) app.call(env) elsif successful_response?(env) authenticate_session(env) - redirect_to requested_url(env) + redirect_to(requested_url(env)) else authentication_request end @@ -36,23 +38,23 @@ def call(env) private def session(env) - env['rack.session'] || raise_session_error + env["rack.session"] || raise_session_error end def raise_session_error - raise RuntimeError, 'Rack::OpenID::SimpleAuth requires a session' + raise "Rack::OpenID::SimpleAuth requires a session" end def session_authenticated?(env) - session(env)['authenticated'] == true + session(env)["authenticated"] == true end def authenticate_session(env) - session(env)['authenticated'] = true + session(env)["authenticated"] = true end def successful_response?(env) - if resp = env[OpenID::RESPONSE] + if (resp = env[Rack::OpenID::RESPONSE]) resp.status == :success && resp.display_identifier == identifier end end @@ -63,15 +65,15 @@ def requested_url(env) end def redirect_to(url) - [303, {'Content-Type' => 'text/html', 'Location' => url}, []] + [303, {"Content-Type" => "text/html", "Location" => url}, []] end def authentication_request - [401, { OpenID::AUTHENTICATE_HEADER => www_authenticate_header }, []] + [401, {OpenID::AUTHENTICATE_HEADER => www_authenticate_header}, []] end def www_authenticate_header - OpenID.build_header(:identifier => identifier) + OpenID.build_header(identifier: identifier) end end end diff --git a/lib/rack/openid/version.rb b/lib/rack/openid/version.rb index d08790b..f50ef65 100644 --- a/lib/rack/openid/version.rb +++ b/lib/rack/openid/version.rb @@ -1,5 +1,7 @@ module Rack class OpenID - VERSION = "1.4.2" + module Version + VERSION = "2.0.3" + end end end diff --git a/rack-openid.gemspec b/rack-openid.gemspec deleted file mode 100644 index da87144..0000000 --- a/rack-openid.gemspec +++ /dev/null @@ -1,20 +0,0 @@ -$LOAD_PATH.unshift File.expand_path("../lib", __FILE__) -name = "rack-openid" -require "rack/openid/version" - -Gem::Specification.new name, Rack::OpenID::VERSION do |s| - s.summary = "Provides a more HTTPish API around the ruby-openid library" - s.authors = ["Michael Grosser", "Joshua Peek"] - s.email = "michael@grosser.it" - s.homepage = "https://github.com/grosser/#{name}" - s.files = `git ls-files lib`.split("\n") - s.license = "MIT" - cert = File.expand_path("~/.ssh/gem-private-key-grosser.pem") - if File.exist?(cert) - s.signing_key = cert - s.cert_chain = ["gem-public_cert.pem"] - end - - s.add_runtime_dependency "rack", ">=1.1.0" - s.add_runtime_dependency "ruby-openid", ">= 2.1.8" -end diff --git a/rack-openid2.gemspec b/rack-openid2.gemspec new file mode 100644 index 0000000..c5749ee --- /dev/null +++ b/rack-openid2.gemspec @@ -0,0 +1,74 @@ +# Get the GEMFILE_VERSION without *require* "my_gem/version", for code coverage accuracy +# See: https://github.com/simplecov-ruby/simplecov/issues/557#issuecomment-825171399 +load "lib/rack/openid/version.rb" +gem_version = Rack::OpenID::Version::VERSION +Rack::OpenID::Version.send(:remove_const, :VERSION) + +Gem::Specification.new do |spec| + spec.name = "rack-openid2" + spec.version = gem_version + spec.summary = "Provides a more HTTPish API around the ruby-openid2 library" + spec.authors = ["Peter Boling", "Michael Grosser", "Joshua Peek"] + spec.email = "peter.boling@gmail.com" + spec.homepage = "https://github.com/oauth-xx/#{spec.name}" + + # See CONTRIBUTING.md + spec.cert_chain = [ENV.fetch("GEM_CERT_PATH", "certs/#{ENV.fetch("GEM_CERT_USER", ENV["USER"])}.pem")] + spec.signing_key = File.expand_path("~/.ssh/gem-private_key.pem") if $PROGRAM_NAME.end_with?("gem") + + # Specify which files should be added to the gem when it is released. + spec.files = Dir[ + # Splats (alphabetical) + "lib/**/*.rb", + # Files (alphabetical) + "CHANGELOG.md", + "CODE_OF_CONDUCT.md", + "CONTRIBUTING.md", + "LICENSE.txt", + "README.md", + "SECURITY.md" + ] + spec.executables = spec.files.grep(%r{^bin/}).map { |f| File.basename(f) } + spec.licenses = ["MIT"] + spec.require_paths = ["lib"] + spec.required_ruby_version = ">= 2.7.0" + + spec.metadata["homepage_uri"] = spec.homepage + spec.metadata["source_code_uri"] = "#{spec.homepage}/tree/v#{spec.version}" + spec.metadata["changelog_uri"] = "#{spec.homepage}/blob/v#{spec.version}/CHANGELOG.md" + spec.metadata["bug_tracker_uri"] = "#{spec.homepage}/issues" + spec.metadata["documentation_uri"] = "https://www.rubydoc.info/gems/#{spec.name}/#{spec.version}" + spec.metadata["wiki_uri"] = "#{spec.homepage}/wiki" + spec.metadata["rubygems_mfa_required"] = "true" + + spec.add_dependency("rack", ">= 2.2") + 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", "< 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") + spec.add_development_dependency("rubocop-packaging", "~> 0.5", ">= 0.5.2") + spec.add_development_dependency("standard", ">= 1.35.1") + + # Documentation + spec.add_development_dependency("yard", "~> 0.9", ">= 0.9.34") + spec.add_development_dependency("yard-junk", "~> 0.0") +end diff --git a/test/helper.rb b/test/helper.rb deleted file mode 100644 index 161a04f..0000000 --- a/test/helper.rb +++ /dev/null @@ -1,12 +0,0 @@ -require 'bundler/setup' -require 'minitest/autorun' -require 'minitest/rg' -require 'net/http' - -require 'rack' -require 'rack/openid' -require 'rack/openid/simple_auth' - -log = Logger.new(STDOUT) -log.level = Logger::WARN -OpenID::Util.logger = log diff --git a/test/support/config.rb b/test/support/config.rb new file mode 100644 index 0000000..a0e5282 --- /dev/null +++ b/test/support/config.rb @@ -0,0 +1,40 @@ +# External dependencies +require "require_bench" if ENV.fetch("REQUIRE_BENCH", "false").casecmp?("true") +require "byebug" if ENV.fetch("DEBUG", "false").casecmp?("true") +require "net/http" +require "rack" +require "rack/session" + +# External testing libraries +require "minitest/rg" + +# Test support +require_relative "logging" + +## Last thing before loading this gem is to setup code coverage +begin + # This does not require "simplecov", but + require "kettle-soup-cover" + # this next line has a side-effect of running `.simplecov` + require "simplecov" if defined?(Kettle::Soup::Cover) && Kettle::Soup::Cover::DO_COV +rescue LoadError + nil +end + +# Testing libraries that need to load after simplecov +require "minitest/autorun" +require "minitest/focus" + +# 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/support/logging.rb b/test/support/logging.rb new file mode 100644 index 0000000..d33aef2 --- /dev/null +++ b/test/support/logging.rb @@ -0,0 +1,7 @@ +require "logger" + +module TestLogging + LOGGER = Logger.new($stdout) +end + +TestLogging::LOGGER.level = Logger::WARN diff --git a/test/test_integration.rb b/test/test_integration.rb index 9ea7ed4..128a2a6 100644 --- a/test/test_integration.rb +++ b/test/test_integration.rb @@ -1,402 +1,259 @@ -require File.expand_path("../helper", __FILE__) +require_relative "support/config" -describe "integration" do - class MockFetcher - def initialize(app) - @app = app - end +describe "openid integration" do + include Rots::Test::RackTestHelpers - 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) + it "with_get" do + app = app({}) + mock_openid_request(app, "/", method: "GET") + follow_openid_redirect!(app) - status, headers, body = @app.call(env) + assert_equal 200, @response.status + assert_equal "GET", @response.headers["X-Method"] + assert_equal "/", @response.headers["X-Path"] + assert_equal "success", @response.body + end - 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_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) - 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 - RotsServerUrl = 'http://localhost:9292' + it "with_post_method" do + app = app({}) + mock_openid_request(app, "/", method: "POST") + follow_openid_redirect!(app) - RotsApp = Rack::Builder.new do - require 'rots' + assert_equal 200, @response.status + assert_equal "POST", @response.headers["X-Method"] + assert_equal "/", @response.headers["X-Path"] + assert_equal "success", @response.body + end - config = { - 'identity' => 'john.doe', - 'sreg' => { - 'nickname' => 'jdoe', - 'fullname' => 'John Doe', - 'email' => 'jhon@doe.com', - 'dob' => Date.parse('1985-09-21'), - 'gender' => 'M' - } - } + it "with_custom_return_to" do + app = app(return_to: "http://example.org/complete") + mock_openid_request(app, "/", method: "GET") + follow_openid_redirect!(app) - map("/%s" % config['identity']) do - run Rots::IdentityPageApp.new(config, {}) - 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 - map '/server' do - run Rots::ServerApp.new(config, :storage => Dir.tmpdir) - 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 - OpenID.fetcher = MockFetcher.new(RotsApp) + 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") - module RackTestHelpers - private + assert_equal 303, @response.status + env = Rack::MockRequest.env_for(@response.headers["Location"]) + _status, headers, _body = Rots::Mocks::RotsServer.new.call(env) - def process(*args) - env = Rack::MockRequest.env_for(*args) - @response = Rack::MockResponse.new(*@app.call(env)) - end + _uri, input = headers["Location"].split("?", 2) + mock_openid_request(app, "http://example.org/complete?user[remember_me]=true", method: "POST", input: input) - def follow_redirect! - assert @response - assert_equal 303, @response.status + 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 - env = Rack::MockRequest.env_for(@response.headers['Location']) - _status, headers, _body = RotsApp.call(env) + 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) - uri = URI(headers['Location']) - process("#{uri.path}?#{uri.query}") - 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 "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 "with_custom_return_method" do + app = app(method: "put") + mock_openid_request(app, "/", method: "GET") + follow_openid_redirect!(app) - 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_equal({}, - Rack::OpenID.parse_header('Realm="Example"')) - end + assert_equal 200, @response.status + assert_equal "PUT", @response.headers["X-Method"] + assert_equal "/", @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 => "#{RotsServerUrl}/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_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" do - @app = app - process('/', :method => 'POST') - follow_redirect! - 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 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_to" do - @app = app(:return_to => 'http://example.org/complete') - process('/', :method => 'GET') - follow_redirect! - 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_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 '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_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") - 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') + location = @response.headers["Location"] - assert_equal 303, @response.status - env = Rack::MockRequest.env_for(@response.headers['Location']) - _status, headers, _body = RotsApp.call(env) + assert_match(/openid.oauth.consumer/, location) + assert_match(/openid.oauth.scope/, location) - _uri, input = headers['Location'].split('?', 2) - process("http://example.org/complete?user[remember_me]=true", :method => 'POST', :input => input) + follow_openid_redirect!(app) - 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 "/", @response.headers["X-Path"] + assert_equal "success", @response.body + end - 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 '/complete', @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, + ) + mock_openid_request(app, "/", method: "GET") - it "with_custom_return_method" do - @app = app(:method => 'put') - process('/', :method => 'GET') - follow_redirect! - 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_equal 200, @response.status - assert_equal 'GET', @response.headers['X-Method'] - assert_equal '/', @response.headers['X-Path'] - assert_equal 'success', @response.body - end + assert_match(/pape\.preferred_auth_policies=test_policy1\+test_policy2/, location) + assert_match(/pape\.max_auth_age=600/, location) - 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 + follow_openid_redirect!(app) - 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') - - location = @response.headers['Location'] - assert_match(/openid.oauth.consumer/, location) - assert_match(/openid.oauth.scope/, location) - - 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_pape" do - @app = app( - :'pape[preferred_auth_policies]' => ['test_policy1', 'test_policy2'], - :'pape[max_auth_age]' => 600 - ) - process('/', :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) - - 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_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") - it "with_immediate_mode_setup_needed" do - skip do - @app = app(:identifier => "#{RotsServerUrl}/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 RotsServerUrl, @response.headers['Location'] - assert_equal 'setup_needed', @response.body - end - end + location = @response.headers["Location"] - it "with_realm_wildcard" do - @app = app( - :realm_domain => "*.example.org" - ) - process('/', :method => 'GET') + assert_match(/openid.mode=checkid_immediate/, location) - location = @response.headers['Location'] - assert_match(/openid.realm=http%3A%2F%2F%2A.example.org/, location) + follow_openid_redirect!(app) - follow_redirect! - 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 Rots::Mocks::RotsServer::SERVER_URL, @response.headers["Location"] + assert_equal "setup_needed", @response.body end + end - it "with_inferred_realm" do - @app = app - process('/', :method => 'GET') + it "with_realm_wildcard" do + app = app( + realm_domain: "*.example.org", + ) + mock_openid_request(app, "/", method: "GET") - location = @response.headers['Location'] - assert_match(/openid.realm=http%3A%2F%2Fexample.org/, location) + location = @response.headers["Location"] - follow_redirect! - assert_equal 200, @response.status - end + assert_match(/openid.realm=http%3A%2F%2F%2A.example.org/, location) - it "with_missing_id" do - @app = app(:identifier => "#{RotsServerUrl}/john.doe") - process('/', :method => 'GET') - 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 + follow_openid_redirect!(app) - it "with_timeout" do - @app = app(:identifier => RotsServerUrl) - process('/', :method => "GET") - 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! - assert_equal 200, @response.status - assert_equal '/', @response.headers['X-Path'] - assert_equal '', @response.headers['X-Query-String'] - end + it "with_inferred_realm" do + app = app({}) + mock_openid_request(app, "/", method: "GET") - it "passthrough_standard_http_basic_auth" do - @app = app - process('/', :method => 'GET', "MOCK_HTTP_BASIC_AUTH" => '1') - assert_equal 401, @response.status - end + location = @response.headers["Location"] - private - - def app(options = {}) - options[:identifier] ||= "#{RotsServerUrl}/john.doe?openid.success=true" - - 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'] = RotsServerUrl #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(app)) - end + assert_match(/openid.realm=http%3A%2F%2Fexample.org/, location) + + follow_openid_redirect!(app) + + assert_equal 200, @response.status end - describe "simple auth" do - include RackTestHelpers + 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) + + assert_equal 400, @response.status + assert_equal "GET", @response.headers["X-Method"] + assert_equal "/", @response.headers["X-Path"] + assert_equal "cancel", @response.body + end - it "can login" do - @app = app "#{RotsServerUrl}/john.doe?openid.success=true" + it "with_timeout" do + app = app(identifier: Rots::Mocks::RotsServer::SERVER_URL) + mock_openid_request(app, "/", method: "GET") - process '/dashboard' - follow_redirect! + 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 303, @response.status - assert_equal 'http://example.org/dashboard', @response.headers['Location'] + it "sanitize_query_string" do + app = app({}) + mock_openid_request(app, "/", method: "GET") + follow_openid_redirect!(app) - cookie = @response.headers['Set-Cookie'].split(';').first - process '/dashboard', 'HTTP_COOKIE' => cookie - assert_equal 200, @response.status - assert_equal 'Hello', @response.body - end + assert_equal 200, @response.status + assert_equal "/", @response.headers["X-Path"] + assert_equal "", @response.headers["X-Query-String"] + end - it "fails login" do - @app = app "#{RotsServerUrl}/john.doe" + it "passthrough_standard_http_basic_auth" do + app = app({}) + mock_openid_request(app, "/", :method => "GET", "MOCK_HTTP_BASIC_AUTH" => "1") - process '/dashboard' - follow_redirect! - assert_match RotsServerUrl, @response.headers['Location'] - end + assert_equal 401, @response.status + end - private + private - def app(identifier) - app = lambda { |env| [200, {'Content-Type' => 'text/html'}, ['Hello']] } - app = Rack::OpenID::SimpleAuth.new(app, identifier) - Rack::Session::Pool.new(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 d9070d5..4c87d28 100644 --- a/test/test_rack_openid.rb +++ b/test/test_rack_openid.rb @@ -1,4 +1,4 @@ -require File.expand_path("../helper", __FILE__) +require_relative "support/config" describe Rack::OpenID do describe ".sanitize_params!" do @@ -15,8 +15,8 @@ def call(*args) call(params) - params["openid.sig"].must_equal "a+string+with+spaces" - params["openid.response_nonce"].must_equal "again+with+spaces!" + _(params["openid.sig"]).must_equal("a+string+with+spaces") + _(params["openid.response_nonce"]).must_equal("again+with+spaces!") end end end 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