New upstream version 12.6.2
This commit is contained in:
parent
0985dbd954
commit
65b755ec3a
247 changed files with 13545 additions and 0 deletions
1
doorkeeper/.coveralls.yml
Normal file
1
doorkeeper/.coveralls.yml
Normal file
|
@ -0,0 +1 @@
|
||||||
|
service_name: travis-ci
|
25
doorkeeper/.github/ISSUE_TEMPLATE.md
vendored
Normal file
25
doorkeeper/.github/ISSUE_TEMPLATE.md
vendored
Normal 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**:
|
17
doorkeeper/.github/PULL_REQUEST_TEMPLATE.md
vendored
Normal file
17
doorkeeper/.github/PULL_REQUEST_TEMPLATE.md
vendored
Normal 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
19
doorkeeper/.gitignore
vendored
Normal 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
2
doorkeeper/.hound.yml
Normal file
|
@ -0,0 +1,2 @@
|
||||||
|
ruby:
|
||||||
|
config_file: .rubocop.yml
|
1
doorkeeper/.rspec
Normal file
1
doorkeeper/.rspec
Normal file
|
@ -0,0 +1 @@
|
||||||
|
--colour
|
13
doorkeeper/.rubocop.yml
Normal file
13
doorkeeper/.rubocop.yml
Normal 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
38
doorkeeper/.travis.yml
Normal 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
18
doorkeeper/Appraisals
Normal 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
|
46
doorkeeper/CODE_OF_CONDUCT.md
Normal file
46
doorkeeper/CODE_OF_CONDUCT.md
Normal 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/
|
47
doorkeeper/CONTRIBUTING.md
Normal file
47
doorkeeper/CONTRIBUTING.md
Normal 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
10
doorkeeper/Gemfile
Normal 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
20
doorkeeper/MIT-LICENSE
Normal 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
647
doorkeeper/NEWS.md
Normal 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 doesn’t 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 don’t 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 you’d 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
487
doorkeeper/README.md
Normal 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
10
doorkeeper/RELEASING.md
Normal 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
20
doorkeeper/Rakefile
Normal 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
15
doorkeeper/SECURITY.md
Normal 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!
|
|
@ -0,0 +1,10 @@
|
||||||
|
/*
|
||||||
|
*= require doorkeeper/bootstrap.min
|
||||||
|
*
|
||||||
|
*= require_self
|
||||||
|
*= require_tree .
|
||||||
|
*/
|
||||||
|
|
||||||
|
td {
|
||||||
|
vertical-align: middle !important;
|
||||||
|
}
|
64
doorkeeper/app/assets/stylesheets/doorkeeper/application.css
Normal file
64
doorkeeper/app/assets/stylesheets/doorkeeper/application.css
Normal 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;
|
||||||
|
}
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
93
doorkeeper/app/controllers/doorkeeper/tokens_controller.rb
Normal file
93
doorkeeper/app/controllers/doorkeeper/tokens_controller.rb
Normal 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
|
19
doorkeeper/app/helpers/doorkeeper/dashboard_helper.rb
Normal file
19
doorkeeper/app/helpers/doorkeeper/dashboard_helper.rb
Normal 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
|
44
doorkeeper/app/validators/redirect_uri_validator.rb
Normal file
44
doorkeeper/app/validators/redirect_uri_validator.rb
Normal 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
|
|
@ -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 %>
|
47
doorkeeper/app/views/doorkeeper/applications/_form.html.erb
Normal file
47
doorkeeper/app/views/doorkeeper/applications/_form.html.erb
Normal 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 %>
|
|
@ -0,0 +1,5 @@
|
||||||
|
<div class="page-header">
|
||||||
|
<h1><%= t('.title') %></h1>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<%= render 'form', application: @application %>
|
26
doorkeeper/app/views/doorkeeper/applications/index.html.erb
Normal file
26
doorkeeper/app/views/doorkeeper/applications/index.html.erb
Normal 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>
|
|
@ -0,0 +1,5 @@
|
||||||
|
<div class="page-header">
|
||||||
|
<h1><%= t('.title') %></h1>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<%= render 'form', application: @application %>
|
39
doorkeeper/app/views/doorkeeper/applications/show.html.erb
Normal file
39
doorkeeper/app/views/doorkeeper/applications/show.html.erb
Normal 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>
|
|
@ -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>
|
40
doorkeeper/app/views/doorkeeper/authorizations/new.html.erb
Normal file
40
doorkeeper/app/views/doorkeeper/authorizations/new.html.erb
Normal 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>
|
|
@ -0,0 +1,7 @@
|
||||||
|
<header class="page-header">
|
||||||
|
<h1><%= t('.title') %>:</h1>
|
||||||
|
</header>
|
||||||
|
|
||||||
|
<main role="main">
|
||||||
|
<code id="authorization_code"><%= params[:code] %></code>
|
||||||
|
</main>
|
|
@ -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 %>
|
|
@ -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>
|
37
doorkeeper/app/views/layouts/doorkeeper/admin.html.erb
Normal file
37
doorkeeper/app/views/layouts/doorkeeper/admin.html.erb
Normal 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>
|
23
doorkeeper/app/views/layouts/doorkeeper/application.html.erb
Normal file
23
doorkeeper/app/views/layouts/doorkeeper/application.html.erb
Normal 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>
|
122
doorkeeper/config/locales/en.yml
Normal file
122
doorkeeper/config/locales/en.yml
Normal 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'
|
30
doorkeeper/doorkeeper.gemspec
Normal file
30
doorkeeper/doorkeeper.gemspec
Normal 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
|
13
doorkeeper/gemfiles/rails_4_2.gemfile
Normal file
13
doorkeeper/gemfiles/rails_4_2.gemfile
Normal 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: "../"
|
12
doorkeeper/gemfiles/rails_5_0.gemfile
Normal file
12
doorkeeper/gemfiles/rails_5_0.gemfile
Normal 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: "../"
|
12
doorkeeper/gemfiles/rails_5_1.gemfile
Normal file
12
doorkeeper/gemfiles/rails_5_1.gemfile
Normal 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: "../"
|
12
doorkeeper/gemfiles/rails_5_2.gemfile
Normal file
12
doorkeeper/gemfiles/rails_5_2.gemfile
Normal 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: "../"
|
14
doorkeeper/gemfiles/rails_master.gemfile
Normal file
14
doorkeeper/gemfiles/rails_master.gemfile
Normal 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: "../"
|
75
doorkeeper/lib/doorkeeper.rb
Normal file
75
doorkeeper/lib/doorkeeper.rb
Normal 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
|
319
doorkeeper/lib/doorkeeper/config.rb
Normal file
319
doorkeeper/lib/doorkeeper/config.rb
Normal 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
|
27
doorkeeper/lib/doorkeeper/engine.rb
Normal file
27
doorkeeper/lib/doorkeeper/engine.rb
Normal 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
|
45
doorkeeper/lib/doorkeeper/errors.rb
Normal file
45
doorkeeper/lib/doorkeeper/errors.rb
Normal 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
|
17
doorkeeper/lib/doorkeeper/grape/authorization_decorator.rb
Normal file
17
doorkeeper/lib/doorkeeper/grape/authorization_decorator.rb
Normal 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
|
52
doorkeeper/lib/doorkeeper/grape/helpers.rb
Normal file
52
doorkeeper/lib/doorkeeper/grape/helpers.rb
Normal 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
|
56
doorkeeper/lib/doorkeeper/helpers/controller.rb
Normal file
56
doorkeeper/lib/doorkeeper/helpers/controller.rb
Normal 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
|
26
doorkeeper/lib/doorkeeper/models/access_grant_mixin.rb
Normal file
26
doorkeeper/lib/doorkeeper/models/access_grant_mixin.rb
Normal 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
|
246
doorkeeper/lib/doorkeeper/models/access_token_mixin.rb
Normal file
246
doorkeeper/lib/doorkeeper/models/access_token_mixin.rb
Normal 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
|
44
doorkeeper/lib/doorkeeper/models/application_mixin.rb
Normal file
44
doorkeeper/lib/doorkeeper/models/application_mixin.rb
Normal 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
|
13
doorkeeper/lib/doorkeeper/models/concerns/accessible.rb
Normal file
13
doorkeeper/lib/doorkeeper/models/concerns/accessible.rb
Normal 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
|
32
doorkeeper/lib/doorkeeper/models/concerns/expirable.rb
Normal file
32
doorkeeper/lib/doorkeeper/models/concerns/expirable.rb
Normal 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
|
13
doorkeeper/lib/doorkeeper/models/concerns/orderable.rb
Normal file
13
doorkeeper/lib/doorkeeper/models/concerns/orderable.rb
Normal 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
|
21
doorkeeper/lib/doorkeeper/models/concerns/ownership.rb
Normal file
21
doorkeeper/lib/doorkeeper/models/concerns/ownership.rb
Normal 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
|
48
doorkeeper/lib/doorkeeper/models/concerns/revocable.rb
Normal file
48
doorkeeper/lib/doorkeeper/models/concerns/revocable.rb
Normal 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
|
17
doorkeeper/lib/doorkeeper/models/concerns/scopes.rb
Normal file
17
doorkeeper/lib/doorkeeper/models/concerns/scopes.rb
Normal 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
|
32
doorkeeper/lib/doorkeeper/oauth/authorization/code.rb
Normal file
32
doorkeeper/lib/doorkeeper/oauth/authorization/code.rb
Normal 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
|
60
doorkeeper/lib/doorkeeper/oauth/authorization/token.rb
Normal file
60
doorkeeper/lib/doorkeeper/oauth/authorization/token.rb
Normal 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
|
31
doorkeeper/lib/doorkeeper/oauth/authorization/uri_builder.rb
Normal file
31
doorkeeper/lib/doorkeeper/oauth/authorization/uri_builder.rb
Normal 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
|
|
@ -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
|
55
doorkeeper/lib/doorkeeper/oauth/base_request.rb
Normal file
55
doorkeeper/lib/doorkeeper/oauth/base_request.rb
Normal 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
|
29
doorkeeper/lib/doorkeeper/oauth/base_response.rb
Normal file
29
doorkeeper/lib/doorkeeper/oauth/base_response.rb
Normal 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
|
29
doorkeeper/lib/doorkeeper/oauth/client.rb
Normal file
29
doorkeeper/lib/doorkeeper/oauth/client.rb
Normal 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
|
32
doorkeeper/lib/doorkeeper/oauth/client/credentials.rb
Normal file
32
doorkeeper/lib/doorkeeper/oauth/client/credentials.rb
Normal 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
|
|
@ -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
|
40
doorkeeper/lib/doorkeeper/oauth/client_credentials/issuer.rb
Normal file
40
doorkeeper/lib/doorkeeper/oauth/client_credentials/issuer.rb
Normal 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
|
|
@ -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
|
|
@ -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
|
29
doorkeeper/lib/doorkeeper/oauth/code_request.rb
Normal file
29
doorkeeper/lib/doorkeeper/oauth/code_request.rb
Normal 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
|
39
doorkeeper/lib/doorkeeper/oauth/code_response.rb
Normal file
39
doorkeeper/lib/doorkeeper/oauth/code_response.rb
Normal 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
|
13
doorkeeper/lib/doorkeeper/oauth/error.rb
Normal file
13
doorkeeper/lib/doorkeeper/oauth/error.rb
Normal 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
|
65
doorkeeper/lib/doorkeeper/oauth/error_response.rb
Normal file
65
doorkeeper/lib/doorkeeper/oauth/error_response.rb
Normal 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
|
29
doorkeeper/lib/doorkeeper/oauth/forbidden_token_response.rb
Normal file
29
doorkeeper/lib/doorkeeper/oauth/forbidden_token_response.rb
Normal 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
|
45
doorkeeper/lib/doorkeeper/oauth/helpers/scope_checker.rb
Normal file
45
doorkeeper/lib/doorkeeper/oauth/helpers/scope_checker.rb
Normal 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
|
13
doorkeeper/lib/doorkeeper/oauth/helpers/unique_token.rb
Normal file
13
doorkeeper/lib/doorkeeper/oauth/helpers/unique_token.rb
Normal 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
|
47
doorkeeper/lib/doorkeeper/oauth/helpers/uri_checker.rb
Normal file
47
doorkeeper/lib/doorkeeper/oauth/helpers/uri_checker.rb
Normal 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
|
29
doorkeeper/lib/doorkeeper/oauth/invalid_token_response.rb
Normal file
29
doorkeeper/lib/doorkeeper/oauth/invalid_token_response.rb
Normal 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
|
|
@ -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
|
66
doorkeeper/lib/doorkeeper/oauth/pre_authorization.rb
Normal file
66
doorkeeper/lib/doorkeeper/oauth/pre_authorization.rb
Normal 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
|
96
doorkeeper/lib/doorkeeper/oauth/refresh_token_request.rb
Normal file
96
doorkeeper/lib/doorkeeper/oauth/refresh_token_request.rb
Normal 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
|
75
doorkeeper/lib/doorkeeper/oauth/scopes.rb
Normal file
75
doorkeeper/lib/doorkeeper/oauth/scopes.rb
Normal 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
|
62
doorkeeper/lib/doorkeeper/oauth/token.rb
Normal file
62
doorkeeper/lib/doorkeeper/oauth/token.rb
Normal 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
|
128
doorkeeper/lib/doorkeeper/oauth/token_introspection.rb
Normal file
128
doorkeeper/lib/doorkeeper/oauth/token_introspection.rb
Normal 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
|
37
doorkeeper/lib/doorkeeper/oauth/token_request.rb
Normal file
37
doorkeeper/lib/doorkeeper/oauth/token_request.rb
Normal 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
|
32
doorkeeper/lib/doorkeeper/oauth/token_response.rb
Normal file
32
doorkeeper/lib/doorkeeper/oauth/token_response.rb
Normal 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
|
34
doorkeeper/lib/doorkeeper/orm/active_record.rb
Normal file
34
doorkeeper/lib/doorkeeper/orm/active_record.rb
Normal 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
|
34
doorkeeper/lib/doorkeeper/orm/active_record/access_grant.rb
Normal file
34
doorkeeper/lib/doorkeeper/orm/active_record/access_grant.rb
Normal 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
|
47
doorkeeper/lib/doorkeeper/orm/active_record/access_token.rb
Normal file
47
doorkeeper/lib/doorkeeper/orm/active_record/access_token.rb
Normal 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
|
44
doorkeeper/lib/doorkeeper/orm/active_record/application.rb
Normal file
44
doorkeeper/lib/doorkeeper/orm/active_record/application.rb
Normal 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
|
78
doorkeeper/lib/doorkeeper/rails/helpers.rb
Normal file
78
doorkeeper/lib/doorkeeper/rails/helpers.rb
Normal 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
|
90
doorkeeper/lib/doorkeeper/rails/routes.rb
Normal file
90
doorkeeper/lib/doorkeeper/rails/routes.rb
Normal 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
Loading…
Reference in a new issue