├── .codeclimate.yml
├── .coveralls.yml
├── .dockerignore
├── .editorconfig
├── .github
├── FUNDING.yml
├── ISSUE_TEMPLATE.md
├── PULL_REQUEST_TEMPLATE.md
├── dependabot.yml
└── workflows
│ ├── check_orm_changes.yml
│ └── ci.yml
├── .gitignore
├── .hound.yml
├── .rspec
├── .rubocop.yml
├── .rubocop_todo.yml
├── CHANGELOG.md
├── CODE_OF_CONDUCT.md
├── CONTRIBUTING.md
├── Dockerfile
├── Gemfile
├── MIT-LICENSE
├── NEWS.md
├── README.md
├── RELEASING.md
├── Rakefile
├── SECURITY.md
├── UPGRADE.md
├── app
├── assets
│ └── stylesheets
│ │ └── doorkeeper
│ │ ├── admin
│ │ └── application.css
│ │ └── application.css
├── controllers
│ └── doorkeeper
│ │ ├── application_controller.rb
│ │ ├── application_metal_controller.rb
│ │ ├── applications_controller.rb
│ │ ├── authorizations_controller.rb
│ │ ├── authorized_applications_controller.rb
│ │ ├── token_info_controller.rb
│ │ └── tokens_controller.rb
├── helpers
│ └── doorkeeper
│ │ └── dashboard_helper.rb
└── views
│ ├── doorkeeper
│ ├── applications
│ │ ├── _delete_form.html.erb
│ │ ├── _form.html.erb
│ │ ├── edit.html.erb
│ │ ├── index.html.erb
│ │ ├── new.html.erb
│ │ └── show.html.erb
│ ├── authorizations
│ │ ├── error.html.erb
│ │ ├── form_post.html.erb
│ │ ├── new.html.erb
│ │ └── show.html.erb
│ └── authorized_applications
│ │ ├── _delete_form.html.erb
│ │ └── index.html.erb
│ └── layouts
│ └── doorkeeper
│ ├── admin.html.erb
│ └── application.html.erb
├── benchmark
├── ruby
│ └── client_credentials.rb
└── wrk
│ └── .keep
├── bin
└── console
├── config
└── locales
│ └── en.yml
├── doorkeeper.gemspec
├── gemfiles
├── rails_7_0.gemfile
├── rails_7_1.gemfile
├── rails_7_2.gemfile
├── rails_8_0.gemfile
└── rails_edge.gemfile
├── lib
├── doorkeeper.rb
├── doorkeeper
│ ├── config.rb
│ ├── config
│ │ ├── abstract_builder.rb
│ │ ├── option.rb
│ │ └── validations.rb
│ ├── engine.rb
│ ├── errors.rb
│ ├── grant_flow.rb
│ ├── grant_flow
│ │ ├── fallback_flow.rb
│ │ ├── flow.rb
│ │ └── registry.rb
│ ├── grape
│ │ ├── authorization_decorator.rb
│ │ └── helpers.rb
│ ├── helpers
│ │ └── controller.rb
│ ├── models
│ │ ├── access_grant_mixin.rb
│ │ ├── access_token_mixin.rb
│ │ ├── application_mixin.rb
│ │ └── concerns
│ │ │ ├── accessible.rb
│ │ │ ├── expirable.rb
│ │ │ ├── expiration_time_sql_math.rb
│ │ │ ├── orderable.rb
│ │ │ ├── ownership.rb
│ │ │ ├── polymorphic_resource_owner.rb
│ │ │ ├── resource_ownerable.rb
│ │ │ ├── reusable.rb
│ │ │ ├── revocable.rb
│ │ │ ├── scopes.rb
│ │ │ └── secret_storable.rb
│ ├── oauth.rb
│ ├── oauth
│ │ ├── authorization
│ │ │ ├── code.rb
│ │ │ ├── context.rb
│ │ │ ├── token.rb
│ │ │ └── uri_builder.rb
│ │ ├── authorization_code_request.rb
│ │ ├── base_request.rb
│ │ ├── base_response.rb
│ │ ├── client.rb
│ │ ├── client
│ │ │ └── credentials.rb
│ │ ├── client_credentials
│ │ │ ├── creator.rb
│ │ │ ├── issuer.rb
│ │ │ └── validator.rb
│ │ ├── client_credentials_request.rb
│ │ ├── code_request.rb
│ │ ├── code_response.rb
│ │ ├── error.rb
│ │ ├── error_response.rb
│ │ ├── forbidden_token_response.rb
│ │ ├── helpers
│ │ │ ├── scope_checker.rb
│ │ │ ├── unique_token.rb
│ │ │ └── uri_checker.rb
│ │ ├── hooks
│ │ │ └── context.rb
│ │ ├── invalid_request_response.rb
│ │ ├── invalid_token_response.rb
│ │ ├── nonstandard.rb
│ │ ├── password_access_token_request.rb
│ │ ├── pre_authorization.rb
│ │ ├── refresh_token_request.rb
│ │ ├── scopes.rb
│ │ ├── token.rb
│ │ ├── token_introspection.rb
│ │ ├── token_request.rb
│ │ └── token_response.rb
│ ├── orm
│ │ ├── active_record.rb
│ │ └── active_record
│ │ │ ├── access_grant.rb
│ │ │ ├── access_token.rb
│ │ │ ├── application.rb
│ │ │ ├── mixins
│ │ │ ├── access_grant.rb
│ │ │ ├── access_token.rb
│ │ │ └── application.rb
│ │ │ ├── redirect_uri_validator.rb
│ │ │ └── stale_records_cleaner.rb
│ ├── rails
│ │ ├── helpers.rb
│ │ ├── routes.rb
│ │ └── routes
│ │ │ ├── abstract_router.rb
│ │ │ ├── mapper.rb
│ │ │ ├── mapping.rb
│ │ │ └── registry.rb
│ ├── rake.rb
│ ├── rake
│ │ ├── db.rake
│ │ └── setup.rake
│ ├── request.rb
│ ├── request
│ │ ├── authorization_code.rb
│ │ ├── client_credentials.rb
│ │ ├── code.rb
│ │ ├── password.rb
│ │ ├── refresh_token.rb
│ │ ├── strategy.rb
│ │ └── token.rb
│ ├── revocable_tokens
│ │ ├── revocable_access_token.rb
│ │ └── revocable_refresh_token.rb
│ ├── secret_storing
│ │ ├── base.rb
│ │ ├── bcrypt.rb
│ │ ├── plain.rb
│ │ └── sha256_hash.rb
│ ├── server.rb
│ ├── stale_records_cleaner.rb
│ ├── validations.rb
│ └── version.rb
└── generators
│ └── doorkeeper
│ ├── application_owner_generator.rb
│ ├── confidential_applications_generator.rb
│ ├── enable_polymorphic_resource_owner_generator.rb
│ ├── install_generator.rb
│ ├── migration_generator.rb
│ ├── pkce_generator.rb
│ ├── previous_refresh_token_generator.rb
│ ├── remove_applications_secret_not_null_constraint_generator.rb
│ ├── templates
│ ├── README
│ ├── add_confidential_to_applications.rb.erb
│ ├── add_owner_to_application_migration.rb.erb
│ ├── add_previous_refresh_token_to_access_tokens.rb.erb
│ ├── enable_pkce_migration.rb.erb
│ ├── enable_polymorphic_resource_owner_migration.rb.erb
│ ├── initializer.rb
│ ├── migration.rb.erb
│ └── remove_applications_secret_not_null_constraint.rb.erb
│ └── views_generator.rb
├── spec
├── controllers
│ ├── application_metal_controller_spec.rb
│ ├── applications_controller_spec.rb
│ ├── authorizations_controller_spec.rb
│ ├── protected_resources_controller_spec.rb
│ ├── token_info_controller_spec.rb
│ └── tokens_controller_spec.rb
├── doorkeeper
│ ├── redirect_uri_validator_spec.rb
│ ├── server_spec.rb
│ ├── stale_records_cleaner_spec.rb
│ └── version_spec.rb
├── dummy
│ ├── Rakefile
│ ├── app
│ │ ├── assets
│ │ │ └── config
│ │ │ │ └── manifest.js
│ │ ├── controllers
│ │ │ ├── application_controller.rb
│ │ │ ├── custom_authorizations_controller.rb
│ │ │ ├── full_protected_resources_controller.rb
│ │ │ ├── home_controller.rb
│ │ │ ├── metal_controller.rb
│ │ │ └── semi_protected_resources_controller.rb
│ │ ├── helpers
│ │ │ └── application_helper.rb
│ │ ├── models
│ │ │ └── user.rb
│ │ └── views
│ │ │ ├── home
│ │ │ └── index.html.erb
│ │ │ └── layouts
│ │ │ └── application.html.erb
│ ├── config.ru
│ ├── config
│ │ ├── application.rb
│ │ ├── boot.rb
│ │ ├── database.yml
│ │ ├── environment.rb
│ │ ├── environments
│ │ │ ├── development.rb
│ │ │ ├── production.rb
│ │ │ └── test.rb
│ │ ├── initializers
│ │ │ ├── backtrace_silencers.rb
│ │ │ ├── doorkeeper.rb
│ │ │ ├── secret_token.rb
│ │ │ ├── session_store.rb
│ │ │ └── wrap_parameters.rb
│ │ ├── locales
│ │ │ └── doorkeeper.en.yml
│ │ └── routes.rb
│ ├── db
│ │ ├── migrate
│ │ │ ├── 20111122132257_create_users.rb
│ │ │ ├── 20120312140401_add_password_to_users.rb
│ │ │ ├── 20151223192035_create_doorkeeper_tables.rb
│ │ │ ├── 20151223200000_add_owner_to_application.rb
│ │ │ ├── 20160320211015_add_previous_refresh_token_to_access_tokens.rb
│ │ │ ├── 20170822064514_enable_pkce.rb
│ │ │ ├── 20180210183654_add_confidential_to_applications.rb
│ │ │ └── 20230205064514_add_custom_attributes.rb
│ │ └── schema.rb
│ ├── public
│ │ ├── 404.html
│ │ ├── 422.html
│ │ ├── 500.html
│ │ └── favicon.ico
│ └── script
│ │ └── rails
├── factories.rb
├── generators
│ ├── application_owner_generator_spec.rb
│ ├── confidential_applications_generator_spec.rb
│ ├── enable_polymorphic_resource_owner_generator_spec.rb
│ ├── install_generator_spec.rb
│ ├── migration_generator_spec.rb
│ ├── pkce_generator_spec.rb
│ ├── previous_refresh_token_generator_spec.rb
│ ├── remove_applications_secret_not_null_constraint_generator_spec.rb
│ ├── templates
│ │ └── routes.rb
│ └── views_generator_spec.rb
├── grape
│ └── grape_integration_spec.rb
├── helpers
│ └── doorkeeper
│ │ └── dashboard_helper_spec.rb
├── lib
│ ├── config_spec.rb
│ ├── doorkeeper_spec.rb
│ ├── grant_flow
│ │ └── flow_spec.rb
│ ├── grant_flow_spec.rb
│ ├── models
│ │ ├── expirable_spec.rb
│ │ ├── reusable_spec.rb
│ │ ├── revocable_spec.rb
│ │ ├── scopes_spec.rb
│ │ └── secret_storable_spec.rb
│ ├── oauth
│ │ ├── authorization
│ │ │ └── uri_builder_spec.rb
│ │ ├── authorization_code_request_spec.rb
│ │ ├── base_request_spec.rb
│ │ ├── base_response_spec.rb
│ │ ├── client
│ │ │ └── credentials_spec.rb
│ │ ├── client_credentials
│ │ │ ├── creator_spec.rb
│ │ │ ├── issuer_spec.rb
│ │ │ └── validation_spec.rb
│ │ ├── client_credentials_integration_spec.rb
│ │ ├── client_credentials_request_spec.rb
│ │ ├── client_spec.rb
│ │ ├── code_request_spec.rb
│ │ ├── code_response_spec.rb
│ │ ├── error_response_spec.rb
│ │ ├── error_spec.rb
│ │ ├── forbidden_token_response_spec.rb
│ │ ├── helpers
│ │ │ ├── scope_checker_spec.rb
│ │ │ ├── unique_token_spec.rb
│ │ │ └── uri_checker_spec.rb
│ │ ├── invalid_request_response_spec.rb
│ │ ├── invalid_token_response_spec.rb
│ │ ├── password_access_token_request_spec.rb
│ │ ├── pre_authorization_spec.rb
│ │ ├── refresh_token_request_spec.rb
│ │ ├── scopes_spec.rb
│ │ ├── token_request_spec.rb
│ │ ├── token_response_spec.rb
│ │ └── token_spec.rb
│ ├── option_spec.rb
│ ├── request
│ │ └── strategy_spec.rb
│ └── secret_storing
│ │ ├── base_spec.rb
│ │ ├── bcrypt_spec.rb
│ │ ├── plain_spec.rb
│ │ └── sha256_hash_spec.rb
├── models
│ └── doorkeeper
│ │ ├── access_grant_spec.rb
│ │ ├── access_token_spec.rb
│ │ └── application_spec.rb
├── requests
│ ├── applications
│ │ ├── applications_request_spec.rb
│ │ └── authorized_applications_spec.rb
│ ├── endpoints
│ │ ├── authorization_spec.rb
│ │ └── token_spec.rb
│ ├── flows
│ │ ├── authorization_code_errors_spec.rb
│ │ ├── authorization_code_spec.rb
│ │ ├── client_credentials_spec.rb
│ │ ├── implicit_grant_errors_spec.rb
│ │ ├── implicit_grant_spec.rb
│ │ ├── password_spec.rb
│ │ ├── refresh_token_spec.rb
│ │ ├── revoke_token_spec.rb
│ │ └── skip_authorization_spec.rb
│ └── protected_resources
│ │ ├── metal_spec.rb
│ │ └── private_api_spec.rb
├── routing
│ ├── custom_controller_routes_spec.rb
│ ├── default_routes_spec.rb
│ └── scoped_routes_spec.rb
├── spec_helper.rb
├── spec_helper_integration.rb
└── support
│ ├── dependencies
│ └── factory_bot.rb
│ ├── doorkeeper_rspec.rb
│ ├── helpers
│ ├── access_token_request_helper.rb
│ ├── authorization_request_helper.rb
│ ├── config_helper.rb
│ ├── model_helper.rb
│ ├── request_spec_helper.rb
│ └── url_helper.rb
│ ├── orm
│ └── active_record.rb
│ ├── render_with_matcher.rb
│ └── shared
│ ├── controllers_shared_context.rb
│ ├── hashing_shared_context.rb
│ └── models_shared_examples.rb
└── vendor
└── assets
└── stylesheets
└── doorkeeper
└── bootstrap.min.css
/.codeclimate.yml:
--------------------------------------------------------------------------------
1 | exclude_patterns:
2 | - "lib/doorkeeper/config.rb"
3 | - "spec/"
4 |
--------------------------------------------------------------------------------
/.coveralls.yml:
--------------------------------------------------------------------------------
1 | service_name: travis-ci
2 |
--------------------------------------------------------------------------------
/.dockerignore:
--------------------------------------------------------------------------------
1 | Gemfile.lock
2 |
--------------------------------------------------------------------------------
/.editorconfig:
--------------------------------------------------------------------------------
1 | root = true
2 |
3 | [*.{rb,json}]
4 | indent_style = space
5 | indent_size = 2
6 | insert_final_newline = true
7 |
--------------------------------------------------------------------------------
/.github/FUNDING.yml:
--------------------------------------------------------------------------------
1 | # These are supported funding model platforms
2 |
3 | open_collective: doorkeeper-gem
4 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE.md:
--------------------------------------------------------------------------------
1 | ### Steps to reproduce
2 | What we need to do to see your problem or bug?
3 |
4 | The more detailed the issue, the more likely that we will fix it ASAP.
5 |
6 | Don't use GitHub issues for questions like "How can I do that?" —
7 | use [StackOverflow](https://stackoverflow.com/questions/tagged/doorkeeper)
8 | instead with the corresponding tag.
9 |
10 | ### Expected behavior
11 | Tell us what should happen
12 |
13 | ### Actual behavior
14 | Tell us what happens instead
15 |
16 | ### System configuration
17 | You can help us to understand your problem if you will share some very
18 | useful information about your project environment (don't forget to
19 | remove any confidential data if it exists).
20 |
21 | **Doorkeeper initializer**:
22 |
23 | ```ruby
24 | # config/initializers/doorkeeper.rb
25 | Doorkeeper.configure do
26 | # ...
27 | end
28 | ```
29 |
30 | **Ruby version**: ``
31 |
32 | **Gemfile.lock**:
33 |
34 |
35 | Gemfile.lock content
36 |
37 | ```
38 | Place your Gemfile.lock content here
39 | ```
40 |
41 |
--------------------------------------------------------------------------------
/.github/PULL_REQUEST_TEMPLATE.md:
--------------------------------------------------------------------------------
1 | ### Summary
2 |
3 | Provide a general description of the code changes in your pull
4 | request... were there any bugs you had fixed? If so, mention them. If
5 | these bugs have open GitHub issues, be sure to tag them here as well,
6 | to keep the conversation linked together.
7 |
8 | ### Other Information
9 |
10 | If there's anything else that's important and relevant to your pull
11 | request, mention that information here. This could include
12 | benchmarks, or other information.
13 |
14 | If you are updating CHANGELOG.md file or are asked to update it by reviewers,
15 | please add the changelog entry at the top of the file.
16 |
17 | Thanks for contributing to Doorkeeper project!
18 |
--------------------------------------------------------------------------------
/.github/dependabot.yml:
--------------------------------------------------------------------------------
1 | version: 2
2 | updates:
3 | - package-ecosystem: bundler
4 | directory: "/"
5 | schedule:
6 | interval: daily
7 | open-pull-requests-limit: 10
8 | - package-ecosystem: github-actions
9 | directory: "/"
10 | schedule:
11 | interval: daily
12 | open-pull-requests-limit: 10
13 |
--------------------------------------------------------------------------------
/.github/workflows/check_orm_changes.yml:
--------------------------------------------------------------------------------
1 | name: Check ORM changes
2 | on:
3 | push:
4 | branches:
5 | - main
6 | jobs:
7 | check_orm_changes_and_notify:
8 | name: Notify ORM extensions about required code updates
9 | runs-on: ubuntu-latest
10 | if: |
11 | !contains(github.event.head_commit.message, 'ga skip') &&
12 | !contains(github.event.head_commit.message, 'skip ga')
13 | env:
14 | ISSUE_TITLE: "ORM requires update due to latest Doorkeeper changes"
15 | ISSUE_BODY: |
16 | Doorkeeper has ORM changes merged to `main` branch.
17 |
18 | See https://github.com/doorkeeper-gem/doorkeeper/commit/${{ github.sha }} for the details.
19 | ISSUE_LABELS: ORM
20 | steps:
21 | - name: List & cache git modified files
22 | id: changed_files
23 | uses: lots0logs/gh-action-get-changed-files@2.2.2
24 | with:
25 | token: ${{ secrets.GITHUB_TOKEN }}
26 | - name: Check if ORM files were changed
27 | id: check_orm_files
28 | run: |
29 | changed_files=$(cat ${HOME}/files.json)
30 |
31 | if [[ $changed_files == *"lib/doorkeeper/orm/active_record"* ]] || \
32 | [[ $changed_files == *"lib/doorkeeper/models/"* ]];
33 | then
34 | echo "ORM files changes detected!"
35 | echo "ORM_CHANGES=yes" >> $GITHUB_ENV
36 | else
37 | echo "No changes in ORM files, exiting"
38 | fi
39 | - name: Create an issue for Mongo extension
40 | id: create_mongodb_issue
41 | if: env.ORM_CHANGES == 'yes'
42 | uses: dacbd/create-issue-action@main
43 | with:
44 | token: ${{ github.token }}
45 | title: ${{ env.ISSUE_TITLE }}
46 | body: ${{ env.ISSUE_BODY }}
47 | org: doorkeeper
48 | repo: doorkeeper-mongodb
49 | labels: ${{ env.ISSUE_LABELS}}
50 | - name: Create an issue for Sequel extension
51 | id: create_sequel_issue
52 | if: env.ORM_CHANGES == 'yes'
53 | uses: dacbd/create-issue-action@main
54 | with:
55 | token: ${{ github.token }}
56 | title: ${{ env.ISSUE_TITLE }}
57 | body: ${{ env.ISSUE_BODY }}
58 | repo: doorkeeper-sequel
59 | labels: ${{ env.ISSUE_LABELS}}
60 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | .bundle/
2 | .rbx
3 | *.rbc
4 | log/*.log
5 | pkg/
6 | spec/dummy/db/*.sqlite3
7 | spec/dummy/log/*.log
8 | spec/dummy/tmp/
9 | spec/generators/tmp
10 | Gemfile.lock
11 | gemfiles/*.lock
12 | .rvmrc
13 | *.swp
14 | .idea
15 | /.yardoc/
16 | /_yardoc/
17 | /doc/
18 | /rdoc/
19 | coverage
20 | *.gem
21 | gemfiles/vendor
22 |
--------------------------------------------------------------------------------
/.hound.yml:
--------------------------------------------------------------------------------
1 | rubocop:
2 | config_file: .rubocop.yml
3 | version: 1.5.2
4 |
--------------------------------------------------------------------------------
/.rspec:
--------------------------------------------------------------------------------
1 | --colour
2 |
--------------------------------------------------------------------------------
/CONTRIBUTING.md:
--------------------------------------------------------------------------------
1 | # Contributing
2 |
3 | We love pull requests from everyone. By participating in this project, you agree
4 | to abide by the [code of conduct](CODE_OF_CONDUCT.md).
5 |
6 | Fork, then clone the repo:
7 |
8 | git clone git@github.com:your-username/doorkeeper.git
9 |
10 | ### Docker Setup
11 |
12 | Build the container image with: `docker build --pull -t doorkeeper:test .`
13 | Run the tests with: `docker run -it --rm doorkeeper:test`
14 |
15 | ### Local Setup
16 |
17 | * Set up Ruby dependencies via Bundler
18 |
19 | bundle install
20 |
21 | * Make sure the tests pass:
22 |
23 | rake spec
24 |
25 | * Make your changes.
26 | * Write tests.
27 | * Follow our [style guides](.rubocop.yml).
28 | * Make the tests pass:
29 |
30 | rake spec
31 |
32 | * Add notes about your changes to the `CHANGELOG.md` file.
33 |
34 | * Write a [good commit message][commit].
35 | * Push to your fork.
36 | * [Submit a pull request][pr].
37 |
38 | [commit]: http://tbaggery.com/2008/04/19/a-note-about-git-commit-messages.html
39 | [pr]: https://github.com/doorkeeper-gem/doorkeeper/compare/
40 |
41 | * If [Hound] catches style violations, fix them. If our bot suggested changes — please add them.
42 |
43 | [hound]: https://houndci.com
44 |
45 | * Wait for us. We try to at least comment on pull requests within one business day.
46 | * We may suggest changes.
47 | * Please, squash your commits to a single one if you introduced a new changes or pushed more than
48 | one commit. Let's keep the history clean.
49 |
50 | Thank you for your contribution! :handshake:
51 |
--------------------------------------------------------------------------------
/Dockerfile:
--------------------------------------------------------------------------------
1 | FROM ruby:3.3.4-alpine
2 |
3 | # Linux UID (user id) for the doorkeeper user, change with [--build-arg UID=1234]
4 | ARG UID="991"
5 | # Linux GID (group id) for the doorkeeper user, change with [--build-arg GID=1234]
6 | ARG GID="991"
7 | # Timezone used by the Docker container and runtime, change with [--build-arg TZ=Europe/Berlin]
8 | ARG TZ="Etc/UTC"
9 |
10 | # Apply timezone
11 | ENV TZ=${TZ}
12 |
13 | RUN addgroup -g "${GID}" doorkeeper; \
14 | adduser -u "${UID}" -G "doorkeeper" -h /srv doorkeeper; \
15 | echo "${TZ}" > /etc/localtime;
16 |
17 | RUN apk add --no-cache \
18 | ca-certificates \
19 | wget \
20 | openssl \
21 | bash \
22 | build-base \
23 | git \
24 | sqlite-dev \
25 | tzdata
26 |
27 | ENV LANG=en_US.UTF-8
28 | ENV LANGUAGE=en_US:en
29 | ENV LC_ALL=en_US.UTF-8
30 |
31 | ENV BUNDLER_VERSION=2.5.11
32 | RUN gem install bundler -v ${BUNDLER_VERSION} -i /usr/local/lib/ruby/gems/$(ls /usr/local/lib/ruby/gems) --force
33 |
34 | WORKDIR /srv
35 |
36 | COPY Gemfile doorkeeper.gemspec /srv/
37 | COPY lib/doorkeeper/version.rb /srv/lib/doorkeeper/version.rb
38 |
39 | # This is a fix for sqlite alpine issues
40 | RUN bundle config force_ruby_platform true
41 | RUN bundle install
42 |
43 | COPY . /srv/
44 |
45 | RUN chown -R doorkeeper:doorkeeper /srv/coverage /srv/spec/dummy/tmp /srv/spec/generators/tmp
46 |
47 | # Set the running user for resulting container
48 | USER doorkeeper
49 |
50 | CMD ["rake"]
51 |
--------------------------------------------------------------------------------
/Gemfile:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | source "https://rubygems.org"
4 | git_source(:github) { |repo| "https://github.com/#{repo}.git" }
5 |
6 | gemspec
7 |
8 | gem "rails", ">= 7.0", "< 8.1"
9 |
10 | gem "sprockets-rails"
11 |
12 | gem "rspec-core"
13 | gem "rspec-expectations"
14 | gem "rspec-mocks"
15 | gem "rspec-rails", "~> 8.0"
16 | gem "rspec-support"
17 |
18 | gem "rubocop", "~> 1.4"
19 | gem "rubocop-performance", require: false
20 | gem "rubocop-rails", require: false
21 | gem "rubocop-rspec", require: false
22 |
23 | gem "bcrypt", "~> 3.1", require: false
24 |
25 | gem "activerecord-jdbcsqlite3-adapter", platform: :jruby
26 | gem "sqlite3", "~> 2.3", platform: [:ruby, :mswin, :mingw, :x64_mingw]
27 |
28 | gem "tzinfo-data", platforms: %i[mingw mswin x64_mingw]
29 | gem "timecop"
30 |
31 | gem 'irb', '~> 1.8'
32 |
33 | # Interactive Debugging tools
34 | gem 'debug', '~> 1.8'
35 |
--------------------------------------------------------------------------------
/MIT-LICENSE:
--------------------------------------------------------------------------------
1 | Copyright 2011 Applicake. http://applicake.com
2 |
3 | Permission is hereby granted, free of charge, to any person obtaining
4 | a copy of this software and associated documentation files (the
5 | "Software"), to deal in the Software without restriction, including
6 | without limitation the rights to use, copy, modify, merge, publish,
7 | distribute, sublicense, and/or sell copies of the Software, and to
8 | permit persons to whom the Software is furnished to do so, subject to
9 | the following conditions:
10 |
11 | The above copyright notice and this permission notice shall be
12 | included in all copies or substantial portions of the Software.
13 |
14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
21 |
--------------------------------------------------------------------------------
/NEWS.md:
--------------------------------------------------------------------------------
1 | Document moved [here](CHANGELOG.md)
2 |
--------------------------------------------------------------------------------
/RELEASING.md:
--------------------------------------------------------------------------------
1 | # Releasing Doorkeeper
2 |
3 | How to release Doorkeeper in five easy steps!
4 |
5 | 1. Update `lib/doorkeeper/version.rb` file accordingly.
6 | 2. Update `CHANGELOG.md` to reflect the changes since last release.
7 | 3. Commit changes: `git commit -am 'Bump to vVERSION'`.
8 | 4. Build and publish the gem.
9 | 4. Create GitHub release.
10 | 5. Announce the new release, making sure to say “thank you” to the contributors
11 | who helped shape this version!
12 |
--------------------------------------------------------------------------------
/Rakefile:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | require "bundler/setup"
4 | require "rspec/core/rake_task"
5 |
6 | desc "Default: run specs."
7 | task default: :spec
8 |
9 | desc "Run all specs"
10 | RSpec::Core::RakeTask.new(:spec) do |config|
11 | config.verbose = false
12 | end
13 |
14 | namespace :doorkeeper do
15 | desc "Install doorkeeper in dummy app"
16 | task :install do
17 | cd "spec/dummy"
18 | system "bundle exec rails g doorkeeper:install --force"
19 | end
20 |
21 | desc "Runs local test server"
22 | task :server do
23 | cd "spec/dummy"
24 | system "bundle exec rails server"
25 | end
26 | end
27 |
28 | Bundler::GemHelper.install_tasks
29 |
--------------------------------------------------------------------------------
/SECURITY.md:
--------------------------------------------------------------------------------
1 | # Reporting security issues in Doorkeeper
2 |
3 | Hello! Thank you for wanting to disclose a possible security
4 | vulnerability within the Doorkeeper gem! Please follow our disclosure
5 | policy as outlined below:
6 |
7 | 1. Do NOT open up a GitHub issue with your report. Security reports
8 | should be kept private until a possible fix is determined.
9 | 2. Send an email to Nikita Bulai at bulaj.nikita AT gmail.com or one of
10 | the others Doorkeeper maintainers listed in gemspec. You should receive
11 | a prompt response.
12 | 3. Be patient. Since Doorkeeper is in a stable maintenance phase, we want to
13 | do as little as possible to rock the boat of the project.
14 |
15 | Thank you very much for adhering for these policies!
16 |
--------------------------------------------------------------------------------
/UPGRADE.md:
--------------------------------------------------------------------------------
1 | See [Upgrade Guides](https://github.com/doorkeeper-gem/doorkeeper/wiki/Migration-from-old-versions)
2 | in the project Wiki.
3 |
--------------------------------------------------------------------------------
/app/assets/stylesheets/doorkeeper/admin/application.css:
--------------------------------------------------------------------------------
1 | /*
2 | *= require doorkeeper/bootstrap.min
3 | *
4 | *= require_self
5 | *= require_tree .
6 | */
7 |
8 | .doorkeeper-admin .form-group > .field_with_errors {
9 | width: 16.66667%;
10 | }
11 |
--------------------------------------------------------------------------------
/app/assets/stylesheets/doorkeeper/application.css:
--------------------------------------------------------------------------------
1 | /*
2 | *= require doorkeeper/bootstrap.min
3 | *
4 | *= require_self
5 | *= require_tree .
6 | */
7 |
8 | body {
9 | background-color: #eee;
10 | font-size: 14px;
11 | }
12 |
13 | #container {
14 | background-color: #fff;
15 | border: 1px solid #999;
16 | border: 1px solid rgba(0, 0, 0, 0.2);
17 | border-radius: 6px;
18 | -webkit-box-shadow: 0 3px 9px rgba(0, 0, 0, 0.5);
19 | box-shadow: 0 3px 20px rgba(0, 0, 0, 0.3);
20 | margin: 2em auto;
21 | max-width: 600px;
22 | outline: 0;
23 | padding: 1em;
24 | width: 80%;
25 | }
26 |
27 | .page-header {
28 | margin-top: 20px;
29 | }
30 |
31 | #oauth-permissions {
32 | width: 260px;
33 | }
34 |
35 | .actions {
36 | border-top: 1px solid #eee;
37 | margin-top: 1em;
38 | padding-top: 9px;
39 | }
40 |
41 | .actions > form > .btn {
42 | margin-top: 5px;
43 | }
44 |
45 | .separator {
46 | color: #eee;
47 | padding: 0 .5em;
48 | }
49 |
50 | .inline_block {
51 | display: inline-block;
52 | }
53 |
54 | #oauth {
55 | margin-bottom: 1em;
56 | }
57 |
58 | #oauth > .btn {
59 | width: 7em;
60 | }
61 |
62 | td {
63 | vertical-align: middle !important;
64 | }
65 |
--------------------------------------------------------------------------------
/app/controllers/doorkeeper/application_controller.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | module Doorkeeper
4 | class ApplicationController <
5 | Doorkeeper.config.resolve_controller(:base)
6 | include Helpers::Controller
7 | include ActionController::MimeResponds if Doorkeeper.config.api_only
8 |
9 | unless Doorkeeper.config.api_only
10 | protect_from_forgery with: :exception
11 | helper "doorkeeper/dashboard"
12 | end
13 | end
14 | end
15 |
--------------------------------------------------------------------------------
/app/controllers/doorkeeper/application_metal_controller.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | module Doorkeeper
4 | class ApplicationMetalController <
5 | Doorkeeper.config.resolve_controller(:base_metal)
6 | include Helpers::Controller
7 |
8 | before_action :enforce_content_type,
9 | if: -> { Doorkeeper.config.enforce_content_type }
10 |
11 | ActiveSupport.run_load_hooks(:doorkeeper_metal_controller, self)
12 | end
13 | end
14 |
--------------------------------------------------------------------------------
/app/controllers/doorkeeper/authorized_applications_controller.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | module Doorkeeper
4 | class AuthorizedApplicationsController < Doorkeeper::ApplicationController
5 | before_action :authenticate_resource_owner!
6 |
7 | def index
8 | @applications = Doorkeeper.config.application_model.authorized_for(current_resource_owner)
9 |
10 | respond_to do |format|
11 | format.html
12 | format.json { render json: @applications, current_resource_owner: current_resource_owner }
13 | end
14 | end
15 |
16 | def destroy
17 | Doorkeeper.config.application_model.revoke_tokens_and_grants_for(
18 | params[:id],
19 | current_resource_owner,
20 | )
21 |
22 | respond_to do |format|
23 | format.html do
24 | redirect_to oauth_authorized_applications_url, notice: I18n.t(
25 | :notice, scope: %i[doorkeeper flash authorized_applications destroy],
26 | )
27 | end
28 |
29 | format.json { head :no_content }
30 | end
31 | end
32 | end
33 | end
34 |
--------------------------------------------------------------------------------
/app/controllers/doorkeeper/token_info_controller.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | module Doorkeeper
4 | class TokenInfoController < Doorkeeper::ApplicationMetalController
5 | def show
6 | if doorkeeper_token&.accessible?
7 | render json: doorkeeper_token_to_json, status: :ok
8 | else
9 | error = OAuth::InvalidTokenResponse.new
10 | response.headers.merge!(error.headers)
11 | render json: error_to_json(error), status: error.status
12 | end
13 | end
14 |
15 | protected
16 |
17 | def doorkeeper_token_to_json
18 | doorkeeper_token
19 | end
20 |
21 | def error_to_json(error)
22 | error.body
23 | end
24 | end
25 | end
26 |
--------------------------------------------------------------------------------
/app/helpers/doorkeeper/dashboard_helper.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | module Doorkeeper
4 | module DashboardHelper
5 | def doorkeeper_errors_for(object, method)
6 | return if object.errors[method].blank?
7 |
8 | output = object.errors[method].map do |msg|
9 | content_tag(:span, class: "invalid-feedback") do
10 | msg.capitalize
11 | end
12 | end
13 |
14 | safe_join(output)
15 | end
16 |
17 | def doorkeeper_submit_path(application)
18 | application.persisted? ? oauth_application_path(application) : oauth_applications_path
19 | end
20 | end
21 | end
22 |
--------------------------------------------------------------------------------
/app/views/doorkeeper/applications/_delete_form.html.erb:
--------------------------------------------------------------------------------
1 | <%- submit_btn_css ||= 'btn btn-link' %>
2 | <%= form_tag oauth_application_path(application), method: :delete do %>
3 | <%= submit_tag t('doorkeeper.applications.buttons.destroy'),
4 | onclick: "return confirm('#{ t('doorkeeper.applications.confirmations.destroy') }')",
5 | class: submit_btn_css %>
6 | <% end %>
7 |
--------------------------------------------------------------------------------
/app/views/doorkeeper/applications/edit.html.erb:
--------------------------------------------------------------------------------
1 |
2 |
<%= t('.title') %>
3 |
4 |
5 | <%= render 'form', application: @application %>
6 |
--------------------------------------------------------------------------------
/app/views/doorkeeper/applications/index.html.erb:
--------------------------------------------------------------------------------
1 |
2 |
<%= t('.title') %>
3 |
4 |
5 | <%= link_to t('.new'), new_oauth_application_path, class: 'btn btn-success' %>
6 |
7 |
8 |
9 |
10 | <%= t('.name') %> |
11 | <%= t('.callback_url') %> |
12 | <%= t('.confidential') %> |
13 | <%= t('.actions') %> |
14 | |
15 |
16 |
17 |
18 | <% @applications.each do |application| %>
19 |
20 |
21 | <%= link_to application.name, oauth_application_path(application) %>
22 | |
23 |
24 | <%= simple_format(application.redirect_uri) %>
25 | |
26 |
27 | <%= application.confidential? ? t('doorkeeper.applications.index.confidentiality.yes') : t('doorkeeper.applications.index.confidentiality.no') %>
28 | |
29 |
30 | <%= link_to t('doorkeeper.applications.buttons.edit'), edit_oauth_application_path(application), class: 'btn btn-link' %>
31 | |
32 |
33 | <%= render 'delete_form', application: application %>
34 | |
35 |
36 | <% end %>
37 |
38 |
39 |
--------------------------------------------------------------------------------
/app/views/doorkeeper/applications/new.html.erb:
--------------------------------------------------------------------------------
1 |
2 |
<%= t('.title') %>
3 |
4 |
5 | <%= render 'form', application: @application %>
6 |
--------------------------------------------------------------------------------
/app/views/doorkeeper/authorizations/error.html.erb:
--------------------------------------------------------------------------------
1 |
2 |
<%= t('doorkeeper.authorizations.error.title') %>
3 |
4 |
5 |
6 |
7 | <%= (local_assigns[:error_response] ? error_response : @pre_auth.error_response).body[:error_description] %>
8 |
9 |
10 |
--------------------------------------------------------------------------------
/app/views/doorkeeper/authorizations/form_post.html.erb:
--------------------------------------------------------------------------------
1 |
4 |
5 | <%= form_tag @pre_auth.redirect_uri, method: :post, name: :redirect_form, authenticity_token: false do %>
6 | <% auth.body.compact.each do |key, value| %>
7 | <%= hidden_field_tag key, value %>
8 | <% end %>
9 | <% end %>
10 |
11 |
16 |
--------------------------------------------------------------------------------
/app/views/doorkeeper/authorizations/new.html.erb:
--------------------------------------------------------------------------------
1 |
4 |
5 |
6 |
7 | <%= raw t('.prompt', client_name: content_tag(:strong, class: 'text-info') { @pre_auth.client.name }) %>
8 |
9 |
10 | <% if @pre_auth.scopes.count > 0 %>
11 |
12 |
<%= t('.able_to') %>:
13 |
14 |
15 | <% @pre_auth.scopes.each do |scope| %>
16 | - <%= t scope, scope: [:doorkeeper, :scopes] %>
17 | <% end %>
18 |
19 |
20 | <% end %>
21 |
22 |
23 | <%= form_tag oauth_authorization_path, method: :post do %>
24 | <%= hidden_field_tag :client_id, @pre_auth.client.uid, id: nil %>
25 | <%= hidden_field_tag :redirect_uri, @pre_auth.redirect_uri, id: nil %>
26 | <%= hidden_field_tag :state, @pre_auth.state, id: nil %>
27 | <%= hidden_field_tag :response_type, @pre_auth.response_type, id: nil %>
28 | <%= hidden_field_tag :response_mode, @pre_auth.response_mode, id: nil %>
29 | <%= hidden_field_tag :scope, @pre_auth.scope, id: nil %>
30 | <%= hidden_field_tag :code_challenge, @pre_auth.code_challenge, id: nil %>
31 | <%= hidden_field_tag :code_challenge_method, @pre_auth.code_challenge_method, id: nil %>
32 | <%= submit_tag t('doorkeeper.authorizations.buttons.authorize'), class: "btn btn-success btn-lg btn-block" %>
33 | <% end %>
34 | <%= form_tag oauth_authorization_path, method: :delete do %>
35 | <%= hidden_field_tag :client_id, @pre_auth.client.uid, id: nil %>
36 | <%= hidden_field_tag :redirect_uri, @pre_auth.redirect_uri, id: nil %>
37 | <%= hidden_field_tag :state, @pre_auth.state, id: nil %>
38 | <%= hidden_field_tag :response_type, @pre_auth.response_type, id: nil %>
39 | <%= hidden_field_tag :response_mode, @pre_auth.response_mode, id: nil %>
40 | <%= hidden_field_tag :scope, @pre_auth.scope, id: nil %>
41 | <%= hidden_field_tag :code_challenge, @pre_auth.code_challenge, id: nil %>
42 | <%= hidden_field_tag :code_challenge_method, @pre_auth.code_challenge_method, id: nil %>
43 | <%= submit_tag t('doorkeeper.authorizations.buttons.deny'), class: "btn btn-danger btn-lg btn-block" %>
44 | <% end %>
45 |
46 |
47 |
--------------------------------------------------------------------------------
/app/views/doorkeeper/authorizations/show.html.erb:
--------------------------------------------------------------------------------
1 |
4 |
5 |
6 | <%= params[:code] %>
7 |
8 |
--------------------------------------------------------------------------------
/app/views/doorkeeper/authorized_applications/_delete_form.html.erb:
--------------------------------------------------------------------------------
1 | <%- submit_btn_css ||= 'btn btn-link' %>
2 | <%= form_tag oauth_authorized_application_path(application), method: :delete do %>
3 | <%= submit_tag t('doorkeeper.authorized_applications.buttons.revoke'), onclick: "return confirm('#{ t('doorkeeper.authorized_applications.confirmations.revoke') }')", class: submit_btn_css %>
4 | <% end %>
5 |
--------------------------------------------------------------------------------
/app/views/doorkeeper/authorized_applications/index.html.erb:
--------------------------------------------------------------------------------
1 |
4 |
5 |
6 |
7 |
8 |
9 | <%= t('doorkeeper.authorized_applications.index.application') %> |
10 | <%= t('doorkeeper.authorized_applications.index.created_at') %> |
11 | |
12 |
13 |
14 |
15 | <% @applications.each do |application| %>
16 |
17 | <%= application.name %> |
18 | <%= application.created_at.strftime(t('doorkeeper.authorized_applications.index.date_format')) %> |
19 | <%= render 'delete_form', application: application %> |
20 |
21 | <% end %>
22 |
23 |
24 |
25 |
--------------------------------------------------------------------------------
/app/views/layouts/doorkeeper/admin.html.erb:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | <%= t('doorkeeper.layouts.admin.title') %>
8 | <%= stylesheet_link_tag "doorkeeper/admin/application" %>
9 | <%= csrf_meta_tags %>
10 |
11 |
12 |
28 |
29 |
30 | <%- if flash[:notice].present? %>
31 |
32 | <%= flash[:notice] %>
33 |
34 | <% end -%>
35 |
36 | <%= yield %>
37 |
38 |
39 |
40 |
--------------------------------------------------------------------------------
/app/views/layouts/doorkeeper/application.html.erb:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | <%= t('doorkeeper.layouts.application.title') %>
5 |
6 |
7 |
8 |
9 | <%= stylesheet_link_tag "doorkeeper/application" %>
10 | <%= csrf_meta_tags %>
11 |
12 |
13 |
14 | <%- if flash[:notice].present? %>
15 |
16 | <%= flash[:notice] %>
17 |
18 | <% end -%>
19 |
20 | <%= yield %>
21 |
22 |
23 |
24 |
--------------------------------------------------------------------------------
/benchmark/ruby/client_credentials.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | $LOAD_PATH.unshift(File.join(File.dirname(__FILE__), "..", "..", "lib"))
4 |
5 | begin
6 | require "bundler/inline"
7 | rescue LoadError => e
8 | warn "Bundler version 1.10 or later is required. Please update your Bundler"
9 | raise e
10 | end
11 |
12 | gemfile(true) do
13 | source "https://rubygems.org"
14 |
15 | gem "sqlite3"
16 | gem "rails"
17 | gem "benchmark-ips"
18 | end
19 |
20 | require "benchmark/ips"
21 | require "ostruct"
22 | require "doorkeeper"
23 | require "active_support/all"
24 | require "active_record/railtie"
25 |
26 | ActiveRecord::Base.establish_connection(adapter: "sqlite3", database: ":memory:")
27 | ActiveRecord::Base.logger = ENV["LOGGER"].present? ? Logger.new(STDOUT) : nil
28 |
29 | # Load database schema
30 | load File.expand_path("../../spec/dummy/db/schema.rb", __dir__)
31 |
32 | Doorkeeper.configure do
33 | orm :active_record
34 |
35 | grant_flows %w[password authorization_code client_credentials]
36 |
37 | skip_authorization do
38 | true
39 | end
40 | end
41 |
42 | client = Doorkeeper::Application.create!(
43 | name: "test",
44 | uid: "123456789",
45 | secret: "987654321",
46 | redirect_uri: "https://doorkeeper.test",
47 | )
48 |
49 | context = OpenStruct.new
50 | request = OpenStruct.new
51 | request.parameters = {
52 | client_id: client.uid,
53 | client_secret: client.secret,
54 | }.with_indifferent_access
55 | context.request = request
56 |
57 | Benchmark.ips do |ips|
58 | ips.report("Client credentials") do
59 | server = Doorkeeper::Server.new(context)
60 | strategy = server.token_request("client_credentials")
61 | strategy.authorize
62 | end
63 | end
64 |
--------------------------------------------------------------------------------
/benchmark/wrk/.keep:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/doorkeeper-gem/doorkeeper/9218cdec8eb295366ffe8ec9129c377b30c00bea/benchmark/wrk/.keep
--------------------------------------------------------------------------------
/bin/console:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env ruby
2 | # frozen_string_literal: true
3 |
4 | require "bundler/setup"
5 | require "rails/all"
6 | require "active_support/all"
7 | require "irb"
8 | require "debug"
9 | require "doorkeeper"
10 |
11 | Rails.logger = Logger.new(STDOUT)
12 |
13 | Rails.logger.info("Doorkeeper version: #{Doorkeeper::VERSION::STRING}")
14 | Rails.logger.info("Rails version: #{Rails::VERSION::STRING}")
15 |
16 | # Default Doorkeeper config
17 | Doorkeeper.configure do
18 | orm :active_record
19 | end
20 |
21 | # Generate in-memory database for testing
22 | ActiveRecord::Base.establish_connection(
23 | adapter: "sqlite3",
24 | database: ":memory:",
25 | )
26 |
27 | # Load database schema
28 | load File.expand_path("../spec/dummy/db/schema.rb", __dir__)
29 |
30 | IRB.start(__FILE__)
31 |
--------------------------------------------------------------------------------
/gemfiles/rails_7_0.gemfile:
--------------------------------------------------------------------------------
1 | # This file was generated by Appraisal
2 |
3 | source "https://rubygems.org"
4 |
5 | gem "rails", "~> 7.0.0"
6 | gem "rspec-core"
7 | gem "rspec-expectations"
8 | gem "rspec-mocks"
9 | gem "rspec-rails", "~> 5.0"
10 | gem "rspec-support"
11 | gem "rubocop", "~> 1.4"
12 | gem "rubocop-performance", require: false
13 | gem "rubocop-rails", require: false
14 | gem "rubocop-rspec", require: false
15 | gem "bcrypt", "~> 3.1", require: false
16 | gem "activerecord-jdbcsqlite3-adapter", platform: :jruby
17 | gem "sprockets-rails"
18 | gem "sqlite3", "~> 1.4", platform: [:ruby, :mswin, :mingw, :x64_mingw]
19 | gem "tzinfo-data", platforms: [:mingw, :mswin, :x64_mingw]
20 | gem "timecop"
21 | gem "base64"
22 | gem "drb"
23 | gem "mutex_m"
24 | gem "concurrent-ruby", "1.3.4"
25 |
26 | gemspec path: "../"
27 |
--------------------------------------------------------------------------------
/gemfiles/rails_7_1.gemfile:
--------------------------------------------------------------------------------
1 | # This file was generated by Appraisal
2 |
3 | source "https://rubygems.org"
4 |
5 | gem "rails", "~> 7.1.0"
6 | gem "rspec-core"
7 | gem "rspec-expectations"
8 | gem "rspec-mocks"
9 | gem "rspec-rails", "~> 7.1"
10 | gem "rspec-support"
11 | gem "rubocop", "~> 1.4"
12 | gem "rubocop-performance", require: false
13 | gem "rubocop-rails", require: false
14 | gem "rubocop-rspec", require: false
15 | gem "bcrypt", "~> 3.1", require: false
16 | gem "activerecord-jdbcsqlite3-adapter", platform: :jruby
17 | gem "sprockets-rails"
18 | gem "sqlite3", "~> 1.4", platform: [:ruby, :mswin, :mingw, :x64_mingw]
19 | gem "tzinfo-data", platforms: [:mingw, :mswin, :x64_mingw]
20 | gem "timecop"
21 |
22 | gemspec path: "../"
23 |
--------------------------------------------------------------------------------
/gemfiles/rails_7_2.gemfile:
--------------------------------------------------------------------------------
1 | # This file was generated by Appraisal
2 |
3 | source "https://rubygems.org"
4 |
5 | gem "rails", "~> 7.2.0"
6 | gem "rspec-core"
7 | gem "rspec-expectations"
8 | gem "rspec-mocks"
9 | gem "rspec-rails", "~> 7.1"
10 | gem "rspec-support"
11 | gem "rubocop", "~> 1.4"
12 | gem "rubocop-performance", require: false
13 | gem "rubocop-rails", require: false
14 | gem "rubocop-rspec", require: false
15 | gem "bcrypt", "~> 3.1", require: false
16 | gem "activerecord-jdbcsqlite3-adapter", platform: :jruby
17 | gem "sprockets-rails"
18 | gem "sqlite3", "~> 1.4", platform: [:ruby, :mswin, :mingw, :x64_mingw]
19 | gem "tzinfo-data", platforms: [:mingw, :mswin, :x64_mingw]
20 | gem "timecop"
21 |
22 | gemspec path: "../"
23 |
--------------------------------------------------------------------------------
/gemfiles/rails_8_0.gemfile:
--------------------------------------------------------------------------------
1 | # This file was generated by Appraisal
2 |
3 | source "https://rubygems.org"
4 |
5 | gem "rails", "~> 8.0.0"
6 | gem "rspec-core"
7 | gem "rspec-expectations"
8 | gem "rspec-mocks"
9 | gem "rspec-rails", "~> 7.1"
10 | gem "rspec-support"
11 | gem "rubocop", "~> 1.4"
12 | gem "rubocop-performance", require: false
13 | gem "rubocop-rails", require: false
14 | gem "rubocop-rspec", require: false
15 | gem "bcrypt", "~> 3.1", require: false
16 | gem "activerecord-jdbcsqlite3-adapter", platform: :jruby
17 | gem "sprockets-rails"
18 | gem "sqlite3", "~> 2.3", platform: [:ruby, :mswin, :mingw, :x64_mingw]
19 | gem "tzinfo-data", platforms: [:mingw, :mswin, :x64_mingw]
20 | gem "timecop"
21 |
22 | gemspec path: "../"
23 |
--------------------------------------------------------------------------------
/gemfiles/rails_edge.gemfile:
--------------------------------------------------------------------------------
1 | # This file was generated by Appraisal
2 |
3 | source "https://rubygems.org"
4 |
5 | gem "rails", git: "https://github.com/rails/rails"
6 | gem "rspec-core"
7 | gem "rspec-expectations"
8 | gem "rspec-mocks"
9 | gem "rspec-rails", "~> 7.1"
10 | gem "rspec-support"
11 | gem "rubocop", "~> 1.4"
12 | gem "rubocop-performance", require: false
13 | gem "rubocop-rails", require: false
14 | gem "rubocop-rspec", require: false
15 | gem "bcrypt", "~> 3.1", require: false
16 | gem "activerecord-jdbcsqlite3-adapter", platform: :jruby
17 | gem "sprockets-rails"
18 | gem "sqlite3", "~> 2.3", platform: [:ruby, :mswin, :mingw, :x64_mingw]
19 | gem "tzinfo-data", platforms: [:mingw, :mswin, :x64_mingw]
20 | gem "timecop"
21 |
22 | gemspec path: "../"
23 |
--------------------------------------------------------------------------------
/lib/doorkeeper/config/abstract_builder.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | module Doorkeeper
4 | class Config
5 | # Abstract base class for Doorkeeper and it's extensions configuration
6 | # builder. Instantiates and validates gem configuration.
7 | #
8 | class AbstractBuilder
9 | attr_reader :config
10 |
11 | # @param [Class] config class
12 | #
13 | def initialize(config = Config.new, &block)
14 | @config = config
15 | instance_eval(&block) if block_given?
16 | end
17 |
18 | # Builds and validates configuration.
19 | #
20 | # @return [Doorkeeper::Config] config instance
21 | #
22 | def build
23 | @config.validate! if @config.respond_to?(:validate!)
24 | @config
25 | end
26 | end
27 | end
28 | end
29 |
--------------------------------------------------------------------------------
/lib/doorkeeper/engine.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | module Doorkeeper
4 | class Engine < Rails::Engine
5 | initializer "doorkeeper.params.filter", after: :load_config_initializers do |app|
6 | if Doorkeeper.configured?
7 | parameters = %w[client_secret authentication_token access_token refresh_token]
8 | parameters << "code" if Doorkeeper.config.grant_flows.include?("authorization_code")
9 | app.config.filter_parameters << /^(#{Regexp.union(parameters)})$/
10 | end
11 | end
12 |
13 | initializer "doorkeeper.routes" do
14 | Doorkeeper::Rails::Routes.install!
15 | end
16 |
17 | initializer "doorkeeper.helpers" do
18 | ActiveSupport.on_load(:action_controller) do
19 | include Doorkeeper::Rails::Helpers
20 | end
21 | end
22 |
23 | config.to_prepare do
24 | Doorkeeper.run_orm_hooks
25 | end
26 |
27 | if defined?(Sprockets) && Sprockets::VERSION.chr.to_i >= 4
28 | initializer "doorkeeper.assets.precompile" do |app|
29 | # Force users to use:
30 | # //= link doorkeeper/admin/application.css
31 | # in Doorkeeper 5 for Sprockets 4 instead of precompile.
32 | # Add note to official docs & Wiki
33 | app.config.assets.precompile += %w[
34 | doorkeeper/application.css
35 | doorkeeper/admin/application.css
36 | ]
37 | end
38 | end
39 | end
40 | end
41 |
--------------------------------------------------------------------------------
/lib/doorkeeper/grant_flow.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | require "doorkeeper/grant_flow/flow"
4 | require "doorkeeper/grant_flow/fallback_flow"
5 | require "doorkeeper/grant_flow/registry"
6 |
7 | module Doorkeeper
8 | module GrantFlow
9 | extend Registry
10 |
11 | register(
12 | :implicit,
13 | response_type_matches: "token",
14 | response_mode_matches: %w[fragment form_post],
15 | response_type_strategy: Doorkeeper::Request::Token,
16 | )
17 |
18 | register(
19 | :authorization_code,
20 | response_type_matches: "code",
21 | response_mode_matches: %w[query fragment form_post],
22 | response_type_strategy: Doorkeeper::Request::Code,
23 | grant_type_matches: "authorization_code",
24 | grant_type_strategy: Doorkeeper::Request::AuthorizationCode,
25 | )
26 |
27 | register(
28 | :client_credentials,
29 | grant_type_matches: "client_credentials",
30 | grant_type_strategy: Doorkeeper::Request::ClientCredentials,
31 | )
32 |
33 | register(
34 | :password,
35 | grant_type_matches: "password",
36 | grant_type_strategy: Doorkeeper::Request::Password,
37 | )
38 |
39 | register(
40 | :refresh_token,
41 | grant_type_matches: "refresh_token",
42 | grant_type_strategy: Doorkeeper::Request::RefreshToken,
43 | )
44 | end
45 | end
46 |
--------------------------------------------------------------------------------
/lib/doorkeeper/grant_flow/fallback_flow.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | module Doorkeeper
4 | module GrantFlow
5 | class FallbackFlow < Flow
6 | def handles_grant_type?
7 | false
8 | end
9 |
10 | def handles_response_type?
11 | false
12 | end
13 | end
14 | end
15 | end
16 |
--------------------------------------------------------------------------------
/lib/doorkeeper/grant_flow/flow.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | module Doorkeeper
4 | module GrantFlow
5 | class Flow
6 | attr_reader :name, :grant_type_matches, :grant_type_strategy,
7 | :response_type_matches, :response_type_strategy,
8 | :response_mode_matches
9 |
10 | def initialize(name, **options)
11 | @name = name
12 | @grant_type_matches = options[:grant_type_matches]
13 | @grant_type_strategy = options[:grant_type_strategy]
14 | @response_type_matches = options[:response_type_matches]
15 | @response_type_strategy = options[:response_type_strategy]
16 | @response_mode_matches = options[:response_mode_matches]
17 | end
18 |
19 | def handles_grant_type?
20 | grant_type_matches.present?
21 | end
22 |
23 | def handles_response_type?
24 | response_type_matches.present?
25 | end
26 |
27 | def matches_grant_type?(value)
28 | grant_type_matches === value
29 | end
30 |
31 | def matches_response_type?(value)
32 | response_type_matches === value
33 | end
34 |
35 | def default_response_mode
36 | response_mode_matches[0]
37 | end
38 |
39 | def matches_response_mode?(value)
40 | response_mode_matches.include?(value)
41 | end
42 | end
43 | end
44 | end
45 |
--------------------------------------------------------------------------------
/lib/doorkeeper/grant_flow/registry.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | module Doorkeeper
4 | module GrantFlow
5 | module Registry
6 | mattr_accessor :flows
7 | self.flows = {}
8 |
9 | mattr_accessor :aliases
10 | self.aliases = {}
11 |
12 | # Allows to register custom OAuth grant flow so that Doorkeeper
13 | # could recognize and process it.
14 | #
15 | def register(name_or_flow, **options)
16 | unless name_or_flow.is_a?(Doorkeeper::GrantFlow::Flow)
17 | name_or_flow = Flow.new(name_or_flow, **options)
18 | end
19 |
20 | flow_key = name_or_flow.name.to_sym
21 |
22 | if flows.key?(flow_key)
23 | ::Kernel.warn <<~WARNING
24 | [DOORKEEPER] '#{flow_key}' grant flow already registered and will be overridden
25 | in #{caller(1..1).first}
26 | WARNING
27 | end
28 |
29 | flows[flow_key] = name_or_flow
30 | end
31 |
32 | # Allows to register aliases that could be used in `grant_flows`
33 | # configuration option. It is possible to have aliases like 1:1 or
34 | # 1:N, i.e. "implicit_oidc" => ['token', 'id_token', 'id_token token'].
35 | #
36 | def register_alias(alias_name, **options)
37 | aliases[alias_name.to_sym] = Array.wrap(options.fetch(:as))
38 | end
39 |
40 | def expand_alias(alias_name)
41 | aliases.fetch(alias_name.to_sym, [])
42 | end
43 |
44 | # [NOTE]: make it to use #fetch after removing fallbacks
45 | def get(name)
46 | flows[name.to_sym]
47 | end
48 | end
49 | end
50 | end
51 |
--------------------------------------------------------------------------------
/lib/doorkeeper/grape/authorization_decorator.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | module Doorkeeper
4 | module Grape
5 | class AuthorizationDecorator < SimpleDelegator
6 | def parameters
7 | params
8 | end
9 |
10 | def authorization
11 | env = __getobj__.env
12 | env["HTTP_AUTHORIZATION"] ||
13 | env["X-HTTP_AUTHORIZATION"] ||
14 | env["X_HTTP_AUTHORIZATION"] ||
15 | env["REDIRECT_X_HTTP_AUTHORIZATION"]
16 | end
17 | end
18 | end
19 | end
20 |
--------------------------------------------------------------------------------
/lib/doorkeeper/grape/helpers.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | require "doorkeeper/grape/authorization_decorator"
4 |
5 | module Doorkeeper
6 | module Grape
7 | # Doorkeeper helpers for Grape applications.
8 | # Provides helpers for endpoints authorization based on defined set of scopes.
9 | module Helpers
10 | # These helpers are for grape >= 0.10
11 | extend ::Grape::API::Helpers
12 | include Doorkeeper::Rails::Helpers
13 |
14 | # endpoint specific scopes > parameter scopes > default scopes
15 | def doorkeeper_authorize!(*scopes)
16 | endpoint_scopes = endpoint.route_setting(:scopes) ||
17 | endpoint.options[:route_options][:scopes]
18 |
19 | scopes = if endpoint_scopes
20 | Doorkeeper::OAuth::Scopes.from_array(endpoint_scopes)
21 | elsif scopes.present?
22 | Doorkeeper::OAuth::Scopes.from_array(scopes)
23 | end
24 |
25 | super(*scopes)
26 | end
27 |
28 | def doorkeeper_render_error_with(error)
29 | status_code = error_status_codes[error.status]
30 | error!({ error: error.description }, status_code, error.headers)
31 | end
32 |
33 | private
34 |
35 | def endpoint
36 | env["api.endpoint"]
37 | end
38 |
39 | def doorkeeper_token
40 | @doorkeeper_token ||= OAuth::Token.authenticate(
41 | decorated_request,
42 | *Doorkeeper.config.access_token_methods,
43 | )
44 | end
45 |
46 | def decorated_request
47 | AuthorizationDecorator.new(request)
48 | end
49 |
50 | def error_status_codes
51 | {
52 | unauthorized: 401,
53 | forbidden: 403,
54 | }
55 | end
56 | end
57 | end
58 | end
59 |
--------------------------------------------------------------------------------
/lib/doorkeeper/models/concerns/accessible.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | module Doorkeeper
4 | module Models
5 | module Accessible
6 | # Indicates whether the object is accessible (not expired and not revoked).
7 | #
8 | # @return [Boolean] true if object accessible or false in other case
9 | #
10 | def accessible?
11 | !expired? && !revoked?
12 | end
13 | end
14 | end
15 | end
16 |
--------------------------------------------------------------------------------
/lib/doorkeeper/models/concerns/expirable.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | module Doorkeeper
4 | module Models
5 | module Expirable
6 | # Indicates whether the object is expired (`#expires_in` present and
7 | # expiration time has come).
8 | #
9 | # @return [Boolean] true if object expired and false in other case
10 | def expired?
11 | !!(expires_in && Time.now.utc > expires_at)
12 | end
13 |
14 | # Calculates expiration time in seconds.
15 | #
16 | # @return [Integer, nil] number of seconds if object has expiration time
17 | # or nil if object never expires.
18 | def expires_in_seconds
19 | return nil if expires_in.nil?
20 |
21 | expires = expires_at - Time.now.utc
22 | expires_sec = expires.seconds.round(0)
23 | expires_sec > 0 ? expires_sec : 0
24 | end
25 |
26 | # Expiration time (date time of creation + TTL).
27 | #
28 | # @return [Time, nil] expiration time in UTC
29 | # or nil if the object never expires.
30 | #
31 | def expires_at
32 | expires_in && created_at + expires_in.seconds
33 | end
34 | end
35 | end
36 | end
37 |
--------------------------------------------------------------------------------
/lib/doorkeeper/models/concerns/orderable.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | module Doorkeeper
4 | module Models
5 | module Orderable
6 | extend ActiveSupport::Concern
7 |
8 | module ClassMethods
9 | def ordered_by(attribute, direction = :asc)
10 | order(attribute => direction)
11 | end
12 | end
13 | end
14 | end
15 | end
16 |
--------------------------------------------------------------------------------
/lib/doorkeeper/models/concerns/ownership.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | module Doorkeeper
4 | module Models
5 | module Ownership
6 | extend ActiveSupport::Concern
7 |
8 | included do
9 | belongs_to :owner, polymorphic: true, optional: true
10 | validates :owner, presence: true, if: :validate_owner?
11 | end
12 |
13 | def validate_owner?
14 | Doorkeeper.config.confirm_application_owner?
15 | end
16 | end
17 | end
18 | end
19 |
--------------------------------------------------------------------------------
/lib/doorkeeper/models/concerns/polymorphic_resource_owner.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | module Doorkeeper
4 | module Models
5 | module PolymorphicResourceOwner
6 | module ForAccessGrant
7 | extend ActiveSupport::Concern
8 |
9 | included do
10 | if Doorkeeper.config.polymorphic_resource_owner?
11 | belongs_to :resource_owner, polymorphic: true, optional: false
12 | else
13 | validates :resource_owner_id, presence: true
14 | end
15 | end
16 | end
17 |
18 | module ForAccessToken
19 | extend ActiveSupport::Concern
20 |
21 | included do
22 | if Doorkeeper.config.polymorphic_resource_owner?
23 | belongs_to :resource_owner, polymorphic: true, optional: true
24 | end
25 | end
26 | end
27 | end
28 | end
29 | end
30 |
31 |
--------------------------------------------------------------------------------
/lib/doorkeeper/models/concerns/resource_ownerable.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | module Doorkeeper
4 | module Models
5 | module ResourceOwnerable
6 | extend ActiveSupport::Concern
7 |
8 | module ClassMethods
9 | # Searches for record by Resource Owner considering Doorkeeper
10 | # configuration for resource owner association.
11 | #
12 | # @param resource_owner [ActiveRecord::Base, Integer]
13 | # resource owner
14 | #
15 | # @return [Doorkeeper::AccessGrant, Doorkeeper::AccessToken]
16 | # collection of records
17 | #
18 | def by_resource_owner(resource_owner)
19 | if Doorkeeper.configuration.polymorphic_resource_owner?
20 | where(resource_owner: resource_owner)
21 | else
22 | where(resource_owner_id: resource_owner_id_for(resource_owner))
23 | end
24 | end
25 |
26 | protected
27 |
28 | # Backward compatible way to retrieve resource owner itself (if
29 | # polymorphic association enabled) or just it's ID.
30 | #
31 | # @param resource_owner [ActiveRecord::Base, Integer]
32 | # resource owner
33 | #
34 | # @return [ActiveRecord::Base, Integer]
35 | # instance of Resource Owner or it's ID
36 | #
37 | def resource_owner_id_for(resource_owner)
38 | if resource_owner.respond_to?(:to_key)
39 | resource_owner.id
40 | else
41 | resource_owner
42 | end
43 | end
44 | end
45 | end
46 | end
47 | end
48 |
--------------------------------------------------------------------------------
/lib/doorkeeper/models/concerns/reusable.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | module Doorkeeper
4 | module Models
5 | module Reusable
6 | # Indicates whether the object is reusable (i.e. It is not expired and
7 | # has not crossed reuse_limit).
8 | #
9 | # @return [Boolean] true if can be reused and false in other case
10 | def reusable?
11 | return false if expired?
12 | return true unless expires_in
13 |
14 | threshold_limit = 100 - Doorkeeper.config.token_reuse_limit
15 | expires_in_seconds >= threshold_limit * expires_in / 100
16 | end
17 | end
18 | end
19 | end
20 |
--------------------------------------------------------------------------------
/lib/doorkeeper/models/concerns/revocable.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | module Doorkeeper
4 | module Models
5 | module Revocable
6 | # Revokes the object (updates `:revoked_at` attribute setting its value
7 | # to the specific time).
8 | #
9 | # @param clock [Time] time object
10 | #
11 | def revoke(clock = Time)
12 | update_attribute(:revoked_at, clock.now.utc)
13 | end
14 |
15 | # Indicates whether the object has been revoked.
16 | #
17 | # @return [Boolean] true if revoked, false in other case
18 | #
19 | def revoked?
20 | !!(revoked_at && revoked_at <= Time.now.utc)
21 | end
22 | end
23 | end
24 | end
25 |
--------------------------------------------------------------------------------
/lib/doorkeeper/models/concerns/scopes.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | module Doorkeeper
4 | module Models
5 | module Scopes
6 | def scopes
7 | OAuth::Scopes.from_string(scopes_string)
8 | end
9 |
10 | def scopes=(value)
11 | if value.is_a?(Array)
12 | super(Doorkeeper::OAuth::Scopes.from_array(value).to_s)
13 | else
14 | super(Doorkeeper::OAuth::Scopes.from_string(value.to_s).to_s)
15 | end
16 | end
17 |
18 | def scopes_string
19 | self[:scopes]
20 | end
21 |
22 | def includes_scope?(*required_scopes)
23 | required_scopes.blank? || required_scopes.any? { |scope| scopes.exists?(scope.to_s) }
24 | end
25 | end
26 | end
27 | end
28 |
--------------------------------------------------------------------------------
/lib/doorkeeper/oauth.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | module Doorkeeper
4 | module OAuth
5 | GRANT_TYPES = [
6 | AUTHORIZATION_CODE = "authorization_code",
7 | IMPLICIT = "implicit",
8 | PASSWORD = "password",
9 | CLIENT_CREDENTIALS = "client_credentials",
10 | REFRESH_TOKEN = "refresh_token",
11 | ].freeze
12 | end
13 | end
14 |
--------------------------------------------------------------------------------
/lib/doorkeeper/oauth/authorization/code.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | module Doorkeeper
4 | module OAuth
5 | module Authorization
6 | class Code
7 | attr_reader :pre_auth, :resource_owner, :token
8 |
9 | def initialize(pre_auth, resource_owner)
10 | @pre_auth = pre_auth
11 | @resource_owner = resource_owner
12 | end
13 |
14 | def issue_token!
15 | return @token if defined?(@token)
16 |
17 | @token = Doorkeeper.config.access_grant_model.create!(access_grant_attributes)
18 | end
19 |
20 | def oob_redirect
21 | { action: :show, code: token.plaintext_token }
22 | end
23 |
24 | def access_grant?
25 | true
26 | end
27 |
28 | private
29 |
30 | def authorization_code_expires_in
31 | Doorkeeper.config.authorization_code_expires_in
32 | end
33 |
34 | def access_grant_attributes
35 | attributes = {
36 | application_id: pre_auth.client.id,
37 | expires_in: authorization_code_expires_in,
38 | redirect_uri: pre_auth.redirect_uri,
39 | scopes: pre_auth.scopes.to_s,
40 | }
41 |
42 | if Doorkeeper.config.polymorphic_resource_owner?
43 | attributes[:resource_owner] = resource_owner
44 | else
45 | attributes[:resource_owner_id] = resource_owner.id
46 | end
47 |
48 | pkce_attributes.merge(attributes).merge(custom_attributes)
49 | end
50 |
51 | def custom_attributes
52 | # Custom access token attributes are saved into the access grant,
53 | # and then included in subsequently generated access tokens.
54 | @pre_auth.custom_access_token_attributes.to_h.with_indifferent_access
55 | end
56 |
57 | def pkce_attributes
58 | return {} unless pkce_supported?
59 |
60 | {
61 | code_challenge: pre_auth.code_challenge,
62 | code_challenge_method: pre_auth.code_challenge_method,
63 | }
64 | end
65 |
66 | # Ensures firstly, if migration with additional PKCE columns was
67 | # generated and migrated
68 | def pkce_supported?
69 | Doorkeeper.config.access_grant_model.pkce_supported?
70 | end
71 | end
72 | end
73 | end
74 | end
75 |
--------------------------------------------------------------------------------
/lib/doorkeeper/oauth/authorization/context.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | module Doorkeeper
4 | module OAuth
5 | module Authorization
6 | class Context
7 | attr_reader :client, :grant_type, :resource_owner, :scopes
8 |
9 | def initialize(**attributes)
10 | attributes.each do |name, value|
11 | instance_variable_set(:"@#{name}", value) if respond_to?(name)
12 | end
13 | end
14 | end
15 | end
16 | end
17 | end
18 |
--------------------------------------------------------------------------------
/lib/doorkeeper/oauth/authorization/uri_builder.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | require "rack/utils"
4 |
5 | module Doorkeeper
6 | module OAuth
7 | module Authorization
8 | class URIBuilder
9 | class << self
10 | def uri_with_query(url, parameters = {})
11 | uri = URI.parse(url)
12 | original_query = Rack::Utils.parse_query(uri.query)
13 | uri.query = build_query(original_query.merge(parameters))
14 | uri.to_s
15 | end
16 |
17 | def uri_with_fragment(url, parameters = {})
18 | uri = URI.parse(url)
19 | uri.fragment = build_query(parameters)
20 | uri.to_s
21 | end
22 |
23 | private
24 |
25 | def build_query(parameters = {})
26 | parameters.reject! { |_, value| value.blank? }
27 | Rack::Utils.build_query(parameters)
28 | end
29 | end
30 | end
31 | end
32 | end
33 | end
34 |
--------------------------------------------------------------------------------
/lib/doorkeeper/oauth/base_request.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | module Doorkeeper
4 | module OAuth
5 | class BaseRequest
6 | include Validations
7 |
8 | attr_reader :grant_type, :server
9 |
10 | delegate :default_scopes, to: :server
11 |
12 | def authorize
13 | if valid?
14 | before_successful_response
15 | @response = TokenResponse.new(access_token)
16 | after_successful_response
17 | @response
18 | elsif error == Errors::InvalidRequest
19 | @response = InvalidRequestResponse.from_request(self)
20 | else
21 | @response = ErrorResponse.from_request(self)
22 | end
23 | end
24 |
25 | def scopes
26 | @scopes ||= build_scopes
27 | end
28 |
29 | def find_or_create_access_token(client, resource_owner, scopes, custom_attributes, server)
30 | context = Authorization::Token.build_context(client, grant_type, scopes, resource_owner)
31 | application = client.is_a?(Doorkeeper.config.application_model) ? client : client&.application
32 |
33 | token_attributes = {
34 | application: application,
35 | resource_owner: resource_owner,
36 | scopes: scopes,
37 | expires_in: Authorization::Token.access_token_expires_in(server, context),
38 | use_refresh_token: Authorization::Token.refresh_token_enabled?(server, context),
39 | }
40 |
41 | @access_token =
42 | Doorkeeper.config.access_token_model.find_or_create_for(**token_attributes.merge(custom_attributes))
43 | end
44 |
45 | def before_successful_response
46 | Doorkeeper.config.before_successful_strategy_response.call(self)
47 | end
48 |
49 | def after_successful_response
50 | Doorkeeper.config.after_successful_strategy_response.call(self, @response)
51 | end
52 |
53 | private
54 |
55 | def build_scopes
56 | if @original_scopes.present?
57 | OAuth::Scopes.from_string(@original_scopes)
58 | else
59 | client_scopes = @client&.scopes
60 | return default_scopes if client_scopes.blank?
61 |
62 | # Avoid using Scope#& for dynamic scopes
63 | client_scopes.allowed(default_scopes)
64 | end
65 | end
66 | end
67 | end
68 | end
69 |
--------------------------------------------------------------------------------
/lib/doorkeeper/oauth/base_response.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | module Doorkeeper
4 | module OAuth
5 | class BaseResponse
6 | def body
7 | {}
8 | end
9 |
10 | def description
11 | ""
12 | end
13 |
14 | def headers
15 | {}
16 | end
17 |
18 | def redirectable?
19 | false
20 | end
21 |
22 | def redirect_uri
23 | ""
24 | end
25 |
26 | def status
27 | :ok
28 | end
29 | end
30 | end
31 | end
32 |
--------------------------------------------------------------------------------
/lib/doorkeeper/oauth/client.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | module Doorkeeper
4 | module OAuth
5 | class Client
6 | attr_reader :application
7 |
8 | delegate :id, :name, :uid, :redirect_uri, :scopes, :confidential, to: :@application
9 |
10 | def initialize(application)
11 | @application = application
12 | end
13 |
14 | def self.find(uid, method = Doorkeeper.config.application_model.method(:by_uid))
15 | return unless (application = method.call(uid))
16 |
17 | new(application)
18 | end
19 |
20 | def self.authenticate(credentials, method = Doorkeeper.config.application_model.method(:by_uid_and_secret))
21 | return if credentials.blank?
22 | return unless (application = method.call(credentials.uid, credentials.secret))
23 |
24 | new(application)
25 | end
26 | end
27 | end
28 | end
29 |
--------------------------------------------------------------------------------
/lib/doorkeeper/oauth/client/credentials.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | module Doorkeeper
4 | module OAuth
5 | class Client
6 | Credentials = Struct.new(:uid, :secret) do
7 | class << self
8 | def from_request(request, *credentials_methods)
9 | credentials_methods.inject(nil) do |_, method|
10 | method = self.method(method) if method.is_a?(Symbol)
11 | credentials = Credentials.new(*method.call(request))
12 | break credentials if credentials.present?
13 | end
14 | end
15 |
16 | def from_params(request)
17 | request.parameters.values_at(:client_id, :client_secret)
18 | end
19 |
20 | def from_basic(request)
21 | authorization = request.authorization
22 | if authorization.present? && authorization =~ /^Basic (.*)/m
23 | Base64.decode64(Regexp.last_match(1)).split(/:/, 2)
24 | end
25 | end
26 | end
27 |
28 | # Public clients may have their secret blank, but "credentials" are
29 | # still present
30 | delegate :blank?, to: :uid
31 | end
32 | end
33 | end
34 | end
35 |
--------------------------------------------------------------------------------
/lib/doorkeeper/oauth/client_credentials/creator.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | module Doorkeeper
4 | module OAuth
5 | module ClientCredentials
6 | class Creator
7 | def call(client, scopes, attributes = {})
8 | existing_token = nil
9 |
10 | if lookup_existing_token?
11 | existing_token = find_active_existing_token_for(client, scopes, attributes)
12 | return existing_token if Doorkeeper.config.reuse_access_token && existing_token&.reusable?
13 | end
14 |
15 | with_revocation(existing_token: existing_token) do
16 | application = client.is_a?(Doorkeeper.config.application_model) ? client : client&.application
17 | Doorkeeper.config.access_token_model.create_for(
18 | application: application,
19 | resource_owner: nil,
20 | scopes: scopes,
21 | **attributes,
22 | )
23 | end
24 | end
25 |
26 | private
27 |
28 | def with_revocation(existing_token:)
29 | if existing_token && Doorkeeper.config.revoke_previous_client_credentials_token?
30 | existing_token.with_lock do
31 | raise Errors::DoorkeeperError, :invalid_token_reuse if existing_token.revoked?
32 |
33 | existing_token.revoke
34 |
35 | yield
36 | end
37 | else
38 | yield
39 | end
40 | end
41 |
42 | def lookup_existing_token?
43 | Doorkeeper.config.reuse_access_token ||
44 | Doorkeeper.config.revoke_previous_client_credentials_token?
45 | end
46 |
47 | def find_active_existing_token_for(client, scopes, attributes)
48 | custom_attributes = Doorkeeper.config.access_token_model.
49 | extract_custom_attributes(attributes).presence
50 | Doorkeeper.config.access_token_model.matching_token_for(
51 | client, nil, scopes, custom_attributes: custom_attributes, include_expired: false)
52 | end
53 | end
54 | end
55 | end
56 | end
57 |
--------------------------------------------------------------------------------
/lib/doorkeeper/oauth/client_credentials/issuer.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | module Doorkeeper
4 | module OAuth
5 | module ClientCredentials
6 | class Issuer
7 | attr_reader :token, :validator, :error
8 |
9 | def initialize(server, validator)
10 | @server = server
11 | @validator = validator
12 | end
13 |
14 | def create(client, scopes, attributes = {}, creator = Creator.new)
15 | if validator.valid?
16 | @token = create_token(client, scopes, attributes, creator)
17 | @error = Errors::ServerError unless @token
18 | else
19 | @token = false
20 | @error = validator.error
21 | end
22 |
23 | @token
24 | end
25 |
26 | private
27 |
28 | def create_token(client, scopes, attributes, creator)
29 | context = Authorization::Token.build_context(
30 | client,
31 | Doorkeeper::OAuth::CLIENT_CREDENTIALS,
32 | scopes,
33 | nil,
34 | )
35 | ttl = Authorization::Token.access_token_expires_in(@server, context)
36 |
37 | creator.call(
38 | client,
39 | scopes,
40 | use_refresh_token: false,
41 | expires_in: ttl,
42 | **attributes
43 | )
44 | end
45 | end
46 | end
47 | end
48 | end
49 |
--------------------------------------------------------------------------------
/lib/doorkeeper/oauth/client_credentials/validator.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | module Doorkeeper
4 | module OAuth
5 | module ClientCredentials
6 | class Validator
7 | include Validations
8 | include OAuth::Helpers
9 |
10 | validate :client, error: Errors::InvalidClient
11 | validate :client_supports_grant_flow, error: Errors::UnauthorizedClient
12 | validate :scopes, error: Errors::InvalidScope
13 |
14 | def initialize(server, request)
15 | @server = server
16 | @request = request
17 | @client = request.client
18 |
19 | validate
20 | end
21 |
22 | private
23 |
24 | def validate_client
25 | @client.present?
26 | end
27 |
28 | def validate_client_supports_grant_flow
29 | return if @client.blank?
30 |
31 | Doorkeeper.config.allow_grant_flow_for_client?(
32 | Doorkeeper::OAuth::CLIENT_CREDENTIALS,
33 | @client.application,
34 | )
35 | end
36 |
37 | def validate_scopes
38 | application_scopes = if @client.present?
39 | @client.application.scopes
40 | else
41 | ""
42 | end
43 | return true if @request.scopes.blank? && application_scopes.blank?
44 |
45 | ScopeChecker.valid?(
46 | scope_str: @request.scopes.to_s,
47 | server_scopes: @server.scopes,
48 | app_scopes: application_scopes,
49 | grant_type: Doorkeeper::OAuth::CLIENT_CREDENTIALS,
50 | )
51 | end
52 | end
53 | end
54 | end
55 | end
56 |
--------------------------------------------------------------------------------
/lib/doorkeeper/oauth/client_credentials_request.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | module Doorkeeper
4 | module OAuth
5 | class ClientCredentialsRequest < BaseRequest
6 | attr_reader :client, :original_scopes, :parameters, :response
7 |
8 | alias error_response response
9 |
10 | delegate :error, to: :issuer
11 |
12 | def initialize(server, client, parameters = {})
13 | @client = client
14 | @server = server
15 | @response = nil
16 | @original_scopes = parameters[:scope]
17 | @parameters = parameters.except(:scope)
18 | end
19 |
20 | def access_token
21 | issuer.token
22 | end
23 |
24 | def issuer
25 | @issuer ||= ClientCredentials::Issuer.new(
26 | server,
27 | ClientCredentials::Validator.new(server, self),
28 | )
29 | end
30 |
31 | private
32 |
33 | def valid?
34 | issuer.create(client, scopes, custom_token_attributes_with_data)
35 | end
36 |
37 | def custom_token_attributes_with_data
38 | parameters
39 | .with_indifferent_access
40 | .slice(*Doorkeeper.config.custom_access_token_attributes)
41 | .symbolize_keys
42 | end
43 | end
44 | end
45 | end
46 |
--------------------------------------------------------------------------------
/lib/doorkeeper/oauth/code_request.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | module Doorkeeper
4 | module OAuth
5 | class CodeRequest
6 | attr_reader :pre_auth, :resource_owner
7 |
8 | def initialize(pre_auth, resource_owner)
9 | @pre_auth = pre_auth
10 | @resource_owner = resource_owner
11 | end
12 |
13 | def authorize
14 | auth = Authorization::Code.new(pre_auth, resource_owner)
15 | auth.issue_token!
16 | CodeResponse.new(pre_auth, auth, response_on_fragment: pre_auth.response_mode == "fragment")
17 | end
18 |
19 | def deny
20 | pre_auth.error = Errors::AccessDenied
21 | pre_auth.error_response
22 | end
23 | end
24 | end
25 | end
26 |
--------------------------------------------------------------------------------
/lib/doorkeeper/oauth/code_response.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | module Doorkeeper
4 | module OAuth
5 | class CodeResponse < BaseResponse
6 | include OAuth::Helpers
7 |
8 | attr_reader :pre_auth, :auth, :response_on_fragment
9 |
10 | def initialize(pre_auth, auth, options = {})
11 | @pre_auth = pre_auth
12 | @auth = auth
13 | @response_on_fragment = options[:response_on_fragment]
14 | end
15 |
16 | def redirectable?
17 | true
18 | end
19 |
20 | def issued_token
21 | auth.token
22 | end
23 |
24 | def body
25 | if auth.try(:access_token?)
26 | {
27 | access_token: auth.token.plaintext_token,
28 | token_type: auth.token.token_type,
29 | expires_in: auth.token.expires_in_seconds,
30 | state: pre_auth.state,
31 | }
32 | elsif auth.try(:access_grant?)
33 | {
34 | code: auth.token.plaintext_token,
35 | state: pre_auth.state,
36 | }
37 | end
38 | end
39 |
40 | def redirect_uri
41 | if URIChecker.oob_uri?(pre_auth.redirect_uri)
42 | auth.oob_redirect
43 | elsif response_on_fragment
44 | Authorization::URIBuilder.uri_with_fragment(pre_auth.redirect_uri, body)
45 | else
46 | Authorization::URIBuilder.uri_with_query(pre_auth.redirect_uri, body)
47 | end
48 | end
49 | end
50 | end
51 | end
52 |
--------------------------------------------------------------------------------
/lib/doorkeeper/oauth/error.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | module Doorkeeper
4 | module OAuth
5 | Error = Struct.new(:name, :state, :translate_options) do
6 | def description
7 | options = (translate_options || {}).merge(
8 | scope: %i[doorkeeper errors messages],
9 | default: :server_error,
10 | )
11 |
12 | I18n.translate(name, **options)
13 | end
14 | end
15 | end
16 | end
17 |
--------------------------------------------------------------------------------
/lib/doorkeeper/oauth/forbidden_token_response.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | module Doorkeeper
4 | module OAuth
5 | class ForbiddenTokenResponse < ErrorResponse
6 | def self.from_scopes(scopes, attributes = {})
7 | new(attributes.merge(scopes: scopes))
8 | end
9 |
10 | def initialize(attributes = {})
11 | super(attributes.merge(name: :invalid_scope, state: :forbidden))
12 | @scopes = attributes[:scopes]
13 | end
14 |
15 | def status
16 | :forbidden
17 | end
18 |
19 | def headers
20 | headers = super
21 | headers.delete "WWW-Authenticate"
22 | headers
23 | end
24 |
25 | def description
26 | @description ||= I18n.t("doorkeeper.errors.messages.forbidden_token.missing_scope",
27 | oauth_scopes: @scopes.map(&:to_s).join(" "),)
28 | end
29 |
30 | protected
31 |
32 | def exception_class
33 | Doorkeeper::Errors::TokenForbidden
34 | end
35 | end
36 | end
37 | end
38 |
--------------------------------------------------------------------------------
/lib/doorkeeper/oauth/helpers/scope_checker.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | module Doorkeeper
4 | module OAuth
5 | module Helpers
6 | module ScopeChecker
7 | class Validator
8 | attr_reader :parsed_scopes, :scope_str
9 |
10 | def initialize(scope_str, server_scopes, app_scopes, grant_type)
11 | @parsed_scopes = OAuth::Scopes.from_string(scope_str)
12 | @scope_str = scope_str
13 | @valid_scopes = valid_scopes(server_scopes, app_scopes)
14 |
15 | @scopes_by_grant_type = Doorkeeper.config.scopes_by_grant_type[grant_type.to_sym] if grant_type
16 | end
17 |
18 | def valid?
19 | scope_str.present? &&
20 | scope_str !~ /[\n\r\t]/ &&
21 | @valid_scopes.has_scopes?(parsed_scopes) &&
22 | permitted_to_grant_type?
23 | end
24 |
25 | private
26 |
27 | def valid_scopes(server_scopes, app_scopes)
28 | app_scopes.presence || server_scopes
29 | end
30 |
31 | def permitted_to_grant_type?
32 | return true unless @scopes_by_grant_type
33 |
34 | OAuth::Scopes.from_array(@scopes_by_grant_type)
35 | .has_scopes?(parsed_scopes)
36 | end
37 | end
38 |
39 | def self.valid?(scope_str:, server_scopes:, app_scopes: nil, grant_type: nil)
40 | Validator.new(
41 | scope_str,
42 | server_scopes,
43 | app_scopes,
44 | grant_type,
45 | ).valid?
46 | end
47 | end
48 | end
49 | end
50 | end
51 |
--------------------------------------------------------------------------------
/lib/doorkeeper/oauth/helpers/unique_token.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | module Doorkeeper
4 | module OAuth
5 | module Helpers
6 | # Default Doorkeeper token generator. Follows OAuth RFC and
7 | # could be customized using `default_generator_method` in
8 | # configuration.
9 | module UniqueToken
10 | def self.generate(options = {})
11 | # Access Token value must be 1*VSCHAR or
12 | # 1*( ALPHA / DIGIT / "-" / "." / "_" / "~" / "+" / "/" ) *"="
13 | #
14 | # @see https://datatracker.ietf.org/doc/html/rfc6749#appendix-A.12
15 | # @see https://datatracker.ietf.org/doc/html/rfc6750#section-2.1
16 | #
17 | generator = options.delete(:generator) || SecureRandom.method(default_generator_method)
18 | token_size = options.delete(:size) || 32
19 | generator.call(token_size)
20 | end
21 |
22 | # Generator method for default generator class (SecureRandom)
23 | #
24 | def self.default_generator_method
25 | Doorkeeper.config.default_generator_method
26 | end
27 | end
28 | end
29 | end
30 | end
31 |
--------------------------------------------------------------------------------
/lib/doorkeeper/oauth/hooks/context.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | module Doorkeeper
4 | module OAuth
5 | module Hooks
6 | class Context
7 | attr_reader :auth, :pre_auth
8 |
9 | def initialize(**attributes)
10 | attributes.each do |name, value|
11 | instance_variable_set(:"@#{name}", value) if respond_to?(name)
12 | end
13 | end
14 |
15 | def issued_token
16 | auth&.issued_token
17 | end
18 | end
19 | end
20 | end
21 | end
22 |
--------------------------------------------------------------------------------
/lib/doorkeeper/oauth/invalid_request_response.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | module Doorkeeper
4 | module OAuth
5 | class InvalidRequestResponse < ErrorResponse
6 | attr_reader :reason
7 |
8 | def self.from_request(request, attributes = {})
9 | new(
10 | attributes.merge(
11 | state: request.try(:state),
12 | redirect_uri: request.try(:redirect_uri),
13 | missing_param: request.try(:missing_param),
14 | reason: request.try(:invalid_request_reason),
15 | ),
16 | )
17 | end
18 |
19 | def initialize(attributes = {})
20 | super(attributes.merge(name: :invalid_request))
21 | @missing_param = attributes[:missing_param]
22 | @reason = @missing_param.nil? ? attributes[:reason] : :missing_param
23 | end
24 |
25 | def status
26 | :bad_request
27 | end
28 |
29 | def description
30 | I18n.translate(
31 | reason,
32 | scope: %i[doorkeeper errors messages invalid_request],
33 | default: :unknown,
34 | value: @missing_param,
35 | )
36 | end
37 |
38 | def exception_class
39 | Doorkeeper::Errors::InvalidRequest
40 | end
41 |
42 | def redirectable?
43 | super && @missing_param != :client_id
44 | end
45 | end
46 | end
47 | end
48 |
--------------------------------------------------------------------------------
/lib/doorkeeper/oauth/invalid_token_response.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | module Doorkeeper
4 | module OAuth
5 | class InvalidTokenResponse < ErrorResponse
6 | attr_reader :reason
7 |
8 | def self.from_access_token(access_token, attributes = {})
9 | reason = if access_token&.revoked?
10 | :revoked
11 | elsif access_token&.expired?
12 | :expired
13 | else
14 | :unknown
15 | end
16 |
17 | new(attributes.merge(reason: reason))
18 | end
19 |
20 | def initialize(attributes = {})
21 | super(attributes.merge(name: :invalid_token, state: :unauthorized))
22 | @reason = attributes[:reason] || :unknown
23 | end
24 |
25 | def status
26 | :unauthorized
27 | end
28 |
29 | def description
30 | @description ||=
31 | I18n.translate(
32 | @reason,
33 | scope: %i[doorkeeper errors messages invalid_token],
34 | )
35 | end
36 |
37 | protected
38 |
39 | def exception_class
40 | errors_mapping.fetch(reason)
41 | end
42 |
43 | private
44 |
45 | def errors_mapping
46 | {
47 | expired: Doorkeeper::Errors::TokenExpired,
48 | revoked: Doorkeeper::Errors::TokenRevoked,
49 | unknown: Doorkeeper::Errors::TokenUnknown,
50 | }
51 | end
52 | end
53 | end
54 | end
55 |
--------------------------------------------------------------------------------
/lib/doorkeeper/oauth/nonstandard.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | module Doorkeeper
4 | module OAuth
5 | class NonStandard
6 | # These are not part of the OAuth 2 specification but are still in use by Google
7 | # and in some other implementations. Native applications should use one of the
8 | # approaches discussed in RFC8252. OOB is 'Out of Band'
9 |
10 | # This value signals to the Google Authorization Server that the authorization
11 | # code should be returned in the title bar of the browser, with the page text
12 | # prompting the user to copy the code and paste it in the application.
13 | # This is useful when the client (such as a Windows application) cannot listen
14 | # on an HTTP port without significant client configuration.
15 |
16 | # When you use this value, your application can then detect that the page has loaded, and can
17 | # read the title of the HTML page to obtain the authorization code. It is then up to your
18 | # application to close the browser window if you want to ensure that the user never sees the
19 | # page that contains the authorization code. The mechanism for doing this varies from platform
20 | # to platform.
21 | #
22 | # If your platform doesn't allow you to detect that the page has loaded or read the title of
23 | # the page, you can have the user paste the code back to your application, as prompted by the
24 | # text in the confirmation page that the OAuth 2.0 server generates.
25 | IETF_WG_OAUTH2_OOB = "urn:ietf:wg:oauth:2.0:oob"
26 |
27 | # This is identical to urn:ietf:wg:oauth:2.0:oob, but the text in the confirmation page that
28 | # the OAuth 2.0 server generates won't instruct the user to copy the authorization code, but
29 | # instead will simply ask the user to close the window.
30 | #
31 | # This is useful when your application reads the title of the HTML page (by checking window
32 | # titles on the desktop, for example) to obtain the authorization code, but can't close the
33 | # page on its own.
34 | IETF_WG_OAUTH2_OOB_AUTO = "urn:ietf:wg:oauth:2.0:oob:auto"
35 |
36 | IETF_WG_OAUTH2_OOB_METHODS = [IETF_WG_OAUTH2_OOB, IETF_WG_OAUTH2_OOB_AUTO].freeze
37 | end
38 | end
39 | end
40 |
--------------------------------------------------------------------------------
/lib/doorkeeper/oauth/token.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | module Doorkeeper
4 | module OAuth
5 | class Token
6 | class << self
7 | def from_request(request, *methods)
8 | methods.inject(nil) do |_, method|
9 | method = self.method(method) if method.is_a?(Symbol)
10 | credentials = method.call(request)
11 | break credentials if credentials.present?
12 | end
13 | end
14 |
15 | def authenticate(request, *methods)
16 | if (token = from_request(request, *methods))
17 | access_token = Doorkeeper.config.access_token_model.by_token(token)
18 | if access_token.present? && Doorkeeper.config.refresh_token_enabled?
19 | access_token.revoke_previous_refresh_token!
20 | end
21 | access_token
22 | end
23 | end
24 |
25 | def from_access_token_param(request)
26 | request.parameters[:access_token]
27 | end
28 |
29 | def from_bearer_param(request)
30 | request.parameters[:bearer_token]
31 | end
32 |
33 | def from_bearer_authorization(request)
34 | pattern = /^Bearer /i
35 | header = request.authorization
36 | token_from_header(header, pattern) if match?(header, pattern)
37 | end
38 |
39 | def from_basic_authorization(request)
40 | pattern = /^Basic /i
41 | header = request.authorization
42 | token_from_basic_header(header, pattern) if match?(header, pattern)
43 | end
44 |
45 | private
46 |
47 | def token_from_basic_header(header, pattern)
48 | encoded_header = token_from_header(header, pattern)
49 | decode_basic_credentials_token(encoded_header)
50 | end
51 |
52 | def decode_basic_credentials_token(encoded_header)
53 | Base64.decode64(encoded_header).split(/:/, 2).first
54 | end
55 |
56 | def token_from_header(header, pattern)
57 | header.gsub(pattern, "")
58 | end
59 |
60 | def match?(header, pattern)
61 | header&.match(pattern)
62 | end
63 | end
64 | end
65 | end
66 | end
67 |
--------------------------------------------------------------------------------
/lib/doorkeeper/oauth/token_request.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | module Doorkeeper
4 | module OAuth
5 | class TokenRequest
6 | attr_reader :pre_auth, :resource_owner
7 |
8 | def initialize(pre_auth, resource_owner)
9 | @pre_auth = pre_auth
10 | @resource_owner = resource_owner
11 | end
12 |
13 | def authorize
14 | auth = Authorization::Token.new(pre_auth, resource_owner)
15 | auth.issue_token!
16 | CodeResponse.new(pre_auth, auth, response_on_fragment: true)
17 | end
18 |
19 | def deny
20 | pre_auth.error = Errors::AccessDenied
21 | pre_auth.error_response
22 | end
23 | end
24 | end
25 | end
26 |
--------------------------------------------------------------------------------
/lib/doorkeeper/oauth/token_response.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | module Doorkeeper
4 | module OAuth
5 | class TokenResponse
6 | attr_reader :token
7 |
8 | alias issued_token token
9 |
10 | def initialize(token)
11 | @token = token
12 | end
13 |
14 | def body
15 | @body ||= {
16 | "access_token" => token.plaintext_token,
17 | "token_type" => token.token_type,
18 | "expires_in" => token.expires_in_seconds,
19 | "refresh_token" => token.plaintext_refresh_token,
20 | "scope" => token.scopes_string,
21 | "created_at" => token.created_at.to_i,
22 | }.reject { |_, value| value.blank? }
23 | end
24 |
25 | def status
26 | :ok
27 | end
28 |
29 | def headers
30 | {
31 | "Cache-Control" => "no-store, no-cache",
32 | "Content-Type" => "application/json; charset=utf-8",
33 | "Pragma" => "no-cache",
34 | }
35 | end
36 | end
37 | end
38 | end
39 |
--------------------------------------------------------------------------------
/lib/doorkeeper/orm/active_record.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | module Doorkeeper
4 | autoload :AccessGrant, "doorkeeper/orm/active_record/access_grant"
5 | autoload :AccessToken, "doorkeeper/orm/active_record/access_token"
6 | autoload :Application, "doorkeeper/orm/active_record/application"
7 | autoload :RedirectUriValidator, "doorkeeper/orm/active_record/redirect_uri_validator"
8 |
9 | module Models
10 | autoload :Ownership, "doorkeeper/models/concerns/ownership"
11 | end
12 |
13 | # ActiveRecord ORM for Doorkeeper entity models.
14 | # Consists of three main OAuth entities:
15 | # * Access Token
16 | # * Access Grant
17 | # * Application (client)
18 | #
19 | # Do a lazy loading of all the required and configured stuff.
20 | #
21 | module Orm
22 | module ActiveRecord
23 | autoload :StaleRecordsCleaner, "doorkeeper/orm/active_record/stale_records_cleaner"
24 |
25 | module Mixins
26 | autoload :AccessGrant, "doorkeeper/orm/active_record/mixins/access_grant"
27 | autoload :AccessToken, "doorkeeper/orm/active_record/mixins/access_token"
28 | autoload :Application, "doorkeeper/orm/active_record/mixins/application"
29 | end
30 |
31 | def self.run_hooks
32 | initialize_configured_associations
33 | end
34 |
35 | def self.initialize_configured_associations
36 | if Doorkeeper.config.enable_application_owner?
37 | Doorkeeper.config.application_model.include ::Doorkeeper::Models::Ownership
38 | end
39 |
40 | Doorkeeper.config.access_grant_model.include ::Doorkeeper::Models::PolymorphicResourceOwner::ForAccessGrant
41 | Doorkeeper.config.access_token_model.include ::Doorkeeper::Models::PolymorphicResourceOwner::ForAccessToken
42 | end
43 | end
44 | end
45 | end
46 |
--------------------------------------------------------------------------------
/lib/doorkeeper/orm/active_record/access_grant.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | require "doorkeeper/orm/active_record/mixins/access_grant"
4 |
5 | module Doorkeeper
6 | class AccessGrant < ::ActiveRecord::Base
7 | include Doorkeeper::Orm::ActiveRecord::Mixins::AccessGrant
8 | end
9 | end
10 |
--------------------------------------------------------------------------------
/lib/doorkeeper/orm/active_record/access_token.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | require "doorkeeper/orm/active_record/mixins/access_token"
4 |
5 | module Doorkeeper
6 | class AccessToken < ::ActiveRecord::Base
7 | include Doorkeeper::Orm::ActiveRecord::Mixins::AccessToken
8 | end
9 | end
10 |
--------------------------------------------------------------------------------
/lib/doorkeeper/orm/active_record/application.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | require "doorkeeper/orm/active_record/redirect_uri_validator"
4 | require "doorkeeper/orm/active_record/mixins/application"
5 |
6 | module Doorkeeper
7 | class Application < ::ActiveRecord::Base
8 | include ::Doorkeeper::Orm::ActiveRecord::Mixins::Application
9 | end
10 | end
11 |
--------------------------------------------------------------------------------
/lib/doorkeeper/orm/active_record/mixins/access_grant.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | module Doorkeeper::Orm::ActiveRecord::Mixins
4 | module AccessGrant
5 | extend ActiveSupport::Concern
6 |
7 | included do
8 | self.table_name = compute_doorkeeper_table_name
9 | self.strict_loading_by_default = false if respond_to?(:strict_loading_by_default)
10 |
11 | include ::Doorkeeper::AccessGrantMixin
12 |
13 | belongs_to :application, class_name: Doorkeeper.config.application_class.to_s,
14 | optional: true,
15 | inverse_of: :access_grants
16 |
17 | validates :application_id,
18 | :token,
19 | :expires_in,
20 | :redirect_uri,
21 | presence: true
22 |
23 | validates :token, uniqueness: { case_sensitive: true }
24 |
25 | before_validation :generate_token, on: :create
26 |
27 | # We keep a volatile copy of the raw token for initial communication
28 | # The stored refresh_token may be mapped and not available in cleartext.
29 | #
30 | # Some strategies allow restoring stored secrets (e.g. symmetric encryption)
31 | # while hashing strategies do not, so you cannot rely on this value
32 | # returning a present value for persisted tokens.
33 | def plaintext_token
34 | if secret_strategy.allows_restoring_secrets?
35 | secret_strategy.restore_secret(self, :token)
36 | else
37 | @raw_token
38 | end
39 | end
40 |
41 | private
42 |
43 | # Generates token value with UniqueToken class.
44 | #
45 | # @return [String] token value
46 | #
47 | def generate_token
48 | @raw_token = Doorkeeper::OAuth::Helpers::UniqueToken.generate
49 | secret_strategy.store_secret(self, :token, @raw_token)
50 | end
51 | end
52 |
53 | module ClassMethods
54 | private
55 |
56 | def compute_doorkeeper_table_name
57 | table_name = "oauth_access_grant"
58 | table_name = table_name.pluralize if pluralize_table_names
59 | "#{table_name_prefix}#{table_name}#{table_name_suffix}"
60 | end
61 | end
62 | end
63 | end
64 |
--------------------------------------------------------------------------------
/lib/doorkeeper/orm/active_record/redirect_uri_validator.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | require "uri"
4 |
5 | module Doorkeeper
6 | # ActiveModel validator for redirect URI validation in according
7 | # to OAuth standards and Doorkeeper configuration.
8 | class RedirectUriValidator < ActiveModel::EachValidator
9 | def validate_each(record, attribute, value)
10 | if value.blank?
11 | return if Doorkeeper.config.allow_blank_redirect_uri?(record)
12 |
13 | record.errors.add(attribute, :blank)
14 | else
15 | value.split.each do |val|
16 | next if oob_redirect_uri?(val)
17 |
18 | uri = ::URI.parse(val)
19 | record.errors.add(attribute, :forbidden_uri) if forbidden_uri?(uri)
20 | record.errors.add(attribute, :fragment_present) unless uri.fragment.nil?
21 | record.errors.add(attribute, :unspecified_scheme) if unspecified_scheme?(uri)
22 | record.errors.add(attribute, :relative_uri) if relative_uri?(uri)
23 | record.errors.add(attribute, :secured_uri) if invalid_ssl_uri?(uri)
24 | record.errors.add(attribute, :invalid_uri) if unspecified_host?(uri)
25 | end
26 | end
27 | rescue URI::InvalidURIError
28 | record.errors.add(attribute, :invalid_uri)
29 | end
30 |
31 | private
32 |
33 | def oob_redirect_uri?(uri)
34 | Doorkeeper::OAuth::NonStandard::IETF_WG_OAUTH2_OOB_METHODS.include?(uri)
35 | end
36 |
37 | def forbidden_uri?(uri)
38 | Doorkeeper.config.forbid_redirect_uri.call(uri)
39 | end
40 |
41 | def unspecified_scheme?(uri)
42 | return true if uri.opaque.present?
43 |
44 | %w[localhost].include?(uri.try(:scheme))
45 | end
46 |
47 | def unspecified_host?(uri)
48 | uri.is_a?(URI::HTTP) && uri.host.blank?
49 | end
50 |
51 | def relative_uri?(uri)
52 | uri.scheme.nil? && uri.host.blank?
53 | end
54 |
55 | def invalid_ssl_uri?(uri)
56 | forces_ssl = Doorkeeper.config.force_ssl_in_redirect_uri
57 | non_https = uri.try(:scheme) == "http"
58 |
59 | if forces_ssl.respond_to?(:call)
60 | forces_ssl.call(uri) && non_https
61 | else
62 | forces_ssl && non_https
63 | end
64 | end
65 | end
66 | end
67 |
--------------------------------------------------------------------------------
/lib/doorkeeper/orm/active_record/stale_records_cleaner.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | module Doorkeeper
4 | module Orm
5 | module ActiveRecord
6 | # Helper class to clear stale and non-active tokens and grants.
7 | # Used by Doorkeeper Rake tasks.
8 | #
9 | class StaleRecordsCleaner
10 | def initialize(base_scope)
11 | @base_scope = base_scope
12 | end
13 |
14 | # Clears revoked records
15 | def clean_revoked
16 | table = @base_scope.arel_table
17 |
18 | @base_scope
19 | .where.not(revoked_at: nil)
20 | .where(table[:revoked_at].lt(Time.current))
21 | .in_batches(&:delete_all)
22 | end
23 |
24 | # Clears expired records
25 | def clean_expired(ttl)
26 | table = @base_scope.arel_table
27 |
28 | @base_scope
29 | .where.not(expires_in: nil)
30 | .where(table[:created_at].lt(Time.current - ttl))
31 | .in_batches(&:delete_all)
32 | end
33 | end
34 | end
35 | end
36 | end
37 |
--------------------------------------------------------------------------------
/lib/doorkeeper/rails/routes/abstract_router.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | module Doorkeeper
4 | module Rails
5 | # Abstract router module that implements base behavior
6 | # for generating and mapping Rails routes.
7 | #
8 | # Could be reused in Doorkeeper extensions.
9 | #
10 | module AbstractRouter
11 | extend ActiveSupport::Concern
12 |
13 | attr_reader :routes
14 |
15 | def initialize(routes, mapper = Mapper.new, &block)
16 | @routes = routes
17 | @mapping = mapper.map(&block)
18 | end
19 |
20 | def generate_routes!(**_options)
21 | raise NotImplementedError, "must be redefined for #{self.class.name}!"
22 | end
23 |
24 | private
25 |
26 | def map_route(name, method)
27 | return if @mapping.skipped?(name)
28 |
29 | send(method, @mapping[name])
30 |
31 | mapping[name] = @mapping[name]
32 | end
33 | end
34 | end
35 | end
36 |
--------------------------------------------------------------------------------
/lib/doorkeeper/rails/routes/mapper.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | module Doorkeeper
4 | module Rails
5 | class Routes # :nodoc:
6 | class Mapper
7 | def initialize(mapping = Mapping.new)
8 | @mapping = mapping
9 | end
10 |
11 | def map(&block)
12 | instance_eval(&block) if block
13 | @mapping
14 | end
15 |
16 | def controllers(controller_names = {})
17 | @mapping.controllers.merge!(controller_names)
18 | end
19 |
20 | def skip_controllers(*controller_names)
21 | @mapping.skips = controller_names
22 | end
23 |
24 | def as(alias_names = {})
25 | @mapping.as.merge!(alias_names)
26 | end
27 | end
28 | end
29 | end
30 | end
31 |
--------------------------------------------------------------------------------
/lib/doorkeeper/rails/routes/mapping.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | module Doorkeeper
4 | module Rails
5 | class Routes # :nodoc:
6 | class Mapping
7 | attr_accessor :controllers, :as, :skips
8 |
9 | def initialize
10 | @controllers = {
11 | authorizations: "doorkeeper/authorizations",
12 | applications: "doorkeeper/applications",
13 | authorized_applications: "doorkeeper/authorized_applications",
14 | tokens: "doorkeeper/tokens",
15 | token_info: "doorkeeper/token_info",
16 | }
17 |
18 | @as = {
19 | authorizations: :authorization,
20 | tokens: :token,
21 | token_info: :token_info,
22 | }
23 |
24 | @skips = []
25 | end
26 |
27 | def [](routes)
28 | {
29 | controllers: @controllers[routes],
30 | as: @as[routes],
31 | }
32 | end
33 |
34 | def skipped?(controller)
35 | @skips.include?(controller)
36 | end
37 | end
38 | end
39 | end
40 | end
41 |
--------------------------------------------------------------------------------
/lib/doorkeeper/rails/routes/registry.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | module Doorkeeper
4 | module Rails
5 | class Routes
6 | # Thread-safe registry of any Doorkeeper additional routes.
7 | # Used to allow implementing of Doorkeeper extensions that must
8 | # use their own routes.
9 | #
10 | module Registry
11 | ROUTES_ACCESS_LOCK = Mutex.new
12 | ROUTES_DEFINITION_LOCK = Mutex.new
13 |
14 | InvalidRouterClass = Class.new(StandardError)
15 |
16 | # Collection of additional registered routes for Doorkeeper.
17 | #
18 | # @return [Array