New upstream version 12.6.2

This commit is contained in:
Utkarsh Gupta 2020-01-04 02:51:38 +05:30
parent 0985dbd954
commit 65b755ec3a
247 changed files with 13545 additions and 0 deletions

View file

@ -0,0 +1 @@
service_name: travis-ci

25
doorkeeper/.github/ISSUE_TEMPLATE.md vendored Normal file
View file

@ -0,0 +1,25 @@
### Steps to reproduce
What we need to do to see your problem or bug?
The more detailed the issue, the more likely that we will fix it ASAP.
Don't use GitHub issues for questions like "How can I do that?" —
use [StackOverflow](https://stackoverflow.com/questions/tagged/doorkeeper)
instead with the corresponding tag.
### Expected behavior
Tell us what should happen
### Actual behavior
Tell us what happens instead
### System configuration
You can help us to understand your problem if you will share some very
useful information about your project environment (don't forget to
remove any confidential data if it exists).
**Doorkeeper initializer**:
**Ruby version**:
**Gemfile.lock**:

View file

@ -0,0 +1,17 @@
### Summary
Provide a general description of the code changes in your pull
request... were there any bugs you had fixed? If so, mention them. If
these bugs have open GitHub issues, be sure to tag them here as well,
to keep the conversation linked together.
### Other Information
If there's anything else that's important and relevant to your pull
request, mention that information here. This could include
benchmarks, or other information.
If you are updating NEWS.md file or are asked to update it by reviewers,
please add the changelog entry at the top of the file.
Thanks for contributing to Doorkeeper project!

19
doorkeeper/.gitignore vendored Normal file
View file

@ -0,0 +1,19 @@
.bundle/
.rbx
*.rbc
log/*.log
pkg/
spec/dummy/db/*.sqlite3
spec/dummy/log/*.log
spec/dummy/tmp/
spec/generators/tmp
Gemfile.lock
gemfiles/*.lock
.rvmrc
*.swp
.idea
/.yardoc/
/_yardoc/
/doc/
/rdoc/
coverage

2
doorkeeper/.hound.yml Normal file
View file

@ -0,0 +1,2 @@
ruby:
config_file: .rubocop.yml

1
doorkeeper/.rspec Normal file
View file

@ -0,0 +1 @@
--colour

13
doorkeeper/.rubocop.yml Normal file
View file

@ -0,0 +1,13 @@
AllCops:
Exclude:
- "spec/dummy/db/*"
LineLength:
Exclude:
- spec/**/*
StringLiterals:
Enabled: false
TrailingBlankLines:
Enabled: true

38
doorkeeper/.travis.yml Normal file
View file

@ -0,0 +1,38 @@
cache: bundler
language: ruby
sudo: false
rvm:
- 2.1
- 2.2
- 2.3
- 2.4
- 2.5
before_install:
- gem update --system # Need for Ruby 2.5.0. https://github.com/travis-ci/travis-ci/issues/8978
- gem install bundler -v '~> 1.10'
gemfile:
- gemfiles/rails_4_2.gemfile
- gemfiles/rails_5_0.gemfile
- gemfiles/rails_5_1.gemfile
- gemfiles/rails_5_2.gemfile
- gemfiles/rails_master.gemfile
matrix:
exclude:
- gemfile: gemfiles/rails_5_0.gemfile
rvm: 2.1
- gemfile: gemfiles/rails_5_1.gemfile
rvm: 2.1
- gemfile: gemfiles/rails_5_2.gemfile
rvm: 2.1
- gemfile: gemfiles/rails_master.gemfile
rvm: 2.1
- gemfile: gemfiles/rails_master.gemfile
rvm: 2.2
- gemfile: gemfiles/rails_master.gemfile
rvm: 2.3
allow_failures:
- gemfile: gemfiles/rails_master.gemfile

18
doorkeeper/Appraisals Normal file
View file

@ -0,0 +1,18 @@
appraise "rails-4-2" do
gem "rails", "~> 4.2.0"
end
appraise "rails-5-0" do
gem "rails", "~> 5.0.0"
gem "rspec-rails", "~> 3.5"
end
appraise "rails-5-1" do
gem "rails", "~> 5.1.0"
gem "rspec-rails", "~> 3.5"
end
appraise "rails-master" do
gem "rails", git: 'https://github.com/rails/rails'
gem "arel", git: 'https://github.com/rails/arel'
end

View file

@ -0,0 +1,46 @@
# Contributor Covenant Code of Conduct
## Our Pledge
In the interest of fostering an open and welcoming environment, we as contributors and maintainers pledge to making participation in our project and our community a harassment-free experience for everyone, regardless of age, body size, disability, ethnicity, gender identity and expression, level of experience, nationality, personal appearance, race, religion, or sexual identity and orientation.
## Our Standards
Examples of behavior that contributes to creating a positive environment include:
* Using welcoming and inclusive language
* Being respectful of differing viewpoints and experiences
* Gracefully accepting constructive criticism
* Focusing on what is best for the community
* Showing empathy towards other community members
Examples of unacceptable behavior by participants include:
* The use of sexualized language or imagery and unwelcome sexual attention or advances
* Trolling, insulting/derogatory comments, and personal or political attacks
* Public or private harassment
* Publishing others' private information, such as a physical or electronic address, without explicit permission
* Other conduct which could reasonably be considered inappropriate in a professional setting
## Our Responsibilities
Project maintainers are responsible for clarifying the standards of acceptable behavior and are expected to take appropriate and fair corrective action in response to any instances of unacceptable behavior.
Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, or to ban temporarily or permanently any contributor for other behaviors that they deem inappropriate, threatening, offensive, or harmful.
## Scope
This Code of Conduct applies both within project spaces and in public spaces when an individual is representing the project or its community. Examples of representing a project or community include using an official project e-mail address, posting via an official social media account, or acting as an appointed representative at an online or offline event. Representation of a project may be further defined and clarified by project maintainers.
## Enforcement
Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by contacting the project team members or current maintainer email, specified in gemspec. The project team will review and investigate all complaints, and will respond in a way that it deems appropriate to the circumstances. The project team is obligated to maintain confidentiality with regard to the reporter of an incident. Further details of specific enforcement policies may be posted separately.
Project maintainers who do not follow or enforce the Code of Conduct in good faith may face temporary or permanent repercussions as determined by other members of the project's leadership.
## Attribution
This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, available at [http://contributor-covenant.org/version/1/4][version]
[homepage]: http://contributor-covenant.org
[version]: http://contributor-covenant.org/version/1/4/

View file

@ -0,0 +1,47 @@
# Contributing
We love pull requests from everyone. By participating in this project, you agree
to abide by the thoughtbot [code of conduct].
[code of conduct]: https://thoughtbot.com/open-source-code-of-conduct
Fork, then clone the repo:
git clone git@github.com:your-username/doorkeeper.git
Set up Ruby dependencies via Bundler
bundle install
Make sure the tests pass:
rake
Make your change.
Write tests.
Follow our [style guide][style].
Make the tests pass:
[style]: https://github.com/thoughtbot/guides/tree/master/style
rake
Add notes on your change to the `NEWS.md` file.
Write a [good commit message][commit].
Push to your fork.
[Submit a pull request][pr].
[commit]: http://tbaggery.com/2008/04/19/a-note-about-git-commit-messages.html
[pr]: https://github.com/doorkeeper-gem/doorkeeper/compare/
If [Hound] catches style violations,
fix them.
[hound]: https://houndci.com
Wait for us.
We try to at least comment on pull requests within one business day.
We may suggest changes.
Thank you for your contribution!

10
doorkeeper/Gemfile Normal file
View file

@ -0,0 +1,10 @@
source "https://rubygems.org"
gem "rails", "~> 5.1"
gem "appraisal"
gem "activerecord-jdbcsqlite3-adapter", platform: :jruby
gem "sqlite3", platform: [:ruby, :mswin, :mingw, :x64_mingw]
gem 'tzinfo-data', platforms: [:mingw, :mswin, :x64_mingw]
gemspec

20
doorkeeper/MIT-LICENSE Normal file
View file

@ -0,0 +1,20 @@
Copyright 2011 Applicake. http://applicake.com
Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the
"Software"), to deal in the Software without restriction, including
without limitation the rights to use, copy, modify, merge, publish,
distribute, sublicense, and/or sell copies of the Software, and to
permit persons to whom the Software is furnished to do so, subject to
the following conditions:
The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

647
doorkeeper/NEWS.md Normal file
View file

@ -0,0 +1,647 @@
# News
User-visible changes worth mentioning.
## master
## 4.3.2
- [#1053] Support authorizing with query params in the request `redirect_uri` if explicitly present in app's `Application#redirect_uri`
## 4.3.1
- Remove `BaseRecord` and introduce additional concern for ordering methods to fix
braking changes for Doorkeeper models.
- [#1032] Refactor BaseRequest callbacks into configurable lambdas
- [#1040] Clear mixins from ActiveRecord DSL and save only overridable API. It
allows to use this mixins in Doorkeeper ORM extensions with minimum code boilerplate.
## 4.3.0
- [#976] Fix to invalidate the second redirect URI when the first URI is the native URI
- [#1035] Allow `Application#redirect_uri=` to handle array of URIs.
- [#1036] Allow to forbid Application redirect URI's with specific rules.
- [#1029] Deprecate `order_method` and introduce `ordered_by`. Sort applications
by `created_at` in index action.
- [#1033] Allow Doorkeeper configuration option #force_ssl_in_redirect_uri to be a callable object.
- Fix Grape integration & add specs for it
- [#913] Deferred ORM (ActiveRecord) models loading
- [#943] Fix Access Token token generation when certain errors occur in custom token generators
- [#1026] Implement RFC7662 - OAuth 2.0 Token Introspection
- [#985] Generate valid migration files for Rails >= 5
- [#972] Replace Struct subclassing with block-form initialization
- [#1003] Use URL query param to pass through native redirect auth code so automated apps can find it.
- [#868] `Scopes#&` and `Scopes#+` now take an array or any other enumerable
object.
- [#1019] Remove translation not in use: `invalid_resource_owner`.
- Use Ruby 2 hash style syntax (min required Ruby version = 2.1)
- [#948] Make Scopes.<=> work with any "other" value.
- [#974] Redirect URI is checked without query params within AuthorizationCodeRequest.
- [#1004] More explicit help text for `native_redirect_uri`.
- [#1023] Update Ruby versions and test against 2.5.0 on Travis CI.
- [#1024] Migrate from FactoryGirl to FactoryBot.
- [#1025] Improve documentation for adding foreign keys
- [#1028] Make it possible to have composite strategy names.
## 4.2.6
- [#970] Escape certain attributes in authorization forms.
## 4.2.5
- [#936] Deprecate `Doorkeeper#configured?`, `Doorkeeper#database_installed?`, and
`Doorkeeper#installed?`
- [#909] Add `InvalidTokenResponse#reason` reader method to allow read the kind
of invalid token error.
- [#928] Test against more recent Ruby versions
- Small refactorings within the codebase
- [#921] Switch to Appraisal, and test against Rails master
- [#892] Add minimum Ruby version requirement
## 4.2.0
- Security fix: Address CVE-2016-6582, implement token revocation according to
spec (tokens might not be revoked if client follows the spec).
- [#873] Add hooks to Doorkeeper::ApplicationMetalController
- [#871] Allow downstream users to better utilize doorkeeper spec factories by
eliminating name conflict on `:user` factory.
## 4.1.0
- [#845] Allow customising the `Doorkeeper::ApplicationController` base
controller
## 4.0.0
- [#834] Fix AssetNotPrecompiled error with Sprockets 4
- [#843] Revert "Fix validation error messages"
- [#847] Specify Null option to timestamps
## 4.0.0.rc4
- [#777] Add support for public client in password grant flow
- [#823] Make configuration and specs ORM independent
- [#745] Add created_at timestamp to token generation options
- [#838] Drop `Application#scopes` generator and warning, introduced for
upgrading doorkeeper from v2 to v3.
- [#801] Fix Rails 5 warning messages
- Test against Rails 5 RC1
## 4.0.0.rc3
- [#769] Revoke refresh token on access token use. To make use of the new config
add `previous_refresh_token` column to `oauth_access_tokens`:
```
rails generate doorkeeper:previous_refresh_token
```
- [#811] Toughen parameters filter with exact match
- [#813] Applications admin bugfix
- [#799] Fix Ruby Warnings
- Drop `attr_accessible` from models
### Backward incompatible changes
- [#730] Force all timezones to use UTC to prevent comparison issues.
- [#802] Remove `config.i18n.fallbacks` from engine
## 4.0.0.rc2
- Fix optional belongs_to for Rails 5
- Fix Ruby warnings
## 4.0.0.rc1
### Backward incompatible changes
- Drops support for Rails 4.1 and earlier
- Drops support for Ruby 2.0
- [#778] Bug fix: use the remaining time that a token is still valid when
building the redirect URI for the implicit grant flow
### Other changes
- [#771] Validation error messages fixes
- Adds foreign key constraints in generated migrations between tokens and
grants, and applications
- Support Rails 5
## 3.1.0
- [#736] Existing valid tokens are now reused in client_credentials flow
- [#749] Allow user to raise authorization error with custom messages.
Under `resource_owner_authenticator` block a user can
`raise Doorkeeper::Errors::DoorkeeperError.new('custom_message')`
- [#762] Check doesnt abort the actual migration, so it runs
- [#722] `doorkeeper_forbidden_render_options` now supports returning a 404 by
specifying `respond_not_found_when_forbidden: true` in the
`doorkeeper_forbidden_render_options` method.
- [#734] Simplify and remove duplication in request strategy classes
## 3.0.1
- [#712] Wrap exchange of grant token for access token and access token refresh
in transactions
- [#704] Allow applications scopes to be mass assigned
- [#707] Fixed order of Mixin inclusion and table_name configuration in models
- [#712] Wrap access token and refresh grants in transactions
- Adds JRuby support
- Specs, views and documentation adjustments
## 3.0.0
### Other changes
- [#693] Updates `en.yml`.
## 3.0.0 (rc2)
### Backward incompatible changes
- [#678] Change application-specific scopes to take precedence over server-wide
scopes. This removes the previous behavior where the intersection between
application and server scopes was used.
### Other changes
- [#671] Fixes `NoMethodError - undefined method 'getlocal'` when calling
the /oauth/token path. Switch from using a DateTime object to update
AR to using a Time object. (Issue #668)
- [#677] Support editing application-specific scopes via the standard forms
- [#682] Pass error hash to Grape `error!`
- [#683] Generate application secret/UID if fields are blank strings
## 3.0.0 (rc1)
### Backward incompatible changes
- [#648] Extracts mongodb ORMs to
https://github.com/doorkeeper-gem/doorkeeper-mongodb. If you use ActiveRecord
you dont need to do any change, otherwise you will need to install the new
plugin.
- [#665] `doorkeeper_unauthorized_render_options(error:)` and
`doorkeeper_forbidden_render_options(error:)` now accept `error` keyword
argument.
### Removed deprecations
- Removes `doorkeeper_for` deprecation notice.
- Remove `applications.scopes` upgrade notice.
## 2.2.2
- [#541] Fixed `undefined method attr_accessible` problem on Rails 4
(happens only when ProtectedAttributes gem is used) in #599
## 2.2.1
- [#636] `custom_access_token_expires_in` bugfixes
- [#641] syntax error fix (Issue #612)
- [#633] Send extra details to Custom Token Generator
- [#628] Refactor: improve orm adapters to ease extension
- [#637] Upgrade to rspec to 3.2
## 2.2.0 - 2015-04-19
- [#611] Allow custom access token generators to be used
- [#632] Properly fallback to `default_scopes` when no scope is specified
- [#622] Clarify that there is a logical OR between scopes for authorizing
- [#635] Upgrade to rspec 3
- [#627] i18n fallbacks to english
- Moved CHANGELOG to NEWS.md
## 2.1.4 - 2015-03-27
- [#595] HTTP spec: Add `scope` for refresh token scope param
- [#596] Limit scopes in app scopes for client credentials
- [#567] Add Grape helpers for easier integration with Grape framework
- [#606] Add custom access token expiration support for Client Credentials flow
## 2.1.3 - 2015-03-01
- [#588] Fixes scopes_match? bug that skipped authorization form in some cases
## 2.1.2 - 2015-02-25
- [#574] Remove unused update authorization route.
- [#576] Filter out sensitive parameters from logs.
- [#582] The Authorization HTTP header fields are now case insensitive.
- [#583] Database connection bugfix in certain scenarios.
- Testing improvements
## 2.1.1 - 2015-02-06
- Remove `wildcard_redirect_url` option
- [#481] Customize token flow OAuth expirations with a config lambda
- [#568] TokensController: Memoize strategy.authorize_response result to enable
subclasses to use the response object.
- [#571] Fix database initialization issues in some configurations.
- Documentation improvements
## 2.1.0 - 2015-01-13
- [#540] Include `created_at` in response.
- [#538] Check application-level scopes in client_credentials and password flow.
- [5596227] Check application scopes in AccessToken when present. Fixes a bug in
doorkeeper 2.0.0 and 2.0.1 referring to application specific scopes.
- [#534] Internationalizes doorkeeper views.
- [#545] Ensure there is a connection to the database before checking for
missing columns
- [#546] Use `Doorkeeper::` prefix when referencing `Application` to avoid
possible application model name conflict.
- [#538] Test with Rails ~> 4.2.
### Potentially backward incompatible changes
- Enable by default `authorization_code` and `client_credentials` grant flows.
Disables implicit and password grant flows by default.
- [#510, #544, 722113f] Revoked refresh token response bugfix.
## 2.0.1 - 2014-12-17
- [#525, #526, #527] Fix `ActiveRecord::NoDatabaseError` on gem load.
## 2.0.0 - 2014-12-16
### Backward incompatible changes
- [#448] Removes `doorkeeper_for` helper. Now we use
`before_action :doorkeeper_authorize!`.
- [#469] Allow client applications to restrict the set of allowable scopes.
Fixes #317. `oauth_applications` relation needs a new `scopes` string column,
non nullable, which defaults to an empty string. To add the column run:
```
rails generate doorkeeper:application_scopes
```
If youd rather do it by hand, your ActiveRecord migration should contain:
```ruby
add_column :oauth_applications, :scopes, :string, null: false, default:
```
### Removed deprecations
- Removes `test_redirect_uri` option. It is now called `native_redirect_uri`.
- [#446] Removes `mount Doorkeeper::Engine`. Now we use `use_doorkeeper`.
### Others
- [#484] Performance improvement - avoid performing order_by when not required.
- [#450] When password is invalid in Password Credentials Grant, Doorkeeper
returned 'invalid_resource_owner' instead of 'invalid_grant', as the spec
declares. Fixes #444.
- [#452] Allows `revoked_at` to be set in the future, for future expiry.
Rationale: https://github.com/doorkeeper-gem/doorkeeper/pull/452#issuecomment-51431459
- [#480] For Implicit grant flow, access tokens can now be reused. Fixes #421.
- [#491] Reworks of @jasl's #454 and #478. ORM refactor that allows doorkeeper
to be extended more easily with unsupported ORMs. It also marks the boundaries
between shared model code and ORM specifics inside of the gem.
- [#496] Tests with Rails 4.2.
- [#489] Adds `force_ssl_in_redirect_uri` to force the usage of the HTTPS
protocol in non-native redirect uris.
- [#516] SECURITY: Adds `protect_from_forgery` to `Doorkeeper::ApplicationController`
- [#518] Fix random failures in mongodb.
---
## 1.4.2 - 2015-03-02
- [#576] Filter out sensitive parameters from logs
## 1.4.1 - 2014-12-17
- [#516] SECURITY: Adds `protect_from_forgery` to `Doorkeeper::ApplicationController`
## 1.4.0 - 2014-07-31
- internals
- [#427] Adds specs expectations.
- [#428] Error response refactor.
- [#417] Moves token validation into Access Token class.
- [#439] Removes redundant module includes.
- [#443] TokensController and TokenInfoController inherit from ActionController::Metal
- bug
- [#418] fixes #243, requests with insufficient scope now respond 403 instead
of 401. (API change)
- [#438] fixes #398, native redirect for implicit token grant bug.
- [#440] namespace fixes
- enhancements
- [#432] Keeps query parameters
## 1.3.1 - 2014-07-06
- enhancements
- [#405] Adds facade to more easily get the token from a request in a route
constraint.
- [#415] Extend Doorkeeper TokenResponse with an `after_successful_response`
callback that allows handling of `response` object.
- internals
- [#409] Deprecates `test_redirect_uri` in favor of `native_redirect_uri`.
See discussion in: [#351].
- [#411] Clean rspec deprecations. General test improvements.
- [#412] rspec line width can go longer than 80 (hound CI config).
- bug
- [#413] fixes #340, routing scope is now taken into account in redirect.
- [#401] and [#425] application is not required any longer for access_token.
## 1.3.0 - 2014-05-23
- enhancements
- [#387] Adds reuse_access_token configuration option.
## 1.2.0 - 2014-05-02
- enhancements
- [#376] Allow users to enable basic header authorization for access tokens.
- [#374] Token revocation implementation [RFC 7009]
- [#295] Only enable specific grant flows.
- internals
- [#381] Locale source fix.
- [#380] Renames `errors_for` to `doorkeeper_errors_for`.
- [#390] Style adjustments in accordance with Ruby Style Guide form
Thoughtbot.
## 1.1.0 - 2014-03-29
- enhancements
- [#336] mongoid4 support.
- [#372] Allow users to set ActiveRecord table_name_prefix/suffix options
- internals
- [#343] separate OAuth's admin and user end-point to different layouts, upgrade theme to Bootstrap 3.1.
- [#348] Move render_options in filter after `@error` has been set
## 1.0.0 - 2014-01-13
- bug (spec)
- [#228] token response `expires_in` value is now in seconds, relative to
request time
- [#296] client is optional for password grant type.
- [#319] If client credentials are present on password grant type they are validated
- [#326] If client credentials are present in refresh token they are validated
- [#326] If authenticated client does not match original client that
obtained a refresh token it responds `invalid_grant` instead of
`invalid_client`. Previous usage was invalid according to Section 5.2 of
the spec.
- [#329] access tokens' `scopes` string wa being compared against
`default_scopes` symbols, always unauthorizing.
- [#318] Include "WWW-Authenticate" header with Unauthorized responses
- enhancements
- [#293] Adds ActionController::Instrumentation in TokensController
- [#298] Support for multiple redirect_uris added.
- [#313] `AccessToken.revoke_all_for` actually revokes all non-revoked
tokens for an application/owner instead of deleting them.
- [#333] Rails 4.1 support
- internals
- Removes jQuery dependency [fixes #300] [PR #312 is related]
- [#294] Client uid and secret will be generated only if not present.
- [#316] Test warnings addressed.
- [#338] Rspec 3 syntax.
---
## 0.7.4 - 2013-12-01
- bug
- Symbols instead of strings for user input.
## 0.7.3 - 2013-10-04
- enhancements
- [#204] Allow to overwrite scope in routes
- internals
- Returns only present keys in Token Response (may imply a backwards
incompatible change). https://github.com/doorkeeper-gem/doorkeeper/issues/220
- bug
- [#290] Support for Rails 4 when 'protected_attributes' gem is present.
## 0.7.2 - 2013-09-11
- enhancements
- [#272] Allow issuing multiple access_tokens for one user/application for multiple devices
- [#170] Increase length of allowed redirect URIs
- [#239] Do not try to load unavailable Request class for the current phase.
- [#273] Relax jquery-rails gem dependency
## 0.7.1 - 2013-08-30
- bug
- [#269] Rails 3.2 raised `ActiveModel::MassAssignmentSecurity::Error`.
## 0.7.0 - 2013-08-21
- enhancements
- [#229] Rails 4!
- internals
- [#203] Changing table name to be specific in column_names_with_table
- [#215] README update
- [#227] Use Rails.config.paths["config/routes"] instead of assuming "config/routes.rb" exists
- [#262] Add jquery as gem dependency
- [#263] Add a configuration for ActiveRecord.establish_connection
- Deprecation and Ruby warnings (PRs merged outside of GitHub).
## 0.6.7 - 2013-01-13
- internals
- [#188] Add IDs to the show views for integration testing [@egtann](https://github.com/egtann)
## 0.6.6 - 2013-01-04
- enhancements
- [#187] Raise error if configuration is not set
## 0.6.5 - 2012-12-26
- enhancements
- [#184] Vendor the Bootstrap CSS [@tylerhunt](https://github.com/tylerhunt)
## 0.6.4 - 2012-12-15
- bug
- [#180] Add localization to authorized_applications destroy notice [@aalvarado](https://github.com/aalvarado)
## 0.6.3 - 2012-12-07
- bugfixes
- [#163] Error response content-type header should be application/json [@ggayan](https://github.com/ggayan)
- [#175] Make token.expires_in_seconds return nil when expires_in is nil [@miyagawa](https://github.com/miyagawa)
- enhancements
- [#166, #172, #174] Behavior to automatically authorize based on a configured proc
- internals
- [#168] Using expectation syntax for controller specs [@rdsoze](https://github.com/rdsoze)
## 0.6.2 - 2012-11-10
- bugfixes
- [#162] Remove ownership columns from base migration template [@rdsoze](https://github.com/rdsoze)
## 0.6.1 - 2012-11-07
- bugfixes
- [#160] Removed |routes| argument from initializer authenticator blocks
- documentation
- [#160] Fixed description of context of authenticator blocks
## 0.6.0 - 2012-11-05
- enhancements
- Mongoid `orm` configuration accepts only :mongoid2 or :mongoid3
- Authorization endpoint does not redirect in #new action anymore. It wasn't specified by OAuth spec
- TokensController now inherits from ActionController::Metal. There might be performance upgrades
- Add link to authorization in Applications scaffold
- [#116] MongoMapper support [@carols10cents](https://github.com/carols10cents)
- [#122] Mongoid3 support [@petergoldstein](https://github.com/petergoldstein)
- [#150] Introduce test redirect uri for applications
- bugfixes
- [#157] Response token status should be `:ok`, not `:success` [@theycallmeswift](https://github.com/theycallmeswift)
- [#159] Remove ActionView::Base.field_error_proc override (fixes #145)
- internals
- Update development dependencies
- Several refactorings
- Rails/ORM are easily swichable with env vars (rails and orm)
- Travis now tests against Mongoid v2
## 0.5.0 - 2012-10-20
Official support for rubinius was removed.
- enhancements
- Configure the way access token is retrieved from request (default to bearer header)
- Authorization Code expiration time is now configurable
- Add support for mongoid
- [#78, #128, #137, #138] Application Ownership
- [#92] Allow users to skip controllers
- [#99] Remove deprecated warnings for data-* attributes [@towerhe](https://github.com/towerhe)
- [#101] Return existing access_token for PasswordAccessTokenRequest [@benoist](https://github.com/benoist)
- [#104] Changed access token scopes example code to default_scopes and optional_scopes [@amkirwan](https://github.com/amkirwan)
- [#107] Fix typos in initializer
- [#123] i18n for validator, flash messages [@petergoldstein](https://github.com/petergoldstein)
- [#140] ActiveRecord is the default value for the ORM [@petergoldstein](https://github.com/petergoldstein)
- internals
- [#112, #120] Replacing update_attribute with update_column to eliminate deprecation warnings [@rmoriz](https://github.com/rmoriz), [@petergoldstein](https://github.com/petergoldstein)
- [#121] Updating all development dependencies to recent versions. [@petergoldstein](https://github.com/petergoldstein)
- [#144] Adding MongoDB dependency to .travis.yml [@petergoldstein](https://github.com/petergoldstein)
- [#143] Displays errors for unconfigured error messages [@timgaleckas](https://github.com/timgaleckas)
- bugfixes
- [#102] Not returning 401 when access token generation fails [@cslew](https://github.com/cslew)
- [#125] Doorkeeper is using ActiveRecord version of as_json in ORM agnostic code [@petergoldstein](https://github.com/petergoldstein)
- [#142] Prevent double submission of password based authentication [@bdurand](https://github.com/bdurand)
- documentation
- [#141] Add rack-cors middleware to readme [@gottfrois](https://github.com/gottfrois)
## 0.4.2 - 2012-06-05
- bugfixes:
- [#94] Uninitialized Constant in Password Flow
## 0.4.1 - 2012-06-02
- enhancements:
- Backport: Move doorkeeper_for extension to Filter helper
## 0.4.0 - 2012-05-26
- deprecation
- Deprecate authorization_scopes
- database changes
- AccessToken#resource_owner_id is not nullable
- enhancements
- [#83] Add Resource Owner Password Credentials flow [@jaimeiniesta](https://github.com/jaimeiniesta)
- [#76] Allow token expiration to be disabled [@mattgreen](https://github.com/mattgreen)
- [#89] Configure the way client credentials are retrieved from request
- [#b6470a] Add Client Credentials flow
- internals
- [#2ece8d, #f93778] Introduce Client and ErrorResponse classes
## 0.3.4 - 2012-05-24
- Fix attr_accessible for rails 3.2.x
## 0.3.3 - 2012-05-07
- [#86] shrink gem package size
## 0.3.2 - 2012-04-29
- enhancements
- [#54] Ignore Authorization: headers that are not Bearer [@miyagawa](https://github.com/miyagawa)
- [#58, #64] Add destroy action to applications endpoint [@jaimeiniesta](https://github.com/jaimeiniesta), [@davidfrey](https://github.com/davidfrey)
- [#63] TokensController responds with `401 unauthorized` [@jaimeiniesta](https://github.com/jaimeiniesta)
- [#67, #72] Fix for mass-assignment [@cicloid](https://github.com/cicloid)
- internals
- [#49] Add Gemnasium status image to README [@laserlemon](https://github.com/laserlemon)
- [#50] Fix typos [@tomekw](https://github.com/tomekw)
- [#51] Updated the factory_girl_rails dependency, fix expires_in response which returned a float number instead of integer [@antekpiechnik](https://github.com/antekpiechnik)
- [#62] Typos, .gitignore [@jaimeiniesta](https://github.com/jaimeiniesta)
- [#65] Change _path redirections to _url redirections [@jaimeiniesta](https://github.com/jaimeiniesta)
- [#75] Fix unknown method #authenticate_admin! [@mattgreen](https://github.com/mattgreen)
- Remove application link in authorized app view
## 0.3.1 - 2012-02-17
- enhancements
- [#48] Add if, else options to doorkeeper_for
- Add views generator
- internals
- Namespace models
## 0.3.0 - 2012-02-11
- enhancements
- [#17, #31] Add support for client credentials in basic auth header [@GoldsteinTechPartners](https://github.com/GoldsteinTechPartners)
- [#28] Add indices to migration [@GoldsteinTechPartners](https://github.com/GoldsteinTechPartners)
- [#29] Allow doorkeeper to run with rails 3.2 [@john-griffin](https://github.com/john-griffin)
- [#30] Improve client's redirect uri validation [@GoldsteinTechPartners](https://github.com/GoldsteinTechPartners)
- [#32] Add token (implicit grant) flow [@GoldsteinTechPartners](https://github.com/GoldsteinTechPartners)
- [#34] Add support for custom unathorized responses [@GoldsteinTechPartners](https://github.com/GoldsteinTechPartners)
- [#36] Remove repetitions from the Authorised Applications view [@carvil](https://github.com/carvil)
- When user revoke an application, all tokens for that application are revoked
- Error messages now can be translated
- Install generator copies the error messages localization file
- internals
- Fix deprecation warnings in ActiveSupport::Base64
- Remove deprecation in doorkeeper_for that handles hash arguments
- Depends on railties instead of whole rails framework
- CI now integrates with rails 3.1 and 3.2
## 0.2.0 - 2011-12-17
- enhancements
- [#4] Add authorized applications endpoint
- [#5, #11] Add access token scopes
- [#10] Add access token expiration by default
- [#9, #12] Add refresh token flow
- internals
- [#7] Improve configuration options with :default
- Improve configuration options with :builder
- Refactor config class
- Improve coverage of authorization request integration
- bug fixes
- [#6, #20] Fix access token response headers
- Fix issue with state parameter
- deprecation
- deprecate :only and :except options in doorkeeper_for
## 0.1.1 - 2011-11-30
- enhancements
- [#3] Authorization code must be short lived and single use
- [#2] Improve views provided by doorkeeper
- [#1] Skips authorization form if the client has been authorized by the resource owner
- Improve readme
- bugfixes
- Fix issue when creating the access token (wrong client id)
## 0.1.0 - 2011-11-25
- Authorization Code flow
- OAuth applications endpoint

487
doorkeeper/README.md Normal file
View file

@ -0,0 +1,487 @@
# Doorkeeper - awesome OAuth 2 provider for your Rails app.
[![Gem Version](https://badge.fury.io/rb/doorkeeper.svg)](https://rubygems.org/gems/doorkeeper)
[![Build Status](https://travis-ci.org/doorkeeper-gem/doorkeeper.svg?branch=master)](https://travis-ci.org/doorkeeper-gem/doorkeeper)
[![Dependency Status](https://gemnasium.com/doorkeeper-gem/doorkeeper.svg?travis)](https://gemnasium.com/doorkeeper-gem/doorkeeper)
[![Code Climate](https://codeclimate.com/github/doorkeeper-gem/doorkeeper.svg)](https://codeclimate.com/github/doorkeeper-gem/doorkeeper)
[![Coverage Status](https://coveralls.io/repos/github/doorkeeper-gem/doorkeeper/badge.svg?branch=master)](https://coveralls.io/github/doorkeeper-gem/doorkeeper?branch=master)
[![Security](https://hakiri.io/github/doorkeeper-gem/doorkeeper/master.svg)](https://hakiri.io/github/doorkeeper-gem/doorkeeper/master)
Doorkeeper is a gem that makes it easy to introduce OAuth 2 provider
functionality to your Rails or Grape application.
Supported features:
- [The OAuth 2.0 Authorization Framework](https://tools.ietf.org/html/rfc6749)
- [Authorization Code Flow](http://tools.ietf.org/html/draft-ietf-oauth-v2-22#section-4.1)
- [Access Token Scopes](http://tools.ietf.org/html/draft-ietf-oauth-v2-22#section-3.3)
- [Refresh token](http://tools.ietf.org/html/draft-ietf-oauth-v2-22#section-1.5)
- [Implicit grant](http://tools.ietf.org/html/draft-ietf-oauth-v2-22#section-4.2)
- [Resource Owner Password Credentials](http://tools.ietf.org/html/draft-ietf-oauth-v2-22#section-4.3)
- [Client Credentials](http://tools.ietf.org/html/draft-ietf-oauth-v2-22#section-4.4)
- [OAuth 2.0 Token Revocation](http://tools.ietf.org/html/rfc7009)
- [OAuth 2.0 Token Introspection](https://tools.ietf.org/html/rfc7662)
## Documentation valid for `master` branch
Please check the documentation for the version of doorkeeper you are using in:
https://github.com/doorkeeper-gem/doorkeeper/releases
- See the [wiki](https://github.com/doorkeeper-gem/doorkeeper/wiki)
- For general questions, please post in [Stack Overflow](http://stackoverflow.com/questions/tagged/doorkeeper)
- See [SECURITY.md](SECURITY.md) for this project's security disclose
policy
## Table of Contents
<!-- START doctoc generated TOC please keep comment here to allow auto update -->
<!-- DON'T EDIT THIS SECTION, INSTEAD RE-RUN doctoc TO UPDATE -->
- [Installation](#installation)
- [Configuration](#configuration)
- [ORM](#orm)
- [Active Record](#active-record)
- [MongoDB](#mongodb)
- [Sequel](#sequel)
- [Couchbase](#couchbase)
- [Routes](#routes)
- [Authenticating](#authenticating)
- [Internationalization (I18n)](#internationalization-i18n)
- [Protecting resources with OAuth (a.k.a your API endpoint)](#protecting-resources-with-oauth-aka-your-api-endpoint)
- [Ruby on Rails controllers](#ruby-on-rails-controllers)
- [Grape endpoints](#grape-endpoints)
- [Route Constraints and other integrations](#route-constraints-and-other-integrations)
- [Access Token Scopes](#access-token-scopes)
- [Custom Access Token Generator](#custom-access-token-generator)
- [Authenticated resource owner](#authenticated-resource-owner)
- [Applications list](#applications-list)
- [Other customizations](#other-customizations)
- [Testing](#testing)
- [Upgrading](#upgrading)
- [Development](#development)
- [Contributing](#contributing)
- [Other resources](#other-resources)
- [Wiki](#wiki)
- [Screencast](#screencast)
- [Client applications](#client-applications)
- [Contributors](#contributors)
- [IETF Standards](#ietf-standards)
- [License](#license)
<!-- END doctoc generated TOC please keep comment here to allow auto update -->
## Installation
Put this in your Gemfile:
``` ruby
gem 'doorkeeper'
```
Run the installation generator with:
rails generate doorkeeper:install
This will install the doorkeeper initializer into `config/initializers/doorkeeper.rb`.
## Configuration
### ORM
#### Active Record
By default doorkeeper is configured to use Active Record, so to start you have
to generate the migration tables (supports Rails >= 5 migrations versioning):
rails generate doorkeeper:migration
You may want to add foreign keys to your migration. For example, if you plan on
using `User` as the resource owner, add the following line to the migration file
for each table that includes a `resource_owner_id` column:
```ruby
add_foreign_key :table_name, :users, column: :resource_owner_id
```
Then run migrations:
```sh
rake db:migrate
```
Remember to add associations to your model so the related records are deleted.
If you don't do this an `ActiveRecord::InvalidForeignKey`-error will be raised
when you try to destroy a model with related access grants or access tokens.
```ruby
class User < ApplicationRecord
has_many :access_grants, class_name: "Doorkeeper::AccessGrant",
foreign_key: :resource_owner_id,
dependent: :delete_all # or :destroy if you need callbacks
has_many :access_tokens, class_name: "Doorkeeper::AccessToken",
foreign_key: :resource_owner_id,
dependent: :delete_all # or :destroy if you need callbacks
end
```
#### MongoDB
See [doorkeeper-mongodb project] for Mongoid and MongoMapper support. Follow along
the implementation in that repository to extend doorkeeper with other ORMs.
[doorkeeper-mongodb project]: https://github.com/doorkeeper-gem/doorkeeper-mongodb
#### Sequel
If you are using [Sequel gem] then you can add [doorkeeper-sequel extension] to your project.
Follow configuration instructions for setting up the necessary Doorkeeper ORM.
[Sequel gem]: https://github.com/jeremyevans/sequel/
[doorkeeper-sequel extension]: https://github.com/nbulaj/doorkeeper-sequel
#### Couchbase
Use [doorkeeper-couchbase] extension if you are using Couchbase database.
[doorkeeper-couchbase]: https://github.com/acaprojects/doorkeeper-couchbase
### Routes
The installation script will also automatically add the Doorkeeper routes into
your app, like this:
``` ruby
Rails.application.routes.draw do
use_doorkeeper
# your routes
end
```
This will mount following routes:
GET /oauth/authorize/native?code
GET /oauth/authorize
POST /oauth/authorize
DELETE /oauth/authorize
POST /oauth/token
POST /oauth/revoke
POST /oauth/introspect
resources /oauth/applications
GET /oauth/authorized_applications
DELETE /oauth/authorized_applications/:id
GET /oauth/token/info
For more information on how to customize routes, check out [this page on the
wiki](https://github.com/doorkeeper-gem/doorkeeper/wiki/Customizing-routes).
### Authenticating
You need to configure Doorkeeper in order to provide `resource_owner` model
and authentication block in `config/initializers/doorkeeper.rb`:
``` ruby
Doorkeeper.configure do
resource_owner_authenticator do
User.find_by(id: session[:current_user_id]) || redirect_to(login_url)
end
end
```
This code is run in the context of your application so you have access to your
models, session or routes helpers. However, since this code is not run in the
context of your application's `ApplicationController` it doesn't have access to
the methods defined over there.
You may want to check other ways of authentication
[here](https://github.com/doorkeeper-gem/doorkeeper/wiki/Authenticating-using-Clearance-or-DIY).
### Internationalization (I18n)
See language files in [the I18n repository](https://github.com/doorkeeper-gem/doorkeeper-i18n).
## Protecting resources with OAuth (a.k.a your API endpoint)
### Ruby on Rails controllers
To protect your controllers (usual one or `ActionController::API`) with OAuth,
you just need to setup `before_action`s specifying the actions you want to
protect. For example:
``` ruby
class Api::V1::ProductsController < Api::V1::ApiController
before_action :doorkeeper_authorize! # Require access token for all actions
# your actions
end
```
You can pass any option `before_action` accepts, such as `if`, `only`,
`except`, and others.
### Grape endpoints
Starting from version 2.2 Doorkeeper provides helpers for the
[Grape framework] >= 0.10. One of them is `doorkeeper_authorize!` that
can be used in a similar way as an example above to protect your API
with OAuth. Note that you have to use `require 'doorkeeper/grape/helpers'`
and `helpers Doorkeeper::Grape::Helpers` in your Grape API class.
For more information about integration with Grape see the [Wiki].
[Grape framework]: https://github.com/ruby-grape/grape
[Wiki]: https://github.com/doorkeeper-gem/doorkeeper/wiki/Grape-Integration
``` ruby
require 'doorkeeper/grape/helpers'
module API
module V1
class Users < Grape::API
helpers Doorkeeper::Grape::Helpers
before do
doorkeeper_authorize!
end
# route_setting :scopes, ['user:email'] - for old versions of Grape
get :emails, scopes: [:user, :write] do
[{'email' => current_user.email}]
end
# ...
end
end
end
```
### Route Constraints and other integrations
You can leverage the `Doorkeeper.authenticate` facade to easily extract a
`Doorkeeper::OAuth::Token` based on the current request. You can then ensure
that token is still good, find its associated `#resource_owner_id`, etc.
```ruby
module Constraint
class Authenticated
def matches?(request)
token = Doorkeeper.authenticate(request)
token && token.accessible?
end
end
end
```
For more information about integration and other integrations, check out [the
related wiki
page](https://github.com/doorkeeper-gem/doorkeeper/wiki/ActionController::Metal-with-doorkeeper).
### Access Token Scopes
You can also require the access token to have specific scopes in certain
actions:
First configure the scopes in `initializers/doorkeeper.rb`
```ruby
Doorkeeper.configure do
default_scopes :public # if no scope was requested, this will be the default
optional_scopes :admin, :write
end
```
And in your controllers:
```ruby
class Api::V1::ProductsController < Api::V1::ApiController
before_action -> { doorkeeper_authorize! :public }, only: :index
before_action only: [:create, :update, :destroy] do
doorkeeper_authorize! :admin, :write
end
end
```
Please note that there is a logical OR between multiple required scopes. In the
above example, `doorkeeper_authorize! :admin, :write` means that the access
token is required to have either `:admin` scope or `:write` scope, but does not
need have both of them.
If you want to require the access token to have multiple scopes at the same
time, use multiple `doorkeeper_authorize!`, for example:
```ruby
class Api::V1::ProductsController < Api::V1::ApiController
before_action -> { doorkeeper_authorize! :public }, only: :index
before_action only: [:create, :update, :destroy] do
doorkeeper_authorize! :admin
doorkeeper_authorize! :write
end
end
```
In the above example, a client can call `:create` action only if its access token
has both `:admin` and `:write` scopes.
### Custom Access Token Generator
By default a 128 bit access token will be generated. If you require a custom
token, such as [JWT](http://jwt.io), specify an object that responds to
`.generate(options = {})` and returns a string to be used as the token.
```ruby
Doorkeeper.configure do
access_token_generator "Doorkeeper::JWT"
end
```
JWT token support is available with
[Doorkeeper-JWT](https://github.com/chriswarren/doorkeeper-jwt).
### Custom Base Controller
By default Doorkeeper's main controller `Doorkeeper::ApplicationController`
inherits from `ActionController::Base`. You may want to use your own
controller to inherit from, to keep Doorkeeper controllers in the same
context than the rest your app:
```ruby
Doorkeeper.configure do
base_controller 'ApplicationController'
end
```
### Authenticated resource owner
If you want to return data based on the current resource owner, in other
words, the access token owner, you may want to define a method in your
controller that returns the resource owner instance:
``` ruby
class Api::V1::CredentialsController < Api::V1::ApiController
before_action :doorkeeper_authorize!
respond_to :json
# GET /me.json
def me
respond_with current_resource_owner
end
private
# Find the user that owns the access token
def current_resource_owner
User.find(doorkeeper_token.resource_owner_id) if doorkeeper_token
end
end
```
In this example, we're returning the credentials (`me.json`) of the access
token owner.
### Applications list
By default, the applications list (`/oauth/applications`) is publicly available.
To protect the endpoint you should uncomment these lines:
```ruby
# config/initializers/doorkeeper.rb
Doorkeeper.configure do
admin_authenticator do |routes|
Admin.find_by(id: session[:admin_id]) || redirect_to(routes.new_admin_session_url)
end
end
```
The logic is the same as the `resource_owner_authenticator` block. **Note:**
since the application list is just a scaffold, it's recommended to either
customize the controller used by the list or skip the controller all together.
For more information see the page
[in the wiki](https://github.com/doorkeeper-gem/doorkeeper/wiki/Customizing-routes).
## Other customizations
- [Associate users to OAuth applications (ownership)](https://github.com/doorkeeper-gem/doorkeeper/wiki/Associate-users-to-OAuth-applications-%28ownership%29)
- [CORS - Cross Origin Resource Sharing](https://github.com/doorkeeper-gem/doorkeeper/wiki/%5BCORS%5D-Cross-Origin-Resource-Sharing)
- see more on [Wiki page](https://github.com/doorkeeper-gem/doorkeeper/wiki)
## Testing
You can use Doorkeeper models in your application test suite. Note that starting from
Doorkeeper 4.3.0 it uses [ActiveSupport lazy loading hooks](http://api.rubyonrails.org/classes/ActiveSupport/LazyLoadHooks.html)
to load models. There are [known issue](https://github.com/doorkeeper-gem/doorkeeper/issues/1043)
with the `factory_bot_rails` gem (it executes factories building before `ActiveRecord::Base`
is initialized using hooks in gem railtie, so you can catch a `uninitialized constant` error).
It is recommended to use pure `factory_bot` gem to solve this problem.
## Upgrading
If you want to upgrade doorkeeper to a new version, check out the [upgrading
notes](https://github.com/doorkeeper-gem/doorkeeper/wiki/Migration-from-old-versions)
and take a look at the
[changelog](https://github.com/doorkeeper-gem/doorkeeper/blob/master/NEWS.md).
Doorkeeper follows [semantic versioning](http://semver.org/).
## Development
To run the local engine server:
```
bundle install
bundle exec rails server
````
By default, it uses the latest Rails version with ActiveRecord. To run the
tests with a specific ORM and Rails version:
```
rails=4.2.0 orm=active_record bundle exec rake
```
## Contributing
Want to contribute and don't know where to start? Check out [features we're
missing](https://github.com/doorkeeper-gem/doorkeeper/wiki/Supported-Features),
create [example
apps](https://github.com/doorkeeper-gem/doorkeeper/wiki/Example-Applications),
integrate the gem with your app and let us know!
Also, check out our [contributing guidelines
page](https://github.com/doorkeeper-gem/doorkeeper/wiki/Contributing).
## Other resources
### Wiki
You can find everything about Doorkeeper in our [wiki
here](https://github.com/doorkeeper-gem/doorkeeper/wiki).
### Screencast
Check out this screencast from [railscasts.com](http://railscasts.com/): [#353
OAuth with
Doorkeeper](http://railscasts.com/episodes/353-oauth-with-doorkeeper)
### Client applications
After you set up the provider, you may want to create a client application to
test the integration. Check out these [client
examples](https://github.com/doorkeeper-gem/doorkeeper/wiki/Example-Applications)
in our wiki or follow this [tutorial
here](https://github.com/doorkeeper-gem/doorkeeper/wiki/Testing-your-provider-with-OAuth2-gem).
### Contributors
Thanks to all our [awesome
contributors](https://github.com/doorkeeper-gem/doorkeeper/graphs/contributors)!
### IETF Standards
* [The OAuth 2.0 Authorization Framework](http://tools.ietf.org/html/rfc6749)
* [OAuth 2.0 Threat Model and Security Considerations](http://tools.ietf.org/html/rfc6819)
* [OAuth 2.0 Token Revocation](http://tools.ietf.org/html/rfc7009)
### License
MIT License. Copyright 2011 Applicake.

10
doorkeeper/RELEASING.md Normal file
View file

@ -0,0 +1,10 @@
# Releasing doorkeeper
How to release doorkeeper in five easy steps!
1. Update `lib/doorkeeper/version.rb` file accordingly.
2. Update `NEWS.md` to reflect the changes since last release.
3. Commit changes: `git commit -am 'Bump to vVERSION'`
4. Run `rake release`
5. Announce the new release, making sure to say “thank you” to the contributors
who helped shape this version!

20
doorkeeper/Rakefile Normal file
View file

@ -0,0 +1,20 @@
require 'bundler/setup'
require 'rspec/core/rake_task'
desc 'Default: run specs.'
task default: :spec
desc "Run all specs"
RSpec::Core::RakeTask.new(:spec) do |config|
config.verbose = false
end
namespace :doorkeeper do
desc "Install doorkeeper in dummy app"
task :install do
cd 'spec/dummy'
system 'bundle exec rails g doorkeeper:install --force'
end
end
Bundler::GemHelper.install_tasks

15
doorkeeper/SECURITY.md Normal file
View file

@ -0,0 +1,15 @@
# Reporting security issues in Doorkeeper
Hello! Thank you for wanting to disclose a possible security
vulnerability within the Doorkeeper gem! Please follow our disclosure
policy as outlined below:
1. Do NOT open up a GitHub issue with your report. Security reports
should be kept private until a possible fix is determined.
2. Send an email to Nikita Bulai at bulaj.nikita AT gmail.com or one of
the others Doorkeeper maintainers listed in gemspec. You should receive
a prompt response.
3. Be patient. Since Doorkeeper is in a stable maintenance phase, we want to
do as little as possible to rock the boat of the project.
Thank you very much for adhering for these policies!

View file

@ -0,0 +1,10 @@
/*
*= require doorkeeper/bootstrap.min
*
*= require_self
*= require_tree .
*/
td {
vertical-align: middle !important;
}

View file

@ -0,0 +1,64 @@
/*
*= require doorkeeper/bootstrap.min
*
*= require_self
*= require_tree .
*/
body {
background-color: #eee;
font-size: 14px;
}
#container {
background-color: #fff;
border: 1px solid #999;
border: 1px solid rgba(0, 0, 0, 0.2);
border-radius: 6px;
-webkit-box-shadow: 0 3px 9px rgba(0, 0, 0, 0.5);
box-shadow: 0 3px 20px rgba(0, 0, 0, 0.3);
margin: 2em auto;
max-width: 600px;
outline: 0;
padding: 1em;
width: 80%;
}
.page-header {
margin-top: 20px;
}
#oauth-permissions {
width: 260px;
}
.actions {
border-top: 1px solid #eee;
margin-top: 1em;
padding-top: 9px;
}
.actions > form > .btn {
margin-top: 5px;
}
.separator {
color: #eee;
padding: 0 .5em;
}
.inline_block {
display: inline-block;
}
#oauth {
margin-bottom: 1em;
}
#oauth > .btn {
width: 7em;
}
td {
vertical-align: middle !important;
}

View file

@ -0,0 +1,11 @@
module Doorkeeper
class ApplicationController <
Doorkeeper.configuration.base_controller.constantize
include Helpers::Controller
protect_from_forgery with: :exception
helper 'doorkeeper/dashboard'
end
end

View file

@ -0,0 +1,17 @@
module Doorkeeper
class ApplicationMetalController < ActionController::Metal
MODULES = [
ActionController::Instrumentation,
AbstractController::Rendering,
ActionController::Rendering,
ActionController::Renderers::All,
Helpers::Controller
].freeze
MODULES.each do |mod|
include mod
end
ActiveSupport.run_load_hooks(:doorkeeper_metal_controller, self)
end
end

View file

@ -0,0 +1,63 @@
module Doorkeeper
class ApplicationsController < Doorkeeper::ApplicationController
layout 'doorkeeper/admin'
before_action :authenticate_admin!
before_action :set_application, only: [:show, :edit, :update, :destroy]
def index
@applications = if Application.respond_to?(:ordered_by)
Application.ordered_by(:created_at)
else
ActiveSupport::Deprecation.warn <<-MSG.squish
Doorkeeper #{Doorkeeper.configuration.orm} extension must implement #ordered_by
method for it's models as it will be used by default in Doorkeeper 5.
MSG
Application.all
end
end
def show; end
def new
@application = Application.new
end
def create
@application = Application.new(application_params)
if @application.save
flash[:notice] = I18n.t(:notice, scope: [:doorkeeper, :flash, :applications, :create])
redirect_to oauth_application_url(@application)
else
render :new
end
end
def edit; end
def update
if @application.update_attributes(application_params)
flash[:notice] = I18n.t(:notice, scope: [:doorkeeper, :flash, :applications, :update])
redirect_to oauth_application_url(@application)
else
render :edit
end
end
def destroy
flash[:notice] = I18n.t(:notice, scope: [:doorkeeper, :flash, :applications, :destroy]) if @application.destroy
redirect_to oauth_applications_url
end
private
def set_application
@application = Application.find(params[:id])
end
def application_params
params.require(:doorkeeper_application).permit(:name, :redirect_uri, :scopes)
end
end
end

View file

@ -0,0 +1,57 @@
module Doorkeeper
class AuthorizationsController < Doorkeeper::ApplicationController
before_action :authenticate_resource_owner!
def new
if pre_auth.authorizable?
if skip_authorization? || matching_token?
auth = authorization.authorize
redirect_to auth.redirect_uri
else
render :new
end
else
render :error
end
end
# TODO: Handle raise invalid authorization
def create
redirect_or_render authorization.authorize
end
def destroy
redirect_or_render authorization.deny
end
private
def matching_token?
AccessToken.matching_token_for pre_auth.client,
current_resource_owner.id,
pre_auth.scopes
end
def redirect_or_render(auth)
if auth.redirectable?
redirect_to auth.redirect_uri
else
render json: auth.body, status: auth.status
end
end
def pre_auth
@pre_auth ||= OAuth::PreAuthorization.new(Doorkeeper.configuration,
server.client_via_uid,
params)
end
def authorization
@authorization ||= strategy.request
end
def strategy
@strategy ||= server.authorization_request pre_auth.response_type
end
end
end

View file

@ -0,0 +1,14 @@
module Doorkeeper
class AuthorizedApplicationsController < Doorkeeper::ApplicationController
before_action :authenticate_resource_owner!
def index
@applications = Application.authorized_for(current_resource_owner)
end
def destroy
AccessToken.revoke_all_for params[:id], current_resource_owner
redirect_to oauth_authorized_applications_url, notice: I18n.t(:notice, scope: [:doorkeeper, :flash, :authorized_applications, :destroy])
end
end
end

View file

@ -0,0 +1,13 @@
module Doorkeeper
class TokenInfoController < Doorkeeper::ApplicationMetalController
def show
if doorkeeper_token && doorkeeper_token.accessible?
render json: doorkeeper_token, status: :ok
else
error = OAuth::ErrorResponse.new(name: :invalid_request)
response.headers.merge!(error.headers)
render json: error.body, status: error.status
end
end
end
end

View file

@ -0,0 +1,93 @@
module Doorkeeper
class TokensController < Doorkeeper::ApplicationMetalController
def create
response = authorize_response
headers.merge! response.headers
self.response_body = response.body.to_json
self.status = response.status
rescue Errors::DoorkeeperError => e
handle_token_exception e
end
# OAuth 2.0 Token Revocation - http://tools.ietf.org/html/rfc7009
def revoke
# The authorization server, if applicable, first authenticates the client
# and checks its ownership of the provided token.
#
# Doorkeeper does not use the token_type_hint logic described in the
# RFC 7009 due to the refresh token implementation that is a field in
# the access token model.
if authorized?
revoke_token
end
# The authorization server responds with HTTP status code 200 if the token
# has been revoked successfully or if the client submitted an invalid
# token
render json: {}, status: 200
end
def introspect
introspection = OAuth::TokenIntrospection.new(server, token)
if introspection.authorized?
render json: introspection.to_json, status: 200
else
error = OAuth::ErrorResponse.new(name: introspection.error)
response.headers.merge!(error.headers)
render json: error.body, status: error.status
end
end
private
# OAuth 2.0 Section 2.1 defines two client types, "public" & "confidential".
# Public clients (as per RFC 7009) do not require authentication whereas
# confidential clients must be authenticated for their token revocation.
#
# Once a confidential client is authenticated, it must be authorized to
# revoke the provided access or refresh token. This ensures one client
# cannot revoke another's tokens.
#
# Doorkeeper determines the client type implicitly via the presence of the
# OAuth client associated with a given access or refresh token. Since public
# clients authenticate the resource owner via "password" or "implicit" grant
# types, they set the application_id as null (since the claim cannot be
# verified).
#
# https://tools.ietf.org/html/rfc6749#section-2.1
# https://tools.ietf.org/html/rfc7009
def authorized?
if token.present?
# Client is confidential, therefore client authentication & authorization
# is required
if token.application_id?
# We authorize client by checking token's application
server.client && server.client.application == token.application
else
# Client is public, authentication unnecessary
true
end
end
end
def revoke_token
if token.accessible?
token.revoke
end
end
def token
@token ||= AccessToken.by_token(request.POST['token']) ||
AccessToken.by_refresh_token(request.POST['token'])
end
def strategy
@strategy ||= server.token_request params[:grant_type]
end
def authorize_response
@authorize_response ||= strategy.authorize
end
end
end

View file

@ -0,0 +1,19 @@
module Doorkeeper
module DashboardHelper
def doorkeeper_errors_for(object, method)
if object.errors[method].present?
output = object.errors[method].map do |msg|
content_tag(:span, class: 'help-block') do
msg.capitalize
end
end
safe_join(output)
end
end
def doorkeeper_submit_path(application)
application.persisted? ? oauth_application_path(application) : oauth_applications_path
end
end
end

View file

@ -0,0 +1,44 @@
require 'uri'
class RedirectUriValidator < ActiveModel::EachValidator
def self.native_redirect_uri
Doorkeeper.configuration.native_redirect_uri
end
def validate_each(record, attribute, value)
if value.blank?
record.errors.add(attribute, :blank)
else
value.split.each do |val|
uri = ::URI.parse(val)
next if native_redirect_uri?(uri)
record.errors.add(attribute, :forbidden_uri) if forbidden_uri?(uri)
record.errors.add(attribute, :fragment_present) unless uri.fragment.nil?
record.errors.add(attribute, :relative_uri) if uri.scheme.nil? || uri.host.nil?
record.errors.add(attribute, :secured_uri) if invalid_ssl_uri?(uri)
end
end
rescue URI::InvalidURIError
record.errors.add(attribute, :invalid_uri)
end
private
def native_redirect_uri?(uri)
self.class.native_redirect_uri.present? && uri.to_s == self.class.native_redirect_uri.to_s
end
def forbidden_uri?(uri)
Doorkeeper.configuration.forbid_redirect_uri.call(uri)
end
def invalid_ssl_uri?(uri)
forces_ssl = Doorkeeper.configuration.force_ssl_in_redirect_uri
if forces_ssl.respond_to?(:call)
forces_ssl.call(uri)
else
forces_ssl && uri.try(:scheme) == 'http'
end
end
end

View file

@ -0,0 +1,4 @@
<%- submit_btn_css ||= 'btn btn-link' %>
<%= form_tag oauth_application_path(application), method: :delete do %>
<%= submit_tag t('doorkeeper.applications.buttons.destroy'), onclick: "return confirm('#{ t('doorkeeper.applications.confirmations.destroy') }')", class: submit_btn_css %>
<% end %>

View file

@ -0,0 +1,47 @@
<%= form_for application, url: doorkeeper_submit_path(application), html: {class: 'form-horizontal', role: 'form'} do |f| %>
<% if application.errors.any? %>
<div class="alert alert-danger" data-alert><p><%= t('doorkeeper.applications.form.error') %></p></div>
<% end %>
<%= content_tag :div, class: "form-group#{' has-error' if application.errors[:name].present?}" do %>
<%= f.label :name, class: 'col-sm-2 control-label' %>
<div class="col-sm-10">
<%= f.text_field :name, class: 'form-control' %>
<%= doorkeeper_errors_for application, :name %>
</div>
<% end %>
<%= content_tag :div, class: "form-group#{' has-error' if application.errors[:redirect_uri].present?}" do %>
<%= f.label :redirect_uri, class: 'col-sm-2 control-label' %>
<div class="col-sm-10">
<%= f.text_area :redirect_uri, class: 'form-control' %>
<%= doorkeeper_errors_for application, :redirect_uri %>
<span class="help-block">
<%= t('doorkeeper.applications.help.redirect_uri') %>
</span>
<% if Doorkeeper.configuration.native_redirect_uri %>
<span class="help-block">
<%= raw t('doorkeeper.applications.help.native_redirect_uri', native_redirect_uri: content_tag(:code) { Doorkeeper.configuration.native_redirect_uri }) %>
</span>
<% end %>
</div>
<% end %>
<%= content_tag :div, class: "form-group#{' has-error' if application.errors[:scopes].present?}" do %>
<%= f.label :scopes, class: 'col-sm-2 control-label' %>
<div class="col-sm-10">
<%= f.text_field :scopes, class: 'form-control' %>
<%= doorkeeper_errors_for application, :scopes %>
<span class="help-block">
<%= t('doorkeeper.applications.help.scopes') %>
</span>
</div>
<% end %>
<div class="form-group">
<div class="col-sm-offset-2 col-sm-10">
<%= f.submit t('doorkeeper.applications.buttons.submit'), class: "btn btn-primary" %>
<%= link_to t('doorkeeper.applications.buttons.cancel'), oauth_applications_path, class: "btn btn-default" %>
</div>
</div>
<% end %>

View file

@ -0,0 +1,5 @@
<div class="page-header">
<h1><%= t('.title') %></h1>
</div>
<%= render 'form', application: @application %>

View file

@ -0,0 +1,26 @@
<div class="page-header">
<h1><%= t('.title') %></h1>
</div>
<p><%= link_to t('.new'), new_oauth_application_path, class: 'btn btn-success' %></p>
<table class="table table-striped">
<thead>
<tr>
<th><%= t('.name') %></th>
<th><%= t('.callback_url') %></th>
<th></th>
<th></th>
</tr>
</thead>
<tbody>
<% @applications.each do |application| %>
<tr id="application_<%= application.id %>">
<td><%= link_to application.name, oauth_application_path(application) %></td>
<td><%= application.redirect_uri %></td>
<td><%= link_to t('doorkeeper.applications.buttons.edit'), edit_oauth_application_path(application), class: 'btn btn-link' %></td>
<td><%= render 'delete_form', application: application %></td>
</tr>
<% end %>
</tbody>
</table>

View file

@ -0,0 +1,5 @@
<div class="page-header">
<h1><%= t('.title') %></h1>
</div>
<%= render 'form', application: @application %>

View file

@ -0,0 +1,39 @@
<div class="page-header">
<h1><%= t('.title', name: @application.name) %></h1>
</div>
<div class="row">
<div class="col-md-8">
<h4><%= t('.application_id') %>:</h4>
<p><code id="application_id"><%= @application.uid %></code></p>
<h4><%= t('.secret') %>:</h4>
<p><code id="secret"><%= @application.secret %></code></p>
<h4><%= t('.scopes') %>:</h4>
<p><code id="scopes"><%= @application.scopes %></code></p>
<h4><%= t('.callback_urls') %>:</h4>
<table>
<% @application.redirect_uri.split.each do |uri| %>
<tr>
<td>
<code><%= uri %></code>
</td>
<td>
<%= link_to t('doorkeeper.applications.buttons.authorize'), oauth_authorization_path(client_id: @application.uid, redirect_uri: uri, response_type: 'code', scope: @application.scopes), class: 'btn btn-success', target: '_blank' %>
</td>
</tr>
<% end %>
</table>
</div>
<div class="col-md-4">
<h3><%= t('.actions') %></h3>
<p><%= link_to t('doorkeeper.applications.buttons.edit'), edit_oauth_application_path(@application), class: 'btn btn-primary' %></p>
<p><%= render 'delete_form', application: @application, submit_btn_css: 'btn btn-danger' %></p>
</div>
</div>

View file

@ -0,0 +1,7 @@
<div class="page-header">
<h1><%= t('doorkeeper.authorizations.error.title') %></h1>
</div>
<main role="main">
<pre><%= @pre_auth.error_response.body[:error_description] %></pre>
</main>

View file

@ -0,0 +1,40 @@
<header class="page-header" role="banner">
<h1><%= t('.title') %></h1>
</header>
<main role="main">
<p class="h4">
<%= raw t('.prompt', client_name: content_tag(:strong, class: 'text-info') { @pre_auth.client.name }) %>
</p>
<% if @pre_auth.scopes.count > 0 %>
<div id="oauth-permissions">
<p><%= t('.able_to') %>:</p>
<ul class="text-info">
<% @pre_auth.scopes.each do |scope| %>
<li><%= t scope, scope: [:doorkeeper, :scopes] %></li>
<% end %>
</ul>
</div>
<% end %>
<div class="actions">
<%= form_tag oauth_authorization_path, method: :post do %>
<%= hidden_field_tag :client_id, @pre_auth.client.uid %>
<%= hidden_field_tag :redirect_uri, @pre_auth.redirect_uri %>
<%= hidden_field_tag :state, @pre_auth.state %>
<%= hidden_field_tag :response_type, @pre_auth.response_type %>
<%= hidden_field_tag :scope, @pre_auth.scope %>
<%= submit_tag t('doorkeeper.authorizations.buttons.authorize'), class: "btn btn-success btn-lg btn-block" %>
<% end %>
<%= form_tag oauth_authorization_path, method: :delete do %>
<%= hidden_field_tag :client_id, @pre_auth.client.uid %>
<%= hidden_field_tag :redirect_uri, @pre_auth.redirect_uri %>
<%= hidden_field_tag :state, @pre_auth.state %>
<%= hidden_field_tag :response_type, @pre_auth.response_type %>
<%= hidden_field_tag :scope, @pre_auth.scope %>
<%= submit_tag t('doorkeeper.authorizations.buttons.deny'), class: "btn btn-danger btn-lg btn-block" %>
<% end %>
</div>
</main>

View file

@ -0,0 +1,7 @@
<header class="page-header">
<h1><%= t('.title') %>:</h1>
</header>
<main role="main">
<code id="authorization_code"><%= params[:code] %></code>
</main>

View file

@ -0,0 +1,4 @@
<%- submit_btn_css ||= 'btn btn-link' %>
<%= form_tag oauth_authorized_application_path(application), method: :delete do %>
<%= submit_tag t('doorkeeper.authorized_applications.buttons.revoke'), onclick: "return confirm('#{ t('doorkeeper.authorized_applications.confirmations.revoke') }')", class: submit_btn_css %>
<% end %>

View file

@ -0,0 +1,24 @@
<header class="page-header">
<h1><%= t('doorkeeper.authorized_applications.index.title') %></h1>
</header>
<main role="main">
<table class="table table-striped">
<thead>
<tr>
<th><%= t('doorkeeper.authorized_applications.index.application') %></th>
<th><%= t('doorkeeper.authorized_applications.index.created_at') %></th>
<th></th>
</tr>
</thead>
<tbody>
<% @applications.each do |application| %>
<tr>
<td><%= application.name %></td>
<td><%= application.created_at.strftime(t('doorkeeper.authorized_applications.index.date_format')) %></td>
<td><%= render 'delete_form', application: application %></td>
</tr>
<% end %>
</tbody>
</table>
</main>

View file

@ -0,0 +1,37 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Doorkeeper</title>
<%= stylesheet_link_tag "doorkeeper/admin/application" %>
<%= csrf_meta_tags %>
</head>
<body>
<div class="navbar navbar-inverse navbar-static-top" role="navigation">
<div class="container-fluid">
<div class="navbar-header">
<%= link_to t('doorkeeper.layouts.admin.nav.oauth2_provider'), oauth_applications_path, class: 'navbar-brand' %>
</div>
<ul class="nav navbar-nav">
<%= content_tag :li, class: "#{'active' if request.path == oauth_applications_path}" do %>
<%= link_to t('doorkeeper.layouts.admin.nav.applications'), oauth_applications_path %>
<% end %>
<%= content_tag :li do %>
<%= link_to t('doorkeeper.layouts.admin.nav.home'), root_path %>
<% end %>
</ul>
</div>
</div>
<div class="container">
<%- if flash[:notice].present? %>
<div class="alert alert-info">
<%= flash[:notice] %>
</div>
<% end -%>
<%= yield %>
</div>
</body>
</html>

View file

@ -0,0 +1,23 @@
<!DOCTYPE html>
<html>
<head>
<title><%= t('doorkeeper.layouts.application.title') %></title>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<%= stylesheet_link_tag "doorkeeper/application" %>
<%= csrf_meta_tags %>
</head>
<body>
<div id="container">
<%- if flash[:notice].present? %>
<div class="alert alert-info">
<%= flash[:notice] %>
</div>
<% end -%>
<%= yield %>
</div>
</body>
</html>

View file

@ -0,0 +1,122 @@
en:
activerecord:
attributes:
doorkeeper/application:
name: 'Name'
redirect_uri: 'Redirect URI'
errors:
models:
doorkeeper/application:
attributes:
redirect_uri:
fragment_present: 'cannot contain a fragment.'
invalid_uri: 'must be a valid URI.'
relative_uri: 'must be an absolute URI.'
secured_uri: 'must be an HTTPS/SSL URI.'
forbidden_uri: 'is forbidden by the server.'
doorkeeper:
applications:
confirmations:
destroy: 'Are you sure?'
buttons:
edit: 'Edit'
destroy: 'Destroy'
submit: 'Submit'
cancel: 'Cancel'
authorize: 'Authorize'
form:
error: 'Whoops! Check your form for possible errors'
help:
redirect_uri: 'Use one line per URI'
native_redirect_uri: 'Use %{native_redirect_uri} if you want to add localhost URIs for development purposes'
scopes: 'Separate scopes with spaces. Leave blank to use the default scopes.'
edit:
title: 'Edit application'
index:
title: 'Your applications'
new: 'New Application'
name: 'Name'
callback_url: 'Callback URL'
new:
title: 'New Application'
show:
title: 'Application: %{name}'
application_id: 'Application Id'
secret: 'Secret'
scopes: 'Scopes'
callback_urls: 'Callback urls'
actions: 'Actions'
authorizations:
buttons:
authorize: 'Authorize'
deny: 'Deny'
error:
title: 'An error has occurred'
new:
title: 'Authorization required'
prompt: 'Authorize %{client_name} to use your account?'
able_to: 'This application will be able to'
show:
title: 'Authorization code'
authorized_applications:
confirmations:
revoke: 'Are you sure?'
buttons:
revoke: 'Revoke'
index:
title: 'Your authorized applications'
application: 'Application'
created_at: 'Created At'
date_format: '%Y-%m-%d %H:%M:%S'
errors:
messages:
# Common error messages
invalid_request: 'The request is missing a required parameter, includes an unsupported parameter value, or is otherwise malformed.'
invalid_redirect_uri: "The requested redirect uri is malformed or doesn't match client redirect URI."
unauthorized_client: 'The client is not authorized to perform this request using this method.'
access_denied: 'The resource owner or authorization server denied the request.'
invalid_scope: 'The requested scope is invalid, unknown, or malformed.'
server_error: 'The authorization server encountered an unexpected condition which prevented it from fulfilling the request.'
temporarily_unavailable: 'The authorization server is currently unable to handle the request due to a temporary overloading or maintenance of the server.'
# Configuration error messages
credential_flow_not_configured: 'Resource Owner Password Credentials flow failed due to Doorkeeper.configure.resource_owner_from_credentials being unconfigured.'
resource_owner_authenticator_not_configured: 'Resource Owner find failed due to Doorkeeper.configure.resource_owner_authenticator being unconfigured.'
# Access grant errors
unsupported_response_type: 'The authorization server does not support this response type.'
# Access token errors
invalid_client: 'Client authentication failed due to unknown client, no client authentication included, or unsupported authentication method.'
invalid_grant: 'The provided authorization grant is invalid, expired, revoked, does not match the redirection URI used in the authorization request, or was issued to another client.'
unsupported_grant_type: 'The authorization grant type is not supported by the authorization server.'
invalid_token:
revoked: "The access token was revoked"
expired: "The access token expired"
unknown: "The access token is invalid"
flash:
applications:
create:
notice: 'Application created.'
destroy:
notice: 'Application deleted.'
update:
notice: 'Application updated.'
authorized_applications:
destroy:
notice: 'Application revoked.'
layouts:
admin:
nav:
oauth2_provider: 'OAuth2 Provider'
applications: 'Applications'
home: 'Home'
application:
title: 'OAuth authorization required'

View file

@ -0,0 +1,30 @@
$LOAD_PATH.push File.expand_path("../lib", __FILE__)
require "doorkeeper/version"
Gem::Specification.new do |s|
s.name = "doorkeeper"
s.version = Doorkeeper.gem_version
s.authors = ["Felipe Elias Philipp", "Tute Costa", "Jon Moss", "Nikita Bulai"]
s.email = %w(bulaj.nikita@gmail.com)
s.homepage = "https://github.com/doorkeeper-gem/doorkeeper"
s.summary = "OAuth 2 provider for Rails and Grape"
s.description = "Doorkeeper is an OAuth 2 provider for Rails and Grape."
s.license = 'MIT'
s.files = `git ls-files`.split("\n")
s.test_files = `git ls-files -- spec/*`.split("\n")
s.require_paths = ["lib"]
s.add_dependency "railties", ">= 4.2"
s.required_ruby_version = ">= 2.1"
s.add_development_dependency "capybara"
s.add_development_dependency "coveralls"
s.add_development_dependency "grape"
s.add_development_dependency "database_cleaner", "~> 1.6"
s.add_development_dependency "factory_bot", "~> 4.8"
s.add_development_dependency "generator_spec", "~> 0.9.3"
s.add_development_dependency "rake", ">= 11.3.0"
s.add_development_dependency "rspec-rails"
end

View file

@ -0,0 +1,13 @@
# This file was generated by Appraisal
source "https://rubygems.org"
gem "rails", "~> 4.2.0"
gem "appraisal"
gem "activerecord-jdbcsqlite3-adapter", platform: :jruby
gem "sqlite3", platform: [:ruby, :mswin, :mingw, :x64_mingw]
gem "tzinfo-data", platforms: [:mingw, :mswin, :x64_mingw]
# Older Grape requires Ruby >= 2.2.2
gem "grape", '~> 0.16', '< 0.19.2'
gemspec path: "../"

View file

@ -0,0 +1,12 @@
# This file was generated by Appraisal
source "https://rubygems.org"
gem "rails", "~> 5.0.0"
gem "appraisal"
gem "activerecord-jdbcsqlite3-adapter", platforms: :jruby
gem "sqlite3", platforms: [:ruby, :mswin, :mingw, :x64_mingw]
gem "tzinfo-data", platforms: [:mingw, :mswin, :x64_mingw]
gem "rspec-rails", "~> 3.5"
gemspec path: "../"

View file

@ -0,0 +1,12 @@
# This file was generated by Appraisal
source "https://rubygems.org"
gem "rails", "~> 5.1.0"
gem "appraisal"
gem "activerecord-jdbcsqlite3-adapter", platforms: :jruby
gem "sqlite3", platforms: [:ruby, :mswin, :mingw, :x64_mingw]
gem "tzinfo-data", platforms: [:mingw, :mswin, :x64_mingw]
gem "rspec-rails", "~> 3.7"
gemspec path: "../"

View file

@ -0,0 +1,12 @@
# This file was generated by Appraisal
source "https://rubygems.org"
gem "rails", "5.2.0.rc1"
gem "appraisal"
gem "activerecord-jdbcsqlite3-adapter", platforms: :jruby
gem "sqlite3", platforms: [:ruby, :mswin, :mingw, :x64_mingw]
gem "tzinfo-data", platforms: [:mingw, :mswin, :x64_mingw]
gem "rspec-rails", "~> 3.7"
gemspec path: "../"

View file

@ -0,0 +1,14 @@
# This file was generated by Appraisal
source "https://rubygems.org"
gem "rails", git: 'https://github.com/rails/rails'
gem "arel", git: 'https://github.com/rails/arel'
gem "appraisal"
gem "activerecord-jdbcsqlite3-adapter", platform: :jruby
gem "sqlite3", platform: [:ruby, :mswin, :mingw, :x64_mingw]
gem "tzinfo-data", platforms: [:mingw, :mswin, :x64_mingw]
gem "rspec-rails", "~> 3.7"
gemspec path: "../"

View file

@ -0,0 +1,75 @@
require 'doorkeeper/version'
require 'doorkeeper/engine'
require 'doorkeeper/config'
require 'doorkeeper/errors'
require 'doorkeeper/server'
require 'doorkeeper/request'
require 'doorkeeper/validations'
require 'doorkeeper/oauth/authorization/code'
require 'doorkeeper/oauth/authorization/token'
require 'doorkeeper/oauth/authorization/uri_builder'
require 'doorkeeper/oauth/helpers/scope_checker'
require 'doorkeeper/oauth/helpers/uri_checker'
require 'doorkeeper/oauth/helpers/unique_token'
require 'doorkeeper/oauth/scopes'
require 'doorkeeper/oauth/error'
require 'doorkeeper/oauth/base_response'
require 'doorkeeper/oauth/code_response'
require 'doorkeeper/oauth/token_response'
require 'doorkeeper/oauth/error_response'
require 'doorkeeper/oauth/pre_authorization'
require 'doorkeeper/oauth/base_request'
require 'doorkeeper/oauth/authorization_code_request'
require 'doorkeeper/oauth/refresh_token_request'
require 'doorkeeper/oauth/password_access_token_request'
require 'doorkeeper/oauth/client_credentials_request'
require 'doorkeeper/oauth/code_request'
require 'doorkeeper/oauth/token_request'
require 'doorkeeper/oauth/client'
require 'doorkeeper/oauth/token'
require 'doorkeeper/oauth/token_introspection'
require 'doorkeeper/oauth/invalid_token_response'
require 'doorkeeper/oauth/forbidden_token_response'
require 'doorkeeper/models/concerns/orderable'
require 'doorkeeper/models/concerns/scopes'
require 'doorkeeper/models/concerns/expirable'
require 'doorkeeper/models/concerns/revocable'
require 'doorkeeper/models/concerns/accessible'
require 'doorkeeper/models/access_grant_mixin'
require 'doorkeeper/models/access_token_mixin'
require 'doorkeeper/models/application_mixin'
require 'doorkeeper/helpers/controller'
require 'doorkeeper/rails/routes'
require 'doorkeeper/rails/helpers'
require 'doorkeeper/orm/active_record'
require 'active_support/deprecation'
module Doorkeeper
def self.configured?
ActiveSupport::Deprecation.warn "Method `Doorkeeper#configured?` has been deprecated without replacement."
@config.present?
end
def self.database_installed?
ActiveSupport::Deprecation.warn "Method `Doorkeeper#database_installed?` has been deprecated without replacement."
[AccessToken, AccessGrant, Application].all?(&:table_exists?)
end
def self.installed?
ActiveSupport::Deprecation.warn "Method `Doorkeeper#installed?` has been deprecated without replacement."
configured? && database_installed?
end
def self.authenticate(request, methods = Doorkeeper.configuration.access_token_methods)
OAuth::Token.authenticate(request, *methods)
end
end

View file

@ -0,0 +1,319 @@
module Doorkeeper
class MissingConfiguration < StandardError
# Defines a MissingConfiguration error for a missing Doorkeeper
# configuration
def initialize
super('Configuration for doorkeeper missing. Do you have doorkeeper initializer?')
end
end
def self.configure(&block)
@config = Config::Builder.new(&block).build
setup_orm_adapter
setup_orm_models
setup_application_owner if @config.enable_application_owner?
end
def self.configuration
@config || (fail MissingConfiguration)
end
def self.setup_orm_adapter
@orm_adapter = "doorkeeper/orm/#{configuration.orm}".classify.constantize
rescue NameError => e
fail e, "ORM adapter not found (#{configuration.orm})", <<-ERROR_MSG.squish
[doorkeeper] ORM adapter not found (#{configuration.orm}), or there was an error
trying to load it.
You probably need to add the related gem for this adapter to work with
doorkeeper.
ERROR_MSG
end
def self.setup_orm_models
@orm_adapter.initialize_models!
end
def self.setup_application_owner
@orm_adapter.initialize_application_owner!
end
class Config
class Builder
def initialize(&block)
@config = Config.new
instance_eval(&block)
end
def build
@config
end
# Provide support for an owner to be assigned to each registered
# application (disabled by default)
# Optional parameter confirmation: true (default false) if you want
# to enforce ownership of a registered application
#
# @param opts [Hash] the options to confirm if an application owner
# is present
# @option opts[Boolean] :confirmation (false)
# Set confirm_application_owner variable
def enable_application_owner(opts = {})
@config.instance_variable_set(:@enable_application_owner, true)
confirm_application_owner if opts[:confirmation].present? && opts[:confirmation]
end
def confirm_application_owner
@config.instance_variable_set(:@confirm_application_owner, true)
end
# Define default access token scopes for your provider
#
# @param scopes [Array] Default set of access (OAuth::Scopes.new)
# token scopes
def default_scopes(*scopes)
@config.instance_variable_set(:@default_scopes, OAuth::Scopes.from_array(scopes))
end
# Define default access token scopes for your provider
#
# @param scopes [Array] Optional set of access (OAuth::Scopes.new)
# token scopes
def optional_scopes(*scopes)
@config.instance_variable_set(:@optional_scopes, OAuth::Scopes.from_array(scopes))
end
# Change the way client credentials are retrieved from the request object.
# By default it retrieves first from the `HTTP_AUTHORIZATION` header, then
# falls back to the `:client_id` and `:client_secret` params from the
# `params` object.
#
# @param methods [Array] Define client credentials
def client_credentials(*methods)
@config.instance_variable_set(:@client_credentials, methods)
end
# Change the way access token is authenticated from the request object.
# By default it retrieves first from the `HTTP_AUTHORIZATION` header, then
# falls back to the `:access_token` or `:bearer_token` params from the
# `params` object.
#
# @param methods [Array] Define access token methods
def access_token_methods(*methods)
@config.instance_variable_set(:@access_token_methods, methods)
end
# Issue access tokens with refresh token (disabled by default)
def use_refresh_token
@config.instance_variable_set(:@refresh_token_enabled, true)
end
# Reuse access token for the same resource owner within an application
# (disabled by default)
# Rationale: https://github.com/doorkeeper-gem/doorkeeper/issues/383
def reuse_access_token
@config.instance_variable_set(:@reuse_access_token, true)
end
end
module Option
# Defines configuration option
#
# When you call option, it defines two methods. One method will take place
# in the +Config+ class and the other method will take place in the
# +Builder+ class.
#
# The +name+ parameter will set both builder method and config attribute.
# If the +:as+ option is defined, the builder method will be the specified
# option while the config attribute will be the +name+ parameter.
#
# If you want to introduce another level of config DSL you can
# define +builder_class+ parameter.
# Builder should take a block as the initializer parameter and respond to function +build+
# that returns the value of the config attribute.
#
# ==== Options
#
# * [:+as+] Set the builder method that goes inside +configure+ block
# * [+:default+] The default value in case no option was set
#
# ==== Examples
#
# option :name
# option :name, as: :set_name
# option :name, default: 'My Name'
# option :scopes builder_class: ScopesBuilder
#
def option(name, options = {})
attribute = options[:as] || name
attribute_builder = options[:builder_class]
Builder.instance_eval do
remove_method name if method_defined?(name)
define_method name do |*args, &block|
# TODO: is builder_class option being used?
value = if attribute_builder
attribute_builder.new(&block).build
else
block ? block : args.first
end
@config.instance_variable_set(:"@#{attribute}", value)
end
end
define_method attribute do |*_args|
if instance_variable_defined?(:"@#{attribute}")
instance_variable_get(:"@#{attribute}")
else
options[:default]
end
end
public attribute
end
end
extend Option
option :resource_owner_authenticator,
as: :authenticate_resource_owner,
default: (lambda do |_routes|
::Rails.logger.warn(I18n.t('doorkeeper.errors.messages.resource_owner_authenticator_not_configured'))
nil
end)
option :admin_authenticator,
as: :authenticate_admin,
default: ->(_routes) {}
option :resource_owner_from_credentials,
default: (lambda do |_routes|
::Rails.logger.warn(I18n.t('doorkeeper.errors.messages.credential_flow_not_configured'))
nil
end)
option :before_successful_strategy_response, default: ->(_request) {}
option :after_successful_strategy_response,
default: ->(_request, _response) {}
option :skip_authorization, default: ->(_routes) {}
option :access_token_expires_in, default: 7200
option :custom_access_token_expires_in, default: ->(_app) { nil }
option :authorization_code_expires_in, default: 600
option :orm, default: :active_record
option :native_redirect_uri, default: 'urn:ietf:wg:oauth:2.0:oob'
option :active_record_options, default: {}
option :grant_flows, default: %w[authorization_code client_credentials]
# Allows to forbid specific Application redirect URI's by custom rules.
# Doesn't forbid any URI by default.
#
# @param forbid_redirect_uri [Proc] Block or any object respond to #call
#
option :forbid_redirect_uri, default: ->(_uri) { false }
# WWW-Authenticate Realm (default "Doorkeeper").
#
# @param realm [String] ("Doorkeeper") Authentication realm
#
option :realm, default: 'Doorkeeper'
# Forces the usage of the HTTPS protocol in non-native redirect uris
# (enabled by default in non-development environments). OAuth2
# delegates security in communication to the HTTPS protocol so it is
# wise to keep this enabled.
#
# @param [Boolean] boolean_or_block value for the parameter, true by default in
# non-development environment
#
# @yield [uri] Conditional usage of SSL redirect uris.
# @yieldparam [URI] Redirect URI
# @yieldreturn [Boolean] Indicates necessity of usage of the HTTPS protocol
# in non-native redirect uris
#
option :force_ssl_in_redirect_uri, default: !Rails.env.development?
# Use a custom class for generating the access token.
# https://github.com/doorkeeper-gem/doorkeeper#custom-access-token-generator
#
# @param access_token_generator [String]
# the name of the access token generator class
#
option :access_token_generator,
default: 'Doorkeeper::OAuth::Helpers::UniqueToken'
# The controller Doorkeeper::ApplicationController inherits from.
# Defaults to ActionController::Base.
# https://github.com/doorkeeper-gem/doorkeeper#custom-base-controller
#
# @param base_controller [String] the name of the base controller
option :base_controller,
default: 'ActionController::Base'
attr_reader :reuse_access_token
def refresh_token_enabled?
@refresh_token_enabled ||= false
!!@refresh_token_enabled
end
def enable_application_owner?
@enable_application_owner ||= false
!!@enable_application_owner
end
def confirm_application_owner?
@confirm_application_owner ||= false
!!@confirm_application_owner
end
def default_scopes
@default_scopes ||= OAuth::Scopes.new
end
def optional_scopes
@optional_scopes ||= OAuth::Scopes.new
end
def scopes
@scopes ||= default_scopes + optional_scopes
end
def client_credentials_methods
@client_credentials ||= %i[from_basic from_params]
end
def access_token_methods
@access_token_methods ||= %i[from_bearer_authorization from_access_token_param from_bearer_param]
end
def authorization_response_types
@authorization_response_types ||= calculate_authorization_response_types
end
def token_grant_types
@token_grant_types ||= calculate_token_grant_types
end
private
# Determines what values are acceptable for 'response_type' param in
# authorization request endpoint, and return them as an array of strings.
#
def calculate_authorization_response_types
types = []
types << 'code' if grant_flows.include? 'authorization_code'
types << 'token' if grant_flows.include? 'implicit'
types
end
# Determines what values are acceptable for 'grant_type' param token
# request endpoint, and return them in array.
#
def calculate_token_grant_types
types = grant_flows - ['implicit']
types << 'refresh_token' if refresh_token_enabled?
types
end
end
end

View file

@ -0,0 +1,27 @@
module Doorkeeper
class Engine < Rails::Engine
initializer "doorkeeper.params.filter" do |app|
parameters = %w[client_secret code authentication_token access_token refresh_token]
app.config.filter_parameters << /^(#{Regexp.union parameters})$/
end
initializer "doorkeeper.routes" do
Doorkeeper::Rails::Routes.install!
end
initializer "doorkeeper.helpers" do
ActiveSupport.on_load(:action_controller) do
include Doorkeeper::Rails::Helpers
end
end
if defined?(Sprockets) && Sprockets::VERSION.chr.to_i >= 4
initializer 'doorkeeper.assets.precompile' do |app|
app.config.assets.precompile += %w[
doorkeeper/application.css
doorkeeper/admin/application.css
]
end
end
end
end

View file

@ -0,0 +1,45 @@
module Doorkeeper
module Errors
class DoorkeeperError < StandardError
def type
message
end
end
class InvalidAuthorizationStrategy < DoorkeeperError
def type
:unsupported_response_type
end
end
class InvalidTokenReuse < DoorkeeperError
def type
:invalid_request
end
end
class InvalidGrantReuse < DoorkeeperError
def type
:invalid_grant
end
end
class InvalidTokenStrategy < DoorkeeperError
def type
:unsupported_grant_type
end
end
class MissingRequestStrategy < DoorkeeperError
def type
:invalid_request
end
end
class UnableToGenerateToken < DoorkeeperError
end
class TokenGeneratorNotFound < DoorkeeperError
end
end
end

View file

@ -0,0 +1,17 @@
module Doorkeeper
module Grape
class AuthorizationDecorator < SimpleDelegator
def parameters
params
end
def authorization
env = __getobj__.env
env['HTTP_AUTHORIZATION'] ||
env['X-HTTP_AUTHORIZATION'] ||
env['X_HTTP_AUTHORIZATION'] ||
env['REDIRECT_X_HTTP_AUTHORIZATION']
end
end
end
end

View file

@ -0,0 +1,52 @@
require 'doorkeeper/grape/authorization_decorator'
module Doorkeeper
module Grape
module Helpers
# These helpers are for grape >= 0.10
extend ::Grape::API::Helpers
include Doorkeeper::Rails::Helpers
# endpoint specific scopes > parameter scopes > default scopes
def doorkeeper_authorize!(*scopes)
endpoint_scopes = endpoint.route_setting(:scopes) || endpoint.options[:route_options][:scopes]
scopes = if endpoint_scopes
Doorkeeper::OAuth::Scopes.from_array(endpoint_scopes)
elsif scopes && !scopes.empty?
Doorkeeper::OAuth::Scopes.from_array(scopes)
end
super(*scopes)
end
def doorkeeper_render_error_with(error)
status_code = error_status_codes[error.status]
error!({ error: error.description }, status_code, error.headers)
end
private
def endpoint
env['api.endpoint']
end
def doorkeeper_token
@_doorkeeper_token ||= OAuth::Token.authenticate(
decorated_request,
*Doorkeeper.configuration.access_token_methods
)
end
def decorated_request
AuthorizationDecorator.new(request)
end
def error_status_codes
{
unauthorized: 401,
forbidden: 403
}
end
end
end
end

View file

@ -0,0 +1,56 @@
# Define methods that can be called in any controller that inherits from
# Doorkeeper::ApplicationMetalController or Doorkeeper::ApplicationController
module Doorkeeper
module Helpers
module Controller
private
# :doc:
def authenticate_resource_owner!
current_resource_owner
end
# :doc:
def current_resource_owner
instance_eval(&Doorkeeper.configuration.authenticate_resource_owner)
end
def resource_owner_from_credentials
instance_eval(&Doorkeeper.configuration.resource_owner_from_credentials)
end
# :doc:
def authenticate_admin!
instance_eval(&Doorkeeper.configuration.authenticate_admin)
end
def server
@server ||= Server.new(self)
end
# :doc:
def doorkeeper_token
@token ||= OAuth::Token.authenticate request, *config_methods
end
def config_methods
@methods ||= Doorkeeper.configuration.access_token_methods
end
def get_error_response_from_exception(exception)
OAuth::ErrorResponse.new name: exception.type, state: params[:state]
end
def handle_token_exception(exception)
error = get_error_response_from_exception exception
headers.merge! error.headers
self.response_body = error.body.to_json
self.status = error.status
end
def skip_authorization?
!!instance_exec([@server.current_resource_owner, @pre_auth.client], &Doorkeeper.configuration.skip_authorization)
end
end
end
end

View file

@ -0,0 +1,26 @@
module Doorkeeper
module AccessGrantMixin
extend ActiveSupport::Concern
include OAuth::Helpers
include Models::Expirable
include Models::Revocable
include Models::Accessible
include Models::Orderable
include Models::Scopes
module ClassMethods
# Searches for Doorkeeper::AccessGrant record with the
# specific token value.
#
# @param token [#to_s] token value (any object that responds to `#to_s`)
#
# @return [Doorkeeper::AccessGrant, nil] AccessGrant object or nil
# if there is no record with such token
#
def by_token(token)
find_by(token: token.to_s)
end
end
end
end

View file

@ -0,0 +1,246 @@
module Doorkeeper
module AccessTokenMixin
extend ActiveSupport::Concern
include OAuth::Helpers
include Models::Expirable
include Models::Revocable
include Models::Accessible
include Models::Orderable
include Models::Scopes
module ClassMethods
# Returns an instance of the Doorkeeper::AccessToken with
# specific token value.
#
# @param token [#to_s]
# token value (any object that responds to `#to_s`)
#
# @return [Doorkeeper::AccessToken, nil] AccessToken object or nil
# if there is no record with such token
#
def by_token(token)
find_by(token: token.to_s)
end
# Returns an instance of the Doorkeeper::AccessToken
# with specific token value.
#
# @param refresh_token [#to_s]
# refresh token value (any object that responds to `#to_s`)
#
# @return [Doorkeeper::AccessToken, nil] AccessToken object or nil
# if there is no record with such refresh token
#
def by_refresh_token(refresh_token)
find_by(refresh_token: refresh_token.to_s)
end
# Revokes AccessToken records that have not been revoked and associated
# with the specific Application and Resource Owner.
#
# @param application_id [Integer]
# ID of the Application
# @param resource_owner [ActiveRecord::Base]
# instance of the Resource Owner model
#
def revoke_all_for(application_id, resource_owner, clock = Time)
where(application_id: application_id,
resource_owner_id: resource_owner.id,
revoked_at: nil).
update_all(revoked_at: clock.now.utc)
end
# Looking for not expired Access Token with a matching set of scopes
# that belongs to specific Application and Resource Owner.
#
# @param application [Doorkeeper::Application]
# Application instance
# @param resource_owner_or_id [ActiveRecord::Base, Integer]
# Resource Owner model instance or it's ID
# @param scopes [String, Doorkeeper::OAuth::Scopes]
# set of scopes
#
# @return [Doorkeeper::AccessToken, nil] Access Token instance or
# nil if matching record was not found
#
def matching_token_for(application, resource_owner_or_id, scopes)
resource_owner_id = if resource_owner_or_id.respond_to?(:to_key)
resource_owner_or_id.id
else
resource_owner_or_id
end
token = last_authorized_token_for(application.try(:id), resource_owner_id)
if token && scopes_match?(token.scopes, scopes, application.try(:scopes))
token
end
end
# Checks whether the token scopes match the scopes from the parameters or
# Application scopes (if present).
#
# @param token_scopes [#to_s]
# set of scopes (any object that responds to `#to_s`)
# @param param_scopes [String]
# scopes from params
# @param app_scopes [String]
# Application scopes
#
# @return [Boolean] true if all scopes are blank or matches
# and false in other cases
#
def scopes_match?(token_scopes, param_scopes, app_scopes)
(!token_scopes.present? && !param_scopes.present?) ||
Doorkeeper::OAuth::Helpers::ScopeChecker.match?(
token_scopes.to_s,
param_scopes,
app_scopes
)
end
# Looking for not expired AccessToken record with a matching set of
# scopes that belongs to specific Application and Resource Owner.
# If it doesn't exists - then creates it.
#
# @param application [Doorkeeper::Application]
# Application instance
# @param resource_owner_id [ActiveRecord::Base, Integer]
# Resource Owner model instance or it's ID
# @param scopes [#to_s]
# set of scopes (any object that responds to `#to_s`)
# @param expires_in [Integer]
# token lifetime in seconds
# @param use_refresh_token [Boolean]
# whether to use the refresh token
#
# @return [Doorkeeper::AccessToken] existing record or a new one
#
def find_or_create_for(application, resource_owner_id, scopes, expires_in, use_refresh_token)
if Doorkeeper.configuration.reuse_access_token
access_token = matching_token_for(application, resource_owner_id, scopes)
if access_token && !access_token.expired?
return access_token
end
end
create!(
application_id: application.try(:id),
resource_owner_id: resource_owner_id,
scopes: scopes.to_s,
expires_in: expires_in,
use_refresh_token: use_refresh_token
)
end
# Looking for not revoked Access Token record that belongs to specific
# Application and Resource Owner.
#
# @param application_id [Integer]
# ID of the Application model instance
# @param resource_owner_id [Integer]
# ID of the Resource Owner model instance
#
# @return [Doorkeeper::AccessToken, nil] matching AccessToken object or
# nil if nothing was found
#
def last_authorized_token_for(application_id, resource_owner_id)
ordered_by(:created_at, :desc).
find_by(application_id: application_id,
resource_owner_id: resource_owner_id,
revoked_at: nil)
end
end
# Access Token type: Bearer.
# @see https://tools.ietf.org/html/rfc6750
# The OAuth 2.0 Authorization Framework: Bearer Token Usage
#
def token_type
'bearer'
end
def use_refresh_token?
@use_refresh_token ||= false
!!@use_refresh_token
end
# JSON representation of the Access Token instance.
#
# @return [Hash] hash with token data
def as_json(_options = {})
{
resource_owner_id: resource_owner_id,
scopes: scopes,
expires_in_seconds: expires_in_seconds,
application: { uid: application.try(:uid) },
created_at: created_at.to_i
}
end
# Indicates whether the token instance have the same credential
# as the other Access Token.
#
# @param access_token [Doorkeeper::AccessToken] other token
#
# @return [Boolean] true if credentials are same of false in other cases
#
def same_credential?(access_token)
application_id == access_token.application_id &&
resource_owner_id == access_token.resource_owner_id
end
# Indicates if token is acceptable for specific scopes.
#
# @param scopes [Array<String>] scopes
#
# @return [Boolean] true if record is accessible and includes scopes or
# false in other cases
#
def acceptable?(scopes)
accessible? && includes_scope?(*scopes)
end
private
# Generates refresh token with UniqueToken generator.
#
# @return [String] refresh token value
#
def generate_refresh_token
self.refresh_token = UniqueToken.generate
end
# Generates and sets the token value with the
# configured Generator class (see Doorkeeper.configuration).
#
# @return [String] generated token value
#
# @raise [Doorkeeper::Errors::UnableToGenerateToken]
# custom class doesn't implement .generate method
# @raise [Doorkeeper::Errors::TokenGeneratorNotFound]
# custom class doesn't exist
#
def generate_token
self.created_at ||= Time.now.utc
self.token = token_generator.generate(
resource_owner_id: resource_owner_id,
scopes: scopes,
application: application,
expires_in: expires_in,
created_at: created_at
)
end
def token_generator
generator_name = Doorkeeper.configuration.access_token_generator
generator = generator_name.constantize
return generator if generator.respond_to?(:generate)
raise Errors::UnableToGenerateToken, "#{generator} does not respond to `.generate`."
rescue NameError
raise Errors::TokenGeneratorNotFound, "#{generator_name} not found"
end
end
end

View file

@ -0,0 +1,44 @@
module Doorkeeper
module ApplicationMixin
extend ActiveSupport::Concern
include OAuth::Helpers
include Models::Orderable
include Models::Scopes
module ClassMethods
# Returns an instance of the Doorkeeper::Application with
# specific UID and secret.
#
# @param uid [#to_s] UID (any object that responds to `#to_s`)
# @param secret [#to_s] secret (any object that responds to `#to_s`)
#
# @return [Doorkeeper::Application, nil] Application instance or nil
# if there is no record with such credentials
#
def by_uid_and_secret(uid, secret)
find_by(uid: uid.to_s, secret: secret.to_s)
end
# Returns an instance of the Doorkeeper::Application with specific UID.
#
# @param uid [#to_s] UID (any object that responds to `#to_s`)
#
# @return [Doorkeeper::Application, nil] Application instance or nil
# if there is no record with such UID
#
def by_uid(uid)
find_by(uid: uid.to_s)
end
end
# Set an application's valid redirect URIs.
#
# @param uris [String, Array] Newline-separated string or array the URI(s)
#
# @return [String] The redirect URI(s) seperated by newlines.
def redirect_uri=(uris)
super(uris.is_a?(Array) ? uris.join("\n") : uris)
end
end
end

View file

@ -0,0 +1,13 @@
module Doorkeeper
module Models
module Accessible
# Indicates whether the object is accessible (not expired and not revoked).
#
# @return [Boolean] true if object accessible or false in other case
#
def accessible?
!expired? && !revoked?
end
end
end
end

View file

@ -0,0 +1,32 @@
module Doorkeeper
module Models
module Expirable
# Indicates whether the object is expired (`#expires_in` present and
# expiration time has come).
#
# @return [Boolean] true if object expired and false in other case
def expired?
expires_in && Time.now.utc > expires_at
end
# Calculates expiration time in seconds.
#
# @return [Integer, nil] number of seconds if object has expiration time
# or nil if object never expires.
def expires_in_seconds
return nil if expires_in.nil?
expires = expires_at - Time.now.utc
expires_sec = expires.seconds.round(0)
expires_sec > 0 ? expires_sec : 0
end
# Expiration time (date time of creation + TTL).
#
# @return [Time] expiration time in UTC
#
def expires_at
created_at + expires_in.seconds
end
end
end
end

View file

@ -0,0 +1,13 @@
module Doorkeeper
module Models
module Orderable
extend ActiveSupport::Concern
module ClassMethods
def ordered_by(attribute, direction = :asc)
order(attribute => direction)
end
end
end
end
end

View file

@ -0,0 +1,21 @@
module Doorkeeper
module Models
module Ownership
extend ActiveSupport::Concern
included do
belongs_to_options = { polymorphic: true }
if defined?(ActiveRecord::Base) && ActiveRecord::VERSION::MAJOR >= 5
belongs_to_options[:optional] = true
end
belongs_to :owner, belongs_to_options
validates :owner, presence: true, if: :validate_owner?
end
def validate_owner?
Doorkeeper.configuration.confirm_application_owner?
end
end
end
end

View file

@ -0,0 +1,48 @@
module Doorkeeper
module Models
module Revocable
# Revokes the object (updates `:revoked_at` attribute setting its value
# to the specific time).
#
# @param clock [Time] time object
#
def revoke(clock = Time)
update_attribute :revoked_at, clock.now.utc
end
# Indicates whether the object has been revoked.
#
# @return [Boolean] true if revoked, false in other case
#
def revoked?
!!(revoked_at && revoked_at <= Time.now.utc)
end
# Revokes token with `:refresh_token` equal to `:previous_refresh_token`
# and clears `:previous_refresh_token` attribute.
#
def revoke_previous_refresh_token!
return unless refresh_token_revoked_on_use?
old_refresh_token.revoke if old_refresh_token
update_attribute :previous_refresh_token, ""
end
private
# Searches for Access Token record with `:refresh_token` equal to
# `:previous_refresh_token` value.
#
# @return [Doorkeeper::AccessToken, nil]
# Access Token record or nil if nothing found
#
def old_refresh_token
@old_refresh_token ||=
AccessToken.by_refresh_token(previous_refresh_token)
end
def refresh_token_revoked_on_use?
AccessToken.refresh_token_revoked_on_use?
end
end
end
end

View file

@ -0,0 +1,17 @@
module Doorkeeper
module Models
module Scopes
def scopes
OAuth::Scopes.from_string(self[:scopes])
end
def scopes_string
self[:scopes]
end
def includes_scope?(*required_scopes)
required_scopes.blank? || required_scopes.any? { |s| scopes.exists?(s.to_s) }
end
end
end
end

View file

@ -0,0 +1,32 @@
module Doorkeeper
module OAuth
module Authorization
class Code
attr_accessor :pre_auth, :resource_owner, :token
def initialize(pre_auth, resource_owner)
@pre_auth = pre_auth
@resource_owner = resource_owner
end
def issue_token
@token ||= AccessGrant.create!(
application_id: pre_auth.client.id,
resource_owner_id: resource_owner.id,
expires_in: configuration.authorization_code_expires_in,
redirect_uri: pre_auth.redirect_uri,
scopes: pre_auth.scopes.to_s
)
end
def native_redirect
{ action: :show, code: token.token }
end
def configuration
Doorkeeper.configuration
end
end
end
end
end

View file

@ -0,0 +1,60 @@
module Doorkeeper
module OAuth
module Authorization
class Token
attr_accessor :pre_auth, :resource_owner, :token
class << self
def access_token_expires_in(server, pre_auth_or_oauth_client)
if (expiration = custom_expiration(server, pre_auth_or_oauth_client))
expiration
else
server.access_token_expires_in
end
end
private
def custom_expiration(server, pre_auth_or_oauth_client)
oauth_client = if pre_auth_or_oauth_client.respond_to?(:client)
pre_auth_or_oauth_client.client
else
pre_auth_or_oauth_client
end
server.custom_access_token_expires_in.call(oauth_client)
end
end
def initialize(pre_auth, resource_owner)
@pre_auth = pre_auth
@resource_owner = resource_owner
end
def issue_token
@token ||= AccessToken.find_or_create_for(
pre_auth.client,
resource_owner.id,
pre_auth.scopes,
self.class.access_token_expires_in(configuration, pre_auth),
false
)
end
def native_redirect
{
controller: 'doorkeeper/token_info',
action: :show,
access_token: token.token
}
end
private
def configuration
Doorkeeper.configuration
end
end
end
end
end

View file

@ -0,0 +1,31 @@
require 'rack/utils'
module Doorkeeper
module OAuth
module Authorization
class URIBuilder
class << self
def uri_with_query(url, parameters = {})
uri = URI.parse(url)
original_query = Rack::Utils.parse_query(uri.query)
uri.query = build_query(original_query.merge(parameters))
uri.to_s
end
def uri_with_fragment(url, parameters = {})
uri = URI.parse(url)
uri.fragment = build_query(parameters)
uri.to_s
end
private
def build_query(parameters = {})
parameters = parameters.reject { |_, v| v.blank? }
Rack::Utils.build_query parameters
end
end
end
end
end
end

View file

@ -0,0 +1,56 @@
module Doorkeeper
module OAuth
class AuthorizationCodeRequest < BaseRequest
validate :attributes, error: :invalid_request
validate :client, error: :invalid_client
validate :grant, error: :invalid_grant
# @see https://tools.ietf.org/html/rfc6749#section-5.2
validate :redirect_uri, error: :invalid_grant
attr_accessor :server, :grant, :client, :redirect_uri, :access_token
def initialize(server, grant, client, parameters = {})
@server = server
@client = client
@grant = grant
@redirect_uri = parameters[:redirect_uri]
end
private
def before_successful_response
grant.transaction do
grant.lock!
raise Errors::InvalidGrantReuse if grant.revoked?
grant.revoke
find_or_create_access_token(grant.application,
grant.resource_owner_id,
grant.scopes,
server)
end
super
end
def validate_attributes
redirect_uri.present?
end
def validate_client
!!client
end
def validate_grant
return false unless grant && grant.application_id == client.id
grant.accessible?
end
def validate_redirect_uri
Helpers::URIChecker.valid_for_authorization?(
redirect_uri,
grant.redirect_uri
)
end
end
end
end

View file

@ -0,0 +1,55 @@
module Doorkeeper
module OAuth
class BaseRequest
include Validations
def authorize
validate
if valid?
before_successful_response
@response = TokenResponse.new(access_token)
after_successful_response
@response
else
@response = ErrorResponse.from_request(self)
end
end
def scopes
@scopes ||= if @original_scopes.present?
OAuth::Scopes.from_string(@original_scopes)
else
default_scopes
end
end
def default_scopes
server.default_scopes
end
def valid?
error.nil?
end
def find_or_create_access_token(client, resource_owner_id, scopes, server)
@access_token = AccessToken.find_or_create_for(
client,
resource_owner_id,
scopes,
Authorization::Token.access_token_expires_in(server, client),
server.refresh_token_enabled?
)
end
def before_successful_response
Doorkeeper.configuration.before_successful_strategy_response.call(self)
end
def after_successful_response
Doorkeeper.configuration.after_successful_strategy_response.
call(self, @response)
end
end
end
end

View file

@ -0,0 +1,29 @@
module Doorkeeper
module OAuth
class BaseResponse
def body
{}
end
def description
""
end
def headers
{}
end
def redirectable?
false
end
def redirect_uri
""
end
def status
:ok
end
end
end
end

View file

@ -0,0 +1,29 @@
require 'doorkeeper/oauth/client/credentials'
module Doorkeeper
module OAuth
class Client
attr_accessor :application
delegate :id, :name, :uid, :redirect_uri, :scopes, to: :@application
def initialize(application)
@application = application
end
def self.find(uid, method = Application.method(:by_uid))
if (application = method.call(uid))
new(application)
end
end
def self.authenticate(credentials, method = Application.method(:by_uid_and_secret))
return false if credentials.blank?
if (application = method.call(credentials.uid, credentials.secret))
new(application)
end
end
end
end
end

View file

@ -0,0 +1,32 @@
module Doorkeeper
module OAuth
class Client
Credentials = Struct.new(:uid, :secret) do
class << self
def from_request(request, *credentials_methods)
credentials_methods.inject(nil) do |credentials, method|
method = self.method(method) if method.is_a?(Symbol)
credentials = Credentials.new(*method.call(request))
break credentials unless credentials.blank?
end
end
def from_params(request)
request.parameters.values_at(:client_id, :client_secret)
end
def from_basic(request)
authorization = request.authorization
if authorization.present? && authorization =~ /^Basic (.*)/m
Base64.decode64(Regexp.last_match(1)).split(/:/, 2)
end
end
end
def blank?
uid.blank? || secret.blank?
end
end
end
end
end

View file

@ -0,0 +1,13 @@
module Doorkeeper
module OAuth
class ClientCredentialsRequest < BaseRequest
class Creator
def call(client, scopes, attributes = {})
AccessToken.find_or_create_for(
client, nil, scopes, attributes[:expires_in],
attributes[:use_refresh_token])
end
end
end
end
end

View file

@ -0,0 +1,40 @@
require 'doorkeeper/oauth/client_credentials/validation'
module Doorkeeper
module OAuth
class ClientCredentialsRequest < BaseRequest
class Issuer
attr_accessor :token, :validation, :error
def initialize(server, validation)
@server = server
@validation = validation
end
def create(client, scopes, creator = Creator.new)
if validation.valid?
@token = create_token(client, scopes, creator)
@error = :server_error unless @token
else
@token = false
@error = validation.error
end
@token
end
private
def create_token(client, scopes, creator)
ttl = Authorization::Token.access_token_expires_in(@server, client)
creator.call(
client,
scopes,
use_refresh_token: false,
expires_in: ttl
)
end
end
end
end
end

View file

@ -0,0 +1,45 @@
require 'doorkeeper/validations'
require 'doorkeeper/oauth/scopes'
require 'doorkeeper/oauth/helpers/scope_checker'
module Doorkeeper
module OAuth
class ClientCredentialsRequest < BaseRequest
class Validation
include Validations
include OAuth::Helpers
validate :client, error: :invalid_client
validate :scopes, error: :invalid_scope
def initialize(server, request)
@server, @request, @client = server, request, request.client
validate
end
private
def validate_client
@client.present?
end
def validate_scopes
return true unless @request.scopes.present?
application_scopes = if @client.present?
@client.application.scopes
else
''
end
ScopeChecker.valid?(
@request.scopes.to_s,
@server.scopes,
application_scopes
)
end
end
end
end
end

View file

@ -0,0 +1,38 @@
require 'doorkeeper/oauth/client_credentials/creator'
require 'doorkeeper/oauth/client_credentials/issuer'
require 'doorkeeper/oauth/client_credentials/validation'
module Doorkeeper
module OAuth
class ClientCredentialsRequest < BaseRequest
attr_accessor :server, :client, :original_scopes
attr_reader :response
attr_writer :issuer
alias_method :error_response, :response
delegate :error, to: :issuer
def issuer
@issuer ||= Issuer.new(server, Validation.new(server, self))
end
def initialize(server, client, parameters = {})
@client = client
@server = server
@response = nil
@original_scopes = parameters[:scope]
end
def access_token
issuer.token
end
private
def valid?
issuer.create(client, scopes)
end
end
end
end

View file

@ -0,0 +1,29 @@
module Doorkeeper
module OAuth
class CodeRequest
attr_accessor :pre_auth, :resource_owner, :client
def initialize(pre_auth, resource_owner)
@pre_auth = pre_auth
@client = pre_auth.client
@resource_owner = resource_owner
end
def authorize
if pre_auth.authorizable?
auth = Authorization::Code.new(pre_auth, resource_owner)
auth.issue_token
@response = CodeResponse.new pre_auth, auth
else
@response = ErrorResponse.from_request pre_auth
end
end
def deny
pre_auth.error = :access_denied
ErrorResponse.from_request pre_auth,
redirect_uri: pre_auth.redirect_uri
end
end
end
end

View file

@ -0,0 +1,39 @@
module Doorkeeper
module OAuth
class CodeResponse < BaseResponse
include OAuth::Helpers
attr_accessor :pre_auth, :auth, :response_on_fragment
def initialize(pre_auth, auth, options = {})
@pre_auth = pre_auth
@auth = auth
@response_on_fragment = options[:response_on_fragment]
end
def redirectable?
true
end
def redirect_uri
if URIChecker.native_uri? pre_auth.redirect_uri
auth.native_redirect
elsif response_on_fragment
Authorization::URIBuilder.uri_with_fragment(
pre_auth.redirect_uri,
access_token: auth.token.token,
token_type: auth.token.token_type,
expires_in: auth.token.expires_in_seconds,
state: pre_auth.state
)
else
Authorization::URIBuilder.uri_with_query(
pre_auth.redirect_uri,
code: auth.token.token,
state: pre_auth.state
)
end
end
end
end
end

View file

@ -0,0 +1,13 @@
module Doorkeeper
module OAuth
Error = Struct.new(:name, :state) do
def description
I18n.translate(
name,
scope: %i[doorkeeper errors messages],
default: :server_error
)
end
end
end
end

View file

@ -0,0 +1,65 @@
module Doorkeeper
module OAuth
class ErrorResponse < BaseResponse
include OAuth::Helpers
def self.from_request(request, attributes = {})
new(attributes.merge(name: request.error, state: request.try(:state)))
end
delegate :name, :description, :state, to: :@error
def initialize(attributes = {})
@error = OAuth::Error.new(*attributes.values_at(:name, :state))
@redirect_uri = attributes[:redirect_uri]
@response_on_fragment = attributes[:response_on_fragment]
end
def body
{
error: name,
error_description: description,
state: state
}.reject { |_, v| v.blank? }
end
def status
:unauthorized
end
def redirectable?
name != :invalid_redirect_uri && name != :invalid_client &&
!URIChecker.native_uri?(@redirect_uri)
end
def redirect_uri
if @response_on_fragment
Authorization::URIBuilder.uri_with_fragment @redirect_uri, body
else
Authorization::URIBuilder.uri_with_query @redirect_uri, body
end
end
def headers
{ 'Cache-Control' => 'no-store',
'Pragma' => 'no-cache',
'Content-Type' => 'application/json; charset=utf-8',
'WWW-Authenticate' => authenticate_info }
end
protected
delegate :realm, to: :configuration
def configuration
Doorkeeper.configuration
end
private
def authenticate_info
%(Bearer realm="#{realm}", error="#{name}", error_description="#{description}")
end
end
end
end

View file

@ -0,0 +1,29 @@
module Doorkeeper
module OAuth
class ForbiddenTokenResponse < ErrorResponse
def self.from_scopes(scopes, attributes = {})
new(attributes.merge(scopes: scopes))
end
def initialize(attributes = {})
super(attributes.merge(name: :invalid_scope, state: :forbidden))
@scopes = attributes[:scopes]
end
def status
:forbidden
end
def headers
headers = super
headers.delete 'WWW-Authenticate'
headers
end
def description
scope = { scope: %i[doorkeeper scopes] }
@description ||= @scopes.map { |r| I18n.translate r, scope }.join('\n')
end
end
end
end

View file

@ -0,0 +1,45 @@
module Doorkeeper
module OAuth
module Helpers
module ScopeChecker
class Validator
attr_reader :parsed_scopes, :scope_str
def initialize(scope_str, server_scopes, application_scopes)
@parsed_scopes = OAuth::Scopes.from_string(scope_str)
@scope_str = scope_str
@valid_scopes = valid_scopes(server_scopes, application_scopes)
end
def valid?
scope_str.present? &&
scope_str !~ /[\n\r\t]/ &&
@valid_scopes.has_scopes?(parsed_scopes)
end
def match?
valid? && parsed_scopes.has_scopes?(@valid_scopes)
end
private
def valid_scopes(server_scopes, application_scopes)
if application_scopes.present?
application_scopes
else
server_scopes
end
end
end
def self.valid?(scope_str, server_scopes, application_scopes = nil)
Validator.new(scope_str, server_scopes, application_scopes).valid?
end
def self.match?(scope_str, server_scopes, application_scopes = nil)
Validator.new(scope_str, server_scopes, application_scopes).match?
end
end
end
end
end

View file

@ -0,0 +1,13 @@
module Doorkeeper
module OAuth
module Helpers
module UniqueToken
def self.generate(options = {})
generator_method = options.delete(:generator) || SecureRandom.method(:hex)
token_size = options.delete(:size) || 32
generator_method.call(token_size)
end
end
end
end
end

View file

@ -0,0 +1,47 @@
module Doorkeeper
module OAuth
module Helpers
module URIChecker
def self.valid?(url)
uri = as_uri(url)
uri.fragment.nil? && !uri.host.nil? && !uri.scheme.nil?
rescue URI::InvalidURIError
false
end
def self.matches?(url, client_url)
url = as_uri(url)
client_url = as_uri(client_url)
if client_url.query.present?
return false unless query_matches?(url.query, client_url.query)
# Clear out queries so rest of URI can be tested. This allows query
# params to be in the request but order not mattering.
client_url.query = nil
end
url.query = nil
url == client_url
end
def self.valid_for_authorization?(url, client_url)
valid?(url) && client_url.split.any? { |other_url| matches?(url, other_url) }
end
def self.as_uri(url)
URI.parse(url)
end
def self.query_matches?(query, client_query)
return true if client_query.nil? && query.nil?
return false if client_query.nil? || query.nil?
# Will return true independent of query order
client_query.split('&').sort == query.split('&').sort
end
def self.native_uri?(url)
url == Doorkeeper.configuration.native_redirect_uri
end
end
end
end
end

View file

@ -0,0 +1,29 @@
module Doorkeeper
module OAuth
class InvalidTokenResponse < ErrorResponse
attr_reader :reason
def self.from_access_token(access_token, attributes = {})
reason = if access_token.try(:revoked?)
:revoked
elsif access_token.try(:expired?)
:expired
else
:unknown
end
new(attributes.merge(reason: reason))
end
def initialize(attributes = {})
super(attributes.merge(name: :invalid_token, state: :unauthorized))
@reason = attributes[:reason] || :unknown
end
def description
scope = { scope: %i[doorkeeper errors messages invalid_token] }
@description ||= I18n.translate @reason, scope
end
end
end
end

View file

@ -0,0 +1,42 @@
module Doorkeeper
module OAuth
class PasswordAccessTokenRequest < BaseRequest
include OAuth::Helpers
validate :client, error: :invalid_client
validate :resource_owner, error: :invalid_grant
validate :scopes, error: :invalid_scope
attr_accessor :server, :client, :resource_owner, :parameters,
:access_token
def initialize(server, client, resource_owner, parameters = {})
@server = server
@resource_owner = resource_owner
@client = client
@parameters = parameters
@original_scopes = parameters[:scope]
end
private
def before_successful_response
find_or_create_access_token(client, resource_owner.id, scopes, server)
super
end
def validate_scopes
return true unless @original_scopes.present?
ScopeChecker.valid? @original_scopes, server.scopes, client.try(:scopes)
end
def validate_resource_owner
!!resource_owner
end
def validate_client
!parameters[:client_id] || !!client
end
end
end
end

View file

@ -0,0 +1,66 @@
module Doorkeeper
module OAuth
class PreAuthorization
include Validations
validate :response_type, error: :unsupported_response_type
validate :client, error: :invalid_client
validate :scopes, error: :invalid_scope
validate :redirect_uri, error: :invalid_redirect_uri
attr_accessor :server, :client, :response_type, :redirect_uri, :state
attr_writer :scope
def initialize(server, client, attrs = {})
@server = server
@client = client
@response_type = attrs[:response_type]
@redirect_uri = attrs[:redirect_uri]
@scope = attrs[:scope]
@state = attrs[:state]
end
def authorizable?
valid?
end
def scopes
Scopes.from_string scope
end
def scope
@scope.presence || server.default_scopes.to_s
end
def error_response
OAuth::ErrorResponse.from_request(self)
end
private
def validate_response_type
server.authorization_response_types.include? response_type
end
def validate_client
client.present?
end
def validate_scopes
return true unless scope.present?
Helpers::ScopeChecker.valid?(
scope,
server.scopes,
client.application.scopes
)
end
# TODO: test uri should be matched against the client's one
def validate_redirect_uri
return false unless redirect_uri.present?
Helpers::URIChecker.native_uri?(redirect_uri) ||
Helpers::URIChecker.valid_for_authorization?(redirect_uri, client.redirect_uri)
end
end
end
end

View file

@ -0,0 +1,96 @@
module Doorkeeper
module OAuth
class RefreshTokenRequest < BaseRequest
include OAuth::Helpers
validate :token_presence, error: :invalid_request
validate :token, error: :invalid_grant
validate :client, error: :invalid_client
validate :client_match, error: :invalid_grant
validate :scope, error: :invalid_scope
attr_accessor :access_token, :client, :credentials, :refresh_token,
:server
def initialize(server, refresh_token, credentials, parameters = {})
@server = server
@refresh_token = refresh_token
@credentials = credentials
@original_scopes = parameters[:scope] || parameters[:scopes]
@refresh_token_parameter = parameters[:refresh_token]
if credentials
@client = Application.by_uid_and_secret credentials.uid,
credentials.secret
end
end
private
def before_successful_response
refresh_token.transaction do
refresh_token.lock!
raise Errors::InvalidTokenReuse if refresh_token.revoked?
refresh_token.revoke unless refresh_token_revoked_on_use?
create_access_token
end
super
end
def refresh_token_revoked_on_use?
Doorkeeper::AccessToken.refresh_token_revoked_on_use?
end
def default_scopes
refresh_token.scopes
end
def create_access_token
@access_token = AccessToken.create!(access_token_attributes)
end
def access_token_attributes
{
application_id: refresh_token.application_id,
resource_owner_id: refresh_token.resource_owner_id,
scopes: scopes.to_s,
expires_in: access_token_expires_in,
use_refresh_token: true
}.tap do |attributes|
if refresh_token_revoked_on_use?
attributes[:previous_refresh_token] = refresh_token.refresh_token
end
end
end
def access_token_expires_in
Authorization::Token.access_token_expires_in(server, client)
end
def validate_token_presence
refresh_token.present? || @refresh_token_parameter.present?
end
def validate_token
refresh_token.present? && !refresh_token.revoked?
end
def validate_client
!credentials || !!client
end
def validate_client_match
!client || refresh_token.application_id == client.id
end
def validate_scope
if @original_scopes.present?
ScopeChecker.valid?(@original_scopes, refresh_token.scopes)
else
true
end
end
end
end
end

View file

@ -0,0 +1,75 @@
module Doorkeeper
module OAuth
class Scopes
include Enumerable
include Comparable
def self.from_string(string)
string ||= ''
new.tap do |scope|
scope.add(*string.split)
end
end
def self.from_array(array)
new.tap do |scope|
scope.add(*array)
end
end
delegate :each, :empty?, to: :@scopes
def initialize
@scopes = []
end
def exists?(scope)
@scopes.include? scope.to_s
end
def add(*scopes)
@scopes.push(*scopes.map(&:to_s))
@scopes.uniq!
end
def all
@scopes
end
def to_s
@scopes.join(' ')
end
def has_scopes?(scopes)
scopes.all? { |s| exists?(s) }
end
def +(other)
self.class.from_array(all + to_array(other))
end
def <=>(other)
if other.respond_to?(:map)
map(&:to_s).sort <=> other.map(&:to_s).sort
else
super
end
end
def &(other)
self.class.from_array(all & to_array(other))
end
private
def to_array(other)
case other
when Scopes
other.all
else
other.to_a
end
end
end
end
end

View file

@ -0,0 +1,62 @@
module Doorkeeper
module OAuth
class Token
class << self
def from_request(request, *methods)
methods.inject(nil) do |credentials, method|
method = self.method(method) if method.is_a?(Symbol)
credentials = method.call(request)
break credentials unless credentials.blank?
end
end
def authenticate(request, *methods)
if (token = from_request(request, *methods))
access_token = AccessToken.by_token(token)
access_token.revoke_previous_refresh_token! if access_token
access_token
end
end
def from_access_token_param(request)
request.parameters[:access_token]
end
def from_bearer_param(request)
request.parameters[:bearer_token]
end
def from_bearer_authorization(request)
pattern = /^Bearer /i
header = request.authorization
token_from_header(header, pattern) if match?(header, pattern)
end
def from_basic_authorization(request)
pattern = /^Basic /i
header = request.authorization
token_from_basic_header(header, pattern) if match?(header, pattern)
end
private
def token_from_basic_header(header, pattern)
encoded_header = token_from_header(header, pattern)
decode_basic_credentials_token(encoded_header)
end
def decode_basic_credentials_token(encoded_header)
Base64.decode64(encoded_header).split(/:/, 2).first
end
def token_from_header(header, pattern)
header.gsub pattern, ''
end
def match?(header, pattern)
header && header.match(pattern)
end
end
end
end
end

View file

@ -0,0 +1,128 @@
module Doorkeeper
module OAuth
# RFC7662 OAuth 2.0 Token Introspection
#
# @see https://tools.ietf.org/html/rfc7662
class TokenIntrospection
attr_reader :server, :token
attr_reader :error
def initialize(server, token)
@server = server
@token = token
authorize!
end
def authorized?
@error.blank?
end
def to_json
active? ? success_response : failure_response
end
private
# If the protected resource uses OAuth 2.0 client credentials to
# authenticate to the introspection endpoint and its credentials are
# invalid, the authorization server responds with an HTTP 401
# (Unauthorized) as described in Section 5.2 of OAuth 2.0 [RFC6749].
#
# Endpoint must first validate the authentication.
# If the authentication is invalid, the endpoint should respond with
# an HTTP 401 status code and an invalid_client response.
#
# @see https://www.oauth.com/oauth2-servers/token-introspection-endpoint/
#
def authorize!
# Requested client authorization
if server.credentials
@error = :invalid_client unless authorized_client
else
# Requested bearer token authorization
@error = :invalid_request unless authorized_token
end
end
# Client Authentication
def authorized_client
@_authorized_client ||= server.credentials && server.client
end
# Bearer Token Authentication
def authorized_token
@_authorized_token ||=
OAuth::Token.authenticate(server.context.request, :from_bearer_authorization)
end
# 2.2. Introspection Response
def success_response
{
active: true,
scope: @token.scopes_string,
client_id: @token.try(:application).try(:uid),
token_type: @token.token_type,
exp: @token.expires_at.to_i,
iat: @token.created_at.to_i
}
end
# If the introspection call is properly authorized but the token is not
# active, does not exist on this server, or the protected resource is
# not allowed to introspect this particular token, then the
# authorization server MUST return an introspection response with the
# "active" field set to "false". Note that to avoid disclosing too
# much of the authorization server's state to a third party, the
# authorization server SHOULD NOT include any additional information
# about an inactive token, including why the token is inactive.
#
# @see https://tools.ietf.org/html/rfc7662 2.2. Introspection Response
#
def failure_response
{
active: false
}
end
# Boolean indicator of whether or not the presented token
# is currently active. The specifics of a token's "active" state
# will vary depending on the implementation of the authorization
# server and the information it keeps about its tokens, but a "true"
# value return for the "active" property will generally indicate
# that a given token has been issued by this authorization server,
# has not been revoked by the resource owner, and is within its
# given time window of validity (e.g., after its issuance time and
# before its expiration time).
#
# Any other error is considered an "inactive" token.
#
# * The token requested does not exist or is invalid
# * The token expired
# * The token was issued to a different client than is making this request
#
def active?
if authorized_client
valid_token? && authorized_for_client?
else
valid_token?
end
end
# Token can be valid only if it is not expired or revoked.
def valid_token?
@token.present? && @token.accessible?
end
# If token doesn't belong to some client, then it is public.
# Otherwise in it required for token to be connected to the same client.
def authorized_for_client?
if @token.application.present?
@token.application == authorized_client.application
else
true
end
end
end
end
end

View file

@ -0,0 +1,37 @@
module Doorkeeper
module OAuth
class TokenRequest
attr_accessor :pre_auth, :resource_owner
def initialize(pre_auth, resource_owner)
@pre_auth = pre_auth
@resource_owner = resource_owner
end
def authorize
if pre_auth.authorizable?
auth = Authorization::Token.new(pre_auth, resource_owner)
auth.issue_token
@response = CodeResponse.new pre_auth,
auth,
response_on_fragment: true
else
@response = error_response
end
end
def deny
pre_auth.error = :access_denied
error_response
end
private
def error_response
ErrorResponse.from_request pre_auth,
redirect_uri: pre_auth.redirect_uri,
response_on_fragment: true
end
end
end
end

View file

@ -0,0 +1,32 @@
module Doorkeeper
module OAuth
class TokenResponse
attr_accessor :token
def initialize(token)
@token = token
end
def body
{
'access_token' => token.token,
'token_type' => token.token_type,
'expires_in' => token.expires_in_seconds,
'refresh_token' => token.refresh_token,
'scope' => token.scopes_string,
'created_at' => token.created_at.to_i
}.reject { |_, value| value.blank? }
end
def status
:ok
end
def headers
{ 'Cache-Control' => 'no-store',
'Pragma' => 'no-cache',
'Content-Type' => 'application/json; charset=utf-8' }
end
end
end
end

View file

@ -0,0 +1,34 @@
require 'active_support/lazy_load_hooks'
module Doorkeeper
module Orm
module ActiveRecord
def self.initialize_models!
lazy_load do
require 'doorkeeper/orm/active_record/access_grant'
require 'doorkeeper/orm/active_record/access_token'
require 'doorkeeper/orm/active_record/application'
if Doorkeeper.configuration.active_record_options[:establish_connection]
[Doorkeeper::AccessGrant, Doorkeeper::AccessToken, Doorkeeper::Application].each do |model|
options = Doorkeeper.configuration.active_record_options[:establish_connection]
model.establish_connection(options)
end
end
end
end
def self.initialize_application_owner!
lazy_load do
require 'doorkeeper/models/concerns/ownership'
Doorkeeper::Application.send :include, Doorkeeper::Models::Ownership
end
end
def self.lazy_load(&block)
ActiveSupport.on_load(:active_record, {}, &block)
end
end
end
end

View file

@ -0,0 +1,34 @@
module Doorkeeper
class AccessGrant < ActiveRecord::Base
self.table_name = "#{table_name_prefix}oauth_access_grants#{table_name_suffix}".to_sym
include AccessGrantMixin
include ActiveModel::MassAssignmentSecurity if defined?(::ProtectedAttributes)
belongs_to_options = {
class_name: 'Doorkeeper::Application',
inverse_of: :access_grants
}
if defined?(ActiveRecord::Base) && ActiveRecord::VERSION::MAJOR >= 5
belongs_to_options[:optional] = true
end
belongs_to :application, belongs_to_options
validates :resource_owner_id, :application_id, :token, :expires_in, :redirect_uri, presence: true
validates :token, uniqueness: true
before_validation :generate_token, on: :create
private
# Generates token value with UniqueToken class.
#
# @return [String] token value
#
def generate_token
self.token = UniqueToken.generate
end
end
end

View file

@ -0,0 +1,47 @@
module Doorkeeper
class AccessToken < ActiveRecord::Base
self.table_name = "#{table_name_prefix}oauth_access_tokens#{table_name_suffix}".to_sym
include AccessTokenMixin
include ActiveModel::MassAssignmentSecurity if defined?(::ProtectedAttributes)
belongs_to_options = {
class_name: 'Doorkeeper::Application',
inverse_of: :access_tokens
}
if defined?(ActiveRecord::Base) && ActiveRecord::VERSION::MAJOR >= 5
belongs_to_options[:optional] = true
end
belongs_to :application, belongs_to_options
validates :token, presence: true, uniqueness: true
validates :refresh_token, uniqueness: true, if: :use_refresh_token?
# @attr_writer [Boolean, nil] use_refresh_token
# indicates the possibility of using refresh token
attr_writer :use_refresh_token
before_validation :generate_token, on: :create
before_validation :generate_refresh_token,
on: :create, if: :use_refresh_token?
# Searches for not revoked Access Tokens associated with the
# specific Resource Owner.
#
# @param resource_owner [ActiveRecord::Base]
# Resource Owner model instance
#
# @return [ActiveRecord::Relation]
# active Access Tokens for Resource Owner
#
def self.active_for(resource_owner)
where(resource_owner_id: resource_owner.id, revoked_at: nil)
end
def self.refresh_token_revoked_on_use?
column_names.include?('previous_refresh_token')
end
end
end

View file

@ -0,0 +1,44 @@
module Doorkeeper
class Application < ActiveRecord::Base
self.table_name = "#{table_name_prefix}oauth_applications#{table_name_suffix}".to_sym
include ApplicationMixin
include ActiveModel::MassAssignmentSecurity if defined?(::ProtectedAttributes)
has_many :access_grants, dependent: :delete_all, class_name: 'Doorkeeper::AccessGrant'
has_many :access_tokens, dependent: :delete_all, class_name: 'Doorkeeper::AccessToken'
validates :name, :secret, :uid, presence: true
validates :uid, uniqueness: true
validates :redirect_uri, redirect_uri: true
before_validation :generate_uid, :generate_secret, on: :create
has_many :authorized_tokens, -> { where(revoked_at: nil) }, class_name: 'AccessToken'
has_many :authorized_applications, through: :authorized_tokens, source: :application
# Returns Applications associated with active (not revoked) Access Tokens
# that are owned by the specific Resource Owner.
#
# @param resource_owner [ActiveRecord::Base]
# Resource Owner model instance
#
# @return [ActiveRecord::Relation]
# Applications authorized for the Resource Owner
#
def self.authorized_for(resource_owner)
resource_access_tokens = AccessToken.active_for(resource_owner)
where(id: resource_access_tokens.select(:application_id).distinct)
end
private
def generate_uid
self.uid = UniqueToken.generate if uid.blank?
end
def generate_secret
self.secret = UniqueToken.generate if secret.blank?
end
end
end

View file

@ -0,0 +1,78 @@
module Doorkeeper
module Rails
module Helpers
def doorkeeper_authorize!(*scopes)
@_doorkeeper_scopes = scopes.presence || Doorkeeper.configuration.default_scopes
unless valid_doorkeeper_token?
doorkeeper_render_error
end
end
def doorkeeper_unauthorized_render_options(**); end
def doorkeeper_forbidden_render_options(**); end
def valid_doorkeeper_token?
doorkeeper_token && doorkeeper_token.acceptable?(@_doorkeeper_scopes)
end
private
def doorkeeper_render_error
error = doorkeeper_error
headers.merge!(error.headers.reject { |k| k == "Content-Type" })
doorkeeper_render_error_with(error)
end
def doorkeeper_render_error_with(error)
options = doorkeeper_render_options(error) || {}
status = doorkeeper_status_for_error(
error, options.delete(:respond_not_found_when_forbidden)
)
if options.blank?
head status
else
options[:status] = status
options[:layout] = false if options[:layout].nil?
render options
end
end
def doorkeeper_error
if doorkeeper_invalid_token_response?
OAuth::InvalidTokenResponse.from_access_token(doorkeeper_token)
else
OAuth::ForbiddenTokenResponse.from_scopes(@_doorkeeper_scopes)
end
end
def doorkeeper_render_options(error)
if doorkeeper_invalid_token_response?
doorkeeper_unauthorized_render_options(error: error)
else
doorkeeper_forbidden_render_options(error: error)
end
end
def doorkeeper_status_for_error(error, respond_not_found_when_forbidden)
if respond_not_found_when_forbidden && error.status == :forbidden
:not_found
else
error.status
end
end
def doorkeeper_invalid_token_response?
!doorkeeper_token || !doorkeeper_token.accessible?
end
def doorkeeper_token
@_doorkeeper_token ||= OAuth::Token.authenticate(
request,
*Doorkeeper.configuration.access_token_methods
)
end
end
end
end

View file

@ -0,0 +1,90 @@
require 'doorkeeper/rails/routes/mapping'
require 'doorkeeper/rails/routes/mapper'
module Doorkeeper
module Rails
class Routes # :nodoc:
module Helper
def use_doorkeeper(options = {}, &block)
Doorkeeper::Rails::Routes.new(self, &block).generate_routes!(options)
end
end
def self.install!
ActionDispatch::Routing::Mapper.send :include, Doorkeeper::Rails::Routes::Helper
end
attr_reader :routes
def initialize(routes, &block)
@routes = routes
@mapping = Mapper.new.map(&block)
end
def generate_routes!(options)
routes.scope options[:scope] || 'oauth', as: 'oauth' do
map_route(:authorizations, :authorization_routes)
map_route(:tokens, :token_routes)
map_route(:tokens, :revoke_routes)
map_route(:tokens, :introspect_routes)
map_route(:applications, :application_routes)
map_route(:authorized_applications, :authorized_applications_routes)
map_route(:token_info, :token_info_routes)
end
end
private
def map_route(name, method)
send(method, @mapping[name]) unless @mapping.skipped?(name)
end
def authorization_routes(mapping)
routes.resource(
:authorization,
path: 'authorize',
only: %i[create destroy],
as: mapping[:as],
controller: mapping[:controllers]
) do
routes.get '/native', action: :show, on: :member
routes.get '/', action: :new, on: :member
end
end
def token_routes(mapping)
routes.resource(
:token,
path: 'token',
only: [:create], as: mapping[:as],
controller: mapping[:controllers]
)
end
def revoke_routes(mapping)
routes.post 'revoke', controller: mapping[:controllers], action: :revoke
end
def introspect_routes(mapping)
routes.post 'introspect', controller: mapping[:controllers], action: :introspect
end
def token_info_routes(mapping)
routes.resource(
:token_info,
path: 'token/info',
only: [:show], as: mapping[:as],
controller: mapping[:controllers]
)
end
def application_routes(mapping)
routes.resources :doorkeeper_applications, controller: mapping[:controllers], as: :applications, path: 'applications'
end
def authorized_applications_routes(mapping)
routes.resources :authorized_applications, only: %i[index destroy], controller: mapping[:controllers]
end
end
end
end

Some files were not shown because too many files have changed in this diff Show more