diff --git a/.aiignore b/.aiignore new file mode 100644 index 0000000..df6bd8b --- /dev/null +++ b/.aiignore @@ -0,0 +1,19 @@ +# An .aiignore file follows the same syntax as a .gitignore file. +# .gitignore documentation: https://git-scm.com/docs/gitignore + +# you can ignore files +.DS_Store +*.log +*.tmp + +# or folders +.devcontainer/ +.qlty/ +.yardoc/ +dist/ +build/ +out/ +coverage/ +docs/ +pkg/ +results/ diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json new file mode 100644 index 0000000..c5fee1c --- /dev/null +++ b/.devcontainer/devcontainer.json @@ -0,0 +1,26 @@ +// For format details, see https://aka.ms/devcontainer.json. For config options, see the +// README at: https://github.com/devcontainers/templates/tree/main/src/ruby +{ + "name": "Ruby", + // Or use a Dockerfile or Docker Compose file. More info: https://containers.dev/guide/dockerfile + "image": "mcr.microsoft.com/devcontainers/ruby:1-3-bookworm", + + // Features to add to the dev container. More info: https://containers.dev/features. + // "features": {}, + + // Use 'forwardPorts' to make a list of ports inside the container available locally. + // "forwardPorts": [], + + // Use 'postCreateCommand' to run commands after the container is created. + // "postCreateCommand": "ruby --version", + + // Configure tool-specific properties. + "customizations" : { + "jetbrains" : { + "backend" : "RubyMine" + } + }, + + // Uncomment to connect as root instead. More info: https://aka.ms/dev-containers-non-root. + // "remoteUser": "root" +} diff --git a/.env.local.example b/.env.local.example new file mode 100644 index 0000000..1901a7a --- /dev/null +++ b/.env.local.example @@ -0,0 +1,27 @@ +# +# DO NOT EDIT THIS FILE +# +# COPY THIS FILE TO .env.local +# +# That file is ignored by .gitignore. This file is not. +# +export DEBUG=false # do not allow byebug statements (override in .env.local) +export FLOSS_FUNDING_DEBUG=false # extra logging to help diagnose issues (override in .env.local) +export AUTOGEN_FIXTURE_CLEANUP=false # autogenerated gem fixture cleanup after every RSpec run +export GIT_HOOK_FOOTER_APPEND=false +export GIT_HOOK_FOOTER_APPEND_DEBUG=false +export GIT_HOOK_FOOTER_SENTINEL="âšĄī¸ A message from a fellow meat-based-AI" + +# Tokens used by ci:act and CI helpers for reading workflow/pipeline status via APIs +# GitHub (either GITHUB_TOKEN or GH_TOKEN will be used; fine-grained recommended) +# - Scope/permissions: For fine-grained tokens, grant repository access (Read) and Actions: Read +# - For classic tokens, public repos need no scopes; private repos typically require repo +export GITHUB_TOKEN= +# Alternatively: +# export GH_TOKEN= + +# GitLab (either GITLAB_TOKEN or GL_TOKEN will be used) +# - Scope: read_api is sufficient to read pipelines +export GITLAB_TOKEN= +# Alternatively: +# export GL_TOKEN= diff --git a/.envrc b/.envrc new file mode 100644 index 0000000..d84c0f8 --- /dev/null +++ b/.envrc @@ -0,0 +1,50 @@ +# Run any command in this library's bin/ without the bin/ prefix! +# Prefer exe version over binstub +PATH_add exe +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 +export K_SOUP_COV_COMMAND_NAME="Test Coverage" +# Available formats are html, xml, rcov, lcov, json, tty +export K_SOUP_COV_FORMATTERS="html,xml,rcov,lcov,json,tty" +export K_SOUP_COV_MIN_BRANCH=70 # Means you want to enforce X% branch coverage +export K_SOUP_COV_MIN_LINE=93 # 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 K_SOUP_COV_OPEN_BIN= # Means don't try to open coverage results in browser +export MAX_ROWS=1 # Setting for simplecov-console gem for tty output, limits to the worst N rows of bad coverage +export KETTLE_TEST_SILENT=true + +# Internal Debugging Controls +export DEBUG=false # do not allow byebug statements (override in .env.local) +export FLOSS_CFG_FUND_DEBUG=false # extra logging to help diagnose issues (override in .env.local) +export FLOSS_CFG_FUND_LOGFILE=tmp/log/debug.log + +# OSC Funding is not active for OmniAuth plugins +export OPENCOLLECTIVE_HANDLE=false +export FUNDING_ORG=false + +# Concurrently developing the rubocop-lts suite? +export RUBOCOP_LTS_LOCAL=false + +# .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/.git-hooks/commit-msg b/.git-hooks/commit-msg new file mode 100755 index 0000000..5d160e6 --- /dev/null +++ b/.git-hooks/commit-msg @@ -0,0 +1,54 @@ +#!/usr/bin/env ruby +# vim: set syntax=ruby + +# Do not rely on Bundler; allow running outside a Bundler context +begin + require "rubygems" +rescue LoadError + # continue +end + +begin + # External gems + require "gitmoji/regex" + + full_text = File.read(ARGV[0]) + # Is the first character a GitMoji? + gitmoji_index = full_text =~ Gitmoji::Regex::REGEX + if gitmoji_index == 0 + exit(0) + else + denied = <<~EOM + Oh snap, think again... + + ______ _______ ___ _______ _______ _______ _______ ______ __ + | _ | | | | || || || || || | | | + | | || | ___| | || ___|| ||_ _|| ___|| _ || | + | |_||_ | |___ | || |___ | | | | | |___ | | | || | + | __ || ___| ___| || ___|| _| | | | ___|| |_| ||__| + | | | || |___ | || |___ | |_ | | | |___ | | __ + |___| |_||_______||_______||_______||_______| |___| |_______||______| |__| + + + Did you forget to add a relevant gitmoji? (see https://gitmoji.dev/ for tools) + In this project, a Gitmoji must be the first grapheme of the commit message. + What's a grapheme? + A symbol rendered to be visually identifiable as a single character, but which may be composed of multiple Unicode code points) + Must match: #{Gitmoji::Regex::REGEX} + #{"Found a gitmoji at character index #{gitmoji_index}... not good enough.\n" if gitmoji_index} + Example: git commit -m "✨ My excellent new feature" + + EOM + puts denied + exit(1) + end +rescue LoadError => e + failure = <<~EOM + gitmoji-regex gem not found: #{e.class}: #{e.message}. + Skipping gitmoji check and allowing commit to proceed. + Recommendation: add 'gitmoji-regex' to your development dependencies to enable this check. + + EOM + warn(failure) + exit(0) +end diff --git a/.git-hooks/commit-subjects-goalie.txt b/.git-hooks/commit-subjects-goalie.txt new file mode 100644 index 0000000..54b905a --- /dev/null +++ b/.git-hooks/commit-subjects-goalie.txt @@ -0,0 +1,8 @@ +🔖 Prepare release v +đŸ”’ī¸ Checksums for v + +# Lines beginning with # are ignored. +# This file is read by .git-hooks/prepare-commit-msg in each project. +# Each line of this file will be matched against the commit subject using `starts_with?`. +# If any `starts_with?` match the project script bin/prepare-commit-msg will run. +# đŸ”’ī¸ Checksums for v is the standard commit message by stone_checksums. diff --git a/.git-hooks/footer-template.erb.txt b/.git-hooks/footer-template.erb.txt new file mode 100644 index 0000000..d732d69 --- /dev/null +++ b/.git-hooks/footer-template.erb.txt @@ -0,0 +1,16 @@ +âšĄī¸ A message from a fellow meat-based-AI âšĄī¸ +- [â¤ī¸] Finely-crafted open-source tools like <%= @gem_name %> (& many more) are a full-time endeavor. +- [â¤ī¸] Though I adore my work, it lacks financial sustainability. +- [â¤ī¸] Please, help me continue enhancing your tools by becoming a sponsor: + - [💲] https://liberapay.com/pboling/donate + - [💲] https://github.com/sponsors/pboling + +<% if ENV["GIT_HOOK_FOOTER_APPEND_DEBUG"] == "true" %> + @pwd = <%= @pwd %> + @gemspecs = <%= @gemspecs %> + @spec = <%= @spec %> + @gemspec_path = <%= @gemspec_path %> + @gem_name <%= @gem_name %> + @spec_name <%= @spec_name %> + @content <%= @content %> +<% end %> diff --git a/.git-hooks/prepare-commit-msg b/.git-hooks/prepare-commit-msg new file mode 100755 index 0000000..dbc3058 --- /dev/null +++ b/.git-hooks/prepare-commit-msg @@ -0,0 +1,8 @@ +#!/bin/sh + +# Fail on error and unset variables +set -eu + +# We are not using direnv exec here because mise and direnv can result in conflicting PATH settings: +# See: https://mise.jdx.dev/direnv.html +exec "kettle-commit-msg" "$@" diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml new file mode 100644 index 0000000..a503bfa --- /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: +patreon: galtzo # Replace with a single Patreon username +polar: pboling +thanks_dev: u/gh/pboling +tidelift: rubygems/omniauth-ldap diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 0000000..956aa5a --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,13 @@ +version: 2 +updates: + - package-ecosystem: bundler + directory: "/" + schedule: + interval: "weekly" + open-pull-requests-limit: 5 + ignore: + - dependency-name: "rubocop-lts" + - package-ecosystem: "github-actions" + directory: "/" + schedule: + interval: "weekly" diff --git a/.github/workflows/ancient.yml b/.github/workflows/ancient.yml new file mode 100644 index 0000000..3cc6e37 --- /dev/null +++ b/.github/workflows/ancient.yml @@ -0,0 +1,81 @@ +name: MRI 2.3, 2.4, 2.5 (EOL) + +permissions: + contents: read + +on: + push: + branches: + - 'main' + - '*-stable' + 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: + if: "!contains(github.event.commits[0].message, '[ci skip]') && !contains(github.event.commits[0].message, '[skip ci]')" + name: Specs ${{ matrix.ruby }} ${{ matrix.appraisal }}${{ matrix.name_extra || '' }} + runs-on: ubuntu-22.04 + continue-on-error: ${{ matrix.experimental || endsWith(matrix.ruby, 'head') }} + env: # $BUNDLE_GEMFILE must be set at job level, so it is set for all steps + BUNDLE_GEMFILE: ${{ github.workspace }}/${{ matrix.gemfile }}.gemfile + strategy: + fail-fast: false + matrix: + include: + # Ruby 2.3 + - ruby: "ruby-2.3" + appraisal: "ruby-2-3" + exec_cmd: "rake test" + gemfile: "Appraisal.root" + rubygems: "3.3.27" + bundler: "2.3.27" + + # Ruby 2.4 + - ruby: "ruby-2.4" + appraisal: "ruby-2-4" + exec_cmd: "rake test" + gemfile: "Appraisal.root" + rubygems: "3.3.27" + bundler: "2.3.27" + + # Ruby 2.5 + - ruby: "ruby-2.5" + appraisal: "ruby-2-5" + exec_cmd: "rake test" + gemfile: "Appraisal.root" + rubygems: "3.3.27" + bundler: "2.3.27" + + steps: + - name: Checkout + uses: actions/checkout@v5 + + - name: Setup Ruby & RubyGems + uses: ruby/setup-ruby@v1 + with: + ruby-version: ${{ matrix.ruby }} + rubygems: ${{ matrix.rubygems }} + bundler: ${{ matrix.bundler }} + bundler-cache: false + + # Raw `bundle` will use the BUNDLE_GEMFILE set to matrix.gemfile (i.e. Appraisal.root) + # We need to do this first to get appraisal installed. + # NOTE: This does not use the primary Gemfile at all. + - name: Install Root Appraisal + run: bundle + - name: Appraisal for ${{ matrix.appraisal }} + run: bundle exec appraisal ${{ matrix.appraisal }} bundle + - name: Tests for ${{ matrix.ruby }} via ${{ matrix.exec_cmd }} + run: bundle exec appraisal ${{ matrix.appraisal }} bundle exec ${{ matrix.exec_cmd }} diff --git a/.github/workflows/auto-assign.yml b/.github/workflows/auto-assign.yml new file mode 100644 index 0000000..96975f2 --- /dev/null +++ b/.github/workflows/auto-assign.yml @@ -0,0 +1,21 @@ +name: Auto Assign +on: + issues: + types: [opened] + pull_request: + types: [opened] +jobs: + run: + runs-on: ubuntu-latest + permissions: + issues: write + pull-requests: write + steps: + - name: 'Auto-assign issue' + uses: pozil/auto-assign-issue@v2 + with: + repo-token: ${{ secrets.GITHUB_TOKEN }} + assignees: pboling + abortIfPreviousAssignees: true + allowSelfAssign: true + numOfAssignee: 1 \ No newline at end of file diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml new file mode 100644 index 0000000..f59e5c3 --- /dev/null +++ b/.github/workflows/codeql-analysis.yml @@ -0,0 +1,70 @@ +# For most projects, this workflow file will not need changing; you simply need +# to commit it to your repository. +# +# You may wish to alter this file to override the set of languages analyzed, +# or to provide custom queries or build logic. +# +# ******** NOTE ******** +# We have attempted to detect the languages in your repository. Please check +# the `language` matrix defined below to confirm you have the correct set of +# supported CodeQL languages. +# +name: "CodeQL" + +on: + push: + branches: [ main, '*-stable' ] + pull_request: + # The branches below must be a subset of the branches above + branches: [ main, '*-stable' ] + schedule: + - cron: '35 1 * * 5' + +jobs: + analyze: + name: Analyze + runs-on: ubuntu-latest + permissions: + actions: read + contents: read + security-events: write + + strategy: + fail-fast: false + matrix: + language: [ 'ruby' ] + # CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python', 'ruby' ] + # Learn more about CodeQL language support at https://git.io/codeql-language-support + + steps: + - name: Checkout repository + uses: actions/checkout@v5 + + # Initializes the CodeQL tools for scanning. + - name: Initialize CodeQL + uses: github/codeql-action/init@v4 + with: + languages: ${{ matrix.language }} + # If you wish to specify custom queries, you can do so here or in a config file. + # By default, queries listed here will override any specified in a config file. + # Prefix the list here with "+" to use these queries and those in the config file. + # queries: ./path/to/local/query, your-org/your-repo/queries@main + + # Autobuild attempts to build any compiled languages (C/C++, C#, or Java). + # If this step fails, then you should remove it and run the build manually (see below) + - name: Autobuild + uses: github/codeql-action/autobuild@v4 + + # â„šī¸ Command-line programs to run using the OS shell. + # 📚 https://git.io/JvXDl + + # âœī¸ If the Autobuild fails above, remove it and uncomment the following three lines + # and modify them (or add more) to build your code if your project + # uses a compiled language + + #- run: | + # make bootstrap + # make release + + - name: Perform CodeQL Analysis + uses: github/codeql-action/analyze@v4 diff --git a/.github/workflows/coverage.yml b/.github/workflows/coverage.yml new file mode 100644 index 0000000..5a9b42e --- /dev/null +++ b/.github/workflows/coverage.yml @@ -0,0 +1,127 @@ +name: Test Coverage + +permissions: + contents: read + pull-requests: write + id-token: write + +env: + K_SOUP_COV_MIN_BRANCH: 70 + K_SOUP_COV_MIN_LINE: 93 + K_SOUP_COV_MIN_HARD: true + K_SOUP_COV_FORMATTERS: "xml,rcov,lcov,tty" + K_SOUP_COV_DO: true + K_SOUP_COV_MULTI_FORMATTERS: true + K_SOUP_COV_COMMAND_NAME: "Test Coverage" + +on: + push: + branches: + - 'main' + - '*-stable' + 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: + coverage: + if: "!contains(github.event.commits[0].message, '[ci skip]') && !contains(github.event.commits[0].message, '[skip ci]')" + name: Code Coverage on ${{ matrix.ruby }}@current + runs-on: ubuntu-latest + continue-on-error: ${{ matrix.experimental || endsWith(matrix.ruby, 'head') }} + env: # $BUNDLE_GEMFILE must be set at job level, so it is set for all steps + BUNDLE_GEMFILE: ${{ github.workspace }}/${{ matrix.gemfile }}.gemfile + strategy: + fail-fast: false + matrix: + include: + # Coverage + - ruby: "ruby" + appraisal: "coverage" + exec_cmd: "rake test" + gemfile: "Appraisal.root" + rubygems: latest + bundler: latest + + steps: + - name: Checkout + uses: actions/checkout@v5 + + - name: Setup Ruby & RubyGems + uses: ruby/setup-ruby@v1 + with: + ruby-version: "${{ matrix.ruby }}" + rubygems: "${{ matrix.rubygems }}" + bundler: "${{ matrix.bundler }}" + bundler-cache: false + + # Raw `bundle` will use the BUNDLE_GEMFILE set to matrix.gemfile (i.e. Appraisal.root) + # We need to do this first to get appraisal installed. + # NOTE: This does not use the primary Gemfile at all. + - name: Install Root Appraisal + run: bundle + - name: Appraisal for ${{ matrix.appraisal }} + run: bundle exec appraisal ${{ matrix.appraisal }} bundle + - name: Tests for ${{ matrix.ruby }}@current via ${{ matrix.exec_cmd }} + run: bundle exec appraisal ${{ matrix.appraisal }} bundle exec ${{ matrix.exec_cmd }} + + # Do SaaS coverage uploads first + - name: Upload coverage to Coveralls + if: ${{ !env.ACT }} + uses: coverallsapp/github-action@master + with: + github-token: ${{ secrets.GITHUB_TOKEN }} + continue-on-error: ${{ matrix.experimental != 'false' }} + + - name: Upload coverage to QLTY + if: ${{ !env.ACT }} + uses: qltysh/qlty-action/coverage@main + with: + token: ${{secrets.QLTY_COVERAGE_TOKEN}} + files: coverage/.resultset.json + continue-on-error: ${{ matrix.experimental != 'false' }} + + # Build will fail here if coverage upload fails + # which will hopefully be noticed for the lack of code coverage comments + - name: Upload coverage to CodeCov + if: ${{ !env.ACT }} + uses: codecov/codecov-action@v5 + with: + use_oidc: true + fail_ci_if_error: false # optional (default = false) + files: coverage/lcov.info,coverage/coverage.xml + verbose: true # optional (default = false) + + # Then PR comments + - name: Code Coverage Summary Report + if: ${{ !env.ACT && github.event_name == 'pull_request' }} + uses: irongut/CodeCoverageSummary@v1.3.0 + 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: '93 70' + continue-on-error: ${{ matrix.experimental != 'false' }} + + - name: Add Coverage PR Comment + uses: marocchino/sticky-pull-request-comment@v2 + if: ${{ !env.ACT && github.event_name == 'pull_request' }} + with: + recreate: true + path: code-coverage-results.md + continue-on-error: ${{ matrix.experimental != 'false' }} diff --git a/.github/workflows/current.yml b/.github/workflows/current.yml new file mode 100644 index 0000000..7dab87e --- /dev/null +++ b/.github/workflows/current.yml @@ -0,0 +1,115 @@ +# Targets the evergreen latest release of ruby, truffleruby, and jruby +name: Current + +permissions: + contents: read + +env: + K_SOUP_COV_DO: false + +on: + push: + branches: + - 'main' + - '*-stable' + 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: + if: "!contains(github.event.commits[0].message, '[ci skip]') && !contains(github.event.commits[0].message, '[skip ci]')" + name: Specs ${{ matrix.ruby }}@${{ matrix.appraisal }} + runs-on: ubuntu-latest + continue-on-error: ${{ matrix.experimental || endsWith(matrix.ruby, 'head') }} + env: # $BUNDLE_GEMFILE must be set at job level, so it is set for all steps + BUNDLE_GEMFILE: ${{ github.workspace }}/${{ matrix.gemfile }}.gemfile + strategy: + matrix: + include: + # Ruby 3.4 + - ruby: "ruby" + appraisal: "current" + exec_cmd: "rake test" + gemfile: "Appraisal.root" + rubygems: latest + bundler: latest + + # truffleruby-24.1 (targets Ruby 3.3 compatibility) + - ruby: "truffleruby" + appraisal: "current" + exec_cmd: "rake test" + gemfile: "Appraisal.root" + rubygems: default + bundler: default + + # jruby-10.0 (targets Ruby 3.4 compatibility) + - ruby: "jruby" + appraisal: "current" + exec_cmd: "rake test" + gemfile: "Appraisal.root" + rubygems: default + bundler: default + + steps: + - name: Checkout + if: ${{ (env.ACT && !(startsWith(matrix.ruby, 'jruby')) || startsWith(matrix.ruby, 'truffleruby')) || (!env.ACT && (startsWith(matrix.ruby, 'jruby')) || startsWith(matrix.ruby, 'truffleruby')) }} + uses: actions/checkout@v5 + + - name: Setup Ruby & RubyGems + if: ${{ (env.ACT && !(startsWith(matrix.ruby, 'jruby')) || startsWith(matrix.ruby, 'truffleruby')) || (!env.ACT && (startsWith(matrix.ruby, 'jruby')) || startsWith(matrix.ruby, 'truffleruby')) }} + uses: ruby/setup-ruby@v1 + with: + ruby-version: ${{ matrix.ruby }} + rubygems: ${{ matrix.rubygems }} + bundler: ${{ matrix.bundler }} + bundler-cache: false + + # Raw `bundle` will use the BUNDLE_GEMFILE set to matrix.gemfile (i.e. Appraisal.root) + # We need to do this first to get appraisal installed. + # NOTE: This does not use the primary Gemfile at all. + - name: Install Root Appraisal + if: ${{ (env.ACT && !(startsWith(matrix.ruby, 'jruby')) || startsWith(matrix.ruby, 'truffleruby')) || (!env.ACT && (startsWith(matrix.ruby, 'jruby')) || startsWith(matrix.ruby, 'truffleruby')) }} + run: bundle + + - name: "[Attempt 1] Install Root Appraisal" + id: bundleAttempt1 + if: ${{ (env.ACT && !(startsWith(matrix.ruby, 'jruby')) || startsWith(matrix.ruby, 'truffleruby')) || (!env.ACT && (startsWith(matrix.ruby, 'jruby')) || startsWith(matrix.ruby, 'truffleruby')) }} + run: bundle + # Continue to the next step on failure + continue-on-error: true + + # Effectively an automatic retry of the previous step. + - name: "[Attempt 2] Install Root Appraisal" + id: bundleAttempt2 + # If bundleAttempt1 failed, try again here; Otherwise skip. + if: ${{ steps.bundleAttempt1.outcome == 'failure' && (env.ACT && !(startsWith(matrix.ruby, 'jruby')) || startsWith(matrix.ruby, 'truffleruby')) || (!env.ACT && (startsWith(matrix.ruby, 'jruby')) || startsWith(matrix.ruby, 'truffleruby')) }} + run: bundle + + - name: "[Attempt 1] Appraisal for ${{ matrix.ruby }}@${{ matrix.appraisal }}" + id: bundleAppraisalAttempt1 + if: ${{ (env.ACT && !(startsWith(matrix.ruby, 'jruby')) || startsWith(matrix.ruby, 'truffleruby')) || (!env.ACT && (startsWith(matrix.ruby, 'jruby')) || startsWith(matrix.ruby, 'truffleruby')) }} + run: bundle exec appraisal ${{ matrix.appraisal }} bundle + # Continue to the next step on failure + continue-on-error: true + + # Effectively an automatic retry of the previous step. + - name: "[Attempt 2] Appraisal for ${{ matrix.ruby }}@${{ matrix.appraisal }}" + id: bundleAppraisalAttempt2 + # If bundleAppraisalAttempt1 failed, try again here; Otherwise skip. + if: ${{ steps.bundleAppraisalAttempt1.outcome == 'failure' && (env.ACT && !(startsWith(matrix.ruby, 'jruby')) || startsWith(matrix.ruby, 'truffleruby')) || (!env.ACT && (startsWith(matrix.ruby, 'jruby')) || startsWith(matrix.ruby, 'truffleruby')) }} + run: bundle exec appraisal ${{ matrix.appraisal }} bundle + + - name: Tests for ${{ matrix.ruby }} via ${{ matrix.exec_cmd }} + if: ${{ (env.ACT && !(startsWith(matrix.ruby, 'jruby')) || startsWith(matrix.ruby, 'truffleruby')) || (!env.ACT && (startsWith(matrix.ruby, 'jruby')) || startsWith(matrix.ruby, 'truffleruby')) }} + run: bundle exec appraisal ${{ matrix.appraisal }} bundle exec ${{ matrix.exec_cmd }} diff --git a/.github/workflows/dep-heads.yml b/.github/workflows/dep-heads.yml new file mode 100644 index 0000000..a3d03f5 --- /dev/null +++ b/.github/workflows/dep-heads.yml @@ -0,0 +1,117 @@ +# Targets the evergreen latest release of ruby, truffleruby, and jruby +# and tests against the HEAD of runtime dependencies +name: Runtime Deps @ HEAD + +permissions: + contents: read + +env: + K_SOUP_COV_DO: false + +on: + push: + branches: + - 'main' + - '*-stable' + 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: + if: "!contains(github.event.commits[0].message, '[ci skip]') && !contains(github.event.commits[0].message, '[skip ci]')" + name: Specs ${{ matrix.ruby }}@${{ matrix.appraisal }}${{ matrix.name_extra || '' }} + runs-on: ubuntu-latest + continue-on-error: ${{ matrix.experimental || endsWith(matrix.ruby, 'head') }} + env: # $BUNDLE_GEMFILE must be set at job level, so it is set for all steps + BUNDLE_GEMFILE: ${{ github.workspace }}/${{ matrix.gemfile }}.gemfile + strategy: + fail-fast: true + matrix: + include: + # Ruby 3.4 + - ruby: "ruby" + appraisal: "dep-heads" + exec_cmd: "rake test" + gemfile: "Appraisal.root" + rubygems: latest + bundler: latest + + # truffleruby-24.1 (targets Ruby 3.3 compatibility) + - ruby: "truffleruby" + appraisal: "dep-heads" + exec_cmd: "rake test" + gemfile: "Appraisal.root" + rubygems: default + bundler: default + + # jruby-10.0 (targets Ruby 3.4 compatibility) + - ruby: "jruby" + appraisal: "dep-heads" + exec_cmd: "rake test" + gemfile: "Appraisal.root" + rubygems: default + bundler: default + + steps: + - name: Checkout + if: ${{ (env.ACT && !(startsWith(matrix.ruby, 'jruby')) || startsWith(matrix.ruby, 'truffleruby')) || (!env.ACT && (startsWith(matrix.ruby, 'jruby')) || startsWith(matrix.ruby, 'truffleruby')) }} + uses: actions/checkout@v5 + + - name: Setup Ruby & RubyGems + if: ${{ (env.ACT && !(startsWith(matrix.ruby, 'jruby')) || startsWith(matrix.ruby, 'truffleruby')) || (!env.ACT && (startsWith(matrix.ruby, 'jruby')) || startsWith(matrix.ruby, 'truffleruby')) }} + uses: ruby/setup-ruby@v1 + with: + ruby-version: ${{ matrix.ruby }} + rubygems: ${{ matrix.rubygems }} + bundler: ${{ matrix.bundler }} + bundler-cache: false + + # Raw `bundle` will use the BUNDLE_GEMFILE set to matrix.gemfile (i.e. Appraisal.root) + # We need to do this first to get appraisal installed. + # NOTE: This does not use the primary Gemfile at all. + - name: Install Root Appraisal + if: ${{ (env.ACT && !(startsWith(matrix.ruby, 'jruby')) || startsWith(matrix.ruby, 'truffleruby')) || (!env.ACT && (startsWith(matrix.ruby, 'jruby')) || startsWith(matrix.ruby, 'truffleruby')) }} + run: bundle + + - name: "[Attempt 1] Install Root Appraisal" + id: bundleAttempt1 + if: ${{ (env.ACT && !(startsWith(matrix.ruby, 'jruby')) || startsWith(matrix.ruby, 'truffleruby')) || (!env.ACT && (startsWith(matrix.ruby, 'jruby')) || startsWith(matrix.ruby, 'truffleruby')) }} + run: bundle + # Continue to the next step on failure + continue-on-error: true + + # Effectively an automatic retry of the previous step. + - name: "[Attempt 2] Install Root Appraisal" + id: bundleAttempt2 + # If bundleAttempt1 failed, try again here; Otherwise skip. + if: ${{ steps.bundleAttempt1.outcome == 'failure' && (env.ACT && !(startsWith(matrix.ruby, 'jruby')) || startsWith(matrix.ruby, 'truffleruby')) || (!env.ACT && (startsWith(matrix.ruby, 'jruby')) || startsWith(matrix.ruby, 'truffleruby')) }} + run: bundle + + - name: "[Attempt 1] Appraisal for ${{ matrix.ruby }}@${{ matrix.appraisal }}" + id: bundleAppraisalAttempt1 + if: ${{ (env.ACT && !(startsWith(matrix.ruby, 'jruby')) || startsWith(matrix.ruby, 'truffleruby')) || (!env.ACT && (startsWith(matrix.ruby, 'jruby')) || startsWith(matrix.ruby, 'truffleruby')) }} + run: bundle exec appraisal ${{ matrix.appraisal }} bundle + # Continue to the next step on failure + continue-on-error: true + + # Effectively an automatic retry of the previous step. + - name: "[Attempt 2] Appraisal for ${{ matrix.ruby }}@${{ matrix.appraisal }}" + id: bundleAppraisalAttempt2 + # If bundleAppraisalAttempt1 failed, try again here; Otherwise skip. + if: ${{ steps.bundleAppraisalAttempt1.outcome == 'failure' && (env.ACT && !(startsWith(matrix.ruby, 'jruby')) || startsWith(matrix.ruby, 'truffleruby')) || (!env.ACT && (startsWith(matrix.ruby, 'jruby')) || startsWith(matrix.ruby, 'truffleruby')) }} + run: bundle exec appraisal ${{ matrix.appraisal }} bundle + + - name: Tests for ${{ matrix.ruby }} via ${{ matrix.exec_cmd }} + if: ${{ (env.ACT && !(startsWith(matrix.ruby, 'jruby')) || startsWith(matrix.ruby, 'truffleruby')) || (!env.ACT && (startsWith(matrix.ruby, 'jruby')) || startsWith(matrix.ruby, 'truffleruby')) }} + run: bundle exec appraisal ${{ matrix.appraisal }} bundle exec ${{ matrix.exec_cmd }} diff --git a/.github/workflows/dependency-review.yml b/.github/workflows/dependency-review.yml new file mode 100644 index 0000000..046e9c8 --- /dev/null +++ b/.github/workflows/dependency-review.yml @@ -0,0 +1,20 @@ +# Dependency Review Action +# +# This Action will scan dependency manifest files that change as part of a Pull Request, surfacing known-vulnerable versions of the packages declared or updated in the PR. Once installed, if the workflow run is marked as required, PRs introducing known-vulnerable packages will be blocked from merging. +# +# Source repository: https://github.com/actions/dependency-review-action +# Public documentation: https://docs.github.com/en/code-security/supply-chain-security/understanding-your-software-supply-chain/about-dependency-review#dependency-review-enforcement +name: 'Dependency Review' +on: [pull_request] + +permissions: + contents: read + +jobs: + dependency-review: + runs-on: ubuntu-latest + steps: + - name: 'Checkout Repository' + uses: actions/checkout@v5 + - name: 'Dependency Review' + uses: actions/dependency-review-action@v4 diff --git a/.github/workflows/heads.yml b/.github/workflows/heads.yml new file mode 100644 index 0000000..104f1a7 --- /dev/null +++ b/.github/workflows/heads.yml @@ -0,0 +1,116 @@ +name: Heads + +permissions: + contents: read + +env: + K_SOUP_COV_DO: false + +on: + push: + branches: + - 'main' + - '*-stable' + 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: + if: "!contains(github.event.commits[0].message, '[ci skip]') && !contains(github.event.commits[0].message, '[skip ci]')" + name: Specs ${{ matrix.ruby }}@${{ matrix.appraisal }}${{ matrix.name_extra || '' }} + runs-on: ubuntu-latest + continue-on-error: ${{ matrix.experimental || endsWith(matrix.ruby, 'head') }} + env: # $BUNDLE_GEMFILE must be set at job level, so it is set for all steps + BUNDLE_GEMFILE: ${{ github.workspace }}/${{ matrix.gemfile }}.gemfile + strategy: + fail-fast: true + matrix: + include: + # NOTE: Heads use default rubygems / bundler; their defaults are custom, unreleased, and from the future! + # ruby-head + - ruby: "ruby-head" + appraisal: "head" + exec_cmd: "rake test" + gemfile: "Appraisal.root" + rubygems: default + bundler: default + + # truffleruby-head + - ruby: "truffleruby-head" + appraisal: "head" + exec_cmd: "rake test" + gemfile: "Appraisal.root" + rubygems: default + bundler: default + + # jruby-head + - ruby: "jruby-head" + appraisal: "head" + exec_cmd: "rake test" + gemfile: "Appraisal.root" + rubygems: default + bundler: default + + steps: + - name: Checkout + if: ${{ (env.ACT && !(startsWith(matrix.ruby, 'jruby')) || startsWith(matrix.ruby, 'truffleruby')) || (!env.ACT && (startsWith(matrix.ruby, 'jruby')) || startsWith(matrix.ruby, 'truffleruby')) }} + uses: actions/checkout@v5 + + - name: Setup Ruby & RubyGems + if: ${{ (env.ACT && !(startsWith(matrix.ruby, 'jruby')) || startsWith(matrix.ruby, 'truffleruby')) || (!env.ACT && (startsWith(matrix.ruby, 'jruby')) || startsWith(matrix.ruby, 'truffleruby')) }} + uses: ruby/setup-ruby@v1 + with: + ruby-version: ${{ matrix.ruby }} + rubygems: ${{ matrix.rubygems }} + bundler: ${{ matrix.bundler }} + bundler-cache: false + + # Raw `bundle` will use the BUNDLE_GEMFILE set to matrix.gemfile (i.e. Appraisal.root) + # We need to do this first to get appraisal installed. + # NOTE: This does not use the primary Gemfile at all. + - name: Install Root Appraisal + if: ${{ (env.ACT && !(startsWith(matrix.ruby, 'jruby')) || startsWith(matrix.ruby, 'truffleruby')) || (!env.ACT && (startsWith(matrix.ruby, 'jruby')) || startsWith(matrix.ruby, 'truffleruby')) }} + run: bundle + + - name: "[Attempt 1] Install Root Appraisal" + id: bundleAttempt1 + if: ${{ (env.ACT && !(startsWith(matrix.ruby, 'jruby')) || startsWith(matrix.ruby, 'truffleruby')) || (!env.ACT && (startsWith(matrix.ruby, 'jruby')) || startsWith(matrix.ruby, 'truffleruby')) }} + run: bundle + # Continue to the next step on failure + continue-on-error: true + + # Effectively an automatic retry of the previous step. + - name: "[Attempt 2] Install Root Appraisal" + id: bundleAttempt2 + # If bundleAttempt1 failed, try again here; Otherwise skip. + if: ${{ steps.bundleAttempt1.outcome == 'failure' && (env.ACT && !(startsWith(matrix.ruby, 'jruby')) || startsWith(matrix.ruby, 'truffleruby')) || (!env.ACT && (startsWith(matrix.ruby, 'jruby')) || startsWith(matrix.ruby, 'truffleruby')) }} + run: bundle + + - name: "[Attempt 1] Appraisal for ${{ matrix.ruby }}@${{ matrix.appraisal }}" + id: bundleAppraisalAttempt1 + if: ${{ (env.ACT && !(startsWith(matrix.ruby, 'jruby')) || startsWith(matrix.ruby, 'truffleruby')) || (!env.ACT && (startsWith(matrix.ruby, 'jruby')) || startsWith(matrix.ruby, 'truffleruby')) }} + run: bundle exec appraisal ${{ matrix.appraisal }} bundle + # Continue to the next step on failure + continue-on-error: true + + # Effectively an automatic retry of the previous step. + - name: "[Attempt 2] Appraisal for ${{ matrix.ruby }}@${{ matrix.appraisal }}" + id: bundleAppraisalAttempt2 + # If bundleAppraisalAttempt1 failed, try again here; Otherwise skip. + if: ${{ steps.bundleAppraisalAttempt1.outcome == 'failure' && (env.ACT && !(startsWith(matrix.ruby, 'jruby')) || startsWith(matrix.ruby, 'truffleruby')) || (!env.ACT && (startsWith(matrix.ruby, 'jruby')) || startsWith(matrix.ruby, 'truffleruby')) }} + run: bundle exec appraisal ${{ matrix.appraisal }} bundle + + - name: Tests for ${{ matrix.ruby }} via ${{ matrix.exec_cmd }} + if: ${{ (env.ACT && !(startsWith(matrix.ruby, 'jruby')) || startsWith(matrix.ruby, 'truffleruby')) || (!env.ACT && (startsWith(matrix.ruby, 'jruby')) || startsWith(matrix.ruby, 'truffleruby')) }} + run: bundle exec appraisal ${{ matrix.appraisal }} bundle exec ${{ matrix.exec_cmd }} diff --git a/.github/workflows/jruby.yml b/.github/workflows/jruby.yml new file mode 100644 index 0000000..26cc8f3 --- /dev/null +++ b/.github/workflows/jruby.yml @@ -0,0 +1,72 @@ +name: JRuby + +permissions: + contents: read + +env: + K_SOUP_COV_DO: false + +on: + push: + branches: + - 'main' + - '*-stable' + 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: + if: "!contains(github.event.commits[0].message, '[ci skip]') && !contains(github.event.commits[0].message, '[skip ci]')" + name: Specs ${{ matrix.ruby }} ${{ matrix.appraisal }}${{ matrix.name_extra || '' }} + runs-on: ubuntu-22.04 + continue-on-error: ${{ matrix.experimental || endsWith(matrix.ruby, 'head') }} + env: # $BUNDLE_GEMFILE must be set at job level, so it is set for all steps + BUNDLE_GEMFILE: ${{ github.workspace }}/${{ matrix.gemfile }}.gemfile + strategy: + matrix: + include: + # jruby-9.4 (targets Ruby 3.1 compatibility) + - ruby: "jruby-9.4" + appraisal: "ruby-3-1" + exec_cmd: "rake test" + gemfile: "Appraisal.root" + rubygems: default + bundler: default + + steps: + - name: Checkout + if: ${{ !env.ACT }} + uses: actions/checkout@v5 + + - name: Setup Ruby & RubyGems + if: ${{ !env.ACT }} + uses: ruby/setup-ruby@v1 + with: + ruby-version: ${{ matrix.ruby }} + rubygems: ${{ matrix.rubygems }} + bundler: ${{ matrix.bundler }} + bundler-cache: false + + # Raw `bundle` will use the BUNDLE_GEMFILE set to matrix.gemfile (i.e. Appraisal.root) + # We need to do this first to get appraisal installed. + # NOTE: This does not use the primary Gemfile at all. + - name: Install Root Appraisal + if: ${{ !env.ACT }} + run: bundle + - name: Appraisal for ${{ matrix.appraisal }} + if: ${{ !env.ACT }} + run: bundle exec appraisal ${{ matrix.appraisal }} bundle + - name: Tests for ${{ matrix.ruby }} via ${{ matrix.exec_cmd }} + if: ${{ !env.ACT }} + run: bundle exec appraisal ${{ matrix.appraisal }} bundle exec ${{ matrix.exec_cmd }} diff --git a/.github/workflows/legacy.yml b/.github/workflows/legacy.yml new file mode 100644 index 0000000..f7853f6 --- /dev/null +++ b/.github/workflows/legacy.yml @@ -0,0 +1,76 @@ +name: MRI 3.0, 3.1 (EOL) + +permissions: + contents: read + +env: + K_SOUP_COV_DO: false + +on: + push: + branches: + - 'main' + - '*-stable' + 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: + if: "!contains(github.event.commits[0].message, '[ci skip]') && !contains(github.event.commits[0].message, '[skip ci]')" + name: Specs ${{ matrix.ruby }} ${{ matrix.appraisal }}${{ matrix.name_extra || '' }} + runs-on: ubuntu-22.04 + continue-on-error: ${{ matrix.experimental || endsWith(matrix.ruby, 'head') }} + env: # $BUNDLE_GEMFILE must be set at job level, so it is set for all steps + BUNDLE_GEMFILE: ${{ github.workspace }}/${{ matrix.gemfile }}.gemfile + strategy: + fail-fast: false + matrix: + include: + # Ruby 3.0 + - ruby: "ruby-3.0" + appraisal: "ruby-3-0" + exec_cmd: "rake test" + gemfile: "Appraisal.root" + rubygems: '3.5.23' + bundler: '2.5.23' + + # Ruby 3.1 + - ruby: "ruby-3.1" + appraisal: "ruby-3-1" + exec_cmd: "rake test" + gemfile: "Appraisal.root" + rubygems: '3.6.9' + bundler: '2.6.9' + + steps: + - name: Checkout + uses: actions/checkout@v5 + + - name: Setup Ruby & RubyGems + uses: ruby/setup-ruby@v1 + with: + ruby-version: ${{ matrix.ruby }} + rubygems: ${{ matrix.rubygems }} + bundler: ${{ matrix.bundler }} + bundler-cache: false + + # Raw `bundle` will use the BUNDLE_GEMFILE set to matrix.gemfile (i.e. Appraisal.root) + # We need to do this first to get appraisal installed. + # NOTE: This does not use the primary Gemfile at all. + - name: Install Root Appraisal + run: bundle + - name: Appraisal for ${{ matrix.appraisal }} + run: bundle exec appraisal ${{ matrix.appraisal }} bundle + - name: Tests for ${{ matrix.ruby }} via ${{ matrix.exec_cmd }} + run: bundle exec appraisal ${{ matrix.appraisal }} bundle exec ${{ matrix.exec_cmd }} diff --git a/.github/workflows/license-eye.yml b/.github/workflows/license-eye.yml new file mode 100644 index 0000000..d5e667d --- /dev/null +++ b/.github/workflows/license-eye.yml @@ -0,0 +1,40 @@ +name: Apache SkyWalking Eyes + +permissions: + contents: read + +on: + push: + branches: + - 'main' + - '*-stable' + 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: + license-check: + if: "!contains(github.event.commits[0].message, '[ci skip]') && !contains(github.event.commits[0].message, '[skip ci]')" + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v5 + + - name: Check Dependencies' License + uses: apache/skywalking-eyes/dependency@main + with: + config: .licenserc.yaml + # Ruby packages declared as dependencies in gemspecs or Gemfiles are + # typically consumed as binaries; enable weak-compatibility + # so permissive and weak-copyleft combinations are treated as compatible. + flags: --weak-compatible diff --git a/.github/workflows/locked_deps.yml b/.github/workflows/locked_deps.yml new file mode 100644 index 0000000..b213cde --- /dev/null +++ b/.github/workflows/locked_deps.yml @@ -0,0 +1,85 @@ +--- +# Lock/Unlock Deps Pattern +# +# Two often conflicting goals resolved! +# +# - unlocked_deps.yml +# - All runtime & dev dependencies, but does not have a `gemfiles/*.gemfile.lock` committed +# - Uses an Appraisal2 "unlocked_deps" gemfile, and the current MRI Ruby release +# - Know when new dependency releases will break local dev with unlocked dependencies +# - Broken workflow indicates that new releases of dependencies may not work +# +# - locked_deps.yml +# - All runtime & dev dependencies, and has a `Gemfile.lock` committed +# - Uses the project's main Gemfile, and the current MRI Ruby release +# - Matches what contributors and maintainers use locally for development +# - Broken workflow indicates that a new contributor will have a bad time +# +name: Deps Locked + +permissions: + contents: read + +env: + # Running coverage, but not validating minimum coverage, + # because it would be redundant with the coverage workflow. + # Also we can validate all output formats without breaking CodeCov, + # since we aren't submitting these reports anywhere. + K_SOUP_COV_MIN_BRANCH: 67 + K_SOUP_COV_MIN_LINE: 86 + K_SOUP_COV_MIN_HARD: false + K_SOUP_COV_FORMATTERS: "html,xml,rcov,lcov,json,tty" + K_SOUP_COV_DO: true + K_SOUP_COV_MULTI_FORMATTERS: true + K_SOUP_COV_COMMAND_NAME: "Test Coverage" + +on: + push: + branches: + - 'main' + - '*-stable' + 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: + if: "!contains(github.event.commits[0].message, '[ci skip]') && !contains(github.event.commits[0].message, '[skip ci]')" + name: Default rake task w/ main Gemfile.lock ${{ matrix.name_extra || '' }} + runs-on: ubuntu-latest + continue-on-error: ${{ matrix.experimental }} + strategy: + fail-fast: false + matrix: + include: + # Ruby + - ruby: "ruby" + exec_cmd: "rake" + rubygems: latest + bundler: latest + experimental: false + + steps: + - name: Checkout + uses: actions/checkout@v5 + + - name: Setup Ruby & RubyGems + uses: ruby/setup-ruby@v1 + with: + ruby-version: ${{ matrix.ruby }} + rubygems: ${{ matrix.rubygems }} + bundler: ${{ matrix.bundler }} + bundler-cache: true + + - name: Checks the kitchen sink via ${{ matrix.exec_cmd }} + run: bundle exec ${{ matrix.exec_cmd }} diff --git a/.github/workflows/opencollective.yml b/.github/workflows/opencollective.yml new file mode 100644 index 0000000..6122df4 --- /dev/null +++ b/.github/workflows/opencollective.yml @@ -0,0 +1,40 @@ +name: Open Collective Backers + +on: + schedule: + # Run once a week on Sunday at 12:00 AM UTC + - cron: '0 0 * * 0' + workflow_dispatch: + +permissions: + contents: write + +jobs: + update-backers: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v5 + with: + persist-credentials: false + - name: Setup Ruby & RubyGems + uses: ruby/setup-ruby@v1 + with: + ruby-version: ruby + rubygems: default + bundler: default + bundler-cache: true + + - name: README Update + env: + # Keep GITHUB_TOKEN for any tools/scripts expecting it, mapped to the same secret + GITHUB_TOKEN: ${{ secrets.README_UPDATER_TOKEN }} + README_UPDATER_TOKEN: ${{ secrets.README_UPDATER_TOKEN }} + REPO: ${{ github.repository }} + run: | + git config user.name 'autobolt' + git config user.email 'autobots@9thbit.net' + # Use the configured token for authenticated pushes + git remote set-url origin "https://x-access-token:${README_UPDATER_TOKEN}@github.com/${REPO}.git" + bin/kettle-readme-backers + # Push back to the same branch/ref that triggered the workflow (default branch for schedule) + git push origin HEAD diff --git a/.github/workflows/style.yml b/.github/workflows/style.yml new file mode 100644 index 0000000..2fe1e03 --- /dev/null +++ b/.github/workflows/style.yml @@ -0,0 +1,65 @@ +name: Style + +permissions: + contents: read + +on: + push: + branches: + - 'main' + - '*-stable' + 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: + rubocop: + if: "!contains(github.event.commits[0].message, '[ci skip]') && !contains(github.event.commits[0].message, '[skip ci]')" + name: Style on ${{ matrix.ruby }}@current + runs-on: ubuntu-latest + continue-on-error: ${{ matrix.experimental || endsWith(matrix.ruby, 'head') }} + env: # $BUNDLE_GEMFILE must be set at job level, so it is set for all steps + BUNDLE_GEMFILE: ${{ github.workspace }}/${{ matrix.gemfile }}.gemfile + strategy: + fail-fast: false + matrix: + include: + # Style + - ruby: "ruby" + appraisal: "style" + exec_cmd: "rake rubocop_gradual:check" + gemfile: "Appraisal.root" + rubygems: latest + bundler: latest + + steps: + - name: Checkout + uses: actions/checkout@v5 + + - name: Setup Ruby & RubyGems + uses: ruby/setup-ruby@v1 + with: + ruby-version: ${{ matrix.ruby }} + rubygems: ${{ matrix.rubygems }} + bundler: ${{ matrix.bundler }} + bundler-cache: false + + # Raw `bundle` will use the BUNDLE_GEMFILE set to matrix.gemfile (i.e. Appraisal.root) + # We need to do this first to get appraisal installed. + # NOTE: This does not use the primary Gemfile at all. + - name: Install Root Appraisal + run: bundle + - name: Appraisal for ${{ matrix.appraisal }} + run: bundle exec appraisal ${{ matrix.appraisal }} bundle + - name: Run ${{ matrix.appraisal }} checks via ${{ matrix.exec_cmd }} + run: bundle exec appraisal ${{ matrix.appraisal }} bundle exec ${{ matrix.exec_cmd }} diff --git a/.github/workflows/supported.yml b/.github/workflows/supported.yml new file mode 100644 index 0000000..887034b --- /dev/null +++ b/.github/workflows/supported.yml @@ -0,0 +1,75 @@ +name: MRI Non-EOL + +permissions: + contents: read + +env: + K_SOUP_COV_DO: false + +on: + push: + branches: + - 'main' + - '*-stable' + 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: + if: "!contains(github.event.commits[0].message, '[ci skip]') && !contains(github.event.commits[0].message, '[skip ci]')" + name: Specs ${{ matrix.ruby }} ${{ matrix.appraisal }}${{ matrix.name_extra || '' }} + runs-on: ubuntu-latest + continue-on-error: ${{ matrix.experimental || endsWith(matrix.ruby, 'head') }} + env: # $BUNDLE_GEMFILE must be set at job level, so it is set for all steps + BUNDLE_GEMFILE: ${{ github.workspace }}/${{ matrix.gemfile }}.gemfile + strategy: + matrix: + include: + # Ruby 3.2 + - ruby: "ruby-3.2" + appraisal: "ruby-3-2" + exec_cmd: "rake test" + gemfile: "Appraisal.root" + rubygems: latest + bundler: latest + + # Ruby 3.3 + - ruby: "ruby-3.3" + appraisal: "ruby-3-3" + exec_cmd: "rake test" + gemfile: "Appraisal.root" + rubygems: latest + bundler: latest + + steps: + - name: Checkout + uses: actions/checkout@v5 + + - name: Setup Ruby & RubyGems + uses: ruby/setup-ruby@v1 + with: + ruby-version: ${{ matrix.ruby }} + rubygems: ${{ matrix.rubygems }} + bundler: ${{ matrix.bundler }} + bundler-cache: false + + # Raw `bundle` will use the BUNDLE_GEMFILE set to matrix.gemfile (i.e. Appraisal.root) + # We need to do this first to get appraisal installed. + # NOTE: This does not use the primary Gemfile at all. + - name: Install Root Appraisal + run: bundle + - name: Appraisal for ${{ matrix.ruby }} ${{ matrix.appraisal }} + run: bundle exec appraisal ${{ matrix.appraisal }} bundle + - name: Tests for ${{ matrix.ruby }} ${{ matrix.appraisal }} via ${{ matrix.exec_cmd }} + run: bundle exec appraisal ${{ matrix.appraisal }} bundle exec ${{ matrix.exec_cmd }} diff --git a/.github/workflows/truffle.yml b/.github/workflows/truffle.yml new file mode 100644 index 0000000..6780723 --- /dev/null +++ b/.github/workflows/truffle.yml @@ -0,0 +1,99 @@ +name: Truffle + +permissions: + contents: read + +env: + K_SOUP_COV_DO: false + +on: + push: + branches: + - 'main' + - '*-stable' + 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: + if: "!contains(github.event.commits[0].message, '[ci skip]') && !contains(github.event.commits[0].message, '[skip ci]')" + name: Specs ${{ matrix.ruby }} ${{ matrix.appraisal }}${{ matrix.name_extra || '' }} + runs-on: ubuntu-22.04 + continue-on-error: ${{ matrix.experimental || endsWith(matrix.ruby, 'head') }} + env: # $BUNDLE_GEMFILE must be set at job level, so it is set for all steps + BUNDLE_GEMFILE: ${{ github.workspace }}/${{ matrix.gemfile }}.gemfile + strategy: + matrix: + include: + # NOTE: truffleruby does not support upgrading rubygems. + # truffleruby-23.1 (targets Ruby 3.2 compatibility) + - ruby: "truffleruby-23.1" + appraisal: "ruby-3-2" + exec_cmd: "rake test" + gemfile: "Appraisal.root" + rubygems: default + bundler: default + + steps: + - name: Checkout + if: ${{ (env.ACT && !(startsWith(matrix.ruby, 'jruby')) || startsWith(matrix.ruby, 'truffleruby')) || (!env.ACT && (startsWith(matrix.ruby, 'jruby')) || startsWith(matrix.ruby, 'truffleruby')) }} + uses: actions/checkout@v5 + + - name: Setup Ruby & RubyGems + if: ${{ (env.ACT && !(startsWith(matrix.ruby, 'jruby')) || startsWith(matrix.ruby, 'truffleruby')) || (!env.ACT && (startsWith(matrix.ruby, 'jruby')) || startsWith(matrix.ruby, 'truffleruby')) }} + uses: ruby/setup-ruby@v1 + with: + ruby-version: ${{ matrix.ruby }} + rubygems: ${{ matrix.rubygems }} + bundler: ${{ matrix.bundler }} + bundler-cache: false + + # Raw `bundle` will use the BUNDLE_GEMFILE set to matrix.gemfile (i.e. Appraisal.root) + # We need to do this first to get appraisal installed. + # NOTE: This does not use the primary Gemfile at all. + - name: Install Root Appraisal + if: ${{ (env.ACT && !(startsWith(matrix.ruby, 'jruby')) || startsWith(matrix.ruby, 'truffleruby')) || (!env.ACT && (startsWith(matrix.ruby, 'jruby')) || startsWith(matrix.ruby, 'truffleruby')) }} + run: bundle + + - name: "[Attempt 1] Install Root Appraisal" + id: bundleAttempt1 + if: ${{ (env.ACT && !(startsWith(matrix.ruby, 'jruby')) || startsWith(matrix.ruby, 'truffleruby')) || (!env.ACT && (startsWith(matrix.ruby, 'jruby')) || startsWith(matrix.ruby, 'truffleruby')) }} + run: bundle + # Continue to the next step on failure + continue-on-error: true + + # Effectively an automatic retry of the previous step. + - name: "[Attempt 2] Install Root Appraisal" + id: bundleAttempt2 + # If bundleAttempt1 failed, try again here; Otherwise skip. + if: ${{ steps.bundleAttempt1.outcome == 'failure' && (env.ACT && !(startsWith(matrix.ruby, 'jruby')) || startsWith(matrix.ruby, 'truffleruby')) || (!env.ACT && (startsWith(matrix.ruby, 'jruby')) || startsWith(matrix.ruby, 'truffleruby')) }} + run: bundle + + - name: "[Attempt 1] Appraisal for ${{ matrix.ruby }}@${{ matrix.appraisal }}" + id: bundleAppraisalAttempt1 + if: ${{ (env.ACT && !(startsWith(matrix.ruby, 'jruby')) || startsWith(matrix.ruby, 'truffleruby')) || (!env.ACT && (startsWith(matrix.ruby, 'jruby')) || startsWith(matrix.ruby, 'truffleruby')) }} + run: bundle exec appraisal ${{ matrix.appraisal }} bundle + # Continue to the next step on failure + continue-on-error: true + + # Effectively an automatic retry of the previous step. + - name: "[Attempt 2] Appraisal for ${{ matrix.ruby }}@${{ matrix.appraisal }}" + id: bundleAppraisalAttempt2 + # If bundleAppraisalAttempt1 failed, try again here; Otherwise skip. + if: ${{ steps.bundleAppraisalAttempt1.outcome == 'failure' && (env.ACT && !(startsWith(matrix.ruby, 'jruby')) || startsWith(matrix.ruby, 'truffleruby')) || (!env.ACT && (startsWith(matrix.ruby, 'jruby')) || startsWith(matrix.ruby, 'truffleruby')) }} + run: bundle exec appraisal ${{ matrix.appraisal }} bundle + + - name: Tests for ${{ matrix.ruby }} via ${{ matrix.exec_cmd }} + if: ${{ (env.ACT && !(startsWith(matrix.ruby, 'jruby')) || startsWith(matrix.ruby, 'truffleruby')) || (!env.ACT && (startsWith(matrix.ruby, 'jruby')) || startsWith(matrix.ruby, 'truffleruby')) }} + run: bundle exec appraisal ${{ matrix.appraisal }} bundle exec ${{ matrix.exec_cmd }} diff --git a/.github/workflows/unlocked_deps.yml b/.github/workflows/unlocked_deps.yml new file mode 100644 index 0000000..7faffa1 --- /dev/null +++ b/.github/workflows/unlocked_deps.yml @@ -0,0 +1,84 @@ +--- +# Lock/Unlock Deps Pattern +# +# Two often conflicting goals resolved! +# +# - unlocked_deps.yml +# - All runtime & dev dependencies, but does not have a `gemfiles/*.gemfile.lock` committed +# - Uses an Appraisal2 "unlocked_deps" gemfile, and the current MRI Ruby release +# - Know when new dependency releases will break local dev with unlocked dependencies +# - Broken workflow indicates that new releases of dependencies may not work +# +# - locked_deps.yml +# - All runtime & dev dependencies, and has a `Gemfile.lock` committed +# - Uses the project's main Gemfile, and the current MRI Ruby release +# - Matches what contributors and maintainers use locally for development +# - Broken workflow indicates that a new contributor will have a bad time +# +name: Deps Unlocked + +permissions: + contents: read + +env: + K_SOUP_COV_DO: false + +on: + push: + branches: + - 'main' + - '*-stable' + 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: + if: "!contains(github.event.commits[0].message, '[ci skip]') && !contains(github.event.commits[0].message, '[skip ci]')" + name: Default rake task w/ unlocked deps ${{ matrix.name_extra || '' }} + runs-on: ubuntu-latest + continue-on-error: ${{ matrix.experimental || endsWith(matrix.ruby, 'head') }} + env: # $BUNDLE_GEMFILE must be set at job level, so it is set for all steps + BUNDLE_GEMFILE: ${{ github.workspace }}/${{ matrix.gemfile }}.gemfile + strategy: + matrix: + include: + # Ruby + - ruby: "ruby" + appraisal_name: "unlocked_deps" + exec_cmd: "rake" + gemfile: "Appraisal.root" + rubygems: latest + bundler: latest + + steps: + - name: Checkout + uses: actions/checkout@v5 + + - name: Setup Ruby & RubyGems + uses: ruby/setup-ruby@v1 + with: + ruby-version: ${{ matrix.ruby }} + rubygems: ${{ matrix.rubygems }} + bundler: ${{ matrix.bundler }} + bundler-cache: false + + # Raw `bundle` will use the BUNDLE_GEMFILE set to matrix.gemfile (i.e. Appraisal.root) + # We need to do this first to get appraisal installed. + # NOTE: This does not use the primary Gemfile at all. + - name: Install Root Appraisal + run: bundle + - name: Appraisal for ${{ matrix.ruby }}@${{ matrix.appraisal_name }} + run: bundle exec appraisal ${{ matrix.appraisal_name }} bundle + - name: Run ${{ matrix.exec_cmd }} on ${{ matrix.ruby }}@${{ matrix.appraisal_name }} + run: bundle exec appraisal ${{ matrix.appraisal_name }} bundle exec ${{ matrix.exec_cmd }} diff --git a/.github/workflows/unsupported.yml b/.github/workflows/unsupported.yml new file mode 100644 index 0000000..9833699 --- /dev/null +++ b/.github/workflows/unsupported.yml @@ -0,0 +1,76 @@ +name: MRI 2.6 & 2.7 (EOL) + +permissions: + contents: read + +env: + K_SOUP_COV_DO: false + +on: + push: + branches: + - 'main' + - '*-stable' + 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: + if: "!contains(github.event.commits[0].message, '[ci skip]') && !contains(github.event.commits[0].message, '[skip ci]')" + name: Specs ${{ matrix.ruby }} ${{ matrix.appraisal }}${{ matrix.name_extra || '' }} + runs-on: ubuntu-22.04 + continue-on-error: ${{ matrix.experimental || endsWith(matrix.ruby, 'head') }} + env: # $BUNDLE_GEMFILE must be set at job level, so it is set for all steps + BUNDLE_GEMFILE: ${{ github.workspace }}/${{ matrix.gemfile }}.gemfile + strategy: + fail-fast: false + matrix: + include: + # Ruby 2.6 + - ruby: "ruby-2.6" + appraisal: "ruby-2-6" + exec_cmd: "rake test" + gemfile: "Appraisal.root" + rubygems: '3.4.22' + bundler: '2.4.22' + + # Ruby 2.7 + - ruby: "ruby-2.7" + appraisal: "ruby-2-7" + exec_cmd: "rake test" + gemfile: "Appraisal.root" + rubygems: '3.4.22' + bundler: '2.4.22' + + steps: + - name: Checkout + uses: actions/checkout@v5 + + - name: Setup Ruby & RubyGems + uses: ruby/setup-ruby@v1 + with: + ruby-version: ${{ matrix.ruby }} + rubygems: ${{ matrix.rubygems }} + bundler: ${{ matrix.bundler }} + bundler-cache: false + + # Raw `bundle` will use the BUNDLE_GEMFILE set to matrix.gemfile (i.e. Appraisal.root) + # We need to do this first to get appraisal installed. + # NOTE: This does not use the primary Gemfile at all. + - name: Install Root Appraisal + run: bundle + - name: Appraisal for ${{ matrix.appraisal }} + run: bundle exec appraisal ${{ matrix.appraisal }} bundle + - name: Tests for ${{ matrix.ruby }} via ${{ matrix.exec_cmd }} + run: bundle exec appraisal ${{ matrix.appraisal }} bundle exec ${{ matrix.exec_cmd }} diff --git a/.gitignore b/.gitignore index 3b653ef..fc62abc 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,50 @@ -.project -coverage -/Gemfile.lock +# Build Artifacts +/pkg/ +/tmp/ +*.gem + +# Bundler +/vendor/bundle/ +/.bundle/ +/gemfiles/*.lock +/gemfiles/.bundle/ +/gemfiles/.bundle/config +/gemfiles/vendor/ +Appraisal.*.gemfile.lock + +# Specs +.rspec_status +/coverage/ +/spec/reports/ +/results/ +.output.txt + +# Documentation +/.yardoc/ +/_yardoc/ +/rdoc/ +/doc/ + +# Ruby Version Managers (RVM, rbenv, etc) +# Ignored because we currently use .tool-versions +.rvmrc +.ruby-version +.ruby-gemset + +# Benchmarking +/measurement/ + +# Debugger detritus +.byebug_history + +# direnv - brew install direnv +.env.local + +# OS Detritus +.DS_Store + +# Editors +*~ + +# Sentinels +.floss_funding.*.lock diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml new file mode 100644 index 0000000..fddd2b0 --- /dev/null +++ b/.gitlab-ci.yml @@ -0,0 +1,136 @@ +# You can override the included template(s) by including variable overrides +# SAST customization: https://docs.gitlab.com/ee/user/application_security/sast/#customizing-the-sast-settings +# Secret Detection customization: https://docs.gitlab.com/ee/user/application_security/secret_detection/#customizing-settings +# Dependency Scanning customization: https://docs.gitlab.com/ee/user/application_security/dependency_scanning/#customizing-the-dependency-scanning-settings +# Container Scanning customization: https://docs.gitlab.com/ee/user/application_security/container_scanning/#customizing-the-container-scanning-settings +# Note that environment variables can be set in several places +# See https://docs.gitlab.com/ee/ci/variables/#cicd-variable-precedence +#stages: +# - test +#sast: +# stage: test +#include: +# - template: Security/SAST.gitlab-ci.yml + +default: + image: ruby + +variables: + BUNDLE_INSTALL_FLAGS: "--quiet --jobs=$(nproc) --retry=3" + BUNDLE_FROZEN: "false" # No lockfile! + BUNDLE_GEMFILE: Appraisal.root.gemfile + K_SOUP_COV_DEBUG: true + K_SOUP_COV_DO: true + K_SOUP_COV_HARD: true + K_SOUP_COV_MIN_BRANCH: 67 + K_SOUP_COV_MIN_LINE: 86 + K_SOUP_COV_VERBOSE: true + K_SOUP_COV_FORMATTERS: "tty" + K_SOUP_COV_MULTI_FORMATTERS: true + K_SOUP_COV_COMMAND_NAME: "RSpec Coverage" + +workflow: + rules: + # For merge requests, create a pipeline. + - if: '$CI_MERGE_REQUEST_IID' + # For the ` main ` branch, create a pipeline (this includes on schedules, pushes, merges, etc.). + - if: '$CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH' + # For tags, create a pipeline. + - if: '$CI_COMMIT_TAG' + +.test_template-current: &test_definition-current + image: ruby:${RUBY_VERSION} + stage: test + script: + # || true so we don't fail here, because it'll probably work even if the gem update fails + - gem update --silent --system > /dev/null 2>&1 || true + - mkdir -p vendor/bundle + - bundle config set path 'vendor/bundle' + - chmod +t -R vendor/bundle + - chmod o-w -R vendor/bundle + # Setup appraisal2 + - bundle install + # Bundle a specific appraisal + - bundle exec appraisal unlocked_deps bundle install + # Light smoke test + - bundle exec appraisal unlocked_deps bin/rake --tasks + # Run tests, skipping those that won't work in CI + - > + bundle exec appraisal unlocked_deps \ + bin/rspec spec \ + --tag \~ci_skip \ + --format progress \ + --format RspecJunitFormatter + cache: + key: ${CI_JOB_IMAGE} + paths: + - vendor/ruby + +.test_template-legacy: &test_definition-legacy + image: ruby:${RUBY_VERSION} + stage: test + script: + # RUBYGEMS_VERSION because we support EOL Ruby still... + # || true so we don't fail here, because it'll probably work even if the gem update fails + - gem install rubygems-update -v ${RUBYGEMS_VERSION} || true + # Actually updates both RubyGems and Bundler! + - update_rubygems + - mkdir -p vendor/bundle + - bundle config set path 'vendor/bundle' + - chmod +t -R vendor/bundle + - chmod o-w -R vendor/bundle + # Setup appraisal2 + - bundle install + # Bundle a specific appraisal + - bundle exec appraisal ${APPRAISAL} bundle install + # Light smoke test + - bundle exec appraisal ${APPRAISAL} bin/rake --tasks + # Run tests, skipping those that won't work in CI + - > + bundle exec appraisal unlocked_deps \ + bin/rspec spec \ + --tag \~ci_skip \ + --format progress \ + --format RspecJunitFormatter + cache: + key: ${CI_JOB_IMAGE} + paths: + - vendor/ruby + +ruby-current: + variables: + K_SOUP_COV_DO: true + <<: *test_definition-current + parallel: + matrix: + - RUBY_VERSION: ["3.2", "3.3", "3.4"] + +ruby-ruby3_1: + variables: + RUBYGEMS_VERSION: "3.6.9" + APPRAISAL: ruby_3_1 + K_SOUP_COV_DO: false + <<: *test_definition-legacy + parallel: + matrix: + - RUBY_VERSION: ["3.1"] + +ruby-ruby3_0: + variables: + RUBYGEMS_VERSION: "3.5.23" + APPRAISAL: ruby_3_0 + K_SOUP_COV_DO: false + <<: *test_definition-legacy + parallel: + matrix: + - RUBY_VERSION: ["3.0"] + +ruby-ruby2_7: + variables: + RUBYGEMS_VERSION: "3.4.22" + APPRAISAL: ruby_2_7 + K_SOUP_COV_DO: false + <<: *test_definition-legacy + parallel: + matrix: + - RUBY_VERSION: ["2.7"] diff --git a/.idea/.gitignore b/.idea/.gitignore new file mode 100644 index 0000000..f867ed5 --- /dev/null +++ b/.idea/.gitignore @@ -0,0 +1,17 @@ +# Default ignored files +/shelf/ +/workspace.xml +# Ignored default folder with query files +/queries/ +# Datasource local storage ignored files +/dataSources/ +/dataSources.local.xml +# Editor-based HTTP Client requests +/httpRequests/ + +# Zencoder local files +/zencoder/chats +/zencoder-chat-index.xml +/zencoder-chats-dedicated.xml +# Local project config +*.iml diff --git a/.idea/GitLink.xml b/.idea/GitLink.xml new file mode 100644 index 0000000..009597c --- /dev/null +++ b/.idea/GitLink.xml @@ -0,0 +1,6 @@ + + + + + \ No newline at end of file diff --git a/.idea/copilot.data.migration.agent.xml b/.idea/copilot.data.migration.agent.xml new file mode 100644 index 0000000..4ea72a9 --- /dev/null +++ b/.idea/copilot.data.migration.agent.xml @@ -0,0 +1,6 @@ + + + + + \ No newline at end of file diff --git a/.idea/copilot.data.migration.ask.xml b/.idea/copilot.data.migration.ask.xml new file mode 100644 index 0000000..7ef04e2 --- /dev/null +++ b/.idea/copilot.data.migration.ask.xml @@ -0,0 +1,6 @@ + + + + + \ No newline at end of file diff --git a/.idea/copilot.data.migration.ask2agent.xml b/.idea/copilot.data.migration.ask2agent.xml new file mode 100644 index 0000000..1f2ea11 --- /dev/null +++ b/.idea/copilot.data.migration.ask2agent.xml @@ -0,0 +1,6 @@ + + + + + \ No newline at end of file diff --git a/.idea/copilot.data.migration.edit.xml b/.idea/copilot.data.migration.edit.xml new file mode 100644 index 0000000..8648f94 --- /dev/null +++ b/.idea/copilot.data.migration.edit.xml @@ -0,0 +1,6 @@ + + + + + \ No newline at end of file diff --git a/.idea/misc.xml b/.idea/misc.xml new file mode 100644 index 0000000..98f0699 --- /dev/null +++ b/.idea/misc.xml @@ -0,0 +1,15 @@ + + + + + + \ No newline at end of file diff --git a/.idea/modules.xml b/.idea/modules.xml new file mode 100644 index 0000000..9e0cb04 --- /dev/null +++ b/.idea/modules.xml @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/.idea/vcs.xml b/.idea/vcs.xml new file mode 100644 index 0000000..8306744 --- /dev/null +++ b/.idea/vcs.xml @@ -0,0 +1,7 @@ + + + + + + + \ No newline at end of file diff --git a/.junie/guidelines-rbs.md b/.junie/guidelines-rbs.md new file mode 100644 index 0000000..9de8973 --- /dev/null +++ b/.junie/guidelines-rbs.md @@ -0,0 +1,49 @@ +# Junie Project Guidelines Addendum: RBS Documentation + +This repository ships RBS type signatures under `sig/` which are included in the published gem and referenced by documentation tooling. + +RBS files must contain only valid RBS syntax. Do not embed Ruby code or YARD-style Ruby documentation constructs in `.rbs` files. + +Requirements for RBS documentation and signatures: + +- Use RBS comment style (`# ...`) for notes and documentation inside `.rbs` files. +- Do not use Ruby heredocs (`<<-DOC`, `<<~RUBY`, etc.) or any Ruby code constructs in `.rbs` files. +- Do not use Ruby metaprogramming notation like `class << self` in `.rbs`. For singleton methods, use: + - `def self.method_name: ...` +- Do not use `extend self` or `module self` in `.rbs`. Declare singleton methods explicitly with `def self.method_name: ...`. +- Keep type aliases, interfaces, and method signatures in proper RBS form only (e.g., `def foo: (String) -> Integer`). +- If you need to document parameters or returns, place brief comments above the signature lines using `#` and keep them RBS-friendly (no `@param` / `@return` tags from YARD). + +Examples: + +Valid (RBS): + +``` +module Foo + # Runs tasks + def self.run: () -> void +end +``` + +Invalid (not allowed in .rbs): + +``` +# Ruby syntax – not RBS +class << self + def run: () -> void +end + +# Not supported across RBS versions; avoid in this project +module self + def run: () -> void +end + +# Heredocs or any Ruby bodies are not allowed in .rbs +def self.run: () -> void + <<~DOC +DOC +end +``` + +Enforcement: +- CI and local builds may parse `.rbs` files during gem install or doc generation. Any non-RBS syntax can cause installation to fail. Keep `.rbs` clean to avoid such failures. diff --git a/.junie/guidelines.md b/.junie/guidelines.md new file mode 100644 index 0000000..a104883 --- /dev/null +++ b/.junie/guidelines.md @@ -0,0 +1,143 @@ +Project: omniauth-ldap — Development Guidelines (for advanced contributors) + +This document captures project-specific knowledge to streamline setup, testing, and ongoing development. + +1. Build and configuration +- ENV is controlled by `direnv`. + - Two files are loaded: + - .envrc — environment variables for local development, committed to source control + - .env.local — environment variables that are not committed to source control. These setting override .envrc. + - Run `direnv allow` after making changes to .envrc or .env.local. + - See .envrc for details. + - See .env.local.example for an example of what to put in .env.local. + - See CONTRIBUTING.md for details on how to set up your local environment. +- Ruby and Bundler + - Runtime supports Ruby >= 0 + - Development tooling targets Ruby >= 2.3 (minimum supported by setup-ruby GHA). + - Use a recent Ruby (>= 3.4 recommended) for fastest setup and to exercise modern coverage behavior. + - Install dependencies via Bundler in project root: + - bundle install +- Rake tasks (preferred entry points) + - The Rakefile wires common workflows. Useful targets: + - rake spec — run RSpec suite (also aliased via rake test) + - rake coverage — run specs with coverage locally and open a report (requires kettle-soup-cover) + - rake rubocop_gradual:autocorrect — RuboCop-LTS Gradual, with autocorrect as default task + - rake reek and rake reek:update — code smell checks and persisted snapshots in REEK + - rake yard — generate YARD docs for lib and selected extra files + - rake bundle:audit and rake bundle:audit:update — dependency vulnerability checks + - rake build / rake release — gem build/release helper tasks (Bundler + stone_checksums) + - The default rake target runs a curated set of tasks; this varies for CI vs local (see CI env var logic in Rakefile). + - Always run the default rake task prior commits, and after making changes to lib/ code, or *.md files, to allow the linter to autocorrect, and to generate updated documentation. +- Coverage orchestration + - Coverage is controlled by kettle-soup-cover and .simplecov. Thresholds (line and branch) are enforced and can fail the process. + - Thresholds are primarily controlled by environment variables (see .simplecov and comments therein) typically loaded via direnv (.envrc) and CI workflow (.github/workflows/coverage.yml). When running only a test subset, thresholds may fail; see Testing below. +- Gem signing (for releases) + - Signing is enabled unless SKIP_GEM_SIGNING is set. If enabled and certificates are present (certs/.pem), gem build will attempt to sign using ~/.ssh/gem-private_key.pem. + - See CONTRIBUTING.md for releasing details; use SKIP_GEM_SIGNING when building in environments without the private key. + - Important for local testing (to avoid hanging prompts): ALWAYS skip signing when building locally to test the packaging or install process. Without the private key password, the build will wait indefinitely at a signing prompt. + - One-off commands (recommended): + - SKIP_GEM_SIGNING=true gem build omniauth-ldap.gemspec + - SKIP_GEM_SIGNING=true bundle exec rake build + - SKIP_GEM_SIGNING=true bundle exec rake release # only to test workflow; do not actually push + - direnv option (optional, not recommended globally): add `export SKIP_GEM_SIGNING=true` to your .env.local when you know you won’t be signing in this environment. + - Remove or unset SKIP_GEM_SIGNING when performing a real, signed release in the environment that has the private key. + +2. Testing +- Framework and helpers + - RSpec 3.13 with custom spec/spec_helper.rb configuration: + - silent_stream: STDOUT is silenced by default for examples to keep logs clean. + - To explicitly test console output, tag the example or group with :check_output. + - Global state hygiene: Around each example, FlossFunding.namespaces and FlossFunding.silenced are snapshotted and restored to prevent cross-test pollution. + - DEBUG toggle: Set DEBUG=true to require 'debug' and avoid silencing output during your run. + - ENV seeding: The suite sets ENV["FLOSS_FUNDING_FLOSS_FUNDING"] = "Free-as-in-beer" so that the library’s own namespace is considered activated (avoids noisy warnings). + - Coverage: kettle-soup-cover integrates SimpleCov; .simplecov is invoked from spec_helper when enabled by Kettle::Soup::Cover::DO_COV, which is controlled by K_SOUP_COV_DO being set to true / false. + - RSpec.describe usage: + - Use `describe "#"` to contain a block of specs that test instance method behavior. + - Use `describe "::"` to contain a block of specs that test class method behavior. + - Do not use `describe "."` because the dot is ambiguous w.r.t instance vs. class methods. + - When adding new code or modifying existing code always add tests to cover the updated behavior, including branches, and different types of expected and unexpected inputs. + - Additional test utilities: + - rspec-stubbed_env: Use stub_env to control ENV safely within examples. + - timecop-rspec: Time manipulation is available, and is setup by kettle-test. + - To freeze time use `freeze: Time.new(*args)` tag on an example or group +- Running tests (verified) + - Full suite (recommended to satisfy coverage thresholds): + - bin/rspec + - or: bundle exec rspec + - or: bundle exec rake spec + - Progress format (less verbose): + - bundle exec rspec --format progress + - Focused runs + - You can run a single file or example, but note: coverage thresholds need to be disabled with K_SOUP_COV_MIN_HARD=false + - Example: K_SOUP_COV_MIN_HARD=false bin/rspec spec/omniauth-ldap/class_spec.rb:42 + - Output visibility + - To see STDOUT from the code under test, use the :check_output tag on the example or group. + Example: + RSpec.describe "output", :check_output do + it "prints" do + puts "This output should be visible" + expect(true).to be true + end + end + - Alternatively, run with DEBUG=true to disable silencing for the entire run. + - During a spec run, the presence of output about missing activation keys is often expected, since it is literally what this library is for. It only indicates a failure if the spec expected all activation keys to be present, and not all specs do. +- Adding new tests (guidelines) + - Organize specs by class/module. Do not create per-task umbrella spec files; add examples to the existing spec for the class/module under test, or create a new spec file for that class/module if one does not exist. Only create a standalone scenario spec when it intentionally spans multiple classes for an integration/benchmark scenario (e.g., bench_integration_spec), and name it accordingly. + - Spec file names must map to a real class or module under lib/ (mirror the path). Do not introduce specs for non-existent classes or ad-hoc names (e.g., avoid template_helpers_replacements_spec.rb when testing Omniauth::Ldap::TemplateHelpers; add those examples to template_helpers_spec.rb). + - REQUIRED: Provide unit tests for every class, module, constant, and public method. Place them in spec/ mirroring the path under lib/. When a file under lib/ is added or changed, ensure a corresponding spec file exists/updated for it. + - Add tests for all public methods and add contexts for variations of their arguments, and arity. + - This repository targets near-100% coverage of its public API; when you add new public methods, rake tasks to a rakelib, or config behavior, add or update specs accordingly. + - Place new specs under spec/ mirroring lib/ structure where possible. Do not require "spec_helper" at the top of spec files, as it is automatically loaded by .rspec. + - If your code relies on environment variables that drive activation (see "Activation env vars" below), prefer using rspec-stubbed_env: + - it does not support stubbing with blocks, but it does automatically clean up after itself. + - the below config is included in all spec scenarios by the kettle-test gem, so no need to do it again; it is here for reference: + include_context 'with stubbed env' + - in a before hook, or in an example: + stub_env("FLOSS_FUNDING_MY_NS" => "Free-as-in-beer") + + # example code continues + + - If your spec needs to assert on console output, tag it with :check_output. By default, STDOUT is silenced. + - Use Timecop for deterministic time-sensitive behavior as needed (require config/timecop is already done by spec_helper). + +- Types and documentation + - REQUIRED: All public APIs must have RBS type signatures checked into sig/ under the corresponding path. When you add a new public method or change a signature, update the matching .rbs file. + - REQUIRED: All public methods must include inline YARD docs with @param/@return (and @yield/@option where applicable). Generate docs with `bundle exec rake yard` to verify formatting. + +3. Additional development information +- Code style and static analysis + - RuboCop-LTS (Gradual) is integrated. Use: + - bundle exec rake rubocop_gradual:autocorrect + - bundle exec rake rubocop_gradual:force_update # only run if there are still linting violations the default rake task, which includes autocorrect locally, or a standalone autocorrect task, has run, and failed, and the violations won't be fixed + - Reek is configured to scan {lib,spec,tests}/**/*.rb. Use: + - bundle exec rake reek + - bundle exec rake reek:update # writes current output to REEK, fails on smells + - Keep REEK file updated with intentional smells snapshot when appropriate (e.g., after refactors). + - Locally, the default rake task includes reek:update. +- Documentation + - Generate YARD docs with: bundle exec rake yard. It includes lib/**/*.rb and extra docs like README.md, CHANGELOG.md, RUBOCOP.md, REEK, etc. +- Appraisal and multi-gemfile testing + - appraisal2 is present to manage multiple dependency sets; see Appraisals and gemfiles/modular/*.gemfile. If you need to verify against alternate dependency versions, use Appraisal to install and run rspec under those Gemfiles. + - You can run a single github workflow by running `act -W /github/workflows/.yml` +- CI/local differences and defaults + - The Rakefile adjusts default tasks based on CI env var. Locally, rake default may include coverage, reek:update, yard, etc. On CI, it tends to just run spec. + +Quick start +1) bundle install +2) K_SOUP_COV_FORMATTERS="json" bin/rspec (generates a JSON coverage report with both line and branch data in coverage/. Use this single format.) +3) Static analysis: bundle exec rake rubocop_gradual:check && bundle exec rake reek + +Notes +- ALWAYS Run bundle exec rake rubocop_gradual:autocorrect as the final step before completing a task, to lint and autocorrect any remaining issues. Then if there are new lint failures, attempt to correct them manually. +- NEVER run vanilla rubocop, as it won't handle the linting config properly. Always run rubocop_gradual:autocorrect or rubocop_gradual. +- Running only a subset of specs is supported but in order to bypass the hard failure due to coverage thresholds, you need to run with K_SOUP_COV_MIN_HARD=false. +- When adding code that writes to STDOUT, remember most specs silence output unless tagged with :check_output or DEBUG=true. +- Completion criteria after changes: Only consider your change “done” when the relevant examples pass, as verified by .rspec_status. Do not rely on STDOUT impressions; consult .rspec_status (and example IDs) to confirm green results for the affected files/examples. If you ran a subset, re-run the full suite before finalizing to restore coverage thresholds. +- Coverage reports: NEVER review the HTML report. Use JSON (preferred), XML, LCOV, or RCOV. For this project, always run tests with K_SOUP_COV_FORMATTERS set to "json". +- Do NOT modify .envrc in tasks; when running tests locally or in scripts, manually prefix each run, e.g.: K_SOUP_COV_FORMATTERS="json" bin/rspec +- For all the kettle-soup-cover options, see .envrc and find the K_SOUP_COV_* env vars. +- NEVER modify ENV variables in tests directly. Always use the stub_env macro from the rspec-stubbed_env gem (more details in the testing section above). + +Important documentation rules +- Do NOT edit files under docs/ manually; they are generated by `bundle exec rake yard` as part of the default rake task. +- Clarification: Executable scripts provided by this gem (exe/* and installed binstubs) work when the gem is installed as a system gem (gem install omniauth-ldap). However, the Rake tasks provided by this gem require omniauth-ldap to be declared as a development dependency in the host project's Gemfile and loaded in the project's Rakefile. diff --git a/.licenserc.yaml b/.licenserc.yaml new file mode 100644 index 0000000..0eb9981 --- /dev/null +++ b/.licenserc.yaml @@ -0,0 +1,7 @@ +header: + license: + spdx-id: MIT + +dependency: + files: + - Gemfile.lock diff --git a/.opencollective.yml b/.opencollective.yml new file mode 100644 index 0000000..1eaf11d --- /dev/null +++ b/.opencollective.yml @@ -0,0 +1,3 @@ +collective: "omniauth" +readme-backers-commit-subject: "💸 Thanks 🙏 to our new backers 🎒 and subscribers 📜" +readme-osc-tag: "OPENCOLLECTIVE" diff --git a/.qlty/qlty.toml b/.qlty/qlty.toml new file mode 100644 index 0000000..9ae9cae --- /dev/null +++ b/.qlty/qlty.toml @@ -0,0 +1,79 @@ +# For a guide to configuration, visit https://qlty.sh/d/config +# Or for a full reference, visit https://qlty.sh/d/qlty-toml +config_version = "0" + +exclude_patterns = [ + "*_min.*", + "*-min.*", + "*.min.*", + "**/.yarn/**", + "**/*.d.ts", + "**/assets/**", + "**/bin/**", + "**/bower_components/**", + "**/build/**", + "**/cache/**", + "**/config/**", + "**/.devcontainer", + "**/db/**", + "**/deps/**", + "**/dist/**", + "**/doc/**", + "**/docs/**", + "**/extern/**", + "**/external/**", + "**/generated/**", + "**/Godeps/**", + "**/gradlew/**", + "**/mvnw/**", + "**/node_modules/**", + "**/protos/**", + "**/seed/**", + "**/target/**", + "**/templates/**", + "**/testdata/**", + "**/vendor/**", + ".github/workflows/codeql-analysis.yml" +] + +test_patterns = [ + "**/test/**", + "**/spec/**", + "**/*.test.*", + "**/*.spec.*", + "**/*_test.*", + "**/*_spec.*", + "**/test_*.*", + "**/spec_*.*", +] + +[smells] +mode = "comment" + +[smells.boolean_logic] +threshold = 4 +enabled = true + +[smells.file_complexity] +threshold = 55 +enabled = false + +[smells.return_statements] +threshold = 4 +enabled = true + +[smells.nested_control_flow] +threshold = 4 +enabled = true + +[smells.function_parameters] +threshold = 4 +enabled = true + +[smells.function_complexity] +threshold = 5 +enabled = true + +[smells.duplication] +enabled = true +threshold = 20 \ No newline at end of file diff --git a/.rspec b/.rspec index 53607ea..a43744c 100644 --- a/.rspec +++ b/.rspec @@ -1 +1,9 @@ ---colour +--format progress +--color +--order random +--require spec_helper +--warnings +--format html +--out results/test_results.html +--format RspecJunitFormatter +--out results/test_results.xml diff --git a/.rubocop.yml b/.rubocop.yml new file mode 100644 index 0000000..aa30b93 --- /dev/null +++ b/.rubocop.yml @@ -0,0 +1,13 @@ +inherit_gem: + rubocop-lts: config/rubygem_rspec.yml + +inherit_from: + - .rubocop_rspec.yml + +plugins: rubocop-on-rbs + +RBS: + Enabled: true + +Layout/IndentationConsistency: + Exclude: ['*.md'] diff --git a/.rubocop_gradual.lock b/.rubocop_gradual.lock new file mode 100644 index 0000000..ef77441 --- /dev/null +++ b/.rubocop_gradual.lock @@ -0,0 +1,36 @@ +{ + "README.md:3434634895": [ + [68, 14, 2, "Lint/Syntax: unexpected token tASSOC\n(Using Ruby 2.0 parser; configure using `TargetRubyVersion` parameter, under `AllCops`)", 5859494], + [69, 15, 2, "Lint/Syntax: unexpected token tASSOC\n(Using Ruby 2.0 parser; configure using `TargetRubyVersion` parameter, under `AllCops`)", 5859494] + ], + "lib/omniauth-ldap/adaptor.rb:3734330877": [ + [36, 7, 413, "Style/ClassMethodsDefinitions: Use `class << self` to define a class method.", 105664470], + [86, 17, 3, "Style/AndOr: Use `&&` instead of `and`.", 193409806], + [86, 30, 3, "Style/AndOr: Use `&&` instead of `and`.", 193409806], + [86, 37, 1, "Lint/AssignmentInCondition: Wrap assignment in parentheses if intentional", 177560] + ], + "lib/omniauth/strategies/ldap.rb:3702989656": [ + [48, 9, 53, "Lint/RescueException: Avoid rescuing the `Exception` class. Perhaps you meant to rescue `StandardError`?", 4018396070], + [54, 27, 3, "Style/AndOr: Use `&&` instead of `and`.", 193409806], + [71, 7, 970, "Style/ClassMethodsDefinitions: Use `class << self` to define a class method.", 3995669691] + ], + "spec/omniauth-ldap/adaptor_spec.rb:3490841684": [ + [72, 7, 26, "RSpec/StubbedMock: Prefer `allow` over `expect` when configuring a response.", 1924417310], + [73, 7, 26, "RSpec/StubbedMock: Prefer `allow` over `expect` when configuring a response.", 1924417310], + [74, 7, 26, "RSpec/StubbedMock: Prefer `allow` over `expect` when configuring a response.", 1924417310], + [80, 7, 26, "RSpec/StubbedMock: Prefer `allow` over `expect` when configuring a response.", 1924417310], + [81, 7, 26, "RSpec/StubbedMock: Prefer `allow` over `expect` when configuring a response.", 1924417310], + [82, 7, 26, "RSpec/StubbedMock: Prefer `allow` over `expect` when configuring a response.", 1924417310] + ], + "spec/omniauth/strategies/ldap_spec.rb:2669791786": [ + [13, 3, 54, "RSpec/LeakyConstantDeclaration: Stub class constant instead of declaring explicitly.", 2419068710], + [76, 13, 9, "RSpec/ContextWording: Context description should match /^when\\b/, /^with\\b/, or /^without\\b/.", 1130140517], + [101, 17, 28, "RSpec/ContextWording: Context description should match /^when\\b/, /^with\\b/, or /^without\\b/.", 3444838747], + [110, 17, 23, "RSpec/ContextWording: Context description should match /^when\\b/, /^with\\b/, or /^without\\b/.", 1584148894], + [121, 17, 32, "RSpec/ContextWording: Context description should match /^when\\b/, /^with\\b/, or /^without\\b/.", 1515076977], + [129, 19, 19, "RSpec/ContextWording: Context description should match /^when\\b/, /^with\\b/, or /^without\\b/.", 2526348694], + [141, 17, 56, "RSpec/ContextWording: Context description should match /^when\\b/, /^with\\b/, or /^without\\b/.", 2413495789], + [156, 13, 9, "RSpec/ContextWording: Context description should match /^when\\b/, /^with\\b/, or /^without\\b/.", 3182939526], + [189, 15, 19, "RSpec/ContextWording: Context description should match /^when\\b/, /^with\\b/, or /^without\\b/.", 2526348694] + ] +} diff --git a/.rubocop_rspec.yml b/.rubocop_rspec.yml new file mode 100644 index 0000000..df5911b --- /dev/null +++ b/.rubocop_rspec.yml @@ -0,0 +1,30 @@ +RSpec/MultipleExpectations: + Enabled: false + +RSpec/NamedSubject: + Enabled: false + +RSpec/ExampleLength: + Enabled: false + +RSpec/VerifiedDoubles: + Enabled: false + +RSpec/MessageSpies: + Enabled: false + +RSpec/InstanceVariable: + Enabled: false + +RSpec/NestedGroups: + Enabled: false + +RSpec/ExpectInHook: + Enabled: false + +RSpec/DescribeClass: + Exclude: + - 'spec/examples/*' + +RSpec/MultipleMemoizedHelpers: + Enabled: false diff --git a/.simplecov b/.simplecov new file mode 100644 index 0000000..af5bd1c --- /dev/null +++ b/.simplecov @@ -0,0 +1,7 @@ +require "kettle/soup/cover/config" + +# Minimum coverage thresholds are set by kettle-soup-cover. +# It is controlled by ENV variables, which are set in .envrc and loaded via `direnv allow` +# If the values for minimum coverage need to change, they should be changed both there, +# and in 2 places in .github/workflows/coverage.yml. +SimpleCov.start diff --git a/.tool-versions b/.tool-versions new file mode 100644 index 0000000..3f03c7a --- /dev/null +++ b/.tool-versions @@ -0,0 +1 @@ +ruby 3.4.7 diff --git a/.yard_gfm_support.rb b/.yard_gfm_support.rb new file mode 100644 index 0000000..49dde5f --- /dev/null +++ b/.yard_gfm_support.rb @@ -0,0 +1,22 @@ +# Gratefully and liberally taken from the MIT-licensed https://github.com/bensheldon/good_job/pull/113/files +require "kramdown" +require "kramdown-parser-gfm" + +# Custom markup provider class that always renders Kramdown using GFM (Github Flavored Markdown). +# GFM is needed to render markdown tables and fenced code blocks in the README. +class KramdownGfmDocument < Kramdown::Document + def initialize(source, options = {}) + options[:input] = "GFM" unless options.key?(:input) + super(source, options) + end +end + +# Insert the new provider as the highest priority option for Markdown. +# See: +# - https://github.com/lsegal/yard/issues/1157 +# - https://github.com/lsegal/yard/issues/1017 +# - https://github.com/lsegal/yard/blob/main/lib/yard/templates/helpers/markup_helper.rb +YARD::Templates::Helpers::MarkupHelper::MARKUP_PROVIDERS[:markdown].insert( + 0, + {:const => "KramdownGfmDocument"}, +) diff --git a/.yardopts b/.yardopts new file mode 100644 index 0000000..479134d --- /dev/null +++ b/.yardopts @@ -0,0 +1,11 @@ +--plugin junk +--plugin relative_markdown_links +--readme README.md +--charset utf-8 +--markup markdown +--output docs +--load .yard_gfm_support.rb +'lib/**/*.rb' +- +'*.md' +'*.txt' \ No newline at end of file diff --git a/Appraisal.root.gemfile b/Appraisal.root.gemfile new file mode 100644 index 0000000..dafd51a --- /dev/null +++ b/Appraisal.root.gemfile @@ -0,0 +1,10 @@ +# frozen_string_literal: true + +source "https://gem.coop" + +# Appraisal Root Gemfile is for running appraisal to generate the Appraisal Gemfiles +# in gemfiles/*gemfile. +# On CI, we use it for the Appraisal-based builds. +# We do not load the standard Gemfile, as it is tailored for local development. + +gemspec diff --git a/Appraisals b/Appraisals new file mode 100644 index 0000000..27b567b --- /dev/null +++ b/Appraisals @@ -0,0 +1,131 @@ +# frozen_string_literal: true + +# HOW TO UPDATE APPRAISALS (will run rubocop_gradual's autocorrect afterward): +# bin/rake appraisals:update + +# Lock/Unlock Deps Pattern +# +# Two often conflicting goals resolved! +# +# - unlocked_deps.yml +# - All runtime & dev dependencies, but does not have a `gemfiles/*.gemfile.lock` committed +# - Uses an Appraisal2 "unlocked_deps" gemfile, and the current MRI Ruby release +# - Know when new dependency releases will break local dev with unlocked dependencies +# - Broken workflow indicates that new releases of dependencies may not work +# +# - locked_deps.yml +# - All runtime & dev dependencies, and has a `Gemfile.lock` committed +# - Uses the project's main Gemfile, and the current MRI Ruby release +# - Matches what contributors and maintainers use locally for development +# - Broken workflow indicates that a new contributor will have a bad time +# +appraise "unlocked_deps" do + eval_gemfile "modular/coverage.gemfile" + eval_gemfile "modular/documentation.gemfile" + eval_gemfile "modular/optional.gemfile" + eval_gemfile "modular/style.gemfile" + eval_gemfile "modular/x_std_libs.gemfile" +end + +# Used for head (nightly) releases of ruby, truffleruby, and jruby. +# Split into discrete appraisals if one of them needs a dependency locked discretely. +appraise "head" do + # Why is gem "cgi" here? See: https://github.com/vcr/vcr/issues/1057 + # gem "cgi", ">= 0.5" + gem "benchmark", "~> 0.4", ">= 0.4.1" + eval_gemfile "modular/omniauth/vHEAD.gemfile" + eval_gemfile "modular/optional.gemfile" + eval_gemfile "modular/rack/vHEAD.gemfile" + eval_gemfile "modular/x_std_libs.gemfile" +end + +# Used for current releases of ruby, truffleruby, and jruby. +# Split into discrete appraisals if one of them needs a dependency locked discretely. +appraise "current" do + eval_gemfile "modular/omniauth/r3/v2.1.gemfile" + eval_gemfile "modular/optional.gemfile" + eval_gemfile "modular/rack/r3/v3.2.gemfile" + eval_gemfile "modular/x_std_libs.gemfile" +end + +# Test current Rubies against head versions of runtime dependencies +appraise "dep-heads" do + eval_gemfile "modular/omniauth/vHEAD.gemfile" + eval_gemfile "modular/optional.gemfile" + eval_gemfile "modular/rack/vHEAD.gemfile" + eval_gemfile "modular/runtime_heads.gemfile" +end + +appraise "ruby-2-3" do + eval_gemfile "modular/omniauth/r2/v1.1.gemfile" + eval_gemfile "modular/rack/r2.1/v1.6.gemfile" + eval_gemfile "modular/x_std_libs/r2.3/libs.gemfile" +end + +appraise "ruby-2-4" do + eval_gemfile "modular/omniauth/r2/v1.5.gemfile" + eval_gemfile "modular/rack/r2.3/v2.1.gemfile" + eval_gemfile "modular/x_std_libs/r2.4/libs.gemfile" +end + +appraise "ruby-2-5" do + eval_gemfile "modular/omniauth/r2/v1.9.gemfile" + eval_gemfile "modular/rack/r2.3/v2.2.gemfile" + eval_gemfile "modular/x_std_libs/r2.6/libs.gemfile" +end + +appraise "ruby-2-6" do + eval_gemfile "modular/omniauth/r2/v2.0.gemfile" + eval_gemfile "modular/rack/r2/v2.2.gemfile" + eval_gemfile "modular/x_std_libs/r2.6/libs.gemfile" +end + +appraise "ruby-2-7" do + eval_gemfile "modular/omniauth/r2/v2.1.gemfile" + eval_gemfile "modular/rack/r2/v3.2.gemfile" + eval_gemfile "modular/x_std_libs/r2/libs.gemfile" +end + +appraise "ruby-3-0" do + eval_gemfile "modular/omniauth/r3/v2.1.gemfile" + eval_gemfile "modular/rack/r3/v3.2.gemfile" + eval_gemfile "modular/x_std_libs/r3.1/libs.gemfile" +end + +appraise "ruby-3-1" do + eval_gemfile "modular/omniauth/r3/v2.1.gemfile" + eval_gemfile "modular/rack/r3/v3.2.gemfile" + eval_gemfile "modular/x_std_libs/r3.1/libs.gemfile" +end + +appraise "ruby-3-2" do + eval_gemfile "modular/omniauth/r3/v2.1.gemfile" + eval_gemfile "modular/rack/r3/v3.2.gemfile" + eval_gemfile "modular/x_std_libs/r3/libs.gemfile" +end + +appraise "ruby-3-3" do + eval_gemfile "modular/omniauth/r3/v2.1.gemfile" + eval_gemfile "modular/rack/r3/v3.2.gemfile" + eval_gemfile "modular/x_std_libs/r3/libs.gemfile" +end + +# Only run security audit on the latest version of Ruby +appraise "audit" do + eval_gemfile "modular/x_std_libs.gemfile" +end + +# Only run coverage on the latest version of Ruby +appraise "coverage" do + eval_gemfile "modular/omniauth/r3/v2.1.gemfile" + eval_gemfile "modular/rack/r3/v3.2.gemfile" + eval_gemfile "modular/coverage.gemfile" + eval_gemfile "modular/optional.gemfile" + eval_gemfile "modular/x_std_libs.gemfile" +end + +# Only run linter on the latest version of Ruby (but, in support of oldest supported Ruby version) +appraise "style" do + eval_gemfile "modular/style.gemfile" + eval_gemfile "modular/x_std_libs.gemfile" +end diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..a35f990 --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,31 @@ +# Changelog + +[![SemVer 2.0.0][📌semver-img]][📌semver] [![Keep-A-Changelog 1.0.0][📗keep-changelog-img]][📗keep-changelog] + +All notable changes to this project will be documented in this file. + +The format is based on [Keep a Changelog][📗keep-changelog], +and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html), +and [yes][📌major-versions-not-sacred], platform and engine support are part of the [public API][📌semver-breaking]. +Please file a bug if you notice a violation of semantic versioning. + +[📌semver]: https://semver.org/spec/v2.0.0.html +[📌semver-img]: https://img.shields.io/badge/semver-2.0.0-FFDD67.svg?style=flat +[📌semver-breaking]: https://github.com/semver/semver/issues/716#issuecomment-869336139 +[📌major-versions-not-sacred]: https://tom.preston-werner.com/2022/05/23/major-version-numbers-are-not-sacred.html +[📗keep-changelog]: https://keepachangelog.com/en/1.0.0/ +[📗keep-changelog-img]: https://img.shields.io/badge/keep--a--changelog-1.0.0-FFDD67.svg?style=flat + +## [Unreleased] + +### Added + +### Changed + +### Deprecated + +### Removed + +### Fixed + +### Security \ No newline at end of file diff --git a/CITATION.cff b/CITATION.cff new file mode 100644 index 0000000..e162f74 --- /dev/null +++ b/CITATION.cff @@ -0,0 +1,20 @@ +cff-version: 1.2.0 +title: omniauth-ldap +message: >- + If you use this work and you want to cite it, + then you can use the metadata from this file. +type: software +authors: + - given-names: Peter Hurn + family-names: Boling + email: peter@railsbling.com + affiliation: railsbling.com + orcid: 'https://orcid.org/0009-0008-8519-441X' +identifiers: + - type: url + value: 'https://github.com/omniauth/omniauth-ldap' + description: omniauth-ldap +repository-code: 'https://github.com/omniauth/omniauth-ldap' +abstract: >- + omniauth-ldap +license: See license file diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md new file mode 100644 index 0000000..7ad4c15 --- /dev/null +++ b/CODE_OF_CONDUCT.md @@ -0,0 +1,134 @@ +# 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, caste, color, 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 email 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 +[![Contact Maintainer][🚂maint-contact-img]][🚂maint-contact]. +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.1, available at +[https://www.contributor-covenant.org/version/2/1/code_of_conduct.html][v2.1]. + +Community Impact Guidelines were inspired by +[Mozilla's code of conduct enforcement ladder][Mozilla CoC]. + +For answers to common questions about this code of conduct, see the FAQ at +[https://www.contributor-covenant.org/faq][FAQ]. Translations are available at +[https://www.contributor-covenant.org/translations][translations]. + +[homepage]: https://www.contributor-covenant.org +[v2.1]: https://www.contributor-covenant.org/version/2/1/code_of_conduct.html +[Mozilla CoC]: https://github.com/mozilla/diversity +[FAQ]: https://www.contributor-covenant.org/faq +[translations]: https://www.contributor-covenant.org/translations +[🚂maint-contact]: http://www.railsbling.com/contact +[🚂maint-contact-img]: https://img.shields.io/badge/Contact-Maintainer-0093D0.svg?style=flat&logo=rubyonrails&logoColor=red diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 0000000..2d8d54b --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,213 @@ +# Contributing + +Bug reports and pull requests are welcome on [GitHub][📜src-gh]. +This project should be a safe, welcoming space for collaboration, so contributors agree to adhere to +the [code of conduct][🤝conduct]. + +To submit a patch, please fork the project, create a patch with tests, and send a pull request. + +Remember to [![Keep A Changelog][📗keep-changelog-img]][📗keep-changelog] if you make changes. + +## Help out! + +Take a look at the `reek` list which is the file called `REEK` and find something to improve. + +Follow these instructions: + +1. Fork the repository +2. Create a feature branch (`git checkout -b my-new-feature`) +3. Make some fixes. +4. Commit changes (`git commit -am 'Added some feature'`) +5. Push to the branch (`git push origin my-new-feature`) +6. Make sure to add tests for it. This is important, so it doesn't break in a future release. +7. Create new Pull Request. + +## Executables vs Rake tasks + +Executables shipped by dependencies, such as omniauth-ldap, and stone_checksums, are available +after running `bin/setup`. These include: + +- gem_checksums +- kettle-changelog +- kettle-commit-msg +- omniauth-ldap-setup +- kettle-dvcs +- kettle-pre-release +- kettle-readme-backers +- kettle-release + +There are many Rake tasks available as well. You can see them by running: + +```shell +bin/rake -T +``` + +## Environment Variables for Local Development + +Below are the primary environment variables recognized by stone_checksums (and its integrated tools). Unless otherwise noted, set boolean values to the string "true" to enable. + +General/runtime +- DEBUG: Enable extra internal logging for this library (default: false) +- REQUIRE_BENCH: Enable `require_bench` to profile requires (default: false) +- CI: When set to true, adjusts default rake tasks toward CI behavior + +Coverage (kettle-soup-cover / SimpleCov) +- K_SOUP_COV_DO: Enable coverage collection (default: true in .envrc) +- K_SOUP_COV_FORMATTERS: Comma-separated list of formatters (html, xml, rcov, lcov, json, tty) +- K_SOUP_COV_MIN_LINE: Minimum line coverage threshold (integer, e.g., 100) +- K_SOUP_COV_MIN_BRANCH: Minimum branch coverage threshold (integer, e.g., 100) +- K_SOUP_COV_MIN_HARD: Fail the run if thresholds are not met (true/false) +- K_SOUP_COV_MULTI_FORMATTERS: Enable multiple formatters at once (true/false) +- K_SOUP_COV_OPEN_BIN: Path to browser opener for HTML (empty disables auto-open) +- MAX_ROWS: Limit console output rows for simplecov-console (e.g., 1) + Tip: When running a single spec file locally, you may want `K_SOUP_COV_MIN_HARD=false` to avoid failing thresholds for a partial run. + +GitHub API and CI helpers +- GITHUB_TOKEN or GH_TOKEN: Token used by `ci:act` and release workflow checks to query GitHub Actions status at higher rate limits + +Releasing and signing +- SKIP_GEM_SIGNING: If set, skip gem signing during build/release +- GEM_CERT_USER: Username for selecting your public cert in `certs/.pem` (defaults to $USER) +- SOURCE_DATE_EPOCH: Reproducible build timestamp. `kettle-release` will set this automatically for the session. + +Git hooks and commit message helpers (exe/kettle-commit-msg) +- GIT_HOOK_BRANCH_VALIDATE: Branch name validation mode (e.g., `jira`) or `false` to disable +- GIT_HOOK_FOOTER_APPEND: Append a footer to commit messages when goalie allows (true/false) +- GIT_HOOK_FOOTER_SENTINEL: Required when footer append is enabled — a unique first-line sentinel to prevent duplicates +- GIT_HOOK_FOOTER_APPEND_DEBUG: Extra debug output in the footer template (true/false) + +For a quick starting point, this repository’s `.envrc` shows sane defaults, and `.env.local` can override them locally. + +## Appraisals + +From time to time the [appraisal2][🚎appraisal2] gemfiles in `gemfiles/` will need to be updated. +They are created and updated with the commands: + +```console +bin/rake appraisal:update +``` + +When adding an appraisal to CI, check the [runner tool cache][đŸƒâ€â™‚ī¸runner-tool-cache] to see which runner to use. + +## The Reek List + +Take a look at the `reek` list which is the file called `REEK` and find something to improve. + +To refresh the `reek` list: + +```console +bundle exec reek > REEK +``` + +## Run Tests + +To run all tests + +```console +bundle exec rake test +``` + +### Spec organization (required) + +- One spec file per class/module. For each class or module under `lib/`, keep all of its unit tests in a single spec file under `spec/` that mirrors the path and file name exactly: `lib/omniauth/ldap/my_class.rb` -> `spec/omniauth/ldap/my_class_spec.rb`. +- Exception: Integration specs that intentionally span multiple classes. Place these under `spec/integration/` (or a clearly named integration folder), and do not directly mirror a single class. Name them after the scenario, not a class. + +## Lint It + +Run all the default tasks, which includes running the gradually autocorrecting linter, `rubocop-gradual`. + +```console +bundle exec rake +``` + +Or just run the linter. + +```console +bundle exec rake rubocop_gradual:autocorrect +``` + +For more detailed information about using RuboCop in this project, please see the [RUBOCOP.md](RUBOCOP.md) guide. This project uses `rubocop_gradual` instead of vanilla RuboCop, which requires specific commands for checking violations. + +### Important: Do not add inline RuboCop disables + +Never add `# rubocop:disable ...` / `# rubocop:enable ...` comments to code or specs (except when following the few existing `rubocop:disable` patterns for a rule already being disabled elsewhere in the code). Instead: + +- Prefer configuration-based exclusions when a rule should not apply to certain paths or files (e.g., via `.rubocop.yml`). +- When a violation is temporary, and you plan to fix it later, record it in `.rubocop_gradual.lock` using the gradual workflow: + - `bundle exec rake rubocop_gradual:autocorrect` (preferred) + - `bundle exec rake rubocop_gradual:force_update` (only when you cannot fix the violations immediately) + +As a general rule, fix style issues rather than ignoring them. For example, our specs should follow RSpec conventions like using `described_class` for the class under test. + +## Contributors + +Your picture could be here! + +[![Contributors][🖐contributors-img]][🖐contributors] + +Made with [contributors-img][🖐contrib-rocks]. + +## For Maintainers + +### One-time, Per-maintainer, Setup + +**IMPORTANT**: To sign a build, +a 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). +All releases are signed releases. +See: [RubyGems Security Guide][đŸ”’ī¸rubygems-security-guide] + +NOTE: To build without signing the gem set `SKIP_GEM_SIGNING` to any value in the environment. + +### To release a new version: + +#### Automated process + +1. Update version.rb to contain the correct version-to-be-released. +2. Run `bundle exec kettle-changelog`. +3. Run `bundle exec kettle-release`. + +#### Manual process + +1. Run `bin/setup && bin/rake` as a "test, coverage, & linting" sanity check +2. Update the version number in `version.rb`, and ensure `CHANGELOG.md` reflects changes +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, and 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 latest trunk code +9. Optional for older Bundler (< 2.7.0): Set `SOURCE_DATE_EPOCH` so `rake build` and `rake release` use the same timestamp and generate the same checksums + - If your Bundler is >= 2.7.0, you can skip this; builds are reproducible by default. + - Run `export SOURCE_DATE_EPOCH=$EPOCHSECONDS && echo $SOURCE_DATE_EPOCH` + - If the echo above has no output, then it didn't work. + - Note: `zsh/datetime` module is needed, if running `zsh`. + - In older versions of `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/gem_checksums` (more context [1][đŸ”’ī¸rubygems-checksums-pr], [2][đŸ”’ī¸rubygems-guides-pr]) + to create SHA-256 and SHA-512 checksums. This functionality is provided by the `stone_checksums` + [gem][💎stone_checksums]. + - The script automatically commits but does not push the checksums +12. Sanity check the SHA256, comparing with the output from the `bin/gem_checksums` command: + - `sha256sum pkg/-.gem` +13. 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 the gem host configured in the gemspec. + +[📜src-gh]: https://github.com/omniauth/omniauth-ldap +[đŸ§Ēbuild]: https://github.com/omniauth/omniauth-ldap/actions +[🤝conduct]: https://gitlab.com/omniauth/omniauth-ldap/-/blob/main/CODE_OF_CONDUCT.md +[🖐contrib-rocks]: https://contrib.rocks +[🖐contributors]: https://github.com/omniauth/omniauth-ldap/graphs/contributors +[🖐contributors-img]: https://contrib.rocks/image?repo=omniauth/omniauth-ldap +[💎gem-coop]: https://gem.coop +[đŸ”’ī¸rubygems-security-guide]: https://guides.rubygems.org/security/#building-gems +[đŸ”’ī¸rubygems-checksums-pr]: https://github.com/rubygems/rubygems/pull/6022 +[đŸ”’ī¸rubygems-guides-pr]: https://github.com/rubygems/guides/pull/325 +[💎stone_checksums]: https://github.com/galtzo-floss/stone_checksums +[📗keep-changelog]: https://keepachangelog.com/en/1.0.0/ +[📗keep-changelog-img]: https://img.shields.io/badge/keep--a--changelog-1.0.0-FFDD67.svg?style=flat +[📌semver-breaking]: https://github.com/semver/semver/issues/716#issuecomment-869336139 +[📌major-versions-not-sacred]: https://tom.preston-werner.com/2022/05/23/major-version-numbers-are-not-sacred.html +[🚎appraisal2]: https://github.com/appraisal-rb/appraisal2 +[đŸƒâ€â™‚ī¸runner-tool-cache]: https://github.com/ruby/ruby-builder/releases/tag/toolcache diff --git a/FUNDING.md b/FUNDING.md new file mode 100644 index 0000000..602bc70 --- /dev/null +++ b/FUNDING.md @@ -0,0 +1,66 @@ + + +Official Discord đŸ‘‰ī¸ [![Live Chat on Discord][âœ‰ī¸discord-invite-img]][âœ‰ī¸discord-invite] + +Many paths lead to being a sponsor or a backer of this project. Are you on such a path? + +[![Sponsor Me on Github][🖇sponsor-img]][🖇sponsor] [![Liberapay Goal Progress][â›ŗliberapay-img]][â›ŗliberapay] [![Donate on PayPal][🖇paypal-img]][🖇paypal] + +[![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&color=a51611&style=flat +[â›ŗ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-a51611.svg?style=flat +[🖇polar]: https://polar.sh/pboling +[🖇kofi-img]: https://img.shields.io/badge/ko--fi-%E2%9C%93-a51611.svg?style=flat +[🖇kofi]: https://ko-fi.com/O5O86SNP4 +[🖇patreon-img]: https://img.shields.io/badge/patreon-donate-a51611.svg?style=flat +[🖇patreon]: https://patreon.com/galtzo +[🖇buyme-small-img]: https://img.shields.io/badge/buy_me_a_coffee-%E2%9C%93-a51611.svg?style=flat +[🖇buyme]: https://www.buymeacoffee.com/pboling +[🖇paypal-img]: https://img.shields.io/badge/donate-paypal-a51611.svg?style=flat&logo=paypal +[🖇paypal]: https://www.paypal.com/paypalme/peterboling +[âœ‰ī¸discord-invite]: https://discord.gg/3qme4XHNKN +[âœ‰ī¸discord-invite-img]: https://img.shields.io/discord/1373797679469170758?style=flat + + + +# 🤑 Request for Help + +Maintainers have teeth and need to pay their dentists. +After getting laid off in an RIF in March and filled with many dozens of rejections, +I'm now spending ~60+ hours a week building open source tools. +I'm hoping to be able to pay for my kids' health insurance this month, +so if you value the work I am doing, I need your support. +Please consider sponsoring me or the project. + +To join the community or get help đŸ‘‡ī¸ Join the Discord. + +[![Live Chat on Discord][âœ‰ī¸discord-invite-img-ftb]][âœ‰ī¸discord-invite] + +To say "thanks for maintaining such a great tool" â˜ī¸ Join the Discord or đŸ‘‡ī¸ send money. + +[![Sponsor me on GitHub Sponsors][🖇sponsor-bottom-img]][🖇sponsor] 💌 [![Sponsor me on Liberapay][â›ŗliberapay-bottom-img]][â›ŗliberapay-img] 💌 [![Donate on PayPal][🖇paypal-bottom-img]][🖇paypal-img] + +# Another Way to Support Open Source Software + +> How wonderful it is that nobody need wait a single moment before starting to improve the world.
+>—Anne Frank + +I’m driven by a passion to foster a thriving open-source community – a space where people can tackle complex problems, no matter how small. Revitalizing libraries that have fallen into disrepair, and building new libraries focused on solving real-world challenges, are my passions — totaling 79 hours of FLOSS coding over just the past seven days, a pretty regular week for me. I was recently affected by layoffs, and the tech jobs market is unwelcoming. I’m reaching out here because your support would significantly aid my efforts to provide for my family, and my farm (11 🐔 chickens, 2 đŸļ dogs, 3 🐰 rabbits, 8 🐈‍ cats). + +If you work at a company that uses my work, please encourage them to support me as a corporate sponsor. My work on gems you use might show up in `bundle fund`. + +I’m developing a new library, [floss_funding][🖇floss-funding-gem], designed to empower open-source developers like myself to get paid for the work we do, in a sustainable way. Please give it a look. + +**[Floss-Funding.dev][🖇floss-funding.dev]: đŸ‘‰ī¸ No network calls. đŸ‘‰ī¸ No tracking. đŸ‘‰ī¸ No oversight. đŸ‘‰ī¸ Minimal crypto hashing. 💡 Easily disabled nags** + +[â›ŗliberapay-bottom-img]: https://img.shields.io/liberapay/goal/pboling.svg?style=for-the-badge&logo=liberapay&color=a51611 +[🖇sponsor-bottom-img]: https://img.shields.io/badge/Sponsor_Me!-pboling-blue?style=for-the-badge&logo=github +[🖇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 +[🖇paypal-bottom-img]: https://img.shields.io/badge/donate-paypal-a51611.svg?style=for-the-badge&logo=paypal&color=0A0A0A +[🖇floss-funding.dev]: https://floss-funding.dev +[🖇floss-funding-gem]: https://github.com/galtzo-floss/floss_funding +[âœ‰ī¸discord-invite-img-ftb]: https://img.shields.io/discord/1373797679469170758?style=for-the-badge diff --git a/Gemfile b/Gemfile index 339f969..f51e3f0 100644 --- a/Gemfile +++ b/Gemfile @@ -1,11 +1,39 @@ -source 'http://rubygems.org' +# frozen_string_literal: true +source "https://gem.coop" + +git_source(:codeberg) { |repo_name| "https://codeberg.org/#{repo_name}" } +git_source(:gitlab) { |repo_name| "https://gitlab.com/#{repo_name}" } + +#### IMPORTANT ####################################################### +# Gemfile is for local development ONLY; Gemfile is NOT loaded in CI # +####################################################### IMPORTANT #### + +# Include dependencies from .gemspec gemspec group :development, :test do - gem 'guard' - gem 'guard-rspec' - gem 'guard-bundler' - gem 'growl' - gem 'rb-fsevent' + gem "guard" + gem "guard-rspec" + gem "guard-bundler" + gem "growl" + gem "rb-fsevent" end + +# Debugging +eval_gemfile "gemfiles/modular/debug.gemfile" + +# Code Coverage +eval_gemfile "gemfiles/modular/coverage.gemfile" + +# Linting +eval_gemfile "gemfiles/modular/style.gemfile" + +# Documentation +eval_gemfile "gemfiles/modular/documentation.gemfile" + +# Optional +eval_gemfile "gemfiles/modular/optional.gemfile" + +### Std Lib Extracted Gems +eval_gemfile "gemfiles/modular/x_std_libs.gemfile" diff --git a/Gemfile.lock b/Gemfile.lock new file mode 100644 index 0000000..a357f22 --- /dev/null +++ b/Gemfile.lock @@ -0,0 +1,418 @@ +GIT + remote: https://github.com/pboling/yard-junk.git + revision: 54ccebabbfa9a9cd44d0b991687ebbfd22c32b55 + branch: next + specs: + yard-junk (0.0.10) + backports (>= 3.18) + benchmark + ostruct + rainbow + yard + +PATH + remote: . + specs: + omniauth-ldap (2.0.0) + net-ldap (~> 0.16) + omniauth (>= 1) + pyu-ruby-sasl (~> 0.0.3.3) + rack (>= 1) + rubyntlm (~> 0.6.2) + version_gem (~> 1.1, >= 1.1.9) + +GEM + remote: https://gem.coop/ + specs: + addressable (2.8.7) + public_suffix (>= 2.0.2, < 7.0) + ansi (1.5.0) + appraisal2 (3.0.0) + bundler (>= 1.17.3) + rake (>= 10) + thor (>= 0.14) + ast (2.4.3) + backports (3.25.2) + base64 (0.3.0) + benchmark (0.5.0) + bigdecimal (3.3.1) + bundler-audit (0.9.2) + bundler (>= 1.2.0, < 3) + thor (~> 1.0) + coderay (1.1.3) + concurrent-ruby (1.3.5) + crack (1.0.1) + bigdecimal + rexml + date (3.5.0) + debug (1.11.0) + irb (~> 1.10) + reline (>= 0.3.8) + delegate (0.4.0) + diff-lcs (1.6.2) + diffy (3.4.4) + docile (1.4.1) + dry-configurable (1.3.0) + dry-core (~> 1.1) + zeitwerk (~> 2.6) + dry-core (1.1.0) + concurrent-ruby (~> 1.0) + logger + zeitwerk (~> 2.6) + dry-inflector (1.2.0) + dry-initializer (3.2.0) + dry-logic (1.6.0) + bigdecimal + concurrent-ruby (~> 1.0) + dry-core (~> 1.1) + zeitwerk (~> 2.6) + dry-schema (1.14.1) + concurrent-ruby (~> 1.0) + dry-configurable (~> 1.0, >= 1.0.1) + dry-core (~> 1.1) + dry-initializer (~> 3.2) + dry-logic (~> 1.5) + dry-types (~> 1.8) + zeitwerk (~> 2.6) + dry-types (1.8.3) + bigdecimal (~> 3.0) + concurrent-ruby (~> 1.0) + dry-core (~> 1.0) + dry-inflector (~> 1.0) + dry-logic (~> 1.4) + zeitwerk (~> 2.6) + erb (5.1.3) + ffi (1.17.2-x86_64-linux-gnu) + formatador (1.2.2) + reline + gem_bench (2.0.5) + bundler (>= 1.14) + version_gem (~> 1.1, >= 1.1.4) + gitmoji-regex (1.0.3) + version_gem (~> 1.1, >= 1.1.8) + growl (1.0.3) + guard (2.19.1) + formatador (>= 0.2.4) + listen (>= 2.7, < 4.0) + logger (~> 1.6) + lumberjack (>= 1.0.12, < 2.0) + nenv (~> 0.1) + notiffany (~> 0.0) + ostruct (~> 0.6) + pry (>= 0.13.0) + shellany (~> 0.0) + thor (>= 0.18.1) + guard-bundler (3.0.1) + bundler (>= 2.1, < 3) + guard (~> 2.2) + guard-compat (~> 1.1) + guard-compat (1.2.1) + guard-rspec (4.7.3) + guard (~> 2.1) + guard-compat (~> 1.1) + rspec (>= 2.99.0, < 4.0) + hashdiff (1.2.1) + hashie (5.0.0) + io-console (0.8.1) + irb (1.15.3) + pp (>= 0.6.0) + rdoc (>= 4.0.0) + reline (>= 0.4.2) + json (2.15.2) + kettle-dev (1.1.45) + kettle-soup-cover (1.0.10) + simplecov (~> 0.22) + simplecov-cobertura (~> 3.0) + simplecov-console (~> 0.9, >= 0.9.3) + simplecov-html (~> 0.13, >= 0.13.1) + simplecov-lcov (~> 0.8) + simplecov-rcov (~> 0.3, >= 0.3.7) + simplecov_json_formatter (~> 0.1, >= 0.1.4) + version_gem (~> 1.1, >= 1.1.8) + kettle-test (1.0.6) + appraisal2 (~> 3.0) + backports (~> 3.0) + rspec (~> 3.0) + rspec-block_is_expected (~> 1.0, >= 1.0.6) + rspec-pending_for (~> 0.1, >= 0.1.19) + rspec-stubbed_env (~> 1.0, >= 1.0.4) + rspec_junit_formatter (~> 0.6) + silent_stream (~> 1.0, >= 1.0.12) + timecop-rspec (~> 1.0, >= 1.0.3) + version_gem (~> 1.1, >= 1.1.9) + kramdown (2.5.1) + rexml (>= 3.3.9) + kramdown-parser-gfm (1.1.0) + kramdown (~> 2.0) + language_server-protocol (3.17.0.5) + lint_roller (1.1.0) + listen (3.9.0) + rb-fsevent (~> 0.10, >= 0.10.3) + rb-inotify (~> 0.9, >= 0.9.10) + logger (1.7.0) + lumberjack (1.4.2) + method_source (1.1.0) + mutex_m (0.3.0) + nenv (0.3.0) + net-ldap (0.20.0) + base64 + ostruct + nkf (0.2.0) + nokogiri (1.18.10-x86_64-linux-gnu) + racc (~> 1.4) + notiffany (0.1.3) + nenv (~> 0.1) + shellany (~> 0.0) + omniauth (2.1.4) + hashie (>= 3.4.6) + logger + rack (>= 2.2.3) + rack-protection + ostruct (0.6.3) + parallel (1.27.0) + parser (3.3.10.0) + ast (~> 2.4.1) + racc + pp (0.6.3) + prettyprint + prettyprint (0.2.0) + prism (1.6.0) + pry (0.15.2) + coderay (~> 1.1) + method_source (~> 1.0) + psych (5.2.6) + date + stringio + public_suffix (6.0.2) + pyu-ruby-sasl (0.0.3.3) + racc (1.8.1) + rack (3.2.4) + rack-protection (4.2.1) + base64 (>= 0.1.0) + logger (>= 1.6.0) + rack (>= 3.0.0, < 4) + rack-test (2.2.0) + rack (>= 1.3) + rainbow (3.1.1) + rake (13.3.1) + rb-fsevent (0.11.2) + rb-inotify (0.11.1) + ffi (~> 1.0) + rbs (3.9.5) + logger + rdoc (6.15.1) + erb + psych (>= 4.0.0) + tsort + reek (6.5.0) + dry-schema (~> 1.13) + logger (~> 1.6) + parser (~> 3.3.0) + rainbow (>= 2.0, < 4.0) + rexml (~> 3.1) + regexp_parser (2.11.3) + reline (0.6.2) + io-console (~> 0.5) + require_bench (1.0.4) + version_gem (>= 1.1.3, < 4) + rexml (3.4.4) + rspec (3.13.2) + rspec-core (~> 3.13.0) + rspec-expectations (~> 3.13.0) + rspec-mocks (~> 3.13.0) + rspec-block_is_expected (1.0.6) + rspec-core (3.13.6) + rspec-support (~> 3.13.0) + rspec-expectations (3.13.5) + diff-lcs (>= 1.2.0, < 2.0) + rspec-support (~> 3.13.0) + rspec-mocks (3.13.7) + diff-lcs (>= 1.2.0, < 2.0) + rspec-support (~> 3.13.0) + rspec-pending_for (0.1.19) + rspec-core (~> 3.0) + ruby_engine (~> 2.0) + ruby_version (~> 1.0) + version_gem (~> 1.1, >= 1.1.8) + rspec-stubbed_env (1.0.4) + rspec-support (3.13.6) + rspec_junit_formatter (0.6.0) + rspec-core (>= 2, < 4, != 2.12.0) + rubocop (1.80.2) + 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.46.0, < 2.0) + ruby-progressbar (~> 1.7) + unicode-display_width (>= 2.4.0, < 4.0) + rubocop-ast (1.47.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 (0.1.1) + rubocop-ruby1_8 (>= 1.0.5, < 2) + standard-rubocop-lts (>= 1.0.3, < 3) + version_gem (>= 1.1.2, < 3) + rubocop-md (1.2.4) + rubocop (>= 1.45) + rubocop-on-rbs (1.8.0) + lint_roller (~> 1.1) + rbs (~> 3.5) + rubocop (>= 1.72.1, < 2.0) + zlib + 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.7.1) + lint_roller (~> 1.1) + rubocop (>= 1.72.1) + rubocop-rspec (3.7.0) + lint_roller (~> 1.1) + rubocop (~> 1.72, >= 1.72.1) + rubocop-ruby1_8 (1.0.7) + 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.18.0) + rubocop (~> 1.62) + rubocop-thread_safety (0.7.3) + lint_roller (~> 1.1) + rubocop (~> 1.72, >= 1.72.1) + rubocop-ast (>= 1.44.0, < 2.0) + ruby-progressbar (1.13.0) + ruby_engine (2.0.3) + ruby_version (1.0.3) + rubyntlm (0.6.5) + base64 + shellany (0.0.1) + silent_stream (1.0.12) + logger (~> 1.2) + version_gem (>= 1.1.8, < 3) + simplecov (0.22.0) + docile (~> 1.1) + simplecov-html (~> 0.11) + simplecov_json_formatter (~> 0.1) + simplecov-cobertura (3.1.0) + rexml + simplecov (~> 0.19) + simplecov-console (0.9.4) + ansi + simplecov + terminal-table + simplecov-html (0.13.2) + simplecov-lcov (0.9.0) + simplecov-rcov (0.3.7) + simplecov (>= 0.4.1) + simplecov_json_formatter (0.1.4) + standard (1.51.1) + language_server-protocol (~> 3.17.0.2) + lint_roller (~> 1.0) + rubocop (~> 1.80.2) + 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) + stone_checksums (1.0.3) + version_gem (~> 1.1, >= 1.1.9) + stringio (3.1.7) + terminal-table (4.0.0) + unicode-display_width (>= 1.1.1, < 4) + thor (1.4.0) + timecop (0.9.10) + timecop-rspec (1.0.3) + delegate (~> 0.1) + rspec (~> 3.0) + timecop (>= 0.7, < 1) + tsort (0.2.0) + unicode-display_width (3.2.0) + unicode-emoji (~> 4.1) + unicode-emoji (4.1.0) + vcr (6.3.1) + base64 + version_gem (1.1.9) + webmock (3.26.1) + addressable (>= 2.8.0) + crack (>= 0.3.2) + hashdiff (>= 0.4.0, < 2.0.0) + yard (0.9.37) + yard-relative_markdown_links (0.5.0) + nokogiri (>= 1.14.3, < 2) + zeitwerk (2.7.3) + zlib (3.2.2) + +PLATFORMS + x86_64-linux-gnu + +DEPENDENCIES + addressable (>= 2.8, < 3) + appraisal2 (~> 3.0) + benchmark (~> 0.4, >= 0.4.1) + bundler-audit (~> 0.9.2) + debug (>= 1.1) + erb (~> 5.0) + gem_bench (~> 2.0, >= 2.0.5) + gitmoji-regex (~> 1.0, >= 1.0.3) + growl + guard + guard-bundler + guard-rspec + irb (~> 1.15, >= 1.15.2) + kettle-dev (~> 1.1) + kettle-soup-cover (~> 1.0, >= 1.0.10) + kettle-test (~> 1.0, >= 1.0.6) + kramdown (~> 2.5, >= 2.5.1) + kramdown-parser-gfm (~> 1.1) + logger (~> 1.7) + mutex_m (~> 0.2) + nkf + omniauth-ldap! + rack-test (~> 2.2) + rake (~> 13.0) + rb-fsevent + rdoc (~> 6.11) + reek (~> 6.5) + require_bench (~> 1.0, >= 1.0.4) + rubocop-lts (~> 0.1) + rubocop-on-rbs (~> 1.8) + rubocop-packaging (~> 0.6, >= 0.6.0) + rubocop-rspec (~> 3.6) + rubocop-ruby1_8 + ruby-progressbar (~> 1.13) + standard (>= 1.50) + stone_checksums (~> 1.0, >= 1.0.2) + stringio (>= 3.0) + vcr (>= 4) + webmock (>= 3) + yard (~> 0.9, >= 0.9.37) + yard-junk (~> 0.0, >= 0.0.10)! + yard-relative_markdown_links (~> 0.5.0) + +BUNDLED WITH + 2.7.2 diff --git a/Guardfile b/Guardfile index df36f0d..875a83d 100644 --- a/Guardfile +++ b/Guardfile @@ -1,11 +1,10 @@ -guard 'rspec', :version => 2 do +guard "rspec", :version => 2 do watch(%r{^spec/.+_spec\.rb$}) - watch(%r{^lib/(.+)\.rb$}) { |m| "spec/#{m[1]}_spec.rb" } - watch('spec/spec_helper.rb') { "spec" } + watch(%r{^lib/(.+)\.rb$}) { |m| "spec/#{m[1]}_spec.rb" } + watch("spec/spec_helper.rb") { "spec" } end - -guard 'bundler' do - watch('Gemfile') +guard "bundler" do + watch("Gemfile") watch(/^.+\.gemspec/) end diff --git a/LICENSE.txt b/LICENSE.txt index ad88e10..1e3c1e9 100644 --- a/LICENSE.txt +++ b/LICENSE.txt @@ -1,7 +1,7 @@ MIT License Copyright (c) 2025 Peter H. Boling, and omniauth-ldap contributors -Copyright (C) 2011 by Ping Yu and Intridea, Inc. +Copyright (c) 2011 by Ping Yu and Intridea, Inc. 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 index 83d7b3c..1f2e179 100644 --- a/README.md +++ b/README.md @@ -1,74 +1,553 @@ -# OmniAuth LDAP +| 📍 NOTE | +|-------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| RubyGems (the [GitHub org][rubygems-org], not the website) [suffered][draper-security] a [hostile takeover][ellen-takeover] in September 2025. | +| Ultimately [4 maintainers][simi-removed] were [hard removed][martin-removed] and a reason has been given for only 1 of those, while 2 others resigned in protest. | +| It is a [complicated story][draper-takeover] which is difficult to [parse quickly][draper-lies]. | +| I'm adding notes like this to gems because I [don't condone theft][draper-theft] of repositories or gems from their rightful owners. | +| If a similar theft happened with my repos/gems, I'd hope some would stand up for me. | +| Disenfranchised former-maintainers have started [gem.coop][gem-coop]. | +| Once available I will publish there exclusively; unless RubyCentral makes amends with the community. | +| The ["Technology for Humans: Joel Draper"][reinteractive-podcast] podcast episode by [reinteractive][reinteractive] is the most cogent summary I'm aware of. | +| See [here][gem-naming], [here][gem-coop] and [here][martin-ann] for more info on what comes next. | +| What I'm doing: A (WIP) proposal for [bundler/gem scopes][gem-scopes], and a (WIP) proposal for a federated [gem server][gem-server]. | -== LDAP +[rubygems-org]: https://github.com/rubygems/ +[draper-security]: https://joel.drapper.me/p/ruby-central-security-measures/ +[draper-takeover]: https://joel.drapper.me/p/ruby-central-takeover/ +[ellen-takeover]: https://pup-e.com/blog/goodbye-rubygems/ +[simi-removed]: https://www.reddit.com/r/ruby/s/gOk42POCaV +[martin-removed]: https://bsky.app/profile/martinemde.com/post/3m3occezxxs2q +[draper-lies]: https://joel.drapper.me/p/ruby-central-fact-check/ +[draper-theft]: https://joel.drapper.me/p/ruby-central/ +[reinteractive]: https://reinteractive.com/ruby-on-rails +[gem-coop]: https://gem.coop +[gem-naming]: https://github.com/gem-coop/gem.coop/issues/12 +[martin-ann]: https://martinemde.com/2025/10/05/announcing-gem-coop.html +[gem-scopes]: https://github.com/galtzo-floss/bundle-namespace +[gem-server]: https://github.com/galtzo-floss/gem-server +[reinteractive-podcast]: https://youtu.be/_H4qbtC5qzU?si=BvuBU90R2wAqD2E6 + +[![Galtzo FLOSS Logo by Aboling0, CC BY-SA 4.0][đŸ–ŧī¸galtzo-i]][đŸ–ŧī¸galtzo-discord] [![ruby-lang Logo, Yukihiro Matsumoto, Ruby Visual Identity Team, CC BY-SA 2.5][đŸ–ŧī¸ruby-lang-i]][đŸ–ŧī¸ruby-lang] [![omniauth-ldap Logo by Aboling0, CC BY-SA 4.0][đŸ–ŧī¸omniauth-ldap-i]][đŸ–ŧī¸omniauth-ldap] + +[đŸ–ŧī¸galtzo-i]: https://logos.galtzo.com/assets/images/galtzo-floss/avatar-192px.svg +[đŸ–ŧī¸galtzo-discord]: https://discord.gg/3qme4XHNKN +[đŸ–ŧī¸ruby-lang-i]: https://logos.galtzo.com/assets/images/ruby-lang/avatar-192px.svg +[đŸ–ŧī¸ruby-lang]: https://www.ruby-lang.org/ +[đŸ–ŧī¸omniauth-ldap-i]: https://logos.galtzo.com/assets/images/omniauth/omniauth-ldap/avatar-192px.svg +[đŸ–ŧī¸omniauth-ldap]: https://github.com/omniauth/omniauth-ldap + +# 📁 OmniAuth LDAP + +[![Version][đŸ‘Ŋversioni]][đŸ‘Ŋversion] [![GitHub tag (latest SemVer)][â›ŗī¸tag-img]][â›ŗī¸tag] [![License: MIT][📄license-img]][📄license-ref] [![Downloads Rank][đŸ‘Ŋdl-ranki]][đŸ‘Ŋdl-rank] [![Open Source Helpers][đŸ‘Ŋoss-helpi]][đŸ‘Ŋoss-help] [![CodeCov Test Coverage][🏀codecovi]][🏀codecov] [![Coveralls Test Coverage][🏀coveralls-img]][🏀coveralls] [![QLTY Test Coverage][🏀qlty-covi]][🏀qlty-cov] [![QLTY Maintainability][🏀qlty-mnti]][🏀qlty-mnt] [![CI Heads][🚎3-hd-wfi]][🚎3-hd-wf] [![CI Runtime Dependencies @ HEAD][🚎12-crh-wfi]][🚎12-crh-wf] [![CI Current][🚎11-c-wfi]][🚎11-c-wf] [![CI Truffle Ruby][🚎9-t-wfi]][🚎9-t-wf] [![CI JRuby][🚎10-j-wfi]][🚎10-j-wf] [![Deps Locked][🚎13-đŸ”’ī¸-wfi]][🚎13-đŸ”’ī¸-wf] [![Deps Unlocked][🚎14-đŸ”“ī¸-wfi]][🚎14-đŸ”“ī¸-wf] [![CI Supported][🚎6-s-wfi]][🚎6-s-wf] [![CI Legacy][🚎4-lg-wfi]][🚎4-lg-wf] [![CI Unsupported][🚎7-us-wfi]][🚎7-us-wf] [![CI Ancient][🚎1-an-wfi]][🚎1-an-wf] [![CI Test Coverage][🚎2-cov-wfi]][🚎2-cov-wf] [![CI Style][🚎5-st-wfi]][🚎5-st-wf] [![CodeQL][🖐codeQL-img]][🖐codeQL] [![Apache SkyWalking Eyes License Compatibility Check][🚎15-đŸĒĒ-wfi]][🚎15-đŸĒĒ-wf] + +`if ci_badges.map(&:color).detect { it != "green"}` â˜ī¸ [let me know][đŸ–ŧī¸galtzo-discord], as I may have missed the [discord notification][đŸ–ŧī¸galtzo-discord]. + +--- + +`if ci_badges.map(&:color).all? { it == "green"}` đŸ‘‡ī¸ send money so I can do more of this. FLOSS maintenance is now my full-time job. + +[![Sponsor Me on Github][🖇sponsor-img]][🖇sponsor] [![Liberapay Goal Progress][â›ŗliberapay-img]][â›ŗliberapay] [![Donate on PayPal][🖇paypal-img]][🖇paypal] [![Buy me a coffee][🖇buyme-small-img]][🖇buyme] [![Donate on Polar][🖇polar-img]][🖇polar] [![Donate at ko-fi.com][🖇kofi-img]][🖇kofi] + +## đŸŒģ Synopsis Use the LDAP strategy as a middleware in your application: - use OmniAuth::Strategies::LDAP, - :title => "My LDAP", - :host => '10.101.10.1', - :port => 389, - :method => :plain, - :base => 'dc=intridea, dc=com', - :uid => 'sAMAccountName', - :name_proc => Proc.new {|name| name.gsub(/@.*$/,'')}, - :bind_dn => 'default_bind_dn', - # Or, alternatively: - #:filter => '(&(uid=%{username})(memberOf=cn=myapp-users,ou=groups,dc=example,dc=com))' - :name_proc => Proc.new {|name| name.gsub(/@.*$/,'')} - :bind_dn => 'default_bind_dn' - :password => 'password' - -All of the listed options are required, with the exception of :title, :name_proc, :bind_dn, and :password. -Allowed values of :method are: :plain, :ssl, :tls. - -:bind_dn and :password is the default credentials to perform user lookup. - most LDAP servers require that you supply a complete DN as a binding-credential, along with an authenticator - such as a password. But for many applications, you often don’t have a full DN to identify the user. - You usually get a simple identifier like a username or an email address, along with a password. - Since many LDAP servers don't allow anonymous access, search function will require a bound connection, - :bind_dn and :password will be required for searching on the username or email to retrieve the DN attribute - for the user. If the LDAP server allows anonymous access, you don't need to provide these two parameters. - -:uid is the LDAP attribute name for the user name in the login form. - typically AD would be 'sAMAccountName' or 'UserPrincipalName', while OpenLDAP is 'uid'. - -:filter is the LDAP filter used to search the user entry. It can be used in place of :uid for more flexibility. - `%{username}` will be replaced by the user name processed by :name_proc. - -:name_proc allows you to match the user name entered with the format of the :uid attributes. - For example, value of 'sAMAccountName' in AD contains only the windows user name. If your user prefers using - email to login, a name_proc as above will trim the email string down to just the windows login name. - In summary, use :name_proc to fill the gap between the submitted username and LDAP uid attribute value. - -:try_sasl and :sasl_mechanisms are optional. :try_sasl [true | false], :sasl_mechanisms ['DIGEST-MD5' | 'GSS-SPNEGO'] - Use them to initialize a SASL connection to server. If you are not familiar with these authentication methods, - please just avoid them. - -Direct users to '/auth/ldap' to have them authenticated via your company's LDAP server. - - -## License - -MIT License - -Copyright (c) 2025 Peter H. Boling, and omniauth-ldap contributors -Copyright (C) 2011 by Ping Yu and Intridea, Inc. - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in -all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -THE SOFTWARE. +```ruby +use OmniAuth::Strategies::LDAP, + :title => "My LDAP", + :host => '10.101.10.1', + :port => 389, + :method => :plain, + :base => 'dc=intridea, dc=com', + :uid => 'sAMAccountName', + :name_proc => Proc.new {|name| name.gsub(/@.*$/,'')}, + :bind_dn => 'default_bind_dn', + # Or, alternatively: + #:filter => '(&(uid=%{username})(memberOf=cn=myapp-users,ou=groups,dc=example,dc=com))' + :name_proc => Proc.new {|name| name.gsub(/@.*$/,'')} + :bind_dn => 'default_bind_dn' + :password => 'password' +``` + +All of the listed options are required, with the exception of `:title`, `:name_proc`, `:bind_dn`, and `:password`. + +## 💡 Info you can shake a stick at + +| Tokens to Remember | [![Gem name][â›ŗī¸name-img]][â›ŗī¸gem-name] [![Gem namespace][â›ŗī¸namespace-img]][â›ŗī¸gem-namespace] | +|-------------------------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| Works with JRuby | ![JRuby 9.1 Compat][💎jruby-9.1i] ![JRuby 9.2 Compat][💎jruby-9.2i] ![JRuby 9.3 Compat][💎jruby-9.3i]
[![JRuby 9.4 Compat][💎jruby-9.4i]][🚎10-j-wf] [![JRuby 10.0 Compat][💎jruby-c-i]][🚎11-c-wf] [![JRuby HEAD Compat][💎jruby-headi]][🚎3-hd-wf] | +| Works with Truffle Ruby | ![Truffle Ruby 22.3 Compat][💎truby-22.3i] ![Truffle Ruby 23.0 Compat][💎truby-23.0i]
[![Truffle Ruby 23.1 Compat][💎truby-23.1i]][🚎9-t-wf] [![Truffle Ruby 24.1 Compat][💎truby-c-i]][🚎11-c-wf] | +| Works with MRI Ruby 3 | [![Ruby 3.0 Compat][💎ruby-3.0i]][🚎4-lg-wf] [![Ruby 3.1 Compat][💎ruby-3.1i]][🚎6-s-wf] [![Ruby 3.2 Compat][💎ruby-3.2i]][🚎6-s-wf] [![Ruby 3.3 Compat][💎ruby-3.3i]][🚎6-s-wf] [![Ruby 3.4 Compat][💎ruby-c-i]][🚎11-c-wf] [![Ruby HEAD Compat][💎ruby-headi]][🚎3-hd-wf] | +| Works with MRI Ruby 2 | ![Ruby 2.0 Compat][💎ruby-2.0i] ![Ruby 2.1 Compat][💎ruby-2.1i] ![Ruby 2.2 Compat][💎ruby-2.2i]
[![Ruby 2.3 Compat][💎ruby-2.3i]][🚎1-an-wf] [![Ruby 2.4 Compat][💎ruby-2.4i]][🚎1-an-wf] [![Ruby 2.5 Compat][💎ruby-2.5i]][🚎1-an-wf] [![Ruby 2.6 Compat][💎ruby-2.6i]][🚎7-us-wf] [![Ruby 2.7 Compat][💎ruby-2.7i]][🚎7-us-wf] | +| Works with MRI Ruby 1 | ![Ruby 1.8 Compat][💎ruby-1.8i] ![Ruby 1.9 Compat][💎ruby-1.9i] | +| Support & Community | [![Join Me on Daily.dev's RubyFriends][âœ‰ī¸ruby-friends-img]][âœ‰ī¸ruby-friends] [![Live Chat on Discord][âœ‰ī¸discord-invite-img-ftb]][âœ‰ī¸discord-invite] [![Get help from me on Upwork][👨đŸŧ‍đŸĢexpsup-upwork-img]][👨đŸŧ‍đŸĢexpsup-upwork] [![Get help from me on Codementor][👨đŸŧ‍đŸĢexpsup-codementor-img]][👨đŸŧ‍đŸĢexpsup-codementor] | +| Source | [![Source on Github.com][📜src-gh-img]][📜src-gh] [![The best SHA: dQw4w9WgXcQ!][🧮kloc-img]][🧮kloc] | +| Documentation | [![Current release on RubyDoc.info][📜docs-cr-rd-img]][🚎yard-current] [![YARD on Galtzo.com][📜docs-head-rd-img]][🚎yard-head] [![Maintainer Blog][🚂maint-blog-img]][🚂maint-blog] [![GitHub Wiki][📜gh-wiki-img]][📜gh-wiki] | +| Compliance | [![License: MIT][📄license-img]][📄license-ref] [![Compatible with Apache Software Projects: Verified by SkyWalking Eyes][📄license-compat-img]][📄license-compat] [![📄ilo-declaration-img]][📄ilo-declaration] [![Security Policy][🔐security-img]][🔐security] [![Contributor Covenant 2.1][đŸĒ‡conduct-img]][đŸĒ‡conduct] [![SemVer 2.0.0][📌semver-img]][📌semver] | +| Style | [![Enforced Code Style Linter][💎rlts-img]][💎rlts] [![Keep-A-Changelog 1.0.0][📗keep-changelog-img]][📗keep-changelog] [![Gitmoji Commits][📌gitmoji-img]][📌gitmoji] [![Compatibility appraised by: appraisal2][💎appraisal2-img]][💎appraisal2] | +| Maintainer đŸŽ–ī¸ | [![Follow Me on LinkedIn][💖🖇linkedin-img]][💖🖇linkedin] [![Follow Me on Ruby.Social][💖🐘ruby-mast-img]][💖🐘ruby-mast] [![Follow Me on Bluesky][💖đŸĻ‹bluesky-img]][💖đŸĻ‹bluesky] [![Contact Maintainer][🚂maint-contact-img]][🚂maint-contact] [![My technical writing][💖💁đŸŧâ€â™‚ī¸devto-img]][💖💁đŸŧâ€â™‚ī¸devto] | +| `...` 💖 | [![Find Me on WellFound:][đŸ’–âœŒī¸wellfound-img]][đŸ’–âœŒī¸wellfound] [![Find Me on CrunchBase][💖💲crunchbase-img]][💖💲crunchbase] [![My LinkTree][đŸ’–đŸŒŗlinktree-img]][đŸ’–đŸŒŗlinktree] [![More About Me][💖💁đŸŧâ€â™‚ī¸aboutme-img]][💖💁đŸŧâ€â™‚ī¸aboutme] [🧊][💖🧊berg] [🐙][💖🐙hub] [🛖][💖🛖hut] [đŸ§Ē][💖đŸ§Ēlab] | + +### Compatibility + +Compatible with MRI Ruby 0+, and concordant releases of JRuby, and TruffleRuby. + +| 🚚 _Amazing_ test matrix was brought to you by | 🔎 appraisal2 🔎 and the color 💚 green 💚 | +|------------------------------------------------|--------------------------------------------------------| +| 👟 Check it out! | ✨ [github.com/appraisal-rb/appraisal2][💎appraisal2] ✨ | + +### Enterprise Support [![Tidelift](https://tidelift.com/badges/package/rubygems/omniauth-ldap)](https://tidelift.com/subscription/pkg/rubygems-omniauth-ldap?utm_source=rubygems-omniauth-ldap&utm_medium=referral&utm_campaign=readme) + +Available as part of the Tidelift Subscription. + +
+ Need enterprise-level guarantees? + +The maintainers of this and thousands of other packages are working with Tidelift to deliver commercial support and maintenance for the open source packages you use to build your applications. Save time, reduce risk, and improve code health, while paying the maintainers of the exact packages you use. + +[![Get help from me on Tidelift][đŸ™ī¸entsup-tidelift-img]][đŸ™ī¸entsup-tidelift] + +- 💡Subscribe for support guarantees covering _all_ your FLOSS dependencies +- 💡Tidelift is part of [Sonar][đŸ™ī¸entsup-tidelift-sonar] +- 💡Tidelift pays maintainers to maintain the software you depend on!
📊`@`Pointy Haired Boss: An [enterprise support][đŸ™ī¸entsup-tidelift] subscription is "[never gonna let you down][🧮kloc]", and *supports* open source maintainers + +Alternatively: + +- [![Live Chat on Discord][âœ‰ī¸discord-invite-img-ftb]][âœ‰ī¸discord-invite] +- [![Get help from me on Upwork][👨đŸŧ‍đŸĢexpsup-upwork-img]][👨đŸŧ‍đŸĢexpsup-upwork] +- [![Get help from me on Codementor][👨đŸŧ‍đŸĢexpsup-codementor-img]][👨đŸŧ‍đŸĢexpsup-codementor] + +
+ +## ✨ Installation + +Install the gem and add to the application's Gemfile by executing: + +```console +bundle add omniauth-ldap +``` + +If bundler is not being used to manage dependencies, install the gem by executing: + +```console +gem install omniauth-ldap +``` + +### 🔒 Secure Installation + +
+ For Medium or High Security Installations + +This gem is cryptographically signed, and has verifiable [SHA-256 and SHA-512][💎SHA_checksums] checksums by +[stone_checksums][💎stone_checksums]. Be sure the gem you install hasn’t been tampered with +by following the instructions below. + +Add my public key (if you haven’t already, expires 2045-04-29) as a trusted certificate: + +```console +gem cert --add <(curl -Ls https://raw.github.com/galtzo-floss/certs/main/pboling.pem) +``` + +You only need to do that once. Then proceed to install with: + +```console +gem install omniauth-ldap -P HighSecurity +``` + +The `HighSecurity` trust profile will verify signed gems, and not allow the installation of unsigned dependencies. + +If you want to up your security game full-time: + +```console +bundle config set --global trust-policy MediumSecurity +``` + +`MediumSecurity` instead of `HighSecurity` is necessary if not all the gems you use are signed. + +NOTE: Be prepared to track down certs for signed gems and add them the same way you added mine. + +
+ +## âš™ī¸ Configuration + + + +## 🔧 Basic Usage + +Allowed values of `:method` are: `:plain`, `:ssl`, `:tls`. + +`:bind_dn` and `:password` is the default credentials to perform user lookup. +Most LDAP servers require that you supply a complete DN as a binding-credential, along with an authenticator +such as a password. But for many applications, you often don’t have a full DN to identify the user. +You usually get a simple identifier like a username or an email address, along with a password. +Since many LDAP servers don't allow anonymous access, search function will require a bound connection, +`:bind_dn` and `:password` will be required for searching on the username or email to retrieve the DN attribute +for the user. If the LDAP server allows anonymous access, you don't need to provide these two parameters. + +`:uid` is the LDAP attribute name for the username in the login form. +typically AD would be 'sAMAccountName' or 'UserPrincipalName', while OpenLDAP is 'uid'. + +`:filter` is the LDAP filter used to search the user entry. It can be used in place of :uid for more flexibility. +`%{username}` will be replaced by the username processed by `:name_proc`. + +`:name_proc` allows you to match the username entered with the format of the `:uid` attributes. +For example, value of 'sAMAccountName' in AD contains only the windows username. If your user prefers using +email to login, a `:name_proc` as above will trim the email string down to just the windows login name. +In summary, use `:name_proc` to fill the gap between the submitted username and LDAP uid attribute value. + +`:try_sasl` and `:sasl_mechanisms` are optional. Valid values are: +- `:try_sasl` => `true` or `false` +- `:sasl_mechanisms` => `"DIGEST-MD5"` or `"GSS-SPNEGO"` +Use them to initialize a SASL connection to server. If you are not familiar with these authentication methods, +please just avoid them. + +Direct users to `/auth/ldap` to have them authenticated via your company's LDAP server. + +## đŸĻˇ FLOSS Funding + +While these tools are free software and will always be, the project would benefit immensely from some funding. +Raising a monthly budget of... "dollars" would make the project more sustainable. + +We welcome both individual and corporate sponsors! We also offer a +wide array of funding channels to account for your preferences. +Currently, [GitHub Sponsors][🖇sponsor], and [Liberapay][â›ŗliberapay] are our preferred funding platforms. + +**If you're working in a company that's making significant use of omniauth tools we'd +appreciate it if you suggest to your company to become a omniauth sponsor.** + +You can support me in development of OmniAuth tools via +[GitHub Sponsors][🖇sponsor], +[Liberapay][â›ŗliberapay], +[PayPal][🖇paypal], +and [Tidelift][đŸ™ī¸entsup-tidelift]. + +| 📍 NOTE | +|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| If doing a sponsorship in the form of donation is problematic for your company
from an accounting standpoint, we'd recommend the use of Tidelift,
where you can get a support-like subscription instead. | + +### Another way to support open-source + +I’m driven by a passion to foster a thriving open-source community – a space where people can tackle complex problems, no matter how small. Revitalizing libraries that have fallen into disrepair, and building new libraries focused on solving real-world challenges, are my passions. I was recently affected by layoffs, and the tech jobs market is unwelcoming. I’m reaching out here because your support would significantly aid my efforts to provide for my family, and my farm (11 🐔 chickens, 2 đŸļ dogs, 3 🐰 rabbits, 8 🐈‍ cats). + +If you work at a company that uses my work, please encourage them to support me as a corporate sponsor. My work on gems you use might show up in `bundle fund`. + +I’m developing a new library, [floss_funding][🖇floss-funding-gem], designed to empower open-source developers like myself to get paid for the work we do, in a sustainable way. Please give it a look. + +**[Floss-Funding.dev][🖇floss-funding.dev]: đŸ‘‰ī¸ No network calls. đŸ‘‰ī¸ No tracking. đŸ‘‰ī¸ No oversight. đŸ‘‰ī¸ Minimal crypto hashing. 💡 Easily disabled nags** + +[![Sponsor Me on Github][🖇sponsor-img]][🖇sponsor] [![Liberapay Goal Progress][â›ŗliberapay-img]][â›ŗliberapay] [![Donate on PayPal][🖇paypal-img]][🖇paypal] [![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] + +## 🔐 Security + +See [SECURITY.md][🔐security]. + +## 🤝 Contributing + +If you need some ideas of where to help, you could work on adding more code coverage, +or if it is already đŸ’¯ (see [below](#code-coverage)) check [reek](REEK), [issues][🤝gh-issues], or [PRs][🤝gh-pulls], +or use the gem and think about how it could be better. + +We [![Keep A Changelog][📗keep-changelog-img]][📗keep-changelog] so if you make changes, remember to update it. + +See [CONTRIBUTING.md][🤝contributing] for more detailed instructions. + +### 🚀 Release Instructions + +See [CONTRIBUTING.md][🤝contributing]. + +### Code Coverage + +[![Coverage Graph][🏀codecov-g]][🏀codecov] + +[![Coveralls Test Coverage][🏀coveralls-img]][🏀coveralls] + +[![QLTY Test Coverage][🏀qlty-covi]][🏀qlty-cov] + +### đŸĒ‡ Code of Conduct + +Everyone interacting with this project's codebases, issue trackers, +chat rooms and mailing lists agrees to follow the [![Contributor Covenant 2.1][đŸĒ‡conduct-img]][đŸĒ‡conduct]. + +## 🌈 Contributors + +[![Contributors][🖐contributors-img]][🖐contributors] + +Made with [contributors-img][🖐contrib-rocks]. + +
+ â­ī¸ Star History + + + + + + Star History Chart + + + +
+ +## 📌 Versioning + +This Library adheres to [![Semantic Versioning 2.0.0][📌semver-img]][📌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. + +> dropping support for a platform is both obviously and objectively a breaking change
+>—Jordan Harband ([@ljharb](https://github.com/ljharb), maintainer of SemVer) [in SemVer issue 716][📌semver-breaking] + +I understand that policy doesn't work universally ("exceptions to every rule!"), +but it is the policy here. +As such, in many cases it is good to specify a dependency on this library using +the [Pessimistic Version Constraint][📌pvc] with two digits of precision. + +For example: + +```ruby +spec.add_dependency("omniauth-ldap", "~> 1.0") +``` + +
+📌 Is "Platform Support" part of the public API? More details inside. + +SemVer should, IMO, but doesn't explicitly, say that dropping support for specific Platforms +is a *breaking change* to an API. +It is obvious to many, but not all, and since the spec is silent, the bike shedding is endless. + +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] + +
+ +See [CHANGELOG.md][📌changelog] for a list of releases. + +## 📄 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]. + +### © Copyright + +
    +
  • + Copyright (c) 2023, 2025 Peter H. Boling, of + + Galtzo.com + + Galtzo.com Logo (Wordless) by Aboling0, CC BY-SA 4.0 + + , and omniauth-ldap contributors. +
  • +
+ +## 🤑 A request for help + +Maintainers have teeth and need to pay their dentists. +After getting laid off in an RIF in March, and encountering difficulty finding a new one, +I began spending most of my time building open source tools. +I'm hoping to be able to pay for my kids' health insurance this month, +so if you value the work I am doing, I need your support. +Please consider sponsoring me or the project. + +To join the community or get help đŸ‘‡ī¸ Join the Discord. + +[![Live Chat on Discord][âœ‰ī¸discord-invite-img-ftb]][âœ‰ī¸discord-invite] + +To say "thanks!" â˜ī¸ Join the Discord or đŸ‘‡ī¸ send money. + +[![Sponsor me on GitHub Sponsors][🖇sponsor-bottom-img]][🖇sponsor] 💌 [![Sponsor me on Liberapay][â›ŗliberapay-bottom-img]][â›ŗliberapay-img] 💌 [![Donate on PayPal][🖇paypal-bottom-img]][🖇paypal-img] + +### Please give the project a star ⭐ â™Ĩ. + +Thanks for RTFM. â˜ēī¸ + +[â›ŗliberapay-img]: https://img.shields.io/liberapay/goal/pboling.svg?logo=liberapay&color=a51611&style=flat +[â›ŗliberapay-bottom-img]: https://img.shields.io/liberapay/goal/pboling.svg?style=for-the-badge&logo=liberapay&color=a51611 +[â›ŗliberapay]: https://liberapay.com/pboling/donate +[🖇sponsor-img]: https://img.shields.io/badge/Sponsor_Me!-pboling.svg?style=social&logo=github +[🖇sponsor-bottom-img]: https://img.shields.io/badge/Sponsor_Me!-pboling-blue?style=for-the-badge&logo=github +[🖇sponsor]: https://github.com/sponsors/pboling +[🖇polar-img]: https://img.shields.io/badge/polar-donate-a51611.svg?style=flat +[🖇polar]: https://polar.sh/pboling +[🖇kofi-img]: https://img.shields.io/badge/ko--fi-%E2%9C%93-a51611.svg?style=flat +[🖇kofi]: https://ko-fi.com/O5O86SNP4 +[🖇patreon-img]: https://img.shields.io/badge/patreon-donate-a51611.svg?style=flat +[🖇patreon]: https://patreon.com/galtzo +[🖇buyme-small-img]: https://img.shields.io/badge/buy_me_a_coffee-%E2%9C%93-a51611.svg?style=flat +[🖇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 +[🖇paypal-img]: https://img.shields.io/badge/donate-paypal-a51611.svg?style=flat&logo=paypal +[🖇paypal-bottom-img]: https://img.shields.io/badge/donate-paypal-a51611.svg?style=for-the-badge&logo=paypal&color=0A0A0A +[🖇paypal]: https://www.paypal.com/paypalme/peterboling +[🖇floss-funding.dev]: https://floss-funding.dev +[🖇floss-funding-gem]: https://github.com/galtzo-floss/floss_funding +[âœ‰ī¸discord-invite]: https://discord.gg/3qme4XHNKN +[âœ‰ī¸discord-invite-img-ftb]: https://img.shields.io/discord/1373797679469170758?style=for-the-badge&logo=discord +[âœ‰ī¸ruby-friends-img]: https://img.shields.io/badge/daily.dev-%F0%9F%92%8E_Ruby_Friends-0A0A0A?style=for-the-badge&logo=dailydotdev&logoColor=white +[âœ‰ī¸ruby-friends]: https://app.daily.dev/squads/rubyfriends + +[✇bundle-group-pattern]: https://gist.github.com/pboling/4564780 +[â›ŗī¸gem-namespace]: https://github.com/omniauth/omniauth-ldap +[â›ŗī¸namespace-img]: https://img.shields.io/badge/namespace-Omniauth::Ldap-3C2D2D.svg?style=square&logo=ruby&logoColor=white +[â›ŗī¸gem-name]: https://bestgems.org/gems/omniauth-ldap +[â›ŗī¸name-img]: https://img.shields.io/badge/name-omniauth--ldap-3C2D2D.svg?style=square&logo=rubygems&logoColor=red +[â›ŗī¸tag-img]: https://img.shields.io/github/tag/omniauth/omniauth-ldap.svg +[â›ŗī¸tag]: http://github.com/omniauth/omniauth-ldap/releases +[🚂maint-blog]: http://www.railsbling.com/tags/omniauth-ldap +[🚂maint-blog-img]: https://img.shields.io/badge/blog-railsbling-0093D0.svg?style=for-the-badge&logo=rubyonrails&logoColor=orange +[🚂maint-contact]: http://www.railsbling.com/contact +[🚂maint-contact-img]: https://img.shields.io/badge/Contact-Maintainer-0093D0.svg?style=flat&logo=rubyonrails&logoColor=red +[💖🖇linkedin]: http://www.linkedin.com/in/peterboling +[💖🖇linkedin-img]: https://img.shields.io/badge/PeterBoling-LinkedIn-0B66C2?style=flat&logo=newjapanprowrestling +[đŸ’–âœŒī¸wellfound]: https://wellfound.com/u/peter-boling +[đŸ’–âœŒī¸wellfound-img]: https://img.shields.io/badge/peter--boling-orange?style=flat&logo=wellfound +[💖💲crunchbase]: https://www.crunchbase.com/person/peter-boling +[💖💲crunchbase-img]: https://img.shields.io/badge/peter--boling-purple?style=flat&logo=crunchbase +[💖🐘ruby-mast]: https://ruby.social/@galtzo +[💖🐘ruby-mast-img]: https://img.shields.io/mastodon/follow/109447111526622197?domain=https://ruby.social&style=flat&logo=mastodon&label=Ruby%20@galtzo +[💖đŸĻ‹bluesky]: https://bsky.app/profile/galtzo.com +[💖đŸĻ‹bluesky-img]: https://img.shields.io/badge/@galtzo.com-0285FF?style=flat&logo=bluesky&logoColor=white +[đŸ’–đŸŒŗlinktree]: https://linktr.ee/galtzo +[đŸ’–đŸŒŗlinktree-img]: https://img.shields.io/badge/galtzo-purple?style=flat&logo=linktree +[💖💁đŸŧâ€â™‚ī¸devto]: https://dev.to/galtzo +[💖💁đŸŧâ€â™‚ī¸devto-img]: https://img.shields.io/badge/dev.to-0A0A0A?style=flat&logo=devdotto&logoColor=white +[💖💁đŸŧâ€â™‚ī¸aboutme]: https://about.me/peter.boling +[💖💁đŸŧâ€â™‚ī¸aboutme-img]: https://img.shields.io/badge/about.me-0A0A0A?style=flat&logo=aboutme&logoColor=white +[💖🧊berg]: https://codeberg.org/pboling +[💖🐙hub]: https://github.org/pboling +[💖🛖hut]: https://sr.ht/~galtzo/ +[💖đŸ§Ēlab]: https://gitlab.com/pboling +[👨đŸŧ‍đŸĢexpsup-upwork]: https://www.upwork.com/freelancers/~014942e9b056abdf86?mp_source=share +[👨đŸŧ‍đŸĢexpsup-upwork-img]: https://img.shields.io/badge/UpWork-13544E?style=for-the-badge&logo=Upwork&logoColor=white +[👨đŸŧ‍đŸĢexpsup-codementor]: https://www.codementor.io/peterboling?utm_source=github&utm_medium=button&utm_term=peterboling&utm_campaign=github +[👨đŸŧ‍đŸĢexpsup-codementor-img]: https://img.shields.io/badge/CodeMentor-Get_Help-1abc9c?style=for-the-badge&logo=CodeMentor&logoColor=white +[đŸ™ī¸entsup-tidelift]: https://tidelift.com/subscription/pkg/rubygems-omniauth-ldap?utm_source=rubygems-omniauth-ldap&utm_medium=referral&utm_campaign=readme +[đŸ™ī¸entsup-tidelift-img]: https://img.shields.io/badge/Tidelift_and_Sonar-Enterprise_Support-FD3456?style=for-the-badge&logo=sonar&logoColor=white +[đŸ™ī¸entsup-tidelift-sonar]: https://blog.tidelift.com/tidelift-joins-sonar +[💁đŸŧâ€â™‚ī¸peterboling]: http://www.peterboling.com +[🚂railsbling]: http://www.railsbling.com +[📜src-cb]: https://codeberg.org/omniauth/omniauth-ldap +[📜src-gh-img]: https://img.shields.io/badge/GitHub-238636?style=for-the-badge&logo=Github&logoColor=green +[📜src-gh]: https://github.com/omniauth/omniauth-ldap +[📜docs-cr-rd-img]: https://img.shields.io/badge/RubyDoc-Current_Release-943CD2?style=for-the-badge&logo=readthedocs&logoColor=white +[📜docs-head-rd-img]: https://img.shields.io/badge/YARD_on_Galtzo.com-HEAD-943CD2?style=for-the-badge&logo=readthedocs&logoColor=white +[📜gh-wiki]: https://github.com/omniauth/omniauth-ldap/wiki +[📜gh-wiki-img]: https://img.shields.io/badge/wiki-examples-943CD2.svg?style=for-the-badge&logo=github&logoColor=white +[đŸ‘Ŋdl-rank]: https://bestgems.org/gems/omniauth-ldap +[đŸ‘Ŋdl-ranki]: https://img.shields.io/gem/rd/omniauth-ldap.svg +[đŸ‘Ŋoss-help]: https://www.codetriage.com/omniauth/omniauth-ldap +[đŸ‘Ŋoss-helpi]: https://www.codetriage.com/omniauth/omniauth-ldap/badges/users.svg +[đŸ‘Ŋversion]: https://bestgems.org/gems/omniauth-ldap +[đŸ‘Ŋversioni]: https://img.shields.io/gem/v/omniauth-ldap.svg +[🏀qlty-mnt]: https://qlty.sh/gh/omniauth/projects/omniauth-ldap +[🏀qlty-mnti]: https://qlty.sh/gh/omniauth/projects/omniauth-ldap/maintainability.svg +[🏀qlty-cov]: https://qlty.sh/gh/omniauth/projects/omniauth-ldap/metrics/code?sort=coverageRating +[🏀qlty-covi]: https://qlty.sh/gh/omniauth/projects/omniauth-ldap/coverage.svg +[🏀codecov]: https://codecov.io/gh/omniauth/omniauth-ldap +[🏀codecovi]: https://codecov.io/gh/omniauth/omniauth-ldap/graph/badge.svg +[🏀coveralls]: https://coveralls.io/github/omniauth/omniauth-ldap?branch=main +[🏀coveralls-img]: https://coveralls.io/repos/github/omniauth/omniauth-ldap/badge.svg?branch=main +[🖐codeQL]: https://github.com/omniauth/omniauth-ldap/security/code-scanning +[🖐codeQL-img]: https://github.com/omniauth/omniauth-ldap/actions/workflows/codeql-analysis.yml/badge.svg +[🚎1-an-wf]: https://github.com/omniauth/omniauth-ldap/actions/workflows/ancient.yml +[🚎1-an-wfi]: https://github.com/omniauth/omniauth-ldap/actions/workflows/ancient.yml/badge.svg +[🚎2-cov-wf]: https://github.com/omniauth/omniauth-ldap/actions/workflows/coverage.yml +[🚎2-cov-wfi]: https://github.com/omniauth/omniauth-ldap/actions/workflows/coverage.yml/badge.svg +[🚎3-hd-wf]: https://github.com/omniauth/omniauth-ldap/actions/workflows/heads.yml +[🚎3-hd-wfi]: https://github.com/omniauth/omniauth-ldap/actions/workflows/heads.yml/badge.svg +[🚎4-lg-wf]: https://github.com/omniauth/omniauth-ldap/actions/workflows/legacy.yml +[🚎4-lg-wfi]: https://github.com/omniauth/omniauth-ldap/actions/workflows/legacy.yml/badge.svg +[🚎5-st-wf]: https://github.com/omniauth/omniauth-ldap/actions/workflows/style.yml +[🚎5-st-wfi]: https://github.com/omniauth/omniauth-ldap/actions/workflows/style.yml/badge.svg +[🚎6-s-wf]: https://github.com/omniauth/omniauth-ldap/actions/workflows/supported.yml +[🚎6-s-wfi]: https://github.com/omniauth/omniauth-ldap/actions/workflows/supported.yml/badge.svg +[🚎7-us-wf]: https://github.com/omniauth/omniauth-ldap/actions/workflows/unsupported.yml +[🚎7-us-wfi]: https://github.com/omniauth/omniauth-ldap/actions/workflows/unsupported.yml/badge.svg +[🚎8-ho-wf]: https://github.com/omniauth/omniauth-ldap/actions/workflows/hoary.yml +[🚎8-ho-wfi]: https://github.com/omniauth/omniauth-ldap/actions/workflows/hoary.yml/badge.svg +[🚎9-t-wf]: https://github.com/omniauth/omniauth-ldap/actions/workflows/truffle.yml +[🚎9-t-wfi]: https://github.com/omniauth/omniauth-ldap/actions/workflows/truffle.yml/badge.svg +[🚎10-j-wf]: https://github.com/omniauth/omniauth-ldap/actions/workflows/jruby.yml +[🚎10-j-wfi]: https://github.com/omniauth/omniauth-ldap/actions/workflows/jruby.yml/badge.svg +[🚎11-c-wf]: https://github.com/omniauth/omniauth-ldap/actions/workflows/current.yml +[🚎11-c-wfi]: https://github.com/omniauth/omniauth-ldap/actions/workflows/current.yml/badge.svg +[🚎12-crh-wf]: https://github.com/omniauth/omniauth-ldap/actions/workflows/dep-heads.yml +[🚎12-crh-wfi]: https://github.com/omniauth/omniauth-ldap/actions/workflows/dep-heads.yml/badge.svg +[🚎13-đŸ”’ī¸-wf]: https://github.com/omniauth/omniauth-ldap/actions/workflows/locked_deps.yml +[🚎13-đŸ”’ī¸-wfi]: https://github.com/omniauth/omniauth-ldap/actions/workflows/locked_deps.yml/badge.svg +[🚎14-đŸ”“ī¸-wf]: https://github.com/omniauth/omniauth-ldap/actions/workflows/unlocked_deps.yml +[🚎14-đŸ”“ī¸-wfi]: https://github.com/omniauth/omniauth-ldap/actions/workflows/unlocked_deps.yml/badge.svg +[🚎15-đŸĒĒ-wf]: https://github.com/omniauth/omniauth-ldap/actions/workflows/license-eye.yml +[🚎15-đŸĒĒ-wfi]: https://github.com/omniauth/omniauth-ldap/actions/workflows/license-eye.yml/badge.svg +[💎ruby-1.8i]: https://img.shields.io/badge/Ruby-1.8_(%F0%9F%9A%ABCI)-AABBCC?style=for-the-badge&logo=ruby&logoColor=white +[💎ruby-1.9i]: https://img.shields.io/badge/Ruby-1.9_(%F0%9F%9A%ABCI)-AABBCC?style=for-the-badge&logo=ruby&logoColor=white +[💎ruby-2.0i]: https://img.shields.io/badge/Ruby-2.0_(%F0%9F%9A%ABCI)-AABBCC?style=for-the-badge&logo=ruby&logoColor=white +[💎ruby-2.1i]: https://img.shields.io/badge/Ruby-2.1_(%F0%9F%9A%ABCI)-AABBCC?style=for-the-badge&logo=ruby&logoColor=white +[💎ruby-2.2i]: https://img.shields.io/badge/Ruby-2.2_(%F0%9F%9A%ABCI)-AABBCC?style=for-the-badge&logo=ruby&logoColor=white +[💎ruby-2.3i]: https://img.shields.io/badge/Ruby-2.3-DF00CA?style=for-the-badge&logo=ruby&logoColor=white +[💎ruby-2.4i]: https://img.shields.io/badge/Ruby-2.4-DF00CA?style=for-the-badge&logo=ruby&logoColor=white +[💎ruby-2.5i]: https://img.shields.io/badge/Ruby-2.5-DF00CA?style=for-the-badge&logo=ruby&logoColor=white +[💎ruby-2.6i]: https://img.shields.io/badge/Ruby-2.6-DF00CA?style=for-the-badge&logo=ruby&logoColor=white +[💎ruby-2.7i]: https://img.shields.io/badge/Ruby-2.7-DF00CA?style=for-the-badge&logo=ruby&logoColor=white +[💎ruby-3.0i]: https://img.shields.io/badge/Ruby-3.0-CC342D?style=for-the-badge&logo=ruby&logoColor=white +[💎ruby-3.1i]: https://img.shields.io/badge/Ruby-3.1-CC342D?style=for-the-badge&logo=ruby&logoColor=white +[💎ruby-3.2i]: https://img.shields.io/badge/Ruby-3.2-CC342D?style=for-the-badge&logo=ruby&logoColor=white +[💎ruby-3.3i]: https://img.shields.io/badge/Ruby-3.3-CC342D?style=for-the-badge&logo=ruby&logoColor=white +[💎ruby-c-i]: https://img.shields.io/badge/Ruby-current-CC342D?style=for-the-badge&logo=ruby&logoColor=green +[💎ruby-headi]: https://img.shields.io/badge/Ruby-HEAD-CC342D?style=for-the-badge&logo=ruby&logoColor=blue +[💎truby-22.3i]: https://img.shields.io/badge/Truffle_Ruby-22.3_(%F0%9F%9A%ABCI)-AABBCC?style=for-the-badge&logo=ruby&logoColor=pink +[💎truby-23.0i]: https://img.shields.io/badge/Truffle_Ruby-23.0_(%F0%9F%9A%ABCI)-AABBCC?style=for-the-badge&logo=ruby&logoColor=pink +[💎truby-23.1i]: https://img.shields.io/badge/Truffle_Ruby-23.1-34BCB1?style=for-the-badge&logo=ruby&logoColor=pink +[💎truby-c-i]: https://img.shields.io/badge/Truffle_Ruby-current-34BCB1?style=for-the-badge&logo=ruby&logoColor=green +[💎truby-headi]: https://img.shields.io/badge/Truffle_Ruby-HEAD-34BCB1?style=for-the-badge&logo=ruby&logoColor=blue +[💎jruby-9.1i]: https://img.shields.io/badge/JRuby-9.1_(%F0%9F%9A%ABCI)-AABBCC?style=for-the-badge&logo=ruby&logoColor=red +[💎jruby-9.2i]: https://img.shields.io/badge/JRuby-9.2_(%F0%9F%9A%ABCI)-AABBCC?style=for-the-badge&logo=ruby&logoColor=red +[💎jruby-9.3i]: https://img.shields.io/badge/JRuby-9.3_(%F0%9F%9A%ABCI)-AABBCC?style=for-the-badge&logo=ruby&logoColor=red +[💎jruby-9.4i]: https://img.shields.io/badge/JRuby-9.4-FBE742?style=for-the-badge&logo=ruby&logoColor=red +[💎jruby-c-i]: https://img.shields.io/badge/JRuby-current-FBE742?style=for-the-badge&logo=ruby&logoColor=green +[💎jruby-headi]: https://img.shields.io/badge/JRuby-HEAD-FBE742?style=for-the-badge&logo=ruby&logoColor=blue +[🤝gh-issues]: https://github.com/omniauth/omniauth-ldap/issues +[🤝gh-pulls]: https://github.com/omniauth/omniauth-ldap/pulls +[🤝contributing]: CONTRIBUTING.md +[🏀codecov-g]: https://codecov.io/gh/omniauth/omniauth-ldap/graphs/tree.svg +[🖐contrib-rocks]: https://contrib.rocks +[🖐contributors]: https://github.com/omniauth/omniauth-ldap/graphs/contributors +[🖐contributors-img]: https://contrib.rocks/image?repo=omniauth/omniauth-ldap +[đŸĒ‡conduct]: CODE_OF_CONDUCT.md +[đŸĒ‡conduct-img]: https://img.shields.io/badge/Contributor_Covenant-2.1-259D6C.svg +[📌pvc]: http://guides.rubygems.org/patterns/#pessimistic-version-constraint +[📌semver]: https://semver.org/spec/v2.0.0.html +[📌semver-img]: https://img.shields.io/badge/semver-2.0.0-259D6C.svg?style=flat +[📌semver-breaking]: https://github.com/semver/semver/issues/716#issuecomment-869336139 +[📌major-versions-not-sacred]: https://tom.preston-werner.com/2022/05/23/major-version-numbers-are-not-sacred.html +[📌changelog]: CHANGELOG.md +[📗keep-changelog]: https://keepachangelog.com/en/1.0.0/ +[📗keep-changelog-img]: https://img.shields.io/badge/keep--a--changelog-1.0.0-34495e.svg?style=flat +[📌gitmoji]:https://gitmoji.dev +[📌gitmoji-img]:https://img.shields.io/badge/gitmoji_commits-%20%F0%9F%98%9C%20%F0%9F%98%8D-34495e.svg?style=flat-square +[🧮kloc]: https://www.youtube.com/watch?v=dQw4w9WgXcQ +[🧮kloc-img]: https://img.shields.io/badge/KLOC-4.076-FFDD67.svg?style=for-the-badge&logo=YouTube&logoColor=blue +[🔐security]: SECURITY.md +[🔐security-img]: https://img.shields.io/badge/security-policy-259D6C.svg?style=flat +[📄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-259D6C.svg +[📄license-compat]: https://dev.to/galtzo/how-to-check-license-compatibility-41h0 +[📄license-compat-img]: https://img.shields.io/badge/Apache_Compatible:_Category_A-%E2%9C%93-259D6C.svg?style=flat&logo=Apache +[📄ilo-declaration]: https://www.ilo.org/declaration/lang--en/index.htm +[📄ilo-declaration-img]: https://img.shields.io/badge/ILO_Fundamental_Principles-✓-259D6C.svg?style=flat +[🚎yard-current]: http://rubydoc.info/gems/omniauth-ldap +[🚎yard-head]: https://omniauth-ldap.galtzo.com +[💎stone_checksums]: https://github.com/galtzo-floss/stone_checksums +[💎SHA_checksums]: https://gitlab.com/omniauth/omniauth-ldap/-/tree/main/checksums +[💎rlts]: https://github.com/rubocop-lts/rubocop-lts +[💎rlts-img]: https://img.shields.io/badge/code_style_&_linting-rubocop--lts-34495e.svg?plastic&logo=ruby&logoColor=white +[💎appraisal2]: https://github.com/appraisal-rb/appraisal2 +[💎appraisal2-img]: https://img.shields.io/badge/appraised_by-appraisal2-34495e.svg?plastic&logo=ruby&logoColor=white +[💎d-in-dvcs]: https://railsbling.com/posts/dvcs/put_the_d_in_dvcs/ diff --git a/RUBOCOP.md b/RUBOCOP.md new file mode 100644 index 0000000..f15b980 --- /dev/null +++ b/RUBOCOP.md @@ -0,0 +1,71 @@ +# RuboCop Usage Guide + +## Overview + +A tale of two RuboCop plugin gems. + +### RuboCop Gradual + +This project uses `rubocop_gradual` instead of vanilla RuboCop for code style checking. The `rubocop_gradual` tool allows for gradual adoption of RuboCop rules by tracking violations in a lock file. + +### RuboCop LTS + +This project uses `rubocop-lts` to ensure, on a best-effort basis, compatibility with Ruby >= 1.9.2. +RuboCop rules are meticulously configured by the `rubocop-lts` family of gems to ensure that a project is compatible with a specific version of Ruby. See: https://rubocop-lts.gitlab.io for more. + +## Checking RuboCop Violations + +To check for RuboCop violations in this project, always use: + +```bash +bundle exec rake rubocop_gradual:check +``` + +**Do not use** the standard RuboCop commands like: +- `bundle exec rubocop` +- `rubocop` + +## Understanding the Lock File + +The `.rubocop_gradual.lock` file tracks all current RuboCop violations in the project. This allows the team to: + +1. Prevent new violations while gradually fixing existing ones +2. Track progress on code style improvements +3. Ensure CI builds don't fail due to pre-existing violations + +## Common Commands + +- **Check violations** + - `bundle exec rake rubocop_gradual` + - `bundle exec rake rubocop_gradual:check` +- **(Safe) Autocorrect violations, and update lockfile if no new violations** + - `bundle exec rake rubocop_gradual:autocorrect` +- **Force update the lock file (w/o autocorrect) to match violations present in code** + - `bundle exec rake rubocop_gradual:force_update` + +## Workflow + +1. Before submitting a PR, run `bundle exec rake rubocop_gradual:autocorrect` + a. or just the default `bundle exec rake`, as autocorrection is a pre-requisite of the default task. +2. If there are new violations, either: + - Fix them in your code + - Run `bundle exec rake rubocop_gradual:force_update` to update the lock file (only for violations you can't fix immediately) +3. Commit the updated `.rubocop_gradual.lock` file along with your changes + +## Never add inline RuboCop disables + +Do not add inline `rubocop:disable` / `rubocop:enable` comments anywhere in the codebase (including specs, except when following the few existing `rubocop:disable` patterns for a rule already being disabled elsewhere in the code). We handle exceptions in two supported ways: + +- Permanent/structural exceptions: prefer adjusting the RuboCop configuration (e.g., in `.rubocop.yml`) to exclude a rule for a path or file pattern when it makes sense project-wide. +- Temporary exceptions while improving code: record the current violations in `.rubocop_gradual.lock` via the gradual workflow: + - `bundle exec rake rubocop_gradual:autocorrect` (preferred; will autocorrect what it can and update the lock only if no new violations were introduced) + - If needed, `bundle exec rake rubocop_gradual:force_update` (as a last resort when you cannot fix the newly reported violations immediately) + +In general, treat the rules as guidance to follow; fix violations rather than ignore them. For example, RSpec conventions in this project expect `described_class` to be used in specs that target a specific class under test. + +## Benefits of rubocop_gradual + +- Allows incremental adoption of code style rules +- Prevents CI failures due to pre-existing violations +- Provides a clear record of code style debt +- Enables focused efforts on improving code quality over time diff --git a/Rakefile b/Rakefile index d2dc1d6..97a28ef 100644 --- a/Rakefile +++ b/Rakefile @@ -1,9 +1,66 @@ -#!/usr/bin/env rake -require "bundler/gem_tasks" -require 'rspec/core/rake_task' +# frozen_string_literal: true -desc 'Default: run specs.' -task :default => :spec +# kettle-dev Rakefile v1.1.45 - 2025-10-31 +# Ruby 2.3 (Safe Navigation) or higher required +# +# MIT License (see License.txt) +# +# Copyright (c) 2025 Peter H. Boling (galtzo.com) +# +# Expected to work in any project that uses Bundler. +# +# Sets up tasks for appraisal, floss_funding, rspec, minitest, rubocop, reek, yard, and stone_checksums. +# +# rake appraisal:update # Update Appraisal gemfiles and run RuboCop... +# rake bench # Run all benchmarks (alias for bench:run) +# rake bench:list # List available benchmark scripts +# rake bench:run # Run all benchmark scripts (skips on CI) +# rake build:generate_checksums # Generate both SHA256 & SHA512 checksums i... +# rake bundle:audit:check # Checks the Gemfile.lock for insecure depe... +# rake bundle:audit:update # Updates the bundler-audit vulnerability d... +# rake ci:act[opt] # Run 'act' with a selected workflow +# rake coverage # Run specs w/ coverage and open results in... +# rake default # Default tasks aggregator +# rake install # Build and install kettle-dev-1.0.0.gem in... +# rake install:local # Build and install kettle-dev-1.0.0.gem in... +# rake kettle:dev:install # Install kettle-dev GitHub automation and ... +# rake kettle:dev:template # Template kettle-dev files into the curren... +# rake reek # Check for code smells +# rake reek:update # Run reek and store the output into the RE... +# rake release[remote] # Create tag v1.0.0 and build and push kett... +# rake rubocop_gradual # Run RuboCop Gradual +# rake rubocop_gradual:autocorrect # Run RuboCop Gradual with autocorrect (onl... +# rake rubocop_gradual:autocorrect_all # Run RuboCop Gradual with autocorrect (saf... +# rake rubocop_gradual:check # Run RuboCop Gradual to check the lock file +# rake rubocop_gradual:force_update # Run RuboCop Gradual to force update the l... +# rake rubocop_gradual_debug # Run RuboCop Gradual +# rake rubocop_gradual_debug:autocorrect # Run RuboCop Gradual with autocorrect (onl... +# rake rubocop_gradual_debug:autocorrect_all # Run RuboCop Gradual with autocorrect (saf... +# rake rubocop_gradual_debug:check # Run RuboCop Gradual to check the lock file +# rake rubocop_gradual_debug:force_update # Run RuboCop Gradual to force update the l... +# rake spec # Run RSpec code examples +# rake test # Run tests +# rake yard # Generate YARD Documentation +# -desc "Run specs" -RSpec::Core::RakeTask.new +require "bundler/gem_tasks" if !Dir[File.join(__dir__, "*.gemspec")].empty? + +# Define a base default task early so other files can enhance it. +desc "Default tasks aggregator" +task :default do + puts "Default task complete." +end + +# External gems that define tasks - add here! +require "kettle/dev" + +### RELEASE TASKS +# Setup stone_checksums +begin + require "stone_checksums" +rescue LoadError + desc("(stub) build:generate_checksums is unavailable") + task("build:generate_checksums") do + warn("NOTE: stone_checksums isn't installed, or is disabled for #{RUBY_VERSION} in the current environment") + end +end diff --git a/SECURITY.md b/SECURITY.md new file mode 100644 index 0000000..a319529 --- /dev/null +++ b/SECURITY.md @@ -0,0 +1,21 @@ +# Security Policy + +## Supported Versions + +| Version | Supported | +|----------|-----------| +| 1.latest | ✅ | + +## Security contact information + +To report a security vulnerability, please use the +[Tidelift security contact](https://tidelift.com/security). +Tidelift will coordinate the fix and disclosure. + +## Additional Support + +If you are interested in support for versions older than the latest release, +please consider sponsoring the project / maintainer @ https://liberapay.com/pboling/donate, +or find other sponsorship links in the [README]. + +[README]: README.md diff --git a/bin/_guard-core b/bin/_guard-core new file mode 100755 index 0000000..3b368e6 --- /dev/null +++ b/bin/_guard-core @@ -0,0 +1,16 @@ +#!/usr/bin/env ruby +# frozen_string_literal: true + +# +# This file was generated by Bundler. +# +# The application '_guard-core' is installed as part of a gem, and +# this file is here to facilitate running it. +# + +ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../Gemfile", __dir__) + +require "rubygems" +require "bundler/setup" + +load Gem.bin_path("guard", "_guard-core") diff --git a/bin/appraisal b/bin/appraisal new file mode 100755 index 0000000..bc7d25b --- /dev/null +++ b/bin/appraisal @@ -0,0 +1,16 @@ +#!/usr/bin/env ruby +# frozen_string_literal: true + +# +# This file was generated by Bundler. +# +# The application 'appraisal' is installed as part of a gem, and +# this file is here to facilitate running it. +# + +ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../Gemfile", __dir__) + +require "rubygems" +require "bundler/setup" + +load Gem.bin_path("appraisal2", "appraisal") diff --git a/bin/bundle-audit b/bin/bundle-audit new file mode 100755 index 0000000..018e702 --- /dev/null +++ b/bin/bundle-audit @@ -0,0 +1,16 @@ +#!/usr/bin/env ruby +# frozen_string_literal: true + +# +# This file was generated by Bundler. +# +# The application 'bundle-audit' is installed as part of a gem, and +# this file is here to facilitate running it. +# + +ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../Gemfile", __dir__) + +require "rubygems" +require "bundler/setup" + +load Gem.bin_path("bundler-audit", "bundle-audit") diff --git a/bin/bundler-audit b/bin/bundler-audit new file mode 100755 index 0000000..fce0291 --- /dev/null +++ b/bin/bundler-audit @@ -0,0 +1,16 @@ +#!/usr/bin/env ruby +# frozen_string_literal: true + +# +# This file was generated by Bundler. +# +# The application 'bundler-audit' is installed as part of a gem, and +# this file is here to facilitate running it. +# + +ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../Gemfile", __dir__) + +require "rubygems" +require "bundler/setup" + +load Gem.bin_path("bundler-audit", "bundler-audit") diff --git a/bin/code_climate_reek b/bin/code_climate_reek new file mode 100755 index 0000000..ca950c5 --- /dev/null +++ b/bin/code_climate_reek @@ -0,0 +1,16 @@ +#!/usr/bin/env ruby +# frozen_string_literal: true + +# +# This file was generated by Bundler. +# +# The application 'code_climate_reek' is installed as part of a gem, and +# this file is here to facilitate running it. +# + +ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../Gemfile", __dir__) + +require "rubygems" +require "bundler/setup" + +load Gem.bin_path("reek", "code_climate_reek") diff --git a/bin/coderay b/bin/coderay new file mode 100755 index 0000000..4d35aba --- /dev/null +++ b/bin/coderay @@ -0,0 +1,16 @@ +#!/usr/bin/env ruby +# frozen_string_literal: true + +# +# This file was generated by Bundler. +# +# The application 'coderay' is installed as part of a gem, and +# this file is here to facilitate running it. +# + +ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../Gemfile", __dir__) + +require "rubygems" +require "bundler/setup" + +load Gem.bin_path("coderay", "coderay") diff --git a/bin/erb b/bin/erb new file mode 100755 index 0000000..58e756a --- /dev/null +++ b/bin/erb @@ -0,0 +1,16 @@ +#!/usr/bin/env ruby +# frozen_string_literal: true + +# +# This file was generated by Bundler. +# +# The application 'erb' is installed as part of a gem, and +# this file is here to facilitate running it. +# + +ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../Gemfile", __dir__) + +require "rubygems" +require "bundler/setup" + +load Gem.bin_path("erb", "erb") diff --git a/bin/gem_checksums b/bin/gem_checksums new file mode 100755 index 0000000..dfefdd3 --- /dev/null +++ b/bin/gem_checksums @@ -0,0 +1,16 @@ +#!/usr/bin/env ruby +# frozen_string_literal: true + +# +# This file was generated by Bundler. +# +# The application 'gem_checksums' is installed as part of a gem, and +# this file is here to facilitate running it. +# + +ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../Gemfile", __dir__) + +require "rubygems" +require "bundler/setup" + +load Gem.bin_path("stone_checksums", "gem_checksums") diff --git a/bin/guard b/bin/guard new file mode 100755 index 0000000..643c81f --- /dev/null +++ b/bin/guard @@ -0,0 +1,16 @@ +#!/usr/bin/env ruby +# frozen_string_literal: true + +# +# This file was generated by Bundler. +# +# The application 'guard' is installed as part of a gem, and +# this file is here to facilitate running it. +# + +ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../Gemfile", __dir__) + +require "rubygems" +require "bundler/setup" + +load Gem.bin_path("guard", "guard") diff --git a/bin/htmldiff b/bin/htmldiff new file mode 100755 index 0000000..f210292 --- /dev/null +++ b/bin/htmldiff @@ -0,0 +1,16 @@ +#!/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__) + +require "rubygems" +require "bundler/setup" + +load Gem.bin_path("diff-lcs", "htmldiff") diff --git a/bin/irb b/bin/irb new file mode 100755 index 0000000..66b4091 --- /dev/null +++ b/bin/irb @@ -0,0 +1,16 @@ +#!/usr/bin/env ruby +# frozen_string_literal: true + +# +# This file was generated by Bundler. +# +# The application 'irb' is installed as part of a gem, and +# this file is here to facilitate running it. +# + +ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../Gemfile", __dir__) + +require "rubygems" +require "bundler/setup" + +load Gem.bin_path("irb", "irb") diff --git a/bin/kettle-changelog b/bin/kettle-changelog new file mode 100755 index 0000000..0e7fcc4 --- /dev/null +++ b/bin/kettle-changelog @@ -0,0 +1,16 @@ +#!/usr/bin/env ruby +# frozen_string_literal: true + +# +# This file was generated by Bundler. +# +# The application 'kettle-changelog' is installed as part of a gem, and +# this file is here to facilitate running it. +# + +ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../Gemfile", __dir__) + +require "rubygems" +require "bundler/setup" + +load Gem.bin_path("kettle-dev", "kettle-changelog") diff --git a/bin/kettle-commit-msg b/bin/kettle-commit-msg new file mode 100755 index 0000000..b228ad6 --- /dev/null +++ b/bin/kettle-commit-msg @@ -0,0 +1,16 @@ +#!/usr/bin/env ruby +# frozen_string_literal: true + +# +# This file was generated by Bundler. +# +# The application 'kettle-commit-msg' is installed as part of a gem, and +# this file is here to facilitate running it. +# + +ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../Gemfile", __dir__) + +require "rubygems" +require "bundler/setup" + +load Gem.bin_path("kettle-dev", "kettle-commit-msg") diff --git a/bin/kettle-dev-setup b/bin/kettle-dev-setup new file mode 100755 index 0000000..276319a --- /dev/null +++ b/bin/kettle-dev-setup @@ -0,0 +1,16 @@ +#!/usr/bin/env ruby +# frozen_string_literal: true + +# +# This file was generated by Bundler. +# +# The application 'kettle-dev-setup' is installed as part of a gem, and +# this file is here to facilitate running it. +# + +ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../Gemfile", __dir__) + +require "rubygems" +require "bundler/setup" + +load Gem.bin_path("kettle-dev", "kettle-dev-setup") diff --git a/bin/kettle-dvcs b/bin/kettle-dvcs new file mode 100755 index 0000000..b572d48 --- /dev/null +++ b/bin/kettle-dvcs @@ -0,0 +1,16 @@ +#!/usr/bin/env ruby +# frozen_string_literal: true + +# +# This file was generated by Bundler. +# +# The application 'kettle-dvcs' is installed as part of a gem, and +# this file is here to facilitate running it. +# + +ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../Gemfile", __dir__) + +require "rubygems" +require "bundler/setup" + +load Gem.bin_path("kettle-dev", "kettle-dvcs") diff --git a/bin/kettle-pre-release b/bin/kettle-pre-release new file mode 100755 index 0000000..1b98ad6 --- /dev/null +++ b/bin/kettle-pre-release @@ -0,0 +1,16 @@ +#!/usr/bin/env ruby +# frozen_string_literal: true + +# +# This file was generated by Bundler. +# +# The application 'kettle-pre-release' is installed as part of a gem, and +# this file is here to facilitate running it. +# + +ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../Gemfile", __dir__) + +require "rubygems" +require "bundler/setup" + +load Gem.bin_path("kettle-dev", "kettle-pre-release") diff --git a/bin/kettle-readme-backers b/bin/kettle-readme-backers new file mode 100755 index 0000000..fec80bd --- /dev/null +++ b/bin/kettle-readme-backers @@ -0,0 +1,16 @@ +#!/usr/bin/env ruby +# frozen_string_literal: true + +# +# This file was generated by Bundler. +# +# The application 'kettle-readme-backers' is installed as part of a gem, and +# this file is here to facilitate running it. +# + +ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../Gemfile", __dir__) + +require "rubygems" +require "bundler/setup" + +load Gem.bin_path("kettle-dev", "kettle-readme-backers") diff --git a/bin/kettle-release b/bin/kettle-release new file mode 100755 index 0000000..1f5758a --- /dev/null +++ b/bin/kettle-release @@ -0,0 +1,16 @@ +#!/usr/bin/env ruby +# frozen_string_literal: true + +# +# This file was generated by Bundler. +# +# The application 'kettle-release' is installed as part of a gem, and +# this file is here to facilitate running it. +# + +ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../Gemfile", __dir__) + +require "rubygems" +require "bundler/setup" + +load Gem.bin_path("kettle-dev", "kettle-release") diff --git a/bin/kramdown b/bin/kramdown new file mode 100755 index 0000000..547fd06 --- /dev/null +++ b/bin/kramdown @@ -0,0 +1,16 @@ +#!/usr/bin/env ruby +# frozen_string_literal: true + +# +# This file was generated by Bundler. +# +# The application 'kramdown' is installed as part of a gem, and +# this file is here to facilitate running it. +# + +ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../Gemfile", __dir__) + +require "rubygems" +require "bundler/setup" + +load Gem.bin_path("kramdown", "kramdown") diff --git a/bin/ldiff b/bin/ldiff new file mode 100755 index 0000000..6fee509 --- /dev/null +++ b/bin/ldiff @@ -0,0 +1,16 @@ +#!/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__) + +require "rubygems" +require "bundler/setup" + +load Gem.bin_path("diff-lcs", "ldiff") diff --git a/bin/listen b/bin/listen new file mode 100755 index 0000000..97adf26 --- /dev/null +++ b/bin/listen @@ -0,0 +1,16 @@ +#!/usr/bin/env ruby +# frozen_string_literal: true + +# +# This file was generated by Bundler. +# +# The application 'listen' is installed as part of a gem, and +# this file is here to facilitate running it. +# + +ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../Gemfile", __dir__) + +require "rubygems" +require "bundler/setup" + +load Gem.bin_path("listen", "listen") diff --git a/bin/nokogiri b/bin/nokogiri new file mode 100755 index 0000000..8b72331 --- /dev/null +++ b/bin/nokogiri @@ -0,0 +1,16 @@ +#!/usr/bin/env ruby +# frozen_string_literal: true + +# +# This file was generated by Bundler. +# +# The application 'nokogiri' is installed as part of a gem, and +# this file is here to facilitate running it. +# + +ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../Gemfile", __dir__) + +require "rubygems" +require "bundler/setup" + +load Gem.bin_path("nokogiri", "nokogiri") diff --git a/bin/pry b/bin/pry new file mode 100755 index 0000000..41bfde5 --- /dev/null +++ b/bin/pry @@ -0,0 +1,16 @@ +#!/usr/bin/env ruby +# frozen_string_literal: true + +# +# This file was generated by Bundler. +# +# The application 'pry' is installed as part of a gem, and +# this file is here to facilitate running it. +# + +ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../Gemfile", __dir__) + +require "rubygems" +require "bundler/setup" + +load Gem.bin_path("pry", "pry") diff --git a/bin/racc b/bin/racc new file mode 100755 index 0000000..35d8ec5 --- /dev/null +++ b/bin/racc @@ -0,0 +1,16 @@ +#!/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__) + +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..822a305 --- /dev/null +++ b/bin/rackup @@ -0,0 +1,16 @@ +#!/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__) + +require "rubygems" +require "bundler/setup" + +load Gem.bin_path("rack", "rackup") diff --git a/bin/rake b/bin/rake new file mode 100755 index 0000000..9efbee9 --- /dev/null +++ b/bin/rake @@ -0,0 +1,16 @@ +#!/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__) + +require "rubygems" +require "bundler/setup" + +load Gem.bin_path("rake", "rake") diff --git a/bin/rbs b/bin/rbs new file mode 100755 index 0000000..ffc95a0 --- /dev/null +++ b/bin/rbs @@ -0,0 +1,16 @@ +#!/usr/bin/env ruby +# frozen_string_literal: true + +# +# This file was generated by Bundler. +# +# The application 'rbs' is installed as part of a gem, and +# this file is here to facilitate running it. +# + +ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../Gemfile", __dir__) + +require "rubygems" +require "bundler/setup" + +load Gem.bin_path("rbs", "rbs") diff --git a/bin/rdbg b/bin/rdbg new file mode 100755 index 0000000..4890959 --- /dev/null +++ b/bin/rdbg @@ -0,0 +1,16 @@ +#!/usr/bin/env ruby +# frozen_string_literal: true + +# +# This file was generated by Bundler. +# +# The application 'rdbg' is installed as part of a gem, and +# this file is here to facilitate running it. +# + +ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../Gemfile", __dir__) + +require "rubygems" +require "bundler/setup" + +load Gem.bin_path("debug", "rdbg") diff --git a/bin/rdoc b/bin/rdoc new file mode 100755 index 0000000..021c9ca --- /dev/null +++ b/bin/rdoc @@ -0,0 +1,16 @@ +#!/usr/bin/env ruby +# frozen_string_literal: true + +# +# This file was generated by Bundler. +# +# The application 'rdoc' is installed as part of a gem, and +# this file is here to facilitate running it. +# + +ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../Gemfile", __dir__) + +require "rubygems" +require "bundler/setup" + +load Gem.bin_path("rdoc", "rdoc") diff --git a/bin/reek b/bin/reek new file mode 100755 index 0000000..201905f --- /dev/null +++ b/bin/reek @@ -0,0 +1,16 @@ +#!/usr/bin/env ruby +# frozen_string_literal: true + +# +# This file was generated by Bundler. +# +# The application 'reek' is installed as part of a gem, and +# this file is here to facilitate running it. +# + +ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../Gemfile", __dir__) + +require "rubygems" +require "bundler/setup" + +load Gem.bin_path("reek", "reek") diff --git a/bin/ri b/bin/ri new file mode 100755 index 0000000..0d19636 --- /dev/null +++ b/bin/ri @@ -0,0 +1,16 @@ +#!/usr/bin/env ruby +# frozen_string_literal: true + +# +# This file was generated by Bundler. +# +# The application 'ri' is installed as part of a gem, and +# this file is here to facilitate running it. +# + +ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../Gemfile", __dir__) + +require "rubygems" +require "bundler/setup" + +load Gem.bin_path("rdoc", "ri") diff --git a/bin/rspec b/bin/rspec new file mode 100755 index 0000000..93e191c --- /dev/null +++ b/bin/rspec @@ -0,0 +1,16 @@ +#!/usr/bin/env ruby +# frozen_string_literal: true + +# +# This file was generated by Bundler. +# +# The application 'rspec' is installed as part of a gem, and +# this file is here to facilitate running it. +# + +ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../Gemfile", __dir__) + +require "rubygems" +require "bundler/setup" + +load Gem.bin_path("rspec-core", "rspec") diff --git a/bin/rubocop b/bin/rubocop new file mode 100755 index 0000000..d73598d --- /dev/null +++ b/bin/rubocop @@ -0,0 +1,16 @@ +#!/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__) + +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..3d21eea --- /dev/null +++ b/bin/rubocop-gradual @@ -0,0 +1,16 @@ +#!/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__) + +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..5dc6740 --- /dev/null +++ b/bin/ruby-parse @@ -0,0 +1,16 @@ +#!/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__) + +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..47cef49 --- /dev/null +++ b/bin/ruby-rewrite @@ -0,0 +1,16 @@ +#!/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__) + +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..3b77a15 --- /dev/null +++ b/bin/standardrb @@ -0,0 +1,16 @@ +#!/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__) + +require "rubygems" +require "bundler/setup" + +load Gem.bin_path("standard", "standardrb") diff --git a/bin/thor b/bin/thor new file mode 100755 index 0000000..d4f29ff --- /dev/null +++ b/bin/thor @@ -0,0 +1,16 @@ +#!/usr/bin/env ruby +# frozen_string_literal: true + +# +# This file was generated by Bundler. +# +# The application 'thor' is installed as part of a gem, and +# this file is here to facilitate running it. +# + +ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../Gemfile", __dir__) + +require "rubygems" +require "bundler/setup" + +load Gem.bin_path("thor", "thor") diff --git a/bin/yard b/bin/yard new file mode 100755 index 0000000..ad94bbd --- /dev/null +++ b/bin/yard @@ -0,0 +1,16 @@ +#!/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__) + +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..6cd0f23 --- /dev/null +++ b/bin/yard-junk @@ -0,0 +1,16 @@ +#!/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__) + +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..5ed01e7 --- /dev/null +++ b/bin/yardoc @@ -0,0 +1,16 @@ +#!/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__) + +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..80fa3d7 --- /dev/null +++ b/bin/yri @@ -0,0 +1,16 @@ +#!/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__) + +require "rubygems" +require "bundler/setup" + +load Gem.bin_path("yard", "yri") diff --git a/gemfiles/audit.gemfile b/gemfiles/audit.gemfile new file mode 100644 index 0000000..6d5d06b --- /dev/null +++ b/gemfiles/audit.gemfile @@ -0,0 +1,7 @@ +# This file was generated by Appraisal2 + +source "https://gem.coop" + +gemspec :path => "../" + +eval_gemfile("modular/x_std_libs.gemfile") diff --git a/gemfiles/coverage.gemfile b/gemfiles/coverage.gemfile new file mode 100644 index 0000000..a2ed98b --- /dev/null +++ b/gemfiles/coverage.gemfile @@ -0,0 +1,15 @@ +# This file was generated by Appraisal2 + +source "https://gem.coop" + +gemspec :path => "../" + +eval_gemfile("modular/omniauth/r3/v2.1.gemfile") + +eval_gemfile("modular/rack/r3/v3.2.gemfile") + +eval_gemfile("modular/coverage.gemfile") + +eval_gemfile("modular/optional.gemfile") + +eval_gemfile("modular/x_std_libs.gemfile") diff --git a/gemfiles/current.gemfile b/gemfiles/current.gemfile new file mode 100644 index 0000000..b95b341 --- /dev/null +++ b/gemfiles/current.gemfile @@ -0,0 +1,13 @@ +# This file was generated by Appraisal2 + +source "https://gem.coop" + +gemspec :path => "../" + +eval_gemfile("modular/omniauth/r3/v2.1.gemfile") + +eval_gemfile("modular/optional.gemfile") + +eval_gemfile("modular/rack/r3/v3.2.gemfile") + +eval_gemfile("modular/x_std_libs.gemfile") diff --git a/gemfiles/dep_heads.gemfile b/gemfiles/dep_heads.gemfile new file mode 100644 index 0000000..6c555e0 --- /dev/null +++ b/gemfiles/dep_heads.gemfile @@ -0,0 +1,13 @@ +# This file was generated by Appraisal2 + +source "https://gem.coop" + +gemspec :path => "../" + +eval_gemfile("modular/omniauth/vHEAD.gemfile") + +eval_gemfile("modular/optional.gemfile") + +eval_gemfile("modular/rack/vHEAD.gemfile") + +eval_gemfile("modular/runtime_heads.gemfile") diff --git a/gemfiles/head.gemfile b/gemfiles/head.gemfile new file mode 100644 index 0000000..68c5773 --- /dev/null +++ b/gemfiles/head.gemfile @@ -0,0 +1,15 @@ +# This file was generated by Appraisal2 + +source "https://gem.coop" + +gem "benchmark", "~> 0.4", ">= 0.4.1" + +gemspec :path => "../" + +eval_gemfile("modular/omniauth/vHEAD.gemfile") + +eval_gemfile("modular/optional.gemfile") + +eval_gemfile("modular/rack/vHEAD.gemfile") + +eval_gemfile("modular/x_std_libs.gemfile") diff --git a/gemfiles/modular/coverage.gemfile b/gemfiles/modular/coverage.gemfile new file mode 100644 index 0000000..f46bb51 --- /dev/null +++ b/gemfiles/modular/coverage.gemfile @@ -0,0 +1,6 @@ +# frozen_string_literal: true + +# We run code coverage on the latest version of Ruby only. + +# Coverage +gem "kettle-soup-cover", "~> 1.0", ">= 1.0.10", :require => false diff --git a/gemfiles/modular/debug.gemfile b/gemfiles/modular/debug.gemfile new file mode 100644 index 0000000..3e86091 --- /dev/null +++ b/gemfiles/modular/debug.gemfile @@ -0,0 +1,13 @@ +# Ex-Standard Library gems +gem "irb", "~> 1.15", ">= 1.15.2" # removed from stdlib in 3.5 + +platform :mri do + # Debugging - Ensure ENV["DEBUG"] == "true" to use debuggers within spec suite + # Use binding.break, binding.b, or debugger in code + gem "debug", ">= 1.1" # ruby >= 2.7 + + # Dev Console - Binding.pry - Irb replacement + # gem "pry", "~> 0.14" # ruby >= 2.0 +end + +gem "gem_bench", "~> 2.0", ">= 2.0.5" diff --git a/gemfiles/modular/documentation.gemfile b/gemfiles/modular/documentation.gemfile new file mode 100644 index 0000000..7c5c2e9 --- /dev/null +++ b/gemfiles/modular/documentation.gemfile @@ -0,0 +1,11 @@ +# frozen_string_literal: true + +# Documentation +gem "kramdown", "~> 2.5", ">= 2.5.1" # Ruby >= 2.5 +gem "kramdown-parser-gfm", "~> 1.1" # Ruby >= 2.3 +gem "yard", "~> 0.9", ">= 0.9.37", :require => false +gem "yard-junk", "~> 0.0", ">= 0.0.10", :github => "pboling/yard-junk", :branch => "next", :require => false +gem "yard-relative_markdown_links", "~> 0.5.0" + +# Std Lib extractions +gem "rdoc", "~> 6.11" diff --git a/gemfiles/modular/erb/r2.3/default.gemfile b/gemfiles/modular/erb/r2.3/default.gemfile new file mode 100644 index 0000000..ca868e8 --- /dev/null +++ b/gemfiles/modular/erb/r2.3/default.gemfile @@ -0,0 +1,6 @@ +# The cake is a lie. +# erb v2.2, the oldest release, was never compatible with Ruby 2.3. +# In addition, erb does not follow SemVer, and old rubies get dropped in a patch. +# This means we have no choice but to use the erb that shipped with Ruby 2.3 +# /opt/hostedtoolcache/Ruby/2.3.8/x64/lib/ruby/gems/2.3.0/gems/erb-2.2.2/lib/erb.rb:670:in `prepare_trim_mode': undefined method `match?' for "-":String (NoMethodError) +# gem "erb", ">= 2.2" # ruby >= 2.3.0 diff --git a/gemfiles/modular/erb/r2.6/v2.2.gemfile b/gemfiles/modular/erb/r2.6/v2.2.gemfile new file mode 100644 index 0000000..7cd8574 --- /dev/null +++ b/gemfiles/modular/erb/r2.6/v2.2.gemfile @@ -0,0 +1,3 @@ +# Ruby >= 2.3.0 (claimed, but not true, minimum support is Ruby 2.4) +# Last version supporting Ruby <= 2.6 +gem "erb", "~> 2.2.2" diff --git a/gemfiles/modular/erb/r2/v3.0.gemfile b/gemfiles/modular/erb/r2/v3.0.gemfile new file mode 100644 index 0000000..c03bd8d --- /dev/null +++ b/gemfiles/modular/erb/r2/v3.0.gemfile @@ -0,0 +1 @@ +gem "erb", "~> 3.0" # ruby >= 2.7.0 diff --git a/gemfiles/modular/erb/r3.1/v4.0.gemfile b/gemfiles/modular/erb/r3.1/v4.0.gemfile new file mode 100644 index 0000000..2e9046d --- /dev/null +++ b/gemfiles/modular/erb/r3.1/v4.0.gemfile @@ -0,0 +1,2 @@ +# last version compatible with Ruby 3.1 +gem "erb", "~> 4.0" # ruby >= 2.7.0 diff --git a/gemfiles/modular/erb/r3/v5.0.gemfile b/gemfiles/modular/erb/r3/v5.0.gemfile new file mode 100644 index 0000000..97033fa --- /dev/null +++ b/gemfiles/modular/erb/r3/v5.0.gemfile @@ -0,0 +1 @@ +gem "erb", "~> 5.0" # ruby >= 3.2.0 diff --git a/gemfiles/modular/erb/vHEAD.gemfile b/gemfiles/modular/erb/vHEAD.gemfile new file mode 100644 index 0000000..f61337d --- /dev/null +++ b/gemfiles/modular/erb/vHEAD.gemfile @@ -0,0 +1,2 @@ +# Ruby >= 3.2 (dependency of kettle-dev) +gem "erb", :github => "ruby/erb", :branch => "master" diff --git a/gemfiles/modular/logger/r2.4/v1.5.3.gemfile b/gemfiles/modular/logger/r2.4/v1.5.3.gemfile new file mode 100644 index 0000000..a9236bc --- /dev/null +++ b/gemfiles/modular/logger/r2.4/v1.5.3.gemfile @@ -0,0 +1,3 @@ +# Ruby >= 2.3.0 +# Last version compatible with Ruby 2.4 +gem "logger", "~> 1.5.3" diff --git a/gemfiles/modular/logger/r2/v1.5.gemfile b/gemfiles/modular/logger/r2/v1.5.gemfile new file mode 100644 index 0000000..c4fc65e --- /dev/null +++ b/gemfiles/modular/logger/r2/v1.5.gemfile @@ -0,0 +1,3 @@ +# Ruby >= 2.3.0 +# Last version compatible with Ruby 2.4 +gem "logger", ">= 1.5.3", "< 2" diff --git a/gemfiles/modular/logger/r3/v1.7.gemfile b/gemfiles/modular/logger/r3/v1.7.gemfile new file mode 100644 index 0000000..f68bea9 --- /dev/null +++ b/gemfiles/modular/logger/r3/v1.7.gemfile @@ -0,0 +1,2 @@ +# Ruby >= 2.5.0 +gem "logger", "~> 1.7" diff --git a/gemfiles/modular/logger/vHEAD.gemfile b/gemfiles/modular/logger/vHEAD.gemfile new file mode 100644 index 0000000..0de4c02 --- /dev/null +++ b/gemfiles/modular/logger/vHEAD.gemfile @@ -0,0 +1,2 @@ +# Ruby >= 2.5 (dependency of omniauth) +gem "logger", :github => "ruby/logger", :branch => "master" diff --git a/gemfiles/modular/mutex_m/r2.4/v0.1.gemfile b/gemfiles/modular/mutex_m/r2.4/v0.1.gemfile new file mode 100644 index 0000000..cabf980 --- /dev/null +++ b/gemfiles/modular/mutex_m/r2.4/v0.1.gemfile @@ -0,0 +1,3 @@ +# Ruby >= 0 +# Last version supporting Ruby <= 2.4 +gem "mutex_m", "~> 0.1" diff --git a/gemfiles/modular/mutex_m/r2/v0.3.gemfile b/gemfiles/modular/mutex_m/r2/v0.3.gemfile new file mode 100644 index 0000000..42e9d9b --- /dev/null +++ b/gemfiles/modular/mutex_m/r2/v0.3.gemfile @@ -0,0 +1,2 @@ +# Ruby >= 2.5 +gem "mutex_m", "~> 0.2" diff --git a/gemfiles/modular/mutex_m/r3/v0.3.gemfile b/gemfiles/modular/mutex_m/r3/v0.3.gemfile new file mode 100644 index 0000000..42e9d9b --- /dev/null +++ b/gemfiles/modular/mutex_m/r3/v0.3.gemfile @@ -0,0 +1,2 @@ +# Ruby >= 2.5 +gem "mutex_m", "~> 0.2" diff --git a/gemfiles/modular/mutex_m/vHEAD.gemfile b/gemfiles/modular/mutex_m/vHEAD.gemfile new file mode 100644 index 0000000..3137d03 --- /dev/null +++ b/gemfiles/modular/mutex_m/vHEAD.gemfile @@ -0,0 +1,2 @@ +# Ruby >= 2.5 (dependency of omniauth) +gem "mutex_m", :github => "ruby/mutex_m", :branch => "master" diff --git a/gemfiles/modular/omniauth/r2/v1.0.gemfile b/gemfiles/modular/omniauth/r2/v1.0.gemfile new file mode 100644 index 0000000..fe51cfe --- /dev/null +++ b/gemfiles/modular/omniauth/r2/v1.0.gemfile @@ -0,0 +1,5 @@ +# frozen_string_literal: true + +# Ruby >= 2.2 +# Released March 6, 2012 +gem "omniauth", "~> 1.0.3" diff --git a/gemfiles/modular/omniauth/r2/v1.1.gemfile b/gemfiles/modular/omniauth/r2/v1.1.gemfile new file mode 100644 index 0000000..053280c --- /dev/null +++ b/gemfiles/modular/omniauth/r2/v1.1.gemfile @@ -0,0 +1,5 @@ +# frozen_string_literal: true + +# Ruby >= 2.2 +# Released April 8, 2013 +gem "omniauth", "~> 1.1.4" diff --git a/gemfiles/modular/omniauth/r2/v1.2.gemfile b/gemfiles/modular/omniauth/r2/v1.2.gemfile new file mode 100644 index 0000000..788293a --- /dev/null +++ b/gemfiles/modular/omniauth/r2/v1.2.gemfile @@ -0,0 +1,5 @@ +# frozen_string_literal: true + +# Ruby >= 2.2 +# Released July 9, 2014 +gem "omniauth", "~> 1.2.2" diff --git a/gemfiles/modular/omniauth/r2/v1.3.gemfile b/gemfiles/modular/omniauth/r2/v1.3.gemfile new file mode 100644 index 0000000..1728f92 --- /dev/null +++ b/gemfiles/modular/omniauth/r2/v1.3.gemfile @@ -0,0 +1,5 @@ +# frozen_string_literal: true + +# Ruby >= 2.2 +# Released January 17, 2017 +gem "omniauth", "~> 1.3.2" diff --git a/gemfiles/modular/omniauth/r2/v1.4.gemfile b/gemfiles/modular/omniauth/r2/v1.4.gemfile new file mode 100644 index 0000000..3bd871e --- /dev/null +++ b/gemfiles/modular/omniauth/r2/v1.4.gemfile @@ -0,0 +1,5 @@ +# frozen_string_literal: true + +# Ruby >= 2.2 +# Released December 18, 2017 +gem "omniauth", "~> 1.4.3" diff --git a/gemfiles/modular/omniauth/r2/v1.5.gemfile b/gemfiles/modular/omniauth/r2/v1.5.gemfile new file mode 100644 index 0000000..387669b --- /dev/null +++ b/gemfiles/modular/omniauth/r2/v1.5.gemfile @@ -0,0 +1,5 @@ +# frozen_string_literal: true + +# Ruby >= 2.1.9 +# Released February 11, 2017 +gem "omniauth", "~> 1.5.0" diff --git a/gemfiles/modular/omniauth/r2/v1.6.gemfile b/gemfiles/modular/omniauth/r2/v1.6.gemfile new file mode 100644 index 0000000..3eeab8a --- /dev/null +++ b/gemfiles/modular/omniauth/r2/v1.6.gemfile @@ -0,0 +1,5 @@ +# frozen_string_literal: true + +# Ruby >= 2.1.9 +# Released February 18, 2017 +gem "omniauth", "~> 1.6.1" diff --git a/gemfiles/modular/omniauth/r2/v1.7.gemfile b/gemfiles/modular/omniauth/r2/v1.7.gemfile new file mode 100644 index 0000000..e91c085 --- /dev/null +++ b/gemfiles/modular/omniauth/r2/v1.7.gemfile @@ -0,0 +1,5 @@ +# frozen_string_literal: true + +# Ruby >= 2.1.9 +# Released October 2, 2017 +gem "omniauth", "~> 1.7.1" diff --git a/gemfiles/modular/omniauth/r2/v1.8.gemfile b/gemfiles/modular/omniauth/r2/v1.8.gemfile new file mode 100644 index 0000000..92b60a3 --- /dev/null +++ b/gemfiles/modular/omniauth/r2/v1.8.gemfile @@ -0,0 +1,5 @@ +# frozen_string_literal: true + +# Ruby >= 2.1.9 +# Released December 28, 2017 +gem "omniauth", "~> 1.8.1" diff --git a/gemfiles/modular/omniauth/r2/v1.9.gemfile b/gemfiles/modular/omniauth/r2/v1.9.gemfile new file mode 100644 index 0000000..7539d4f --- /dev/null +++ b/gemfiles/modular/omniauth/r2/v1.9.gemfile @@ -0,0 +1,5 @@ +# frozen_string_literal: true + +# Ruby >= 2.2 +# Released August 18, 2022 +gem "omniauth", "~> 1.9.2" diff --git a/gemfiles/modular/omniauth/r2/v2.0.gemfile b/gemfiles/modular/omniauth/r2/v2.0.gemfile new file mode 100644 index 0000000..5682a6e --- /dev/null +++ b/gemfiles/modular/omniauth/r2/v2.0.gemfile @@ -0,0 +1,5 @@ +# frozen_string_literal: true + +# Ruby >= 2.2 +# Released April 7, 2021 +gem "omniauth", "~> 2.0.4" diff --git a/gemfiles/modular/omniauth/r2/v2.1.gemfile b/gemfiles/modular/omniauth/r2/v2.1.gemfile new file mode 100644 index 0000000..38923d2 --- /dev/null +++ b/gemfiles/modular/omniauth/r2/v2.1.gemfile @@ -0,0 +1,5 @@ +# frozen_string_literal: true + +# Ruby >= 2.2; first version compatible with Ruby >= 3 +# Released February 27, 2025 +gem "omniauth", "~> 2.1.3" diff --git a/gemfiles/modular/omniauth/r3/v2.1.gemfile b/gemfiles/modular/omniauth/r3/v2.1.gemfile new file mode 100644 index 0000000..38923d2 --- /dev/null +++ b/gemfiles/modular/omniauth/r3/v2.1.gemfile @@ -0,0 +1,5 @@ +# frozen_string_literal: true + +# Ruby >= 2.2; first version compatible with Ruby >= 3 +# Released February 27, 2025 +gem "omniauth", "~> 2.1.3" diff --git a/gemfiles/modular/omniauth/vHEAD.gemfile b/gemfiles/modular/omniauth/vHEAD.gemfile new file mode 100644 index 0000000..1f0fa38 --- /dev/null +++ b/gemfiles/modular/omniauth/vHEAD.gemfile @@ -0,0 +1,4 @@ +# frozen_string_literal: true + +# Ruby >= 2.2 +gem "omniauth", :github => "omniauth/omniauth", :branch => "master" diff --git a/gemfiles/modular/optional.gemfile b/gemfiles/modular/optional.gemfile new file mode 100644 index 0000000..05e5ab1 --- /dev/null +++ b/gemfiles/modular/optional.gemfile @@ -0,0 +1,11 @@ +# Optional dependencies are not depended on directly, but may be used if present. + +# Required for kettle-pre-release +# URL parsing with Unicode support (falls back to URI if not available) +gem "addressable", ">= 2.8", "< 3" # ruby >= 2.2 + +# nkf/kconv has been part of Ruby since long ago. +# Eventually it became a standard gem, but was removed from stdlib in Ruby 3.4. +# In general, kconv and iconv have been superseded since Ruby 1.9 by the built-in +# encoding support provided by String#encode, String#force_encoding, and similar methods. +gem "nkf" # ruby >= 2.3 diff --git a/gemfiles/modular/rack/r2.1/v1.6.gemfile b/gemfiles/modular/rack/r2.1/v1.6.gemfile new file mode 100644 index 0000000..2ed90b5 --- /dev/null +++ b/gemfiles/modular/rack/r2.1/v1.6.gemfile @@ -0,0 +1 @@ +gem "rack", "~> 1.6", ">= 1.6.13" diff --git a/gemfiles/modular/rack/r2.3/v2.0.gemfile b/gemfiles/modular/rack/r2.3/v2.0.gemfile new file mode 100644 index 0000000..451e743 --- /dev/null +++ b/gemfiles/modular/rack/r2.3/v2.0.gemfile @@ -0,0 +1 @@ +gem "rack", "~> 2.0.9", ">= 2.0.9.4" diff --git a/gemfiles/modular/rack/r2.3/v2.1.gemfile b/gemfiles/modular/rack/r2.3/v2.1.gemfile new file mode 100644 index 0000000..c9fe7b4 --- /dev/null +++ b/gemfiles/modular/rack/r2.3/v2.1.gemfile @@ -0,0 +1 @@ +gem "rack", "~> 2.1.4", ">= 2.1.4.4" diff --git a/gemfiles/modular/rack/r2.3/v2.2.gemfile b/gemfiles/modular/rack/r2.3/v2.2.gemfile new file mode 100644 index 0000000..6466c9b --- /dev/null +++ b/gemfiles/modular/rack/r2.3/v2.2.gemfile @@ -0,0 +1 @@ +gem "rack", "~> 2.2", ">= 2.2.21" diff --git a/gemfiles/modular/rack/r2/v2.2.gemfile b/gemfiles/modular/rack/r2/v2.2.gemfile new file mode 100644 index 0000000..6466c9b --- /dev/null +++ b/gemfiles/modular/rack/r2/v2.2.gemfile @@ -0,0 +1 @@ +gem "rack", "~> 2.2", ">= 2.2.21" diff --git a/gemfiles/modular/rack/r2/v3.2.gemfile b/gemfiles/modular/rack/r2/v3.2.gemfile new file mode 100644 index 0000000..f5cea69 --- /dev/null +++ b/gemfiles/modular/rack/r2/v3.2.gemfile @@ -0,0 +1 @@ +gem "rack", "~> 3.2", ">= 3.2.4" diff --git a/gemfiles/modular/rack/r3/v3.2.gemfile b/gemfiles/modular/rack/r3/v3.2.gemfile new file mode 100644 index 0000000..f5cea69 --- /dev/null +++ b/gemfiles/modular/rack/r3/v3.2.gemfile @@ -0,0 +1 @@ +gem "rack", "~> 3.2", ">= 3.2.4" diff --git a/gemfiles/modular/rack/vHEAD.gemfile b/gemfiles/modular/rack/vHEAD.gemfile new file mode 100644 index 0000000..ae66484 --- /dev/null +++ b/gemfiles/modular/rack/vHEAD.gemfile @@ -0,0 +1 @@ +gem "rack", :github => "rack/rack", :branch => "main" diff --git a/gemfiles/modular/runtime_heads.gemfile b/gemfiles/modular/runtime_heads.gemfile new file mode 100644 index 0000000..a51e134 --- /dev/null +++ b/gemfiles/modular/runtime_heads.gemfile @@ -0,0 +1,10 @@ +# frozen_string_literal: true + +# Test against HEAD of runtime dependencies so we can proactively file bugs + +# Ruby >= 2.2 +gem "version_gem", :github => "ruby-oauth/version_gem", :branch => "main" + +eval_gemfile("rack/vHEAD.gemfile") + +eval_gemfile("x_std_libs/vHEAD.gemfile") diff --git a/gemfiles/modular/stringio/r2.4/v0.0.2.gemfile b/gemfiles/modular/stringio/r2.4/v0.0.2.gemfile new file mode 100644 index 0000000..94021cf --- /dev/null +++ b/gemfiles/modular/stringio/r2.4/v0.0.2.gemfile @@ -0,0 +1,4 @@ +# !!WARNING!! +# NOT SEMVER +# Last version to support Ruby <= 2.5 +gem "stringio", ">= 0.0.2" diff --git a/gemfiles/modular/stringio/r2/v3.0.gemfile b/gemfiles/modular/stringio/r2/v3.0.gemfile new file mode 100644 index 0000000..e85bb18 --- /dev/null +++ b/gemfiles/modular/stringio/r2/v3.0.gemfile @@ -0,0 +1,5 @@ +# !!WARNING!! +# NOT SEMVER +# Version 3.0.7 dropped support for Ruby <= 2.7 +# Version 3.0.0 dropped support for Ruby <= 2.4 +gem "stringio", ">= 3.0" diff --git a/gemfiles/modular/stringio/r3/v3.0.gemfile b/gemfiles/modular/stringio/r3/v3.0.gemfile new file mode 100644 index 0000000..e85bb18 --- /dev/null +++ b/gemfiles/modular/stringio/r3/v3.0.gemfile @@ -0,0 +1,5 @@ +# !!WARNING!! +# NOT SEMVER +# Version 3.0.7 dropped support for Ruby <= 2.7 +# Version 3.0.0 dropped support for Ruby <= 2.4 +gem "stringio", ">= 3.0" diff --git a/gemfiles/modular/stringio/vHEAD.gemfile b/gemfiles/modular/stringio/vHEAD.gemfile new file mode 100644 index 0000000..22794f3 --- /dev/null +++ b/gemfiles/modular/stringio/vHEAD.gemfile @@ -0,0 +1,2 @@ +# Ruby >= 2.5 (dependency of omniauth) +gem "stringio", :github => "ruby/stringio", :branch => "master" diff --git a/gemfiles/modular/style.gemfile b/gemfiles/modular/style.gemfile new file mode 100644 index 0000000..3be1c01 --- /dev/null +++ b/gemfiles/modular/style.gemfile @@ -0,0 +1,25 @@ +# frozen_string_literal: true + +# We run rubocop on the latest version of Ruby, +# but in support of the oldest supported version of Ruby + +gem "reek", "~> 6.5" +# gem "rubocop", "~> 1.73", ">= 1.73.2" # constrained by standard +gem "rubocop-packaging", "~> 0.6", ">= 0.6.0" +gem "standard", ">= 1.50" +gem "rubocop-on-rbs", "~> 1.8" # ruby >= 3.1.0 + +# Std Lib extractions +gem "benchmark", "~> 0.4", ">= 0.4.1" # Removed from Std Lib in Ruby 3.5 + +if ENV.fetch("RUBOCOP_LTS_LOCAL", "false").casecmp("true").zero? + home = ENV["HOME"] + gem "rubocop-lts", :path => "#{home}/src/rubocop-lts/rubocop-lts" + gem "rubocop-lts-rspec", :path => "#{home}/src/rubocop-lts/rubocop-lts-rspec" + gem "rubocop-ruby1_8", :path => "#{home}/src/rubocop-lts/rubocop-ruby1_8" + gem "standard-rubocop-lts", :path => "#{home}/src/rubocop-lts/standard-rubocop-lts" +else + gem "rubocop-lts", "~> 0.1" + gem "rubocop-ruby1_8" + gem "rubocop-rspec", "~> 3.6" +end diff --git a/gemfiles/modular/x_std_libs.gemfile b/gemfiles/modular/x_std_libs.gemfile new file mode 100644 index 0000000..cb67775 --- /dev/null +++ b/gemfiles/modular/x_std_libs.gemfile @@ -0,0 +1,2 @@ +### Std Lib Extracted Gems +eval_gemfile "x_std_libs/r3/libs.gemfile" diff --git a/gemfiles/modular/x_std_libs/r2.3/libs.gemfile b/gemfiles/modular/x_std_libs/r2.3/libs.gemfile new file mode 100644 index 0000000..76539eb --- /dev/null +++ b/gemfiles/modular/x_std_libs/r2.3/libs.gemfile @@ -0,0 +1,4 @@ +eval_gemfile "../../erb/r2.3/default.gemfile" +eval_gemfile "../../mutex_m/r2.4/v0.1.gemfile" +eval_gemfile "../../stringio/r2.4/v0.0.2.gemfile" +eval_gemfile "../../logger/r2/v1.5.gemfile" diff --git a/gemfiles/modular/x_std_libs/r2.4/libs.gemfile b/gemfiles/modular/x_std_libs/r2.4/libs.gemfile new file mode 100644 index 0000000..d4bcb14 --- /dev/null +++ b/gemfiles/modular/x_std_libs/r2.4/libs.gemfile @@ -0,0 +1,4 @@ +eval_gemfile "../../erb/r2.6/v2.2.gemfile" +eval_gemfile "../../mutex_m/r2.4/v0.1.gemfile" +eval_gemfile "../../stringio/r2.4/v0.0.2.gemfile" +eval_gemfile "../../logger/r2.4/v1.5.3.gemfile" diff --git a/gemfiles/modular/x_std_libs/r2.6/libs.gemfile b/gemfiles/modular/x_std_libs/r2.6/libs.gemfile new file mode 100644 index 0000000..3245fd4 --- /dev/null +++ b/gemfiles/modular/x_std_libs/r2.6/libs.gemfile @@ -0,0 +1,4 @@ +eval_gemfile "../../erb/r2.6/v2.2.gemfile" +eval_gemfile "../../mutex_m/r2/v0.3.gemfile" +eval_gemfile "../../stringio/r2/v3.0.gemfile" +eval_gemfile "../../logger/r2/v1.5.gemfile" diff --git a/gemfiles/modular/x_std_libs/r2/libs.gemfile b/gemfiles/modular/x_std_libs/r2/libs.gemfile new file mode 100644 index 0000000..0e21572 --- /dev/null +++ b/gemfiles/modular/x_std_libs/r2/libs.gemfile @@ -0,0 +1,4 @@ +eval_gemfile "../../erb/r2/v3.0.gemfile" +eval_gemfile "../../mutex_m/r2/v0.3.gemfile" +eval_gemfile "../../stringio/r2/v3.0.gemfile" +eval_gemfile "../../logger/r2/v1.5.gemfile" diff --git a/gemfiles/modular/x_std_libs/r3.1/libs.gemfile b/gemfiles/modular/x_std_libs/r3.1/libs.gemfile new file mode 100644 index 0000000..c18fa6e --- /dev/null +++ b/gemfiles/modular/x_std_libs/r3.1/libs.gemfile @@ -0,0 +1,4 @@ +eval_gemfile "../../erb/r3.1/v4.0.gemfile" +eval_gemfile "../../mutex_m/r3/v0.3.gemfile" +eval_gemfile "../../stringio/r3/v3.0.gemfile" +eval_gemfile "../../logger/r3/v1.7.gemfile" diff --git a/gemfiles/modular/x_std_libs/r3/libs.gemfile b/gemfiles/modular/x_std_libs/r3/libs.gemfile new file mode 100644 index 0000000..4cb7b32 --- /dev/null +++ b/gemfiles/modular/x_std_libs/r3/libs.gemfile @@ -0,0 +1,4 @@ +eval_gemfile "../../erb/r3/v5.0.gemfile" +eval_gemfile "../../mutex_m/r3/v0.3.gemfile" +eval_gemfile "../../stringio/r3/v3.0.gemfile" +eval_gemfile "../../logger/r3/v1.7.gemfile" diff --git a/gemfiles/modular/x_std_libs/vHEAD.gemfile b/gemfiles/modular/x_std_libs/vHEAD.gemfile new file mode 100644 index 0000000..cc6e806 --- /dev/null +++ b/gemfiles/modular/x_std_libs/vHEAD.gemfile @@ -0,0 +1,4 @@ +eval_gemfile "../erb/vHEAD.gemfile" +eval_gemfile "../mutex_m/vHEAD.gemfile" +eval_gemfile "../stringio/vHEAD.gemfile" +eval_gemfile "../logger/vHEAD.gemfile" diff --git a/gemfiles/ruby_2_3.gemfile b/gemfiles/ruby_2_3.gemfile new file mode 100644 index 0000000..93dfbbf --- /dev/null +++ b/gemfiles/ruby_2_3.gemfile @@ -0,0 +1,11 @@ +# This file was generated by Appraisal2 + +source "https://gem.coop" + +gemspec :path => "../" + +eval_gemfile("modular/omniauth/r2/v1.1.gemfile") + +eval_gemfile("modular/rack/r2.1/v1.6.gemfile") + +eval_gemfile("modular/x_std_libs/r2.3/libs.gemfile") diff --git a/gemfiles/ruby_2_4.gemfile b/gemfiles/ruby_2_4.gemfile new file mode 100644 index 0000000..fc574a4 --- /dev/null +++ b/gemfiles/ruby_2_4.gemfile @@ -0,0 +1,11 @@ +# This file was generated by Appraisal2 + +source "https://gem.coop" + +gemspec :path => "../" + +eval_gemfile("modular/omniauth/r2/v1.5.gemfile") + +eval_gemfile("modular/rack/r2.3/v2.1.gemfile") + +eval_gemfile("modular/x_std_libs/r2.4/libs.gemfile") diff --git a/gemfiles/ruby_2_5.gemfile b/gemfiles/ruby_2_5.gemfile new file mode 100644 index 0000000..5436566 --- /dev/null +++ b/gemfiles/ruby_2_5.gemfile @@ -0,0 +1,11 @@ +# This file was generated by Appraisal2 + +source "https://gem.coop" + +gemspec :path => "../" + +eval_gemfile("modular/omniauth/r2/v1.9.gemfile") + +eval_gemfile("modular/rack/r2.3/v2.2.gemfile") + +eval_gemfile("modular/x_std_libs/r2.6/libs.gemfile") diff --git a/gemfiles/ruby_2_6.gemfile b/gemfiles/ruby_2_6.gemfile new file mode 100644 index 0000000..c565538 --- /dev/null +++ b/gemfiles/ruby_2_6.gemfile @@ -0,0 +1,11 @@ +# This file was generated by Appraisal2 + +source "https://gem.coop" + +gemspec :path => "../" + +eval_gemfile("modular/omniauth/r2/v2.0.gemfile") + +eval_gemfile("modular/rack/r2/v2.2.gemfile") + +eval_gemfile("modular/x_std_libs/r2.6/libs.gemfile") diff --git a/gemfiles/ruby_2_7.gemfile b/gemfiles/ruby_2_7.gemfile new file mode 100644 index 0000000..9d497a9 --- /dev/null +++ b/gemfiles/ruby_2_7.gemfile @@ -0,0 +1,11 @@ +# This file was generated by Appraisal2 + +source "https://gem.coop" + +gemspec :path => "../" + +eval_gemfile("modular/omniauth/r2/v2.1.gemfile") + +eval_gemfile("modular/rack/r2/v3.2.gemfile") + +eval_gemfile("modular/x_std_libs/r2/libs.gemfile") diff --git a/gemfiles/ruby_3_0.gemfile b/gemfiles/ruby_3_0.gemfile new file mode 100644 index 0000000..b0c229d --- /dev/null +++ b/gemfiles/ruby_3_0.gemfile @@ -0,0 +1,11 @@ +# This file was generated by Appraisal2 + +source "https://gem.coop" + +gemspec :path => "../" + +eval_gemfile("modular/omniauth/r3/v2.1.gemfile") + +eval_gemfile("modular/rack/r3/v3.2.gemfile") + +eval_gemfile("modular/x_std_libs/r3.1/libs.gemfile") diff --git a/gemfiles/ruby_3_1.gemfile b/gemfiles/ruby_3_1.gemfile new file mode 100644 index 0000000..b0c229d --- /dev/null +++ b/gemfiles/ruby_3_1.gemfile @@ -0,0 +1,11 @@ +# This file was generated by Appraisal2 + +source "https://gem.coop" + +gemspec :path => "../" + +eval_gemfile("modular/omniauth/r3/v2.1.gemfile") + +eval_gemfile("modular/rack/r3/v3.2.gemfile") + +eval_gemfile("modular/x_std_libs/r3.1/libs.gemfile") diff --git a/gemfiles/ruby_3_2.gemfile b/gemfiles/ruby_3_2.gemfile new file mode 100644 index 0000000..690fdd5 --- /dev/null +++ b/gemfiles/ruby_3_2.gemfile @@ -0,0 +1,11 @@ +# This file was generated by Appraisal2 + +source "https://gem.coop" + +gemspec :path => "../" + +eval_gemfile("modular/omniauth/r3/v2.1.gemfile") + +eval_gemfile("modular/rack/r3/v3.2.gemfile") + +eval_gemfile("modular/x_std_libs/r3/libs.gemfile") diff --git a/gemfiles/ruby_3_3.gemfile b/gemfiles/ruby_3_3.gemfile new file mode 100644 index 0000000..690fdd5 --- /dev/null +++ b/gemfiles/ruby_3_3.gemfile @@ -0,0 +1,11 @@ +# This file was generated by Appraisal2 + +source "https://gem.coop" + +gemspec :path => "../" + +eval_gemfile("modular/omniauth/r3/v2.1.gemfile") + +eval_gemfile("modular/rack/r3/v3.2.gemfile") + +eval_gemfile("modular/x_std_libs/r3/libs.gemfile") diff --git a/gemfiles/style.gemfile b/gemfiles/style.gemfile new file mode 100644 index 0000000..e4e8ff7 --- /dev/null +++ b/gemfiles/style.gemfile @@ -0,0 +1,9 @@ +# This file was generated by Appraisal2 + +source "https://gem.coop" + +gemspec :path => "../" + +eval_gemfile("modular/style.gemfile") + +eval_gemfile("modular/x_std_libs.gemfile") diff --git a/gemfiles/unlocked_deps.gemfile b/gemfiles/unlocked_deps.gemfile new file mode 100644 index 0000000..a3d951e --- /dev/null +++ b/gemfiles/unlocked_deps.gemfile @@ -0,0 +1,15 @@ +# This file was generated by Appraisal2 + +source "https://gem.coop" + +gemspec :path => "../" + +eval_gemfile("modular/coverage.gemfile") + +eval_gemfile("modular/documentation.gemfile") + +eval_gemfile("modular/optional.gemfile") + +eval_gemfile("modular/style.gemfile") + +eval_gemfile("modular/x_std_libs.gemfile") diff --git a/lib/omniauth-ldap.rb b/lib/omniauth-ldap.rb index e2eb5b4..f294db4 100644 --- a/lib/omniauth-ldap.rb +++ b/lib/omniauth-ldap.rb @@ -1,4 +1,9 @@ +require "version_gem" + require "omniauth-ldap/version" require "omniauth-ldap/adaptor" -require 'omniauth/strategies/ldap' +require "omniauth/strategies/ldap" +OmniAuth::LDAP::Version.class_eval do + extend VersionGem::Basic +end diff --git a/lib/omniauth-ldap/adaptor.rb b/lib/omniauth-ldap/adaptor.rb index c8a123a..ed295f1 100644 --- a/lib/omniauth-ldap/adaptor.rb +++ b/lib/omniauth-ldap/adaptor.rb @@ -1,10 +1,17 @@ -#this code borrowed pieces from activeldap and net-ldap +# this code borrowed pieces from activeldap and net-ldap + +# nkf/kconv has been part of Ruby since long ago. +# Eventually it became a standard gem, but was removed from stdlib in Ruby 3.4. +# In general, kconv and iconv have been superseded since Ruby 1.9 by the built-in +# encoding support provided by String#encode, String#force_encoding, and similar methods. +require "nkf" + +# External Gems +require "net/ldap" +require "net/ntlm" +require "rack" +require "sasl" -require 'rack' -require 'net/ldap' -require 'net/ntlm' -require 'sasl' -require 'kconv' module OmniAuth module LDAP class Adaptor @@ -26,18 +33,19 @@ class ConnectionError < StandardError; end attr_accessor :bind_dn, :password attr_reader :connection, :uid, :base, :auth, :filter - def self.validate(configuration={}) + def self.validate(configuration = {}) message = [] MUST_HAVE_KEYS.each do |names| names = [names].flatten - missing_keys = names.select{|name| configuration[name].nil?} + missing_keys = names.select { |name| configuration[name].nil? } if missing_keys == names - message << names.join(' or ') + message << names.join(" or ") end end - raise ArgumentError.new(message.join(",") +" MUST be provided") unless message.empty? + raise ArgumentError.new(message.join(",") + " MUST be provided") unless message.empty? end - def initialize(configuration={}) + + def initialize(configuration = {}) Adaptor.validate(configuration) @configuration = configuration.dup @configuration[:allow_anonymous] ||= false @@ -49,16 +57,20 @@ def initialize(configuration={}) config = { :host => @host, :port => @port, - :base => @base + :base => @base, } - @bind_method = @try_sasl ? :sasl : (@allow_anonymous||!@bind_dn||!@password ? :anonymous : :simple) - + @bind_method = if @try_sasl + :sasl + else + ((@allow_anonymous || !@bind_dn || !@password) ? :anonymous : :simple) + end @auth = sasl_auths({:username => @bind_dn, :password => @password}).first if @bind_method == :sasl - @auth ||= { :method => @bind_method, - :username => @bind_dn, - :password => @password - } + @auth ||= { + :method => @bind_method, + :username => @bind_dn, + :password => @password, + } config[:auth] = @auth config[:encryption] = method @connection = Net::LDAP.new(config) @@ -70,16 +82,19 @@ def initialize(configuration={}) def bind_as(args = {}) result = false @connection.open do |me| - rs = me.search args + rs = me.search(args) if rs and rs.first and dn = rs.first.dn password = args[:password] method = args[:method] || @method password = password.call if password.respond_to?(:call) - if method == 'sasl' - result = rs.first if me.bind(sasl_auths({:username => dn, :password => password}).first) - else - result = rs.first if me.bind(:method => :simple, :username => dn, - :password => password) + if method == "sasl" + result = rs.first if me.bind(sasl_auths({:username => dn, :password => password}).first) + elsif me.bind( + :method => :simple, + :username => dn, + :password => password, + ) + result = rs.first end end end @@ -87,21 +102,22 @@ def bind_as(args = {}) end private + def ensure_method(method) - method ||= "plain" - normalized_method = method.to_s.downcase.to_sym - return METHOD[normalized_method] if METHOD.has_key?(normalized_method) + method ||= "plain" + normalized_method = method.to_s.downcase.to_sym + return METHOD[normalized_method] if METHOD.has_key?(normalized_method) - available_methods = METHOD.keys.collect {|m| m.inspect}.join(", ") - format = "%s is not one of the available connect methods: %s" - raise ConfigurationError, format % [method.inspect, available_methods] + available_methods = METHOD.keys.collect { |m| m.inspect }.join(", ") + format = "%s is not one of the available connect methods: %s" + raise ConfigurationError, format % [method.inspect, available_methods] end - def sasl_auths(options={}) + def sasl_auths(options = {}) auths = [] sasl_mechanisms = options[:sasl_mechanisms] || @sasl_mechanisms sasl_mechanisms.each do |mechanism| - normalized_mechanism = mechanism.downcase.gsub(/-/, '_') + normalized_mechanism = mechanism.downcase.tr("-", "_") sasl_bind_setup = "sasl_bind_setup_#{normalized_mechanism}" next unless respond_to?(sasl_bind_setup, true) initial_credential, challenge_response = send(sasl_bind_setup, options) @@ -109,7 +125,7 @@ def sasl_auths(options={}) :method => :sasl, :initial_credential => initial_credential, :mechanism => mechanism, - :challenge_response => challenge_response + :challenge_response => challenge_response, } end auths @@ -118,8 +134,8 @@ def sasl_auths(options={}) def sasl_bind_setup_digest_md5(options) bind_dn = options[:username] initial_credential = "" - challenge_response = Proc.new do |cred| - pref = SASL::Preferences.new :digest_uri => "ldap/#{@host}", :username => bind_dn, :has_password? => true, :password => options[:password] + challenge_response = proc do |cred| + pref = SASL::Preferences.new(:digest_uri => "ldap/#{@host}", :username => bind_dn, :has_password? => true, :password => options[:password]) sasl = SASL.new("DIGEST-MD5", pref) response = sasl.receive("challenge", cred) response[1] @@ -130,18 +146,17 @@ def sasl_bind_setup_digest_md5(options) def sasl_bind_setup_gss_spnego(options) bind_dn = options[:username] psw = options[:password] - raise LdapError.new( "invalid binding information" ) unless (bind_dn && psw) + raise LdapError.new("invalid binding information") unless bind_dn && psw - nego = proc {|challenge| - t2_msg = Net::NTLM::Message.parse( challenge ) - bind_dn, domain = bind_dn.split('\\').reverse - t2_msg.target_name = Net::NTLM::encode_utf16le(domain) if domain - t3_msg = t2_msg.response( {:user => bind_dn, :password => psw}, {:ntlmv2 => true} ) + nego = proc { |challenge| + t2_msg = Net::NTLM::Message.parse(challenge) + bind_dn, domain = bind_dn.split("\\").reverse + t2_msg.target_name = Net::NTLM.encode_utf16le(domain) if domain + t3_msg = t2_msg.response({:user => bind_dn, :password => psw}, {:ntlmv2 => true}) t3_msg.serialize } [Net::NTLM::Message::Type1.new.serialize, nego] end - end end end diff --git a/lib/omniauth-ldap/version.rb b/lib/omniauth-ldap/version.rb index 507a2af..e210899 100644 --- a/lib/omniauth-ldap/version.rb +++ b/lib/omniauth-ldap/version.rb @@ -1,5 +1,8 @@ module OmniAuth module LDAP - VERSION = "2.0.0" + module Version + VERSION = "2.0.0" + end + VERSION = Version::VERSION # Make VERSION available in traditional way end end diff --git a/lib/omniauth/strategies/ldap.rb b/lib/omniauth/strategies/ldap.rb index 9a4d880..bdcd63b 100644 --- a/lib/omniauth/strategies/ldap.rb +++ b/lib/omniauth/strategies/ldap.rb @@ -1,59 +1,60 @@ -require 'omniauth' +require "omniauth" module OmniAuth module Strategies class LDAP include OmniAuth::Strategy + @@config = { - 'name' => 'cn', - 'first_name' => 'givenName', - 'last_name' => 'sn', - 'email' => ['mail', "email", 'userPrincipalName'], - 'phone' => ['telephoneNumber', 'homePhone', 'facsimileTelephoneNumber'], - 'mobile' => ['mobile', 'mobileTelephoneNumber'], - 'nickname' => ['uid', 'userid', 'sAMAccountName'], - 'title' => 'title', - 'location' => {"%0, %1, %2, %3 %4" => [['address', 'postalAddress', 'homePostalAddress', 'street', 'streetAddress'], ['l'], ['st'],['co'],['postOfficeBox']]}, - 'uid' => 'dn', - 'url' => ['wwwhomepage'], - 'image' => 'jpegPhoto', - 'description' => 'description' + "name" => "cn", + "first_name" => "givenName", + "last_name" => "sn", + "email" => ["mail", "email", "userPrincipalName"], + "phone" => ["telephoneNumber", "homePhone", "facsimileTelephoneNumber"], + "mobile" => ["mobile", "mobileTelephoneNumber"], + "nickname" => ["uid", "userid", "sAMAccountName"], + "title" => "title", + "location" => {"%0, %1, %2, %3 %4" => [["address", "postalAddress", "homePostalAddress", "street", "streetAddress"], ["l"], ["st"], ["co"], ["postOfficeBox"]]}, + "uid" => "dn", + "url" => ["wwwhomepage"], + "image" => "jpegPhoto", + "description" => "description", } - option :title, "LDAP Authentication" #default title for authentication form + option :title, "LDAP Authentication" # default title for authentication form option :port, 389 option :method, :plain - option :uid, 'sAMAccountName' - option :name_proc, lambda {|n| n} + option :uid, "sAMAccountName" + option :name_proc, lambda { |n| n } def request_phase - OmniAuth::LDAP::Adaptor.validate @options - f = OmniAuth::Form.new(:title => (options[:title] || "LDAP Authentication"), :url => callback_path) - f.text_field 'Login', 'username' - f.password_field 'Password', 'password' - f.button "Sign In" + OmniAuth::LDAP::Adaptor.validate(@options) + f = OmniAuth::Form.new(:title => options[:title] || "LDAP Authentication", :url => callback_path) + f.text_field("Login", "username") + f.password_field("Password", "password") + f.button("Sign In") f.to_response end def callback_phase - @adaptor = OmniAuth::LDAP::Adaptor.new @options + @adaptor = OmniAuth::LDAP::Adaptor.new(@options) return fail!(:missing_credentials) if missing_credentials? begin - @ldap_user_info = @adaptor.bind_as(:filter => filter(@adaptor), :size => 1, :password => request['password']) + @ldap_user_info = @adaptor.bind_as(:filter => filter(@adaptor), :size => 1, :password => request.params["password"]) return fail!(:invalid_credentials) if !@ldap_user_info @user_info = self.class.map_user(@@config, @ldap_user_info) super rescue Exception => e - return fail!(:ldap_error, e) + fail!(:ldap_error, e) end end def filter adaptor if adaptor.filter and !adaptor.filter.empty? - Net::LDAP::Filter.construct(adaptor.filter % {username: @options[:name_proc].call(request['username'])}) + Net::LDAP::Filter.construct(adaptor.filter % {:username => @options[:name_proc].call(request.params["username"])}) else - Net::LDAP::Filter.eq(adaptor.uid, @options[:name_proc].call(request['username'])) + Net::LDAP::Filter.eq(adaptor.uid, @options[:name_proc].call(request.params["username"])) end end @@ -64,7 +65,7 @@ def filter adaptor @user_info } extra { - { :raw_info => @ldap_user_info } + {:raw_info => @ldap_user_info} } def self.map_user(mapper, object) @@ -72,15 +73,24 @@ def self.map_user(mapper, object) mapper.each do |key, value| case value when String - user[key] = object[value.downcase.to_sym].first if object.respond_to? value.downcase.to_sym + user[key] = object[value.downcase.to_sym].first if object.respond_to?(value.downcase.to_sym) when Array - value.each {|v| (user[key] = object[v.downcase.to_sym].first; break;) if object.respond_to? v.downcase.to_sym} + value.each { |v| + (user[key] = object[v.downcase.to_sym].first + break + ) if object.respond_to?(v.downcase.to_sym) + } when Hash value.map do |key1, value1| pattern = key1.dup - value1.each_with_index do |v,i| - part = ''; v.collect(&:downcase).collect(&:to_sym).each {|v1| (part = object[v1].first; break;) if object.respond_to? v1} - pattern.gsub!("%#{i}",part||'') + value1.each_with_index do |v, i| + part = "" + v.collect(&:downcase).collect(&:to_sym).each { |v1| + (part = object[v1].first + break + ) if object.respond_to?(v1) + } + pattern.gsub!("%#{i}", part || "") end user[key] = pattern end @@ -92,10 +102,10 @@ def self.map_user(mapper, object) protected def missing_credentials? - request['username'].nil? or request['username'].empty? or request['password'].nil? or request['password'].empty? + request.params["username"].nil? or request.params["username"].empty? or request.params["password"].nil? or request.params["password"].empty? end # missing_credentials? end end end -OmniAuth.config.add_camelization 'ldap', 'LDAP' +OmniAuth.config.add_camelization("ldap", "LDAP") diff --git a/omniauth-ldap.gemspec b/omniauth-ldap.gemspec index 42bd276..e95e03e 100644 --- a/omniauth-ldap.gemspec +++ b/omniauth-ldap.gemspec @@ -1,26 +1,166 @@ -# -*- encoding: utf-8 -*- -require File.expand_path('../lib/omniauth-ldap/version', __FILE__) - -Gem::Specification.new do |gem| - gem.authors = ["Ping Yu", "Tom Milewski"] - gem.email = ["ping@intridea.com", "tmilewski@gmail.com"] - gem.description = %q{A LDAP strategy for OmniAuth.} - gem.summary = %q{A LDAP strategy for OmniAuth.} - gem.homepage = "https://github.com/intridea/omniauth-ldap" - gem.license = "MIT" - - gem.add_runtime_dependency 'omniauth', '~> 2.0.0' - gem.add_runtime_dependency 'net-ldap', '~> 0.16' - gem.add_runtime_dependency 'pyu-ruby-sasl', '~> 0.0.3.3' - gem.add_runtime_dependency 'rubyntlm', '~> 0.6.2' - gem.add_development_dependency 'rspec', '~> 3.0' - gem.add_development_dependency 'simplecov', '~> 0.11' - gem.add_development_dependency 'rack-test', '~> 0.6', '>= 0.6.3' - - gem.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) } - gem.files = `git ls-files`.split("\n") - gem.test_files = `git ls-files -- {test,spec,features}/*`.split("\n") - gem.name = "omniauth-ldap" - gem.require_paths = ["lib"] - gem.version = OmniAuth::LDAP::VERSION +# coding: utf-8 +# frozen_string_literal: true + +gem_version = + if RUBY_VERSION >= "3.1" # rubocop:disable Gemspec/RubyVersionGlobalsUsage + # Loading Version into an anonymous module allows version.rb to get code coverage from SimpleCov! + # See: https://github.com/simplecov-ruby/simplecov/issues/557#issuecomment-2630782358 + # See: https://github.com/panorama-ed/memo_wise/pull/397 + Module.new.tap { |mod| Kernel.load("#{__dir__}/lib/omniauth-ldap/version.rb", mod) }::OmniAuth::LDAP::Version::VERSION + else + # NOTE: Use __FILE__ or __dir__ until removal of Ruby 1.x support + # __dir__ introduced in Ruby 1.9.1 + # lib = File.expand_path("../lib", __FILE__) + lib = File.expand_path("lib", __dir__) + $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib) + require "omniauth-ldap/version" + OmniAuth::LDAP::Version::VERSION + end + +Gem::Specification.new do |spec| + spec.name = "omniauth-ldap" + spec.version = gem_version + spec.authors = ["Peter Boling", "Ping Yu", "Tom Milewski"] + spec.email = ["floss@galtzo.com"] + + spec.summary = "📁 LDAP strategy for OmniAuth." + spec.description = "📁 LDAP strategy for OmniAuth." + spec.homepage = "https://github.com/omniauth/omniauth-ldap" + spec.licenses = ["MIT"] + spec.required_ruby_version = ">= 0" + + # Linux distros often package gems and securely certify them independent + # of the official RubyGem certification process. Allowed via ENV["SKIP_GEM_SIGNING"] + # Ref: https://gitlab.com/ruby-oauth/version_gem/-/issues/3 + # Hence, only enable signing if `SKIP_GEM_SIGNING` is not set in ENV. + # See CONTRIBUTING.md + unless ENV.include?("SKIP_GEM_SIGNING") + user_cert = "certs/#{ENV.fetch("GEM_CERT_USER", ENV["USER"])}.pem" + cert_file_path = File.join(__dir__, user_cert) + cert_chain = cert_file_path.split(",") + cert_chain.select! { |fp| File.exist?(fp) } + if cert_file_path && cert_chain.any? + spec.cert_chain = cert_chain + if $PROGRAM_NAME.end_with?("gem") && ARGV[0] == "build" + spec.signing_key = File.join(Gem.user_home, ".ssh", "gem-private_key.pem") + end + end + end + + spec.metadata["homepage_uri"] = "https://#{spec.name.tr("_", "-")}.galtzo.com/" + 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["funding_uri"] = "https://github.com/sponsors/pboling" + spec.metadata["wiki_uri"] = "#{spec.homepage}/wiki" + spec.metadata["news_uri"] = "https://www.railsbling.com/tags/#{spec.name}" + spec.metadata["discord_uri"] = "https://discord.gg/3qme4XHNKN" + spec.metadata["rubygems_mfa_required"] = "true" + + # Specify which files are part of the released package. + spec.files = Dir[ + # Code / tasks / data (NOTE: exe/ is specified via spec.bindir and spec.executables below) + "lib/**/*.rb", + "lib/**/*.rake", + # Signatures + "sig/**/*.rbs", + ] + + # Automatically included with gem package, no need to list again in files. + spec.extra_rdoc_files = Dir[ + # Files (alphabetical) + "CHANGELOG.md", + "CITATION.cff", + "CODE_OF_CONDUCT.md", + "CONTRIBUTING.md", + "FUNDING.md", + "LICENSE.txt", + "README.md", + "REEK", + "RUBOCOP.md", + "SECURITY.md", + ] + spec.rdoc_options += [ + "--title", + "#{spec.name} - #{spec.summary}", + "--main", + "README.md", + "--exclude", + "^sig/", + "--line-numbers", + "--inline-source", + "--quiet", + ] + spec.require_paths = ["lib"] + spec.bindir = "exe" + # Listed files are the relative paths from bindir above. + spec.executables = [] + + spec.add_dependency("net-ldap", "~> 0.16") # ruby >= 2.0 + # nkf/kconv has been part of Ruby since long ago. + # Eventually it became a standard gem, but was removed from stdlib in Ruby 3.4. + # In general, kconv and iconv have been superseded since Ruby 1.9 by the built-in + # encoding support provided by String#encode, String#force_encoding, and similar methods. + # spec.add_dependency("nkf") # ruby >= 2.3 + spec.add_dependency("omniauth", ">= 1") # ruby >= 0.0 + spec.add_dependency("pyu-ruby-sasl", "~> 0.0.3.3") # ruby >= 0.0 + spec.add_dependency("rack", ">= 1") # ruby >= 0.0 + spec.add_dependency("rubyntlm", "~> 0.6.2") # ruby >= 1.8.7 + + # Utilities + spec.add_dependency("version_gem", "~> 1.1", ">= 1.1.9") # ruby >= 2.2.0 + + # NOTE: It is preferable to list development dependencies in the gemspec due to increased + # visibility and discoverability. + # However, development dependencies in gemspec will install on + # all versions of Ruby that will run in CI. + # This gem, and its gemspec runtime dependencies, will install on Ruby down to 0. + # This gem, and its gemspec development dependencies, will install on Ruby down to 2.3. + # Thus, dev dependencies in gemspec must have + # + # required_ruby_version ">= 2.3" (or lower) + # + # Development dependencies that require strictly newer Ruby versions should be in a "gemfile", + # and preferably a modular one (see gemfiles/modular/*.gemfile). + + # Dev, Test, & Release Tasks + spec.add_development_dependency("kettle-dev", "~> 1.1") # ruby >= 2.3.0 + + # Security + spec.add_development_dependency("bundler-audit", "~> 0.9.2") # ruby >= 2.0.0 + + # Tasks + spec.add_development_dependency("rake", "~> 13.0") # ruby >= 2.2.0 + + # Debugging + spec.add_development_dependency("require_bench", "~> 1.0", ">= 1.0.4") # ruby >= 2.2.0 + + # Testing + spec.add_development_dependency("appraisal2", "~> 3.0") # ruby >= 1.8.7, for testing against multiple versions of dependencies + spec.add_development_dependency("kettle-test", "~> 1.0", ">= 1.0.6") # ruby >= 2.3 + spec.add_development_dependency("rack-test", "~> 2.2") # ruby >= 2.0 + + # Releasing + spec.add_development_dependency("ruby-progressbar", "~> 1.13") # ruby >= 0 + spec.add_development_dependency("stone_checksums", "~> 1.0", ">= 1.0.2") # ruby >= 2.2.0 + + # Git integration (optional) + # The 'git' gem is optional; omniauth-ldap falls back to shelling out to `git` if it is not present. + # The current release of the git gem depends on activesupport, which makes it too heavy to depend on directly + # spec.add_dependency("git", ">= 1.19.1") # ruby >= 2.3 + + # Development tasks + # The cake is a lie. erb v2.2, the oldest release, was never compatible with Ruby 2.3. + # This means we have no choice but to use the erb that shipped with Ruby 2.3 + # /opt/hostedtoolcache/Ruby/2.3.8/x64/lib/ruby/gems/2.3.0/gems/erb-2.2.2/lib/erb.rb:670:in `prepare_trim_mode': undefined method `match?' for "-":String (NoMethodError) + # spec.add_development_dependency("erb", ">= 2.2") # ruby >= 2.3.0, not SemVer, old rubies get dropped in a patch. + spec.add_development_dependency("gitmoji-regex", "~> 1.0", ">= 1.0.3") # ruby >= 2.3.0 + + # HTTP recording for deterministic specs + # In Ruby 3.5 (HEAD) the CGI library has been pared down, so we also need to depend on gem "cgi" for ruby@head + # This is done in the "head" appraisal. + # See: https://github.com/vcr/vcr/issues/1057 + spec.add_development_dependency("vcr", ">= 4") # 6.0 claims to support ruby >= 2.3, but fails on ruby 2.4 + spec.add_development_dependency("webmock", ">= 3") # Last version to support ruby >= 2.3 end diff --git a/spec/config/debug.rb b/spec/config/debug.rb new file mode 100644 index 0000000..3ec9afe --- /dev/null +++ b/spec/config/debug.rb @@ -0,0 +1,5 @@ +$LOAD_PATH.each { |p| puts p } +load_debugger = ENV.fetch("DEBUG", "false").casecmp("true").zero? +puts "LOADING DEBUGGER: #{load_debugger}" if load_debugger + +require "debug" if load_debugger diff --git a/spec/config/omniauth.rb b/spec/config/omniauth.rb new file mode 100644 index 0000000..4c36492 --- /dev/null +++ b/spec/config/omniauth.rb @@ -0,0 +1,7 @@ +require "logger" +require "omniauth" + +if OmniAuth.config.respond_to?(:logger=) + logger = Logger.new($stdout) + OmniAuth.config.logger = logger +end diff --git a/spec/config/rspec/rack_test.rb b/spec/config/rspec/rack_test.rb new file mode 100644 index 0000000..0fc97ca --- /dev/null +++ b/spec/config/rspec/rack_test.rb @@ -0,0 +1,5 @@ +require "rack/test" + +RSpec.configure do |config| + config.include Rack::Test::Methods +end diff --git a/spec/config/vcr.rb b/spec/config/vcr.rb new file mode 100644 index 0000000..666a45e --- /dev/null +++ b/spec/config/vcr.rb @@ -0,0 +1,12 @@ +# VCR/WebMock: record and replay HTTP to external services (RubyGems, GitHub, etc.) +# require "webmock/rspec" +# require "vcr" +# VCR.configure do |c| +# c.cassette_library_dir = File.join(__dir__, "..", "support", "fixtures", "vcr_cassettes") +# c.hook_into :webmock +# c.configure_rspec_metadata! +# c.default_cassette_options = {record: :once} +# # Allow localhost and code coverage server, block others by default when no cassette +# c.ignore_localhost = true +# end +# WebMock.disable_net_connect!(allow_localhost: true) diff --git a/spec/omniauth-ldap/adaptor_spec.rb b/spec/omniauth-ldap/adaptor_spec.rb index a4200c0..aa1300d 100644 --- a/spec/omniauth-ldap/adaptor_spec.rb +++ b/spec/omniauth-ldap/adaptor_spec.rb @@ -1,82 +1,82 @@ -require 'spec_helper' -describe "OmniAuth::LDAP::Adaptor" do +# frozen_string_literal: true - describe 'initialize' do - it 'should throw exception when must have field is not set' do +RSpec.describe "OmniAuth::LDAP::Adaptor" do + describe "initialize" do + it "throws exception when must have field is not set" do #[:host, :port, :method, :bind_dn] expect { - OmniAuth::LDAP::Adaptor.new({ host: "192.168.1.145", method: 'plain' }) + OmniAuth::LDAP::Adaptor.new({:host => "192.168.1.145", :method => "plain"}) }.to raise_error(ArgumentError) end - it 'should throw exception when method is not supported' do + it "throws exception when method is not supported" do expect { - OmniAuth::LDAP::Adaptor.new({ host: "192.168.1.145", method: 'myplain', uid: 'uid', port: 389, base: 'dc=com' }) + OmniAuth::LDAP::Adaptor.new({:host => "192.168.1.145", :method => "myplain", :uid => "uid", :port => 389, :base => "dc=com"}) }.to raise_error(OmniAuth::LDAP::Adaptor::ConfigurationError) end - it 'should setup ldap connection with anonymous' do - adaptor = OmniAuth::LDAP::Adaptor.new({ host: "192.168.1.145", method: 'plain', base: 'dc=intridea, dc=com', port: 389, uid: 'sAMAccountName' }) - expect(adaptor.connection).to_not be_nil - expect(adaptor.connection.host).to eq '192.168.1.145' + it "setups ldap connection with anonymous" do + adaptor = OmniAuth::LDAP::Adaptor.new({:host => "192.168.1.145", :method => "plain", :base => "dc=intridea, dc=com", :port => 389, :uid => "sAMAccountName"}) + expect(adaptor.connection).not_to be_nil + expect(adaptor.connection.host).to eq "192.168.1.145" expect(adaptor.connection.port).to eq 389 - expect(adaptor.connection.base).to eq 'dc=intridea, dc=com' - expect(adaptor.connection.instance_variable_get('@auth')).to eq({ method: :anonymous, username: nil, password: nil }) + expect(adaptor.connection.base).to eq "dc=intridea, dc=com" + expect(adaptor.connection.instance_variable_get(:@auth)).to eq({:method => :anonymous, :username => nil, :password => nil}) end - it 'should setup ldap connection with simple' do - adaptor = OmniAuth::LDAP::Adaptor.new({ host: "192.168.1.145", method: 'plain', base: 'dc=intridea, dc=com', port: 389, uid: 'sAMAccountName', bind_dn: 'bind_dn', password: 'password' }) - expect(adaptor.connection).to_not be_nil - expect(adaptor.connection.host).to eq '192.168.1.145' + it "setups ldap connection with simple" do + adaptor = OmniAuth::LDAP::Adaptor.new({:host => "192.168.1.145", :method => "plain", :base => "dc=intridea, dc=com", :port => 389, :uid => "sAMAccountName", :bind_dn => "bind_dn", :password => "password"}) + expect(adaptor.connection).not_to be_nil + expect(adaptor.connection.host).to eq "192.168.1.145" expect(adaptor.connection.port).to eq 389 - expect(adaptor.connection.base).to eq 'dc=intridea, dc=com' - expect(adaptor.connection.instance_variable_get('@auth')).to eq({ method: :simple, username: 'bind_dn', password: 'password' }) + expect(adaptor.connection.base).to eq "dc=intridea, dc=com" + expect(adaptor.connection.instance_variable_get(:@auth)).to eq({:method => :simple, :username => "bind_dn", :password => "password"}) end - it 'should setup ldap connection with sasl-md5' do - adaptor = OmniAuth::LDAP::Adaptor.new({ host: "192.168.1.145", method: 'plain', base: 'dc=intridea, dc=com', port: 389, uid: 'sAMAccountName', try_sasl: true, sasl_mechanisms: ["DIGEST-MD5"], bind_dn: 'bind_dn', password: 'password' }) - expect(adaptor.connection).to_not be_nil - expect(adaptor.connection.host).to eq '192.168.1.145' + it "setups ldap connection with sasl-md5" do + adaptor = OmniAuth::LDAP::Adaptor.new({:host => "192.168.1.145", :method => "plain", :base => "dc=intridea, dc=com", :port => 389, :uid => "sAMAccountName", :try_sasl => true, :sasl_mechanisms => ["DIGEST-MD5"], :bind_dn => "bind_dn", :password => "password"}) + expect(adaptor.connection).not_to be_nil + expect(adaptor.connection.host).to eq "192.168.1.145" expect(adaptor.connection.port).to eq 389 - expect(adaptor.connection.base).to eq 'dc=intridea, dc=com' - expect(adaptor.connection.instance_variable_get('@auth')[:method]).to eq :sasl - expect(adaptor.connection.instance_variable_get('@auth')[:mechanism]).to eq 'DIGEST-MD5' - expect(adaptor.connection.instance_variable_get('@auth')[:initial_credential]).to eq '' - expect(adaptor.connection.instance_variable_get('@auth')[:challenge_response]).to_not be_nil + expect(adaptor.connection.base).to eq "dc=intridea, dc=com" + expect(adaptor.connection.instance_variable_get(:@auth)[:method]).to eq :sasl + expect(adaptor.connection.instance_variable_get(:@auth)[:mechanism]).to eq "DIGEST-MD5" + expect(adaptor.connection.instance_variable_get(:@auth)[:initial_credential]).to eq "" + expect(adaptor.connection.instance_variable_get(:@auth)[:challenge_response]).not_to be_nil end - it 'should setup ldap connection with sasl-gss' do - adaptor = OmniAuth::LDAP::Adaptor.new({host: "192.168.1.145", method: 'plain', base: 'dc=intridea, dc=com', port: 389, uid: 'sAMAccountName', try_sasl: true, sasl_mechanisms: ["GSS-SPNEGO"], bind_dn: 'bind_dn', password: 'password'}) - expect(adaptor.connection).to_not be_nil - expect(adaptor.connection.host).to eq '192.168.1.145' + it "setups ldap connection with sasl-gss" do + adaptor = OmniAuth::LDAP::Adaptor.new({:host => "192.168.1.145", :method => "plain", :base => "dc=intridea, dc=com", :port => 389, :uid => "sAMAccountName", :try_sasl => true, :sasl_mechanisms => ["GSS-SPNEGO"], :bind_dn => "bind_dn", :password => "password"}) + expect(adaptor.connection).not_to be_nil + expect(adaptor.connection.host).to eq "192.168.1.145" expect(adaptor.connection.port).to eq 389 - expect(adaptor.connection.base).to eq 'dc=intridea, dc=com' - expect(adaptor.connection.instance_variable_get('@auth')[:method]).to eq :sasl - expect(adaptor.connection.instance_variable_get('@auth')[:mechanism]).to eq 'GSS-SPNEGO' - expect(adaptor.connection.instance_variable_get('@auth')[:initial_credential]).to match /^NTLMSSP/ - expect(adaptor.connection.instance_variable_get('@auth')[:challenge_response]).to_not be_nil + expect(adaptor.connection.base).to eq "dc=intridea, dc=com" + expect(adaptor.connection.instance_variable_get(:@auth)[:method]).to eq :sasl + expect(adaptor.connection.instance_variable_get(:@auth)[:mechanism]).to eq "GSS-SPNEGO" + expect(adaptor.connection.instance_variable_get(:@auth)[:initial_credential]).to match(/^NTLMSSP/) + expect(adaptor.connection.instance_variable_get(:@auth)[:challenge_response]).not_to be_nil end - it 'should set the encryption method correctly' do - adaptor = OmniAuth::LDAP::Adaptor.new({ host: "192.168.1.145", method: 'tls', base: 'dc=intridea, dc=com', port: 389, uid: 'sAMAccountName' }) - expect(adaptor.connection.instance_variable_get('@encryption')).to include method: :start_tls + it "sets the encryption method correctly" do + adaptor = OmniAuth::LDAP::Adaptor.new({:host => "192.168.1.145", :method => "tls", :base => "dc=intridea, dc=com", :port => 389, :uid => "sAMAccountName"}) + expect(adaptor.connection.instance_variable_get(:@encryption)).to include :method => :start_tls end end - describe 'bind_as' do - let(:args) { { :filter => Net::LDAP::Filter.eq('sAMAccountName', 'username'), :password => 'password', :size => 1 } } - let(:rs) { Struct.new(:dn).new('new dn') } + describe "bind_as" do + let(:args) { {:filter => Net::LDAP::Filter.eq("sAMAccountName", "username"), :password => "password", :size => 1} } + let(:rs) { Struct.new(:dn).new("new dn") } - it 'should bind simple' do - adaptor = OmniAuth::LDAP::Adaptor.new({ host: "192.168.1.126", method: 'plain', base: 'dc=score, dc=local', port: 389, uid: 'sAMAccountName', bind_dn: 'bind_dn', password: 'password' }) + it "binds simple" do + adaptor = OmniAuth::LDAP::Adaptor.new({:host => "192.168.1.126", :method => "plain", :base => "dc=score, dc=local", :port => 389, :uid => "sAMAccountName", :bind_dn => "bind_dn", :password => "password"}) expect(adaptor.connection).to receive(:open).and_yield(adaptor.connection) expect(adaptor.connection).to receive(:search).with(args).and_return([rs]) - expect(adaptor.connection).to receive(:bind).with({ :username => 'new dn', :password => args[:password], :method => :simple }).and_return(true) + expect(adaptor.connection).to receive(:bind).with({:username => "new dn", :password => args[:password], :method => :simple}).and_return(true) expect(adaptor.bind_as(args)).to eq rs end - it 'should bind sasl' do - adaptor = OmniAuth::LDAP::Adaptor.new({ host: "192.168.1.145", method: 'plain', base: 'dc=intridea, dc=com', port: 389, uid: 'sAMAccountName', try_sasl: true, sasl_mechanisms: ["GSS-SPNEGO"], bind_dn: 'bind_dn', password: 'password' }) + it "binds sasl" do + adaptor = OmniAuth::LDAP::Adaptor.new({:host => "192.168.1.145", :method => "plain", :base => "dc=intridea, dc=com", :port => 389, :uid => "sAMAccountName", :try_sasl => true, :sasl_mechanisms => ["GSS-SPNEGO"], :bind_dn => "bind_dn", :password => "password"}) expect(adaptor.connection).to receive(:open).and_yield(adaptor.connection) expect(adaptor.connection).to receive(:search).with(args).and_return([rs]) expect(adaptor.connection).to receive(:bind).and_return(true) diff --git a/spec/omniauth/strategies/ldap_spec.rb b/spec/omniauth/strategies/ldap_spec.rb index ff2784c..2ae5584 100644 --- a/spec/omniauth/strategies/ldap_spec.rb +++ b/spec/omniauth/strategies/ldap_spec.rb @@ -1,5 +1,6 @@ -require 'spec_helper' -describe "OmniAuth::Strategies::LDAP" do +# frozen_string_literal: true + +RSpec.describe "OmniAuth::Strategies::LDAP" do # :title => "My LDAP", # :host => '10.101.10.1', # :port => 389, @@ -14,143 +15,152 @@ class MyLdapProvider < OmniAuth::Strategies::LDAP; end let(:app) do Rack::Builder.new { use OmniAuth::Test::PhonySession - use MyLdapProvider, :name => 'ldap', :title => 'MyLdap Form', :host => '192.168.1.145', :base => 'dc=score, dc=local', :name_proc => Proc.new {|name| name.gsub(/@.*$/,'')} - run lambda { |env| [404, {'Content-Type' => 'text/plain'}, [env.key?('omniauth.auth').to_s]] } + use MyLdapProvider, :name => "ldap", :title => "MyLdap Form", :host => "192.168.1.145", :base => "dc=score, dc=local", :name_proc => proc { |name| name.gsub(/@.*$/, "") } + run lambda { |env| [404, {"Content-Type" => "text/plain"}, [env.key?("omniauth.auth").to_s]] } }.to_app end let(:session) do - last_request.env['rack.session'] + last_request.env["rack.session"] end - it 'should add a camelization for itself' do - expect(OmniAuth::Utils.camelize('ldap')).to eq 'LDAP' + it "adds a camelization for itself" do + expect(OmniAuth::Utils.camelize("ldap")).to eq "LDAP" end - describe 'get /auth/ldap' do - before(:each){ get '/auth/ldap' } + describe "get /auth/ldap" do + before { get "/auth/ldap" } - it 'should return 404' do - expect(last_response.status).to eq 404 - expect(last_response.body).to_not include("= Gem::Version.new("2.0.0") + it "returns 404" do + expect(last_response.status).to eq 404 + expect(last_response.body).not_to include(" 1 + + it "has a label of the form title" do + expect(last_response.body.scan("MyLdap Form").size).to be > 1 end end - describe 'post /auth/ldap/callback' do - before(:each) do - @adaptor = double(OmniAuth::LDAP::Adaptor, {:uid => 'ping'}) + describe "post /auth/ldap/callback" do + before do + @adaptor = double(OmniAuth::LDAP::Adaptor, {:uid => "ping"}) allow(@adaptor).to receive(:filter) allow(OmniAuth::LDAP::Adaptor).to receive(:new) { @adaptor } end - context 'failure' do - before(:each) do - allow(@adaptor).to receive(:bind_as) { false } + context "failure" do + before do + allow(@adaptor).to receive(:bind_as).and_return(false) end context "when username is not preset" do - it 'should redirect to error page' do - post('/auth/ldap/callback', {}) + it "redirects to error page" do + post("/auth/ldap/callback", {}) # expect(last_response).to be redirect expect(last_response).to be_redirect - expect(last_response.headers['Location']).to match %r{missing_credentials} + expect(last_response.headers["Location"]).to match %r{missing_credentials} end end context "when username is empty" do - it 'should redirect to error page' do - post('/auth/ldap/callback', { username: "" }) + it "redirects to error page" do + post("/auth/ldap/callback", {:username => ""}) expect(last_response).to be_redirect - expect(last_response.headers['Location']).to match %r{missing_credentials} + expect(last_response.headers["Location"]).to match %r{missing_credentials} end end context "when username is present" do context "and password is not preset" do - it 'should redirect to error page' do - post('/auth/ldap/callback', { username: "ping" }) + it "redirects to error page" do + post("/auth/ldap/callback", {:username => "ping"}) expect(last_response).to be_redirect - expect(last_response.headers['Location']).to match %r{missing_credentials} + expect(last_response.headers["Location"]).to match %r{missing_credentials} end end context "and password is empty" do - it 'should redirect to error page' do - post('/auth/ldap/callback', { username: "ping", password: "" }) + it "redirects to error page" do + post("/auth/ldap/callback", {:username => "ping", :password => ""}) expect(last_response).to be_redirect - expect(last_response.headers['Location']).to match %r{missing_credentials} + expect(last_response.headers["Location"]).to match %r{missing_credentials} end end end context "when username and password are present" do context "and bind on LDAP server failed" do - it 'should redirect to error page' do - post('/auth/ldap/callback', { username: 'ping', password: 'password' }) + it "redirects to error page" do + post("/auth/ldap/callback", {:username => "ping", :password => "password"}) expect(last_response).to be_redirect - expect(last_response.headers['Location']).to match %r{invalid_credentials} + expect(last_response.headers["Location"]).to match %r{invalid_credentials} end - context 'and filter is set' do - it 'should bind with filter' do - allow(@adaptor).to receive(:filter) { 'uid=%{username}' } - expect(Net::LDAP::Filter).to receive(:construct).with('uid=ping') - post('/auth/ldap/callback', { username: 'ping', password: 'password' }) + + context "and filter is set" do + it "binds with filter" do + allow(@adaptor).to receive(:filter).and_return("uid=%{username}") + expect(Net::LDAP::Filter).to receive(:construct).with("uid=ping") + post("/auth/ldap/callback", {:username => "ping", :password => "password"}) expect(last_response).to be_redirect - expect(last_response.headers['Location']).to match %r{invalid_credentials} + expect(last_response.headers["Location"]).to match %r{invalid_credentials} end end - end context "and communication with LDAP server caused an exception" do - before :each do - allow(@adaptor).to receive(:bind_as).and_throw(Exception.new('connection_error')) + before do + allow(@adaptor).to receive(:bind_as).and_throw(Exception.new("connection_error")) end - it 'should redirect to error page' do - post('/auth/ldap/callback', { username: "ping", password: "password" }) + it "redirects to error page" do + post("/auth/ldap/callback", {:username => "ping", :password => "password"}) expect(last_response).to be_redirect - expect(last_response.headers['Location']).to match %r{ldap_error} + expect(last_response.headers["Location"]).to match %r{ldap_error} end end end end - context 'success' do - let(:auth_hash){ last_request.env['omniauth.auth'] } + context "success" do + let(:auth_hash) { last_request.env["omniauth.auth"] } - before(:each) do + before do allow(@adaptor).to receive(:filter) - allow(@adaptor).to receive(:bind_as) { Net::LDAP::Entry.from_single_ldif_string( - %Q{dn: cn=ping, dc=intridea, dc=com + allow(@adaptor).to receive(:bind_as) { + Net::LDAP::Entry.from_single_ldif_string( + %{dn: cn=ping, dc=intridea, dc=com mail: ping@intridea.com givenname: Ping sn: Yu @@ -166,43 +176,44 @@ class MyLdapProvider < OmniAuth::Strategies::LDAP; end wwwhomepage: www.intridea.com jpegphoto: http://www.intridea.com/ping.jpg description: omniauth-ldap -} - )} +}, + ) + } end - it 'should not redirect to error page' do - post('/auth/ldap/callback', {:username => 'ping', :password => 'password'}) - expect(last_response).to_not be_redirect + it "does not redirect to error page" do + post("/auth/ldap/callback", {:username => "ping", :password => "password"}) + expect(last_response).not_to be_redirect end - context 'and filter is set' do - it 'should bind with filter' do - allow(@adaptor).to receive(:filter) { 'uid=%{username}' } - expect(Net::LDAP::Filter).to receive(:construct).with('uid=ping') - post('/auth/ldap/callback', { username: 'ping', password: 'password' }) + context "and filter is set" do + it "binds with filter" do + allow(@adaptor).to receive(:filter).and_return("uid=%{username}") + expect(Net::LDAP::Filter).to receive(:construct).with("uid=ping") + post("/auth/ldap/callback", {:username => "ping", :password => "password"}) - expect(last_response).to_not be_redirect + expect(last_response).not_to be_redirect end end - it 'should map user info to Auth Hash' do - post('/auth/ldap/callback', { username: 'ping', password: 'password' }) + it "maps user info to Auth Hash" do + post("/auth/ldap/callback", {:username => "ping", :password => "password"}) - expect(auth_hash.uid).to eq 'cn=ping, dc=intridea, dc=com' + expect(auth_hash.uid).to eq "cn=ping, dc=intridea, dc=com" info = auth_hash.info - expect(info.email).to eq 'ping@intridea.com' - expect(info.first_name).to eq 'Ping' - expect(info.last_name).to eq 'Yu' - expect(info.phone).to eq '555-555-5555' - expect(info.mobile).to eq '444-444-4444' - expect(info.nickname).to eq 'ping' - expect(info.title).to eq 'dev' - expect(info.location).to eq 'k street, Washington, DC, U.S.A 20001' - expect(info.url).to eq 'www.intridea.com' - expect(info.image).to eq 'http://www.intridea.com/ping.jpg' - expect(info.description).to eq 'omniauth-ldap' + expect(info.email).to eq "ping@intridea.com" + expect(info.first_name).to eq "Ping" + expect(info.last_name).to eq "Yu" + expect(info.phone).to eq "555-555-5555" + expect(info.mobile).to eq "444-444-4444" + expect(info.nickname).to eq "ping" + expect(info.title).to eq "dev" + expect(info.location).to eq "k street, Washington, DC, U.S.A 20001" + expect(info.url).to eq "www.intridea.com" + expect(info.image).to eq "http://www.intridea.com/ping.jpg" + expect(info.description).to eq "omniauth-ldap" end end end diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index be818c2..9d93e05 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -1,18 +1,45 @@ -$:.unshift File.expand_path('..', __FILE__) -$:.unshift File.expand_path('../../lib', __FILE__) -require 'simplecov' -SimpleCov.start -require 'rspec' -require 'rack/test' -require 'omniauth' -require 'omniauth-ldap' +# frozen_string_literal: true + +require "logger" +require "rack/test" + +# External RSpec & related config +require "kettle/test/rspec" + +# External library dependencies +require "omniauth" +require "omniauth/version" + +# RSpec Configs +require "config/debug" +require "config/omniauth" +require "config/rspec/rack_test" +require "config/vcr" + +# RSpec Support +spec_root_matcher = %r{#{__dir__}/(.+)\.rb\Z} +Dir.glob(Pathname.new(__dir__).join("support/**/", "*.rb")).each { |f| require f.match(spec_root_matcher)[1] } TEST_LOGGER = Logger.new(StringIO.new) OmniAuth.config.logger = TEST_LOGGER -OmniAuth.config.request_validation_phase = proc {} +# New config for OmniAuth 2.0+ +OmniAuth.config.request_validation_phase = proc {} if OmniAuth.config.respond_to?(:request_validation_phase=) RSpec.configure do |config| config.include Rack::Test::Methods - config.extend OmniAuth::Test::StrategyMacros, :type => :strategy + config.extend OmniAuth::Test::StrategyMacros, :type => :strategy +end + +# The last thing before loading this gem is to set up 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 => error + # check the error message and conditionally re-raise + raise error unless error.message.include?("kettle") end +# This gem +require "omniauth-ldap"