doorkeeper-5.0.2/ 0000755 0000041 0000041 00000000000 13410042500 013672 5 ustar www-data www-data doorkeeper-5.0.2/.travis.yml 0000644 0000041 0000041 00000001764 13410042500 016013 0 ustar www-data www-data cache: bundler
language: ruby
sudo: false
rvm:
- 2.1
- 2.2
- 2.3
- 2.4
- 2.5
- ruby-2.6.0-preview1
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:
fast_finish: true
# Run Danger only once
include:
- rvm: 2.5
gemfile: gemfiles/rails_5_2.gemfile
script: bundle exec danger
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
doorkeeper-5.0.2/.rspec 0000644 0000041 0000041 00000000011 13410042500 014777 0 ustar www-data www-data --colour
doorkeeper-5.0.2/README.md 0000644 0000041 0000041 00000047134 13410042500 015162 0 ustar www-data www-data # Doorkeeper — awesome OAuth 2 provider for your Rails / Grape app.
[](https://rubygems.org/gems/doorkeeper)
[](https://travis-ci.org/doorkeeper-gem/doorkeeper)
[](https://codeclimate.com/github/doorkeeper-gem/doorkeeper)
[](https://coveralls.io/github/doorkeeper-gem/doorkeeper?branch=master)
[](https://hakiri.io/github/doorkeeper-gem/doorkeeper/master)
[](https://houndci.com)
Doorkeeper is a gem (Rails engine) that makes it easy to introduce OAuth 2 provider
functionality to your Ruby on 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)
- [Proof Key for Code Exchange](https://tools.ietf.org/html/rfc7636)
- [OAuth 2.0 Token Revocation](http://tools.ietf.org/html/rfc7009)
- [OAuth 2.0 Token Introspection](https://tools.ietf.org/html/rfc7662)
See [list of tutorials](https://github.com/doorkeeper-gem/doorkeeper/wiki#how-tos--tutorials) in order to
learn how to use the gem or integrate it with other solutions / gems.
## 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)
- See [upgrade guides](https://github.com/doorkeeper-gem/doorkeeper/wiki/Migration-from-old-versions)
- 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
- [Installation](#installation)
- [Configuration](#configuration)
- [ORM](#orm)
- [Active Record](#active-record)
- [MongoDB](#mongodb)
- [Sequel](#sequel)
- [Couchbase](#couchbase)
- [API mode](#api-mode)
- [Routes](#routes)
- [Authenticating](#authenticating)
- [Internationalization (I18n)](#internationalization-i18n)
- [Customizing errors](#customizing-errors)
- [Rake Tasks](#rake-tasks)
- [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)
## 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
```
If you want to enable [PKCE flow] for mobile apps, you need to generate another
migration:
[PKCE flow]: https://tools.ietf.org/html/rfc7636
```sh
rails generate doorkeeper:pkce
```
Then run migrations:
```sh
rake db:migrate
```
Ensure to use non-confidential apps for pkce. PKCE is created, because
you cannot trust its apps' secret. So whatever app needs pkce: it means, it cannot
be a confidential app by design.
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
### API mode
By default Doorkeeper uses full Rails stack to provide all the OAuth 2 functionality
with additional features like administration area for managing applications. By the
way, starting from Doorkeeper 5 you can use API mode for your [API only Rails 5 applications](http://edgeguides.rubyonrails.org/api_app.html).
All you need is just to configure the gem to work in desired mode:
``` ruby
Doorkeeper.configure do
# ...
api_only
end
```
Keep in mind, that in this mode you will not be able to access `Applications` or
`Authorized Applications` controllers because they will be skipped. CSRF protections (which are otherwise enabled) will be skipped, and all the redirects will be returned as JSON response with corresponding locations.
### 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)
Doorkeeper support multiple languages. See language files in
[the I18n repository](https://github.com/doorkeeper-gem/doorkeeper-i18n).
### Customizing errors
If you don't want to use default Doorkeeper error responses you can raise and rescue it's
exceptions. All you need is to set configuration option `handle_auth_errors` to `:raise`.
In this case Doorkeeper will raise `Doorkeeper::Errors::TokenForbidden`,
`Doorkeeper::Errors::TokenExpired`, `Doorkeeper::Errors::TokenRevoked` or other exceptions
that you need to care about.
### Rake Tasks
If you are using `rake`, you can load rake tasks provided by this gem, by adding
the following line to your `Rakefile`:
```ruby
Doorkeeper::Rake.load_tasks
```
#### Cleaning up
By default Doorkeeper is retaining expired and revoked access tokens and grants.
This allows to keep an audit log of those records, but it also leads to the
corresponding tables to grow large over the lifetime of your application.
If you are concerned about those tables growing too large,
you can regularly run the following rake task to remove stale entries
from the database:
```rake
rake doorkeeper:db:cleanup
```
Note that this will remove tokens that are expired according to the configured TTL
in `Doorkeeper.configuration.access_token_expires_in`. The specific `expires_in`
value of each access token **is not considered**. The same is true for access
grants.
## 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! # Requires access token for all actions
# before_action -> { doorkeeper_authorize! :read, :write }
# 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 to 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 (before 5.0 release).
Starting from Doorkeeper 5.0 it returns 403 Forbidden if `admin_authenticator` option is not configured
by developers.
To change the protection rules of this 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).
By default, everybody can create application with any scopes. However,
you can enforce users to create applications only with configured scopes
(`default_scopes` and `optional_scopes` from the Doorkeeper initializer):
```ruby
# config/initializers/doorkeeper.rb
Doorkeeper.configure do
# ...
default_scopes :read, :write
optional_scopes :create, :update
enforce_configured_scopes
end
```
## 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 rake doorkeeper: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.
doorkeeper-5.0.2/gemfiles/ 0000755 0000041 0000041 00000000000 13410042500 015465 5 ustar www-data www-data doorkeeper-5.0.2/gemfiles/rails_4_2.gemfile 0000644 0000041 0000041 00000000575 13410042500 020604 0 ustar www-data www-data # 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: "../"
doorkeeper-5.0.2/gemfiles/rails_master.gemfile 0000644 0000041 0000041 00000001045 13410042500 021504 0 ustar www-data www-data # 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]
%w[rspec-core rspec-expectations rspec-mocks rspec-rails rspec-support].each do |lib|
gem lib, git: "https://github.com/rspec/#{lib}.git", branch: 'master'
end
gemspec path: "../"
doorkeeper-5.0.2/gemfiles/rails_5_2.gemfile 0000644 0000041 0000041 00000000520 13410042500 020573 0 ustar www-data www-data # This file was generated by Appraisal
source "https://rubygems.org"
gem "rails", "5.2.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: "../"
doorkeeper-5.0.2/gemfiles/rails_5_0.gemfile 0000644 0000041 0000041 00000000523 13410042500 020574 0 ustar www-data www-data # 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: "../"
doorkeeper-5.0.2/gemfiles/rails_5_1.gemfile 0000644 0000041 0000041 00000000523 13410042500 020575 0 ustar www-data www-data # 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: "../"
doorkeeper-5.0.2/spec/ 0000755 0000041 0000041 00000000000 13410042500 014624 5 ustar www-data www-data doorkeeper-5.0.2/spec/grape/ 0000755 0000041 0000041 00000000000 13410042500 015722 5 ustar www-data www-data doorkeeper-5.0.2/spec/grape/grape_integration_spec.rb 0000644 0000041 0000041 00000007034 13410042500 022766 0 ustar www-data www-data require 'spec_helper'
require 'grape'
require 'rack/test'
require 'doorkeeper/grape/helpers'
# Test Grape API application
module GrapeApp
class API < Grape::API
version 'v1', using: :path
format :json
prefix :api
helpers Doorkeeper::Grape::Helpers
resource :protected do
before do
doorkeeper_authorize!
end
desc 'Protected resource, requires token.'
get :status do
{ token: doorkeeper_token.token }
end
end
resource :protected_with_endpoint_scopes do
before do
doorkeeper_authorize!
end
desc 'Protected resource, requires token with scopes (defined in endpoint).'
get :status, scopes: [:admin] do
{ response: 'OK' }
end
end
resource :protected_with_helper_scopes do
before do
doorkeeper_authorize! :admin
end
desc 'Protected resource, requires token with scopes (defined in helper).'
get :status do
{ response: 'OK' }
end
end
resource :public do
desc "Public resource, no token required."
get :status do
{ response: 'OK' }
end
end
end
end
describe 'Grape integration' do
include Rack::Test::Methods
def app
GrapeApp::API
end
def json_body
JSON.parse(last_response.body)
end
let(:client) { FactoryBot.create(:application) }
let(:resource) { FactoryBot.create(:doorkeeper_testing_user, name: 'Joe', password: 'sekret') }
let(:access_token) { client_is_authorized(client, resource) }
context 'with valid Access Token' do
it 'successfully requests protected resource' do
get "api/v1/protected/status.json?access_token=#{access_token.token}"
expect(last_response).to be_successful
expect(json_body['token']).to eq(access_token.token)
end
it 'successfully requests protected resource with token that has required scopes (Grape endpoint)' do
access_token = client_is_authorized(client, resource, scopes: 'admin')
get "api/v1/protected_with_endpoint_scopes/status.json?access_token=#{access_token.token}"
expect(last_response).to be_successful
expect(json_body).to have_key('response')
end
it 'successfully requests protected resource with token that has required scopes (Doorkeeper helper)' do
access_token = client_is_authorized(client, resource, scopes: 'admin')
get "api/v1/protected_with_helper_scopes/status.json?access_token=#{access_token.token}"
expect(last_response).to be_successful
expect(json_body).to have_key('response')
end
it 'successfully requests public resource' do
get "api/v1/public/status.json"
expect(last_response).to be_successful
expect(json_body).to have_key('response')
end
end
context 'with invalid Access Token' do
it 'fails without access token' do
get "api/v1/protected/status.json"
expect(last_response).not_to be_successful
expect(json_body).to have_key('error')
end
it 'fails for access token without scopes' do
get "api/v1/protected_with_endpoint_scopes/status.json?access_token=#{access_token.token}"
expect(last_response).not_to be_successful
expect(json_body).to have_key('error')
end
it 'fails for access token with invalid scopes' do
access_token = client_is_authorized(client, resource, scopes: 'read write')
get "api/v1/protected_with_endpoint_scopes/status.json?access_token=#{access_token.token}"
expect(last_response).not_to be_successful
expect(json_body).to have_key('error')
end
end
end
doorkeeper-5.0.2/spec/factories.rb 0000644 0000041 0000041 00000001504 13410042500 017130 0 ustar www-data www-data FactoryBot.define do
factory :access_grant, class: Doorkeeper::AccessGrant do
sequence(:resource_owner_id) { |n| n }
application
redirect_uri { 'https://app.com/callback' }
expires_in { 100 }
scopes { 'public write' }
end
factory :access_token, class: Doorkeeper::AccessToken do
sequence(:resource_owner_id) { |n| n }
application
expires_in { 2.hours }
factory :clientless_access_token do
application { nil }
end
end
factory :application, class: Doorkeeper::Application do
sequence(:name) { |n| "Application #{n}" }
redirect_uri { 'https://app.com/callback' }
end
# do not name this factory :user, otherwise it will conflict with factories
# from applications that use doorkeeper factories in their own tests
factory :doorkeeper_testing_user, class: :user
end
doorkeeper-5.0.2/spec/requests/ 0000755 0000041 0000041 00000000000 13410042500 016477 5 ustar www-data www-data doorkeeper-5.0.2/spec/requests/protected_resources/ 0000755 0000041 0000041 00000000000 13410042500 022562 5 ustar www-data www-data doorkeeper-5.0.2/spec/requests/protected_resources/metal_spec.rb 0000644 0000041 0000041 00000000626 13410042500 025227 0 ustar www-data www-data require 'spec_helper'
describe 'ActionController::Metal API' do
before do
@client = FactoryBot.create(:application)
@resource = User.create!(name: 'Joe', password: 'sekret')
@token = client_is_authorized(@client, @resource)
end
it 'client requests protected resource with valid token' do
get "/metal.json?access_token=#{@token.token}"
should_have_json 'ok', true
end
end
doorkeeper-5.0.2/spec/requests/protected_resources/private_api_spec.rb 0000644 0000041 0000041 00000005352 13410042500 026431 0 ustar www-data www-data require 'spec_helper'
feature 'Private API' do
background do
@client = FactoryBot.create(:application)
@resource = User.create!(name: 'Joe', password: 'sekret')
@token = client_is_authorized(@client, @resource)
end
scenario 'client requests protected resource with valid token' do
with_access_token_header @token.token
visit '/full_protected_resources'
expect(page.body).to have_content('index')
end
scenario 'client requests protected resource with disabled header authentication' do
config_is_set :access_token_methods, [:from_access_token_param]
with_access_token_header @token.token
visit '/full_protected_resources'
response_status_should_be 401
end
scenario 'client attempts to request protected resource with invalid token' do
with_access_token_header 'invalid'
visit '/full_protected_resources'
response_status_should_be 401
end
scenario 'client attempts to request protected resource with expired token' do
@token.update_attribute :expires_in, -100 # expires token
with_access_token_header @token.token
visit '/full_protected_resources'
response_status_should_be 401
end
scenario 'client requests protected resource with permanent token' do
@token.update_attribute :expires_in, nil # never expires
with_access_token_header @token.token
visit '/full_protected_resources'
expect(page.body).to have_content('index')
end
scenario 'access token with no default scopes' do
Doorkeeper.configuration.instance_eval do
@default_scopes = Doorkeeper::OAuth::Scopes.from_array([:public])
@scopes = default_scopes + optional_scopes
end
@token.update_attribute :scopes, 'dummy'
with_access_token_header @token.token
visit '/full_protected_resources'
response_status_should_be 403
end
scenario 'access token with no allowed scopes' do
@token.update_attribute :scopes, nil
with_access_token_header @token.token
visit '/full_protected_resources/1.json'
response_status_should_be 403
end
scenario 'access token with one of allowed scopes' do
@token.update_attribute :scopes, 'admin'
with_access_token_header @token.token
visit '/full_protected_resources/1.json'
expect(page.body).to have_content('show')
end
scenario 'access token with another of allowed scopes' do
@token.update_attribute :scopes, 'write'
with_access_token_header @token.token
visit '/full_protected_resources/1.json'
expect(page.body).to have_content('show')
end
scenario 'access token with both allowed scopes' do
@token.update_attribute :scopes, 'write admin'
with_access_token_header @token.token
visit '/full_protected_resources/1.json'
expect(page.body).to have_content('show')
end
end
doorkeeper-5.0.2/spec/requests/flows/ 0000755 0000041 0000041 00000000000 13410042500 017631 5 ustar www-data www-data doorkeeper-5.0.2/spec/requests/flows/client_credentials_spec.rb 0000644 0000041 0000041 00000010304 13410042500 025021 0 ustar www-data www-data require 'spec_helper'
describe 'Client Credentials Request' do
let(:client) { FactoryBot.create :application }
context 'a valid request' do
it 'authorizes the client and returns the token response' do
headers = authorization client.uid, client.secret
params = { grant_type: 'client_credentials' }
post '/oauth/token', params: params, headers: headers
should_have_json 'access_token', Doorkeeper::AccessToken.first.token
should_have_json_within 'expires_in', Doorkeeper.configuration.access_token_expires_in, 1
should_not_have_json 'scope'
should_not_have_json 'refresh_token'
should_not_have_json 'error'
should_not_have_json 'error_description'
end
context 'with scopes' do
before do
optional_scopes_exist :write
default_scopes_exist :public
end
it 'adds the scope to the token an returns in the response' do
headers = authorization client.uid, client.secret
params = { grant_type: 'client_credentials', scope: 'write' }
post '/oauth/token', params: params, headers: headers
should_have_json 'access_token', Doorkeeper::AccessToken.first.token
should_have_json 'scope', 'write'
end
context 'that are default' do
it 'adds the scope to the token an returns in the response' do
headers = authorization client.uid, client.secret
params = { grant_type: 'client_credentials', scope: 'public' }
post '/oauth/token', params: params, headers: headers
should_have_json 'access_token', Doorkeeper::AccessToken.first.token
should_have_json 'scope', 'public'
end
end
context 'that are invalid' do
it 'does not authorize the client and returns the error' do
headers = authorization client.uid, client.secret
params = { grant_type: 'client_credentials', scope: 'random' }
post '/oauth/token', params: params, headers: headers
should_have_json 'error', 'invalid_scope'
should_have_json 'error_description', translated_error_message(:invalid_scope)
should_not_have_json 'access_token'
expect(response.status).to eq(401)
end
end
end
end
context 'when application scopes contain some of the default scopes and no scope is passed' do
before do
client.update_attributes(scopes: 'read write public')
end
it 'issues new token with one default scope that are present in application scopes' do
default_scopes_exist :public
headers = authorization client.uid, client.secret
params = { grant_type: 'client_credentials' }
expect do
post '/oauth/token', params: params, headers: headers
end.to change { Doorkeeper::AccessToken.count }.by(1)
token = Doorkeeper::AccessToken.first
expect(token.application_id).to eq client.id
should_have_json 'access_token', token.token
should_have_json 'scope', 'public'
end
it 'issues new token with multiple default scopes that are present in application scopes' do
default_scopes_exist :public, :read, :update
headers = authorization client.uid, client.secret
params = { grant_type: 'client_credentials' }
expect do
post '/oauth/token', params: params, headers: headers
end.to change { Doorkeeper::AccessToken.count }.by(1)
token = Doorkeeper::AccessToken.first
expect(token.application_id).to eq client.id
should_have_json 'access_token', token.token
should_have_json 'scope', 'public read'
end
end
context 'an invalid request' do
it 'does not authorize the client and returns the error' do
headers = {}
params = { grant_type: 'client_credentials' }
post '/oauth/token', params: params, headers: headers
should_have_json 'error', 'invalid_client'
should_have_json 'error_description', translated_error_message(:invalid_client)
should_not_have_json 'access_token'
expect(response.status).to eq(401)
end
end
def authorization(username, password)
credentials = ActionController::HttpAuthentication::Basic.encode_credentials username, password
{ 'HTTP_AUTHORIZATION' => credentials }
end
end
doorkeeper-5.0.2/spec/requests/flows/password_spec.rb 0000644 0000041 0000041 00000020571 13410042500 023037 0 ustar www-data www-data require 'spec_helper'
describe 'Resource Owner Password Credentials Flow not set up' do
before do
client_exists
create_resource_owner
end
context 'with valid user credentials' do
it 'does not issue new token' do
expect do
post password_token_endpoint_url(client: @client, resource_owner: @resource_owner)
end.to_not(change { Doorkeeper::AccessToken.count })
end
end
end
describe 'Resource Owner Password Credentials Flow' do
let(:client_attributes) { {} }
before do
config_is_set(:grant_flows, ["password"])
config_is_set(:resource_owner_from_credentials) { User.authenticate! params[:username], params[:password] }
client_exists(client_attributes)
create_resource_owner
end
context 'with valid user credentials' do
context "with non-confidential/public client" do
let(:client_attributes) { { confidential: false } }
context "when client_secret absent" do
it "should issue new token" do
expect do
post password_token_endpoint_url(client_id: @client.uid, resource_owner: @resource_owner)
end.to change { Doorkeeper::AccessToken.count }.by(1)
token = Doorkeeper::AccessToken.first
expect(token.application_id).to eq @client.id
should_have_json 'access_token', token.token
end
end
context "when client_secret present" do
it "should issue new token" do
expect do
post password_token_endpoint_url(client: @client, resource_owner: @resource_owner)
end.to change { Doorkeeper::AccessToken.count }.by(1)
token = Doorkeeper::AccessToken.first
expect(token.application_id).to eq @client.id
should_have_json 'access_token', token.token
end
context "when client_secret incorrect" do
it "should not issue new token" do
expect do
post password_token_endpoint_url(
client_id: @client.uid,
client_secret: 'foobar',
resource_owner: @resource_owner
)
end.not_to(change { Doorkeeper::AccessToken.count })
expect(response).not_to be_ok
end
end
end
end
context "with confidential/private client" do
it "should issue new token" do
expect do
post password_token_endpoint_url(client: @client, resource_owner: @resource_owner)
end.to change { Doorkeeper::AccessToken.count }.by(1)
token = Doorkeeper::AccessToken.first
expect(token.application_id).to eq @client.id
should_have_json 'access_token', token.token
end
context "when client_secret absent" do
it "should not issue new token" do
expect do
post password_token_endpoint_url(client_id: @client.uid, resource_owner: @resource_owner)
end.not_to(change { Doorkeeper::AccessToken.count })
expect(response).not_to be_ok
end
end
end
it 'should issue new token without client credentials' do
expect do
post password_token_endpoint_url(resource_owner: @resource_owner)
end.to(change { Doorkeeper::AccessToken.count }.by(1))
token = Doorkeeper::AccessToken.first
expect(token.application_id).to be_nil
should_have_json 'access_token', token.token
end
it 'should issue a refresh token if enabled' do
config_is_set(:refresh_token_enabled, true)
post password_token_endpoint_url(client: @client, resource_owner: @resource_owner)
token = Doorkeeper::AccessToken.first
should_have_json 'refresh_token', token.refresh_token
end
it 'should return the same token if it is still accessible' do
allow(Doorkeeper.configuration).to receive(:reuse_access_token).and_return(true)
client_is_authorized(@client, @resource_owner)
post password_token_endpoint_url(client: @client, resource_owner: @resource_owner)
expect(Doorkeeper::AccessToken.count).to be(1)
should_have_json 'access_token', Doorkeeper::AccessToken.first.token
end
context 'with valid, default scope' do
before do
default_scopes_exist :public
end
it 'should issue new token' do
expect do
post password_token_endpoint_url(client: @client, resource_owner: @resource_owner, scope: 'public')
end.to change { Doorkeeper::AccessToken.count }.by(1)
token = Doorkeeper::AccessToken.first
expect(token.application_id).to eq @client.id
should_have_json 'access_token', token.token
should_have_json 'scope', 'public'
end
end
end
context 'when application scopes are present and differs from configured default scopes and no scope is passed' do
before do
default_scopes_exist :public
@client.update_attributes(scopes: 'abc')
end
it 'issues new token without any scope' do
expect do
post password_token_endpoint_url(client: @client, resource_owner: @resource_owner)
end.to change { Doorkeeper::AccessToken.count }.by(1)
token = Doorkeeper::AccessToken.first
expect(token.application_id).to eq @client.id
expect(token.scopes).to be_empty
should_have_json 'access_token', token.token
should_not_have_json 'scope'
end
end
context 'when application scopes contain some of the default scopes and no scope is passed' do
before do
@client.update_attributes(scopes: 'read write public')
end
it 'issues new token with one default scope that are present in application scopes' do
default_scopes_exist :public, :admin
expect do
post password_token_endpoint_url(client: @client, resource_owner: @resource_owner)
end.to change { Doorkeeper::AccessToken.count }.by(1)
token = Doorkeeper::AccessToken.first
expect(token.application_id).to eq @client.id
should_have_json 'access_token', token.token
should_have_json 'scope', 'public'
end
it 'issues new token with multiple default scopes that are present in application scopes' do
default_scopes_exist :public, :read, :update
expect do
post password_token_endpoint_url(client: @client, resource_owner: @resource_owner)
end.to change { Doorkeeper::AccessToken.count }.by(1)
token = Doorkeeper::AccessToken.first
expect(token.application_id).to eq @client.id
should_have_json 'access_token', token.token
should_have_json 'scope', 'public read'
end
end
context 'with invalid scopes' do
subject do
post password_token_endpoint_url(client: @client,
resource_owner: @resource_owner,
scope: 'random')
end
it 'should not issue new token' do
expect { subject }.to_not(change { Doorkeeper::AccessToken.count })
end
it 'should return invalid_scope error' do
subject
should_have_json 'error', 'invalid_scope'
should_have_json 'error_description', translated_error_message(:invalid_scope)
should_not_have_json 'access_token'
expect(response.status).to eq(401)
end
end
context 'with invalid user credentials' do
it 'should not issue new token with bad password' do
expect do
post password_token_endpoint_url(client: @client,
resource_owner_username: @resource_owner.name,
resource_owner_password: 'wrongpassword')
end.to_not(change { Doorkeeper::AccessToken.count })
end
it 'should not issue new token without credentials' do
expect do
post password_token_endpoint_url(client: @client)
end.to_not(change { Doorkeeper::AccessToken.count })
end
end
context 'with invalid confidential client credentials' do
it 'should not issue new token with bad client credentials' do
expect do
post password_token_endpoint_url(client_id: @client.uid,
client_secret: 'bad_secret',
resource_owner: @resource_owner)
end.to_not(change { Doorkeeper::AccessToken.count })
end
end
context 'with invalid public client id' do
it 'should not issue new token with bad client id' do
expect do
post password_token_endpoint_url(client_id: 'bad_id', resource_owner: @resource_owner)
end.to_not(change { Doorkeeper::AccessToken.count })
end
end
end
doorkeeper-5.0.2/spec/requests/flows/skip_authorization_spec.rb 0000644 0000041 0000041 00000004644 13410042500 025126 0 ustar www-data www-data require 'spec_helper'
feature 'Skip authorization form' do
background do
config_is_set(:authenticate_resource_owner) { User.first || redirect_to('/sign_in') }
client_exists
default_scopes_exist :public
optional_scopes_exist :write
end
context 'for previously authorized clients' do
background do
create_resource_owner
sign_in
end
scenario 'skips the authorization and return a new grant code' do
client_is_authorized(@client, @resource_owner, scopes: "public")
visit authorization_endpoint_url(client: @client, scope: "public")
i_should_not_see "Authorize"
client_should_be_authorized @client
i_should_be_on_client_callback @client
url_should_have_param "code", Doorkeeper::AccessGrant.first.token
end
scenario "skips the authorization if other scopes are not requested" do
client_exists scopes: "public read write"
client_is_authorized(@client, @resource_owner, scopes: "public")
visit authorization_endpoint_url(client: @client, scope: "public")
i_should_not_see "Authorize"
client_should_be_authorized @client
i_should_be_on_client_callback @client
url_should_have_param "code", Doorkeeper::AccessGrant.first.token
end
scenario 'does not skip authorization when scopes differ (new request has fewer scopes)' do
client_is_authorized(@client, @resource_owner, scopes: 'public write')
visit authorization_endpoint_url(client: @client, scope: 'public')
i_should_see 'Authorize'
end
scenario 'does not skip authorization when scopes differ (new request has more scopes)' do
client_is_authorized(@client, @resource_owner, scopes: 'public write')
visit authorization_endpoint_url(client: @client, scopes: 'public write email')
i_should_see 'Authorize'
end
scenario 'creates grant with new scope when scopes differ' do
client_is_authorized(@client, @resource_owner, scopes: 'public write')
visit authorization_endpoint_url(client: @client, scope: 'public')
click_on 'Authorize'
access_grant_should_have_scopes :public
end
scenario 'creates grant with new scope when scopes are greater' do
client_is_authorized(@client, @resource_owner, scopes: 'public')
visit authorization_endpoint_url(client: @client, scope: 'public write')
click_on 'Authorize'
access_grant_should_have_scopes :public, :write
end
end
end
doorkeeper-5.0.2/spec/requests/flows/implicit_grant_errors_spec.rb 0000644 0000041 0000041 00000001742 13410042500 025575 0 ustar www-data www-data require 'spec_helper'
feature 'Implicit Grant Flow Errors' do
background do
config_is_set(:authenticate_resource_owner) { User.first || redirect_to('/sign_in') }
config_is_set(:grant_flows, ["implicit"])
client_exists
create_resource_owner
sign_in
end
after do
access_token_should_not_exist
end
[
%i[client_id invalid_client],
%i[redirect_uri invalid_redirect_uri]
].each do |error|
scenario "displays #{error.last} error for invalid #{error.first}" do
visit authorization_endpoint_url(client: @client, error.first => 'invalid', response_type: 'token')
i_should_not_see 'Authorize'
i_should_see_translated_error_message error.last
end
scenario "displays #{error.last} error when #{error.first} is missing" do
visit authorization_endpoint_url(client: @client, error.first => '', response_type: 'token')
i_should_not_see 'Authorize'
i_should_see_translated_error_message error.last
end
end
end
doorkeeper-5.0.2/spec/requests/flows/authorization_code_spec.rb 0000644 0000041 0000041 00000034537 13410042500 025076 0 ustar www-data www-data require 'spec_helper'
feature 'Authorization Code Flow' do
background do
config_is_set(:authenticate_resource_owner) { User.first || redirect_to('/sign_in') }
client_exists
create_resource_owner
sign_in
end
scenario 'resource owner authorizes the client' do
visit authorization_endpoint_url(client: @client)
click_on 'Authorize'
access_grant_should_exist_for(@client, @resource_owner)
i_should_be_on_client_callback(@client)
url_should_have_param('code', Doorkeeper::AccessGrant.first.token)
url_should_not_have_param('state')
url_should_not_have_param('error')
end
scenario 'resource owner authorizes using test url' do
@client.redirect_uri = Doorkeeper.configuration.native_redirect_uri
@client.save!
visit authorization_endpoint_url(client: @client)
click_on 'Authorize'
access_grant_should_exist_for(@client, @resource_owner)
url_should_have_param('code', Doorkeeper::AccessGrant.first.token)
i_should_see 'Authorization code:'
i_should_see Doorkeeper::AccessGrant.first.token
end
scenario 'resource owner authorizes the client with state parameter set' do
visit authorization_endpoint_url(client: @client, state: 'return-me')
click_on 'Authorize'
url_should_have_param('code', Doorkeeper::AccessGrant.first.token)
url_should_have_param('state', 'return-me')
url_should_not_have_param('code_challenge_method')
end
scenario 'resource owner requests an access token with authorization code' do
visit authorization_endpoint_url(client: @client)
click_on 'Authorize'
authorization_code = Doorkeeper::AccessGrant.first.token
create_access_token authorization_code, @client
access_token_should_exist_for(@client, @resource_owner)
should_not_have_json 'error'
should_have_json 'access_token', Doorkeeper::AccessToken.first.token
should_have_json 'token_type', 'Bearer'
should_have_json_within 'expires_in', Doorkeeper::AccessToken.first.expires_in, 1
end
scenario 'resource owner requests an access token with authorization code but without secret' do
visit authorization_endpoint_url(client: @client)
click_on 'Authorize'
authorization_code = Doorkeeper::AccessGrant.first.token
page.driver.post token_endpoint_url(code: authorization_code, client_id: @client.uid,
redirect_uri: @client.redirect_uri)
expect(Doorkeeper::AccessToken.count).to be_zero
should_have_json 'error', 'invalid_client'
end
scenario 'silently authorizes if matching token exists' do
default_scopes_exist :public, :write
access_token_exists application: @client,
expires_in: -100, # even expired token
resource_owner_id: @resource_owner.id,
scopes: 'public write'
visit authorization_endpoint_url(client: @client, scope: 'public write')
response_status_should_be 200
i_should_not_see 'Authorize'
end
context 'with PKCE' do
context 'plain' do
let(:code_challenge) { 'a45a9fea-0676-477e-95b1-a40f72ac3cfb' }
let(:code_verifier) { 'a45a9fea-0676-477e-95b1-a40f72ac3cfb' }
scenario 'resource owner authorizes the client with code_challenge parameter set' do
visit authorization_endpoint_url(
client: @client,
code_challenge: code_challenge,
code_challenge_method: 'plain'
)
click_on 'Authorize'
url_should_have_param('code', Doorkeeper::AccessGrant.first.token)
url_should_not_have_param('code_challenge_method')
url_should_not_have_param('code_challenge')
end
scenario 'mobile app requests an access token with authorization code but not pkce token' do
visit authorization_endpoint_url(client: @client)
click_on 'Authorize'
authorization_code = current_params['code']
create_access_token authorization_code, @client, code_verifier
should_have_json 'error', 'invalid_grant'
end
scenario 'mobile app requests an access token with authorization code and plain code challenge method' do
visit authorization_endpoint_url(
client: @client,
code_challenge: code_challenge,
code_challenge_method: 'plain'
)
click_on 'Authorize'
authorization_code = current_params['code']
create_access_token authorization_code, @client, code_verifier
access_token_should_exist_for(@client, @resource_owner)
should_not_have_json 'error'
should_have_json 'access_token', Doorkeeper::AccessToken.first.token
should_have_json 'token_type', 'Bearer'
should_have_json_within 'expires_in', Doorkeeper::AccessToken.first.expires_in, 1
end
scenario 'mobile app requests an access token with authorization code and code_challenge' do
visit authorization_endpoint_url(client: @client,
code_challenge: code_verifier,
code_challenge_method: 'plain')
click_on 'Authorize'
authorization_code = current_params['code']
create_access_token authorization_code, @client, code_verifier: nil
should_not_have_json 'access_token'
should_have_json 'error', 'invalid_grant'
end
end
context 's256' do
let(:code_challenge) { 'Oz733NtQ0rJP8b04fgZMJMwprn6Iw8sMCT_9bR1q4tA' }
let(:code_verifier) { 'a45a9fea-0676-477e-95b1-a40f72ac3cfb' }
scenario 'resource owner authorizes the client with code_challenge parameter set' do
visit authorization_endpoint_url(
client: @client,
code_challenge: code_challenge,
code_challenge_method: 'S256'
)
click_on 'Authorize'
url_should_have_param('code', Doorkeeper::AccessGrant.first.token)
url_should_not_have_param('code_challenge_method')
url_should_not_have_param('code_challenge')
end
scenario 'mobile app requests an access token with authorization code and S256 code challenge method' do
visit authorization_endpoint_url(
client: @client,
code_challenge: code_challenge,
code_challenge_method: 'S256'
)
click_on 'Authorize'
authorization_code = current_params['code']
create_access_token authorization_code, @client, code_verifier
access_token_should_exist_for(@client, @resource_owner)
should_not_have_json 'error'
should_have_json 'access_token', Doorkeeper::AccessToken.first.token
should_have_json 'token_type', 'Bearer'
should_have_json_within 'expires_in', Doorkeeper::AccessToken.first.expires_in, 1
end
scenario 'mobile app requests an access token with authorization code and without code_verifier' do
visit authorization_endpoint_url(
client: @client,
code_challenge: code_challenge,
code_challenge_method: 'S256'
)
click_on 'Authorize'
authorization_code = current_params['code']
create_access_token authorization_code, @client
should_have_json 'error', 'invalid_request'
should_not_have_json 'access_token'
end
scenario 'mobile app requests an access token with authorization code and without secret' do
visit authorization_endpoint_url(
client: @client,
code_challenge: code_challenge,
code_challenge_method: 'S256'
)
click_on 'Authorize'
authorization_code = current_params['code']
page.driver.post token_endpoint_url(code: authorization_code, client_id: @client.uid,
redirect_uri: @client.redirect_uri, code_verifier: code_verifier)
should_have_json 'error', 'invalid_client'
should_not_have_json 'access_token'
end
scenario 'mobile app requests an access token with authorization code and without secret but is marked as not confidential' do
@client.update_attribute :confidential, false
visit authorization_endpoint_url(client: @client, code_challenge: code_challenge, code_challenge_method: 'S256')
click_on 'Authorize'
authorization_code = current_params['code']
page.driver.post token_endpoint_url(
code: authorization_code,
client_id: @client.uid,
redirect_uri: @client.redirect_uri,
code_verifier: code_verifier
)
should_not_have_json 'error'
should_have_json 'access_token', Doorkeeper::AccessToken.first.token
should_have_json 'token_type', 'Bearer'
should_have_json_within 'expires_in', Doorkeeper::AccessToken.first.expires_in, 1
end
scenario 'mobile app requests an access token with authorization code but no code verifier' do
visit authorization_endpoint_url(
client: @client,
code_challenge: code_challenge,
code_challenge_method: 'S256'
)
click_on 'Authorize'
authorization_code = current_params['code']
create_access_token authorization_code, @client
should_not_have_json 'access_token'
should_have_json 'error', 'invalid_request'
end
scenario 'mobile app requests an access token with authorization code with wrong verifier' do
visit authorization_endpoint_url(
client: @client,
code_challenge: code_challenge,
code_challenge_method: 'S256'
)
click_on 'Authorize'
authorization_code = current_params['code']
create_access_token authorization_code, @client, 'incorrect-code-verifier'
should_not_have_json 'access_token'
should_have_json 'error', 'invalid_grant'
end
scenario 'code_challenge_mehthod in token request is totally ignored' do
visit authorization_endpoint_url(
client: @client,
code_challenge: code_challenge,
code_challenge_method: 'S256'
)
click_on 'Authorize'
authorization_code = current_params['code']
page.driver.post token_endpoint_url(
code: authorization_code,
client: @client,
code_verifier: code_challenge,
code_challenge_method: 'plain'
)
should_not_have_json 'access_token'
should_have_json 'error', 'invalid_grant'
end
scenario 'expects to set code_challenge_method explicitely without fallback' do
visit authorization_endpoint_url(client: @client, code_challenge: code_challenge)
expect(page).to have_content('The code challenge method must be plain or S256.')
end
end
end
context 'when application scopes are present and no scope is passed' do
background do
@client.update_attributes(scopes: 'public write read')
end
scenario 'access grant has no scope' do
default_scopes_exist :admin
visit authorization_endpoint_url(client: @client)
click_on 'Authorize'
access_grant_should_exist_for(@client, @resource_owner)
grant = Doorkeeper::AccessGrant.first
expect(grant.scopes).to be_empty
end
scenario 'access grant have scopes which are common in application scopees and default scopes' do
default_scopes_exist :public, :write
visit authorization_endpoint_url(client: @client)
click_on 'Authorize'
access_grant_should_exist_for(@client, @resource_owner)
access_grant_should_have_scopes :public, :write
end
end
context 'with scopes' do
background do
default_scopes_exist :public
optional_scopes_exist :write
end
scenario 'resource owner authorizes the client with default scopes' do
visit authorization_endpoint_url(client: @client)
click_on 'Authorize'
access_grant_should_exist_for(@client, @resource_owner)
access_grant_should_have_scopes :public
end
scenario 'resource owner authorizes the client with required scopes' do
visit authorization_endpoint_url(client: @client, scope: 'public write')
click_on 'Authorize'
access_grant_should_have_scopes :public, :write
end
scenario 'resource owner authorizes the client with required scopes (without defaults)' do
visit authorization_endpoint_url(client: @client, scope: 'write')
click_on 'Authorize'
access_grant_should_have_scopes :write
end
scenario 'new access token matches required scopes' do
visit authorization_endpoint_url(client: @client, scope: 'public write')
click_on 'Authorize'
authorization_code = Doorkeeper::AccessGrant.first.token
create_access_token authorization_code, @client
access_token_should_exist_for(@client, @resource_owner)
access_token_should_have_scopes :public, :write
end
scenario 'returns new token if scopes have changed' do
client_is_authorized(@client, @resource_owner, scopes: 'public write')
visit authorization_endpoint_url(client: @client, scope: 'public')
click_on 'Authorize'
authorization_code = Doorkeeper::AccessGrant.first.token
create_access_token authorization_code, @client
expect(Doorkeeper::AccessToken.count).to be(2)
should_have_json 'access_token', Doorkeeper::AccessToken.last.token
end
scenario 'resource owner authorizes the client with extra scopes' do
client_is_authorized(@client, @resource_owner, scopes: 'public')
visit authorization_endpoint_url(client: @client, scope: 'public write')
click_on 'Authorize'
authorization_code = Doorkeeper::AccessGrant.first.token
create_access_token authorization_code, @client
expect(Doorkeeper::AccessToken.count).to be(2)
should_have_json 'access_token', Doorkeeper::AccessToken.last.token
access_token_should_have_scopes :public, :write
end
end
end
describe 'Authorization Code Flow' do
before do
Doorkeeper.configure do
orm DOORKEEPER_ORM
use_refresh_token
end
client_exists
end
context 'issuing a refresh token' do
before do
authorization_code_exists application: @client
end
it 'second of simultaneous client requests get an error for revoked acccess token' do
authorization_code = Doorkeeper::AccessGrant.first.token
allow_any_instance_of(Doorkeeper::AccessGrant)
.to receive(:revoked?).and_return(false, true)
post token_endpoint_url(code: authorization_code, client: @client)
should_not_have_json 'access_token'
should_have_json 'error', 'invalid_grant'
end
end
end
doorkeeper-5.0.2/spec/requests/flows/authorization_code_errors_spec.rb 0000644 0000041 0000041 00000004547 13410042500 026470 0 ustar www-data www-data require 'spec_helper'
feature 'Authorization Code Flow Errors' do
let(:client_params) { {} }
background do
config_is_set(:authenticate_resource_owner) { User.first || redirect_to('/sign_in') }
client_exists client_params
create_resource_owner
sign_in
end
after do
access_grant_should_not_exist
end
context "with a client trying to xss resource owner" do
let(:client_name) { "
XSS
" }
let(:client_params) { { name: client_name } }
scenario "resource owner visit authorization endpoint" do
visit authorization_endpoint_url(client: @client)
expect(page).not_to have_css("#xss")
end
end
context 'when access was denied' do
scenario 'redirects with error' do
visit authorization_endpoint_url(client: @client)
click_on 'Deny'
i_should_be_on_client_callback @client
url_should_not_have_param 'code'
url_should_have_param 'error', 'access_denied'
url_should_have_param 'error_description', translated_error_message(:access_denied)
end
scenario 'redirects with state parameter' do
visit authorization_endpoint_url(client: @client, state: 'return-this')
click_on 'Deny'
i_should_be_on_client_callback @client
url_should_not_have_param 'code'
url_should_have_param 'state', 'return-this'
end
end
end
describe 'Authorization Code Flow Errors', 'after authorization' do
before do
client_exists
authorization_code_exists application: @client
end
it 'returns :invalid_grant error when posting an already revoked grant code' do
# First successful request
post token_endpoint_url(code: @authorization.token, client: @client)
# Second attempt with same token
expect do
post token_endpoint_url(code: @authorization.token, client: @client)
end.to_not(change { Doorkeeper::AccessToken.count })
should_not_have_json 'access_token'
should_have_json 'error', 'invalid_grant'
should_have_json 'error_description', translated_error_message('invalid_grant')
end
it 'returns :invalid_grant error for invalid grant code' do
post token_endpoint_url(code: 'invalid', client: @client)
access_token_should_not_exist
should_not_have_json 'access_token'
should_have_json 'error', 'invalid_grant'
should_have_json 'error_description', translated_error_message('invalid_grant')
end
end
doorkeeper-5.0.2/spec/requests/flows/refresh_token_spec.rb 0000644 0000041 0000041 00000016200 13410042500 024025 0 ustar www-data www-data require 'spec_helper'
describe 'Refresh Token Flow' do
before do
Doorkeeper.configure do
orm DOORKEEPER_ORM
use_refresh_token
end
client_exists
end
context 'issuing a refresh token' do
before do
authorization_code_exists application: @client
end
it 'client gets the refresh token and refreshes it' do
post token_endpoint_url(code: @authorization.token, client: @client)
token = Doorkeeper::AccessToken.first
should_have_json 'access_token', token.token
should_have_json 'refresh_token', token.refresh_token
expect(@authorization.reload).to be_revoked
post refresh_token_endpoint_url(client: @client, refresh_token: token.refresh_token)
new_token = Doorkeeper::AccessToken.last
should_have_json 'access_token', new_token.token
should_have_json 'refresh_token', new_token.refresh_token
expect(token.token).not_to eq(new_token.token)
expect(token.refresh_token).not_to eq(new_token.refresh_token)
end
end
context 'refreshing the token' do
before do
@token = FactoryBot.create(
:access_token,
application: @client,
resource_owner_id: 1,
use_refresh_token: true
)
end
context "refresh_token revoked on use" do
it 'client request a token with refresh token' do
post refresh_token_endpoint_url(
client: @client, refresh_token: @token.refresh_token
)
should_have_json(
'refresh_token', Doorkeeper::AccessToken.last.refresh_token
)
expect(@token.reload).not_to be_revoked
end
it 'client request a token with expired access token' do
@token.update_attribute :expires_in, -100
post refresh_token_endpoint_url(
client: @client, refresh_token: @token.refresh_token
)
should_have_json(
'refresh_token', Doorkeeper::AccessToken.last.refresh_token
)
expect(@token.reload).not_to be_revoked
end
end
context "refresh_token revoked on refresh_token request" do
before do
allow(Doorkeeper::AccessToken).to receive(:refresh_token_revoked_on_use?).and_return(false)
end
it 'client request a token with refresh token' do
post refresh_token_endpoint_url(
client: @client, refresh_token: @token.refresh_token
)
should_have_json(
'refresh_token', Doorkeeper::AccessToken.last.refresh_token
)
expect(@token.reload).to be_revoked
end
it 'client request a token with expired access token' do
@token.update_attribute :expires_in, -100
post refresh_token_endpoint_url(
client: @client, refresh_token: @token.refresh_token
)
should_have_json(
'refresh_token', Doorkeeper::AccessToken.last.refresh_token
)
expect(@token.reload).to be_revoked
end
end
context "public & private clients" do
let(:public_client) do
FactoryBot.create(
:application,
confidential: false
)
end
let(:token_for_private_client) do
FactoryBot.create(
:access_token,
application: @client,
resource_owner_id: 1,
use_refresh_token: true
)
end
let(:token_for_public_client) do
FactoryBot.create(
:access_token,
application: public_client,
resource_owner_id: 1,
use_refresh_token: true
)
end
it 'issues a new token without client_secret when refresh token was issued to a public client' do
post refresh_token_endpoint_url(
client_id: public_client.uid,
refresh_token: token_for_public_client.refresh_token
)
new_token = Doorkeeper::AccessToken.last
should_have_json 'access_token', new_token.token
should_have_json 'refresh_token', new_token.refresh_token
end
it 'returns an error without credentials' do
post refresh_token_endpoint_url(refresh_token: token_for_private_client.refresh_token)
should_not_have_json 'refresh_token'
should_have_json 'error', 'invalid_grant'
end
it 'returns an error with wrong credentials' do
post refresh_token_endpoint_url(
client_id: '1',
client_secret: '1',
refresh_token: token_for_private_client.refresh_token
)
should_not_have_json 'refresh_token'
should_have_json 'error', 'invalid_client'
end
end
it 'client gets an error for invalid refresh token' do
post refresh_token_endpoint_url(client: @client, refresh_token: 'invalid')
should_not_have_json 'refresh_token'
should_have_json 'error', 'invalid_grant'
end
it 'client gets an error for revoked access token' do
@token.revoke
post refresh_token_endpoint_url(client: @client, refresh_token: @token.refresh_token)
should_not_have_json 'refresh_token'
should_have_json 'error', 'invalid_grant'
end
it 'second of simultaneous client requests get an error for revoked access token' do
allow_any_instance_of(Doorkeeper::AccessToken).to receive(:revoked?).and_return(false, true)
post refresh_token_endpoint_url(client: @client, refresh_token: @token.refresh_token)
should_not_have_json 'refresh_token'
should_have_json 'error', 'invalid_request'
end
end
context 'refreshing the token with multiple sessions (devices)' do
before do
# enable password auth to simulate other devices
config_is_set(:grant_flows, ["password"])
config_is_set(:resource_owner_from_credentials) do
User.authenticate! params[:username], params[:password]
end
create_resource_owner
_another_token = post password_token_endpoint_url(
client: @client, resource_owner: @resource_owner
)
last_token.update_attribute :created_at, 5.seconds.ago
@token = FactoryBot.create(
:access_token,
application: @client,
resource_owner_id: @resource_owner.id,
use_refresh_token: true
)
@token.update_attribute :expires_in, -100
end
context "refresh_token revoked on use" do
it 'client request a token after creating another token with the same user' do
post refresh_token_endpoint_url(
client: @client, refresh_token: @token.refresh_token
)
should_have_json 'refresh_token', last_token.refresh_token
expect(@token.reload).not_to be_revoked
end
end
context "refresh_token revoked on refresh_token request" do
before do
allow(Doorkeeper::AccessToken).to receive(:refresh_token_revoked_on_use?).and_return(false)
end
it 'client request a token after creating another token with the same user' do
post refresh_token_endpoint_url(
client: @client, refresh_token: @token.refresh_token
)
should_have_json 'refresh_token', last_token.refresh_token
expect(@token.reload).to be_revoked
end
end
def last_token
Doorkeeper::AccessToken.last_authorized_token_for(
@client.id, @resource_owner.id
)
end
end
end
doorkeeper-5.0.2/spec/requests/flows/revoke_token_spec.rb 0000644 0000041 0000041 00000012641 13410042500 023667 0 ustar www-data www-data require 'spec_helper'
describe 'Revoke Token Flow' do
before do
Doorkeeper.configure { orm DOORKEEPER_ORM }
end
context 'with default parameters' do
let(:client_application) { FactoryBot.create :application }
let(:resource_owner) { User.create!(name: 'John', password: 'sekret') }
let(:access_token) do
FactoryBot.create(:access_token,
application: client_application,
resource_owner_id: resource_owner.id,
use_refresh_token: true)
end
context 'with authenticated, confidential OAuth 2.0 client/application' do
let(:headers) do
client_id = client_application.uid
client_secret = client_application.secret
credentials = Base64.encode64("#{client_id}:#{client_secret}")
{ 'HTTP_AUTHORIZATION' => "Basic #{credentials}" }
end
it 'should revoke the access token provided' do
post revocation_token_endpoint_url, params: { token: access_token.token }, headers: headers
access_token.reload
expect(response).to be_successful
expect(access_token.revoked?).to be_truthy
end
it 'should revoke the refresh token provided' do
post revocation_token_endpoint_url, params: { token: access_token.refresh_token }, headers: headers
access_token.reload
expect(response).to be_successful
expect(access_token.revoked?).to be_truthy
end
context 'with invalid token to revoke' do
it 'should not revoke any tokens and respond successfully' do
num_prev_revoked_tokens = Doorkeeper::AccessToken.where(revoked_at: nil).count
post revocation_token_endpoint_url, params: { token: 'I_AM_AN_INVALID_TOKEN' }, headers: headers
# The authorization server responds with HTTP status code 200 even if
# token is invalid
expect(response).to be_successful
expect(Doorkeeper::AccessToken.where(revoked_at: nil).count).to eq(num_prev_revoked_tokens)
end
end
context 'with bad credentials and a valid token' do
let(:headers) do
client_id = client_application.uid
credentials = Base64.encode64("#{client_id}:poop")
{ 'HTTP_AUTHORIZATION' => "Basic #{credentials}" }
end
it 'should not revoke any tokens and respond successfully' do
post revocation_token_endpoint_url, params: { token: access_token.token }, headers: headers
access_token.reload
expect(response).to be_successful
expect(access_token.revoked?).to be_falsey
end
end
context 'with no credentials and a valid token' do
it 'should not revoke any tokens and respond successfully' do
post revocation_token_endpoint_url, params: { token: access_token.token }
access_token.reload
expect(response).to be_successful
expect(access_token.revoked?).to be_falsey
end
end
context 'with valid token for another client application' do
let(:other_client_application) { FactoryBot.create :application }
let(:headers) do
client_id = other_client_application.uid
client_secret = other_client_application.secret
credentials = Base64.encode64("#{client_id}:#{client_secret}")
{ 'HTTP_AUTHORIZATION' => "Basic #{credentials}" }
end
it 'should not revoke the token as its unauthorized' do
post revocation_token_endpoint_url, params: { token: access_token.token }, headers: headers
access_token.reload
expect(response).to be_successful
expect(access_token.revoked?).to be_falsey
end
end
end
context 'with public OAuth 2.0 client/application' do
let(:access_token) do
FactoryBot.create(:access_token,
application: nil,
resource_owner_id: resource_owner.id,
use_refresh_token: true)
end
it 'should revoke the access token provided' do
post revocation_token_endpoint_url, params: { token: access_token.token }
access_token.reload
expect(response).to be_successful
expect(access_token.revoked?).to be_truthy
end
it 'should revoke the refresh token provided' do
post revocation_token_endpoint_url, params: { token: access_token.refresh_token }
access_token.reload
expect(response).to be_successful
expect(access_token.revoked?).to be_truthy
end
context 'with a valid token issued for a confidential client' do
let(:access_token) do
FactoryBot.create(:access_token,
application: client_application,
resource_owner_id: resource_owner.id,
use_refresh_token: true)
end
it 'should not revoke the access token provided' do
post revocation_token_endpoint_url, params: { token: access_token.token }
access_token.reload
expect(response).to be_successful
expect(access_token.revoked?).to be_falsey
end
it 'should not revoke the refresh token provided' do
post revocation_token_endpoint_url, params: { token: access_token.token }
access_token.reload
expect(response).to be_successful
expect(access_token.revoked?).to be_falsey
end
end
end
end
end
doorkeeper-5.0.2/spec/requests/flows/implicit_grant_spec.rb 0000644 0000041 0000041 00000005320 13410042500 024175 0 ustar www-data www-data require 'spec_helper'
feature 'Implicit Grant Flow (feature spec)' do
background do
config_is_set(:authenticate_resource_owner) { User.first || redirect_to('/sign_in') }
config_is_set(:grant_flows, ["implicit"])
client_exists
create_resource_owner
sign_in
end
scenario 'resource owner authorizes the client' do
visit authorization_endpoint_url(client: @client, response_type: 'token')
click_on 'Authorize'
access_token_should_exist_for @client, @resource_owner
i_should_be_on_client_callback @client
end
context 'when application scopes are present and no scope is passed' do
background do
@client.update_attributes(scopes: 'public write read')
end
scenario 'access token has no scopes' do
default_scopes_exist :admin
visit authorization_endpoint_url(client: @client, response_type: 'token')
click_on 'Authorize'
access_token_should_exist_for @client, @resource_owner
token = Doorkeeper::AccessToken.first
expect(token.scopes).to be_empty
end
scenario 'access token has scopes which are common in application scopees and default scopes' do
default_scopes_exist :public, :write
visit authorization_endpoint_url(client: @client, response_type: 'token')
click_on 'Authorize'
access_token_should_exist_for @client, @resource_owner
access_token_should_have_scopes :public, :write
end
end
end
describe 'Implicit Grant Flow (request spec)' do
before do
config_is_set(:authenticate_resource_owner) { User.first || redirect_to('/sign_in') }
config_is_set(:grant_flows, ["implicit"])
client_exists
create_resource_owner
end
context 'token reuse' do
it 'should return a new token each request' do
allow(Doorkeeper.configuration).to receive(:reuse_access_token).and_return(false)
token = client_is_authorized(@client, @resource_owner)
post "/oauth/authorize",
params: {
client_id: @client.uid,
state: '',
redirect_uri: @client.redirect_uri,
response_type: 'token',
commit: 'Authorize'
}
expect(response.location).not_to include(token.token)
end
it 'should return the same token if it is still accessible' do
allow(Doorkeeper.configuration).to receive(:reuse_access_token).and_return(true)
token = client_is_authorized(@client, @resource_owner)
post "/oauth/authorize",
params: {
client_id: @client.uid,
state: '',
redirect_uri: @client.redirect_uri,
response_type: 'token',
commit: 'Authorize'
}
expect(response.location).to include(token.token)
end
end
end
doorkeeper-5.0.2/spec/requests/applications/ 0000755 0000041 0000041 00000000000 13410042500 021165 5 ustar www-data www-data doorkeeper-5.0.2/spec/requests/applications/authorized_applications_spec.rb 0000644 0000041 0000041 00000001704 13410042500 027452 0 ustar www-data www-data require 'spec_helper'
feature 'Authorized applications' do
background do
@user = User.create!(name: 'Joe', password: 'sekret')
@client = client_exists(name: 'Amazing Client App')
resource_owner_is_authenticated @user
client_is_authorized @client, @user
end
scenario 'display user\'s authorized applications' do
visit '/oauth/authorized_applications'
i_should_see 'Amazing Client App'
end
scenario 'do not display other user\'s authorized applications' do
client = client_exists(name: 'Another Client App')
client_is_authorized client, User.create!(name: 'Joe', password: 'sekret')
visit '/oauth/authorized_applications'
i_should_not_see 'Another Client App'
end
scenario 'user revoke access to application' do
visit '/oauth/authorized_applications'
i_should_see 'Amazing Client App'
click_on 'Revoke'
i_should_see 'Application revoked'
i_should_not_see 'Amazing Client App'
end
end
doorkeeper-5.0.2/spec/requests/applications/applications_request_spec.rb 0000644 0000041 0000041 00000013746 13410042500 026775 0 ustar www-data www-data require 'spec_helper'
feature 'Adding applications' do
context 'in application form' do
background do
i_am_logged_in
visit '/oauth/applications/new'
end
scenario 'adding a valid app' do
fill_in 'doorkeeper_application[name]', with: 'My Application'
fill_in 'doorkeeper_application[redirect_uri]',
with: 'https://example.com'
click_button 'Submit'
i_should_see 'Application created'
i_should_see 'My Application'
end
scenario 'adding invalid app' do
click_button 'Submit'
i_should_see 'Whoops! Check your form for possible errors'
end
scenario "adding app ignoring bad scope" do
config_is_set("enforce_configured_scopes", false)
fill_in "doorkeeper_application[name]", with: "My Application"
fill_in "doorkeeper_application[redirect_uri]",
with: "https://example.com"
fill_in "doorkeeper_application[scopes]", with: "blahblah"
click_button "Submit"
i_should_see "Application created"
i_should_see "My Application"
end
scenario "adding app validating bad scope" do
config_is_set("enforce_configured_scopes", true)
fill_in "doorkeeper_application[name]", with: "My Application"
fill_in "doorkeeper_application[redirect_uri]",
with: "https://example.com"
fill_in "doorkeeper_application[scopes]", with: "blahblah"
click_button "Submit"
i_should_see "Whoops! Check your form for possible errors"
end
scenario "adding app validating scope, blank scope is accepted" do
config_is_set("enforce_configured_scopes", true)
fill_in "doorkeeper_application[name]", with: "My Application"
fill_in "doorkeeper_application[redirect_uri]",
with: "https://example.com"
fill_in "doorkeeper_application[scopes]", with: ""
click_button "Submit"
i_should_see "Application created"
i_should_see "My Application"
end
scenario "adding app validating scope, multiple scopes configured" do
config_is_set("enforce_configured_scopes", true)
scopes = Doorkeeper::OAuth::Scopes.from_array(%w[read write admin])
config_is_set("optional_scopes", scopes)
fill_in "doorkeeper_application[name]", with: "My Application"
fill_in "doorkeeper_application[redirect_uri]",
with: "https://example.com"
fill_in "doorkeeper_application[scopes]", with: "read write"
click_button "Submit"
i_should_see "Application created"
i_should_see "My Application"
end
scenario "adding app validating scope, bad scope with multiple scopes configured" do
config_is_set("enforce_configured_scopes", true)
scopes = Doorkeeper::OAuth::Scopes.from_array(%w[read write admin])
config_is_set("optional_scopes", scopes)
fill_in "doorkeeper_application[name]", with: "My Application"
fill_in "doorkeeper_application[redirect_uri]",
with: "https://example.com"
fill_in "doorkeeper_application[scopes]", with: "read blah"
click_button "Submit"
i_should_see "Whoops! Check your form for possible errors"
i_should_see Regexp.new(
I18n.t('activerecord.errors.models.doorkeeper/application.attributes.scopes.not_match_configured'),
true
)
end
end
end
feature 'Listing applications' do
background do
i_am_logged_in
FactoryBot.create :application, name: 'Oauth Dude'
FactoryBot.create :application, name: 'Awesome App'
end
scenario 'application list' do
visit '/oauth/applications'
i_should_see 'Awesome App'
i_should_see 'Oauth Dude'
end
end
feature 'Renders assets' do
scenario 'admin stylesheets' do
visit '/assets/doorkeeper/admin/application.css'
i_should_see 'Bootstrap'
i_should_see '.doorkeeper-admin'
end
scenario 'application stylesheets' do
visit '/assets/doorkeeper/application.css'
i_should_see 'Bootstrap'
i_should_see '#oauth-permissions'
i_should_see '#container'
end
end
feature 'Show application' do
given :app do
i_am_logged_in
FactoryBot.create :application, name: 'Just another oauth app'
end
scenario 'visiting application page' do
visit "/oauth/applications/#{app.id}"
i_should_see 'Just another oauth app'
end
end
feature 'Edit application' do
let :app do
FactoryBot.create :application, name: 'OMG my app'
end
background do
i_am_logged_in
visit "/oauth/applications/#{app.id}/edit"
end
scenario 'updating a valid app' do
fill_in 'doorkeeper_application[name]', with: 'Serious app'
click_button 'Submit'
i_should_see 'Application updated'
i_should_see 'Serious app'
i_should_not_see 'OMG my app'
end
scenario 'updating an invalid app' do
fill_in 'doorkeeper_application[name]', with: ''
click_button 'Submit'
i_should_see 'Whoops! Check your form for possible errors'
end
end
feature 'Remove application' do
background do
i_am_logged_in
@app = FactoryBot.create :application
end
scenario 'deleting an application from list' do
visit '/oauth/applications'
i_should_see @app.name
within(:css, "tr#application_#{@app.id}") do
click_button 'Destroy'
end
i_should_see 'Application deleted'
i_should_not_see @app.name
end
scenario 'deleting an application from show' do
visit "/oauth/applications/#{@app.id}"
click_button 'Destroy'
i_should_see 'Application deleted'
end
end
context 'when admin authenticator block is default' do
let(:app) { FactoryBot.create :application, name: 'app' }
feature 'application list' do
scenario 'fails with forbidden' do
visit '/oauth/applications'
should_have_status 403
end
end
feature 'adding an app' do
scenario 'fails with forbidden' do
visit '/oauth/applications/new'
should_have_status 403
end
end
feature 'editing an app' do
scenario 'fails with forbidden' do
visit "/oauth/applications/#{app.id}/edit"
should_have_status 403
end
end
end
doorkeeper-5.0.2/spec/requests/endpoints/ 0000755 0000041 0000041 00000000000 13410042500 020502 5 ustar www-data www-data doorkeeper-5.0.2/spec/requests/endpoints/token_spec.rb 0000644 0000041 0000041 00000005404 13410042500 023164 0 ustar www-data www-data require 'spec_helper'
describe 'Token endpoint' do
before do
client_exists
authorization_code_exists application: @client, scopes: 'public'
end
it 'respond with correct headers' do
post token_endpoint_url(code: @authorization.token, client: @client)
should_have_header 'Pragma', 'no-cache'
# Rails 5.2 changed headers
if ::Rails::VERSION::MAJOR >= 5 && ::Rails::VERSION::MINOR >= 2 || ::Rails::VERSION::MAJOR >= 6
should_have_header 'Cache-Control', 'private, no-store'
else
should_have_header 'Cache-Control', 'no-store'
end
should_have_header 'Content-Type', 'application/json; charset=utf-8'
end
it 'accepts client credentials with basic auth header' do
post token_endpoint_url,
params: {
code: @authorization.token,
redirect_uri: @client.redirect_uri
},
headers: { 'HTTP_AUTHORIZATION' => basic_auth_header_for_client(@client) }
should_have_json 'access_token', Doorkeeper::AccessToken.first.token
end
it 'returns null for expires_in when a permanent token is set' do
config_is_set(:access_token_expires_in, nil)
post token_endpoint_url(code: @authorization.token, client: @client)
should_have_json 'access_token', Doorkeeper::AccessToken.first.token
should_not_have_json 'expires_in'
end
it 'returns unsupported_grant_type for invalid grant_type param' do
post token_endpoint_url(code: @authorization.token, client: @client, grant_type: 'nothing')
should_not_have_json 'access_token'
should_have_json 'error', 'unsupported_grant_type'
should_have_json 'error_description', translated_error_message('unsupported_grant_type')
end
it 'returns unsupported_grant_type for disabled grant flows' do
config_is_set(:grant_flows, ['implicit'])
post token_endpoint_url(code: @authorization.token, client: @client, grant_type: 'authorization_code')
should_not_have_json 'access_token'
should_have_json 'error', 'unsupported_grant_type'
should_have_json 'error_description', translated_error_message('unsupported_grant_type')
end
it 'returns unsupported_grant_type when refresh_token is not in use' do
post token_endpoint_url(code: @authorization.token, client: @client, grant_type: 'refresh_token')
should_not_have_json 'access_token'
should_have_json 'error', 'unsupported_grant_type'
should_have_json 'error_description', translated_error_message('unsupported_grant_type')
end
it 'returns invalid_request if grant_type is missing' do
post token_endpoint_url(code: @authorization.token, client: @client, grant_type: '')
should_not_have_json 'access_token'
should_have_json 'error', 'invalid_request'
should_have_json 'error_description', translated_error_message('invalid_request')
end
end
doorkeeper-5.0.2/spec/requests/endpoints/authorization_spec.rb 0000644 0000041 0000041 00000004244 13410042500 024745 0 ustar www-data www-data require 'spec_helper'
feature 'Authorization endpoint' do
background do
config_is_set(:authenticate_resource_owner) { User.first || redirect_to('/sign_in') }
client_exists(name: 'MyApp')
end
scenario 'requires resource owner to be authenticated' do
visit authorization_endpoint_url(client: @client)
i_should_see 'Sign in'
i_should_be_on '/'
end
context 'with authenticated resource owner' do
background do
create_resource_owner
sign_in
end
scenario 'displays the authorization form' do
visit authorization_endpoint_url(client: @client)
i_should_see 'Authorize MyApp to use your account?'
end
scenario 'displays all requested scopes' do
default_scopes_exist :public
optional_scopes_exist :write
visit authorization_endpoint_url(client: @client, scope: 'public write')
i_should_see 'Access your public data'
i_should_see 'Update your data'
end
end
context 'with a invalid request' do
background do
create_resource_owner
sign_in
end
scenario 'displays the related error' do
visit authorization_endpoint_url(client: @client, response_type: '')
i_should_not_see 'Authorize'
i_should_see_translated_error_message :unsupported_response_type
end
scenario "displays unsupported_response_type error when using a disabled response type" do
config_is_set(:grant_flows, ['implicit'])
visit authorization_endpoint_url(client: @client, response_type: 'code')
i_should_not_see "Authorize"
i_should_see_translated_error_message :unsupported_response_type
end
end
context 'forgery protection enabled' do
background do
create_resource_owner
sign_in
end
scenario 'raises exception on forged requests' do
allowing_forgery_protection do
expect do
page.driver.post authorization_endpoint_url(client_id: @client.uid,
redirect_uri: @client.redirect_uri,
response_type: 'code')
end.to raise_error(ActionController::InvalidAuthenticityToken)
end
end
end
end
doorkeeper-5.0.2/spec/helpers/ 0000755 0000041 0000041 00000000000 13410042500 016266 5 ustar www-data www-data doorkeeper-5.0.2/spec/helpers/doorkeeper/ 0000755 0000041 0000041 00000000000 13410042500 020425 5 ustar www-data www-data doorkeeper-5.0.2/spec/helpers/doorkeeper/dashboard_helper_spec.rb 0000644 0000041 0000041 00000001222 13410042500 025247 0 ustar www-data www-data require 'spec_helper'
describe Doorkeeper::DashboardHelper do
describe '#doorkeeper_errors_for' do
let(:object) { double errors: { method: messages } }
let(:messages) { ['first message', 'second message'] }
context 'when object has errors' do
it 'returns error messages' do
messages.each do |message|
expect(helper.doorkeeper_errors_for(object, :method)).to include(
message.capitalize
)
end
end
end
context 'when object has no errors' do
it 'returns nil' do
expect(helper.doorkeeper_errors_for(object, :amonter_method)).to be_nil
end
end
end
end
doorkeeper-5.0.2/spec/controllers/ 0000755 0000041 0000041 00000000000 13410042500 017172 5 ustar www-data www-data doorkeeper-5.0.2/spec/controllers/protected_resources_controller_spec.rb 0000644 0000041 0000041 00000025170 13410042500 027064 0 ustar www-data www-data require 'spec_helper'
module ControllerActions
def index
render plain: 'index'
end
def show
render plain: 'show'
end
def doorkeeper_unauthorized_render_options(*); end
def doorkeeper_forbidden_render_options(*); end
end
describe 'doorkeeper authorize filter' do
context 'accepts token code specified as' do
controller do
before_action :doorkeeper_authorize!
def index
render plain: 'index'
end
end
let(:token_string) { '1A2BC3' }
let(:token) do
double(Doorkeeper::AccessToken,
acceptable?: true, previous_refresh_token: "",
revoke_previous_refresh_token!: true)
end
it 'access_token param' do
expect(Doorkeeper::AccessToken).to receive(:by_token).with(token_string).and_return(token)
get :index, params: { access_token: token_string }
end
it 'bearer_token param' do
expect(Doorkeeper::AccessToken).to receive(:by_token).with(token_string).and_return(token)
get :index, params: { bearer_token: token_string }
end
it 'Authorization header' do
expect(Doorkeeper::AccessToken).to receive(:by_token).with(token_string).and_return(token)
request.env['HTTP_AUTHORIZATION'] = "Bearer #{token_string}"
get :index
end
it 'different kind of Authorization header' do
expect(Doorkeeper::AccessToken).not_to receive(:by_token)
request.env['HTTP_AUTHORIZATION'] = "MAC #{token_string}"
get :index
end
it 'does not change Authorization header value' do
expect(Doorkeeper::AccessToken).to receive(:by_token).exactly(2).times.and_return(token)
request.env['HTTP_AUTHORIZATION'] = "Bearer #{token_string}"
get :index
controller.send(:remove_instance_variable, :@doorkeeper_token)
get :index
end
end
context 'defined for all actions' do
controller do
before_action :doorkeeper_authorize!
include ControllerActions
end
context 'with valid token', token: :valid do
it 'allows into index action' do
get :index, params: { access_token: token_string }
expect(response).to be_successful
end
it 'allows into show action' do
get :show, params: { id: '4', access_token: token_string }
expect(response).to be_successful
end
end
context 'with invalid token', token: :invalid do
it 'does not allow into index action' do
get :index, params: { access_token: token_string }
expect(response.status).to eq 401
expect(response.header['WWW-Authenticate']).to match(/^Bearer/)
end
it 'does not allow into show action' do
get :show, params: { id: '4', access_token: token_string }
expect(response.status).to eq 401
expect(response.header['WWW-Authenticate']).to match(/^Bearer/)
end
end
end
context 'defined with scopes' do
controller do
before_action -> { doorkeeper_authorize! :write }
include ControllerActions
end
let(:token_string) { '1A2DUWE' }
it 'allows if the token has particular scopes' do
token = double(Doorkeeper::AccessToken,
accessible?: true, scopes: %w[write public],
previous_refresh_token: "",
revoke_previous_refresh_token!: true)
expect(token).to receive(:acceptable?).with([:write]).and_return(true)
expect(
Doorkeeper::AccessToken
).to receive(:by_token).with(token_string).and_return(token)
get :index, params: { access_token: token_string }
expect(response).to be_successful
end
it 'does not allow if the token does not include given scope' do
token = double(Doorkeeper::AccessToken,
accessible?: true, scopes: ['public'], revoked?: false,
expired?: false, previous_refresh_token: "",
revoke_previous_refresh_token!: true)
expect(
Doorkeeper::AccessToken
).to receive(:by_token).with(token_string).and_return(token)
expect(token).to receive(:acceptable?).with([:write]).and_return(false)
get :index, params: { access_token: token_string }
expect(response.status).to eq 403
expect(response.header).to_not include('WWW-Authenticate')
end
end
context 'when custom unauthorized render options are configured' do
controller do
before_action :doorkeeper_authorize!
include ControllerActions
end
context 'with a JSON custom render', token: :invalid do
before do
module ControllerActions
remove_method :doorkeeper_unauthorized_render_options
def doorkeeper_unauthorized_render_options(error: nil)
{ json: ActiveSupport::JSON.encode(error_message: error.description) }
end
end
end
after do
module ControllerActions
remove_method :doorkeeper_unauthorized_render_options
def doorkeeper_unauthorized_render_options(error: nil); end
end
end
it 'it renders a custom JSON response', token: :invalid do
get :index, params: { access_token: token_string }
expect(response.status).to eq 401
expect(response.content_type).to eq('application/json')
expect(response.header['WWW-Authenticate']).to match(/^Bearer/)
expect(json_response).not_to be_nil
expect(json_response['error_message']).to match('token is invalid')
end
end
context 'with a text custom render', token: :invalid do
before do
module ControllerActions
remove_method :doorkeeper_unauthorized_render_options
def doorkeeper_unauthorized_render_options(**)
{ plain: 'Unauthorized' }
end
end
end
after do
module ControllerActions
remove_method :doorkeeper_unauthorized_render_options
def doorkeeper_unauthorized_render_options(error: nil); end
end
end
it 'it renders a custom text response', token: :invalid do
get :index, params: { access_token: token_string }
expect(response.status).to eq 401
expect(response.content_type).to eq('text/plain')
expect(response.header['WWW-Authenticate']).to match(/^Bearer/)
expect(response.body).to eq('Unauthorized')
end
end
end
context 'when custom forbidden render options are configured' do
before do
expect(Doorkeeper::AccessToken).to receive(:by_token).with(token_string).and_return(token)
expect(token).to receive(:acceptable?).with([:write]).and_return(false)
end
after do
module ControllerActions
remove_method :doorkeeper_forbidden_render_options
def doorkeeper_forbidden_render_options(*); end
end
end
controller do
before_action -> { doorkeeper_authorize! :write }
include ControllerActions
end
let(:token) do
double(Doorkeeper::AccessToken,
accessible?: true, scopes: ['public'], revoked?: false,
expired?: false, previous_refresh_token: "",
revoke_previous_refresh_token!: true)
end
let(:token_string) { '1A2DUWE' }
context 'with a JSON custom render' do
before do
module ControllerActions
remove_method :doorkeeper_forbidden_render_options
def doorkeeper_forbidden_render_options(*)
{ json: { error_message: 'Forbidden' } }
end
end
end
it 'renders a custom JSON response' do
get :index, params: { access_token: token_string }
expect(response.header).to_not include('WWW-Authenticate')
expect(response.content_type).to eq('application/json')
expect(response.status).to eq 403
expect(json_response).not_to be_nil
expect(json_response['error_message']).to match('Forbidden')
end
end
context 'with a status and JSON custom render' do
before do
module ControllerActions
remove_method :doorkeeper_forbidden_render_options
def doorkeeper_forbidden_render_options(*)
{ json: { error_message: 'Not Found' },
respond_not_found_when_forbidden: true }
end
end
end
it 'overrides the default status code' do
get :index, params: { access_token: token_string }
expect(response.status).to eq 404
end
end
context 'with a text custom render' do
before do
module ControllerActions
remove_method :doorkeeper_forbidden_render_options
def doorkeeper_forbidden_render_options(*)
{ plain: 'Forbidden' }
end
end
end
it 'renders a custom status code and text response' do
get :index, params: { access_token: token_string }
expect(response.header).to_not include('WWW-Authenticate')
expect(response.status).to eq 403
expect(response.body).to eq('Forbidden')
end
end
context 'with a status and text custom render' do
before do
module ControllerActions
remove_method :doorkeeper_forbidden_render_options
def doorkeeper_forbidden_render_options(*)
{ respond_not_found_when_forbidden: true, plain: 'Not Found' }
end
end
end
it 'overrides the default status code' do
get :index, params: { access_token: token_string }
expect(response.status).to eq 404
end
end
end
context 'when handle_auth_errors option is set to :raise' do
subject { get :index, params: { access_token: token_string } }
before do
config_is_set(:handle_auth_errors, :raise)
end
controller do
before_action :doorkeeper_authorize!
include ControllerActions
end
context 'when token is unknown' do
it 'raises Doorkeeper::Errors::TokenUnknown exception', token: :invalid do
expect { subject }.to raise_error(Doorkeeper::Errors::TokenUnknown)
end
end
context 'when token is expired' do
it 'raises Doorkeeper::Errors::TokenExpired exception', token: :expired do
expect { subject }.to raise_error(Doorkeeper::Errors::TokenExpired)
end
end
context 'when token is revoked' do
it 'raises Doorkeeper::Errors::TokenRevoked exception', token: :revoked do
expect { subject }.to raise_error(Doorkeeper::Errors::TokenRevoked)
end
end
context 'when token is forbidden' do
it 'raises Doorkeeper::Errors::TokenForbidden exception', token: :forbidden do
expect { subject }.to raise_error(Doorkeeper::Errors::TokenForbidden)
end
end
context 'when token is valid' do
it 'allows into index action', token: :valid do
expect(response).to be_successful
end
end
end
end
doorkeeper-5.0.2/spec/controllers/token_info_controller_spec.rb 0000644 0000041 0000041 00000002700 13410042500 025126 0 ustar www-data www-data require 'spec_helper'
describe Doorkeeper::TokenInfoController do
describe 'when requesting token info with valid token' do
let(:doorkeeper_token) { FactoryBot.create(:access_token) }
describe 'successful request' do
it 'responds with token info' do
get :show, params: { access_token: doorkeeper_token.token }
expect(response.body).to eq(doorkeeper_token.to_json)
end
it 'responds with a 200 status' do
get :show, params: { access_token: doorkeeper_token.token }
expect(response.status).to eq 200
end
end
describe 'invalid token response' do
it 'responds with 401 when doorkeeper_token is not valid' do
get :show
expect(response.status).to eq 401
expect(response.headers['WWW-Authenticate']).to match(/^Bearer/)
end
it 'responds with 401 when doorkeeper_token is invalid, expired or revoked' do
allow(controller).to receive(:doorkeeper_token).and_return(doorkeeper_token)
allow(doorkeeper_token).to receive(:accessible?).and_return(false)
get :show
expect(response.status).to eq 401
expect(response.headers['WWW-Authenticate']).to match(/^Bearer/)
end
it 'responds body message for error' do
get :show
expect(response.body).to eq(
Doorkeeper::OAuth::ErrorResponse.new(name: :invalid_request, status: :unauthorized).body.to_json
)
end
end
end
end
doorkeeper-5.0.2/spec/controllers/application_metal_controller_spec.rb 0000644 0000041 0000041 00000002334 13410042500 026463 0 ustar www-data www-data # frozen_string_literal: true
require 'spec_helper_integration'
describe Doorkeeper::ApplicationMetalController do
controller(Doorkeeper::ApplicationMetalController) do
def index
render json: {}, status: 200
end
end
it "lazy run hooks" do
i = 0
ActiveSupport.on_load(:doorkeeper_metal_controller) { i += 1 }
expect(i).to eq 1
end
describe 'enforce_content_type' do
before { allow(Doorkeeper.configuration).to receive(:enforce_content_type).and_return(flag) }
context 'enabled' do
let(:flag) { true }
it '200 for the correct media type' do
get :index, params: {}, as: :url_encoded_form
expect(response).to have_http_status 200
end
it 'returns a 415 for an incorrect media type' do
get :index, as: :json
expect(response).to have_http_status 415
end
end
context 'disabled' do
let(:flag) { false }
it 'returns a 200 for the correct media type' do
get :index, as: :url_encoded_form
expect(response).to have_http_status 200
end
it 'returns a 200 for an incorrect media type' do
get :index, as: :json
expect(response).to have_http_status 200
end
end
end
end
doorkeeper-5.0.2/spec/controllers/applications_controller_spec.rb 0000644 0000041 0000041 00000012340 13410042500 025462 0 ustar www-data www-data require 'spec_helper'
module Doorkeeper
describe ApplicationsController do
context 'JSON API' do
render_views
before do
allow(Doorkeeper.configuration).to receive(:api_only).and_return(true)
allow(Doorkeeper.configuration).to receive(:authenticate_admin).and_return(->(*) { true })
end
it 'creates an application' do
expect do
post :create, params: {
doorkeeper_application: {
name: 'Example',
redirect_uri: 'https://example.com'
}, format: :json
}
end.to(change { Doorkeeper::Application.count })
expect(response).to be_successful
expect(json_response).to include('id', 'name', 'uid', 'secret', 'redirect_uri', 'scopes')
expect(json_response['name']).to eq('Example')
expect(json_response['redirect_uri']).to eq('https://example.com')
end
it 'returns validation errors on wrong create params' do
expect do
post :create, params: {
doorkeeper_application: {
name: 'Example'
}, format: :json
}
end.not_to(change { Doorkeeper::Application.count })
expect(response).to have_http_status(422)
expect(json_response).to include('errors')
end
it 'returns application info' do
application = FactoryBot.create(:application, name: 'Change me')
get :show, params: { id: application.id, format: :json }
expect(response).to be_successful
expect(json_response).to include('id', 'name', 'uid', 'secret', 'redirect_uri', 'scopes')
end
it 'updates application' do
application = FactoryBot.create(:application, name: 'Change me')
put :update, params: {
id: application.id,
doorkeeper_application: {
name: 'Example App',
redirect_uri: 'https://example.com'
}, format: :json
}
expect(application.reload.name).to eq 'Example App'
expect(json_response).to include('id', 'name', 'uid', 'secret', 'redirect_uri', 'scopes')
end
it 'returns validation errors on wrong update params' do
application = FactoryBot.create(:application, name: 'Change me')
put :update, params: {
id: application.id,
doorkeeper_application: {
name: 'Example App',
redirect_uri: 'localhost:3000'
}, format: :json
}
expect(response).to have_http_status(422)
expect(json_response).to include('errors')
end
it 'destroys an application' do
application = FactoryBot.create(:application)
delete :destroy, params: { id: application.id, format: :json }
expect(response).to have_http_status(204)
expect(Application.count).to be_zero
end
end
context 'when admin is not authenticated' do
before do
allow(Doorkeeper.configuration).to receive(:authenticate_admin).and_return(proc do
redirect_to main_app.root_url
end)
end
it 'redirects as set in Doorkeeper.authenticate_admin' do
get :index
expect(response).to redirect_to(controller.main_app.root_url)
end
it 'does not create application' do
expect do
post :create, params: {
doorkeeper_application: {
name: 'Example',
redirect_uri: 'https://example.com'
}
}
end.not_to(change { Doorkeeper::Application.count })
end
end
context 'when admin is authenticated' do
render_views
before do
allow(Doorkeeper.configuration).to receive(:authenticate_admin).and_return(->(*) { true })
end
it 'sorts applications by created_at' do
first_application = FactoryBot.create(:application)
second_application = FactoryBot.create(:application)
expect(Doorkeeper::Application).to receive(:ordered_by).and_call_original
get :index
expect(response.body).to have_selector("tbody tr:first-child#application_#{first_application.id}")
expect(response.body).to have_selector("tbody tr:last-child#application_#{second_application.id}")
end
it 'creates application' do
expect do
post :create, params: {
doorkeeper_application: {
name: 'Example',
redirect_uri: 'https://example.com'
}
}
end.to change { Doorkeeper::Application.count }.by(1)
expect(response).to be_redirect
end
it 'does not allow mass assignment of uid or secret' do
application = FactoryBot.create(:application)
put :update, params: {
id: application.id,
doorkeeper_application: {
uid: '1A2B3C4D',
secret: '1A2B3C4D'
}
}
expect(application.reload.uid).not_to eq '1A2B3C4D'
end
it 'updates application' do
application = FactoryBot.create(:application)
put :update, params: {
id: application.id, doorkeeper_application: {
name: 'Example',
redirect_uri: 'https://example.com'
}
}
expect(application.reload.name).to eq 'Example'
end
end
end
end
doorkeeper-5.0.2/spec/controllers/tokens_controller_spec.rb 0000644 0000041 0000041 00000021477 13410042500 024312 0 ustar www-data www-data require 'spec_helper'
describe Doorkeeper::TokensController do
describe 'when authorization has succeeded' do
let(:token) { double(:token, authorize: true) }
it 'returns the authorization' do
skip 'verify need of these specs'
expect(token).to receive(:authorization)
post :create
end
end
describe 'when authorization has failed' do
it 'returns the error response' do
token = double(:token, authorize: false)
allow(controller).to receive(:token) { token }
post :create
expect(response.status).to eq 401
expect(response.headers['WWW-Authenticate']).to match(/Bearer/)
end
end
describe 'when there is a failure due to a custom error' do
it 'returns the error response with a custom message' do
# I18n looks for `doorkeeper.errors.messages.custom_message` in locale files
custom_message = "my_message"
allow(I18n).to receive(:translate)
.with(
custom_message,
hash_including(scope: %i[doorkeeper errors messages])
)
.and_return('Authorization custom message')
doorkeeper_error = Doorkeeper::Errors::DoorkeeperError.new(custom_message)
strategy = double(:strategy)
request = double(token_request: strategy)
allow(strategy).to receive(:authorize).and_raise(doorkeeper_error)
allow(controller).to receive(:server).and_return(request)
post :create
expected_response_body = {
"error" => custom_message,
"error_description" => "Authorization custom message"
}
expect(response.status).to eq 401
expect(response.headers['WWW-Authenticate']).to match(/Bearer/)
expect(JSON.parse(response.body)).to eq expected_response_body
end
end
# http://tools.ietf.org/html/rfc7009#section-2.2
describe 'revoking tokens' do
let(:client) { FactoryBot.create(:application) }
let(:access_token) { FactoryBot.create(:access_token, application: client) }
before(:each) do
allow(controller).to receive(:token) { access_token }
end
context 'when associated app is public' do
let(:client) { FactoryBot.create(:application, confidential: false) }
it 'returns 200' do
post :revoke
expect(response.status).to eq 200
end
it 'revokes the access token' do
post :revoke
expect(access_token.reload).to have_attributes(revoked?: true)
end
end
context 'when associated app is confidential' do
let(:client) { FactoryBot.create(:application, confidential: true) }
let(:oauth_client) { Doorkeeper::OAuth::Client.new(client) }
before(:each) do
allow_any_instance_of(Doorkeeper::Server).to receive(:client) { oauth_client }
end
it 'returns 200' do
post :revoke
expect(response.status).to eq 200
end
it 'revokes the access token' do
post :revoke
expect(access_token.reload).to have_attributes(revoked?: true)
end
context 'when authorization fails' do
let(:some_other_client) { FactoryBot.create(:application, confidential: true) }
let(:oauth_client) { Doorkeeper::OAuth::Client.new(some_other_client) }
it 'returns 200' do
post :revoke
expect(response.status).to eq 200
end
it 'does not revoke the access token' do
post :revoke
expect(access_token.reload).to have_attributes(revoked?: false)
end
end
end
end
describe 'authorize response memoization' do
it "memoizes the result of the authorization" do
strategy = double(:strategy, authorize: true)
expect(strategy).to receive(:authorize).once
allow(controller).to receive(:strategy) { strategy }
allow(controller).to receive(:create) do
2.times { controller.send :authorize_response }
controller.render json: {}, status: :ok
end
post :create
end
end
describe 'when requested token introspection' do
context 'authorized using Bearer token' do
let(:client) { FactoryBot.create(:application) }
let(:access_token) { FactoryBot.create(:access_token, application: client) }
it 'responds with full token introspection' do
request.headers['Authorization'] = "Bearer #{access_token.token}"
post :introspect, params: { token: access_token.token }
should_have_json 'active', true
expect(json_response).to include('client_id', 'token_type', 'exp', 'iat')
end
end
context 'authorized using Client Authentication' do
let(:client) { FactoryBot.create(:application) }
let(:access_token) { FactoryBot.create(:access_token, application: client) }
it 'responds with full token introspection' do
request.headers['Authorization'] = basic_auth_header_for_client(client)
post :introspect, params: { token: access_token.token }
should_have_json 'active', true
expect(json_response).to include('client_id', 'token_type', 'exp', 'iat')
should_have_json 'client_id', client.uid
end
end
context 'public access token' do
let(:client) { FactoryBot.create(:application) }
let(:access_token) { FactoryBot.create(:access_token, application: nil) }
it 'responds with full token introspection' do
request.headers['Authorization'] = basic_auth_header_for_client(client)
post :introspect, params: { token: access_token.token }
should_have_json 'active', true
expect(json_response).to include('client_id', 'token_type', 'exp', 'iat')
should_have_json 'client_id', nil
end
end
context 'token was issued to a different client than is making this request' do
let(:client) { FactoryBot.create(:application) }
let(:different_client) { FactoryBot.create(:application) }
let(:access_token) { FactoryBot.create(:access_token, application: client) }
it 'responds with only active state' do
request.headers['Authorization'] = basic_auth_header_for_client(different_client)
post :introspect, params: { token: access_token.token }
expect(response).to be_successful
should_have_json 'active', false
expect(json_response).not_to include('client_id', 'token_type', 'exp', 'iat')
end
end
context 'using invalid credentials to authorize' do
let(:client) { double(uid: '123123', secret: '666999') }
let(:access_token) { FactoryBot.create(:access_token) }
it 'responds with invalid_client error' do
request.headers['Authorization'] = basic_auth_header_for_client(client)
post :introspect, params: { token: access_token.token }
expect(response).not_to be_successful
response_status_should_be 401
should_not_have_json 'active'
should_have_json 'error', 'invalid_client'
end
end
context 'using wrong token value' do
let(:client) { FactoryBot.create(:application) }
let(:access_token) { FactoryBot.create(:access_token, application: client) }
it 'responds with only active state' do
request.headers['Authorization'] = basic_auth_header_for_client(client)
post :introspect, params: { token: SecureRandom.hex(16) }
should_have_json 'active', false
expect(json_response).not_to include('client_id', 'token_type', 'exp', 'iat')
end
end
context 'when requested Access Token expired' do
let(:client) { FactoryBot.create(:application) }
let(:access_token) { FactoryBot.create(:access_token, application: client, created_at: 1.year.ago) }
it 'responds with only active state' do
request.headers['Authorization'] = basic_auth_header_for_client(client)
post :introspect, params: { token: access_token.token }
should_have_json 'active', false
expect(json_response).not_to include('client_id', 'token_type', 'exp', 'iat')
end
end
context 'when requested Access Token revoked' do
let(:client) { FactoryBot.create(:application) }
let(:access_token) { FactoryBot.create(:access_token, application: client, revoked_at: 1.year.ago) }
it 'responds with only active state' do
request.headers['Authorization'] = basic_auth_header_for_client(client)
post :introspect, params: { token: access_token.token }
should_have_json 'active', false
expect(json_response).not_to include('client_id', 'token_type', 'exp', 'iat')
end
end
context 'unauthorized (no bearer token or client credentials)' do
let(:access_token) { FactoryBot.create(:access_token) }
it 'responds with invalid_request error' do
post :introspect, params: { token: access_token.token }
expect(response).not_to be_successful
response_status_should_be 401
should_not_have_json 'active'
should_have_json 'error', 'invalid_request'
end
end
end
end
doorkeeper-5.0.2/spec/controllers/authorizations_controller_spec.rb 0000644 0000041 0000041 00000040363 13410042500 026065 0 ustar www-data www-data require 'spec_helper'
describe Doorkeeper::AuthorizationsController, 'implicit grant flow' do
include AuthorizationRequestHelper
if Rails::VERSION::MAJOR >= 5
class ActionDispatch::TestResponse
def query_params
@query_params ||= begin
fragment = URI.parse(location).fragment
Rack::Utils.parse_query(fragment)
end
end
end
else
class ActionController::TestResponse
def query_params
@query_params ||= begin
fragment = URI.parse(location).fragment
Rack::Utils.parse_query(fragment)
end
end
end
end
def translated_error_message(key)
I18n.translate key, scope: %i[doorkeeper errors messages]
end
let(:client) { FactoryBot.create :application }
let(:user) { User.create!(name: 'Joe', password: 'sekret') }
let(:access_token) { FactoryBot.build :access_token, resource_owner_id: user.id, application_id: client.id }
before do
allow(Doorkeeper.configuration).to receive(:grant_flows).and_return(["implicit"])
allow(controller).to receive(:current_resource_owner).and_return(user)
allow(Doorkeeper.configuration).to receive(:custom_access_token_expires_in).and_return(proc { |context|
context.grant_type == Doorkeeper::OAuth::IMPLICIT ? 1234 : nil
})
end
describe 'POST #create' do
before do
post :create, params: { client_id: client.uid, response_type: 'token', redirect_uri: client.redirect_uri }
end
it 'redirects after authorization' do
expect(response).to be_redirect
end
it 'redirects to client redirect uri' do
expect(response.location).to match(/^#{client.redirect_uri}/)
end
it 'includes access token in fragment' do
expect(response.query_params['access_token']).to eq(Doorkeeper::AccessToken.first.token)
end
it 'includes token type in fragment' do
expect(response.query_params['token_type']).to eq('Bearer')
end
it 'includes token expiration in fragment' do
expect(response.query_params['expires_in'].to_i).to eq(1234)
end
it 'issues the token for the current client' do
expect(Doorkeeper::AccessToken.first.application_id).to eq(client.id)
end
it 'issues the token for the current resource owner' do
expect(Doorkeeper::AccessToken.first.resource_owner_id).to eq(user.id)
end
end
describe "POST #create in API mode" do
before do
allow(Doorkeeper.configuration).to receive(:api_only).and_return(true)
post :create, params: { client_id: client.uid, response_type: "token", redirect_uri: client.redirect_uri }
end
let(:response_json_body) { JSON.parse(response.body) }
let(:redirect_uri) { response_json_body["redirect_uri"] }
it "renders success after authorization" do
expect(response).to be_successful
end
it "renders correct redirect uri" do
expect(redirect_uri).to match(/^#{client.redirect_uri}/)
end
it "includes access token in fragment" do
expect(redirect_uri.match(/access_token=([a-f0-9]+)&?/)[1]).to eq(Doorkeeper::AccessToken.first.token)
end
it "includes token type in fragment" do
expect(redirect_uri.match(/token_type=(\w+)&?/)[1]).to eq "Bearer"
end
it "includes token expiration in fragment" do
expect(redirect_uri.match(/expires_in=(\d+)&?/)[1].to_i).to eq 1234
end
it "issues the token for the current client" do
expect(Doorkeeper::AccessToken.first.application_id).to eq(client.id)
end
it "issues the token for the current resource owner" do
expect(Doorkeeper::AccessToken.first.resource_owner_id).to eq(user.id)
end
end
describe 'POST #create with errors' do
before do
default_scopes_exist :public
post :create, params: {
client_id: client.uid,
response_type: 'token',
scope: 'invalid',
redirect_uri: client.redirect_uri
}
end
it 'redirects after authorization' do
expect(response).to be_redirect
end
it 'redirects to client redirect uri' do
expect(response.location).to match(/^#{client.redirect_uri}/)
end
it 'does not include access token in fragment' do
expect(response.query_params['access_token']).to be_nil
end
it 'includes error in fragment' do
expect(response.query_params['error']).to eq('invalid_scope')
end
it 'includes error description in fragment' do
expect(response.query_params['error_description']).to eq(translated_error_message(:invalid_scope))
end
it 'does not issue any access token' do
expect(Doorkeeper::AccessToken.all).to be_empty
end
end
describe 'POST #create in API mode with errors' do
before do
allow(Doorkeeper.configuration).to receive(:api_only).and_return(true)
default_scopes_exist :public
post :create, params: {
client_id: client.uid,
response_type: 'token',
scope: 'invalid',
redirect_uri: client.redirect_uri
}
end
let(:response_json_body) { JSON.parse(response.body) }
let(:redirect_uri) { response_json_body['redirect_uri'] }
it 'renders 400 error' do
expect(response.status).to eq 401
end
it 'includes correct redirect URI' do
expect(redirect_uri).to match(/^#{client.redirect_uri}/)
end
it 'does not include access token in fragment' do
expect(redirect_uri.match(/access_token=([a-f0-9]+)&?/)).to be_nil
end
it 'includes error in redirect uri' do
expect(redirect_uri.match(/error=([a-z_]+)&?/)[1]).to eq 'invalid_scope'
end
it 'includes error description in redirect uri' do
expect(redirect_uri.match(/error_description=(.+)&?/)[1]).to_not be_nil
end
it 'does not issue any access token' do
expect(Doorkeeper::AccessToken.all).to be_empty
end
end
describe 'POST #create with application already authorized' do
before do
allow(Doorkeeper.configuration).to receive(:reuse_access_token).and_return(true)
access_token.save!
post :create, params: {
client_id: client.uid,
response_type: 'token',
redirect_uri: client.redirect_uri
}
end
it 'returns the existing access token in a fragment' do
expect(response.query_params['access_token']).to eq(access_token.token)
end
it 'does not creates a new access token' do
expect(Doorkeeper::AccessToken.count).to eq(1)
end
end
describe 'POST #create with callbacks' do
after do
client.update_attribute :redirect_uri, 'urn:ietf:wg:oauth:2.0:oob'
end
describe 'when successful' do
after do
post :create, params: {
client_id: client.uid,
response_type: 'token',
redirect_uri: client.redirect_uri
}
end
it 'should call :before_successful_authorization callback' do
expect(Doorkeeper.configuration)
.to receive_message_chain(:before_successful_authorization, :call).with(instance_of(described_class))
end
it 'should call :after_successful_authorization callback' do
expect(Doorkeeper.configuration)
.to receive_message_chain(:after_successful_authorization, :call).with(instance_of(described_class))
end
end
describe 'with errors' do
after do
post :create, params: { client_id: client.uid, response_type: 'token', redirect_uri: 'bad_uri' }
end
it 'should not call :before_successful_authorization callback' do
expect(Doorkeeper.configuration).not_to receive(:before_successful_authorization)
end
it 'should not call :after_successful_authorization callback' do
expect(Doorkeeper.configuration).not_to receive(:after_successful_authorization)
end
end
end
describe 'GET #new token request with native url and skip_authorization true' do
before do
allow(Doorkeeper.configuration).to receive(:skip_authorization).and_return(proc do
true
end)
client.update_attribute :redirect_uri, 'urn:ietf:wg:oauth:2.0:oob'
get :new, params: {
client_id: client.uid,
response_type: 'token',
redirect_uri: client.redirect_uri
}
end
it 'should redirect immediately' do
expect(response).to be_redirect
expect(response.location).to match(%r{/oauth/token/info\?access_token=})
end
it 'should not issue a grant' do
expect(Doorkeeper::AccessGrant.count).to be 0
end
it 'should issue a token' do
expect(Doorkeeper::AccessToken.count).to be 1
end
end
describe 'GET #new code request with native url and skip_authorization true' do
before do
allow(Doorkeeper.configuration).to receive(:grant_flows).and_return(%w[authorization_code])
allow(Doorkeeper.configuration).to receive(:skip_authorization).and_return(proc do
true
end)
client.update_attribute :redirect_uri, 'urn:ietf:wg:oauth:2.0:oob'
get :new, params: {
client_id: client.uid,
response_type: 'code',
redirect_uri: client.redirect_uri
}
end
it 'should redirect immediately' do
expect(response).to be_redirect
expect(response.location)
.to match(%r{/oauth/authorize/native\?code=#{Doorkeeper::AccessGrant.first.token}})
end
it 'should issue a grant' do
expect(Doorkeeper::AccessGrant.count).to be 1
end
it 'should not issue a token' do
expect(Doorkeeper::AccessToken.count).to be 0
end
end
describe 'GET #new with skip_authorization true' do
before do
allow(Doorkeeper.configuration).to receive(:skip_authorization).and_return(proc do
true
end)
get :new, params: {
client_id: client.uid,
response_type: 'token',
redirect_uri: client.redirect_uri
}
end
it 'should redirect immediately' do
expect(response).to be_redirect
expect(response.location).to match(/^#{client.redirect_uri}/)
end
it 'should issue a token' do
expect(Doorkeeper::AccessToken.count).to be 1
end
it 'includes token type in fragment' do
expect(response.query_params['token_type']).to eq('Bearer')
end
it 'includes token expiration in fragment' do
expect(response.query_params['expires_in'].to_i).to eq(1234)
end
it 'issues the token for the current client' do
expect(Doorkeeper::AccessToken.first.application_id).to eq(client.id)
end
it 'issues the token for the current resource owner' do
expect(Doorkeeper::AccessToken.first.resource_owner_id).to eq(user.id)
end
end
describe 'GET #new in API mode' do
before do
allow(Doorkeeper.configuration).to receive(:api_only).and_return(true)
get :new, params: {
client_id: client.uid,
response_type: 'token',
redirect_uri: client.redirect_uri
}
end
it 'should render success' do
expect(response).to be_successful
end
it "sets status to pre-authorization" do
expect(json_response["status"]).to eq(I18n.t('doorkeeper.pre_authorization.status'))
end
it "sets correct values" do
expect(json_response['client_id']).to eq(client.uid)
expect(json_response['redirect_uri']).to eq(client.redirect_uri)
expect(json_response['state']).to be_nil
expect(json_response['response_type']).to eq('token')
expect(json_response['scope']).to eq('')
end
end
describe 'GET #new in API mode with skip_authorization true' do
before do
allow(Doorkeeper.configuration).to receive(:skip_authorization).and_return(proc { true })
allow(Doorkeeper.configuration).to receive(:api_only).and_return(true)
get :new, params: {
client_id: client.uid,
response_type: 'token',
redirect_uri: client.redirect_uri
}
end
it 'should render success' do
expect(response).to be_successful
end
it 'should issue a token' do
expect(Doorkeeper::AccessToken.count).to be 1
end
it "sets status to redirect" do
expect(JSON.parse(response.body)["status"]).to eq("redirect")
end
it "sets redirect_uri to correct value" do
redirect_uri = JSON.parse(response.body)["redirect_uri"]
expect(redirect_uri).to_not be_nil
expect(redirect_uri.match(/token_type=(\w+)&?/)[1]).to eq "Bearer"
expect(redirect_uri.match(/expires_in=(\d+)&?/)[1].to_i).to eq 1234
expect(
redirect_uri.match(/access_token=([a-f0-9]+)&?/)[1]
).to eq Doorkeeper::AccessToken.first.token
end
it "issues the token for the current client" do
expect(Doorkeeper::AccessToken.first.application_id).to eq(client.id)
end
it "issues the token for the current resource owner" do
expect(Doorkeeper::AccessToken.first.resource_owner_id).to eq(user.id)
end
end
describe 'GET #new with errors' do
before do
default_scopes_exist :public
get :new, params: { an_invalid: 'request' }
end
it 'does not redirect' do
expect(response).to_not be_redirect
end
it 'does not issue any token' do
expect(Doorkeeper::AccessGrant.count).to eq 0
expect(Doorkeeper::AccessToken.count).to eq 0
end
end
describe 'GET #new in API mode with errors' do
let(:response_json_body) { JSON.parse(response.body) }
before do
default_scopes_exist :public
allow(Doorkeeper.configuration).to receive(:api_only).and_return(true)
get :new, params: { an_invalid: 'request' }
end
it 'should render bad request' do
expect(response).to have_http_status(:bad_request)
end
it 'includes error in body' do
expect(response_json_body['error']).to eq('unsupported_response_type')
end
it 'includes error description in body' do
expect(response_json_body['error_description'])
.to eq(translated_error_message(:unsupported_response_type))
end
it 'does not issue any token' do
expect(Doorkeeper::AccessGrant.count).to eq 0
expect(Doorkeeper::AccessToken.count).to eq 0
end
end
describe 'GET #new with callbacks' do
after do
client.update_attribute :redirect_uri, 'urn:ietf:wg:oauth:2.0:oob'
get :new, params: { client_id: client.uid, response_type: 'token', redirect_uri: client.redirect_uri }
end
describe 'when authorizing' do
before do
allow(Doorkeeper.configuration).to receive(:skip_authorization).and_return(proc { true })
end
it 'should call :before_successful_authorization callback' do
expect(Doorkeeper.configuration)
.to receive_message_chain(:before_successful_authorization, :call).with(instance_of(described_class))
end
it 'should call :after_successful_authorization callback' do
expect(Doorkeeper.configuration)
.to receive_message_chain(:after_successful_authorization, :call).with(instance_of(described_class))
end
end
describe 'when not authorizing' do
before do
allow(Doorkeeper.configuration).to receive(:skip_authorization).and_return(proc { false })
end
it 'should not call :before_successful_authorization callback' do
expect(Doorkeeper.configuration).not_to receive(:before_successful_authorization)
end
it 'should not call :after_successful_authorization callback' do
expect(Doorkeeper.configuration).not_to receive(:after_successful_authorization)
end
end
describe 'when not authorizing in api mode' do
before do
allow(Doorkeeper.configuration).to receive(:skip_authorization).and_return(proc { false })
allow(Doorkeeper.configuration).to receive(:api_only).and_return(true)
end
it 'should not call :before_successful_authorization callback' do
expect(Doorkeeper.configuration).not_to receive(:before_successful_authorization)
end
it 'should not call :after_successful_authorization callback' do
expect(Doorkeeper.configuration).not_to receive(:after_successful_authorization)
end
end
end
describe 'authorize response memoization' do
it 'memoizes the result of the authorization' do
strategy = double(:strategy, authorize: true)
expect(strategy).to receive(:authorize).once
allow(controller).to receive(:strategy) { strategy }
allow(controller).to receive(:create) do
2.times { controller.send :authorize_response }
controller.render json: {}, status: :ok
end
post :create
end
end
end
doorkeeper-5.0.2/spec/validators/ 0000755 0000041 0000041 00000000000 13410042500 016774 5 ustar www-data www-data doorkeeper-5.0.2/spec/validators/redirect_uri_validator_spec.rb 0000644 0000041 0000041 00000010267 13410042500 025066 0 ustar www-data www-data require 'spec_helper'
describe RedirectUriValidator do
subject do
FactoryBot.create(:application)
end
it 'is valid when the uri is a uri' do
subject.redirect_uri = 'https://example.com/callback'
expect(subject).to be_valid
end
# Most mobile and desktop operating systems allow apps to register a custom URL
# scheme that will launch the app when a URL with that scheme is visited from
# the system browser.
#
# @see https://www.oauth.com/oauth2-servers/redirect-uris/redirect-uris-native-apps/
it 'is valid when the uri is custom native URI' do
subject.redirect_uri = 'myapp://callback'
expect(subject).to be_valid
end
it 'is valid when the uri has a query parameter' do
subject.redirect_uri = 'https://example.com/abcd?xyz=123'
expect(subject).to be_valid
end
it 'accepts native redirect uri' do
subject.redirect_uri = 'urn:ietf:wg:oauth:2.0:oob'
expect(subject).to be_valid
end
it 'rejects if test uri is disabled' do
allow(RedirectUriValidator).to receive(:native_redirect_uri).and_return(nil)
subject.redirect_uri = 'urn:some:test'
expect(subject).not_to be_valid
end
it 'is invalid when the uri is not a uri' do
subject.redirect_uri = ']'
expect(subject).not_to be_valid
expect(subject.errors[:redirect_uri].first).to eq('must be a valid URI.')
end
it 'is invalid when the uri is relative' do
subject.redirect_uri = '/abcd'
expect(subject).not_to be_valid
expect(subject.errors[:redirect_uri].first).to eq('must be an absolute URI.')
end
it 'is invalid when the uri has a fragment' do
subject.redirect_uri = 'https://example.com/abcd#xyz'
expect(subject).not_to be_valid
expect(subject.errors[:redirect_uri].first).to eq('cannot contain a fragment.')
end
context 'force secured uri' do
it 'accepts an valid uri' do
subject.redirect_uri = 'https://example.com/callback'
expect(subject).to be_valid
end
it 'accepts native redirect uri' do
subject.redirect_uri = 'urn:ietf:wg:oauth:2.0:oob'
expect(subject).to be_valid
end
it 'accepts app redirect uri' do
subject.redirect_uri = 'some-awesome-app://oauth/callback'
expect(subject).to be_valid
end
it 'accepts a non secured protocol when disabled' do
subject.redirect_uri = 'http://example.com/callback'
allow(Doorkeeper.configuration).to receive(
:force_ssl_in_redirect_uri
).and_return(false)
expect(subject).to be_valid
end
it 'accepts a non secured protocol when conditional option defined' do
Doorkeeper.configure do
orm DOORKEEPER_ORM
force_ssl_in_redirect_uri { |uri| uri.host != 'localhost' }
end
application = FactoryBot.build(:application, redirect_uri: 'http://localhost/callback')
expect(application).to be_valid
application = FactoryBot.build(:application, redirect_uri: 'https://test.com/callback')
expect(application).to be_valid
application = FactoryBot.build(:application, redirect_uri: 'http://localhost2/callback')
expect(application).not_to be_valid
application = FactoryBot.build(:application, redirect_uri: 'https://test.com/callback')
expect(application).to be_valid
end
it 'forbids redirect uri if required' do
subject.redirect_uri = 'javascript://document.cookie'
Doorkeeper.configure do
orm DOORKEEPER_ORM
forbid_redirect_uri { |uri| uri.scheme == 'javascript' }
end
expect(subject).to be_invalid
expect(subject.errors[:redirect_uri].first).to eq('is forbidden by the server.')
subject.redirect_uri = 'https://localhost/callback'
expect(subject).to be_valid
end
it 'invalidates the uri when the uri does not use a secure protocol' do
subject.redirect_uri = 'http://example.com/callback'
expect(subject).not_to be_valid
error = subject.errors[:redirect_uri].first
expect(error).to eq('must be an HTTPS/SSL URI.')
end
end
context 'multiple redirect uri' do
it 'invalidates the second uri when the first uri is native uri' do
subject.redirect_uri = "urn:ietf:wg:oauth:2.0:oob\nexample.com/callback"
expect(subject).to be_invalid
end
end
end
doorkeeper-5.0.2/spec/models/ 0000755 0000041 0000041 00000000000 13410042500 016107 5 ustar www-data www-data doorkeeper-5.0.2/spec/models/doorkeeper/ 0000755 0000041 0000041 00000000000 13410042500 020246 5 ustar www-data www-data doorkeeper-5.0.2/spec/models/doorkeeper/application_spec.rb 0000644 0000041 0000041 00000022317 13410042500 024115 0 ustar www-data www-data require 'spec_helper'
module Doorkeeper
describe Application do
let(:require_owner) { Doorkeeper.configuration.instance_variable_set('@confirm_application_owner', true) }
let(:unset_require_owner) { Doorkeeper.configuration.instance_variable_set('@confirm_application_owner', false) }
let(:new_application) { FactoryBot.build(:application) }
let(:uid) { SecureRandom.hex(8) }
let(:secret) { SecureRandom.hex(8) }
context 'application_owner is enabled' do
before do
Doorkeeper.configure do
orm DOORKEEPER_ORM
enable_application_owner
end
end
context 'application owner is not required' do
before(:each) do
unset_require_owner
end
it 'is valid given valid attributes' do
expect(new_application).to be_valid
end
end
context 'application owner is required' do
before(:each) do
require_owner
@owner = FactoryBot.build_stubbed(:doorkeeper_testing_user)
end
it 'is invalid without an owner' do
expect(new_application).not_to be_valid
end
it 'is valid with an owner' do
new_application.owner = @owner
expect(new_application).to be_valid
end
end
end
it 'is invalid without a name' do
new_application.name = nil
expect(new_application).not_to be_valid
end
it 'is invalid without determining confidentiality' do
new_application.confidential = nil
expect(new_application).not_to be_valid
end
it 'generates uid on create' do
expect(new_application.uid).to be_nil
new_application.save
expect(new_application.uid).not_to be_nil
end
it 'generates uid on create if an empty string' do
new_application.uid = ''
new_application.save
expect(new_application.uid).not_to be_blank
end
it 'generates uid on create unless one is set' do
new_application.uid = uid
new_application.save
expect(new_application.uid).to eq(uid)
end
it 'is invalid without uid' do
new_application.save
new_application.uid = nil
expect(new_application).not_to be_valid
end
it 'is invalid without redirect_uri' do
new_application.save
new_application.redirect_uri = nil
expect(new_application).not_to be_valid
end
it 'checks uniqueness of uid' do
app1 = FactoryBot.create(:application)
app2 = FactoryBot.create(:application)
app2.uid = app1.uid
expect(app2).not_to be_valid
end
it 'expects database to throw an error when uids are the same' do
app1 = FactoryBot.create(:application)
app2 = FactoryBot.create(:application)
app2.uid = app1.uid
expect { app2.save!(validate: false) }.to raise_error(uniqueness_error)
end
it 'generate secret on create' do
expect(new_application.secret).to be_nil
new_application.save
expect(new_application.secret).not_to be_nil
end
it 'generate secret on create if is blank string' do
new_application.secret = ''
new_application.save
expect(new_application.secret).not_to be_blank
end
it 'generate secret on create unless one is set' do
new_application.secret = secret
new_application.save
expect(new_application.secret).to eq(secret)
end
it 'is invalid without secret' do
new_application.save
new_application.secret = nil
expect(new_application).not_to be_valid
end
describe 'destroy related models on cascade' do
before(:each) do
new_application.save
end
it 'should destroy its access grants' do
FactoryBot.create(:access_grant, application: new_application)
expect { new_application.destroy }.to change { Doorkeeper::AccessGrant.count }.by(-1)
end
it 'should destroy its access tokens' do
FactoryBot.create(:access_token, application: new_application)
FactoryBot.create(:access_token, application: new_application, revoked_at: Time.now.utc)
expect do
new_application.destroy
end.to change { Doorkeeper::AccessToken.count }.by(-2)
end
end
describe :ordered_by do
let(:applications) { FactoryBot.create_list(:application, 5) }
context 'when a direction is not specified' do
it 'calls order with a default order of asc' do
names = applications.map(&:name).sort
expect(Application.ordered_by(:name).map(&:name)).to eq(names)
end
end
context 'when a direction is specified' do
it 'calls order with specified direction' do
names = applications.map(&:name).sort.reverse
expect(Application.ordered_by(:name, :desc).map(&:name)).to eq(names)
end
end
end
describe "#redirect_uri=" do
context "when array of valid redirect_uris" do
it "should join by newline" do
new_application.redirect_uri = ['http://localhost/callback1', 'http://localhost/callback2']
expect(new_application.redirect_uri).to eq("http://localhost/callback1\nhttp://localhost/callback2")
end
end
context "when string of valid redirect_uris" do
it "should store as-is" do
new_application.redirect_uri = "http://localhost/callback1\nhttp://localhost/callback2"
expect(new_application.redirect_uri).to eq("http://localhost/callback1\nhttp://localhost/callback2")
end
end
end
describe :authorized_for do
let(:resource_owner) { double(:resource_owner, id: 10) }
it 'is empty if the application is not authorized for anyone' do
expect(Application.authorized_for(resource_owner)).to be_empty
end
it 'returns only application for a specific resource owner' do
FactoryBot.create(:access_token, resource_owner_id: resource_owner.id + 1)
token = FactoryBot.create(:access_token, resource_owner_id: resource_owner.id)
expect(Application.authorized_for(resource_owner)).to eq([token.application])
end
it 'excludes revoked tokens' do
FactoryBot.create(:access_token, resource_owner_id: resource_owner.id, revoked_at: 2.days.ago)
expect(Application.authorized_for(resource_owner)).to be_empty
end
it 'returns all applications that have been authorized' do
token1 = FactoryBot.create(:access_token, resource_owner_id: resource_owner.id)
token2 = FactoryBot.create(:access_token, resource_owner_id: resource_owner.id)
expect(Application.authorized_for(resource_owner)).to eq([token1.application, token2.application])
end
it 'returns only one application even if it has been authorized twice' do
application = FactoryBot.create(:application)
FactoryBot.create(:access_token, resource_owner_id: resource_owner.id, application: application)
FactoryBot.create(:access_token, resource_owner_id: resource_owner.id, application: application)
expect(Application.authorized_for(resource_owner)).to eq([application])
end
end
describe :revoke_tokens_and_grants_for do
it 'revokes all access tokens and access grants' do
application_id = 42
resource_owner = double
expect(Doorkeeper::AccessToken)
.to receive(:revoke_all_for).with(application_id, resource_owner)
expect(Doorkeeper::AccessGrant)
.to receive(:revoke_all_for).with(application_id, resource_owner)
Application.revoke_tokens_and_grants_for(application_id, resource_owner)
end
end
describe :by_uid_and_secret do
context "when application is private/confidential" do
it "finds the application via uid/secret" do
app = FactoryBot.create :application
authenticated = Application.by_uid_and_secret(app.uid, app.secret)
expect(authenticated).to eq(app)
end
context "when secret is wrong" do
it "should not find the application" do
app = FactoryBot.create :application
authenticated = Application.by_uid_and_secret(app.uid, 'bad')
expect(authenticated).to eq(nil)
end
end
end
context "when application is public/non-confidential" do
context "when secret is blank" do
it "should find the application" do
app = FactoryBot.create :application, confidential: false
authenticated = Application.by_uid_and_secret(app.uid, nil)
expect(authenticated).to eq(app)
end
end
context "when secret is wrong" do
it "should not find the application" do
app = FactoryBot.create :application, confidential: false
authenticated = Application.by_uid_and_secret(app.uid, 'bad')
expect(authenticated).to eq(nil)
end
end
end
end
describe :confidential? do
subject { FactoryBot.create(:application, confidential: confidential).confidential? }
context 'when application is private/confidential' do
let(:confidential) { true }
it { expect(subject).to eq(true) }
end
context 'when application is public/non-confidential' do
let(:confidential) { false }
it { expect(subject).to eq(false) }
end
end
end
end
doorkeeper-5.0.2/spec/models/doorkeeper/access_token_spec.rb 0000644 0000041 0000041 00000037713 13410042500 024261 0 ustar www-data www-data require 'spec_helper'
module Doorkeeper
describe AccessToken do
subject { FactoryBot.build(:access_token) }
it { expect(subject).to be_valid }
it_behaves_like 'an accessible token'
it_behaves_like 'a revocable token'
it_behaves_like 'a unique token' do
let(:factory_name) { :access_token }
end
module CustomGeneratorArgs
def self.generate; end
end
describe :generate_token do
it 'generates a token using the default method' do
FactoryBot.create :access_token
token = FactoryBot.create :access_token
expect(token.token).to be_a(String)
end
it 'generates a token using a custom object' do
eigenclass = class << CustomGeneratorArgs; self; end
eigenclass.class_eval do
remove_method :generate
end
module CustomGeneratorArgs
def self.generate(opts = {})
"custom_generator_token_#{opts[:resource_owner_id]}"
end
end
Doorkeeper.configure do
orm DOORKEEPER_ORM
access_token_generator "Doorkeeper::CustomGeneratorArgs"
end
token = FactoryBot.create :access_token
expect(token.token).to match(/custom_generator_token_\d+/)
end
it 'allows the custom generator to access the application details' do
eigenclass = class << CustomGeneratorArgs; self; end
eigenclass.class_eval do
remove_method :generate
end
module CustomGeneratorArgs
def self.generate(opts = {})
"custom_generator_token_#{opts[:application].name}"
end
end
Doorkeeper.configure do
orm DOORKEEPER_ORM
access_token_generator "Doorkeeper::CustomGeneratorArgs"
end
token = FactoryBot.create :access_token
expect(token.token).to match(/custom_generator_token_Application \d+/)
end
it 'allows the custom generator to access the scopes' do
eigenclass = class << CustomGeneratorArgs; self; end
eigenclass.class_eval do
remove_method :generate
end
module CustomGeneratorArgs
def self.generate(opts = {})
"custom_generator_token_#{opts[:scopes].count}_#{opts[:scopes]}"
end
end
Doorkeeper.configure do
orm DOORKEEPER_ORM
access_token_generator "Doorkeeper::CustomGeneratorArgs"
end
token = FactoryBot.create :access_token, scopes: 'public write'
expect(token.token).to eq 'custom_generator_token_2_public write'
end
it 'allows the custom generator to access the expiry length' do
eigenclass = class << CustomGeneratorArgs; self; end
eigenclass.class_eval do
remove_method :generate
end
module CustomGeneratorArgs
def self.generate(opts = {})
"custom_generator_token_#{opts[:expires_in]}"
end
end
Doorkeeper.configure do
orm DOORKEEPER_ORM
access_token_generator "Doorkeeper::CustomGeneratorArgs"
end
token = FactoryBot.create :access_token
expect(token.token).to eq 'custom_generator_token_7200'
end
it 'allows the custom generator to access the created time' do
module CustomGeneratorArgs
def self.generate(opts = {})
"custom_generator_token_#{opts[:created_at].to_i}"
end
end
Doorkeeper.configure do
orm DOORKEEPER_ORM
access_token_generator "Doorkeeper::CustomGeneratorArgs"
end
token = FactoryBot.create :access_token
created_at = token.created_at
expect(token.token).to eq "custom_generator_token_#{created_at.to_i}"
end
it 'raises an error if the custom object does not support generate' do
module NoGenerate
end
Doorkeeper.configure do
orm DOORKEEPER_ORM
access_token_generator "Doorkeeper::NoGenerate"
end
expect { FactoryBot.create :access_token }.to(
raise_error(Doorkeeper::Errors::UnableToGenerateToken)
)
end
it 'raises original error if something went wrong in custom generator' do
eigenclass = class << CustomGeneratorArgs; self; end
eigenclass.class_eval do
remove_method :generate
end
module CustomGeneratorArgs
def self.generate(_opts = {})
raise LoadError, 'custom behaviour'
end
end
Doorkeeper.configure do
orm DOORKEEPER_ORM
access_token_generator "Doorkeeper::CustomGeneratorArgs"
end
expect { FactoryBot.create :access_token }.to(
raise_error(LoadError)
)
end
it 'raises an error if the custom object does not exist' do
Doorkeeper.configure do
orm DOORKEEPER_ORM
access_token_generator "Doorkeeper::NotReal"
end
expect { FactoryBot.create :access_token }.to(
raise_error(Doorkeeper::Errors::TokenGeneratorNotFound, /NotReal/)
)
end
end
describe :refresh_token do
it 'has empty refresh token if it was not required' do
token = FactoryBot.create :access_token
expect(token.refresh_token).to be_nil
end
it 'generates a refresh token if it was requested' do
token = FactoryBot.create :access_token, use_refresh_token: true
expect(token.refresh_token).not_to be_nil
end
it 'is not valid if token exists' do
token1 = FactoryBot.create :access_token, use_refresh_token: true
token2 = FactoryBot.create :access_token, use_refresh_token: true
token2.refresh_token = token1.refresh_token
expect(token2).not_to be_valid
end
it 'expects database to raise an error if refresh tokens are the same' do
token1 = FactoryBot.create :access_token, use_refresh_token: true
token2 = FactoryBot.create :access_token, use_refresh_token: true
expect do
token2.refresh_token = token1.refresh_token
token2.save(validate: false)
end.to raise_error(uniqueness_error)
end
end
describe 'validations' do
it 'is valid without resource_owner_id' do
# For client credentials flow
subject.resource_owner_id = nil
expect(subject).to be_valid
end
it 'is valid without application_id' do
# For resource owner credentials flow
subject.application_id = nil
expect(subject).to be_valid
end
end
describe '#same_credential?' do
context 'with default parameters' do
let(:resource_owner_id) { 100 }
let(:application) { FactoryBot.create :application }
let(:default_attributes) do
{ application: application, resource_owner_id: resource_owner_id }
end
let(:access_token1) { FactoryBot.create :access_token, default_attributes }
context 'the second token has the same owner and same app' do
let(:access_token2) { FactoryBot.create :access_token, default_attributes }
it 'success' do
expect(access_token1.same_credential?(access_token2)).to be_truthy
end
end
context 'the second token has same owner and different app' do
let(:other_application) { FactoryBot.create :application }
let(:access_token2) do
FactoryBot.create :access_token,
application: other_application,
resource_owner_id: resource_owner_id
end
it 'fail' do
expect(access_token1.same_credential?(access_token2)).to be_falsey
end
end
context 'the second token has different owner and different app' do
let(:other_application) { FactoryBot.create :application }
let(:access_token2) do
FactoryBot.create :access_token, application: other_application, resource_owner_id: 42
end
it 'fail' do
expect(access_token1.same_credential?(access_token2)).to be_falsey
end
end
context 'the second token has different owner and same app' do
let(:access_token2) do
FactoryBot.create :access_token, application: application, resource_owner_id: 42
end
it 'fail' do
expect(access_token1.same_credential?(access_token2)).to be_falsey
end
end
end
end
describe '#acceptable?' do
context 'a token that is not accessible' do
let(:token) { FactoryBot.create(:access_token, created_at: 6.hours.ago) }
it 'should return false' do
expect(token.acceptable?(nil)).to be false
end
end
context 'a token that has the incorrect scopes' do
let(:token) { FactoryBot.create(:access_token) }
it 'should return false' do
expect(token.acceptable?(['public'])).to be false
end
end
context 'a token is acceptable with the correct scopes' do
let(:token) do
token = FactoryBot.create(:access_token)
token[:scopes] = 'public'
token
end
it 'should return true' do
expect(token.acceptable?(['public'])).to be true
end
end
end
describe '.revoke_all_for' do
let(:resource_owner) { double(id: 100) }
let(:application) { FactoryBot.create :application }
let(:default_attributes) do
{ application: application, resource_owner_id: resource_owner.id }
end
it 'revokes all tokens for given application and resource owner' do
FactoryBot.create :access_token, default_attributes
AccessToken.revoke_all_for application.id, resource_owner
AccessToken.all.each do |token|
expect(token).to be_revoked
end
end
it 'matches application' do
access_token_for_different_app = FactoryBot.create(
:access_token,
default_attributes.merge(application: FactoryBot.create(:application))
)
AccessToken.revoke_all_for application.id, resource_owner
expect(access_token_for_different_app.reload).not_to be_revoked
end
it 'matches resource owner' do
access_token_for_different_owner = FactoryBot.create(
:access_token,
default_attributes.merge(resource_owner_id: 90)
)
AccessToken.revoke_all_for application.id, resource_owner
expect(access_token_for_different_owner.reload).not_to be_revoked
end
end
describe '.matching_token_for' do
let(:resource_owner_id) { 100 }
let(:application) { FactoryBot.create :application }
let(:scopes) { Doorkeeper::OAuth::Scopes.from_string('public write') }
let(:default_attributes) do
{
application: application,
resource_owner_id: resource_owner_id,
scopes: scopes.to_s
}
end
before do
default_scopes_exist(*scopes.all)
end
it 'returns only one token' do
token = FactoryBot.create :access_token, default_attributes
last_token = AccessToken.matching_token_for(application, resource_owner_id, scopes)
expect(last_token).to eq(token)
end
it 'accepts resource owner as object' do
resource_owner = double(to_key: true, id: 100)
token = FactoryBot.create :access_token, default_attributes
last_token = AccessToken.matching_token_for(application, resource_owner, scopes)
expect(last_token).to eq(token)
end
it 'accepts nil as resource owner' do
token = FactoryBot.create :access_token, default_attributes.merge(resource_owner_id: nil)
last_token = AccessToken.matching_token_for(application, nil, scopes)
expect(last_token).to eq(token)
end
it 'excludes revoked tokens' do
FactoryBot.create :access_token, default_attributes.merge(revoked_at: 1.day.ago)
last_token = AccessToken.matching_token_for(application, resource_owner_id, scopes)
expect(last_token).to be_nil
end
it "excludes tokens with a different application" do
FactoryBot.create :access_token, default_attributes.merge(application: FactoryBot.create(:application))
last_token = AccessToken.matching_token_for(application, resource_owner_id, scopes)
expect(last_token).to be_nil
end
it "excludes tokens with a different resource owner" do
FactoryBot.create :access_token, default_attributes.merge(resource_owner_id: 2)
last_token = AccessToken.matching_token_for(application, resource_owner_id, scopes)
expect(last_token).to be_nil
end
it "excludes tokens with fewer scopes" do
FactoryBot.create :access_token, default_attributes.merge(scopes: 'public')
last_token = AccessToken.matching_token_for(application, resource_owner_id, scopes)
expect(last_token).to be_nil
end
it 'excludes tokens with different scopes' do
FactoryBot.create :access_token, default_attributes.merge(scopes: 'public email')
last_token = AccessToken.matching_token_for(application, resource_owner_id, scopes)
expect(last_token).to be_nil
end
it 'excludes tokens with additional scopes' do
FactoryBot.create :access_token, default_attributes.merge(scopes: 'public write email')
last_token = AccessToken.matching_token_for(application, resource_owner_id, scopes)
expect(last_token).to be_nil
end
it 'excludes tokens with scopes that are not present in server scopes' do
FactoryBot.create :access_token, default_attributes.merge(
application: application, scopes: 'public read'
)
last_token = AccessToken.matching_token_for(application, resource_owner_id, scopes)
expect(last_token).to be_nil
end
it 'excludes tokens with scopes that are not present in application scopes' do
application = FactoryBot.create :application, scopes: "private read"
FactoryBot.create :access_token, default_attributes.merge(
application: application
)
last_token = AccessToken.matching_token_for(application, resource_owner_id, scopes)
expect(last_token).to be_nil
end
it 'does not match token if empty scope requested and token/app scopes present' do
application = FactoryBot.create :application, scopes: "sample:scope"
app_params = {
application_id: application.id, scopes: "sample:scope",
resource_owner_id: 100
}
FactoryBot.create :access_token, app_params
empty_scopes = Doorkeeper::OAuth::Scopes.from_string("")
last_token = AccessToken.matching_token_for(application, 100, empty_scopes)
expect(last_token).to be_nil
end
it 'matches token if empty scope requested and no token scopes present' do
empty_scopes = Doorkeeper::OAuth::Scopes.from_string("")
token = FactoryBot.create :access_token, default_attributes.merge(scopes: empty_scopes)
last_token = AccessToken.matching_token_for(application, 100, empty_scopes)
expect(last_token).to eq(token)
end
it 'returns the last matching token' do
FactoryBot.create :access_token, default_attributes.merge(created_at: 1.day.ago)
matching_token = FactoryBot.create :access_token, default_attributes
FactoryBot.create :access_token, default_attributes.merge(scopes: 'public')
last_token = AccessToken.matching_token_for(application, resource_owner_id, scopes)
expect(last_token).to eq(matching_token)
end
end
describe "#as_json" do
it "returns as_json hash" do
token = FactoryBot.create :access_token
token_hash = {
resource_owner_id: token.resource_owner_id,
scope: token.scopes,
expires_in: token.expires_in_seconds,
application: { uid: token.application.uid },
created_at: token.created_at.to_i
}
expect(token.as_json).to eq token_hash
end
end
end
end
doorkeeper-5.0.2/spec/models/doorkeeper/access_grant_spec.rb 0000644 0000041 0000041 00000004101 13410042500 024235 0 ustar www-data www-data require 'spec_helper'
describe Doorkeeper::AccessGrant do
subject { FactoryBot.build(:access_grant) }
it { expect(subject).to be_valid }
it_behaves_like 'an accessible token'
it_behaves_like 'a revocable token'
it_behaves_like 'a unique token' do
let(:factory_name) { :access_grant }
end
describe 'validations' do
it 'is invalid without resource_owner_id' do
subject.resource_owner_id = nil
expect(subject).not_to be_valid
end
it 'is invalid without application_id' do
subject.application_id = nil
expect(subject).not_to be_valid
end
it 'is invalid without token' do
subject.save
subject.token = nil
expect(subject).not_to be_valid
end
it 'is invalid without expires_in' do
subject.expires_in = nil
expect(subject).not_to be_valid
end
end
describe '.revoke_all_for' do
let(:resource_owner) { double(id: 100) }
let(:application) { FactoryBot.create :application }
let(:default_attributes) do
{
application: application,
resource_owner_id: resource_owner.id
}
end
it 'revokes all tokens for given application and resource owner' do
FactoryBot.create :access_grant, default_attributes
described_class.revoke_all_for(application.id, resource_owner)
described_class.all.each do |token|
expect(token).to be_revoked
end
end
it 'matches application' do
access_grant_for_different_app = FactoryBot.create(
:access_grant,
default_attributes.merge(application: FactoryBot.create(:application))
)
described_class.revoke_all_for(application.id, resource_owner)
expect(access_grant_for_different_app.reload).not_to be_revoked
end
it 'matches resource owner' do
access_grant_for_different_owner = FactoryBot.create(
:access_grant,
default_attributes.merge(resource_owner_id: 90)
)
described_class.revoke_all_for application.id, resource_owner
expect(access_grant_for_different_owner.reload).not_to be_revoked
end
end
end
doorkeeper-5.0.2/spec/version/ 0000755 0000041 0000041 00000000000 13410042500 016311 5 ustar www-data www-data doorkeeper-5.0.2/spec/version/version_spec.rb 0000644 0000041 0000041 00000000562 13410042500 021340 0 ustar www-data www-data require 'spec_helper'
describe Doorkeeper::VERSION do
context '#gem_version' do
it 'returns Gem::Version instance' do
expect(Doorkeeper.gem_version).to be_an_instance_of(Gem::Version)
end
end
context 'VERSION' do
it 'returns gem version string' do
expect(Doorkeeper::VERSION::STRING).to match(/^\d+\.\d+\.\d+(\.\w+)?$/)
end
end
end
doorkeeper-5.0.2/spec/routing/ 0000755 0000041 0000041 00000000000 13410042500 016313 5 ustar www-data www-data doorkeeper-5.0.2/spec/routing/scoped_routes_spec.rb 0000644 0000041 0000041 00000002714 13410042500 022534 0 ustar www-data www-data require 'spec_helper'
describe 'Scoped routes' do
before :all do
Rails.application.routes.disable_clear_and_finalize = true
Rails.application.routes.draw do
use_doorkeeper scope: 'scope'
end
end
after :all do
Rails.application.routes.clear!
load File.expand_path('../dummy/config/routes.rb', __dir__)
end
it 'GET /scope/authorize routes to authorizations controller' do
expect(get('/scope/authorize')).to route_to('doorkeeper/authorizations#new')
end
it 'POST /scope/authorize routes to authorizations controller' do
expect(post('/scope/authorize')).to route_to('doorkeeper/authorizations#create')
end
it 'DELETE /scope/authorize routes to authorizations controller' do
expect(delete('/scope/authorize')).to route_to('doorkeeper/authorizations#destroy')
end
it 'POST /scope/token routes to tokens controller' do
expect(post('/scope/token')).to route_to('doorkeeper/tokens#create')
end
it 'GET /scope/applications routes to applications controller' do
expect(get('/scope/applications')).to route_to('doorkeeper/applications#index')
end
it 'GET /scope/authorized_applications routes to authorized applications controller' do
expect(get('/scope/authorized_applications')).to route_to('doorkeeper/authorized_applications#index')
end
it 'GET /scope/token/info route to authorized TokenInfo controller' do
expect(get('/scope/token/info')).to route_to('doorkeeper/token_info#show')
end
end
doorkeeper-5.0.2/spec/routing/default_routes_spec.rb 0000644 0000041 0000041 00000002710 13410042500 022677 0 ustar www-data www-data require 'spec_helper'
describe 'Default routes' do
it 'GET /oauth/authorize routes to authorizations controller' do
expect(get('/oauth/authorize')).to route_to('doorkeeper/authorizations#new')
end
it 'POST /oauth/authorize routes to authorizations controller' do
expect(post('/oauth/authorize')).to route_to('doorkeeper/authorizations#create')
end
it 'DELETE /oauth/authorize routes to authorizations controller' do
expect(delete('/oauth/authorize')).to route_to('doorkeeper/authorizations#destroy')
end
it 'POST /oauth/token routes to tokens controller' do
expect(post('/oauth/token')).to route_to('doorkeeper/tokens#create')
end
it 'POST /oauth/revoke routes to tokens controller' do
expect(post('/oauth/revoke')).to route_to('doorkeeper/tokens#revoke')
end
it 'POST /oauth/introspect routes to tokens controller' do
expect(post('/oauth/introspect')).to route_to('doorkeeper/tokens#introspect')
end
it 'GET /oauth/applications routes to applications controller' do
expect(get('/oauth/applications')).to route_to('doorkeeper/applications#index')
end
it 'GET /oauth/authorized_applications routes to authorized applications controller' do
expect(get('/oauth/authorized_applications')).to route_to('doorkeeper/authorized_applications#index')
end
it 'GET /oauth/token/info route to authorized TokenInfo controller' do
expect(get('/oauth/token/info')).to route_to('doorkeeper/token_info#show')
end
end
doorkeeper-5.0.2/spec/routing/custom_controller_routes_spec.rb 0000644 0000041 0000041 00000011037 13410042500 025032 0 ustar www-data www-data require 'spec_helper'
describe 'Custom controller for routes' do
before :all do
Rails.application.routes.disable_clear_and_finalize = true
Rails.application.routes.draw do
scope 'inner_space' do
use_doorkeeper scope: 'scope' do
controllers authorizations: 'custom_authorizations',
tokens: 'custom_authorizations',
applications: 'custom_authorizations',
token_info: 'custom_authorizations'
as authorizations: 'custom_auth',
tokens: 'custom_token',
token_info: 'custom_token_info'
end
end
scope 'space' do
use_doorkeeper do
controllers authorizations: 'custom_authorizations',
tokens: 'custom_authorizations',
applications: 'custom_authorizations',
token_info: 'custom_authorizations'
as authorizations: 'custom_auth',
tokens: 'custom_token',
token_info: 'custom_token_info'
end
end
scope 'outer_space' do
use_doorkeeper do
controllers authorizations: 'custom_authorizations',
tokens: 'custom_authorizations',
token_info: 'custom_authorizations'
as authorizations: 'custom_auth',
tokens: 'custom_token',
token_info: 'custom_token_info'
skip_controllers :tokens, :applications, :token_info
end
end
end
end
after :all do
Rails.application.routes.clear!
load File.expand_path('../dummy/config/routes.rb', __dir__)
end
it 'GET /inner_space/scope/authorize routes to custom authorizations controller' do
expect(get('/inner_space/scope/authorize')).to route_to('custom_authorizations#new')
end
it 'POST /inner_space/scope/authorize routes to custom authorizations controller' do
expect(post('/inner_space/scope/authorize')).to route_to('custom_authorizations#create')
end
it 'DELETE /inner_space/scope/authorize routes to custom authorizations controller' do
expect(delete('/inner_space/scope/authorize')).to route_to('custom_authorizations#destroy')
end
it 'POST /inner_space/scope/token routes to tokens controller' do
expect(post('/inner_space/scope/token')).to route_to('custom_authorizations#create')
end
it 'GET /inner_space/scope/applications routes to applications controller' do
expect(get('/inner_space/scope/applications')).to route_to('custom_authorizations#index')
end
it 'GET /inner_space/scope/token/info routes to the token_info controller' do
expect(get('/inner_space/scope/token/info')).to route_to('custom_authorizations#show')
end
it 'GET /space/oauth/authorize routes to custom authorizations controller' do
expect(get('/space/oauth/authorize')).to route_to('custom_authorizations#new')
end
it 'POST /space/oauth/authorize routes to custom authorizations controller' do
expect(post('/space/oauth/authorize')).to route_to('custom_authorizations#create')
end
it 'DELETE /space/oauth/authorize routes to custom authorizations controller' do
expect(delete('/space/oauth/authorize')).to route_to('custom_authorizations#destroy')
end
it 'POST /space/oauth/token routes to tokens controller' do
expect(post('/space/oauth/token')).to route_to('custom_authorizations#create')
end
it 'POST /space/oauth/revoke routes to tokens controller' do
expect(post('/space/oauth/revoke')).to route_to('custom_authorizations#revoke')
end
it 'POST /space/oauth/introspect routes to tokens controller' do
expect(post('/space/oauth/introspect')).to route_to('custom_authorizations#introspect')
end
it 'GET /space/oauth/applications routes to applications controller' do
expect(get('/space/oauth/applications')).to route_to('custom_authorizations#index')
end
it 'GET /space/oauth/token/info routes to the token_info controller' do
expect(get('/space/oauth/token/info')).to route_to('custom_authorizations#show')
end
it 'POST /outer_space/oauth/token is not be routable' do
expect(post('/outer_space/oauth/token')).not_to be_routable
end
it 'GET /outer_space/oauth/authorize routes to custom authorizations controller' do
expect(get('/outer_space/oauth/authorize')).to be_routable
end
it 'GET /outer_space/oauth/applications is not routable' do
expect(get('/outer_space/oauth/applications')).not_to be_routable
end
it 'GET /outer_space/oauth/token_info is not routable' do
expect(get('/outer_space/oauth/token/info')).not_to be_routable
end
end
doorkeeper-5.0.2/spec/generators/ 0000755 0000041 0000041 00000000000 13410042500 016775 5 ustar www-data www-data doorkeeper-5.0.2/spec/generators/pkce_generator_spec.rb 0000644 0000041 0000041 00000002211 13410042500 023320 0 ustar www-data www-data # frozen_string_literal: true
require 'spec_helper'
require 'generators/doorkeeper/pkce_generator'
describe 'Doorkeeper::PkceGenerator' do
include GeneratorSpec::TestCase
tests Doorkeeper::PkceGenerator
destination ::File.expand_path('../tmp/dummy', __FILE__)
describe 'after running the generator' do
before :each do
prepare_destination
end
context 'pre Rails 5.0.0' do
it 'creates a migration with no version specifier' do
stub_const("ActiveRecord::VERSION::MAJOR", 4)
stub_const("ActiveRecord::VERSION::MINOR", 2)
run_generator
assert_migration 'db/migrate/enable_pkce.rb' do |migration|
assert migration.include?("ActiveRecord::Migration\n")
end
end
end
context 'post Rails 5.0.0' do
it 'creates a migration with a version specifier' do
stub_const("ActiveRecord::VERSION::MAJOR", 5)
stub_const("ActiveRecord::VERSION::MINOR", 0)
run_generator
assert_migration 'db/migrate/enable_pkce.rb' do |migration|
assert migration.include?("ActiveRecord::Migration[5.0]\n")
end
end
end
end
end
doorkeeper-5.0.2/spec/generators/install_generator_spec.rb 0000644 0000041 0000041 00000001754 13410042500 024057 0 ustar www-data www-data require 'spec_helper'
require 'generators/doorkeeper/install_generator'
describe 'Doorkeeper::InstallGenerator' do
include GeneratorSpec::TestCase
tests Doorkeeper::InstallGenerator
destination ::File.expand_path('../tmp/dummy', __FILE__)
describe 'after running the generator' do
before :each do
prepare_destination
FileUtils.mkdir(::File.expand_path('config', Pathname(destination_root)))
FileUtils.mkdir(::File.expand_path('db', Pathname(destination_root)))
FileUtils.copy_file(
::File.expand_path('../templates/routes.rb', __FILE__),
::File.expand_path('config/routes.rb', Pathname.new(destination_root))
)
run_generator
end
it 'creates an initializer file' do
assert_file 'config/initializers/doorkeeper.rb'
end
it 'copies the locale file' do
assert_file 'config/locales/doorkeeper.en.yml'
end
it 'adds sample route' do
assert_file 'config/routes.rb', /use_doorkeeper/
end
end
end
doorkeeper-5.0.2/spec/generators/application_owner_generator_spec.rb 0000644 0000041 0000041 00000002251 13410042500 026117 0 ustar www-data www-data require 'spec_helper'
require 'generators/doorkeeper/application_owner_generator'
describe 'Doorkeeper::ApplicationOwnerGenerator' do
include GeneratorSpec::TestCase
tests Doorkeeper::ApplicationOwnerGenerator
destination ::File.expand_path('../tmp/dummy', __FILE__)
describe 'after running the generator' do
before :each do
prepare_destination
end
context 'pre Rails 5.0.0' do
it 'creates a migration with no version specifier' do
stub_const("ActiveRecord::VERSION::MAJOR", 4)
stub_const("ActiveRecord::VERSION::MINOR", 2)
run_generator
assert_migration 'db/migrate/add_owner_to_application.rb' do |migration|
assert migration.include?("ActiveRecord::Migration\n")
end
end
end
context 'post Rails 5.0.0' do
it 'creates a migration with a version specifier' do
stub_const("ActiveRecord::VERSION::MAJOR", 5)
stub_const("ActiveRecord::VERSION::MINOR", 0)
run_generator
assert_migration 'db/migrate/add_owner_to_application.rb' do |migration|
assert migration.include?("ActiveRecord::Migration[5.0]\n")
end
end
end
end
end
doorkeeper-5.0.2/spec/generators/templates/ 0000755 0000041 0000041 00000000000 13410042500 020773 5 ustar www-data www-data doorkeeper-5.0.2/spec/generators/templates/routes.rb 0000644 0000041 0000041 00000000045 13410042500 022640 0 ustar www-data www-data Rails.application.routes.draw do
end
doorkeeper-5.0.2/spec/generators/views_generator_spec.rb 0000644 0000041 0000041 00000001615 13410042500 023542 0 ustar www-data www-data require 'spec_helper'
require 'generators/doorkeeper/views_generator'
describe Doorkeeper::Generators::ViewsGenerator do
include GeneratorSpec::TestCase
tests Doorkeeper::Generators::ViewsGenerator
destination File.expand_path('tmp/dummy', __dir__)
before :each do
prepare_destination
end
it 'create all views' do
run_generator
assert_file 'app/views/doorkeeper/applications/_form.html.erb'
assert_file 'app/views/doorkeeper/applications/edit.html.erb'
assert_file 'app/views/doorkeeper/applications/index.html.erb'
assert_file 'app/views/doorkeeper/applications/new.html.erb'
assert_file 'app/views/doorkeeper/applications/show.html.erb'
assert_file 'app/views/doorkeeper/authorizations/error.html.erb'
assert_file 'app/views/doorkeeper/authorizations/new.html.erb'
assert_file 'app/views/doorkeeper/authorized_applications/index.html.erb'
end
end
doorkeeper-5.0.2/spec/generators/previous_refresh_token_generator_spec.rb 0000644 0000041 0000041 00000003340 13410042500 027174 0 ustar www-data www-data require 'spec_helper'
require 'generators/doorkeeper/previous_refresh_token_generator'
describe 'Doorkeeper::PreviousRefreshTokenGenerator' do
include GeneratorSpec::TestCase
tests Doorkeeper::PreviousRefreshTokenGenerator
destination ::File.expand_path('../tmp/dummy', __FILE__)
describe 'after running the generator' do
before :each do
prepare_destination
allow_any_instance_of(Doorkeeper::PreviousRefreshTokenGenerator).to(
receive(:no_previous_refresh_token_column?).and_return(true)
)
end
context 'pre Rails 5.0.0' do
it 'creates a migration with no version specifier' do
stub_const('ActiveRecord::VERSION::MAJOR', 4)
stub_const('ActiveRecord::VERSION::MINOR', 2)
run_generator
assert_migration 'db/migrate/add_previous_refresh_token_to_access_tokens.rb' do |migration|
assert migration.include?("ActiveRecord::Migration\n")
end
end
end
context 'post Rails 5.0.0' do
it 'creates a migration with a version specifier' do
stub_const('ActiveRecord::VERSION::MAJOR', 5)
stub_const('ActiveRecord::VERSION::MINOR', 0)
run_generator
assert_migration 'db/migrate/add_previous_refresh_token_to_access_tokens.rb' do |migration|
assert migration.include?("ActiveRecord::Migration[5.0]\n")
end
end
end
context 'already exist' do
it 'does not create a migration' do
allow_any_instance_of(Doorkeeper::PreviousRefreshTokenGenerator).to(
receive(:no_previous_refresh_token_column?).and_call_original
)
run_generator
assert_no_migration 'db/migrate/add_previous_refresh_token_to_access_tokens.rb'
end
end
end
end
doorkeeper-5.0.2/spec/generators/confidential_applications_generator_spec.rb 0000644 0000041 0000041 00000002532 13410042500 027611 0 ustar www-data www-data # frozen_string_literal: true
require 'spec_helper'
require 'generators/doorkeeper/confidential_applications_generator'
describe 'Doorkeeper::ConfidentialApplicationsGenerator' do
include GeneratorSpec::TestCase
tests Doorkeeper::ConfidentialApplicationsGenerator
destination ::File.expand_path('../tmp/dummy', __FILE__)
describe 'after running the generator' do
before :each do
prepare_destination
end
context 'pre Rails 5.0.0' do
it 'creates a migration with no version specifier' do
stub_const("ActiveRecord::VERSION::MAJOR", 4)
stub_const("ActiveRecord::VERSION::MINOR", 2)
run_generator
assert_migration 'db/migrate/add_confidential_to_applications.rb' do |migration|
assert migration.include?("ActiveRecord::Migration\n")
assert migration.include?(':confidential')
end
end
end
context 'post Rails 5.0.0' do
it 'creates a migration with a version specifier' do
stub_const("ActiveRecord::VERSION::MAJOR", 5)
stub_const("ActiveRecord::VERSION::MINOR", 0)
run_generator
assert_migration 'db/migrate/add_confidential_to_applications.rb' do |migration|
assert migration.include?("ActiveRecord::Migration[5.0]\n")
assert migration.include?(':confidential')
end
end
end
end
end
doorkeeper-5.0.2/spec/generators/migration_generator_spec.rb 0000644 0000041 0000041 00000002223 13410042500 024372 0 ustar www-data www-data require 'spec_helper'
require 'generators/doorkeeper/migration_generator'
describe 'Doorkeeper::MigrationGenerator' do
include GeneratorSpec::TestCase
tests Doorkeeper::MigrationGenerator
destination ::File.expand_path('../tmp/dummy', __FILE__)
describe 'after running the generator' do
before :each do
prepare_destination
end
context 'pre Rails 5.0.0' do
it 'creates a migration with no version specifier' do
stub_const('ActiveRecord::VERSION::MAJOR', 4)
stub_const('ActiveRecord::VERSION::MINOR', 2)
run_generator
assert_migration 'db/migrate/create_doorkeeper_tables.rb' do |migration|
assert migration.include?("ActiveRecord::Migration\n")
end
end
end
context 'post Rails 5.0.0' do
it 'creates a migration with a version specifier' do
stub_const('ActiveRecord::VERSION::MAJOR', 5)
stub_const('ActiveRecord::VERSION::MINOR', 0)
run_generator
assert_migration 'db/migrate/create_doorkeeper_tables.rb' do |migration|
assert migration.include?("ActiveRecord::Migration[5.0]\n")
end
end
end
end
end
doorkeeper-5.0.2/spec/lib/ 0000755 0000041 0000041 00000000000 13410042500 015372 5 ustar www-data www-data doorkeeper-5.0.2/spec/lib/doorkeeper_spec.rb 0000644 0000041 0000041 00000001220 13410042500 021063 0 ustar www-data www-data require 'spec_helper'
describe Doorkeeper do
describe "#authenticate" do
let(:request) { double }
it "calls OAuth::Token#authenticate" do
token_strategies = Doorkeeper.configuration.access_token_methods
expect(Doorkeeper::OAuth::Token).to receive(:authenticate)
.with(request, *token_strategies)
Doorkeeper.authenticate(request)
end
it "accepts custom token strategies" do
token_strategies = %i[first_way second_way]
expect(Doorkeeper::OAuth::Token).to receive(:authenticate)
.with(request, *token_strategies)
Doorkeeper.authenticate(request, token_strategies)
end
end
end
doorkeeper-5.0.2/spec/lib/models/ 0000755 0000041 0000041 00000000000 13410042500 016655 5 ustar www-data www-data doorkeeper-5.0.2/spec/lib/models/revocable_spec.rb 0000644 0000041 0000041 00000003161 13410042500 022157 0 ustar www-data www-data require 'spec_helper'
describe 'Revocable' do
subject do
Class.new do
include Doorkeeper::Models::Revocable
end.new
end
describe :revoke do
it 'updates :revoked_at attribute with current time' do
utc = double utc: double
clock = double now: utc
expect(subject).to receive(:update_attribute).with(:revoked_at, clock.now.utc)
subject.revoke(clock)
end
end
describe :revoked? do
it 'is revoked if :revoked_at has passed' do
allow(subject).to receive(:revoked_at).and_return(Time.now.utc - 1000)
expect(subject).to be_revoked
end
it 'is not revoked if :revoked_at has not passed' do
allow(subject).to receive(:revoked_at).and_return(Time.now.utc + 1000)
expect(subject).not_to be_revoked
end
it 'is not revoked if :revoked_at is not set' do
allow(subject).to receive(:revoked_at).and_return(nil)
expect(subject).not_to be_revoked
end
end
describe :revoke_previous_refresh_token! do
it "revokes the previous token if existing, and resets the
`previous_refresh_token` attribute" do
previous_token = FactoryBot.create(
:access_token,
refresh_token: "refresh_token"
)
current_token = FactoryBot.create(
:access_token,
previous_refresh_token: previous_token.refresh_token
)
expect_any_instance_of(
Doorkeeper::AccessToken
).to receive(:revoke).and_call_original
current_token.revoke_previous_refresh_token!
expect(current_token.previous_refresh_token).to be_empty
expect(previous_token.reload).to be_revoked
end
end
end
doorkeeper-5.0.2/spec/lib/models/expirable_spec.rb 0000644 0000041 0000041 00000002466 13410042500 022177 0 ustar www-data www-data require 'spec_helper'
describe 'Expirable' do
subject do
Class.new do
include Doorkeeper::Models::Expirable
end.new
end
before do
allow(subject).to receive(:created_at).and_return(1.minute.ago)
end
describe :expired? do
it 'is not expired if time has not passed' do
allow(subject).to receive(:expires_in).and_return(2.minutes)
expect(subject).not_to be_expired
end
it 'is expired if time has passed' do
allow(subject).to receive(:expires_in).and_return(10.seconds)
expect(subject).to be_expired
end
it 'is not expired if expires_in is not set' do
allow(subject).to receive(:expires_in).and_return(nil)
expect(subject).not_to be_expired
end
end
describe :expires_in_seconds do
it 'should return the amount of time remaining until the token is expired' do
allow(subject).to receive(:expires_in).and_return(2.minutes)
expect(subject.expires_in_seconds).to eq(60)
end
it 'should return 0 when expired' do
allow(subject).to receive(:expires_in).and_return(30.seconds)
expect(subject.expires_in_seconds).to eq(0)
end
it 'should return nil when expires_in is nil' do
allow(subject).to receive(:expires_in).and_return(nil)
expect(subject.expires_in_seconds).to be_nil
end
end
end
doorkeeper-5.0.2/spec/lib/models/scopes_spec.rb 0000644 0000041 0000041 00000001576 13410042500 021521 0 ustar www-data www-data require 'spec_helper'
describe 'Doorkeeper::Models::Scopes' do
subject do
Class.new(Hash) do
include Doorkeeper::Models::Scopes
end.new
end
before do
subject[:scopes] = 'public admin'
end
describe :scopes do
it 'is a `Scopes` class' do
expect(subject.scopes).to be_a(Doorkeeper::OAuth::Scopes)
end
it 'includes scopes' do
expect(subject.scopes).to include('public')
end
end
describe :scopes_string do
it 'is a `Scopes` class' do
expect(subject.scopes_string).to eq('public admin')
end
end
describe :includes_scope? do
it 'should return true if at least one scope is included' do
expect(subject.includes_scope?('public', 'private')).to be true
end
it 'should return false if no scopes are included' do
expect(subject.includes_scope?('teacher', 'student')).to be false
end
end
end
doorkeeper-5.0.2/spec/lib/stale_records_cleaner_spec.rb 0000644 0000041 0000041 00000004577 13410042500 023270 0 ustar www-data www-data # frozen_string_literal: true
require 'spec_helper'
describe Doorkeeper::StaleRecordsCleaner do
let(:cleaner) { described_class.new(model) }
let(:models_by_name) do
{
access_token: Doorkeeper::AccessToken,
access_grant: Doorkeeper::AccessGrant
}
end
context 'when ORM has no cleaner class' do
it 'raises an error' do
allow_any_instance_of(Doorkeeper::Config).to receive(:orm).and_return('hibernate')
expect do
described_class.for(Doorkeeper::AccessToken)
end.to raise_error(Doorkeeper::Errors::NoOrmCleaner, /has no cleaner/)
end
end
%i[access_token access_grant].each do |model_name|
context "(#{model_name})" do
let(:model) { models_by_name.fetch(model_name) }
describe '#clean_revoked' do
subject { cleaner.clean_revoked }
context 'with revoked record' do
before do
FactoryBot.create model_name, revoked_at: Time.current - 1.minute
end
it 'removes the record' do
expect { subject }.to change { model.count }.to(0)
end
end
context 'with record revoked in the future' do
before do
FactoryBot.create model_name, revoked_at: Time.current + 1.minute
end
it 'keeps the record' do
expect { subject }.not_to(change { model.count })
end
end
context 'with unrevoked record' do
before do
FactoryBot.create model_name, revoked_at: nil
end
it 'keeps the record' do
expect { subject }.not_to(change { model.count })
end
end
end
describe '#clean_expired' do
subject { cleaner.clean_expired(ttl) }
let(:ttl) { 500 }
let(:expiry_border) { ttl.seconds.ago }
context 'with record that is expired' do
before do
FactoryBot.create model_name, created_at: expiry_border - 1.minute
end
it 'removes the record' do
expect { subject }.to change { model.count }.to(0)
end
end
context 'with record that is not expired' do
before do
FactoryBot.create model_name, created_at: expiry_border + 1.minute
end
it 'keeps the record' do
expect { subject }.not_to(change { model.count })
end
end
end
end
end
end
doorkeeper-5.0.2/spec/lib/config_spec.rb 0000644 0000041 0000041 00000033146 13410042500 020205 0 ustar www-data www-data require 'spec_helper'
describe Doorkeeper, 'configuration' do
subject { Doorkeeper.configuration }
describe 'resource_owner_authenticator' do
it 'sets the block that is accessible via authenticate_resource_owner' do
block = proc {}
Doorkeeper.configure do
orm DOORKEEPER_ORM
resource_owner_authenticator(&block)
end
expect(subject.authenticate_resource_owner).to eq(block)
end
it 'prints warning message by default' do
Doorkeeper.configure do
orm DOORKEEPER_ORM
end
expect(Rails.logger).to receive(:warn).with(
I18n.t('doorkeeper.errors.messages.resource_owner_authenticator_not_configured')
)
subject.authenticate_resource_owner.call(nil)
end
end
describe 'resource_owner_from_credentials' do
it 'sets the block that is accessible via authenticate_resource_owner' do
block = proc {}
Doorkeeper.configure do
orm DOORKEEPER_ORM
resource_owner_from_credentials(&block)
end
expect(subject.resource_owner_from_credentials).to eq(block)
end
it 'prints warning message by default' do
Doorkeeper.configure do
orm DOORKEEPER_ORM
end
expect(Rails.logger).to receive(:warn).with(
I18n.t('doorkeeper.errors.messages.credential_flow_not_configured')
)
subject.resource_owner_from_credentials.call(nil)
end
end
describe 'setup_orm_adapter' do
it 'adds specific error message to NameError exception' do
expect do
Doorkeeper.configure { orm 'hibernate' }
end.to raise_error(NameError, /ORM adapter not found \(hibernate\)/)
end
it 'does not change other exceptions' do
allow_any_instance_of(String).to receive(:classify) { raise NoMethodError }
expect do
Doorkeeper.configure { orm 'hibernate' }
end.to raise_error(NoMethodError, /ORM adapter not found \(hibernate\)/)
end
end
describe 'admin_authenticator' do
it 'sets the block that is accessible via authenticate_admin' do
default_behaviour = 'default behaviour'
allow(Doorkeeper::Config).to receive(:head).and_return(default_behaviour)
Doorkeeper.configure do
orm DOORKEEPER_ORM
end
expect(subject.authenticate_admin.call({})).to eq(default_behaviour)
end
it 'sets the block that is accessible via authenticate_admin' do
block = proc {}
Doorkeeper.configure do
orm DOORKEEPER_ORM
admin_authenticator(&block)
end
expect(subject.authenticate_admin).to eq(block)
end
end
describe 'access_token_expires_in' do
it 'has 2 hours by default' do
expect(subject.access_token_expires_in).to eq(2.hours)
end
it 'can change the value' do
Doorkeeper.configure do
orm DOORKEEPER_ORM
access_token_expires_in 4.hours
end
expect(subject.access_token_expires_in).to eq(4.hours)
end
it 'can be set to nil' do
Doorkeeper.configure do
orm DOORKEEPER_ORM
access_token_expires_in nil
end
expect(subject.access_token_expires_in).to be_nil
end
end
describe 'scopes' do
it 'has default scopes' do
Doorkeeper.configure do
orm DOORKEEPER_ORM
default_scopes :public
end
expect(subject.default_scopes).to include('public')
end
it 'has optional scopes' do
Doorkeeper.configure do
orm DOORKEEPER_ORM
optional_scopes :write, :update
end
expect(subject.optional_scopes).to include('write', 'update')
end
it 'has all scopes' do
Doorkeeper.configure do
orm DOORKEEPER_ORM
default_scopes :normal
optional_scopes :admin
end
expect(subject.scopes).to include('normal', 'admin')
end
end
describe 'use_refresh_token' do
it 'is false by default' do
expect(subject.refresh_token_enabled?).to eq(false)
end
it 'can change the value' do
Doorkeeper.configure do
orm DOORKEEPER_ORM
use_refresh_token
end
expect(subject.refresh_token_enabled?).to eq(true)
end
it 'can accept a boolean parameter' do
Doorkeeper.configure do
orm DOORKEEPER_ORM
use_refresh_token false
end
expect(subject.refresh_token_enabled?).to eq(false)
end
it 'can accept a block parameter' do
Doorkeeper.configure do
orm DOORKEEPER_ORM
use_refresh_token { |_context| nil }
end
expect(subject.refresh_token_enabled?).to be_a(Proc)
end
it "does not includes 'refresh_token' in authorization_response_types" do
expect(subject.token_grant_types).not_to include 'refresh_token'
end
context "is enabled" do
before do
Doorkeeper.configure do
orm DOORKEEPER_ORM
use_refresh_token
end
end
it "includes 'refresh_token' in authorization_response_types" do
expect(subject.token_grant_types).to include 'refresh_token'
end
end
end
describe 'enforce_configured_scopes' do
it 'is false by default' do
expect(subject.enforce_configured_scopes?).to eq(false)
end
it 'can change the value' do
Doorkeeper.configure do
orm DOORKEEPER_ORM
enforce_configured_scopes
end
expect(subject.enforce_configured_scopes?).to eq(true)
end
end
describe 'client_credentials' do
it 'has defaults order' do
expect(subject.client_credentials_methods)
.to eq(%i[from_basic from_params])
end
it 'can change the value' do
Doorkeeper.configure do
orm DOORKEEPER_ORM
client_credentials :from_digest, :from_params
end
expect(subject.client_credentials_methods)
.to eq(%i[from_digest from_params])
end
end
describe 'force_ssl_in_redirect_uri' do
it 'is true by default in non-development environments' do
expect(subject.force_ssl_in_redirect_uri).to eq(true)
end
it 'can change the value' do
Doorkeeper.configure do
orm DOORKEEPER_ORM
force_ssl_in_redirect_uri(false)
end
expect(subject.force_ssl_in_redirect_uri).to eq(false)
end
it 'can be a callable object' do
block = proc { false }
Doorkeeper.configure do
orm DOORKEEPER_ORM
force_ssl_in_redirect_uri(&block)
end
expect(subject.force_ssl_in_redirect_uri).to eq(block)
expect(subject.force_ssl_in_redirect_uri.call).to eq(false)
end
end
describe 'access_token_methods' do
it 'has defaults order' do
expect(subject.access_token_methods)
.to eq(%i[from_bearer_authorization from_access_token_param from_bearer_param])
end
it 'can change the value' do
Doorkeeper.configure do
orm DOORKEEPER_ORM
access_token_methods :from_access_token_param, :from_bearer_param
end
expect(subject.access_token_methods)
.to eq(%i[from_access_token_param from_bearer_param])
end
end
describe 'forbid_redirect_uri' do
it 'is false by default' do
expect(subject.forbid_redirect_uri.call(URI.parse('https://localhost'))).to eq(false)
end
it 'can be a callable object' do
block = proc { true }
Doorkeeper.configure do
orm DOORKEEPER_ORM
forbid_redirect_uri(&block)
end
expect(subject.forbid_redirect_uri).to eq(block)
expect(subject.forbid_redirect_uri.call).to eq(true)
end
end
describe 'enable_application_owner' do
it 'is disabled by default' do
expect(Doorkeeper.configuration.enable_application_owner?).not_to eq(true)
end
context 'when enabled without confirmation' do
before do
Doorkeeper.configure do
orm DOORKEEPER_ORM
enable_application_owner
end
end
it 'adds support for application owner' do
expect(Doorkeeper::Application.new).to respond_to :owner
end
it 'Doorkeeper.configuration.confirm_application_owner? returns false' do
expect(Doorkeeper.configuration.confirm_application_owner?).not_to eq(true)
end
end
context 'when enabled with confirmation set to true' do
before do
Doorkeeper.configure do
orm DOORKEEPER_ORM
enable_application_owner confirmation: true
end
end
it 'adds support for application owner' do
expect(Doorkeeper::Application.new).to respond_to :owner
end
it 'Doorkeeper.configuration.confirm_application_owner? returns true' do
expect(Doorkeeper.configuration.confirm_application_owner?).to eq(true)
end
end
end
describe 'realm' do
it 'is \'Doorkeeper\' by default' do
expect(Doorkeeper.configuration.realm).to eq('Doorkeeper')
end
it 'can change the value' do
Doorkeeper.configure do
orm DOORKEEPER_ORM
realm 'Example'
end
expect(subject.realm).to eq('Example')
end
end
describe "grant_flows" do
it "is set to all grant flows by default" do
expect(Doorkeeper.configuration.grant_flows)
.to eq(%w[authorization_code client_credentials])
end
it "can change the value" do
Doorkeeper.configure do
orm DOORKEEPER_ORM
grant_flows %w[authorization_code implicit]
end
expect(subject.grant_flows).to eq %w[authorization_code implicit]
end
context "when including 'authorization_code'" do
before do
Doorkeeper.configure do
orm DOORKEEPER_ORM
grant_flows ['authorization_code']
end
end
it "includes 'code' in authorization_response_types" do
expect(subject.authorization_response_types).to include 'code'
end
it "includes 'authorization_code' in token_grant_types" do
expect(subject.token_grant_types).to include 'authorization_code'
end
end
context "when including 'implicit'" do
before do
Doorkeeper.configure do
orm DOORKEEPER_ORM
grant_flows ['implicit']
end
end
it "includes 'token' in authorization_response_types" do
expect(subject.authorization_response_types).to include 'token'
end
end
context "when including 'password'" do
before do
Doorkeeper.configure do
orm DOORKEEPER_ORM
grant_flows ['password']
end
end
it "includes 'password' in token_grant_types" do
expect(subject.token_grant_types).to include 'password'
end
end
context "when including 'client_credentials'" do
before do
Doorkeeper.configure do
orm DOORKEEPER_ORM
grant_flows ['client_credentials']
end
end
it "includes 'client_credentials' in token_grant_types" do
expect(subject.token_grant_types).to include 'client_credentials'
end
end
end
it 'raises an exception when configuration is not set' do
old_config = Doorkeeper.configuration
Doorkeeper.module_eval do
@config = nil
end
expect do
Doorkeeper.configuration
end.to raise_error Doorkeeper::MissingConfiguration
Doorkeeper.module_eval do
@config = old_config
end
end
describe 'access_token_generator' do
it 'is \'Doorkeeper::OAuth::Helpers::UniqueToken\' by default' do
expect(Doorkeeper.configuration.access_token_generator).to(
eq('Doorkeeper::OAuth::Helpers::UniqueToken')
)
end
it 'can change the value' do
Doorkeeper.configure do
orm DOORKEEPER_ORM
access_token_generator 'Example'
end
expect(subject.access_token_generator).to eq('Example')
end
end
describe 'base_controller' do
context 'default' do
it { expect(Doorkeeper.configuration.base_controller).to eq('ActionController::Base') }
end
context 'custom' do
before do
Doorkeeper.configure do
orm DOORKEEPER_ORM
base_controller 'ApplicationController'
end
end
it { expect(Doorkeeper.configuration.base_controller).to eq('ApplicationController') }
end
end
if DOORKEEPER_ORM == :active_record
describe 'active_record_options' do
let(:models) { [Doorkeeper::AccessGrant, Doorkeeper::AccessToken, Doorkeeper::Application] }
before do
models.each do |model|
allow(model).to receive(:establish_connection).and_return(true)
end
end
it 'establishes connection for Doorkeeper models based on options' do
models.each do |model|
expect(model).to receive(:establish_connection)
end
Doorkeeper.configure do
orm DOORKEEPER_ORM
active_record_options(
establish_connection: Rails.configuration.database_configuration[Rails.env]
)
end
end
end
end
describe "api_only" do
it "is false by default" do
expect(subject.api_only).to eq(false)
end
it "can change the value" do
Doorkeeper.configure do
orm DOORKEEPER_ORM
api_only
end
expect(subject.api_only).to eq(true)
end
end
describe 'strict_content_type' do
it 'is false by default' do
expect(subject.enforce_content_type).to eq(false)
end
it "can change the value" do
Doorkeeper.configure do
orm DOORKEEPER_ORM
enforce_content_type
end
expect(subject.enforce_content_type).to eq(true)
end
end
describe 'handle_auth_errors' do
it 'is set to render by default' do
expect(Doorkeeper.configuration.handle_auth_errors).to eq(:render)
end
it 'can change the value' do
Doorkeeper.configure do
orm DOORKEEPER_ORM
handle_auth_errors :raise
end
expect(subject.handle_auth_errors).to eq(:raise)
end
end
end
doorkeeper-5.0.2/spec/lib/request/ 0000755 0000041 0000041 00000000000 13410042500 017062 5 ustar www-data www-data doorkeeper-5.0.2/spec/lib/request/strategy_spec.rb 0000644 0000041 0000041 00000002320 13410042500 022260 0 ustar www-data www-data require 'spec_helper'
module Doorkeeper
module Request
describe Strategy do
let(:server) { double }
subject(:strategy) { Strategy.new(server) }
describe :initialize do
it "sets the server attribute" do
expect(strategy.server).to eq server
end
end
describe :request do
it "requires an implementation" do
expect { strategy.request }.to raise_exception NotImplementedError
end
end
describe "a sample Strategy subclass" do
let(:fake_request) { double }
let(:strategy_class) do
subclass = Class.new(Strategy) do
class << self
attr_accessor :fake_request
end
def request
self.class.fake_request
end
end
subclass.fake_request = fake_request
subclass
end
subject(:strategy) { strategy_class.new(server) }
it "provides a request implementation" do
expect(strategy.request).to eq fake_request
end
it "authorizes the request" do
expect(fake_request).to receive :authorize
strategy.authorize
end
end
end
end
end
doorkeeper-5.0.2/spec/lib/server_spec.rb 0000644 0000041 0000041 00000003463 13410042500 020245 0 ustar www-data www-data require 'spec_helper'
describe Doorkeeper::Server do
let(:fake_class) { double :fake_class }
subject do
described_class.new
end
describe '.authorization_request' do
it 'raises error when strategy does not exist' do
expect do
subject.authorization_request(:duh)
end.to raise_error(Doorkeeper::Errors::InvalidAuthorizationStrategy)
end
it 'raises error when strategy does not match phase' do
expect do
subject.token_request(:code)
end.to raise_error(Doorkeeper::Errors::InvalidTokenStrategy)
end
context 'when only Authorization Code strategy is enabled' do
before do
allow(Doorkeeper.configuration)
.to receive(:grant_flows)
.and_return(['authorization_code'])
end
it 'raises error when using the disabled Implicit strategy' do
expect do
subject.authorization_request(:token)
end.to raise_error(Doorkeeper::Errors::InvalidAuthorizationStrategy)
end
it 'raises error when using the disabled Client Credentials strategy' do
expect do
subject.token_request(:client_credentials)
end.to raise_error(Doorkeeper::Errors::InvalidTokenStrategy)
end
end
it 'builds the request with selected strategy' do
stub_const 'Doorkeeper::Request::Code', fake_class
expect(fake_class).to receive(:new).with(subject)
subject.authorization_request :code
end
it 'builds the request with composite strategy name' do
allow(Doorkeeper.configuration)
.to receive(:authorization_response_types)
.and_return(['id_token token'])
stub_const 'Doorkeeper::Request::IdTokenToken', fake_class
expect(fake_class).to receive(:new).with(subject)
subject.authorization_request 'id_token token'
end
end
end
doorkeeper-5.0.2/spec/lib/oauth/ 0000755 0000041 0000041 00000000000 13410042500 016512 5 ustar www-data www-data doorkeeper-5.0.2/spec/lib/oauth/token_spec.rb 0000644 0000041 0000041 00000011424 13410042500 021173 0 ustar www-data www-data require 'spec_helper'
module Doorkeeper
unless defined?(AccessToken)
class AccessToken
end
end
module OAuth
describe Token do
describe :from_request do
let(:request) { double.as_null_object }
let(:method) do
->(*) { 'token-value' }
end
it 'accepts anything that responds to #call' do
expect(method).to receive(:call).with(request)
Token.from_request request, method
end
it 'delegates methods received as symbols to Token class' do
expect(Token).to receive(:from_params).with(request)
Token.from_request request, :from_params
end
it 'stops at the first credentials found' do
not_called_method = double
expect(not_called_method).not_to receive(:call)
Token.from_request request, ->(_r) {}, method, not_called_method
end
it 'returns the credential from extractor method' do
credentials = Token.from_request request, method
expect(credentials).to eq('token-value')
end
end
describe :from_access_token_param do
it 'returns token from access_token parameter' do
request = double parameters: { access_token: 'some-token' }
token = Token.from_access_token_param(request)
expect(token).to eq('some-token')
end
end
describe :from_bearer_param do
it 'returns token from bearer_token parameter' do
request = double parameters: { bearer_token: 'some-token' }
token = Token.from_bearer_param(request)
expect(token).to eq('some-token')
end
end
describe :from_bearer_authorization do
it 'returns token from capitalized authorization bearer' do
request = double authorization: 'Bearer SomeToken'
token = Token.from_bearer_authorization(request)
expect(token).to eq('SomeToken')
end
it 'returns token from lowercased authorization bearer' do
request = double authorization: 'bearer SomeToken'
token = Token.from_bearer_authorization(request)
expect(token).to eq('SomeToken')
end
it 'does not return token if authorization is not bearer' do
request = double authorization: 'MAC SomeToken'
token = Token.from_bearer_authorization(request)
expect(token).to be_blank
end
end
describe :from_basic_authorization do
it 'returns token from capitalized authorization basic' do
request = double authorization: "Basic #{Base64.encode64 'SomeToken:'}"
token = Token.from_basic_authorization(request)
expect(token).to eq('SomeToken')
end
it 'returns token from lowercased authorization basic' do
request = double authorization: "basic #{Base64.encode64 'SomeToken:'}"
token = Token.from_basic_authorization(request)
expect(token).to eq('SomeToken')
end
it 'does not return token if authorization is not basic' do
request = double authorization: "MAC #{Base64.encode64 'SomeToken:'}"
token = Token.from_basic_authorization(request)
expect(token).to be_blank
end
end
describe :authenticate do
context 'refresh tokens are disabled (default)' do
context 'refresh tokens are enabled' do
it 'does not revoke previous refresh_token if token was found' do
token = ->(_r) { 'token' }
expect(
AccessToken
).to receive(:by_token).with('token').and_return(token)
expect(token).not_to receive(:revoke_previous_refresh_token!)
Token.authenticate double, token
end
end
it 'calls the finder if token was returned' do
token = ->(_r) { 'token' }
expect(AccessToken).to receive(:by_token).with('token')
Token.authenticate double, token
end
end
context 'refresh tokens are enabled' do
before do
Doorkeeper.configure do
orm DOORKEEPER_ORM
use_refresh_token
end
end
it 'revokes previous refresh_token if token was found' do
token = ->(_r) { 'token' }
expect(
AccessToken
).to receive(:by_token).with('token').and_return(token)
expect(token).to receive(:revoke_previous_refresh_token!)
Token.authenticate double, token
end
it 'calls the finder if token was returned' do
token = ->(_r) { 'token' }
expect(AccessToken).to receive(:by_token).with('token')
Token.authenticate double, token
end
end
end
end
end
end
doorkeeper-5.0.2/spec/lib/oauth/client/ 0000755 0000041 0000041 00000000000 13410042500 017770 5 ustar www-data www-data doorkeeper-5.0.2/spec/lib/oauth/client/credentials_spec.rb 0000644 0000041 0000041 00000005464 13410042500 023635 0 ustar www-data www-data require 'spec_helper'
class Doorkeeper::OAuth::Client
describe Credentials do
let(:client_id) { 'some-uid' }
let(:client_secret) { 'some-secret' }
it 'is blank when the uid in credentials is blank' do
expect(Credentials.new(nil, nil)).to be_blank
expect(Credentials.new(nil, 'something')).to be_blank
expect(Credentials.new('something', nil)).to be_present
expect(Credentials.new('something', 'something')).to be_present
end
describe :from_request do
let(:request) { double.as_null_object }
let(:method) do
->(_request) { %w[uid secret] }
end
it 'accepts anything that responds to #call' do
expect(method).to receive(:call).with(request)
Credentials.from_request request, method
end
it 'delegates methods received as symbols to Credentials class' do
expect(Credentials).to receive(:from_params).with(request)
Credentials.from_request request, :from_params
end
it 'stops at the first credentials found' do
not_called_method = double
expect(not_called_method).not_to receive(:call)
Credentials.from_request request, ->(_) {}, method, not_called_method
end
it 'returns new Credentials' do
credentials = Credentials.from_request request, method
expect(credentials).to be_a(Credentials)
end
it 'returns uid and secret from extractor method' do
credentials = Credentials.from_request request, method
expect(credentials.uid).to eq('uid')
expect(credentials.secret).to eq('secret')
end
end
describe :from_params do
it 'returns credentials from parameters when Authorization header is not available' do
request = double parameters: { client_id: client_id, client_secret: client_secret }
uid, secret = Credentials.from_params(request)
expect(uid).to eq('some-uid')
expect(secret).to eq('some-secret')
end
it 'is blank when there are no credentials' do
request = double parameters: {}
uid, secret = Credentials.from_params(request)
expect(uid).to be_blank
expect(secret).to be_blank
end
end
describe :from_basic do
let(:credentials) { Base64.encode64("#{client_id}:#{client_secret}") }
it 'decodes the credentials' do
request = double authorization: "Basic #{credentials}"
uid, secret = Credentials.from_basic(request)
expect(uid).to eq('some-uid')
expect(secret).to eq('some-secret')
end
it 'is blank if Authorization is not Basic' do
request = double authorization: credentials.to_s
uid, secret = Credentials.from_basic(request)
expect(uid).to be_blank
expect(secret).to be_blank
end
end
end
end
doorkeeper-5.0.2/spec/lib/oauth/client_credentials/ 0000755 0000041 0000041 00000000000 13410042500 022345 5 ustar www-data www-data doorkeeper-5.0.2/spec/lib/oauth/client_credentials/validation_spec.rb 0000644 0000041 0000041 00000004145 13410042500 026042 0 ustar www-data www-data require 'spec_helper'
class Doorkeeper::OAuth::ClientCredentialsRequest
describe Validation do
let(:server) { double :server, scopes: nil }
let(:application) { double scopes: nil }
let(:client) { double application: application }
let(:request) { double :request, client: client, scopes: nil }
subject { Validation.new(server, request) }
it 'is valid with valid request' do
expect(subject).to be_valid
end
it 'is invalid when client is not present' do
allow(request).to receive(:client).and_return(nil)
expect(subject).not_to be_valid
end
context 'with scopes' do
it 'is invalid when scopes are not included in the server' do
server_scopes = Doorkeeper::OAuth::Scopes.from_string 'email'
allow(server).to receive(:scopes).and_return(server_scopes)
allow(request).to receive(:scopes).and_return(
Doorkeeper::OAuth::Scopes.from_string('invalid')
)
expect(subject).not_to be_valid
end
context 'with application scopes' do
it 'is valid when scopes are included in the application' do
application_scopes = Doorkeeper::OAuth::Scopes.from_string 'app'
server_scopes = Doorkeeper::OAuth::Scopes.from_string 'email app'
allow(application).to receive(:scopes).and_return(application_scopes)
allow(server).to receive(:scopes).and_return(server_scopes)
allow(request).to receive(:scopes).and_return(application_scopes)
expect(subject).to be_valid
end
it 'is invalid when scopes are not included in the application' do
application_scopes = Doorkeeper::OAuth::Scopes.from_string 'app'
server_scopes = Doorkeeper::OAuth::Scopes.from_string 'email app'
allow(application).to receive(:scopes).and_return(application_scopes)
allow(server).to receive(:scopes).and_return(server_scopes)
allow(request).to receive(:scopes).and_return(
Doorkeeper::OAuth::Scopes.from_string('email')
)
expect(subject).not_to be_valid
end
end
end
end
end
doorkeeper-5.0.2/spec/lib/oauth/client_credentials/issuer_spec.rb 0000644 0000041 0000041 00000005711 13410042500 025222 0 ustar www-data www-data require 'spec_helper'
class Doorkeeper::OAuth::ClientCredentialsRequest
describe Issuer do
let(:creator) { double :acces_token_creator }
let(:server) do
double(
:server,
access_token_expires_in: 100,
custom_access_token_expires_in: ->(_context) { nil }
)
end
let(:validation) { double :validation, valid?: true }
subject { Issuer.new(server, validation) }
describe :create do
let(:client) { double :client, id: 'some-id' }
let(:scopes) { 'some scope' }
it 'creates and sets the token' do
expect(creator).to receive(:call).and_return('token')
subject.create client, scopes, creator
expect(subject.token).to eq('token')
end
it 'creates with correct token parameters' do
expect(creator).to receive(:call).with(
client,
scopes,
expires_in: 100,
use_refresh_token: false
)
subject.create client, scopes, creator
end
it 'has error set to :server_error if creator fails' do
expect(creator).to receive(:call).and_return(false)
subject.create client, scopes, creator
expect(subject.error).to eq(:server_error)
end
context 'when validation fails' do
before do
allow(validation).to receive(:valid?).and_return(false)
allow(validation).to receive(:error).and_return(:validation_error)
expect(creator).not_to receive(:create)
end
it 'has error set from validation' do
subject.create client, scopes, creator
expect(subject.error).to eq(:validation_error)
end
it 'returns false' do
expect(subject.create(client, scopes, creator)).to be_falsey
end
end
context 'with custom expirations' do
let(:custom_ttl_grant) { 1234 }
let(:custom_ttl_scope) { 1235 }
let(:custom_scope) { 'special' }
let(:server) do
double(
:server,
custom_access_token_expires_in: lambda { |context|
# scopes is normally an object but is a string in this test
if context.scopes == custom_scope
custom_ttl_scope
elsif context.grant_type == Doorkeeper::OAuth::CLIENT_CREDENTIALS
custom_ttl_grant
end
}
)
end
it 'respects grant based rules' do
expect(creator).to receive(:call).with(
client,
scopes,
expires_in: custom_ttl_grant,
use_refresh_token: false
)
subject.create client, scopes, creator
end
it 'respects scope based rules' do
expect(creator).to receive(:call).with(
client,
custom_scope,
expires_in: custom_ttl_scope,
use_refresh_token: false
)
subject.create client, custom_scope, creator
end
end
end
end
end
doorkeeper-5.0.2/spec/lib/oauth/client_credentials/creator_spec.rb 0000644 0000041 0000041 00000002670 13410042500 025350 0 ustar www-data www-data require 'spec_helper'
class Doorkeeper::OAuth::ClientCredentialsRequest
describe Creator do
let(:client) { FactoryBot.create :application }
let(:scopes) { Doorkeeper::OAuth::Scopes.from_string('public') }
before do
default_scopes_exist :public
end
it 'creates a new token' do
expect do
subject.call(client, scopes)
end.to change { Doorkeeper::AccessToken.count }.by(1)
end
context "when reuse_access_token is true" do
it "returns the existing valid token" do
allow(Doorkeeper.configuration).to receive(:reuse_access_token).and_return(true)
existing_token = subject.call(client, scopes)
result = subject.call(client, scopes)
expect(Doorkeeper::AccessToken.count).to eq(1)
expect(result).to eq(existing_token)
end
end
context "when reuse_access_token is false" do
it "returns a new token" do
allow(Doorkeeper.configuration).to receive(:reuse_access_token).and_return(false)
existing_token = subject.call(client, scopes)
result = subject.call(client, scopes)
expect(Doorkeeper::AccessToken.count).to eq(2)
expect(result).not_to eq(existing_token)
end
end
it 'returns false if creation fails' do
expect(Doorkeeper::AccessToken).to receive(:find_or_create_for).and_return(false)
created = subject.call(client, scopes)
expect(created).to be_falsey
end
end
end
doorkeeper-5.0.2/spec/lib/oauth/token_response_spec.rb 0000644 0000041 0000041 00000004441 13410042500 023112 0 ustar www-data www-data require 'spec_helper'
module Doorkeeper::OAuth
describe TokenResponse do
subject { TokenResponse.new(double.as_null_object) }
it 'includes access token response headers' do
headers = subject.headers
expect(headers.fetch('Cache-Control')).to eq('no-store')
expect(headers.fetch('Pragma')).to eq('no-cache')
end
it 'status is ok' do
expect(subject.status).to eq(:ok)
end
describe '.body' do
let(:access_token) do
double :access_token,
token: 'some-token',
expires_in: '3600',
expires_in_seconds: '300',
scopes_string: 'two scopes',
refresh_token: 'some-refresh-token',
token_type: 'bearer',
created_at: 0
end
subject { TokenResponse.new(access_token).body }
it 'includes :access_token' do
expect(subject['access_token']).to eq('some-token')
end
it 'includes :token_type' do
expect(subject['token_type']).to eq('bearer')
end
# expires_in_seconds is returned as `expires_in` in order to match
# the OAuth spec (section 4.2.2)
it 'includes :expires_in' do
expect(subject['expires_in']).to eq('300')
end
it 'includes :scope' do
expect(subject['scope']).to eq('two scopes')
end
it 'includes :refresh_token' do
expect(subject['refresh_token']).to eq('some-refresh-token')
end
it 'includes :created_at' do
expect(subject['created_at']).to eq(0)
end
end
describe '.body filters out empty values' do
let(:access_token) do
double :access_token,
token: 'some-token',
expires_in_seconds: '',
scopes_string: '',
refresh_token: '',
token_type: 'bearer',
created_at: 0
end
subject { TokenResponse.new(access_token).body }
it 'includes :expires_in' do
expect(subject['expires_in']).to be_nil
end
it 'includes :scope' do
expect(subject['scope']).to be_nil
end
it 'includes :refresh_token' do
expect(subject['refresh_token']).to be_nil
end
end
end
end
doorkeeper-5.0.2/spec/lib/oauth/helpers/ 0000755 0000041 0000041 00000000000 13410042500 020154 5 ustar www-data www-data doorkeeper-5.0.2/spec/lib/oauth/helpers/uri_checker_spec.rb 0000644 0000041 0000041 00000017516 13410042500 024010 0 ustar www-data www-data require 'spec_helper'
module Doorkeeper::OAuth::Helpers
describe URIChecker do
describe '.valid?' do
it 'is valid for valid uris' do
uri = 'http://app.co'
expect(URIChecker.valid?(uri)).to be_truthy
end
it 'is valid if include path param' do
uri = 'http://app.co/path'
expect(URIChecker.valid?(uri)).to be_truthy
end
it 'is valid if include query param' do
uri = 'http://app.co/?query=1'
expect(URIChecker.valid?(uri)).to be_truthy
end
it 'is invalid if uri includes fragment' do
uri = 'http://app.co/test#fragment'
expect(URIChecker.valid?(uri)).to be_falsey
end
it 'is invalid if scheme is missing' do
uri = 'app.co'
expect(URIChecker.valid?(uri)).to be_falsey
end
it 'is invalid if is a relative uri' do
uri = '/abc/123'
expect(URIChecker.valid?(uri)).to be_falsey
end
it 'is invalid if is not a url' do
uri = 'http://'
expect(URIChecker.valid?(uri)).to be_falsey
end
it 'is invalid if is not an uri' do
uri = ' '
expect(URIChecker.valid?(uri)).to be_falsey
end
it 'is valid for native uris' do
uri = 'urn:ietf:wg:oauth:2.0:oob'
expect(URIChecker.valid?(uri)).to be_truthy
end
end
describe '.matches?' do
it 'is true if both url matches' do
uri = client_uri = 'http://app.co/aaa'
expect(URIChecker.matches?(uri, client_uri)).to be_truthy
end
it 'ignores query parameter on comparsion' do
uri = 'http://app.co/?query=hello'
client_uri = 'http://app.co'
expect(URIChecker.matches?(uri, client_uri)).to be_truthy
end
it 'doesn\'t allow non-matching domains through' do
uri = 'http://app.abc/?query=hello'
client_uri = 'http://app.co'
expect(URIChecker.matches?(uri, client_uri)).to be_falsey
end
it 'doesn\'t allow non-matching domains that don\'t start at the beginning' do
uri = 'http://app.co/?query=hello'
client_uri = 'http://example.com?app.co=test'
expect(URIChecker.matches?(uri, client_uri)).to be_falsey
end
context "client registered query params" do
it "doesn't allow query being absent" do
uri = 'http://app.co'
client_uri = 'http://app.co/?vendorId=AJ4L7XXW9'
expect(URIChecker.matches?(uri, client_uri)).to be_falsey
end
it "is false if query values differ but key same" do
uri = 'http://app.co/?vendorId=pancakes'
client_uri = 'http://app.co/?vendorId=waffles'
expect(URIChecker.matches?(uri, client_uri)).to be_falsey
end
it "is false if query values same but key differs" do
uri = 'http://app.co/?foo=pancakes'
client_uri = 'http://app.co/?bar=pancakes'
expect(URIChecker.matches?(uri, client_uri)).to be_falsey
end
it "is false if query present and match, but unknown queries present" do
uri = 'http://app.co/?vendorId=pancakes&unknown=query'
client_uri = 'http://app.co/?vendorId=waffles'
expect(URIChecker.matches?(uri, client_uri)).to be_falsey
end
it "is true if queries are present and matche" do
uri = 'http://app.co/?vendorId=AJ4L7XXW9&foo=bar'
client_uri = 'http://app.co/?vendorId=AJ4L7XXW9&foo=bar'
expect(URIChecker.matches?(uri, client_uri)).to be_truthy
end
it "is true if queries are present, match and in different order" do
uri = 'http://app.co/?bing=bang&foo=bar'
client_uri = 'http://app.co/?foo=bar&bing=bang'
expect(URIChecker.matches?(uri, client_uri)).to be_truthy
end
end
end
describe '.valid_for_authorization?' do
it 'is true if valid and matches' do
uri = client_uri = 'http://app.co/aaa'
expect(URIChecker.valid_for_authorization?(uri, client_uri)).to be_truthy
uri = client_uri = 'http://app.co/aaa?b=c'
expect(URIChecker.valid_for_authorization?(uri, client_uri)).to be_truthy
end
it 'is true if uri includes blank query' do
uri = client_uri = 'http://app.co/aaa?'
expect(URIChecker.valid_for_authorization?(uri, client_uri)).to be_truthy
uri = 'http://app.co/aaa?'
client_uri = 'http://app.co/aaa'
expect(URIChecker.valid_for_authorization?(uri, client_uri)).to be_truthy
uri = 'http://app.co/aaa'
client_uri = 'http://app.co/aaa?'
expect(URIChecker.valid_for_authorization?(uri, client_uri)).to be_truthy
end
it 'is false if valid and mismatches' do
uri = 'http://app.co/aaa'
client_uri = 'http://app.co/bbb'
expect(URIChecker.valid_for_authorization?(uri, client_uri)).to be_falsey
end
it 'is true if valid and included in array' do
uri = 'http://app.co/aaa'
client_uri = "http://example.com/bbb\nhttp://app.co/aaa"
expect(URIChecker.valid_for_authorization?(uri, client_uri)).to be_truthy
end
it 'is false if valid and not included in array' do
uri = 'http://app.co/aaa'
client_uri = "http://example.com/bbb\nhttp://app.co/cc"
expect(URIChecker.valid_for_authorization?(uri, client_uri)).to be_falsey
end
it 'is false if queries does not match' do
uri = 'http://app.co/aaa?pankcakes=abc'
client_uri = 'http://app.co/aaa?waffles=abc'
expect(URIChecker.valid_for_authorization?(uri, client_uri)).to be false
end
it 'calls .matches?' do
uri = 'http://app.co/aaa?pankcakes=abc'
client_uri = 'http://app.co/aaa?waffles=abc'
expect(URIChecker).to receive(:matches?).with(uri, client_uri).once
URIChecker.valid_for_authorization?(uri, client_uri)
end
it 'calls .valid?' do
uri = 'http://app.co/aaa?pankcakes=abc'
client_uri = 'http://app.co/aaa?waffles=abc'
expect(URIChecker).to receive(:valid?).with(uri).once
URIChecker.valid_for_authorization?(uri, client_uri)
end
end
describe '.query_matches?' do
it 'is true if no queries' do
expect(URIChecker.query_matches?('', '')).to be_truthy
expect(URIChecker.query_matches?(nil, nil)).to be_truthy
end
it 'is true if same query' do
expect(URIChecker.query_matches?('foo', 'foo')).to be_truthy
end
it 'is false if different query' do
expect(URIChecker.query_matches?('foo', 'bar')).to be_falsey
end
it 'is true if same queries' do
expect(URIChecker.query_matches?('foo&bar', 'foo&bar')).to be_truthy
end
it 'is true if same queries, different order' do
expect(URIChecker.query_matches?('foo&bar', 'bar&foo')).to be_truthy
end
it 'is false if one different query' do
expect(URIChecker.query_matches?('foo&bang', 'foo&bing')).to be_falsey
end
it 'is true if same query with same value' do
expect(URIChecker.query_matches?('foo=bar', 'foo=bar')).to be_truthy
end
it 'is true if same queries with same values' do
expect(URIChecker.query_matches?('foo=bar&bing=bang', 'foo=bar&bing=bang')).to be_truthy
end
it 'is true if same queries with same values, different order' do
expect(URIChecker.query_matches?('foo=bar&bing=bang', 'bing=bang&foo=bar')).to be_truthy
end
it 'is false if same query with different value' do
expect(URIChecker.query_matches?('foo=bar', 'foo=bang')).to be_falsey
end
it 'is false if some queries missing' do
expect(URIChecker.query_matches?('foo=bar', 'foo=bar&bing=bang')).to be_falsey
end
it 'is false if some queries different value' do
expect(URIChecker.query_matches?('foo=bar&bing=bang', 'foo=bar&bing=banana')).to be_falsey
end
end
end
end
doorkeeper-5.0.2/spec/lib/oauth/helpers/scope_checker_spec.rb 0000644 0000041 0000041 00000003465 13410042500 024320 0 ustar www-data www-data require 'spec_helper'
module Doorkeeper::OAuth::Helpers
describe ScopeChecker, '.valid?' do
let(:server_scopes) { Doorkeeper::OAuth::Scopes.new }
it 'is valid if scope is present' do
server_scopes.add :scope
expect(ScopeChecker.valid?('scope', server_scopes)).to be_truthy
end
it 'is invalid if includes tabs space' do
expect(ScopeChecker.valid?("\tsomething", server_scopes)).to be_falsey
end
it 'is invalid if scope is not present' do
expect(ScopeChecker.valid?(nil, server_scopes)).to be_falsey
end
it 'is invalid if scope is blank' do
expect(ScopeChecker.valid?(' ', server_scopes)).to be_falsey
end
it 'is invalid if includes return space' do
expect(ScopeChecker.valid?("scope\r", server_scopes)).to be_falsey
end
it 'is invalid if includes new lines' do
expect(ScopeChecker.valid?("scope\nanother", server_scopes)).to be_falsey
end
it 'is invalid if any scope is not included in server scopes' do
expect(ScopeChecker.valid?('scope another', server_scopes)).to be_falsey
end
context 'with application_scopes' do
let(:server_scopes) do
Doorkeeper::OAuth::Scopes.from_string 'common svr'
end
let(:application_scopes) do
Doorkeeper::OAuth::Scopes.from_string 'app123'
end
it 'is valid if scope is included in the application scope list' do
expect(ScopeChecker.valid?(
'app123',
server_scopes,
application_scopes
)).to be_truthy
end
it 'is invalid if any scope is not included in the application' do
expect(ScopeChecker.valid?(
'svr',
server_scopes,
application_scopes
)).to be_falsey
end
end
end
end
doorkeeper-5.0.2/spec/lib/oauth/helpers/unique_token_spec.rb 0000644 0000041 0000041 00000000727 13410042500 024227 0 ustar www-data www-data require 'spec_helper'
module Doorkeeper::OAuth::Helpers
describe UniqueToken do
let :generator do
->(size) { 'a' * size }
end
it 'is able to customize the generator method' do
token = UniqueToken.generate(generator: generator)
expect(token).to eq('a' * 32)
end
it 'is able to customize the size of the token' do
token = UniqueToken.generate(generator: generator, size: 2)
expect(token).to eq('aa')
end
end
end
doorkeeper-5.0.2/spec/lib/oauth/authorization/ 0000755 0000041 0000041 00000000000 13410042500 021412 5 ustar www-data www-data doorkeeper-5.0.2/spec/lib/oauth/authorization/uri_builder_spec.rb 0000644 0000041 0000041 00000002363 13410042500 025262 0 ustar www-data www-data require 'spec_helper'
module Doorkeeper::OAuth::Authorization
describe URIBuilder do
subject { URIBuilder }
describe :uri_with_query do
it 'returns the uri with query' do
uri = subject.uri_with_query 'http://example.com/', parameter: 'value'
expect(uri).to eq('http://example.com/?parameter=value')
end
it 'rejects nil values' do
uri = subject.uri_with_query 'http://example.com/', parameter: ''
expect(uri).to eq('http://example.com/?')
end
it 'preserves original query parameters' do
uri = subject.uri_with_query 'http://example.com/?query1=value', parameter: 'value'
expect(uri).to match(/query1=value/)
expect(uri).to match(/parameter=value/)
end
end
describe :uri_with_fragment do
it 'returns uri with parameters as fragments' do
uri = subject.uri_with_fragment 'http://example.com/', parameter: 'value'
expect(uri).to eq('http://example.com/#parameter=value')
end
it 'preserves original query parameters' do
uri = subject.uri_with_fragment 'http://example.com/?query1=value1', parameter: 'value'
expect(uri).to eq('http://example.com/?query1=value1#parameter=value')
end
end
end
end
doorkeeper-5.0.2/spec/lib/oauth/token_request_spec.rb 0000644 0000041 0000041 00000005570 13410042500 022750 0 ustar www-data www-data require 'spec_helper'
module Doorkeeper::OAuth
describe TokenRequest do
let :application do
FactoryBot.create(:application, scopes: 'public')
end
let :pre_auth do
double(
:pre_auth,
client: application,
redirect_uri: 'http://tst.com/cb',
state: nil,
scopes: Scopes.from_string('public'),
error: nil,
authorizable?: true
)
end
let :owner do
double :owner, id: 7866
end
subject do
TokenRequest.new(pre_auth, owner)
end
it 'creates an access token' do
expect do
subject.authorize
end.to change { Doorkeeper::AccessToken.count }.by(1)
end
it 'returns a code response' do
expect(subject.authorize).to be_a(CodeResponse)
end
it 'does not create token when not authorizable' do
allow(pre_auth).to receive(:authorizable?).and_return(false)
expect { subject.authorize }.not_to(change { Doorkeeper::AccessToken.count })
end
it 'returns a error response' do
allow(pre_auth).to receive(:authorizable?).and_return(false)
expect(subject.authorize).to be_a(ErrorResponse)
end
context 'with custom expirations' do
before do
Doorkeeper.configure do
orm DOORKEEPER_ORM
custom_access_token_expires_in do |context|
context.grant_type == Doorkeeper::OAuth::IMPLICIT ? 1234 : nil
end
end
end
it 'should use the custom ttl' do
subject.authorize
token = Doorkeeper::AccessToken.first
expect(token.expires_in).to eq(1234)
end
end
context 'token reuse' do
it 'creates a new token if there are no matching tokens' do
allow(Doorkeeper.configuration).to receive(:reuse_access_token).and_return(true)
expect do
subject.authorize
end.to change { Doorkeeper::AccessToken.count }.by(1)
end
it 'creates a new token if scopes do not match' do
allow(Doorkeeper.configuration).to receive(:reuse_access_token).and_return(true)
FactoryBot.create(:access_token, application_id: pre_auth.client.id,
resource_owner_id: owner.id, scopes: '')
expect do
subject.authorize
end.to change { Doorkeeper::AccessToken.count }.by(1)
end
it 'skips token creation if there is a matching one' do
allow(Doorkeeper.configuration).to receive(:reuse_access_token).and_return(true)
allow(application.scopes).to receive(:has_scopes?).and_return(true)
allow(application.scopes).to receive(:all?).and_return(true)
FactoryBot.create(:access_token, application_id: pre_auth.client.id,
resource_owner_id: owner.id, scopes: 'public')
expect { subject.authorize }.not_to(change { Doorkeeper::AccessToken.count })
end
end
end
end
doorkeeper-5.0.2/spec/lib/oauth/authorization_code_request_spec.rb 0000644 0000041 0000041 00000007651 13410042500 025524 0 ustar www-data www-data require 'spec_helper'
module Doorkeeper::OAuth
describe AuthorizationCodeRequest do
let(:server) do
double :server,
access_token_expires_in: 2.days,
refresh_token_enabled?: false,
custom_access_token_expires_in: lambda { |context|
context.grant_type == Doorkeeper::OAuth::AUTHORIZATION_CODE ? 1234 : nil
}
end
let(:grant) { FactoryBot.create :access_grant }
let(:client) { grant.application }
let(:redirect_uri) { client.redirect_uri }
let(:params) { { redirect_uri: redirect_uri } }
subject do
AuthorizationCodeRequest.new server, grant, client, params
end
it 'issues a new token for the client' do
expect do
subject.authorize
end.to change { client.reload.access_tokens.count }.by(1)
expect(client.reload.access_tokens.max_by(&:created_at).expires_in).to eq(1234)
end
it "issues the token with same grant's scopes" do
subject.authorize
expect(Doorkeeper::AccessToken.last.scopes).to eq(grant.scopes)
end
it 'revokes the grant' do
expect { subject.authorize }.to(change { grant.reload.accessible? })
end
it 'requires the grant to be accessible' do
grant.revoke
subject.validate
expect(subject.error).to eq(:invalid_grant)
end
it 'requires the grant' do
subject.grant = nil
subject.validate
expect(subject.error).to eq(:invalid_grant)
end
it 'requires the client' do
subject.client = nil
subject.validate
expect(subject.error).to eq(:invalid_client)
end
it 'requires the redirect_uri' do
subject.redirect_uri = nil
subject.validate
expect(subject.error).to eq(:invalid_request)
end
it "matches the redirect_uri with grant's one" do
subject.redirect_uri = 'http://other.com'
subject.validate
expect(subject.error).to eq(:invalid_grant)
end
it "matches the client with grant's one" do
subject.client = FactoryBot.create :application
subject.validate
expect(subject.error).to eq(:invalid_grant)
end
it 'skips token creation if there is a matching one' do
scopes = grant.scopes
Doorkeeper.configure do
orm DOORKEEPER_ORM
reuse_access_token
default_scopes(*scopes)
end
FactoryBot.create(:access_token, application_id: client.id,
resource_owner_id: grant.resource_owner_id, scopes: grant.scopes.to_s)
expect { subject.authorize }.to_not(change { Doorkeeper::AccessToken.count })
end
it "calls configured request callback methods" do
expect(Doorkeeper.configuration.before_successful_strategy_response)
.to receive(:call).with(subject).once
expect(Doorkeeper.configuration.after_successful_strategy_response)
.to receive(:call).with(subject, instance_of(Doorkeeper::OAuth::TokenResponse)).once
subject.authorize
end
context "when redirect_uri contains some query params" do
let(:redirect_uri) { client.redirect_uri + "?query=q" }
it "compares only host part with grant's redirect_uri" do
subject.validate
expect(subject.error).to eq(nil)
end
end
context "when redirect_uri is not an URI" do
let(:redirect_uri) { '123d#!s' }
it "responds with invalid_grant" do
subject.validate
expect(subject.error).to eq(:invalid_grant)
end
end
context "when redirect_uri is the native one" do
let(:redirect_uri) { 'urn:ietf:wg:oauth:2.0:oob' }
it "invalidates when redirect_uri of the grant is not native" do
subject.validate
expect(subject.error).to eq(:invalid_grant)
end
it "validates when redirect_uri of the grant is also native" do
allow(grant).to receive(:redirect_uri) { redirect_uri }
subject.validate
expect(subject.error).to eq(nil)
end
end
end
end
doorkeeper-5.0.2/spec/lib/oauth/invalid_token_response_spec.rb 0000644 0000041 0000041 00000002466 13410042500 024625 0 ustar www-data www-data require 'spec_helper'
module Doorkeeper::OAuth
describe InvalidTokenResponse do
describe "#name" do
it { expect(subject.name).to eq(:invalid_token) }
end
describe "#status" do
it { expect(subject.status).to eq(:unauthorized) }
end
describe :from_access_token do
let(:response) { InvalidTokenResponse.from_access_token(access_token) }
context "revoked" do
let(:access_token) { double(revoked?: true, expired?: true) }
it "sets a description" do
expect(response.description).to include("revoked")
end
it "sets the reason" do
expect(response.reason).to eq(:revoked)
end
end
context "expired" do
let(:access_token) { double(revoked?: false, expired?: true) }
it "sets a description" do
expect(response.description).to include("expired")
end
it "sets the reason" do
expect(response.reason).to eq(:expired)
end
end
context "unknown" do
let(:access_token) { double(revoked?: false, expired?: false) }
it "sets a description" do
expect(response.description).to include("invalid")
end
it "sets the reason" do
expect(response.reason).to eq(:unknown)
end
end
end
end
end
doorkeeper-5.0.2/spec/lib/oauth/forbidden_token_response_spec.rb 0000644 0000041 0000041 00000000762 13410042500 025130 0 ustar www-data www-data require 'spec_helper'
module Doorkeeper::OAuth
describe ForbiddenTokenResponse do
describe '#name' do
it { expect(subject.name).to eq(:invalid_scope) }
end
describe '#status' do
it { expect(subject.status).to eq(:forbidden) }
end
describe :from_scopes do
it 'should have a list of acceptable scopes' do
response = ForbiddenTokenResponse.from_scopes(["public"])
expect(response.description).to include('public')
end
end
end
end
doorkeeper-5.0.2/spec/lib/oauth/error_spec.rb 0000644 0000041 0000041 00000001021 13410042500 021174 0 ustar www-data www-data require 'spec_helper'
module Doorkeeper::OAuth
describe Error do
subject(:error) { Error.new(:some_error, :some_state) }
it { expect(subject).to respond_to(:name) }
it { expect(subject).to respond_to(:state) }
describe :description do
it 'is translated from translation messages' do
expect(I18n).to receive(:translate).with(
:some_error,
scope: %i[doorkeeper errors messages],
default: :server_error
)
error.description
end
end
end
end
doorkeeper-5.0.2/spec/lib/oauth/code_response_spec.rb 0000644 0000041 0000041 00000001772 13410042500 022710 0 ustar www-data www-data require 'spec_helper'
module Doorkeeper
module OAuth
describe CodeResponse do
describe '.redirect_uri' do
context 'when generating the redirect URI for an implicit grant' do
let :pre_auth do
double(
:pre_auth,
client: double(:application, id: 1),
redirect_uri: 'http://tst.com/cb',
state: nil,
scopes: Scopes.from_string('public')
)
end
let :auth do
Authorization::Token.new(pre_auth, double(id: 1)).tap do |c|
c.issue_token
allow(c.token).to receive(:expires_in_seconds).and_return(3600)
end
end
subject { CodeResponse.new(pre_auth, auth, response_on_fragment: true).redirect_uri }
it 'includes the remaining TTL of the token relative to the time the token was generated' do
expect(subject).to include('expires_in=3600')
end
end
end
end
end
end
doorkeeper-5.0.2/spec/lib/oauth/client_spec.rb 0000644 0000041 0000041 00000002361 13410042500 021331 0 ustar www-data www-data require 'spec_helper'
module Doorkeeper::OAuth
describe Client do
describe :find do
let(:method) { double }
it 'finds the client via uid' do
client = double
expect(method).to receive(:call).with('uid').and_return(client)
expect(Client.find('uid', method)).to be_a(Client)
end
it 'returns nil if client was not found' do
expect(method).to receive(:call).with('uid').and_return(nil)
expect(Client.find('uid', method)).to be_nil
end
end
describe :authenticate do
it 'returns the authenticated client via credentials' do
credentials = Client::Credentials.new('some-uid', 'some-secret')
authenticator = double
expect(authenticator).to receive(:call).with('some-uid', 'some-secret').and_return(double)
expect(Client.authenticate(credentials, authenticator)).to be_a(Client)
end
it 'returns nil if client was not authenticated' do
credentials = Client::Credentials.new('some-uid', 'some-secret')
authenticator = double
expect(authenticator).to receive(:call).with('some-uid', 'some-secret').and_return(nil)
expect(Client.authenticate(credentials, authenticator)).to be_nil
end
end
end
end
doorkeeper-5.0.2/spec/lib/oauth/password_access_token_request_spec.rb 0000644 0000041 0000041 00000010745 13410042500 026213 0 ustar www-data www-data require 'spec_helper'
module Doorkeeper::OAuth
describe PasswordAccessTokenRequest do
let(:server) do
double(
:server,
default_scopes: Doorkeeper::OAuth::Scopes.new,
access_token_expires_in: 2.hours,
refresh_token_enabled?: false,
custom_access_token_expires_in: lambda { |context|
context.grant_type == Doorkeeper::OAuth::PASSWORD ? 1234 : nil
}
)
end
let(:client) { FactoryBot.create(:application) }
let(:owner) { double :owner, id: 99 }
subject do
PasswordAccessTokenRequest.new(server, client, owner)
end
it 'issues a new token for the client' do
expect do
subject.authorize
end.to change { client.reload.access_tokens.count }.by(1)
expect(client.reload.access_tokens.max_by(&:created_at).expires_in).to eq(1234)
end
it 'issues a new token without a client' do
expect do
subject.client = nil
subject.authorize
end.to change { Doorkeeper::AccessToken.count }.by(1)
end
it 'does not issue a new token with an invalid client' do
expect do
subject.client = nil
subject.parameters = { client_id: 'bad_id' }
subject.authorize
end.not_to(change { Doorkeeper::AccessToken.count })
expect(subject.error).to eq(:invalid_client)
end
it 'requires the owner' do
subject.resource_owner = nil
subject.validate
expect(subject.error).to eq(:invalid_grant)
end
it 'optionally accepts the client' do
subject.client = nil
expect(subject).to be_valid
end
it 'creates token even when there is already one (default)' do
FactoryBot.create(:access_token, application_id: client.id, resource_owner_id: owner.id)
expect do
subject.authorize
end.to change { Doorkeeper::AccessToken.count }.by(1)
end
it 'skips token creation if there is already one' do
allow(Doorkeeper.configuration).to receive(:reuse_access_token).and_return(true)
FactoryBot.create(:access_token, application_id: client.id, resource_owner_id: owner.id)
expect do
subject.authorize
end.not_to(change { Doorkeeper::AccessToken.count })
end
it "calls configured request callback methods" do
expect(Doorkeeper.configuration.before_successful_strategy_response)
.to receive(:call).with(subject).once
expect(Doorkeeper.configuration.after_successful_strategy_response)
.to receive(:call).with(subject, instance_of(Doorkeeper::OAuth::TokenResponse)).once
subject.authorize
end
describe 'with scopes' do
subject do
PasswordAccessTokenRequest.new(server, client, owner, scope: 'public')
end
it 'validates the current scope' do
allow(server).to receive(:scopes).and_return(Doorkeeper::OAuth::Scopes.from_string('another'))
subject.validate
expect(subject.error).to eq(:invalid_scope)
end
it 'creates the token with scopes' do
allow(server).to receive(:scopes).and_return(Doorkeeper::OAuth::Scopes.from_string('public'))
expect do
subject.authorize
end.to change { Doorkeeper::AccessToken.count }.by(1)
expect(Doorkeeper::AccessToken.last.scopes).to include('public')
end
end
describe 'with custom expiry' do
let(:server) do
double(
:server,
default_scopes: Doorkeeper::OAuth::Scopes.new,
access_token_expires_in: 2.hours,
refresh_token_enabled?: false,
custom_access_token_expires_in: lambda { |context|
context.scopes.exists?('public') ? 222 : nil
}
)
end
it 'checks scopes' do
subject = PasswordAccessTokenRequest.new(server, client, owner, scope: 'public')
allow(server).to receive(:scopes).and_return(Doorkeeper::OAuth::Scopes.from_string('public'))
expect do
subject.authorize
end.to change { Doorkeeper::AccessToken.count }.by(1)
expect(Doorkeeper::AccessToken.last.expires_in).to eq(222)
end
it 'falls back to the default otherwise' do
subject = PasswordAccessTokenRequest.new(server, client, owner, scope: 'private')
allow(server).to receive(:scopes).and_return(Doorkeeper::OAuth::Scopes.from_string('private'))
expect do
subject.authorize
end.to change { Doorkeeper::AccessToken.count }.by(1)
expect(Doorkeeper::AccessToken.last.expires_in).to eq(2.hours)
end
end
end
end
doorkeeper-5.0.2/spec/lib/oauth/base_response_spec.rb 0000644 0000041 0000041 00000001567 13410042500 022712 0 ustar www-data www-data require 'spec_helper'
module Doorkeeper::OAuth
describe BaseResponse do
subject do
BaseResponse.new
end
describe "#body" do
it "returns an empty Hash" do
expect(subject.body).to eq({})
end
end
describe "#description" do
it "returns an empty String" do
expect(subject.description).to eq("")
end
end
describe "#headers" do
it "returns an empty Hash" do
expect(subject.headers).to eq({})
end
end
describe "#redirectable?" do
it "returns false" do
expect(subject.redirectable?).to eq(false)
end
end
describe "#redirect_uri" do
it "returns an empty String" do
expect(subject.redirect_uri).to eq("")
end
end
describe "#status" do
it "returns :ok" do
expect(subject.status).to eq(:ok)
end
end
end
end
doorkeeper-5.0.2/spec/lib/oauth/error_response_spec.rb 0000644 0000041 0000041 00000003544 13410042500 023126 0 ustar www-data www-data require 'spec_helper'
module Doorkeeper::OAuth
describe ErrorResponse do
describe '#status' do
it 'should have a status of unauthorized' do
expect(subject.status).to eq(:unauthorized)
end
end
describe :from_request do
it 'has the error from request' do
error = ErrorResponse.from_request double(error: :some_error)
expect(error.name).to eq(:some_error)
end
it 'ignores state if request does not respond to state' do
error = ErrorResponse.from_request double(error: :some_error)
expect(error.state).to be_nil
end
it 'has state if request responds to state' do
error = ErrorResponse.from_request double(error: :some_error, state: :hello)
expect(error.state).to eq(:hello)
end
end
it 'ignores empty error values' do
subject = ErrorResponse.new(error: :some_error, state: nil)
expect(subject.body).not_to have_key(:state)
end
describe '.body' do
subject { ErrorResponse.new(name: :some_error, state: :some_state).body }
describe '#body' do
it { expect(subject).to have_key(:error) }
it { expect(subject).to have_key(:error_description) }
it { expect(subject).to have_key(:state) }
end
end
describe '.headers' do
let(:error_response) { ErrorResponse.new(name: :some_error, state: :some_state) }
subject { error_response.headers }
it { expect(subject).to include 'WWW-Authenticate' }
describe "WWW-Authenticate header" do
subject { error_response.headers["WWW-Authenticate"] }
it { expect(subject).to include("realm=\"#{error_response.realm}\"") }
it { expect(subject).to include("error=\"#{error_response.name}\"") }
it { expect(subject).to include("error_description=\"#{error_response.description}\"") }
end
end
end
end
doorkeeper-5.0.2/spec/lib/oauth/code_request_spec.rb 0000644 0000041 0000041 00000002162 13410042500 022534 0 ustar www-data www-data require 'spec_helper'
module Doorkeeper::OAuth
describe CodeRequest do
let(:pre_auth) do
double(
:pre_auth,
client: double(:application, id: 9990),
redirect_uri: 'http://tst.com/cb',
scopes: nil,
state: nil,
error: nil,
authorizable?: true,
code_challenge: nil,
code_challenge_method: nil
)
end
let(:owner) { double :owner, id: 8900 }
subject do
CodeRequest.new(pre_auth, owner)
end
it 'creates an access grant' do
expect do
subject.authorize
end.to change { Doorkeeper::AccessGrant.count }.by(1)
end
it 'returns a code response' do
expect(subject.authorize).to be_a(CodeResponse)
end
it 'does not create grant when not authorizable' do
allow(pre_auth).to receive(:authorizable?).and_return(false)
expect { subject.authorize }.not_to(change { Doorkeeper::AccessGrant.count })
end
it 'returns a error response' do
allow(pre_auth).to receive(:authorizable?).and_return(false)
expect(subject.authorize).to be_a(ErrorResponse)
end
end
end
doorkeeper-5.0.2/spec/lib/oauth/client_credentials_integration_spec.rb 0000644 0000041 0000041 00000001350 13410042500 026306 0 ustar www-data www-data require 'spec_helper'
module Doorkeeper::OAuth
describe ClientCredentialsRequest do
let(:server) { Doorkeeper.configuration }
context 'with a valid request' do
let(:client) { FactoryBot.create :application }
it 'issues an access token' do
request = ClientCredentialsRequest.new(server, client, {})
expect do
request.authorize
end.to change { Doorkeeper::AccessToken.count }.by(1)
end
end
describe 'with an invalid request' do
it 'does not issue an access token' do
request = ClientCredentialsRequest.new(server, nil, {})
expect do
request.authorize
end.to_not(change { Doorkeeper::AccessToken.count })
end
end
end
end
doorkeeper-5.0.2/spec/lib/oauth/base_request_spec.rb 0000644 0000041 0000041 00000012436 13410042500 022541 0 ustar www-data www-data require 'spec_helper'
module Doorkeeper::OAuth
describe BaseRequest do
let(:access_token) do
double :access_token,
token: "some-token",
expires_in: "3600",
expires_in_seconds: "300",
scopes_string: "two scopes",
refresh_token: "some-refresh-token",
token_type: "bearer",
created_at: 0
end
let(:client) { double :client, id: '1' }
let(:scopes_array) { %w[public write] }
let(:server) do
double :server,
access_token_expires_in: 100,
custom_access_token_expires_in: ->(_context) { nil },
refresh_token_enabled?: false
end
subject do
BaseRequest.new
end
describe "#authorize" do
before do
allow(subject).to receive(:access_token).and_return(access_token)
end
it "validates itself" do
expect(subject).to receive(:validate).once
subject.authorize
end
context "valid" do
before do
allow(subject).to receive(:valid?).and_return(true)
end
it "calls callback methods" do
expect(subject).to receive(:before_successful_response).once
expect(subject).to receive(:after_successful_response).once
subject.authorize
end
it "returns a TokenResponse object" do
result = subject.authorize
expect(result).to be_an_instance_of(TokenResponse)
expect(result.body).to eq(
TokenResponse.new(access_token).body
)
end
end
context "invalid" do
before do
allow(subject).to receive(:valid?).and_return(false)
allow(subject).to receive(:error).and_return("server_error")
allow(subject).to receive(:state).and_return("hello")
end
it "returns an ErrorResponse object" do
error_description = I18n.translate(
"server_error",
scope: %i[doorkeeper errors messages]
)
result = subject.authorize
expect(result).to be_an_instance_of(ErrorResponse)
expect(result.body).to eq(
error: "server_error",
error_description: error_description,
state: "hello"
)
end
end
end
describe "#default_scopes" do
it "delegates to the server" do
expect(subject).to receive(:server).and_return(server).once
expect(server).to receive(:default_scopes).once
subject.default_scopes
end
end
describe "#find_or_create_access_token" do
it "returns an instance of AccessToken" do
result = subject.find_or_create_access_token(
client,
"1",
"public",
server
)
expect(result).to be_an_instance_of(Doorkeeper::AccessToken)
end
it "respects custom_access_token_expires_in" do
server = double(:server,
access_token_expires_in: 100,
custom_access_token_expires_in: ->(context) { context.scopes == "public" ? 500 : nil },
refresh_token_enabled?: false)
result = subject.find_or_create_access_token(
client,
"1",
"public",
server
)
expect(result.expires_in).to eql(500)
end
it "respects use_refresh_token with a block" do
server = double(:server,
access_token_expires_in: 100,
custom_access_token_expires_in: ->(_context) { nil },
refresh_token_enabled?: lambda { |context|
context.scopes == "public"
})
result = subject.find_or_create_access_token(
client,
"1",
"public",
server
)
expect(result.refresh_token).to_not be_nil
result = subject.find_or_create_access_token(
client,
"1",
"private",
server
)
expect(result.refresh_token).to be_nil
end
end
describe "#scopes" do
context "@original_scopes is present" do
before do
subject.instance_variable_set(:@original_scopes, "public write")
end
it "returns array of @original_scopes" do
result = subject.scopes
expect(result).to eq(scopes_array)
end
end
context "@original_scopes is not present" do
before do
subject.instance_variable_set(:@original_scopes, "")
end
it "calls #default_scopes" do
allow(subject).to receive(:server).and_return(server).once
allow(server).to receive(:default_scopes).and_return(scopes_array).once
result = subject.scopes
expect(result).to eq(scopes_array)
end
end
end
describe "#valid?" do
context "error is nil" do
it "returns true" do
allow(subject).to receive(:error).and_return(nil).once
expect(subject.valid?).to eq(true)
end
end
context "error is not nil" do
it "returns false" do
allow(subject).to receive(:error).and_return(Object.new).once
expect(subject.valid?).to eq(false)
end
end
end
end
end
doorkeeper-5.0.2/spec/lib/oauth/client_credentials_request_spec.rb 0000644 0000041 0000041 00000006544 13410042500 025465 0 ustar www-data www-data require 'spec_helper'
module Doorkeeper::OAuth
describe ClientCredentialsRequest do
let(:server) do
double(
default_scopes: nil,
access_token_expires_in: 2.hours,
custom_access_token_expires_in: ->(_context) { nil }
)
end
let(:application) { FactoryBot.create(:application, scopes: '') }
let(:client) { double :client, application: application }
let(:token_creator) { double :issuer, create: true, token: double }
subject { ClientCredentialsRequest.new(server, client) }
before do
subject.issuer = token_creator
end
it 'issues an access token for the current client' do
expect(token_creator).to receive(:create).with(client, nil)
subject.authorize
end
it 'has successful response when issue was created' do
subject.authorize
expect(subject.response).to be_a(TokenResponse)
end
context 'if issue was not created' do
before do
subject.issuer = double create: false, error: :invalid
end
it 'has an error response' do
subject.authorize
expect(subject.response).to be_a(Doorkeeper::OAuth::ErrorResponse)
end
it 'delegates the error to issuer' do
subject.authorize
expect(subject.error).to eq(:invalid)
end
end
context 'with scopes' do
let(:default_scopes) { Doorkeeper::OAuth::Scopes.from_string('public email') }
before do
allow(server).to receive(:default_scopes).and_return(default_scopes)
end
it 'issues an access token with default scopes if none was requested' do
expect(token_creator).to receive(:create).with(client, default_scopes)
subject.authorize
end
it 'issues an access token with requested scopes' do
subject = ClientCredentialsRequest.new(server, client, scope: 'email')
subject.issuer = token_creator
expect(token_creator).to receive(:create).with(client, Doorkeeper::OAuth::Scopes.from_string('email'))
subject.authorize
end
end
context 'with restricted client' do
let(:default_scopes) do
Doorkeeper::OAuth::Scopes.from_string('public email')
end
let(:server_scopes) do
Doorkeeper::OAuth::Scopes.from_string('public email phone')
end
let(:client_scopes) do
Doorkeeper::OAuth::Scopes.from_string('public phone')
end
before do
allow(server).to receive(:default_scopes).and_return(default_scopes)
allow(server).to receive(:scopes).and_return(server_scopes)
allow(server).to receive(:access_token_expires_in).and_return(100)
allow(application).to receive(:scopes).and_return(client_scopes)
allow(client).to receive(:id).and_return(nil)
end
it 'delegates the error to issuer if no scope was requested' do
subject = ClientCredentialsRequest.new(server, client)
subject.authorize
expect(subject.response).to be_a(Doorkeeper::OAuth::ErrorResponse)
expect(subject.error).to eq(:invalid_scope)
end
it 'issues an access token with requested scopes' do
subject = ClientCredentialsRequest.new(server, client, scope: 'phone')
subject.authorize
expect(subject.response).to be_a(Doorkeeper::OAuth::TokenResponse)
expect(subject.response.token.scopes_string).to eq('phone')
end
end
end
end
doorkeeper-5.0.2/spec/lib/oauth/pre_authorization_spec.rb 0000644 0000041 0000041 00000013024 13410042500 023617 0 ustar www-data www-data require 'spec_helper'
module Doorkeeper::OAuth
describe PreAuthorization do
let(:server) do
server = Doorkeeper.configuration
allow(server).to receive(:default_scopes).and_return(Scopes.new)
allow(server).to receive(:scopes).and_return(Scopes.from_string('public profile'))
server
end
let(:application) do
application = double :application
allow(application).to receive(:scopes).and_return(Scopes.from_string(''))
application
end
let(:client) do
double :client, redirect_uri: 'http://tst.com/auth', application: application
end
let :attributes do
{
response_type: 'code',
redirect_uri: 'http://tst.com/auth',
state: 'save-this'
}
end
subject do
PreAuthorization.new(server, client, attributes)
end
it 'is authorizable when request is valid' do
expect(subject).to be_authorizable
end
it 'accepts code as response type' do
subject.response_type = 'code'
expect(subject).to be_authorizable
end
it 'accepts token as response type' do
allow(server).to receive(:grant_flows).and_return(['implicit'])
subject.response_type = 'token'
expect(subject).to be_authorizable
end
context 'when using default grant flows' do
it 'accepts "code" as response type' do
subject.response_type = 'code'
expect(subject).to be_authorizable
end
it 'accepts "token" as response type' do
allow(server).to receive(:grant_flows).and_return(['implicit'])
subject.response_type = 'token'
expect(subject).to be_authorizable
end
end
context 'when authorization code grant flow is disabled' do
before do
allow(server).to receive(:grant_flows).and_return(['implicit'])
end
it 'does not accept "code" as response type' do
subject.response_type = 'code'
expect(subject).not_to be_authorizable
end
end
context 'when implicit grant flow is disabled' do
before do
allow(server).to receive(:grant_flows).and_return(['authorization_code'])
end
it 'does not accept "token" as response type' do
subject.response_type = 'token'
expect(subject).not_to be_authorizable
end
end
context 'client application does not restrict valid scopes' do
it 'accepts valid scopes' do
subject.scope = 'public'
expect(subject).to be_authorizable
end
it 'rejects (globally) non-valid scopes' do
subject.scope = 'invalid'
expect(subject).not_to be_authorizable
end
end
context 'client application restricts valid scopes' do
let(:application) do
application = double :application
allow(application).to receive(:scopes).and_return(Scopes.from_string('public nonsense'))
application
end
it 'accepts valid scopes' do
subject.scope = 'public'
expect(subject).to be_authorizable
end
it 'rejects (globally) non-valid scopes' do
subject.scope = 'invalid'
expect(subject).not_to be_authorizable
end
it 'rejects (application level) non-valid scopes' do
subject.scope = 'profile'
expect(subject).to_not be_authorizable
end
end
it 'uses default scopes when none is required' do
allow(server).to receive(:default_scopes).and_return(Scopes.from_string('default'))
subject.scope = nil
expect(subject.scope).to eq('default')
expect(subject.scopes).to eq(Scopes.from_string('default'))
end
context 'with native redirect uri' do
let(:native_redirect_uri) { 'urn:ietf:wg:oauth:2.0:oob' }
it 'accepts redirect_uri when it matches with the client' do
subject.redirect_uri = native_redirect_uri
allow(subject.client).to receive(:redirect_uri) { native_redirect_uri }
expect(subject).to be_authorizable
end
it 'invalidates redirect_uri when it does\'n match with the client' do
subject.redirect_uri = 'urn:ietf:wg:oauth:2.0:oob'
expect(subject).not_to be_authorizable
end
end
it 'matches the redirect uri against client\'s one' do
subject.redirect_uri = 'http://nothesame.com'
expect(subject).not_to be_authorizable
end
it 'stores the state' do
expect(subject.state).to eq('save-this')
end
it 'rejects if response type is not allowed' do
subject.response_type = 'whops'
expect(subject).not_to be_authorizable
end
it 'requires an existing client' do
subject.client = nil
expect(subject).not_to be_authorizable
end
it 'requires a redirect uri' do
subject.redirect_uri = nil
expect(subject).not_to be_authorizable
end
describe "as_json" do
let(:client_id) { "client_uid_123" }
let(:client_name) { "Acme Co." }
before do
allow(client).to receive(:uid).and_return client_id
allow(client).to receive(:name).and_return client_name
end
let(:json) { subject.as_json({}) }
it { is_expected.to respond_to :as_json }
it "returns correct values" do
expect(json[:client_id]).to eq client_id
expect(json[:redirect_uri]).to eq subject.redirect_uri
expect(json[:state]).to eq subject.state
expect(json[:response_type]).to eq subject.response_type
expect(json[:scope]).to eq subject.scope
expect(json[:client_name]).to eq client_name
expect(json[:status]).to eq I18n.t('doorkeeper.pre_authorization.status')
end
end
end
end
doorkeeper-5.0.2/spec/lib/oauth/scopes_spec.rb 0000644 0000041 0000041 00000010722 13410042500 021347 0 ustar www-data www-data require 'spec_helper'
module Doorkeeper::OAuth
describe Scopes do
describe '#add' do
it 'allows you to add scopes with symbols' do
subject.add :public
expect(subject.all).to eq(['public'])
end
it 'allows you to add scopes with strings' do
subject.add 'public'
expect(subject.all).to eq(['public'])
end
it 'do not add already included scopes' do
subject.add :public
subject.add :public
expect(subject.all).to eq(['public'])
end
end
describe '#exists' do
before do
subject.add :public
end
it 'returns true if scope with given name is present' do
expect(subject.exists?('public')).to be_truthy
end
it 'returns false if scope with given name does not exist' do
expect(subject.exists?('other')).to be_falsey
end
it 'handles symbols' do
expect(subject.exists?(:public)).to be_truthy
expect(subject.exists?(:other)).to be_falsey
end
end
describe '.from_string' do
let(:string) { 'public write' }
subject { Scopes.from_string(string) }
it { expect(subject).to be_a(Scopes) }
describe '#all' do
it 'should be an array of the expected scopes' do
scopes_array = subject.all
expect(scopes_array.size).to eq(2)
expect(scopes_array).to include('public')
expect(scopes_array).to include('write')
end
end
end
describe '#+' do
it 'can add to another scope object' do
scopes = Scopes.from_string('public') + Scopes.from_string('admin')
expect(scopes.all).to eq(%w[public admin])
end
it 'does not change the existing object' do
origin = Scopes.from_string('public')
expect(origin.to_s).to eq('public')
end
it 'can add an array to a scope object' do
scopes = Scopes.from_string('public') + ['admin']
expect(scopes.all).to eq(%w[public admin])
end
it 'raises an error if cannot handle addition' do
expect do
Scopes.from_string('public') + 'admin'
end.to raise_error(NoMethodError)
end
end
describe '#&' do
it 'can get intersection with another scope object' do
scopes = Scopes.from_string('public admin') & Scopes.from_string('write admin')
expect(scopes.all).to eq(%w[admin])
end
it 'does not change the existing object' do
origin = Scopes.from_string('public admin')
origin & Scopes.from_string('write admin')
expect(origin.to_s).to eq('public admin')
end
it 'can get intersection with an array' do
scopes = Scopes.from_string('public admin') & %w[write admin]
expect(scopes.all).to eq(%w[admin])
end
end
describe '#==' do
it 'is equal to another set of scopes' do
expect(Scopes.from_string('public')).to eq(Scopes.from_string('public'))
end
it 'is equal to another set of scopes with no particular order' do
expect(Scopes.from_string('public write')).to eq(Scopes.from_string('write public'))
end
it 'differs from another set of scopes when scopes are not the same' do
expect(Scopes.from_string('public write')).not_to eq(Scopes.from_string('write'))
end
it "does not raise an error when compared to a non-enumerable object" do
expect { Scopes.from_string("public") == false }.not_to raise_error
end
end
describe '#has_scopes?' do
subject { Scopes.from_string('public admin') }
it 'returns true when at least one scope is included' do
expect(subject.has_scopes?(Scopes.from_string('public'))).to be_truthy
end
it 'returns true when all scopes are included' do
expect(subject.has_scopes?(Scopes.from_string('public admin'))).to be_truthy
end
it 'is true if all scopes are included in any order' do
expect(subject.has_scopes?(Scopes.from_string('admin public'))).to be_truthy
end
it 'is false if no scopes are included' do
expect(subject.has_scopes?(Scopes.from_string('notexistent'))).to be_falsey
end
it 'returns false when any scope is not included' do
expect(subject.has_scopes?(Scopes.from_string('public nope'))).to be_falsey
end
it 'is false if no scopes are included even for existing ones' do
expect(subject.has_scopes?(Scopes.from_string('public admin notexistent'))).to be_falsey
end
end
end
end
doorkeeper-5.0.2/spec/lib/oauth/refresh_token_request_spec.rb 0000644 0000041 0000041 00000013403 13410042500 024460 0 ustar www-data www-data require 'spec_helper'
module Doorkeeper::OAuth
describe RefreshTokenRequest do
before do
allow(Doorkeeper::AccessToken).to receive(:refresh_token_revoked_on_use?).and_return(false)
end
let(:server) do
double :server,
access_token_expires_in: 2.minutes,
custom_access_token_expires_in: ->(_context) { nil }
end
let(:refresh_token) do
FactoryBot.create(:access_token, use_refresh_token: true)
end
let(:client) { refresh_token.application }
let(:credentials) { Client::Credentials.new(client.uid, client.secret) }
subject { RefreshTokenRequest.new server, refresh_token, credentials }
it 'issues a new token for the client' do
expect { subject.authorize }.to change { client.reload.access_tokens.count }.by(1)
# #sort_by used for MongoDB ORM extensions for valid ordering
expect(client.reload.access_tokens.max_by(&:created_at).expires_in).to eq(120)
end
it 'issues a new token for the client with custom expires_in' do
server = double :server,
access_token_expires_in: 2.minutes,
custom_access_token_expires_in: lambda { |context|
context.grant_type == Doorkeeper::OAuth::REFRESH_TOKEN ? 1234 : nil
}
allow(Doorkeeper::AccessToken).to receive(:refresh_token_revoked_on_use?).and_return(false)
RefreshTokenRequest.new(server, refresh_token, credentials).authorize
# #sort_by used for MongoDB ORM extensions for valid ordering
expect(client.reload.access_tokens.max_by(&:created_at).expires_in).to eq(1234)
end
it 'revokes the previous token' do
expect { subject.authorize }.to change { refresh_token.revoked? }.from(false).to(true)
end
it "calls configured request callback methods" do
expect(Doorkeeper.configuration.before_successful_strategy_response)
.to receive(:call).with(subject).once
expect(Doorkeeper.configuration.after_successful_strategy_response)
.to receive(:call).with(subject, instance_of(Doorkeeper::OAuth::TokenResponse)).once
subject.authorize
end
it 'requires the refresh token' do
subject.refresh_token = nil
subject.validate
expect(subject.error).to eq(:invalid_request)
end
it 'requires credentials to be valid if provided' do
subject.client = nil
subject.validate
expect(subject.error).to eq(:invalid_client)
end
it "requires the token's client and current client to match" do
subject.client = FactoryBot.create(:application)
subject.validate
expect(subject.error).to eq(:invalid_grant)
end
it 'rejects revoked tokens' do
refresh_token.revoke
subject.validate
expect(subject.error).to eq(:invalid_grant)
end
it 'accepts expired tokens' do
refresh_token.expires_in = -1
refresh_token.save
subject.validate
expect(subject).to be_valid
end
context 'refresh tokens expire on access token use' do
let(:server) do
double :server,
access_token_expires_in: 2.minutes,
custom_access_token_expires_in: lambda { |context|
context.grant_type == Doorkeeper::OAuth::REFRESH_TOKEN ? 1234 : nil
}
end
before do
allow(Doorkeeper::AccessToken).to receive(:refresh_token_revoked_on_use?).and_return(true)
end
it 'issues a new token for the client' do
expect { subject.authorize }.to change { client.reload.access_tokens.count }.by(1)
end
it 'does not revoke the previous token' do
subject.authorize
expect(refresh_token).not_to be_revoked
end
it 'sets the previous refresh token in the new access token' do
subject.authorize
expect(
# #sort_by used for MongoDB ORM extensions for valid ordering
client.access_tokens.max_by(&:created_at).previous_refresh_token
).to eq(refresh_token.refresh_token)
end
end
context 'clientless access tokens' do
let!(:refresh_token) { FactoryBot.create(:clientless_access_token, use_refresh_token: true) }
subject { RefreshTokenRequest.new server, refresh_token, nil }
it 'issues a new token without a client' do
expect { subject.authorize }.to change { Doorkeeper::AccessToken.count }.by(1)
end
end
context 'with scopes' do
let(:refresh_token) do
FactoryBot.create :access_token,
use_refresh_token: true,
scopes: 'public write'
end
let(:parameters) { {} }
subject { RefreshTokenRequest.new server, refresh_token, credentials, parameters }
it 'transfers scopes from the old token to the new token' do
subject.authorize
expect(Doorkeeper::AccessToken.last.scopes).to eq(%i[public write])
end
it 'reduces scopes to the provided scopes' do
parameters[:scopes] = 'public'
subject.authorize
expect(Doorkeeper::AccessToken.last.scopes).to eq(%i[public])
end
it 'validates that scopes are included in the original access token' do
parameters[:scopes] = 'public update'
subject.validate
expect(subject.error).to eq(:invalid_scope)
end
it 'uses params[:scope] in favor of scopes if present (valid)' do
parameters[:scopes] = 'public update'
parameters[:scope] = 'public'
subject.authorize
expect(Doorkeeper::AccessToken.last.scopes).to eq(%i[public])
end
it 'uses params[:scope] in favor of scopes if present (invalid)' do
parameters[:scopes] = 'public'
parameters[:scope] = 'public update'
subject.validate
expect(subject.error).to eq(:invalid_scope)
end
end
end
end
doorkeeper-5.0.2/spec/dummy/ 0000755 0000041 0000041 00000000000 13410042500 015757 5 ustar www-data www-data doorkeeper-5.0.2/spec/dummy/config.ru 0000644 0000041 0000041 00000000232 13410042500 017571 0 ustar www-data www-data # This file is used by Rack-based servers to start the application.
require ::File.expand_path('../config/environment', __FILE__)
run Dummy::Application
doorkeeper-5.0.2/spec/dummy/script/ 0000755 0000041 0000041 00000000000 13410042500 017263 5 ustar www-data www-data doorkeeper-5.0.2/spec/dummy/script/rails 0000755 0000041 0000041 00000000437 13410042500 020327 0 ustar www-data www-data #!/usr/bin/env ruby
# This command will automatically be run when you run "rails" with Rails 3 gems
# installed from the root of your application.
APP_PATH = File.expand_path('../config/application', __dir__)
require File.expand_path('../config/boot', __dir__)
require 'rails/commands'
doorkeeper-5.0.2/spec/dummy/app/ 0000755 0000041 0000041 00000000000 13410042500 016537 5 ustar www-data www-data doorkeeper-5.0.2/spec/dummy/app/helpers/ 0000755 0000041 0000041 00000000000 13410042500 020201 5 ustar www-data www-data doorkeeper-5.0.2/spec/dummy/app/helpers/application_helper.rb 0000644 0000041 0000041 00000000157 13410042500 024373 0 ustar www-data www-data module ApplicationHelper
def current_user
@current_user ||= User.find_by_id(session[:user_id])
end
end
doorkeeper-5.0.2/spec/dummy/app/controllers/ 0000755 0000041 0000041 00000000000 13410042500 021105 5 ustar www-data www-data doorkeeper-5.0.2/spec/dummy/app/controllers/application_controller.rb 0000644 0000041 0000041 00000000120 13410042500 026171 0 ustar www-data www-data class ApplicationController < ActionController::Base
protect_from_forgery
end
doorkeeper-5.0.2/spec/dummy/app/controllers/metal_controller.rb 0000644 0000041 0000041 00000000413 13410042500 024775 0 ustar www-data www-data class MetalController < ActionController::Metal
include AbstractController::Callbacks
include ActionController::Head
include Doorkeeper::Rails::Helpers
before_action :doorkeeper_authorize!
def index
self.response_body = { ok: true }.to_json
end
end
doorkeeper-5.0.2/spec/dummy/app/controllers/home_controller.rb 0000644 0000041 0000041 00000000576 13410042500 024635 0 ustar www-data www-data class HomeController < ApplicationController
def index; end
def sign_in
session[:user_id] = if Rails.env.development?
User.first || User.create!(name: 'Joe', password: 'sekret')
else
User.first
end
redirect_to '/'
end
def callback
render plain: 'ok'
end
end
doorkeeper-5.0.2/spec/dummy/app/controllers/full_protected_resources_controller.rb 0000644 0000041 0000041 00000000431 13410042500 031000 0 ustar www-data www-data class FullProtectedResourcesController < ApplicationController
before_action -> { doorkeeper_authorize! :write, :admin }, only: :show
before_action :doorkeeper_authorize!, only: :index
def index
render plain: 'index'
end
def show
render plain: 'show'
end
end
doorkeeper-5.0.2/spec/dummy/app/controllers/semi_protected_resources_controller.rb 0000644 0000041 0000041 00000000350 13410042500 030773 0 ustar www-data www-data class SemiProtectedResourcesController < ApplicationController
before_action :doorkeeper_authorize!, only: :index
def index
render plain: 'protected index'
end
def show
render plain: 'non protected show'
end
end
doorkeeper-5.0.2/spec/dummy/app/controllers/custom_authorizations_controller.rb 0000644 0000041 0000041 00000000311 13410042500 030345 0 ustar www-data www-data class CustomAuthorizationsController < ::ApplicationController
%w[index show new create edit update destroy].each do |action|
define_method action do
render nothing: true
end
end
end
doorkeeper-5.0.2/spec/dummy/app/models/ 0000755 0000041 0000041 00000000000 13410042500 020022 5 ustar www-data www-data doorkeeper-5.0.2/spec/dummy/app/models/user.rb 0000644 0000041 0000041 00000000210 13410042500 021316 0 ustar www-data www-data class User < ActiveRecord::Base
def self.authenticate!(name, password)
User.where(name: name, password: password).first
end
end
doorkeeper-5.0.2/spec/dummy/app/assets/ 0000755 0000041 0000041 00000000000 13410042500 020041 5 ustar www-data www-data doorkeeper-5.0.2/spec/dummy/app/assets/config/ 0000755 0000041 0000041 00000000000 13410042500 021306 5 ustar www-data www-data doorkeeper-5.0.2/spec/dummy/app/assets/config/manifest.js 0000644 0000041 0000041 00000000031 13410042500 023444 0 ustar www-data www-data // JS and CSS bundles
//
doorkeeper-5.0.2/spec/dummy/app/views/ 0000755 0000041 0000041 00000000000 13410042500 017674 5 ustar www-data www-data doorkeeper-5.0.2/spec/dummy/app/views/layouts/ 0000755 0000041 0000041 00000000000 13410042500 021374 5 ustar www-data www-data doorkeeper-5.0.2/spec/dummy/app/views/layouts/application.html.erb 0000644 0000041 0000041 00000000241 13410042500 025331 0 ustar www-data www-data
Dummy
<%= csrf_meta_tags %>
<%= link_to "Sign in", '/sign_in' %>
<%= yield %>
doorkeeper-5.0.2/spec/dummy/app/views/home/ 0000755 0000041 0000041 00000000000 13410042500 020624 5 ustar www-data www-data doorkeeper-5.0.2/spec/dummy/app/views/home/index.html.erb 0000644 0000041 0000041 00000000000 13410042500 023356 0 ustar www-data www-data doorkeeper-5.0.2/spec/dummy/db/ 0000755 0000041 0000041 00000000000 13410042500 016344 5 ustar www-data www-data doorkeeper-5.0.2/spec/dummy/db/schema.rb 0000644 0000041 0000041 00000005306 13410042500 020135 0 ustar www-data www-data # This file is auto-generated from the current state of the database. Instead
# of editing this file, please use the migrations feature of Active Record to
# incrementally modify your database, and then regenerate this schema definition.
#
# Note that this schema.rb definition is the authoritative source for your
# database schema. If you need to create the application database on another
# system, you should be using db:schema:load, not running all the migrations
# from scratch. The latter is a flawed and unsustainable approach (the more migrations
# you'll amass, the slower it'll run and the greater likelihood for issues).
#
# It's strongly recommended that you check this file into your version control system.
ActiveRecord::Schema.define(version: 20180210183654) do
create_table "oauth_access_grants", force: :cascade do |t|
t.integer "resource_owner_id", null: false
t.integer "application_id", null: false
t.string "token", null: false
t.integer "expires_in", null: false
t.text "redirect_uri", null: false
t.datetime "created_at", null: false
t.datetime "revoked_at"
t.string "scopes"
unless ENV['WITHOUT_PKCE']
t.string "code_challenge"
t.string "code_challenge_method"
end
t.index ["token"], name: "index_oauth_access_grants_on_token", unique: true
end
create_table "oauth_access_tokens", force: :cascade do |t|
t.integer "resource_owner_id"
t.integer "application_id"
t.string "token", null: false
t.string "refresh_token"
t.integer "expires_in"
t.datetime "revoked_at"
t.datetime "created_at", null: false
t.string "scopes"
t.string "previous_refresh_token", default: "", null: false
t.index ["refresh_token"], name: "index_oauth_access_tokens_on_refresh_token", unique: true
t.index ["resource_owner_id"], name: "index_oauth_access_tokens_on_resource_owner_id"
t.index ["token"], name: "index_oauth_access_tokens_on_token", unique: true
end
create_table "oauth_applications", force: :cascade do |t|
t.string "name", null: false
t.string "uid", null: false
t.string "secret", null: false
t.text "redirect_uri", null: false
t.string "scopes", default: "", null: false
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.integer "owner_id"
t.string "owner_type"
t.boolean "confidential", default: true, null: false
t.index ["owner_id", "owner_type"], name: "index_oauth_applications_on_owner_id_and_owner_type"
t.index ["uid"], name: "index_oauth_applications_on_uid", unique: true
end
create_table "users", force: :cascade do |t|
t.string "name"
t.datetime "created_at"
t.datetime "updated_at"
t.string "password"
end
end
doorkeeper-5.0.2/spec/dummy/db/migrate/ 0000755 0000041 0000041 00000000000 13410042500 017774 5 ustar www-data www-data doorkeeper-5.0.2/spec/dummy/db/migrate/20160320211015_add_previous_refresh_token_to_access_tokens.rb0000644 0000041 0000041 00000000406 13410042500 032760 0 ustar www-data www-data # frozen_string_literal: true
class AddPreviousRefreshTokenToAccessTokens < ActiveRecord::Migration[4.2]
def change
add_column(
:oauth_access_tokens,
:previous_refresh_token,
:string,
default: "",
null: false
)
end
end
doorkeeper-5.0.2/spec/dummy/db/migrate/20151223200000_add_owner_to_application.rb 0000644 0000041 0000041 00000000465 13410042500 026776 0 ustar www-data www-data # frozen_string_literal: true
class AddOwnerToApplication < ActiveRecord::Migration[4.2]
def change
add_column :oauth_applications, :owner_id, :integer, null: true
add_column :oauth_applications, :owner_type, :string, null: true
add_index :oauth_applications, %i[owner_id owner_type]
end
end
doorkeeper-5.0.2/spec/dummy/db/migrate/20170822064514_enable_pkce.rb 0000644 0000041 0000041 00000000342 13410042500 024221 0 ustar www-data www-data class EnablePkce < ActiveRecord::Migration[4.2]
def change
add_column :oauth_access_grants, :code_challenge, :string, null: true
add_column :oauth_access_grants, :code_challenge_method, :string, null: true
end
end
doorkeeper-5.0.2/spec/dummy/db/migrate/20180210183654_add_confidential_to_applications.rb 0000644 0000041 0000041 00000000455 13410042500 030514 0 ustar www-data www-data # frozen_string_literal: true
class AddConfidentialToApplications < ActiveRecord::Migration[5.1]
def change
add_column(
:oauth_applications,
:confidential,
:boolean,
null: false,
default: true # maintaining backwards compatibility: require secrets
)
end
end
doorkeeper-5.0.2/spec/dummy/db/migrate/20111122132257_create_users.rb 0000644 0000041 0000041 00000000267 13410042500 024447 0 ustar www-data www-data # frozen_string_literal: true
class CreateUsers < ActiveRecord::Migration[4.2]
def change
create_table :users do |t|
t.string :name
t.timestamps
end
end
end
doorkeeper-5.0.2/spec/dummy/db/migrate/20120312140401_add_password_to_users.rb 0000644 0000041 0000041 00000000230 13410042500 026335 0 ustar www-data www-data # frozen_string_literal: true
class AddPasswordToUsers < ActiveRecord::Migration[4.2]
def change
add_column :users, :password, :string
end
end
doorkeeper-5.0.2/spec/dummy/db/migrate/20151223192035_create_doorkeeper_tables.rb 0000644 0000041 0000041 00000003771 13410042500 027010 0 ustar www-data www-data # frozen_string_literal: true
class CreateDoorkeeperTables < ActiveRecord::Migration[4.2]
def change
create_table :oauth_applications do |t|
t.string :name, null: false
t.string :uid, null: false
t.string :secret, null: false
t.text :redirect_uri, null: false
t.string :scopes, null: false, default: ''
t.timestamps null: false
end
add_index :oauth_applications, :uid, unique: true
create_table :oauth_access_grants do |t|
t.integer :resource_owner_id, null: false
t.references :application, null: false
t.string :token, null: false
t.integer :expires_in, null: false
t.text :redirect_uri, null: false
t.datetime :created_at, null: false
t.datetime :revoked_at
t.string :scopes
end
add_index :oauth_access_grants, :token, unique: true
add_foreign_key(
:oauth_access_grants,
:oauth_applications,
column: :application_id
)
create_table :oauth_access_tokens do |t|
t.integer :resource_owner_id
t.references :application
# If you use a custom token generator you may need to change this column
# from string to text, so that it accepts tokens larger than 255
# characters. More info on custom token generators in:
# https://github.com/doorkeeper-gem/doorkeeper/tree/v3.0.0.rc1#custom-access-token-generator
#
# t.text :token, null: false
t.string :token, null: false
t.string :refresh_token
t.integer :expires_in
t.datetime :revoked_at
t.datetime :created_at, null: false
t.string :scopes
end
add_index :oauth_access_tokens, :token, unique: true
add_index :oauth_access_tokens, :resource_owner_id
add_index :oauth_access_tokens, :refresh_token, unique: true
add_foreign_key(
:oauth_access_tokens,
:oauth_applications,
column: :application_id
)
end
end
doorkeeper-5.0.2/spec/dummy/Rakefile 0000755 0000041 0000041 00000000412 13410042500 017424 0 ustar www-data www-data #!/usr/bin/env rake
# Add your own tasks in files placed in lib/tasks ending in .rake,
# for example lib/tasks/capistrano.rake, and they will automatically be available to Rake.
require File.expand_path('config/application', __dir__)
Dummy::Application.load_tasks
doorkeeper-5.0.2/spec/dummy/config/ 0000755 0000041 0000041 00000000000 13410042500 017224 5 ustar www-data www-data doorkeeper-5.0.2/spec/dummy/config/boot.rb 0000644 0000041 0000041 00000000364 13410042500 020517 0 ustar www-data www-data require 'rubygems'
require 'bundler/setup'
orm = ENV['BUNDLE_GEMFILE'].match(/Gemfile\.(.+)\.rb/)
DOORKEEPER_ORM = (orm && orm[1]) || :active_record unless defined?(DOORKEEPER_ORM)
$LOAD_PATH.unshift File.expand_path('../../../lib', __dir__)
doorkeeper-5.0.2/spec/dummy/config/routes.rb 0000644 0000041 0000041 00000000417 13410042500 021074 0 ustar www-data www-data Rails.application.routes.draw do
use_doorkeeper
resources :semi_protected_resources
resources :full_protected_resources
get 'metal.json' => 'metal#index'
get '/callback', to: 'home#callback'
get '/sign_in', to: 'home#sign_in'
root to: 'home#index'
end
doorkeeper-5.0.2/spec/dummy/config/initializers/ 0000755 0000041 0000041 00000000000 13410042500 021732 5 ustar www-data www-data doorkeeper-5.0.2/spec/dummy/config/initializers/doorkeeper.rb 0000644 0000041 0000041 00000012227 13410042500 024422 0 ustar www-data www-data Doorkeeper.configure do
# Change the ORM that doorkeeper will use.
orm DOORKEEPER_ORM
# This block will be called to check whether the resource owner is authenticated or not.
resource_owner_authenticator do
# Put your resource owner authentication logic here.
User.where(id: session[:user_id]).first || redirect_to(root_url, alert: 'Needs sign in.')
end
# If you didn't skip applications controller from Doorkeeper routes in your application routes.rb
# file then you need to declare this block in order to restrict access to the web interface for
# adding oauth authorized applications. In other case it will return 403 Forbidden response
# every time somebody will try to access the admin web interface.
#
# admin_authenticator do
# # Put your admin authentication logic here.
# # Example implementation:
# Admin.find_by_id(session[:admin_id]) || redirect_to(new_admin_session_url)
# end
# Authorization Code expiration time (default 10 minutes).
# authorization_code_expires_in 10.minutes
# Access token expiration time (default 2 hours).
# If you want to disable expiration, set this to nil.
# access_token_expires_in 2.hours
# Reuse access token for the same resource owner within an application (disabled by default)
# Rationale: https://github.com/doorkeeper-gem/doorkeeper/issues/383
# reuse_access_token
# Issue access tokens with refresh token (disabled by default)
use_refresh_token
# Forbids creating/updating applications with arbitrary scopes that are
# not in configuration, i.e. `default_scopes` or `optional_scopes`.
# (disabled by default)
#
# enforce_configured_scopes
# 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
# Note: you must also run the rails g doorkeeper:application_owner generator to provide the necessary support
# enable_application_owner confirmation: false
# Define access token scopes for your provider
# For more information go to
# https://github.com/doorkeeper-gem/doorkeeper/wiki/Using-Scopes
default_scopes :public
optional_scopes :write, :update
# 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.
# Check out the wiki for more information on customization
# client_credentials :from_basic, :from_params
# 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.
# Check out the wiki for more information on customization
# access_token_methods :from_bearer_authorization, :from_access_token_param, :from_bearer_param
# Change the native redirect uri for client apps
# When clients register with the following redirect uri, they won't be redirected to any server and
# the authorization code will be displayed within the provider
# The value can be any string. Use nil to disable this feature. When disabled, clients must provide a valid URL
# (Similar behaviour: https://developers.google.com/accounts/docs/OAuth2InstalledApp#choosingredirecturi)
#
# native_redirect_uri 'urn:ietf:wg:oauth:2.0:oob'
# 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.
#
# force_ssl_in_redirect_uri !Rails.env.development?
# Specify what grant flows are enabled in array of Strings. The valid
# strings and the flows they enable are:
#
# "authorization_code" => Authorization Code Grant Flow
# "implicit" => Implicit Grant Flow
# "password" => Resource Owner Password Credentials Grant Flow
# "client_credentials" => Client Credentials Grant Flow
#
# If not specified, Doorkeeper enables authorization_code and
# client_credentials.
#
# implicit and password grant flows have risks that you should understand
# before enabling:
# http://tools.ietf.org/html/rfc6819#section-4.4.2
# http://tools.ietf.org/html/rfc6819#section-4.4.3
#
# grant_flows %w[authorization_code client_credentials]
# Hook into the strategies' request & response life-cycle in case your
# application needs advanced customization or logging:
#
# before_successful_strategy_response do |request|
# puts "BEFORE HOOK FIRED! #{request}"
# end
#
# after_successful_strategy_response do |request, response|
# puts "AFTER HOOK FIRED! #{request}, #{response}"
# end
# Under some circumstances you might want to have applications auto-approved,
# so that the user skips the authorization step.
# For example if dealing with a trusted application.
# skip_authorization do |resource_owner, client|
# client.superapp? or resource_owner.admin?
# end
# WWW-Authenticate Realm (default "Doorkeeper").
realm "Doorkeeper"
end
doorkeeper-5.0.2/spec/dummy/config/initializers/backtrace_silencers.rb 0000644 0000041 0000041 00000000624 13410042500 026247 0 ustar www-data www-data # Be sure to restart your server when you modify this file.
# You can add backtrace silencers for libraries that you're using but don't wish to see in your backtraces.
# Rails.backtrace_cleaner.add_silencer { |line| line =~ /my_noisy_library/ }
# You can also remove all the silencers if you're trying to debug a problem that might stem from framework code.
# Rails.backtrace_cleaner.remove_silencers!
doorkeeper-5.0.2/spec/dummy/config/initializers/session_store.rb 0000644 0000041 0000041 00000000627 13410042500 025163 0 ustar www-data www-data # Be sure to restart your server when you modify this file.
Dummy::Application.config.session_store :cookie_store, key: '_dummy_session'
# Use the database for sessions instead of the cookie-based default,
# which shouldn't be used to store highly confidential information
# (create the session table with "rails generate session_migration")
# Dummy::Application.config.session_store :active_record_store
doorkeeper-5.0.2/spec/dummy/config/initializers/new_framework_defaults.rb 0000644 0000041 0000041 00000000677 13410042500 027026 0 ustar www-data www-data # Require `belongs_to` associations by default. This is a new Rails 5.0
# default, so it is introduced as a configuration option to ensure that apps
# made on earlier versions of Rails are not affected when upgrading.
if Rails::VERSION::MAJOR >= 5
Rails.application.config.active_record.belongs_to_required_by_default = true
Rails.application.config.active_record.sqlite3.represent_boolean_as_integer = true if Rails::VERSION::MINOR >= 2
end
doorkeeper-5.0.2/spec/dummy/config/initializers/secret_token.rb 0000644 0000041 0000041 00000000646 13410042500 024752 0 ustar www-data www-data # Be sure to restart your server when you modify this file.
# Your secret key for verifying the integrity of signed cookies.
# If you change this key, all old signed cookies will become invalid!
# Make sure the secret is at least 30 characters and all random,
# no regular words or you'll be exposed to dictionary attacks.
Dummy::Application.config.secret_key_base =
'c00157b5a1bb6181792f0f4a8a080485de7bab9987e6cf159'
doorkeeper-5.0.2/spec/dummy/config/initializers/wrap_parameters.rb 0000644 0000041 0000041 00000000721 13410042500 025453 0 ustar www-data www-data # Be sure to restart your server when you modify this file.
#
# This file contains settings for ActionController::ParamsWrapper which
# is enabled by default.
# Enable parameter wrapping for JSON. You can disable this by setting :format to an empty array.
ActiveSupport.on_load(:action_controller) do
wrap_parameters format: [:json]
end
# Disable root element in JSON by default.
ActiveSupport.on_load(:active_record) do
self.include_root_in_json = false
end
doorkeeper-5.0.2/spec/dummy/config/application.rb 0000644 0000041 0000041 00000001162 13410042500 022054 0 ustar www-data www-data require File.expand_path('boot', __dir__)
require 'rails/all'
Bundler.require(*Rails.groups)
require 'yaml'
orm = if DOORKEEPER_ORM =~ /mongoid/
Mongoid.load!(File.join(File.dirname(File.expand_path(__FILE__)), "#{DOORKEEPER_ORM}.yml"))
:mongoid
else
DOORKEEPER_ORM
end
require "#{orm}/railtie"
module Dummy
class Application < Rails::Application
# Settings in config/environments/* take precedence over those specified here.
# Application configuration should go into files in config/initializers
# -- all .rb files in that directory are automatically loaded.
end
end
doorkeeper-5.0.2/spec/dummy/config/locales/ 0000755 0000041 0000041 00000000000 13410042500 020646 5 ustar www-data www-data doorkeeper-5.0.2/spec/dummy/config/locales/doorkeeper.en.yml 0000644 0000041 0000041 00000000147 13410042500 024133 0 ustar www-data www-data en:
doorkeeper:
scopes:
public: "Access your public data"
write: "Update your data"
doorkeeper-5.0.2/spec/dummy/config/environment.rb 0000644 0000041 0000041 00000000220 13410042500 022107 0 ustar www-data www-data # Load the rails application
require File.expand_path('application', __dir__)
# Initialize the rails application
Rails.application.initialize!
doorkeeper-5.0.2/spec/dummy/config/environments/ 0000755 0000041 0000041 00000000000 13410042500 021753 5 ustar www-data www-data doorkeeper-5.0.2/spec/dummy/config/environments/test.rb 0000644 0000041 0000041 00000003403 13410042500 023257 0 ustar www-data www-data Dummy::Application.configure do
# Settings specified here will take precedence over those in config/application.rb
# The test environment is used exclusively to run your application's
# test suite. You never need to work with it otherwise. Remember that
# your test database is "scratch space" for the test suite and is wiped
# and recreated between test runs. Don't rely on the data there!
config.cache_classes = true
config.assets.enabled = true
config.assets.version = '1.0'
config.assets.digest = false
# Do not eager load code on boot. This avoids loading your whole application
# just for the purpose of running a single test. If you are using a tool that
# preloads Rails for running tests, you may have to set it to true.
config.eager_load = false
# Show full error reports and disable caching
config.consider_all_requests_local = true
config.action_controller.perform_caching = false
# Raise exceptions instead of rendering exception templates
config.action_dispatch.show_exceptions = false
# Disable request forgery protection in test environment
config.action_controller.allow_forgery_protection = false
# Tell Action Mailer not to deliver emails to the real world.
# The :test delivery method accumulates sent emails in the
# ActionMailer::Base.deliveries array.
# config.action_mailer.delivery_method = :test
# Use SQL instead of Active Record's schema dumper when creating the test database.
# This is necessary if your schema can't be completely dumped by the schema dumper,
# like if you have constraints or database-specific column types
# config.active_record.schema_format = :sql
# Print deprecation notices to the stderr
config.active_support.deprecation = :stderr
config.eager_load = true
end
doorkeeper-5.0.2/spec/dummy/config/environments/production.rb 0000644 0000041 0000041 00000004205 13410042500 024467 0 ustar www-data www-data Dummy::Application.configure do
# Settings specified here will take precedence over those in config/application.rb
# Code is not reloaded between requests
config.cache_classes = true
# Full error reports are disabled and caching is turned on
config.consider_all_requests_local = false
config.action_controller.perform_caching = true
# Disable Rails's static asset server (Apache or nginx will already do this)
config.serve_static_assets = false
# Compress JavaScripts and CSS
config.assets.compress = true
# Don't fallback to assets pipeline if a precompiled asset is missed
config.assets.compile = false
# Generate digests for assets URLs
config.assets.digest = true
# Defaults to Rails.root.join("public/assets")
# config.assets.manifest = YOUR_PATH
# Specifies the header that your server uses for sending files
# config.action_dispatch.x_sendfile_header = "X-Sendfile" # for apache
# config.action_dispatch.x_sendfile_header = 'X-Accel-Redirect' # for nginx
# Force all access to the app over SSL, use Strict-Transport-Security, and use secure cookies.
# config.force_ssl = true
# See everything in the log (default is :info)
# config.log_level = :debug
# Use a different logger for distributed setups
# config.logger = SyslogLogger.new
# Use a different cache store in production
# config.cache_store = :mem_cache_store
# Enable serving of images, stylesheets, and JavaScripts from an asset server
# config.action_controller.asset_host = "http://assets.example.com"
# Precompile additional assets (application.js, application.css, and all non-JS/CSS are already added)
# config.assets.precompile += %w( search.js )
# Disable delivery errors, bad email addresses will be ignored
# config.action_mailer.raise_delivery_errors = false
# Enable threaded mode
# config.threadsafe!
# Enable locale fallbacks for I18n (makes lookups for any locale fall back to
# the I18n.default_locale when a translation can not be found)
config.i18n.fallbacks = true
# Send deprecation notices to registered listeners
config.active_support.deprecation = :notify
config.eager_load = true
end
doorkeeper-5.0.2/spec/dummy/config/environments/development.rb 0000644 0000041 0000041 00000001757 13410042500 024634 0 ustar www-data www-data Dummy::Application.configure do
# Settings specified here will take precedence over those in config/application.rb
# In the development environment your application's code is reloaded on
# every request. This slows down response time but is perfect for development
# since you don't have to restart the web server when you make code changes.
config.cache_classes = false
# Show full error reports and disable caching
config.consider_all_requests_local = true
config.action_controller.perform_caching = false
# Don't care if the mailer can't send
# config.action_mailer.raise_delivery_errors = false
# Print deprecation notices to the Rails logger
config.active_support.deprecation = :log
# Only use best-standards-support built into browsers
config.action_dispatch.best_standards_support = :builtin
# Do not compress assets
config.assets.compress = false
# Expands the lines which load the assets
config.assets.debug = true
config.eager_load = false
end
doorkeeper-5.0.2/spec/dummy/config/database.yml 0000644 0000041 0000041 00000000343 13410042500 021513 0 ustar www-data www-data development:
adapter: sqlite3
database: db/development.sqlite3
pool: 5
timeout: 5000
test:
adapter: sqlite3
database: ":memory:"
timeout: 500
production:
adapter: sqlite3
database: ":memory:"
timeout: 500
doorkeeper-5.0.2/spec/dummy/public/ 0000755 0000041 0000041 00000000000 13410042500 017235 5 ustar www-data www-data doorkeeper-5.0.2/spec/dummy/public/422.html 0000644 0000041 0000041 00000001307 13410042500 020433 0 ustar www-data www-data
The change you wanted was rejected (422)
The change you wanted was rejected.
Maybe you tried to change something you didn't have access to.
doorkeeper-5.0.2/spec/dummy/public/500.html 0000644 0000041 0000041 00000001330 13410042500 020424 0 ustar www-data www-data
We're sorry, but something went wrong (500)
We're sorry, but something went wrong.
We've been notified about this issue and we'll take a look at it shortly.
doorkeeper-5.0.2/spec/dummy/public/404.html 0000644 0000041 0000041 00000001330 13410042500 020427 0 ustar www-data www-data
The page you were looking for doesn't exist (404)
The page you were looking for doesn't exist.
You may have mistyped the address or the page may have moved.
doorkeeper-5.0.2/spec/dummy/public/favicon.ico 0000644 0000041 0000041 00000000000 13410042500 021344 0 ustar www-data www-data doorkeeper-5.0.2/spec/spec_helper.rb 0000644 0000041 0000041 00000002420 13410042500 017440 0 ustar www-data www-data require 'coveralls'
Coveralls.wear!('rails') do
add_filter('/spec/')
add_filter('/lib/generators/doorkeeper/templates/')
end
ENV['RAILS_ENV'] ||= 'test'
$LOAD_PATH.unshift File.dirname(__FILE__)
require "#{File.dirname(__FILE__)}/support/doorkeeper_rspec.rb"
DOORKEEPER_ORM = Doorkeeper::RSpec.detect_orm
require 'dummy/config/environment'
require 'rspec/rails'
require 'capybara/rspec'
require 'database_cleaner'
require 'generator_spec/test_case'
# Load JRuby SQLite3 if in that platform
if defined? JRUBY_VERSION
require 'jdbc/sqlite3'
Jdbc::SQLite3.load_driver
end
Doorkeeper::RSpec.print_configuration_info
# Remove after dropping support of Rails 4.2
require "#{File.dirname(__FILE__)}/support/http_method_shim.rb"
require "support/orm/#{DOORKEEPER_ORM}"
Dir["#{File.dirname(__FILE__)}/support/{dependencies,helpers,shared}/*.rb"].each { |file| require file }
RSpec.configure do |config|
config.infer_spec_type_from_file_location!
config.mock_with :rspec
config.infer_base_class_for_anonymous_controllers = false
config.include RSpec::Rails::RequestExampleGroup, type: :request
config.before do
DatabaseCleaner.start
Doorkeeper.configure { orm DOORKEEPER_ORM }
end
config.after do
DatabaseCleaner.clean
end
config.order = 'random'
end
doorkeeper-5.0.2/spec/spec_helper_integration.rb 0000644 0000041 0000041 00000000057 13410042500 022047 0 ustar www-data www-data # For compatibility only
require 'spec_helper'
doorkeeper-5.0.2/spec/support/ 0000755 0000041 0000041 00000000000 13410042500 016340 5 ustar www-data www-data doorkeeper-5.0.2/spec/support/doorkeeper_rspec.rb 0000644 0000041 0000041 00000001255 13410042500 022223 0 ustar www-data www-data module Doorkeeper
class RSpec
# Print's useful information about env: Ruby / Rails versions,
# Doorkeeper configuration, etc.
def self.print_configuration_info
puts <<-INFO.strip_heredoc
====> Doorkeeper ORM: '#{Doorkeeper.configuration.orm}'
====> Doorkeeper version: #{Doorkeeper.gem_version}
====> Rails version: #{::Rails.version}
====> Ruby version: #{RUBY_VERSION} on #{RUBY_PLATFORM}
INFO
end
# Tries to find ORM from the Gemfile used to run test suite
def self.detect_orm
orm = (ENV['BUNDLE_GEMFILE'] || '').match(/Gemfile\.(.+)\.rb/)
(orm && orm[1] || :active_record).to_sym
end
end
end
doorkeeper-5.0.2/spec/support/helpers/ 0000755 0000041 0000041 00000000000 13410042500 020002 5 ustar www-data www-data doorkeeper-5.0.2/spec/support/helpers/url_helper.rb 0000644 0000041 0000041 00000004253 13410042500 022474 0 ustar www-data www-data module UrlHelper
def token_endpoint_url(options = {})
parameters = {
code: options[:code],
client_id: options[:client_id] || options[:client].try(:uid),
client_secret: options[:client_secret] || options[:client].try(:secret),
redirect_uri: options[:redirect_uri] || options[:client].try(:redirect_uri),
grant_type: options[:grant_type] || 'authorization_code',
code_verifier: options[:code_verifier],
code_challenge_method: options[:code_challenge_method]
}.reject { |_, v| v.blank? }
"/oauth/token?#{build_query(parameters)}"
end
def password_token_endpoint_url(options = {})
parameters = {
code: options[:code],
client_id: options[:client_id] || options[:client].try(:uid),
client_secret: options[:client_secret] || options[:client].try(:secret),
username: options[:resource_owner_username] || options[:resource_owner].try(:name),
password: options[:resource_owner_password] || options[:resource_owner].try(:password),
scope: options[:scope],
grant_type: 'password'
}
"/oauth/token?#{build_query(parameters)}"
end
def authorization_endpoint_url(options = {})
parameters = {
client_id: options[:client_id] || options[:client].try(:uid),
redirect_uri: options[:redirect_uri] || options[:client].try(:redirect_uri),
response_type: options[:response_type] || 'code',
scope: options[:scope],
state: options[:state],
code_challenge: options[:code_challenge],
code_challenge_method: options[:code_challenge_method]
}.reject { |_, v| v.blank? }
"/oauth/authorize?#{build_query(parameters)}"
end
def refresh_token_endpoint_url(options = {})
parameters = {
refresh_token: options[:refresh_token],
client_id: options[:client_id] || options[:client].try(:uid),
client_secret: options[:client_secret] || options[:client].try(:secret),
grant_type: options[:grant_type] || 'refresh_token'
}
"/oauth/token?#{build_query(parameters)}"
end
def revocation_token_endpoint_url
'/oauth/revoke'
end
def build_query(hash)
Rack::Utils.build_query(hash)
end
end
RSpec.configuration.send :include, UrlHelper
doorkeeper-5.0.2/spec/support/helpers/model_helper.rb 0000644 0000041 0000041 00000004630 13410042500 022771 0 ustar www-data www-data module ModelHelper
def client_exists(client_attributes = {})
@client = FactoryBot.create(:application, client_attributes)
end
def create_resource_owner
@resource_owner = User.create!(name: 'Joe', password: 'sekret')
end
def authorization_code_exists(options = {})
@authorization = FactoryBot.create(:access_grant, options)
end
def access_token_exists(options = {})
@access_token = FactoryBot.create(:access_token, options)
end
def access_grant_should_exist_for(client, resource_owner)
grant = Doorkeeper::AccessGrant.first
expect(grant.application).to have_attributes(id: client.id)
.and(be_instance_of(Doorkeeper::Application))
expect(grant.resource_owner_id).to eq(resource_owner.id)
end
def access_token_should_exist_for(client, resource_owner)
token = Doorkeeper::AccessToken.first
expect(token.application).to have_attributes(id: client.id)
.and(be_instance_of(Doorkeeper::Application))
expect(token.resource_owner_id).to eq(resource_owner.id)
end
def access_grant_should_not_exist
expect(Doorkeeper::AccessGrant.all).to be_empty
end
def access_token_should_not_exist
expect(Doorkeeper::AccessToken.all).to be_empty
end
def access_grant_should_have_scopes(*args)
grant = Doorkeeper::AccessGrant.first
expect(grant.scopes).to eq(Doorkeeper::OAuth::Scopes.from_array(args))
end
def access_token_should_have_scopes(*args)
grant = Doorkeeper::AccessToken.last
expect(grant.scopes).to eq(Doorkeeper::OAuth::Scopes.from_array(args))
end
def uniqueness_error
case DOORKEEPER_ORM
when :active_record
ActiveRecord::RecordNotUnique
when :sequel
error_classes = [Sequel::UniqueConstraintViolation, Sequel::ValidationFailed]
proc { |error| expect(error.class).to be_in(error_classes) }
when :mongo_mapper
error_classes = [MongoMapper::DocumentNotValid, Mongo::OperationFailure]
proc { |error| expect(error.class).to be_in(error_classes) }
when /mongoid/
error_classes = [Mongoid::Errors::Validations]
error_classes << Moped::Errors::OperationFailure if defined?(::Moped) # Mongoid 4
error_classes << Mongo::Error::OperationFailure if defined?(::Mongo) # Mongoid 5
proc { |error| expect(error.class).to be_in(error_classes) }
else
raise "'#{DOORKEEPER_ORM}' ORM is not supported!"
end
end
end
RSpec.configuration.send :include, ModelHelper
doorkeeper-5.0.2/spec/support/helpers/authorization_request_helper.rb 0000644 0000041 0000041 00000002627 13410042500 026345 0 ustar www-data www-data module AuthorizationRequestHelper
def resource_owner_is_authenticated(resource_owner = nil)
resource_owner ||= User.create!(name: 'Joe', password: 'sekret')
Doorkeeper.configuration.instance_variable_set(:@authenticate_resource_owner, proc { resource_owner })
end
def resource_owner_is_not_authenticated
Doorkeeper.configuration.instance_variable_set(:@authenticate_resource_owner, proc { redirect_to('/sign_in') })
end
def default_scopes_exist(*scopes)
Doorkeeper.configuration.instance_variable_set(:@default_scopes, Doorkeeper::OAuth::Scopes.from_array(scopes))
end
def optional_scopes_exist(*scopes)
Doorkeeper.configuration.instance_variable_set(:@optional_scopes, Doorkeeper::OAuth::Scopes.from_array(scopes))
end
def client_should_be_authorized(client)
expect(client.access_grants.size).to eq(1)
end
def client_should_not_be_authorized(client)
expect(client.size).to eq(0)
end
def i_should_be_on_client_callback(client)
expect(client.redirect_uri).to eq("#{current_uri.scheme}://#{current_uri.host}#{current_uri.path}")
end
def allowing_forgery_protection(&_block)
original_value = ActionController::Base.allow_forgery_protection
ActionController::Base.allow_forgery_protection = true
yield
ensure
ActionController::Base.allow_forgery_protection = original_value
end
end
RSpec.configuration.send :include, AuthorizationRequestHelper
doorkeeper-5.0.2/spec/support/helpers/request_spec_helper.rb 0000644 0000041 0000041 00000004340 13410042500 024371 0 ustar www-data www-data module RequestSpecHelper
def i_am_logged_in
allow(Doorkeeper.configuration).to receive(:authenticate_admin).and_return(->(*) {})
end
def i_should_see(content)
expect(page).to have_content(content)
end
def i_should_not_see(content)
expect(page).to have_no_content(content)
end
def i_should_be_on(path)
expect(current_path).to eq(path)
end
def url_should_have_param(param, value)
expect(current_params[param]).to eq(value)
end
def url_should_not_have_param(param)
expect(current_params).not_to have_key(param)
end
def current_params
Rack::Utils.parse_query(current_uri.query)
end
def current_uri
URI.parse(page.current_url)
end
def request_response
respond_to?(:response) ? response : page.driver.response
end
def json_response
JSON.parse(request_response.body)
end
def should_have_header(header, value)
expect(headers[header]).to eq(value)
end
def should_have_status(status)
expect(page.driver.response.status).to eq(status)
end
def with_access_token_header(token)
with_header 'Authorization', "Bearer #{token}"
end
def with_header(header, value)
page.driver.header header, value
end
def basic_auth_header_for_client(client)
ActionController::HttpAuthentication::Basic.encode_credentials client.uid, client.secret
end
def should_have_json(key, value)
expect(json_response.fetch(key)).to eq(value)
end
def should_have_json_within(key, value, range)
expect(json_response.fetch(key)).to be_within(range).of(value)
end
def should_not_have_json(key)
expect(json_response).not_to have_key(key)
end
def sign_in
visit '/'
click_on 'Sign in'
end
def create_access_token(authorization_code, client, code_verifier = nil)
page.driver.post token_endpoint_url(code: authorization_code, client: client, code_verifier: code_verifier)
end
def i_should_see_translated_error_message(key)
i_should_see translated_error_message(key)
end
def translated_error_message(key)
I18n.translate key, scope: %i[doorkeeper errors messages]
end
def response_status_should_be(status)
expect(request_response.status.to_i).to eq(status)
end
end
RSpec.configuration.send :include, RequestSpecHelper
doorkeeper-5.0.2/spec/support/helpers/access_token_request_helper.rb 0000644 0000041 0000041 00000000547 13410042500 026105 0 ustar www-data www-data module AccessTokenRequestHelper
def client_is_authorized(client, resource_owner, access_token_attributes = {})
attributes = {
application: client,
resource_owner_id: resource_owner.id
}.merge(access_token_attributes)
FactoryBot.create(:access_token, attributes)
end
end
RSpec.configuration.send :include, AccessTokenRequestHelper
doorkeeper-5.0.2/spec/support/helpers/config_helper.rb 0000644 0000041 0000041 00000000423 13410042500 023132 0 ustar www-data www-data module ConfigHelper
def config_is_set(setting, value = nil, &block)
setting_ivar = "@#{setting}"
value = block_given? ? block : value
Doorkeeper.configuration.instance_variable_set(setting_ivar, value)
end
end
RSpec.configuration.send :include, ConfigHelper
doorkeeper-5.0.2/spec/support/http_method_shim.rb 0000644 0000041 0000041 00000001762 13410042500 022232 0 ustar www-data www-data # Rails 5 deprecates calling HTTP action methods with positional arguments
# in favor of keyword arguments. However, the keyword argument form is only
# supported in Rails 5+. Since we support back to 4, we need some sort of shim
# to avoid super noisy deprecations when running tests.
module RoutingHTTPMethodShim
def get(path, **args)
super(path, args[:params], args[:headers])
end
def post(path, **args)
super(path, args[:params], args[:headers])
end
def put(path, **args)
super(path, args[:params], args[:headers])
end
end
module ControllerHTTPMethodShim
def process(action, http_method = 'GET', **args)
if (as = args.delete(:as))
@request.headers['Content-Type'] = Mime[as].to_s
end
super(action, http_method, args[:params], args[:session], args[:flash])
end
end
if ::Rails::VERSION::MAJOR < 5
RSpec.configure do |config|
config.include ControllerHTTPMethodShim, type: :controller
config.include RoutingHTTPMethodShim, type: :request
end
end
doorkeeper-5.0.2/spec/support/shared/ 0000755 0000041 0000041 00000000000 13410042500 017606 5 ustar www-data www-data doorkeeper-5.0.2/spec/support/shared/controllers_shared_context.rb 0000644 0000041 0000041 00000006275 13410042500 025605 0 ustar www-data www-data shared_context 'valid token', token: :valid do
let(:token_string) { '1A2B3C4D' }
let :token do
double(Doorkeeper::AccessToken,
accessible?: true, includes_scope?: true, acceptable?: true,
previous_refresh_token: "", revoke_previous_refresh_token!: true)
end
before :each do
allow(
Doorkeeper::AccessToken
).to receive(:by_token).with(token_string).and_return(token)
end
end
shared_context 'invalid token', token: :invalid do
let(:token_string) { '1A2B3C4D' }
let :token do
double(Doorkeeper::AccessToken,
accessible?: false, revoked?: false, expired?: false,
includes_scope?: false, acceptable?: false,
previous_refresh_token: "", revoke_previous_refresh_token!: true)
end
before :each do
allow(
Doorkeeper::AccessToken
).to receive(:by_token).with(token_string).and_return(token)
end
end
shared_context 'authenticated resource owner' do
before do
user = double(:resource, id: 1)
allow(Doorkeeper.configuration).to receive(:authenticate_resource_owner) { proc { user } }
end
end
shared_context 'not authenticated resource owner' do
before do
allow(Doorkeeper.configuration).to receive(:authenticate_resource_owner) { proc { redirect_to '/' } }
end
end
shared_context 'valid authorization request' do
let :authorization do
double(:authorization, valid?: true, authorize: true, success_redirect_uri: 'http://something.com/cb?code=token')
end
before do
allow(controller).to receive(:authorization) { authorization }
end
end
shared_context 'invalid authorization request' do
let :authorization do
double(:authorization, valid?: false, authorize: false, redirect_on_error?: false)
end
before do
allow(controller).to receive(:authorization) { authorization }
end
end
shared_context 'expired token', token: :expired do
let :token_string do
'1A2B3C4DEXP'
end
let :token do
double(Doorkeeper::AccessToken,
accessible?: false, revoked?: false, expired?: true,
includes_scope?: false, acceptable?: false,
previous_refresh_token: "", revoke_previous_refresh_token!: true)
end
before :each do
allow(
Doorkeeper::AccessToken
).to receive(:by_token).with(token_string).and_return(token)
end
end
shared_context 'revoked token', token: :revoked do
let :token_string do
'1A2B3C4DREV'
end
let :token do
double(Doorkeeper::AccessToken,
accessible?: false, revoked?: true, expired?: false,
includes_scope?: false, acceptable?: false,
previous_refresh_token: "", revoke_previous_refresh_token!: true)
end
before :each do
allow(
Doorkeeper::AccessToken
).to receive(:by_token).with(token_string).and_return(token)
end
end
shared_context 'forbidden token', token: :forbidden do
let :token_string do
'1A2B3C4DFORB'
end
let :token do
double(Doorkeeper::AccessToken,
accessible?: true, includes_scope?: true, acceptable?: false,
previous_refresh_token: "", revoke_previous_refresh_token!: true)
end
before :each do
allow(
Doorkeeper::AccessToken
).to receive(:by_token).with(token_string).and_return(token)
end
end
doorkeeper-5.0.2/spec/support/shared/models_shared_examples.rb 0000644 0000041 0000041 00000002577 13410042500 024655 0 ustar www-data www-data shared_examples 'an accessible token' do
describe :accessible? do
it 'is accessible if token is not expired' do
allow(subject).to receive(:expired?).and_return(false)
should be_accessible
end
it 'is not accessible if token is expired' do
allow(subject).to receive(:expired?).and_return(true)
should_not be_accessible
end
end
end
shared_examples 'a revocable token' do
describe :accessible? do
before { subject.save! }
it 'is accessible if token is not revoked' do
expect(subject).to be_accessible
end
it 'is not accessible if token is revoked' do
subject.revoke
expect(subject).not_to be_accessible
end
end
end
shared_examples 'a unique token' do
describe :token do
it 'is generated before validation' do
expect { subject.valid? }.to change { subject.token }.from(nil)
end
it 'is not valid if token exists' do
token1 = FactoryBot.create factory_name
token2 = FactoryBot.create factory_name
token2.token = token1.token
expect(token2).not_to be_valid
end
it 'expects database to throw an error when tokens are the same' do
token1 = FactoryBot.create factory_name
token2 = FactoryBot.create factory_name
token2.token = token1.token
expect do
token2.save!(validate: false)
end.to raise_error(uniqueness_error)
end
end
end
doorkeeper-5.0.2/spec/support/orm/ 0000755 0000041 0000041 00000000000 13410042500 017135 5 ustar www-data www-data doorkeeper-5.0.2/spec/support/orm/active_record.rb 0000644 0000041 0000041 00000000153 13410042500 022272 0 ustar www-data www-data # load schema to in memory sqlite
ActiveRecord::Migration.verbose = false
load Rails.root + 'db/schema.rb'
doorkeeper-5.0.2/spec/support/dependencies/ 0000755 0000041 0000041 00000000000 13410042500 020766 5 ustar www-data www-data doorkeeper-5.0.2/spec/support/dependencies/factory_bot.rb 0000644 0000041 0000041 00000000062 13410042500 023624 0 ustar www-data www-data require 'factory_bot'
FactoryBot.find_definitions
doorkeeper-5.0.2/RELEASING.md 0000644 0000041 0000041 00000000601 13410042500 015522 0 ustar www-data www-data # 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!
doorkeeper-5.0.2/.rubocop.yml 0000644 0000041 0000041 00000000314 13410042500 016142 0 ustar www-data www-data AllCops:
Exclude:
- "spec/dummy/db/*"
Metrics/BlockLength:
Exclude:
- spec/**/*
LineLength:
Exclude:
- spec/**/*
StringLiterals:
Enabled: false
TrailingBlankLines:
Enabled: true
doorkeeper-5.0.2/Appraisals 0000644 0000041 0000041 00000000550 13410042500 015714 0 ustar www-data www-data 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.7"
end
appraise "rails-5-1" do
gem "rails", "~> 5.1.0"
gem "rspec-rails", "~> 3.7"
end
appraise "rails-master" do
gem "rails", git: 'https://github.com/rails/rails'
gem "arel", git: 'https://github.com/rails/arel'
end
doorkeeper-5.0.2/doorkeeper.gemspec 0000644 0000041 0000041 00000002347 13410042500 017404 0 ustar www-data www-data $LOAD_PATH.push File.expand_path('../lib', __FILE__)
require 'doorkeeper/version'
Gem::Specification.new do |gem|
gem.name = 'doorkeeper'
gem.version = Doorkeeper.gem_version
gem.authors = ['Felipe Elias Philipp', 'Tute Costa', 'Jon Moss', 'Nikita Bulai']
gem.email = %w(bulaj.nikita@gmail.com)
gem.homepage = 'https://github.com/doorkeeper-gem/doorkeeper'
gem.summary = 'OAuth 2 provider for Rails and Grape'
gem.description = 'Doorkeeper is an OAuth 2 provider for Rails and Grape.'
gem.license = 'MIT'
gem.files = `git ls-files`.split("\n")
gem.test_files = `git ls-files -- spec/*`.split("\n")
gem.require_paths = ['lib']
gem.add_dependency 'railties', '>= 4.2'
gem.required_ruby_version = '>= 2.1'
gem.add_development_dependency 'capybara', '~> 2.18'
gem.add_development_dependency 'coveralls'
gem.add_development_dependency 'danger', '~> 5.0'
gem.add_development_dependency 'grape'
gem.add_development_dependency 'database_cleaner', '~> 1.6'
gem.add_development_dependency 'factory_bot', '~> 4.8'
gem.add_development_dependency 'generator_spec', '~> 0.9.3'
gem.add_development_dependency 'rake', '>= 11.3.0'
gem.add_development_dependency 'rspec-rails'
end
doorkeeper-5.0.2/.gitignore 0000644 0000041 0000041 00000000324 13410042500 015661 0 ustar www-data www-data .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
*.gem
doorkeeper-5.0.2/CODE_OF_CONDUCT.md 0000644 0000041 0000041 00000006341 13410042500 016475 0 ustar www-data www-data # 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/
doorkeeper-5.0.2/app/ 0000755 0000041 0000041 00000000000 13410042500 014452 5 ustar www-data www-data doorkeeper-5.0.2/app/helpers/ 0000755 0000041 0000041 00000000000 13410042500 016114 5 ustar www-data www-data doorkeeper-5.0.2/app/helpers/doorkeeper/ 0000755 0000041 0000041 00000000000 13410042500 020253 5 ustar www-data www-data doorkeeper-5.0.2/app/helpers/doorkeeper/dashboard_helper.rb 0000644 0000041 0000041 00000000766 13410042500 024077 0 ustar www-data www-data # frozen_string_literal: true
module Doorkeeper
module DashboardHelper
def doorkeeper_errors_for(object, method)
return if object.errors[method].blank?
output = object.errors[method].map do |msg|
content_tag(:span, class: 'form-text') do
msg.capitalize
end
end
safe_join(output)
end
def doorkeeper_submit_path(application)
application.persisted? ? oauth_application_path(application) : oauth_applications_path
end
end
end
doorkeeper-5.0.2/app/controllers/ 0000755 0000041 0000041 00000000000 13410042500 017020 5 ustar www-data www-data doorkeeper-5.0.2/app/controllers/doorkeeper/ 0000755 0000041 0000041 00000000000 13410042500 021157 5 ustar www-data www-data doorkeeper-5.0.2/app/controllers/doorkeeper/authorizations_controller.rb 0000644 0000041 0000041 00000004561 13410042500 027040 0 ustar www-data www-data # frozen_string_literal: true
module Doorkeeper
class AuthorizationsController < Doorkeeper::ApplicationController
before_action :authenticate_resource_owner!
def new
if pre_auth.authorizable?
render_success
else
render_error
end
end
# TODO: Handle raise invalid authorization
def create
redirect_or_render authorize_response
end
def destroy
redirect_or_render authorization.deny
end
private
def render_success
if skip_authorization? || matching_token?
redirect_or_render authorize_response
elsif Doorkeeper.configuration.api_only
render json: pre_auth
else
render :new
end
end
def render_error
if Doorkeeper.configuration.api_only
render json: pre_auth.error_response.body,
status: :bad_request
else
render :error
end
end
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?
if Doorkeeper.configuration.api_only
render(
json: { status: :redirect, redirect_uri: auth.redirect_uri },
status: auth.status
)
else
redirect_to auth.redirect_uri
end
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
def authorize_response
@authorize_response ||= begin
authorizable = pre_auth.authorizable?
before_successful_authorization if authorizable
auth = strategy.authorize
after_successful_authorization if authorizable
auth
end
end
def after_successful_authorization
Doorkeeper.configuration.after_successful_authorization.call(self)
end
def before_successful_authorization
Doorkeeper.configuration.before_successful_authorization.call(self)
end
end
end
doorkeeper-5.0.2/app/controllers/doorkeeper/application_controller.rb 0000644 0000041 0000041 00000000473 13410042500 026256 0 ustar www-data www-data # frozen_string_literal: true
module Doorkeeper
class ApplicationController <
Doorkeeper.configuration.base_controller.constantize
include Helpers::Controller
unless Doorkeeper.configuration.api_only
protect_from_forgery with: :exception
helper 'doorkeeper/dashboard'
end
end
end
doorkeeper-5.0.2/app/controllers/doorkeeper/application_metal_controller.rb 0000644 0000041 0000041 00000001135 13410042500 027434 0 ustar www-data www-data # frozen_string_literal: true
module Doorkeeper
class ApplicationMetalController < ActionController::Metal
MODULES = [
ActionController::Instrumentation,
AbstractController::Rendering,
ActionController::Rendering,
ActionController::Renderers::All,
AbstractController::Callbacks,
Helpers::Controller
].freeze
MODULES.each do |mod|
include mod
end
before_action :enforce_content_type,
if: -> { Doorkeeper.configuration.enforce_content_type }
ActiveSupport.run_load_hooks(:doorkeeper_metal_controller, self)
end
end
doorkeeper-5.0.2/app/controllers/doorkeeper/token_info_controller.rb 0000644 0000041 0000041 00000000675 13410042500 026112 0 ustar www-data www-data # frozen_string_literal: true
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
doorkeeper-5.0.2/app/controllers/doorkeeper/tokens_controller.rb 0000644 0000041 0000041 00000006110 13410042500 025250 0 ustar www-data www-data # frozen_string_literal: true
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.
revoke_token if authorized?
# 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?
return unless token.present?
# Client is confidential, therefore client authentication & authorization
# is required
if token.application_id? && token.application.confidential?
# We authorize client by checking token's application
server.client && server.client.application == token.application
else
# Client is public, authentication unnecessary
true
end
end
def revoke_token
token.revoke if token.accessible?
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
doorkeeper-5.0.2/app/controllers/doorkeeper/authorized_applications_controller.rb 0000644 0000041 0000041 00000001451 13410042500 030674 0 ustar www-data www-data # frozen_string_literal: true
module Doorkeeper
class AuthorizedApplicationsController < Doorkeeper::ApplicationController
before_action :authenticate_resource_owner!
def index
@applications = Application.authorized_for(current_resource_owner)
respond_to do |format|
format.html
format.json { render json: @applications }
end
end
def destroy
Application.revoke_tokens_and_grants_for(
params[:id],
current_resource_owner
)
respond_to do |format|
format.html do
redirect_to oauth_authorized_applications_url, notice: I18n.t(
:notice, scope: %i[doorkeeper flash authorized_applications destroy]
)
end
format.json { render :no_content }
end
end
end
end
doorkeeper-5.0.2/app/controllers/doorkeeper/applications_controller.rb 0000644 0000041 0000041 00000004464 13410042500 026445 0 ustar www-data www-data # frozen_string_literal: true
module Doorkeeper
class ApplicationsController < Doorkeeper::ApplicationController
layout 'doorkeeper/admin' unless Doorkeeper.configuration.api_only
before_action :authenticate_admin!
before_action :set_application, only: %i[show edit update destroy]
def index
@applications = Application.ordered_by(:created_at)
respond_to do |format|
format.html
format.json { head :no_content }
end
end
def show
respond_to do |format|
format.html
format.json { render json: @application }
end
end
def new
@application = Application.new
end
def create
@application = Application.new(application_params)
if @application.save
flash[:notice] = I18n.t(:notice, scope: %i[doorkeeper flash applications create])
respond_to do |format|
format.html { redirect_to oauth_application_url(@application) }
format.json { render json: @application }
end
else
respond_to do |format|
format.html { render :new }
format.json { render json: { errors: @application.errors.full_messages }, status: :unprocessable_entity }
end
end
end
def edit; end
def update
if @application.update(application_params)
flash[:notice] = I18n.t(:notice, scope: %i[doorkeeper flash applications update])
respond_to do |format|
format.html { redirect_to oauth_application_url(@application) }
format.json { render json: @application }
end
else
respond_to do |format|
format.html { render :edit }
format.json { render json: { errors: @application.errors.full_messages }, status: :unprocessable_entity }
end
end
end
def destroy
flash[:notice] = I18n.t(:notice, scope: %i[doorkeeper flash applications destroy]) if @application.destroy
respond_to do |format|
format.html { redirect_to oauth_applications_url }
format.json { head :no_content }
end
end
private
def set_application
@application = Application.find(params[:id])
end
def application_params
params.require(:doorkeeper_application)
.permit(:name, :redirect_uri, :scopes, :confidential)
end
end
end
doorkeeper-5.0.2/app/validators/ 0000755 0000041 0000041 00000000000 13410042500 016622 5 ustar www-data www-data doorkeeper-5.0.2/app/validators/redirect_uri_validator.rb 0000644 0000041 0000041 00000002502 13410042500 023673 0 ustar www-data www-data # frozen_string_literal: true
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
non_https = uri.try(:scheme) == 'http'
if forces_ssl.respond_to?(:call)
forces_ssl.call(uri) && non_https
else
forces_ssl && non_https
end
end
end
doorkeeper-5.0.2/app/assets/ 0000755 0000041 0000041 00000000000 13410042500 015754 5 ustar www-data www-data doorkeeper-5.0.2/app/assets/stylesheets/ 0000755 0000041 0000041 00000000000 13410042500 020330 5 ustar www-data www-data doorkeeper-5.0.2/app/assets/stylesheets/doorkeeper/ 0000755 0000041 0000041 00000000000 13410042500 022467 5 ustar www-data www-data doorkeeper-5.0.2/app/assets/stylesheets/doorkeeper/application.css 0000644 0000041 0000041 00000001620 13410042500 025503 0 ustar www-data www-data /*
*= 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;
}
doorkeeper-5.0.2/app/assets/stylesheets/doorkeeper/admin/ 0000755 0000041 0000041 00000000000 13410042500 023557 5 ustar www-data www-data doorkeeper-5.0.2/app/assets/stylesheets/doorkeeper/admin/application.css 0000644 0000041 0000041 00000000236 13410042500 026575 0 ustar www-data www-data /*
*= require doorkeeper/bootstrap.min
*
*= require_self
*= require_tree .
*/
.doorkeeper-admin .form-group > .field_with_errors {
width: 16.66667%;
}
doorkeeper-5.0.2/app/views/ 0000755 0000041 0000041 00000000000 13410042500 015607 5 ustar www-data www-data doorkeeper-5.0.2/app/views/doorkeeper/ 0000755 0000041 0000041 00000000000 13410042500 017746 5 ustar www-data www-data doorkeeper-5.0.2/app/views/doorkeeper/authorized_applications/ 0000755 0000041 0000041 00000000000 13410042500 024672 5 ustar www-data www-data doorkeeper-5.0.2/app/views/doorkeeper/authorized_applications/_delete_form.html.erb 0000644 0000041 0000041 00000000510 13410042500 030747 0 ustar www-data www-data <%- 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 %>
doorkeeper-5.0.2/app/views/doorkeeper/authorized_applications/index.html.erb 0000644 0000041 0000041 00000001344 13410042500 027440 0 ustar www-data www-data
doorkeeper-5.0.2/.hound.yml 0000644 0000041 0000041 00000000042 13410042500 015604 0 ustar www-data www-data ruby:
config_file: .rubocop.yml
doorkeeper-5.0.2/Rakefile 0000644 0000041 0000041 00000001014 13410042500 015333 0 ustar www-data www-data 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
desc 'Runs local test server'
task :server do
cd 'spec/dummy'
system 'bundle exec rails server'
end
end
Bundler::GemHelper.install_tasks
doorkeeper-5.0.2/lib/ 0000755 0000041 0000041 00000000000 13410042500 014440 5 ustar www-data www-data doorkeeper-5.0.2/lib/doorkeeper/ 0000755 0000041 0000041 00000000000 13410042500 016577 5 ustar www-data www-data doorkeeper-5.0.2/lib/doorkeeper/grape/ 0000755 0000041 0000041 00000000000 13410042500 017675 5 ustar www-data www-data doorkeeper-5.0.2/lib/doorkeeper/grape/helpers.rb 0000644 0000041 0000041 00000002601 13410042500 021663 0 ustar www-data www-data # frozen_string_literal: true
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
doorkeeper-5.0.2/lib/doorkeeper/grape/authorization_decorator.rb 0000644 0000041 0000041 00000000631 13410042500 025164 0 ustar www-data www-data # frozen_string_literal: true
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
doorkeeper-5.0.2/lib/doorkeeper/version.rb 0000644 0000041 0000041 00000000464 13410042500 020615 0 ustar www-data www-data # frozen_string_literal: true
module Doorkeeper
def self.gem_version
Gem::Version.new VERSION::STRING
end
module VERSION
# Semantic versioning
MAJOR = 5
MINOR = 0
TINY = 2
PRE = nil
# Full version number
STRING = [MAJOR, MINOR, TINY, PRE].compact.join('.')
end
end
doorkeeper-5.0.2/lib/doorkeeper/rake.rb 0000644 0000041 0000041 00000000425 13410042500 020047 0 ustar www-data www-data # frozen_string_literal: true
module Doorkeeper
module Rake
class << self
def load_tasks
glob = File.join(File.absolute_path(__dir__), 'rake', '*.rake')
Dir[glob].each do |rake_file|
load rake_file
end
end
end
end
end
doorkeeper-5.0.2/lib/doorkeeper/helpers/ 0000755 0000041 0000041 00000000000 13410042500 020241 5 ustar www-data www-data doorkeeper-5.0.2/lib/doorkeeper/helpers/controller.rb 0000644 0000041 0000041 00000003377 13410042500 022763 0 ustar www-data www-data # frozen_string_literal: true
# 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
@doorkeeper_token ||= OAuth::Token.authenticate request, *config_methods
end
def config_methods
@config_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
def enforce_content_type
return if request.content_type == 'application/x-www-form-urlencoded'
render json: {}, status: :unsupported_media_type
end
end
end
end
doorkeeper-5.0.2/lib/doorkeeper/models/ 0000755 0000041 0000041 00000000000 13410042500 020062 5 ustar www-data www-data doorkeeper-5.0.2/lib/doorkeeper/models/access_token_mixin.rb 0000644 0000041 0000041 00000021702 13410042500 024256 0 ustar www-data www-data # frozen_string_literal: true
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 revoked 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
tokens = authorized_tokens_for(application.try(:id), resource_owner_id)
tokens.detect do |token|
scopes_match?(token.scopes, scopes, application.try(:scopes))
end
end
# Checks whether the token scopes match the scopes from the parameters
#
# @param token_scopes [#to_s]
# set of scopes (any object that responds to `#to_s`)
# @param param_scopes [Doorkeeper::OAuth::Scopes]
# scopes from params
# @param app_scopes [Doorkeeper::OAuth::Scopes]
# Application scopes
#
# @return [Boolean] true if the param scopes match the token scopes,
# and all the param scopes are defined in the application (or in the
# server configuration if the application doesn't define any scopes),
# and false in other cases
#
def scopes_match?(token_scopes, param_scopes, app_scopes)
return true if token_scopes.empty? && param_scopes.empty?
(token_scopes.sort == param_scopes.sort) &&
Doorkeeper::OAuth::Helpers::ScopeChecker.valid?(
param_scopes.to_s,
Doorkeeper.configuration.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)
return access_token if access_token && !access_token.expired?
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 records 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] array of matching AccessToken objects
#
def authorized_tokens_for(application_id, resource_owner_id)
ordered_by(:created_at, :desc)
.where(application_id: application_id,
resource_owner_id: resource_owner_id,
revoked_at: nil)
end
# Convenience method for backwards-compatibility, return the last
# matching token for the given 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)
authorized_tokens_for(application_id, resource_owner_id).first
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,
scope: scopes,
expires_in: 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] 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
doorkeeper-5.0.2/lib/doorkeeper/models/concerns/ 0000755 0000041 0000041 00000000000 13410042500 021674 5 ustar www-data www-data doorkeeper-5.0.2/lib/doorkeeper/models/concerns/accessible.rb 0000644 0000041 0000041 00000000525 13410042500 024320 0 ustar www-data www-data # frozen_string_literal: true
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
doorkeeper-5.0.2/lib/doorkeeper/models/concerns/ownership.rb 0000644 0000041 0000041 00000001063 13410042500 024237 0 ustar www-data www-data # frozen_string_literal: true
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
doorkeeper-5.0.2/lib/doorkeeper/models/concerns/revocable.rb 0000644 0000041 0000041 00000002604 13410042500 024165 0 ustar www-data www-data # frozen_string_literal: true
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
doorkeeper-5.0.2/lib/doorkeeper/models/concerns/orderable.rb 0000644 0000041 0000041 00000000431 13410042500 024156 0 ustar www-data www-data # frozen_string_literal: true
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
doorkeeper-5.0.2/lib/doorkeeper/models/concerns/scopes.rb 0000644 0000041 0000041 00000000577 13410042500 023526 0 ustar www-data www-data # frozen_string_literal: true
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? { |scope| scopes.exists?(scope.to_s) }
end
end
end
end
doorkeeper-5.0.2/lib/doorkeeper/models/concerns/expirable.rb 0000644 0000041 0000041 00000001664 13410042500 024203 0 ustar www-data www-data # frozen_string_literal: true
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
doorkeeper-5.0.2/lib/doorkeeper/models/access_grant_mixin.rb 0000644 0000041 0000041 00000007017 13410042500 024254 0 ustar www-data www-data # frozen_string_literal: true
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
# never uses pkce, if pkce migrations were not generated
def uses_pkce?
pkce_supported? && code_challenge.present?
end
def pkce_supported?
respond_to? :code_challenge
end
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
# Revokes AccessGrant 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
# Implements PKCE code_challenge encoding without base64 padding as described in the spec.
# https://tools.ietf.org/html/rfc7636#appendix-A
# Appendix A. Notes on Implementing Base64url Encoding without Padding
#
# This appendix describes how to implement a base64url-encoding
# function without padding, based upon the standard base64-encoding
# function that uses padding.
#
# To be concrete, example C# code implementing these functions is shown
# below. Similar code could be used in other languages.
#
# static string base64urlencode(byte [] arg)
# {
# string s = Convert.ToBase64String(arg); // Regular base64 encoder
# s = s.Split('=')[0]; // Remove any trailing '='s
# s = s.Replace('+', '-'); // 62nd char of encoding
# s = s.Replace('/', '_'); // 63rd char of encoding
# return s;
# }
#
# An example correspondence between unencoded and encoded values
# follows. The octet sequence below encodes into the string below,
# which when decoded, reproduces the octet sequence.
#
# 3 236 255 224 193
#
# A-z_4ME
#
# https://ruby-doc.org/stdlib-2.1.3/libdoc/base64/rdoc/Base64.html#method-i-urlsafe_encode64
#
# urlsafe_encode64(bin)
# Returns the Base64-encoded version of bin. This method complies with
# "Base 64 Encoding with URL and Filename Safe Alphabet" in RFC 4648.
# The alphabet uses '-' instead of '+' and '_' instead of '/'.
# @param code_verifier [#to_s] a one time use value (any object that responds to `#to_s`)
#
# @return [#to_s] An encoded code challenge based on the provided verifier suitable for PKCE validation
def generate_code_challenge(code_verifier)
padded_result = Base64.urlsafe_encode64(Digest::SHA256.digest(code_verifier))
padded_result.split('=')[0] # Remove any trailing '='
end
def pkce_supported?
new.pkce_supported?
end
end
end
end
doorkeeper-5.0.2/lib/doorkeeper/models/application_mixin.rb 0000644 0000041 0000041 00000003104 13410042500 024114 0 ustar www-data www-data # frozen_string_literal: true
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.
#
# Public/Non-confidential applications will only find by uid if secret is
# blank.
#
# @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)
app = by_uid(uid)
return unless app
return app if secret.blank? && !app.confidential?
return unless app.secret == secret
app
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
doorkeeper-5.0.2/lib/doorkeeper/errors.rb 0000644 0000041 0000041 00000002347 13410042500 020446 0 ustar www-data www-data # frozen_string_literal: true
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 BaseResponseError < DoorkeeperError
attr_reader :response
def initialize(response)
@response = response
end
end
UnableToGenerateToken = Class.new(DoorkeeperError)
TokenGeneratorNotFound = Class.new(DoorkeeperError)
NoOrmCleaner = Class.new(DoorkeeperError)
InvalidToken = Class.new BaseResponseError
TokenExpired = Class.new InvalidToken
TokenRevoked = Class.new InvalidToken
TokenUnknown = Class.new InvalidToken
TokenForbidden = Class.new InvalidToken
end
end
doorkeeper-5.0.2/lib/doorkeeper/request/ 0000755 0000041 0000041 00000000000 13410042500 020267 5 ustar www-data www-data doorkeeper-5.0.2/lib/doorkeeper/request/authorization_code.rb 0000644 0000041 0000041 00000000703 13410042500 024506 0 ustar www-data www-data # frozen_string_literal: true
module Doorkeeper
module Request
class AuthorizationCode < Strategy
delegate :client, :parameters, to: :server
def request
@request ||= OAuth::AuthorizationCodeRequest.new(
Doorkeeper.configuration,
grant,
client,
parameters
)
end
private
def grant
AccessGrant.by_token(parameters[:code])
end
end
end
end
doorkeeper-5.0.2/lib/doorkeeper/request/refresh_token.rb 0000644 0000041 0000041 00000000713 13410042500 023453 0 ustar www-data www-data # frozen_string_literal: true
module Doorkeeper
module Request
class RefreshToken < Strategy
delegate :credentials, :parameters, to: :server
def refresh_token
AccessToken.by_refresh_token(parameters[:refresh_token])
end
def request
@request ||= OAuth::RefreshTokenRequest.new(
Doorkeeper.configuration,
refresh_token, credentials,
parameters
)
end
end
end
end
doorkeeper-5.0.2/lib/doorkeeper/request/strategy.rb 0000644 0000041 0000041 00000000533 13410042500 022457 0 ustar www-data www-data # frozen_string_literal: true
module Doorkeeper
module Request
class Strategy
attr_accessor :server
delegate :authorize, to: :request
def initialize(server)
self.server = server
end
def request
raise NotImplementedError, "request strategies must define #request"
end
end
end
end
doorkeeper-5.0.2/lib/doorkeeper/request/token.rb 0000644 0000041 0000041 00000000524 13410042500 021735 0 ustar www-data www-data # frozen_string_literal: true
module Doorkeeper
module Request
class Token < Strategy
delegate :current_resource_owner, to: :server
def pre_auth
server.context.send(:pre_auth)
end
def request
@request ||= OAuth::TokenRequest.new(pre_auth, current_resource_owner)
end
end
end
end
doorkeeper-5.0.2/lib/doorkeeper/request/client_credentials.rb 0000644 0000041 0000041 00000000530 13410042500 024445 0 ustar www-data www-data # frozen_string_literal: true
module Doorkeeper
module Request
class ClientCredentials < Strategy
delegate :client, :parameters, to: :server
def request
@request ||= OAuth::ClientCredentialsRequest.new(
Doorkeeper.configuration,
client,
parameters
)
end
end
end
end
doorkeeper-5.0.2/lib/doorkeeper/request/code.rb 0000644 0000041 0000041 00000000522 13410042500 021525 0 ustar www-data www-data # frozen_string_literal: true
module Doorkeeper
module Request
class Code < Strategy
delegate :current_resource_owner, to: :server
def pre_auth
server.context.send(:pre_auth)
end
def request
@request ||= OAuth::CodeRequest.new(pre_auth, current_resource_owner)
end
end
end
end
doorkeeper-5.0.2/lib/doorkeeper/request/password.rb 0000644 0000041 0000041 00000000612 13410042500 022455 0 ustar www-data www-data # frozen_string_literal: true
module Doorkeeper
module Request
class Password < Strategy
delegate :credentials, :resource_owner, :parameters, :client, to: :server
def request
@request ||= OAuth::PasswordAccessTokenRequest.new(
Doorkeeper.configuration,
client,
resource_owner,
parameters
)
end
end
end
end
doorkeeper-5.0.2/lib/doorkeeper/request.rb 0000644 0000041 0000041 00000002221 13410042500 020611 0 ustar www-data www-data # frozen_string_literal: true
module Doorkeeper
module Request
class << self
def authorization_strategy(response_type)
get_strategy(response_type, authorization_response_types)
rescue NameError
raise Errors::InvalidAuthorizationStrategy
end
def token_strategy(grant_type)
get_strategy(grant_type, token_grant_types)
rescue NameError
raise Errors::InvalidTokenStrategy
end
def get_strategy(grant_or_request_type, available)
raise Errors::MissingRequestStrategy if grant_or_request_type.blank?
raise NameError unless available.include?(grant_or_request_type.to_s)
build_strategy_class(grant_or_request_type)
end
private
def authorization_response_types
Doorkeeper.configuration.authorization_response_types
end
def token_grant_types
Doorkeeper.configuration.token_grant_types
end
def build_strategy_class(grant_or_request_type)
strategy_class_name = grant_or_request_type.to_s.tr(' ', '_').camelize
"Doorkeeper::Request::#{strategy_class_name}".constantize
end
end
end
end
doorkeeper-5.0.2/lib/doorkeeper/engine.rb 0000644 0000041 0000041 00000001774 13410042500 020402 0 ustar www-data www-data # frozen_string_literal: true
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|
# Force users to use:
# //= link doorkeeper/admin/application.css
# in Doorkeeper 5 for Sprockets 4 instead of precompile.
# Add note to official docs & Wiki
app.config.assets.precompile += %w[
doorkeeper/application.css
doorkeeper/admin/application.css
]
end
end
end
end
doorkeeper-5.0.2/lib/doorkeeper/stale_records_cleaner.rb 0000644 0000041 0000041 00000001036 13410042500 023446 0 ustar www-data www-data # frozen_string_literal: true
module Doorkeeper
class StaleRecordsCleaner
CLEANER_CLASS = 'StaleRecordsCleaner'.freeze
def self.for(base_scope)
orm_adapter = "doorkeeper/orm/#{Doorkeeper.configuration.orm}".classify
orm_cleaner = "#{orm_adapter}::#{CLEANER_CLASS}".constantize
orm_cleaner.new(base_scope)
rescue NameError
raise Doorkeeper::Errors::NoOrmCleaner, "'#{Doorkeeper.configuration.orm}' ORM has no cleaner!"
end
def self.new(base_scope)
self.for(base_scope)
end
end
end
doorkeeper-5.0.2/lib/doorkeeper/orm/ 0000755 0000041 0000041 00000000000 13410042500 017374 5 ustar www-data www-data doorkeeper-5.0.2/lib/doorkeeper/orm/active_record.rb 0000644 0000041 0000041 00000002210 13410042500 022525 0 ustar www-data www-data # frozen_string_literal: true
require 'active_support/lazy_load_hooks'
require 'doorkeeper/orm/active_record/stale_records_cleaner'
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
doorkeeper-5.0.2/lib/doorkeeper/orm/active_record/ 0000755 0000041 0000041 00000000000 13410042500 022205 5 ustar www-data www-data doorkeeper-5.0.2/lib/doorkeeper/orm/active_record/stale_records_cleaner.rb 0000644 0000041 0000041 00000001204 13410042500 027051 0 ustar www-data www-data # frozen_string_literal: true
module Doorkeeper
module Orm
module ActiveRecord
class StaleRecordsCleaner
def initialize(base_scope)
@base_scope = base_scope
end
def clean_revoked
table = @base_scope.arel_table
@base_scope.where.not(revoked_at: nil)
.where(table[:revoked_at].lt(Time.current))
.delete_all
end
def clean_expired(ttl)
table = @base_scope.arel_table
@base_scope.where(table[:created_at].lt(Time.current - ttl))
.delete_all
end
end
end
end
end
doorkeeper-5.0.2/lib/doorkeeper/orm/active_record/access_grant.rb 0000644 0000041 0000041 00000001651 13410042500 025171 0 ustar www-data www-data 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
doorkeeper-5.0.2/lib/doorkeeper/orm/active_record/application.rb 0000644 0000041 0000041 00000004543 13410042500 025043 0 ustar www-data www-data 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
validates :confidential, inclusion: { in: [true, false] }
validate :scopes_match_configured, if: :enforce_scopes?
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
# Revokes AccessToken and AccessGrant records that have not been revoked and
# associated with the specific Application and Resource Owner.
#
# @param resource_owner [ActiveRecord::Base]
# instance of the Resource Owner model
#
def self.revoke_tokens_and_grants_for(id, resource_owner)
AccessToken.revoke_all_for(id, resource_owner)
AccessGrant.revoke_all_for(id, resource_owner)
end
private
def generate_uid
self.uid = UniqueToken.generate if uid.blank?
end
def generate_secret
self.secret = UniqueToken.generate if secret.blank?
end
def scopes_match_configured
if scopes.present? &&
!ScopeChecker.valid?(scopes.to_s, Doorkeeper.configuration.scopes)
errors.add(:scopes, :not_match_configured)
end
end
def enforce_scopes?
Doorkeeper.configuration.enforce_configured_scopes?
end
end
end
doorkeeper-5.0.2/lib/doorkeeper/orm/active_record/access_token.rb 0000644 0000041 0000041 00000002734 13410042500 025201 0 ustar www-data www-data 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
doorkeeper-5.0.2/lib/doorkeeper/rails/ 0000755 0000041 0000041 00000000000 13410042500 017711 5 ustar www-data www-data doorkeeper-5.0.2/lib/doorkeeper/rails/routes.rb 0000644 0000041 0000041 00000005742 13410042500 021567 0 ustar www-data www-data # frozen_string_literal: true
require 'doorkeeper/rails/routes/mapping'
require 'doorkeeper/rails/routes/mapper'
module Doorkeeper
module Rails
class Routes # :nodoc:
mattr_reader :mapping do
{}
end
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)
if Doorkeeper.configuration.api_only
@mapping.skips.push(:applications, :authorized_applications)
end
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)
return if @mapping.skipped?(name)
send(method, @mapping[name])
mapping[name] = @mapping[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
doorkeeper-5.0.2/lib/doorkeeper/rails/helpers.rb 0000644 0000041 0000041 00000004366 13410042500 021711 0 ustar www-data www-data # frozen_string_literal: true
module Doorkeeper
module Rails
module Helpers
def doorkeeper_authorize!(*scopes)
@_doorkeeper_scopes = scopes.presence || Doorkeeper.configuration.default_scopes
doorkeeper_render_error unless valid_doorkeeper_token?
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
error.raise_exception! if Doorkeeper.configuration.raise_on_errors?
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
doorkeeper-5.0.2/lib/doorkeeper/rails/routes/ 0000755 0000041 0000041 00000000000 13410042500 021232 5 ustar www-data www-data doorkeeper-5.0.2/lib/doorkeeper/rails/routes/mapper.rb 0000644 0000041 0000041 00000001144 13410042500 023043 0 ustar www-data www-data # frozen_string_literal: true
module Doorkeeper
module Rails
class Routes # :nodoc:
class Mapper
def initialize
@mapping = Mapping.new
end
def map(&block)
instance_eval(&block) if block
@mapping
end
def controllers(controller_names = {})
@mapping.controllers.merge!(controller_names)
end
def skip_controllers(*controller_names)
@mapping.skips = controller_names
end
def as(alias_names = {})
@mapping.as.merge!(alias_names)
end
end
end
end
end
doorkeeper-5.0.2/lib/doorkeeper/rails/routes/mapping.rb 0000644 0000041 0000041 00000001625 13410042500 023216 0 ustar www-data www-data # frozen_string_literal: true
module Doorkeeper
module Rails
class Routes # :nodoc:
class Mapping
attr_accessor :controllers, :as, :skips
def initialize
@controllers = {
authorizations: 'doorkeeper/authorizations',
applications: 'doorkeeper/applications',
authorized_applications: 'doorkeeper/authorized_applications',
tokens: 'doorkeeper/tokens',
token_info: 'doorkeeper/token_info'
}
@as = {
authorizations: :authorization,
tokens: :token,
token_info: :token_info
}
@skips = []
end
def [](routes)
{
controllers: @controllers[routes],
as: @as[routes]
}
end
def skipped?(controller)
@skips.include?(controller)
end
end
end
end
end
doorkeeper-5.0.2/lib/doorkeeper/validations.rb 0000644 0000041 0000041 00000001175 13410042500 021445 0 ustar www-data www-data # frozen_string_literal: true
module Doorkeeper
module Validations
extend ActiveSupport::Concern
attr_accessor :error
def validate
@error = nil
self.class.validations.each do |validation|
@error = validation[:options][:error] unless send("validate_#{validation[:attribute]}")
break if @error
end
end
def valid?
validate
@error.nil?
end
module ClassMethods
def validate(attribute, options = {})
validations << { attribute: attribute, options: options }
end
def validations
@validations ||= []
end
end
end
end
doorkeeper-5.0.2/lib/doorkeeper/rake/ 0000755 0000041 0000041 00000000000 13410042500 017521 5 ustar www-data www-data doorkeeper-5.0.2/lib/doorkeeper/rake/db.rake 0000644 0000041 0000041 00000002644 13410042500 020760 0 ustar www-data www-data # frozen_string_literal: true
namespace :doorkeeper do
namespace :db do
desc 'Removes stale data from doorkeeper related database tables'
task cleanup: [
'doorkeeper:db:cleanup:revoked_tokens',
'doorkeeper:db:cleanup:expired_tokens',
'doorkeeper:db:cleanup:revoked_grants',
'doorkeeper:db:cleanup:expired_grants'
]
namespace :cleanup do
desc 'Removes stale access tokens'
task revoked_tokens: 'doorkeeper:setup' do
cleaner = Doorkeeper::StaleRecordsCleaner.new(Doorkeeper::AccessToken)
cleaner.clean_revoked
end
desc 'Removes expired (TTL passed) access tokens'
task expired_tokens: 'doorkeeper:setup' do
expirable_tokens = Doorkeeper::AccessToken.where(refresh_token: nil)
cleaner = Doorkeeper::StaleRecordsCleaner.new(expirable_tokens)
cleaner.clean_expired(Doorkeeper.configuration.access_token_expires_in)
end
desc 'Removes stale access grants'
task revoked_grants: 'doorkeeper:setup' do
cleaner = Doorkeeper::StaleRecordsCleaner.new(Doorkeeper::AccessGrant)
cleaner.clean_revoked
end
desc 'Removes expired (TTL passed) access grants'
task expired_grants: 'doorkeeper:setup' do
cleaner = Doorkeeper::StaleRecordsCleaner.new(Doorkeeper::AccessGrant)
cleaner.clean_expired(Doorkeeper.configuration.authorization_code_expires_in)
end
end
end
end
doorkeeper-5.0.2/lib/doorkeeper/rake/setup.rake 0000644 0000041 0000041 00000000140 13410042500 021520 0 ustar www-data www-data # frozen_string_literal: true
namespace :doorkeeper do
task setup: :environment do
end
end
doorkeeper-5.0.2/lib/doorkeeper/oauth.rb 0000644 0000041 0000041 00000000520 13410042500 020241 0 ustar www-data www-data # frozen_string_literal: true
module Doorkeeper
module OAuth
GRANT_TYPES = [
AUTHORIZATION_CODE = 'authorization_code'.freeze,
IMPLICIT = 'implicit'.freeze,
PASSWORD = 'password'.freeze,
CLIENT_CREDENTIALS = 'client_credentials'.freeze,
REFRESH_TOKEN = 'refresh_token'.freeze
].freeze
end
end
doorkeeper-5.0.2/lib/doorkeeper/server.rb 0000644 0000041 0000041 00000002124 13410042500 020431 0 ustar www-data www-data # frozen_string_literal: true
module Doorkeeper
class Server
attr_accessor :context
def initialize(context = nil)
@context = context
end
def authorization_request(strategy)
klass = Request.authorization_strategy strategy
klass.new self
end
def token_request(strategy)
klass = Request.token_strategy strategy
klass.new self
end
# TODO: context should be the request
def parameters
context.request.parameters
end
def client
@client ||= OAuth::Client.authenticate(credentials)
end
def client_via_uid
@client_via_uid ||= OAuth::Client.find(parameters[:client_id])
end
def current_resource_owner
context.send :current_resource_owner
end
# TODO: Use configuration and evaluate proper context on block
def resource_owner
context.send :resource_owner_from_credentials
end
def credentials
methods = Doorkeeper.configuration.client_credentials_methods
@credentials ||= OAuth::Client::Credentials.from_request(context.request, *methods)
end
end
end
doorkeeper-5.0.2/lib/doorkeeper/oauth/ 0000755 0000041 0000041 00000000000 13410042500 017717 5 ustar www-data www-data doorkeeper-5.0.2/lib/doorkeeper/oauth/client/ 0000755 0000041 0000041 00000000000 13410042500 021175 5 ustar www-data www-data doorkeeper-5.0.2/lib/doorkeeper/oauth/client/credentials.rb 0000644 0000041 0000041 00000002041 13410042500 024014 0 ustar www-data www-data # frozen_string_literal: true
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 |_, 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
# Public clients may have their secret blank, but "credentials" are
# still present
def blank?
uid.blank?
end
end
end
end
end
doorkeeper-5.0.2/lib/doorkeeper/oauth/base_response.rb 0000644 0000041 0000041 00000000547 13410042500 023102 0 ustar www-data www-data # frozen_string_literal: true
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
doorkeeper-5.0.2/lib/doorkeeper/oauth/forbidden_token_response.rb 0000644 0000041 0000041 00000001417 13410042500 025321 0 ustar www-data www-data # frozen_string_literal: true
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
@description ||= @scopes.map { |s| I18n.t(s, scope: %i[doorkeeper scopes]) }.join("\n")
end
protected
def exception_class
Doorkeeper::Errors::TokenForbidden
end
end
end
end
doorkeeper-5.0.2/lib/doorkeeper/oauth/client_credentials/ 0000755 0000041 0000041 00000000000 13410042500 023552 5 ustar www-data www-data doorkeeper-5.0.2/lib/doorkeeper/oauth/client_credentials/validation.rb 0000644 0000041 0000041 00000001767 13410042500 026244 0 ustar www-data www-data # frozen_string_literal: true
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 = server
@request = request
@client = request.client
validate
end
private
def validate_client
@client.present?
end
def validate_scopes
return true if @request.scopes.blank?
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
doorkeeper-5.0.2/lib/doorkeeper/oauth/client_credentials/creator.rb 0000644 0000041 0000041 00000000572 13410042500 025542 0 ustar www-data www-data # frozen_string_literal: true
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
doorkeeper-5.0.2/lib/doorkeeper/oauth/client_credentials/issuer.rb 0000644 0000041 0000041 00000002125 13410042500 025411 0 ustar www-data www-data # frozen_string_literal: true
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)
context = Authorization::Token.build_context(
client,
Doorkeeper::OAuth::CLIENT_CREDENTIALS,
scopes
)
ttl = Authorization::Token.access_token_expires_in(@server, context)
creator.call(
client,
scopes,
use_refresh_token: false,
expires_in: ttl
)
end
end
end
end
end
doorkeeper-5.0.2/lib/doorkeeper/oauth/password_access_token_request.rb 0000644 0000041 0000041 00000002360 13410042500 026400 0 ustar www-data www-data # frozen_string_literal: true
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]
@grant_type = Doorkeeper::OAuth::PASSWORD
end
private
def before_successful_response
find_or_create_access_token(client, resource_owner.id, scopes, server)
super
end
def validate_scopes
client_scopes = client.try(:scopes)
return true if scopes.blank?
ScopeChecker.valid?(scopes.to_s, server.scopes, client_scopes)
end
def validate_resource_owner
!resource_owner.nil?
end
def validate_client
!parameters[:client_id] || !client.nil?
end
end
end
end
doorkeeper-5.0.2/lib/doorkeeper/oauth/helpers/ 0000755 0000041 0000041 00000000000 13410042500 021361 5 ustar www-data www-data doorkeeper-5.0.2/lib/doorkeeper/oauth/helpers/unique_token.rb 0000644 0000041 0000041 00000000573 13410042500 024421 0 ustar www-data www-data # frozen_string_literal: true
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
doorkeeper-5.0.2/lib/doorkeeper/oauth/helpers/uri_checker.rb 0000644 0000041 0000041 00000002732 13410042500 024175 0 ustar www-data www-data # frozen_string_literal: true
module Doorkeeper
module OAuth
module Helpers
module URIChecker
def self.valid?(url)
return true if native_uri?(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)
unless client_url.query.nil?
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.blank? && query.blank?
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
doorkeeper-5.0.2/lib/doorkeeper/oauth/helpers/scope_checker.rb 0000644 0000041 0000041 00000002032 13410042500 024500 0 ustar www-data www-data # frozen_string_literal: true
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
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
end
end
end
end
doorkeeper-5.0.2/lib/doorkeeper/oauth/authorization/ 0000755 0000041 0000041 00000000000 13410042500 022617 5 ustar www-data www-data doorkeeper-5.0.2/lib/doorkeeper/oauth/authorization/uri_builder.rb 0000644 0000041 0000041 00000001472 13410042500 025455 0 ustar www-data www-data # frozen_string_literal: true
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
doorkeeper-5.0.2/lib/doorkeeper/oauth/authorization/context.rb 0000644 0000041 0000041 00000000534 13410042500 024632 0 ustar www-data www-data # frozen_string_literal: true
module Doorkeeper
module OAuth
module Authorization
class Context
attr_reader :client, :grant_type, :scopes
def initialize(client, grant_type, scopes)
@client = client
@grant_type = grant_type
@scopes = scopes
end
end
end
end
end
doorkeeper-5.0.2/lib/doorkeeper/oauth/authorization/token.rb 0000644 0000041 0000041 00000004370 13410042500 024270 0 ustar www-data www-data # frozen_string_literal: true
module Doorkeeper
module OAuth
module Authorization
class Token
attr_accessor :pre_auth, :resource_owner, :token
class << self
def build_context(pre_auth_or_oauth_client, grant_type, scopes)
oauth_client = if pre_auth_or_oauth_client.respond_to?(:client)
pre_auth_or_oauth_client.client
else
pre_auth_or_oauth_client
end
Doorkeeper::OAuth::Authorization::Context.new(
oauth_client,
grant_type,
scopes
)
end
def access_token_expires_in(server, context)
if (expiration = server.custom_access_token_expires_in.call(context))
expiration
else
server.access_token_expires_in
end
end
def refresh_token_enabled?(server, context)
if server.refresh_token_enabled?.respond_to? :call
server.refresh_token_enabled?.call(context)
else
!!server.refresh_token_enabled?
end
end
end
def initialize(pre_auth, resource_owner)
@pre_auth = pre_auth
@resource_owner = resource_owner
end
def issue_token
context = self.class.build_context(
pre_auth.client,
Doorkeeper::OAuth::IMPLICIT,
pre_auth.scopes
)
@token ||= AccessToken.find_or_create_for(
pre_auth.client,
resource_owner.id,
pre_auth.scopes,
self.class.access_token_expires_in(configuration, context),
false
)
end
def native_redirect
{
controller: controller,
action: :show,
access_token: token.token
}
end
private
def configuration
Doorkeeper.configuration
end
def controller
@controller ||= begin
mapping = Doorkeeper::Rails::Routes.mapping[:token_info] || {}
mapping[:controllers] || 'doorkeeper/token_info'
end
end
end
end
end
end
doorkeeper-5.0.2/lib/doorkeeper/oauth/authorization/code.rb 0000644 0000041 0000041 00000002776 13410042500 024072 0 ustar www-data www-data # frozen_string_literal: true
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! access_grant_attributes
end
def native_redirect
{ action: :show, code: token.token }
end
def configuration
Doorkeeper.configuration
end
private
def authorization_code_expires_in
configuration.authorization_code_expires_in
end
def access_grant_attributes
pkce_attributes.merge application_id: pre_auth.client.id,
resource_owner_id: resource_owner.id,
expires_in: authorization_code_expires_in,
redirect_uri: pre_auth.redirect_uri,
scopes: pre_auth.scopes.to_s
end
def pkce_attributes
return {} unless pkce_supported?
{
code_challenge: pre_auth.code_challenge,
code_challenge_method: pre_auth.code_challenge_method
}
end
# ensures firstly, if migration with additional pcke columns was
# generated and migrated
def pkce_supported?
Doorkeeper::AccessGrant.pkce_supported?
end
end
end
end
end
doorkeeper-5.0.2/lib/doorkeeper/oauth/error_response.rb 0000644 0000041 0000041 00000003736 13410042500 023324 0 ustar www-data www-data # frozen_string_literal: true
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),
redirect_uri: request.try(:redirect_uri)
)
)
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
def raise_exception!
raise exception_class.new(self), description
end
protected
delegate :realm, to: :configuration
def configuration
Doorkeeper.configuration
end
def exception_class
raise NotImplementedError, "error response must define #exception_class"
end
private
def authenticate_info
%(Bearer realm="#{realm}", error="#{name}", error_description="#{description}")
end
end
end
end
doorkeeper-5.0.2/lib/doorkeeper/oauth/token_response.rb 0000644 0000041 0000041 00000001435 13410042500 023305 0 ustar www-data www-data # frozen_string_literal: true
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
doorkeeper-5.0.2/lib/doorkeeper/oauth/client.rb 0000644 0000041 0000041 00000001301 13410042500 021515 0 ustar www-data www-data # frozen_string_literal: true
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
doorkeeper-5.0.2/lib/doorkeeper/oauth/error.rb 0000644 0000041 0000041 00000000437 13410042500 021401 0 ustar www-data www-data # frozen_string_literal: true
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
doorkeeper-5.0.2/lib/doorkeeper/oauth/pre_authorization.rb 0000644 0000041 0000041 00000005105 13410042500 024013 0 ustar www-data www-data # frozen_string_literal: true
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
validate :code_challenge_method, error: :invalid_code_challenge_method
attr_accessor :server, :client, :response_type, :redirect_uri, :state,
:code_challenge, :code_challenge_method
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]
@code_challenge = attrs[:code_challenge]
@code_challenge_method = attrs[:code_challenge_method]
end
def authorizable?
valid?
end
def scopes
Scopes.from_string scope
end
def scope
@scope.presence || build_scopes
end
def error_response
OAuth::ErrorResponse.from_request(self)
end
def as_json(_options)
{
client_id: client.uid,
redirect_uri: redirect_uri,
state: state,
response_type: response_type,
scope: scope,
client_name: client.name,
status: I18n.t('doorkeeper.pre_authorization.status')
}
end
private
def build_scopes
client_scopes = client.application.scopes
if client_scopes.blank?
server.default_scopes.to_s
else
(server.default_scopes & client_scopes).to_s
end
end
def validate_response_type
server.authorization_response_types.include? response_type
end
def validate_client
client.present?
end
def validate_scopes
return true if scope.blank?
Helpers::ScopeChecker.valid?(
scope,
server.scopes,
client.application.scopes
)
end
def validate_redirect_uri
return false if redirect_uri.blank?
Helpers::URIChecker.valid_for_authorization?(
redirect_uri,
client.redirect_uri
)
end
def validate_code_challenge_method
!code_challenge.present? || (code_challenge_method.present? && code_challenge_method =~ /^plain$|^S256$/)
end
end
end
end
doorkeeper-5.0.2/lib/doorkeeper/oauth/invalid_token_response.rb 0000644 0000041 0000041 00000002206 13410042500 025010 0 ustar www-data www-data # frozen_string_literal: true
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
protected
def exception_class
errors_mapping.fetch(reason)
end
private
def errors_mapping
{
expired: Doorkeeper::Errors::TokenExpired,
revoked: Doorkeeper::Errors::TokenRevoked,
unknown: Doorkeeper::Errors::TokenUnknown
}
end
end
end
end
doorkeeper-5.0.2/lib/doorkeeper/oauth/base_request.rb 0000644 0000041 0000041 00000003165 13410042500 022733 0 ustar www-data www-data # frozen_string_literal: true
module Doorkeeper
module OAuth
class BaseRequest
include Validations
attr_reader :grant_type
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 ||= build_scopes
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)
context = Authorization::Token.build_context(client, grant_type, scopes)
@access_token = AccessToken.find_or_create_for(
client,
resource_owner_id,
scopes,
Authorization::Token.access_token_expires_in(server, context),
Authorization::Token.refresh_token_enabled?(server, context)
)
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
private
def build_scopes
if @original_scopes.present?
OAuth::Scopes.from_string(@original_scopes)
else
client_scopes = @client.try(:scopes)
return default_scopes if client_scopes.blank?
default_scopes & @client.scopes
end
end
end
end
end
doorkeeper-5.0.2/lib/doorkeeper/oauth/authorization_code_request.rb 0000644 0000041 0000041 00000004776 13410042500 025724 0 ustar www-data www-data # frozen_string_literal: true
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
validate :code_verifier, error: :invalid_grant
attr_accessor :server, :grant, :client, :redirect_uri, :access_token,
:code_verifier
def initialize(server, grant, client, parameters = {})
@server = server
@client = client
@grant = grant
@grant_type = Doorkeeper::OAuth::AUTHORIZATION_CODE
@redirect_uri = parameters[:redirect_uri]
@code_verifier = parameters[:code_verifier]
end
private
def client_by_uid(parameters)
Doorkeeper::Application.by_uid(parameters[:client_id])
end
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
return false if grant && grant.uses_pkce? && code_verifier.blank?
return false if grant && !grant.pkce_supported? && !code_verifier.blank?
redirect_uri.present?
end
def validate_client
!client.nil?
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
# if either side (server or client) request pkce, check the verifier
# against the DB - if pkce is supported
def validate_code_verifier
return true unless grant.uses_pkce? || code_verifier
return false unless grant.pkce_supported?
if grant.code_challenge_method == 'S256'
grant.code_challenge == AccessGrant.generate_code_challenge(code_verifier)
elsif grant.code_challenge_method == 'plain'
grant.code_challenge == code_verifier
else
false
end
end
end
end
end
doorkeeper-5.0.2/lib/doorkeeper/oauth/scopes.rb 0000644 0000041 0000041 00000002470 13410042500 021543 0 ustar www-data www-data # frozen_string_literal: true
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? { |scope| exists?(scope) }
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
doorkeeper-5.0.2/lib/doorkeeper/oauth/refresh_token_request.rb 0000644 0000041 0000041 00000006032 13410042500 024653 0 ustar www-data www-data # frozen_string_literal: true
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
context = Authorization::Token.build_context(
client,
Doorkeeper::OAuth::REFRESH_TOKEN,
scopes
)
Authorization::Token.access_token_expires_in(server, context)
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
return true if credentials.blank?
client.present?
end
# @see https://tools.ietf.org/html/draft-ietf-oauth-v2-22#section-1.5
#
def validate_client_match
return true if refresh_token.application_id.blank?
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
doorkeeper-5.0.2/lib/doorkeeper/oauth/token_request.rb 0000644 0000041 0000041 00000001733 13410042500 023140 0 ustar www-data www-data # frozen_string_literal: true
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
doorkeeper-5.0.2/lib/doorkeeper/oauth/token.rb 0000644 0000041 0000041 00000003664 13410042500 021375 0 ustar www-data www-data # frozen_string_literal: true
module Doorkeeper
module OAuth
class Token
class << self
def from_request(request, *methods)
methods.inject(nil) do |_, 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)
refresh_token_enabled = Doorkeeper.configuration.refresh_token_enabled?
if access_token.present? && refresh_token_enabled
access_token.revoke_previous_refresh_token!
end
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
doorkeeper-5.0.2/lib/doorkeeper/oauth/code_response.rb 0000644 0000041 0000041 00000002051 13410042500 023072 0 ustar www-data www-data # frozen_string_literal: true
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
doorkeeper-5.0.2/lib/doorkeeper/oauth/client_credentials_request.rb 0000644 0000041 0000041 00000001345 13410042500 025652 0 ustar www-data www-data # frozen_string_literal: true
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
doorkeeper-5.0.2/lib/doorkeeper/oauth/code_request.rb 0000644 0000041 0000041 00000001456 13410042500 022734 0 ustar www-data www-data # frozen_string_literal: true
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
doorkeeper-5.0.2/lib/doorkeeper/oauth/token_introspection.rb 0000644 0000041 0000041 00000010352 13410042500 024345 0 ustar www-data www-data # frozen_string_literal: true
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
doorkeeper-5.0.2/lib/doorkeeper/config.rb 0000644 0000041 0000041 00000030731 13410042500 020375 0 ustar www-data www-data 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 || (raise MissingConfiguration)
end
def self.setup_orm_adapter
@orm_adapter = "doorkeeper/orm/#{configuration.orm}".classify.constantize
rescue NameError => error
raise error, "ORM adapter not found (#{configuration.orm})", <<-ERROR_MSG.strip_heredoc
[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, 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 if not set)
def use_refresh_token(enabled = true, &block)
@config.instance_variable_set(
:@refresh_token_enabled,
block || enabled
)
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
# Use an API mode for applications generated with --api argument
# It will skip applications controller, disable forgery protection
def api_only
@config.instance_variable_set(:@api_only, true)
end
# Forbids creating/updating applications with arbitrary scopes that are
# not in configuration, i.e. `default_scopes` or `optional_scopes`.
# (disabled by default)
def enforce_configured_scopes
@config.instance_variable_set(:@enforce_configured_scopes, true)
end
# Enforce request content type as the spec requires:
# disabled by default for backward compatibility.
def enforce_content_type
@config.instance_variable_set(:@enforce_content_type, 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 || 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: (lambda do |_routes|
::Rails.logger.warn(
I18n.t('doorkeeper.errors.messages.admin_authenticator_not_configured')
)
head :forbidden
end)
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_authorization, default: ->(_context) {}
option :after_successful_authorization, default: ->(_context) {}
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: ->(_context) { 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]
option :handle_auth_errors, default: :render
# 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
attr_reader :api_only
attr_reader :enforce_content_type
def api_only
@api_only ||= false
end
def enforce_content_type
@enforce_content_type ||= false
end
def refresh_token_enabled?
if defined?(@refresh_token_enabled)
@refresh_token_enabled
else
false
end
end
def enforce_configured_scopes?
!!(defined?(@enforce_configured_scopes) && @enforce_configured_scopes)
end
def enable_application_owner?
!!(defined?(@enable_application_owner) && @enable_application_owner)
end
def confirm_application_owner?
!!(defined?(@confirm_application_owner) && @confirm_application_owner)
end
def raise_on_errors?
handle_auth_errors == :raise
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_methods ||= %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.freeze
end
def token_grant_types
@token_grant_types ||= calculate_token_grant_types.freeze
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
doorkeeper-5.0.2/lib/doorkeeper.rb 0000644 0000041 0000041 00000005237 13410042500 017133 0 ustar www-data www-data require 'doorkeeper/version'
require 'doorkeeper/engine'
require 'doorkeeper/config'
require 'doorkeeper/request/strategy'
require 'doorkeeper/request/authorization_code'
require 'doorkeeper/request/client_credentials'
require 'doorkeeper/request/code'
require 'doorkeeper/request/password'
require 'doorkeeper/request/refresh_token'
require 'doorkeeper/request/token'
require 'doorkeeper/errors'
require 'doorkeeper/server'
require 'doorkeeper/request'
require 'doorkeeper/validations'
require 'doorkeeper/oauth/authorization/code'
require 'doorkeeper/oauth/authorization/context'
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'
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/validation'
require 'doorkeeper/oauth/client_credentials/creator'
require 'doorkeeper/oauth/client_credentials/issuer'
require 'doorkeeper/oauth/client_credentials/validation'
require 'doorkeeper/oauth/client/credentials'
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/rake'
require 'doorkeeper/stale_records_cleaner'
require 'doorkeeper/orm/active_record'
module Doorkeeper
def self.authenticate(request, methods = Doorkeeper.configuration.access_token_methods)
OAuth::Token.authenticate(request, *methods)
end
end
doorkeeper-5.0.2/lib/generators/ 0000755 0000041 0000041 00000000000 13410042500 016611 5 ustar www-data www-data doorkeeper-5.0.2/lib/generators/doorkeeper/ 0000755 0000041 0000041 00000000000 13410042500 020750 5 ustar www-data www-data doorkeeper-5.0.2/lib/generators/doorkeeper/confidential_applications_generator.rb 0000644 0000041 0000041 00000001542 13410042500 030552 0 ustar www-data www-data # frozen_string_literal: true
require 'rails/generators'
require 'rails/generators/active_record'
module Doorkeeper
class ConfidentialApplicationsGenerator < ::Rails::Generators::Base
include ::Rails::Generators::Migration
source_root File.expand_path('templates', __dir__)
desc 'Add confidential column to Doorkeeper applications'
def pkce
migration_template(
'add_confidential_to_applications.rb.erb',
'db/migrate/add_confidential_to_applications.rb',
migration_version: migration_version
)
end
def self.next_migration_number(dirname)
ActiveRecord::Generators::Base.next_migration_number(dirname)
end
private
def migration_version
if ActiveRecord::VERSION::MAJOR >= 5
"[#{ActiveRecord::VERSION::MAJOR}.#{ActiveRecord::VERSION::MINOR}]"
end
end
end
end
doorkeeper-5.0.2/lib/generators/doorkeeper/views_generator.rb 0000644 0000041 0000041 00000000671 13410042500 024504 0 ustar www-data www-data # frozen_string_literal: true
module Doorkeeper
module Generators
class ViewsGenerator < ::Rails::Generators::Base
source_root File.expand_path('../../../app/views', __dir__)
desc 'Copies default Doorkeeper views and layouts to your application.'
def manifest
directory 'doorkeeper', 'app/views/doorkeeper'
directory 'layouts/doorkeeper', 'app/views/layouts/doorkeeper'
end
end
end
end
doorkeeper-5.0.2/lib/generators/doorkeeper/templates/ 0000755 0000041 0000041 00000000000 13410042500 022746 5 ustar www-data www-data doorkeeper-5.0.2/lib/generators/doorkeeper/templates/add_owner_to_application_migration.rb.erb 0000644 0000041 0000041 00000000452 13410042500 033143 0 ustar www-data www-data class AddOwnerToApplication < ActiveRecord::Migration<%= migration_version %>
def change
add_column :oauth_applications, :owner_id, :integer, null: true
add_column :oauth_applications, :owner_type, :string, null: true
add_index :oauth_applications, [:owner_id, :owner_type]
end
end
doorkeeper-5.0.2/lib/generators/doorkeeper/templates/enable_pkce_migration.rb.erb 0000644 0000041 0000041 00000000365 13410042500 030347 0 ustar www-data www-data class EnablePkce < ActiveRecord::Migration<%= migration_version %>
def change
add_column :oauth_access_grants, :code_challenge, :string, null: true
add_column :oauth_access_grants, :code_challenge_method, :string, null: true
end
end
doorkeeper-5.0.2/lib/generators/doorkeeper/templates/add_confidential_to_applications.rb.erb 0000644 0000041 0000041 00000000411 13410042500 032555 0 ustar www-data www-data # frozen_string_literal: true
class AddConfidentialToApplications < ActiveRecord::Migration<%= migration_version %>
def change
add_column(
:oauth_applications,
:confidential,
:boolean,
null: false,
default: true
)
end
end
doorkeeper-5.0.2/lib/generators/doorkeeper/templates/migration.rb.erb 0000644 0000041 0000041 00000004700 13410042500 026034 0 ustar www-data www-data class CreateDoorkeeperTables < ActiveRecord::Migration<%= migration_version %>
def change
create_table :oauth_applications do |t|
t.string :name, null: false
t.string :uid, null: false
t.string :secret, null: false
t.text :redirect_uri, null: false
t.string :scopes, null: false, default: ''
t.boolean :confidential, null: false, default: true
t.timestamps null: false
end
add_index :oauth_applications, :uid, unique: true
create_table :oauth_access_grants do |t|
t.references :resource_owner, null: false
t.references :application, null: false
t.string :token, null: false
t.integer :expires_in, null: false
t.text :redirect_uri, null: false
t.datetime :created_at, null: false
t.datetime :revoked_at
t.string :scopes
end
add_index :oauth_access_grants, :token, unique: true
add_foreign_key(
:oauth_access_grants,
:oauth_applications,
column: :application_id
)
create_table :oauth_access_tokens do |t|
t.references :resource_owner, index: true
t.references :application
# If you use a custom token generator you may need to change this column
# from string to text, so that it accepts tokens larger than 255
# characters. More info on custom token generators in:
# https://github.com/doorkeeper-gem/doorkeeper/tree/v3.0.0.rc1#custom-access-token-generator
#
# t.text :token, null: false
t.string :token, null: false
t.string :refresh_token
t.integer :expires_in
t.datetime :revoked_at
t.datetime :created_at, null: false
t.string :scopes
# If there is a previous_refresh_token column,
# refresh tokens will be revoked after a related access token is used.
# If there is no previous_refresh_token column,
# previous tokens are revoked as soon as a new access token is created.
# Comment out this line if you'd rather have refresh tokens
# instantly revoked.
t.string :previous_refresh_token, null: false, default: ""
end
add_index :oauth_access_tokens, :token, unique: true
add_index :oauth_access_tokens, :refresh_token, unique: true
add_foreign_key(
:oauth_access_tokens,
:oauth_applications,
column: :application_id
)
end
end
doorkeeper-5.0.2/lib/generators/doorkeeper/templates/initializer.rb 0000644 0000041 0000041 00000022121 13410042500 025614 0 ustar www-data www-data Doorkeeper.configure do
# Change the ORM that doorkeeper will use (needs plugins)
orm :active_record
# This block will be called to check whether the resource owner is authenticated or not.
resource_owner_authenticator do
raise "Please configure doorkeeper resource_owner_authenticator block located in #{__FILE__}"
# Put your resource owner authentication logic here.
# Example implementation:
# User.find_by_id(session[:user_id]) || redirect_to(new_user_session_url)
end
# If you didn't skip applications controller from Doorkeeper routes in your application routes.rb
# file then you need to declare this block in order to restrict access to the web interface for
# adding oauth authorized applications. In other case it will return 403 Forbidden response
# every time somebody will try to access the admin web interface.
#
# admin_authenticator do
# # Put your admin authentication logic here.
# # Example implementation:
#
# if current_user
# head :forbidden unless current_user.admin?
# else
# redirect_to sign_in_url
# end
# end
# If you are planning to use Doorkeeper in Rails 5 API-only application, then you might
# want to use API mode that will skip all the views management and change the way how
# Doorkeeper responds to a requests.
#
# api_only
# Enforce token request content type to application/x-www-form-urlencoded.
# It is not enabled by default to not break prior versions of the gem.
#
# enforce_content_type
# Authorization Code expiration time (default 10 minutes).
#
# authorization_code_expires_in 10.minutes
# Access token expiration time (default 2 hours).
# If you want to disable expiration, set this to nil.
#
# access_token_expires_in 2.hours
# Assign custom TTL for access tokens. Will be used instead of access_token_expires_in
# option if defined. `context` has the following properties available
#
# `client` - the OAuth client application (see Doorkeeper::OAuth::Client)
# `grant_type` - the grant type of the request (see Doorkeeper::OAuth)
# `scopes` - the requested scopes (see Doorkeeper::OAuth::Scopes)
#
# custom_access_token_expires_in do |context|
# context.client.application.additional_settings.implicit_oauth_expiration
# end
# Use a custom class for generating the access token.
# See https://github.com/doorkeeper-gem/doorkeeper#custom-access-token-generator
#
# access_token_generator '::Doorkeeper::JWT'
# The controller Doorkeeper::ApplicationController inherits from.
# Defaults to ActionController::Base.
# See https://github.com/doorkeeper-gem/doorkeeper#custom-base-controller
#
# base_controller 'ApplicationController'
# Reuse access token for the same resource owner within an application (disabled by default).
#
# This option protects your application from creating new tokens before old valid one becomes
# expired so your database doesn't bloat. Keep in mind that when this option is `on` Doorkeeper
# doesn't updates existing token expiration time, it will create a new token instead.
# Rationale: https://github.com/doorkeeper-gem/doorkeeper/issues/383
#
# reuse_access_token
# Issue access tokens with refresh token (disabled by default), you may also
# pass a block which accepts `context` to customize when to give a refresh
# token or not. Similar to `custom_access_token_expires_in`, `context` has
# the properties:
#
# `client` - the OAuth client application (see Doorkeeper::OAuth::Client)
# `grant_type` - the grant type of the request (see Doorkeeper::OAuth)
# `scopes` - the requested scopes (see Doorkeeper::OAuth::Scopes)
#
# use_refresh_token
# Forbids creating/updating applications with arbitrary scopes that are
# not in configuration, i.e. `default_scopes` or `optional_scopes`.
# (disabled by default)
#
# enforce_configured_scopes
# 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
# Note: you must also run the rails g doorkeeper:application_owner generator to provide the necessary support
#
# enable_application_owner confirmation: false
# Define access token scopes for your provider
# For more information go to
# https://github.com/doorkeeper-gem/doorkeeper/wiki/Using-Scopes
#
# default_scopes :public
# optional_scopes :write, :update
# 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.
# Check out https://github.com/doorkeeper-gem/doorkeeper/wiki/Changing-how-clients-are-authenticated
# for more information on customization
#
# client_credentials :from_basic, :from_params
# 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.
# Check out https://github.com/doorkeeper-gem/doorkeeper/wiki/Changing-how-clients-are-authenticated
# for more information on customization
#
# access_token_methods :from_bearer_authorization, :from_access_token_param, :from_bearer_param
# Change the native redirect uri for client apps
# When clients register with the following redirect uri, they won't be redirected to any server and
# the authorizationcode will be displayed within the provider
# The value can be any string. Use nil to disable this feature. When disabled, clients must provide a valid URL
# (Similar behaviour: https://developers.google.com/accounts/docs/OAuth2InstalledApp#choosingredirecturi)
#
# native_redirect_uri 'urn:ietf:wg:oauth:2.0:oob'
# 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.
#
# Callable objects such as proc, lambda, block or any object that responds to
# #call can be used in order to allow conditional checks (to allow non-SSL
# redirects to localhost for example).
#
# force_ssl_in_redirect_uri !Rails.env.development?
#
# force_ssl_in_redirect_uri { |uri| uri.host != 'localhost' }
# Specify what redirect URI's you want to block during Application creation.
# Any redirect URI is whitelisted by default.
#
# You can use this option in order to forbid URI's with 'javascript' scheme
# for example.
#
# forbid_redirect_uri { |uri| uri.scheme.to_s.downcase == 'javascript' }
# Specify how authorization errors should be handled.
# By default, doorkeeper renders json errors when access token
# is invalid, expired, revoked or has invalid scopes.
#
# If you want to render error response yourself (i.e. rescue exceptions),
# set handle_auth_errors to `:raise` and rescue Doorkeeper::Errors::InvalidToken
# or following specific errors:
#
# Doorkeeper::Errors::TokenForbidden, Doorkeeper::Errors::TokenExpired,
# Doorkeeper::Errors::TokenRevoked, Doorkeeper::Errors::TokenUnknown
#
# handle_auth_errors :raise
# Specify what grant flows are enabled in array of Strings. The valid
# strings and the flows they enable are:
#
# "authorization_code" => Authorization Code Grant Flow
# "implicit" => Implicit Grant Flow
# "password" => Resource Owner Password Credentials Grant Flow
# "client_credentials" => Client Credentials Grant Flow
#
# If not specified, Doorkeeper enables authorization_code and
# client_credentials.
#
# implicit and password grant flows have risks that you should understand
# before enabling:
# http://tools.ietf.org/html/rfc6819#section-4.4.2
# http://tools.ietf.org/html/rfc6819#section-4.4.3
#
# grant_flows %w[authorization_code client_credentials]
# Hook into the strategies' request & response life-cycle in case your
# application needs advanced customization or logging:
#
# before_successful_strategy_response do |request|
# puts "BEFORE HOOK FIRED! #{request}"
# end
#
# after_successful_strategy_response do |request, response|
# puts "AFTER HOOK FIRED! #{request}, #{response}"
# end
# Hook into Authorization flow in order to implement Single Sign Out
# or add ny other functionality.
#
# before_successful_authorization do |controller|
# Rails.logger.info(params.inspect)
# end
#
# after_successful_authorization do |controller|
# controller.session[:logout_urls] <<
# Doorkeeper::Application
# .find_by(controller.request.params.slice(:redirect_uri))
# .logout_uri
# end
# Under some circumstances you might want to have applications auto-approved,
# so that the user skips the authorization step.
# For example if dealing with a trusted application.
#
# skip_authorization do |resource_owner, client|
# client.superapp? or resource_owner.admin?
# end
# WWW-Authenticate Realm (default "Doorkeeper").
#
# realm "Doorkeeper"
end
doorkeeper-5.0.2/lib/generators/doorkeeper/templates/README 0000644 0000041 0000041 00000000763 13410042500 023634 0 ustar www-data www-data ===============================================================================
There is a setup that you need to do before you can use doorkeeper.
Step 1.
Go to config/initializers/doorkeeper.rb and configure
resource_owner_authenticator block.
Step 2.
Choose the ORM:
If you want to use ActiveRecord run:
rails generate doorkeeper:migration
And run
rake db:migrate
Step 3.
That's it, that's all. Enjoy!
===============================================================================
././@LongLink 0000644 0000000 0000000 00000000150 00000000000 011577 L ustar root root doorkeeper-5.0.2/lib/generators/doorkeeper/templates/add_previous_refresh_token_to_access_tokens.rb.erb doorkeeper-5.0.2/lib/generators/doorkeeper/templates/add_previous_refresh_token_to_access_tokens.rb.0000644 0000041 0000041 00000000372 13410042500 034363 0 ustar www-data www-data class AddPreviousRefreshTokenToAccessTokens < ActiveRecord::Migration<%= migration_version %>
def change
add_column(
:oauth_access_tokens,
:previous_refresh_token,
:string,
default: "",
null: false
)
end
end
doorkeeper-5.0.2/lib/generators/doorkeeper/application_owner_generator.rb 0000644 0000041 0000041 00000001540 13410042500 027060 0 ustar www-data www-data # frozen_string_literal: true
require 'rails/generators'
require 'rails/generators/active_record'
module Doorkeeper
class ApplicationOwnerGenerator < ::Rails::Generators::Base
include ::Rails::Generators::Migration
source_root File.expand_path('templates', __dir__)
desc 'Provide support for client application ownership.'
def application_owner
migration_template(
'add_owner_to_application_migration.rb.erb',
'db/migrate/add_owner_to_application.rb',
migration_version: migration_version
)
end
def self.next_migration_number(dirname)
ActiveRecord::Generators::Base.next_migration_number(dirname)
end
private
def migration_version
if ActiveRecord::VERSION::MAJOR >= 5
"[#{ActiveRecord::VERSION::MAJOR}.#{ActiveRecord::VERSION::MINOR}]"
end
end
end
end
doorkeeper-5.0.2/lib/generators/doorkeeper/pkce_generator.rb 0000644 0000041 0000041 00000001425 13410042500 024267 0 ustar www-data www-data # frozen_string_literal: true
require 'rails/generators'
require 'rails/generators/active_record'
module Doorkeeper
class PkceGenerator < ::Rails::Generators::Base
include ::Rails::Generators::Migration
source_root File.expand_path('templates', __dir__)
desc 'Provide support for PKCE.'
def pkce
migration_template(
'enable_pkce_migration.rb.erb',
'db/migrate/enable_pkce.rb',
migration_version: migration_version
)
end
def self.next_migration_number(dirname)
ActiveRecord::Generators::Base.next_migration_number(dirname)
end
private
def migration_version
if ActiveRecord::VERSION::MAJOR >= 5
"[#{ActiveRecord::VERSION::MAJOR}.#{ActiveRecord::VERSION::MINOR}]"
end
end
end
end
doorkeeper-5.0.2/lib/generators/doorkeeper/migration_generator.rb 0000644 0000041 0000041 00000001450 13410042500 025334 0 ustar www-data www-data # frozen_string_literal: true
require 'rails/generators'
require 'rails/generators/active_record'
module Doorkeeper
class MigrationGenerator < ::Rails::Generators::Base
include ::Rails::Generators::Migration
source_root File.expand_path('templates', __dir__)
desc 'Installs Doorkeeper migration file.'
def install
migration_template(
'migration.rb.erb',
'db/migrate/create_doorkeeper_tables.rb',
migration_version: migration_version
)
end
def self.next_migration_number(dirname)
ActiveRecord::Generators::Base.next_migration_number(dirname)
end
private
def migration_version
if ActiveRecord::VERSION::MAJOR >= 5
"[#{ActiveRecord::VERSION::MAJOR}.#{ActiveRecord::VERSION::MINOR}]"
end
end
end
end
doorkeeper-5.0.2/lib/generators/doorkeeper/install_generator.rb 0000644 0000041 0000041 00000001110 13410042500 025002 0 ustar www-data www-data # frozen_string_literal: true
require 'rails/generators'
require 'rails/generators/active_record'
module Doorkeeper
class InstallGenerator < ::Rails::Generators::Base
include ::Rails::Generators::Migration
source_root File.expand_path('templates', __dir__)
desc 'Installs Doorkeeper.'
def install
template 'initializer.rb', 'config/initializers/doorkeeper.rb'
copy_file File.expand_path('../../../config/locales/en.yml', __dir__),
'config/locales/doorkeeper.en.yml'
route 'use_doorkeeper'
readme 'README'
end
end
end
doorkeeper-5.0.2/lib/generators/doorkeeper/previous_refresh_token_generator.rb 0000644 0000041 0000041 00000002073 13410042500 030137 0 ustar www-data www-data # frozen_string_literal: true
require 'rails/generators'
require 'rails/generators/active_record'
module Doorkeeper
class PreviousRefreshTokenGenerator < ::Rails::Generators::Base
include ::Rails::Generators::Migration
source_root File.expand_path('templates', __dir__)
desc 'Support revoke refresh token on access token use'
def self.next_migration_number(path)
ActiveRecord::Generators::Base.next_migration_number(path)
end
def previous_refresh_token
if no_previous_refresh_token_column?
migration_template(
'add_previous_refresh_token_to_access_tokens.rb.erb',
'db/migrate/add_previous_refresh_token_to_access_tokens.rb'
)
end
end
private
def migration_version
if ActiveRecord::VERSION::MAJOR >= 5
"[#{ActiveRecord::VERSION::MAJOR}.#{ActiveRecord::VERSION::MINOR}]"
end
end
def no_previous_refresh_token_column?
!ActiveRecord::Base.connection.column_exists?(
:oauth_access_tokens,
:previous_refresh_token
)
end
end
end
doorkeeper-5.0.2/CONTRIBUTING.md 0000644 0000041 0000041 00000002035 13410042500 016123 0 ustar www-data www-data # 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!
doorkeeper-5.0.2/vendor/ 0000755 0000041 0000041 00000000000 13410042500 015167 5 ustar www-data www-data doorkeeper-5.0.2/vendor/assets/ 0000755 0000041 0000041 00000000000 13410042500 016471 5 ustar www-data www-data doorkeeper-5.0.2/vendor/assets/stylesheets/ 0000755 0000041 0000041 00000000000 13410042500 021045 5 ustar www-data www-data doorkeeper-5.0.2/vendor/assets/stylesheets/doorkeeper/ 0000755 0000041 0000041 00000000000 13410042500 023204 5 ustar www-data www-data doorkeeper-5.0.2/vendor/assets/stylesheets/doorkeeper/bootstrap.min.css 0000644 0000041 0000041 00000432700 13410042500 026523 0 ustar www-data www-data /*!
* Bootstrap v4.0.0 (https://getbootstrap.com)
* Copyright 2011-2018 The Bootstrap Authors
* Copyright 2011-2018 Twitter, Inc.
* Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE)
*/:root{--blue:#007bff;--indigo:#6610f2;--purple:#6f42c1;--pink:#e83e8c;--red:#dc3545;--orange:#fd7e14;--yellow:#ffc107;--green:#28a745;--teal:#20c997;--cyan:#17a2b8;--white:#fff;--gray:#6c757d;--gray-dark:#343a40;--primary:#007bff;--secondary:#6c757d;--success:#28a745;--info:#17a2b8;--warning:#ffc107;--danger:#dc3545;--light:#f8f9fa;--dark:#343a40;--breakpoint-xs:0;--breakpoint-sm:576px;--breakpoint-md:768px;--breakpoint-lg:992px;--breakpoint-xl:1200px;--font-family-sans-serif:-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,"Helvetica Neue",Arial,sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol";--font-family-monospace:SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace}*,::after,::before{box-sizing:border-box}html{font-family:sans-serif;line-height:1.15;-webkit-text-size-adjust:100%;-ms-text-size-adjust:100%;-ms-overflow-style:scrollbar;-webkit-tap-highlight-color:transparent}@-ms-viewport{width:device-width}article,aside,dialog,figcaption,figure,footer,header,hgroup,main,nav,section{display:block}body{margin:0;font-family:-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,"Helvetica Neue",Arial,sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol";font-size:1rem;font-weight:400;line-height:1.5;color:#212529;text-align:left;background-color:#fff}[tabindex="-1"]:focus{outline:0!important}hr{box-sizing:content-box;height:0;overflow:visible}h1,h2,h3,h4,h5,h6{margin-top:0;margin-bottom:.5rem}p{margin-top:0;margin-bottom:1rem}abbr[data-original-title],abbr[title]{text-decoration:underline;-webkit-text-decoration:underline dotted;text-decoration:underline dotted;cursor:help;border-bottom:0}address{margin-bottom:1rem;font-style:normal;line-height:inherit}dl,ol,ul{margin-top:0;margin-bottom:1rem}ol ol,ol ul,ul ol,ul ul{margin-bottom:0}dt{font-weight:700}dd{margin-bottom:.5rem;margin-left:0}blockquote{margin:0 0 1rem}dfn{font-style:italic}b,strong{font-weight:bolder}small{font-size:80%}sub,sup{position:relative;font-size:75%;line-height:0;vertical-align:baseline}sub{bottom:-.25em}sup{top:-.5em}a{color:#007bff;text-decoration:none;background-color:transparent;-webkit-text-decoration-skip:objects}a:hover{color:#0056b3;text-decoration:underline}a:not([href]):not([tabindex]){color:inherit;text-decoration:none}a:not([href]):not([tabindex]):focus,a:not([href]):not([tabindex]):hover{color:inherit;text-decoration:none}a:not([href]):not([tabindex]):focus{outline:0}code,kbd,pre,samp{font-family:monospace,monospace;font-size:1em}pre{margin-top:0;margin-bottom:1rem;overflow:auto;-ms-overflow-style:scrollbar}figure{margin:0 0 1rem}img{vertical-align:middle;border-style:none}svg:not(:root){overflow:hidden}table{border-collapse:collapse}caption{padding-top:.75rem;padding-bottom:.75rem;color:#6c757d;text-align:left;caption-side:bottom}th{text-align:inherit}label{display:inline-block;margin-bottom:.5rem}button{border-radius:0}button:focus{outline:1px dotted;outline:5px auto -webkit-focus-ring-color}button,input,optgroup,select,textarea{margin:0;font-family:inherit;font-size:inherit;line-height:inherit}button,input{overflow:visible}button,select{text-transform:none}[type=reset],[type=submit],button,html [type=button]{-webkit-appearance:button}[type=button]::-moz-focus-inner,[type=reset]::-moz-focus-inner,[type=submit]::-moz-focus-inner,button::-moz-focus-inner{padding:0;border-style:none}input[type=checkbox],input[type=radio]{box-sizing:border-box;padding:0}input[type=date],input[type=datetime-local],input[type=month],input[type=time]{-webkit-appearance:listbox}textarea{overflow:auto;resize:vertical}fieldset{min-width:0;padding:0;margin:0;border:0}legend{display:block;width:100%;max-width:100%;padding:0;margin-bottom:.5rem;font-size:1.5rem;line-height:inherit;color:inherit;white-space:normal}progress{vertical-align:baseline}[type=number]::-webkit-inner-spin-button,[type=number]::-webkit-outer-spin-button{height:auto}[type=search]{outline-offset:-2px;-webkit-appearance:none}[type=search]::-webkit-search-cancel-button,[type=search]::-webkit-search-decoration{-webkit-appearance:none}::-webkit-file-upload-button{font:inherit;-webkit-appearance:button}output{display:inline-block}summary{display:list-item;cursor:pointer}template{display:none}[hidden]{display:none!important}.h1,.h2,.h3,.h4,.h5,.h6,h1,h2,h3,h4,h5,h6{margin-bottom:.5rem;font-family:inherit;font-weight:500;line-height:1.2;color:inherit}.h1,h1{font-size:2.5rem}.h2,h2{font-size:2rem}.h3,h3{font-size:1.75rem}.h4,h4{font-size:1.5rem}.h5,h5{font-size:1.25rem}.h6,h6{font-size:1rem}.lead{font-size:1.25rem;font-weight:300}.display-1{font-size:6rem;font-weight:300;line-height:1.2}.display-2{font-size:5.5rem;font-weight:300;line-height:1.2}.display-3{font-size:4.5rem;font-weight:300;line-height:1.2}.display-4{font-size:3.5rem;font-weight:300;line-height:1.2}hr{margin-top:1rem;margin-bottom:1rem;border:0;border-top:1px solid rgba(0,0,0,.1)}.small,small{font-size:80%;font-weight:400}.mark,mark{padding:.2em;background-color:#fcf8e3}.list-unstyled{padding-left:0;list-style:none}.list-inline{padding-left:0;list-style:none}.list-inline-item{display:inline-block}.list-inline-item:not(:last-child){margin-right:.5rem}.initialism{font-size:90%;text-transform:uppercase}.blockquote{margin-bottom:1rem;font-size:1.25rem}.blockquote-footer{display:block;font-size:80%;color:#6c757d}.blockquote-footer::before{content:"\2014 \00A0"}.img-fluid{max-width:100%;height:auto}.img-thumbnail{padding:.25rem;background-color:#fff;border:1px solid #dee2e6;border-radius:.25rem;max-width:100%;height:auto}.figure{display:inline-block}.figure-img{margin-bottom:.5rem;line-height:1}.figure-caption{font-size:90%;color:#6c757d}code,kbd,pre,samp{font-family:SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace}code{font-size:87.5%;color:#e83e8c;word-break:break-word}a>code{color:inherit}kbd{padding:.2rem .4rem;font-size:87.5%;color:#fff;background-color:#212529;border-radius:.2rem}kbd kbd{padding:0;font-size:100%;font-weight:700}pre{display:block;font-size:87.5%;color:#212529}pre code{font-size:inherit;color:inherit;word-break:normal}.pre-scrollable{max-height:340px;overflow-y:scroll}.container{width:100%;padding-right:15px;padding-left:15px;margin-right:auto;margin-left:auto}@media (min-width:576px){.container{max-width:540px}}@media (min-width:768px){.container{max-width:720px}}@media (min-width:992px){.container{max-width:960px}}@media (min-width:1200px){.container{max-width:1140px}}.container-fluid{width:100%;padding-right:15px;padding-left:15px;margin-right:auto;margin-left:auto}.row{display:-webkit-box;display:-ms-flexbox;display:flex;-ms-flex-wrap:wrap;flex-wrap:wrap;margin-right:-15px;margin-left:-15px}.no-gutters{margin-right:0;margin-left:0}.no-gutters>.col,.no-gutters>[class*=col-]{padding-right:0;padding-left:0}.col,.col-1,.col-10,.col-11,.col-12,.col-2,.col-3,.col-4,.col-5,.col-6,.col-7,.col-8,.col-9,.col-auto,.col-lg,.col-lg-1,.col-lg-10,.col-lg-11,.col-lg-12,.col-lg-2,.col-lg-3,.col-lg-4,.col-lg-5,.col-lg-6,.col-lg-7,.col-lg-8,.col-lg-9,.col-lg-auto,.col-md,.col-md-1,.col-md-10,.col-md-11,.col-md-12,.col-md-2,.col-md-3,.col-md-4,.col-md-5,.col-md-6,.col-md-7,.col-md-8,.col-md-9,.col-md-auto,.col-sm,.col-sm-1,.col-sm-10,.col-sm-11,.col-sm-12,.col-sm-2,.col-sm-3,.col-sm-4,.col-sm-5,.col-sm-6,.col-sm-7,.col-sm-8,.col-sm-9,.col-sm-auto,.col-xl,.col-xl-1,.col-xl-10,.col-xl-11,.col-xl-12,.col-xl-2,.col-xl-3,.col-xl-4,.col-xl-5,.col-xl-6,.col-xl-7,.col-xl-8,.col-xl-9,.col-xl-auto{position:relative;width:100%;min-height:1px;padding-right:15px;padding-left:15px}.col{-ms-flex-preferred-size:0;flex-basis:0;-webkit-box-flex:1;-ms-flex-positive:1;flex-grow:1;max-width:100%}.col-auto{-webkit-box-flex:0;-ms-flex:0 0 auto;flex:0 0 auto;width:auto;max-width:none}.col-1{-webkit-box-flex:0;-ms-flex:0 0 8.333333%;flex:0 0 8.333333%;max-width:8.333333%}.col-2{-webkit-box-flex:0;-ms-flex:0 0 16.666667%;flex:0 0 16.666667%;max-width:16.666667%}.col-3{-webkit-box-flex:0;-ms-flex:0 0 25%;flex:0 0 25%;max-width:25%}.col-4{-webkit-box-flex:0;-ms-flex:0 0 33.333333%;flex:0 0 33.333333%;max-width:33.333333%}.col-5{-webkit-box-flex:0;-ms-flex:0 0 41.666667%;flex:0 0 41.666667%;max-width:41.666667%}.col-6{-webkit-box-flex:0;-ms-flex:0 0 50%;flex:0 0 50%;max-width:50%}.col-7{-webkit-box-flex:0;-ms-flex:0 0 58.333333%;flex:0 0 58.333333%;max-width:58.333333%}.col-8{-webkit-box-flex:0;-ms-flex:0 0 66.666667%;flex:0 0 66.666667%;max-width:66.666667%}.col-9{-webkit-box-flex:0;-ms-flex:0 0 75%;flex:0 0 75%;max-width:75%}.col-10{-webkit-box-flex:0;-ms-flex:0 0 83.333333%;flex:0 0 83.333333%;max-width:83.333333%}.col-11{-webkit-box-flex:0;-ms-flex:0 0 91.666667%;flex:0 0 91.666667%;max-width:91.666667%}.col-12{-webkit-box-flex:0;-ms-flex:0 0 100%;flex:0 0 100%;max-width:100%}.order-first{-webkit-box-ordinal-group:0;-ms-flex-order:-1;order:-1}.order-last{-webkit-box-ordinal-group:14;-ms-flex-order:13;order:13}.order-0{-webkit-box-ordinal-group:1;-ms-flex-order:0;order:0}.order-1{-webkit-box-ordinal-group:2;-ms-flex-order:1;order:1}.order-2{-webkit-box-ordinal-group:3;-ms-flex-order:2;order:2}.order-3{-webkit-box-ordinal-group:4;-ms-flex-order:3;order:3}.order-4{-webkit-box-ordinal-group:5;-ms-flex-order:4;order:4}.order-5{-webkit-box-ordinal-group:6;-ms-flex-order:5;order:5}.order-6{-webkit-box-ordinal-group:7;-ms-flex-order:6;order:6}.order-7{-webkit-box-ordinal-group:8;-ms-flex-order:7;order:7}.order-8{-webkit-box-ordinal-group:9;-ms-flex-order:8;order:8}.order-9{-webkit-box-ordinal-group:10;-ms-flex-order:9;order:9}.order-10{-webkit-box-ordinal-group:11;-ms-flex-order:10;order:10}.order-11{-webkit-box-ordinal-group:12;-ms-flex-order:11;order:11}.order-12{-webkit-box-ordinal-group:13;-ms-flex-order:12;order:12}.offset-1{margin-left:8.333333%}.offset-2{margin-left:16.666667%}.offset-3{margin-left:25%}.offset-4{margin-left:33.333333%}.offset-5{margin-left:41.666667%}.offset-6{margin-left:50%}.offset-7{margin-left:58.333333%}.offset-8{margin-left:66.666667%}.offset-9{margin-left:75%}.offset-10{margin-left:83.333333%}.offset-11{margin-left:91.666667%}@media (min-width:576px){.col-sm{-ms-flex-preferred-size:0;flex-basis:0;-webkit-box-flex:1;-ms-flex-positive:1;flex-grow:1;max-width:100%}.col-sm-auto{-webkit-box-flex:0;-ms-flex:0 0 auto;flex:0 0 auto;width:auto;max-width:none}.col-sm-1{-webkit-box-flex:0;-ms-flex:0 0 8.333333%;flex:0 0 8.333333%;max-width:8.333333%}.col-sm-2{-webkit-box-flex:0;-ms-flex:0 0 16.666667%;flex:0 0 16.666667%;max-width:16.666667%}.col-sm-3{-webkit-box-flex:0;-ms-flex:0 0 25%;flex:0 0 25%;max-width:25%}.col-sm-4{-webkit-box-flex:0;-ms-flex:0 0 33.333333%;flex:0 0 33.333333%;max-width:33.333333%}.col-sm-5{-webkit-box-flex:0;-ms-flex:0 0 41.666667%;flex:0 0 41.666667%;max-width:41.666667%}.col-sm-6{-webkit-box-flex:0;-ms-flex:0 0 50%;flex:0 0 50%;max-width:50%}.col-sm-7{-webkit-box-flex:0;-ms-flex:0 0 58.333333%;flex:0 0 58.333333%;max-width:58.333333%}.col-sm-8{-webkit-box-flex:0;-ms-flex:0 0 66.666667%;flex:0 0 66.666667%;max-width:66.666667%}.col-sm-9{-webkit-box-flex:0;-ms-flex:0 0 75%;flex:0 0 75%;max-width:75%}.col-sm-10{-webkit-box-flex:0;-ms-flex:0 0 83.333333%;flex:0 0 83.333333%;max-width:83.333333%}.col-sm-11{-webkit-box-flex:0;-ms-flex:0 0 91.666667%;flex:0 0 91.666667%;max-width:91.666667%}.col-sm-12{-webkit-box-flex:0;-ms-flex:0 0 100%;flex:0 0 100%;max-width:100%}.order-sm-first{-webkit-box-ordinal-group:0;-ms-flex-order:-1;order:-1}.order-sm-last{-webkit-box-ordinal-group:14;-ms-flex-order:13;order:13}.order-sm-0{-webkit-box-ordinal-group:1;-ms-flex-order:0;order:0}.order-sm-1{-webkit-box-ordinal-group:2;-ms-flex-order:1;order:1}.order-sm-2{-webkit-box-ordinal-group:3;-ms-flex-order:2;order:2}.order-sm-3{-webkit-box-ordinal-group:4;-ms-flex-order:3;order:3}.order-sm-4{-webkit-box-ordinal-group:5;-ms-flex-order:4;order:4}.order-sm-5{-webkit-box-ordinal-group:6;-ms-flex-order:5;order:5}.order-sm-6{-webkit-box-ordinal-group:7;-ms-flex-order:6;order:6}.order-sm-7{-webkit-box-ordinal-group:8;-ms-flex-order:7;order:7}.order-sm-8{-webkit-box-ordinal-group:9;-ms-flex-order:8;order:8}.order-sm-9{-webkit-box-ordinal-group:10;-ms-flex-order:9;order:9}.order-sm-10{-webkit-box-ordinal-group:11;-ms-flex-order:10;order:10}.order-sm-11{-webkit-box-ordinal-group:12;-ms-flex-order:11;order:11}.order-sm-12{-webkit-box-ordinal-group:13;-ms-flex-order:12;order:12}.offset-sm-0{margin-left:0}.offset-sm-1{margin-left:8.333333%}.offset-sm-2{margin-left:16.666667%}.offset-sm-3{margin-left:25%}.offset-sm-4{margin-left:33.333333%}.offset-sm-5{margin-left:41.666667%}.offset-sm-6{margin-left:50%}.offset-sm-7{margin-left:58.333333%}.offset-sm-8{margin-left:66.666667%}.offset-sm-9{margin-left:75%}.offset-sm-10{margin-left:83.333333%}.offset-sm-11{margin-left:91.666667%}}@media (min-width:768px){.col-md{-ms-flex-preferred-size:0;flex-basis:0;-webkit-box-flex:1;-ms-flex-positive:1;flex-grow:1;max-width:100%}.col-md-auto{-webkit-box-flex:0;-ms-flex:0 0 auto;flex:0 0 auto;width:auto;max-width:none}.col-md-1{-webkit-box-flex:0;-ms-flex:0 0 8.333333%;flex:0 0 8.333333%;max-width:8.333333%}.col-md-2{-webkit-box-flex:0;-ms-flex:0 0 16.666667%;flex:0 0 16.666667%;max-width:16.666667%}.col-md-3{-webkit-box-flex:0;-ms-flex:0 0 25%;flex:0 0 25%;max-width:25%}.col-md-4{-webkit-box-flex:0;-ms-flex:0 0 33.333333%;flex:0 0 33.333333%;max-width:33.333333%}.col-md-5{-webkit-box-flex:0;-ms-flex:0 0 41.666667%;flex:0 0 41.666667%;max-width:41.666667%}.col-md-6{-webkit-box-flex:0;-ms-flex:0 0 50%;flex:0 0 50%;max-width:50%}.col-md-7{-webkit-box-flex:0;-ms-flex:0 0 58.333333%;flex:0 0 58.333333%;max-width:58.333333%}.col-md-8{-webkit-box-flex:0;-ms-flex:0 0 66.666667%;flex:0 0 66.666667%;max-width:66.666667%}.col-md-9{-webkit-box-flex:0;-ms-flex:0 0 75%;flex:0 0 75%;max-width:75%}.col-md-10{-webkit-box-flex:0;-ms-flex:0 0 83.333333%;flex:0 0 83.333333%;max-width:83.333333%}.col-md-11{-webkit-box-flex:0;-ms-flex:0 0 91.666667%;flex:0 0 91.666667%;max-width:91.666667%}.col-md-12{-webkit-box-flex:0;-ms-flex:0 0 100%;flex:0 0 100%;max-width:100%}.order-md-first{-webkit-box-ordinal-group:0;-ms-flex-order:-1;order:-1}.order-md-last{-webkit-box-ordinal-group:14;-ms-flex-order:13;order:13}.order-md-0{-webkit-box-ordinal-group:1;-ms-flex-order:0;order:0}.order-md-1{-webkit-box-ordinal-group:2;-ms-flex-order:1;order:1}.order-md-2{-webkit-box-ordinal-group:3;-ms-flex-order:2;order:2}.order-md-3{-webkit-box-ordinal-group:4;-ms-flex-order:3;order:3}.order-md-4{-webkit-box-ordinal-group:5;-ms-flex-order:4;order:4}.order-md-5{-webkit-box-ordinal-group:6;-ms-flex-order:5;order:5}.order-md-6{-webkit-box-ordinal-group:7;-ms-flex-order:6;order:6}.order-md-7{-webkit-box-ordinal-group:8;-ms-flex-order:7;order:7}.order-md-8{-webkit-box-ordinal-group:9;-ms-flex-order:8;order:8}.order-md-9{-webkit-box-ordinal-group:10;-ms-flex-order:9;order:9}.order-md-10{-webkit-box-ordinal-group:11;-ms-flex-order:10;order:10}.order-md-11{-webkit-box-ordinal-group:12;-ms-flex-order:11;order:11}.order-md-12{-webkit-box-ordinal-group:13;-ms-flex-order:12;order:12}.offset-md-0{margin-left:0}.offset-md-1{margin-left:8.333333%}.offset-md-2{margin-left:16.666667%}.offset-md-3{margin-left:25%}.offset-md-4{margin-left:33.333333%}.offset-md-5{margin-left:41.666667%}.offset-md-6{margin-left:50%}.offset-md-7{margin-left:58.333333%}.offset-md-8{margin-left:66.666667%}.offset-md-9{margin-left:75%}.offset-md-10{margin-left:83.333333%}.offset-md-11{margin-left:91.666667%}}@media (min-width:992px){.col-lg{-ms-flex-preferred-size:0;flex-basis:0;-webkit-box-flex:1;-ms-flex-positive:1;flex-grow:1;max-width:100%}.col-lg-auto{-webkit-box-flex:0;-ms-flex:0 0 auto;flex:0 0 auto;width:auto;max-width:none}.col-lg-1{-webkit-box-flex:0;-ms-flex:0 0 8.333333%;flex:0 0 8.333333%;max-width:8.333333%}.col-lg-2{-webkit-box-flex:0;-ms-flex:0 0 16.666667%;flex:0 0 16.666667%;max-width:16.666667%}.col-lg-3{-webkit-box-flex:0;-ms-flex:0 0 25%;flex:0 0 25%;max-width:25%}.col-lg-4{-webkit-box-flex:0;-ms-flex:0 0 33.333333%;flex:0 0 33.333333%;max-width:33.333333%}.col-lg-5{-webkit-box-flex:0;-ms-flex:0 0 41.666667%;flex:0 0 41.666667%;max-width:41.666667%}.col-lg-6{-webkit-box-flex:0;-ms-flex:0 0 50%;flex:0 0 50%;max-width:50%}.col-lg-7{-webkit-box-flex:0;-ms-flex:0 0 58.333333%;flex:0 0 58.333333%;max-width:58.333333%}.col-lg-8{-webkit-box-flex:0;-ms-flex:0 0 66.666667%;flex:0 0 66.666667%;max-width:66.666667%}.col-lg-9{-webkit-box-flex:0;-ms-flex:0 0 75%;flex:0 0 75%;max-width:75%}.col-lg-10{-webkit-box-flex:0;-ms-flex:0 0 83.333333%;flex:0 0 83.333333%;max-width:83.333333%}.col-lg-11{-webkit-box-flex:0;-ms-flex:0 0 91.666667%;flex:0 0 91.666667%;max-width:91.666667%}.col-lg-12{-webkit-box-flex:0;-ms-flex:0 0 100%;flex:0 0 100%;max-width:100%}.order-lg-first{-webkit-box-ordinal-group:0;-ms-flex-order:-1;order:-1}.order-lg-last{-webkit-box-ordinal-group:14;-ms-flex-order:13;order:13}.order-lg-0{-webkit-box-ordinal-group:1;-ms-flex-order:0;order:0}.order-lg-1{-webkit-box-ordinal-group:2;-ms-flex-order:1;order:1}.order-lg-2{-webkit-box-ordinal-group:3;-ms-flex-order:2;order:2}.order-lg-3{-webkit-box-ordinal-group:4;-ms-flex-order:3;order:3}.order-lg-4{-webkit-box-ordinal-group:5;-ms-flex-order:4;order:4}.order-lg-5{-webkit-box-ordinal-group:6;-ms-flex-order:5;order:5}.order-lg-6{-webkit-box-ordinal-group:7;-ms-flex-order:6;order:6}.order-lg-7{-webkit-box-ordinal-group:8;-ms-flex-order:7;order:7}.order-lg-8{-webkit-box-ordinal-group:9;-ms-flex-order:8;order:8}.order-lg-9{-webkit-box-ordinal-group:10;-ms-flex-order:9;order:9}.order-lg-10{-webkit-box-ordinal-group:11;-ms-flex-order:10;order:10}.order-lg-11{-webkit-box-ordinal-group:12;-ms-flex-order:11;order:11}.order-lg-12{-webkit-box-ordinal-group:13;-ms-flex-order:12;order:12}.offset-lg-0{margin-left:0}.offset-lg-1{margin-left:8.333333%}.offset-lg-2{margin-left:16.666667%}.offset-lg-3{margin-left:25%}.offset-lg-4{margin-left:33.333333%}.offset-lg-5{margin-left:41.666667%}.offset-lg-6{margin-left:50%}.offset-lg-7{margin-left:58.333333%}.offset-lg-8{margin-left:66.666667%}.offset-lg-9{margin-left:75%}.offset-lg-10{margin-left:83.333333%}.offset-lg-11{margin-left:91.666667%}}@media (min-width:1200px){.col-xl{-ms-flex-preferred-size:0;flex-basis:0;-webkit-box-flex:1;-ms-flex-positive:1;flex-grow:1;max-width:100%}.col-xl-auto{-webkit-box-flex:0;-ms-flex:0 0 auto;flex:0 0 auto;width:auto;max-width:none}.col-xl-1{-webkit-box-flex:0;-ms-flex:0 0 8.333333%;flex:0 0 8.333333%;max-width:8.333333%}.col-xl-2{-webkit-box-flex:0;-ms-flex:0 0 16.666667%;flex:0 0 16.666667%;max-width:16.666667%}.col-xl-3{-webkit-box-flex:0;-ms-flex:0 0 25%;flex:0 0 25%;max-width:25%}.col-xl-4{-webkit-box-flex:0;-ms-flex:0 0 33.333333%;flex:0 0 33.333333%;max-width:33.333333%}.col-xl-5{-webkit-box-flex:0;-ms-flex:0 0 41.666667%;flex:0 0 41.666667%;max-width:41.666667%}.col-xl-6{-webkit-box-flex:0;-ms-flex:0 0 50%;flex:0 0 50%;max-width:50%}.col-xl-7{-webkit-box-flex:0;-ms-flex:0 0 58.333333%;flex:0 0 58.333333%;max-width:58.333333%}.col-xl-8{-webkit-box-flex:0;-ms-flex:0 0 66.666667%;flex:0 0 66.666667%;max-width:66.666667%}.col-xl-9{-webkit-box-flex:0;-ms-flex:0 0 75%;flex:0 0 75%;max-width:75%}.col-xl-10{-webkit-box-flex:0;-ms-flex:0 0 83.333333%;flex:0 0 83.333333%;max-width:83.333333%}.col-xl-11{-webkit-box-flex:0;-ms-flex:0 0 91.666667%;flex:0 0 91.666667%;max-width:91.666667%}.col-xl-12{-webkit-box-flex:0;-ms-flex:0 0 100%;flex:0 0 100%;max-width:100%}.order-xl-first{-webkit-box-ordinal-group:0;-ms-flex-order:-1;order:-1}.order-xl-last{-webkit-box-ordinal-group:14;-ms-flex-order:13;order:13}.order-xl-0{-webkit-box-ordinal-group:1;-ms-flex-order:0;order:0}.order-xl-1{-webkit-box-ordinal-group:2;-ms-flex-order:1;order:1}.order-xl-2{-webkit-box-ordinal-group:3;-ms-flex-order:2;order:2}.order-xl-3{-webkit-box-ordinal-group:4;-ms-flex-order:3;order:3}.order-xl-4{-webkit-box-ordinal-group:5;-ms-flex-order:4;order:4}.order-xl-5{-webkit-box-ordinal-group:6;-ms-flex-order:5;order:5}.order-xl-6{-webkit-box-ordinal-group:7;-ms-flex-order:6;order:6}.order-xl-7{-webkit-box-ordinal-group:8;-ms-flex-order:7;order:7}.order-xl-8{-webkit-box-ordinal-group:9;-ms-flex-order:8;order:8}.order-xl-9{-webkit-box-ordinal-group:10;-ms-flex-order:9;order:9}.order-xl-10{-webkit-box-ordinal-group:11;-ms-flex-order:10;order:10}.order-xl-11{-webkit-box-ordinal-group:12;-ms-flex-order:11;order:11}.order-xl-12{-webkit-box-ordinal-group:13;-ms-flex-order:12;order:12}.offset-xl-0{margin-left:0}.offset-xl-1{margin-left:8.333333%}.offset-xl-2{margin-left:16.666667%}.offset-xl-3{margin-left:25%}.offset-xl-4{margin-left:33.333333%}.offset-xl-5{margin-left:41.666667%}.offset-xl-6{margin-left:50%}.offset-xl-7{margin-left:58.333333%}.offset-xl-8{margin-left:66.666667%}.offset-xl-9{margin-left:75%}.offset-xl-10{margin-left:83.333333%}.offset-xl-11{margin-left:91.666667%}}.table{width:100%;max-width:100%;margin-bottom:1rem;background-color:transparent}.table td,.table th{padding:.75rem;vertical-align:top;border-top:1px solid #dee2e6}.table thead th{vertical-align:bottom;border-bottom:2px solid #dee2e6}.table tbody+tbody{border-top:2px solid #dee2e6}.table .table{background-color:#fff}.table-sm td,.table-sm th{padding:.3rem}.table-bordered{border:1px solid #dee2e6}.table-bordered td,.table-bordered th{border:1px solid #dee2e6}.table-bordered thead td,.table-bordered thead th{border-bottom-width:2px}.table-striped tbody tr:nth-of-type(odd){background-color:rgba(0,0,0,.05)}.table-hover tbody tr:hover{background-color:rgba(0,0,0,.075)}.table-primary,.table-primary>td,.table-primary>th{background-color:#b8daff}.table-hover .table-primary:hover{background-color:#9fcdff}.table-hover .table-primary:hover>td,.table-hover .table-primary:hover>th{background-color:#9fcdff}.table-secondary,.table-secondary>td,.table-secondary>th{background-color:#d6d8db}.table-hover .table-secondary:hover{background-color:#c8cbcf}.table-hover .table-secondary:hover>td,.table-hover .table-secondary:hover>th{background-color:#c8cbcf}.table-success,.table-success>td,.table-success>th{background-color:#c3e6cb}.table-hover .table-success:hover{background-color:#b1dfbb}.table-hover .table-success:hover>td,.table-hover .table-success:hover>th{background-color:#b1dfbb}.table-info,.table-info>td,.table-info>th{background-color:#bee5eb}.table-hover .table-info:hover{background-color:#abdde5}.table-hover .table-info:hover>td,.table-hover .table-info:hover>th{background-color:#abdde5}.table-warning,.table-warning>td,.table-warning>th{background-color:#ffeeba}.table-hover .table-warning:hover{background-color:#ffe8a1}.table-hover .table-warning:hover>td,.table-hover .table-warning:hover>th{background-color:#ffe8a1}.table-danger,.table-danger>td,.table-danger>th{background-color:#f5c6cb}.table-hover .table-danger:hover{background-color:#f1b0b7}.table-hover .table-danger:hover>td,.table-hover .table-danger:hover>th{background-color:#f1b0b7}.table-light,.table-light>td,.table-light>th{background-color:#fdfdfe}.table-hover .table-light:hover{background-color:#ececf6}.table-hover .table-light:hover>td,.table-hover .table-light:hover>th{background-color:#ececf6}.table-dark,.table-dark>td,.table-dark>th{background-color:#c6c8ca}.table-hover .table-dark:hover{background-color:#b9bbbe}.table-hover .table-dark:hover>td,.table-hover .table-dark:hover>th{background-color:#b9bbbe}.table-active,.table-active>td,.table-active>th{background-color:rgba(0,0,0,.075)}.table-hover .table-active:hover{background-color:rgba(0,0,0,.075)}.table-hover .table-active:hover>td,.table-hover .table-active:hover>th{background-color:rgba(0,0,0,.075)}.table .thead-dark th{color:#fff;background-color:#212529;border-color:#32383e}.table .thead-light th{color:#495057;background-color:#e9ecef;border-color:#dee2e6}.table-dark{color:#fff;background-color:#212529}.table-dark td,.table-dark th,.table-dark thead th{border-color:#32383e}.table-dark.table-bordered{border:0}.table-dark.table-striped tbody tr:nth-of-type(odd){background-color:rgba(255,255,255,.05)}.table-dark.table-hover tbody tr:hover{background-color:rgba(255,255,255,.075)}@media (max-width:575.98px){.table-responsive-sm{display:block;width:100%;overflow-x:auto;-webkit-overflow-scrolling:touch;-ms-overflow-style:-ms-autohiding-scrollbar}.table-responsive-sm>.table-bordered{border:0}}@media (max-width:767.98px){.table-responsive-md{display:block;width:100%;overflow-x:auto;-webkit-overflow-scrolling:touch;-ms-overflow-style:-ms-autohiding-scrollbar}.table-responsive-md>.table-bordered{border:0}}@media (max-width:991.98px){.table-responsive-lg{display:block;width:100%;overflow-x:auto;-webkit-overflow-scrolling:touch;-ms-overflow-style:-ms-autohiding-scrollbar}.table-responsive-lg>.table-bordered{border:0}}@media (max-width:1199.98px){.table-responsive-xl{display:block;width:100%;overflow-x:auto;-webkit-overflow-scrolling:touch;-ms-overflow-style:-ms-autohiding-scrollbar}.table-responsive-xl>.table-bordered{border:0}}.table-responsive{display:block;width:100%;overflow-x:auto;-webkit-overflow-scrolling:touch;-ms-overflow-style:-ms-autohiding-scrollbar}.table-responsive>.table-bordered{border:0}.form-control{display:block;width:100%;padding:.375rem .75rem;font-size:1rem;line-height:1.5;color:#495057;background-color:#fff;background-clip:padding-box;border:1px solid #ced4da;border-radius:.25rem;transition:border-color .15s ease-in-out,box-shadow .15s ease-in-out}.form-control::-ms-expand{background-color:transparent;border:0}.form-control:focus{color:#495057;background-color:#fff;border-color:#80bdff;outline:0;box-shadow:0 0 0 .2rem rgba(0,123,255,.25)}.form-control::-webkit-input-placeholder{color:#6c757d;opacity:1}.form-control::-moz-placeholder{color:#6c757d;opacity:1}.form-control:-ms-input-placeholder{color:#6c757d;opacity:1}.form-control::-ms-input-placeholder{color:#6c757d;opacity:1}.form-control::placeholder{color:#6c757d;opacity:1}.form-control:disabled,.form-control[readonly]{background-color:#e9ecef;opacity:1}select.form-control:not([size]):not([multiple]){height:calc(2.25rem + 2px)}select.form-control:focus::-ms-value{color:#495057;background-color:#fff}.form-control-file,.form-control-range{display:block;width:100%}.col-form-label{padding-top:calc(.375rem + 1px);padding-bottom:calc(.375rem + 1px);margin-bottom:0;font-size:inherit;line-height:1.5}.col-form-label-lg{padding-top:calc(.5rem + 1px);padding-bottom:calc(.5rem + 1px);font-size:1.25rem;line-height:1.5}.col-form-label-sm{padding-top:calc(.25rem + 1px);padding-bottom:calc(.25rem + 1px);font-size:.875rem;line-height:1.5}.form-control-plaintext{display:block;width:100%;padding-top:.375rem;padding-bottom:.375rem;margin-bottom:0;line-height:1.5;background-color:transparent;border:solid transparent;border-width:1px 0}.form-control-plaintext.form-control-lg,.form-control-plaintext.form-control-sm,.input-group-lg>.form-control-plaintext.form-control,.input-group-lg>.input-group-append>.form-control-plaintext.btn,.input-group-lg>.input-group-append>.form-control-plaintext.input-group-text,.input-group-lg>.input-group-prepend>.form-control-plaintext.btn,.input-group-lg>.input-group-prepend>.form-control-plaintext.input-group-text,.input-group-sm>.form-control-plaintext.form-control,.input-group-sm>.input-group-append>.form-control-plaintext.btn,.input-group-sm>.input-group-append>.form-control-plaintext.input-group-text,.input-group-sm>.input-group-prepend>.form-control-plaintext.btn,.input-group-sm>.input-group-prepend>.form-control-plaintext.input-group-text{padding-right:0;padding-left:0}.form-control-sm,.input-group-sm>.form-control,.input-group-sm>.input-group-append>.btn,.input-group-sm>.input-group-append>.input-group-text,.input-group-sm>.input-group-prepend>.btn,.input-group-sm>.input-group-prepend>.input-group-text{padding:.25rem .5rem;font-size:.875rem;line-height:1.5;border-radius:.2rem}.input-group-sm>.input-group-append>select.btn:not([size]):not([multiple]),.input-group-sm>.input-group-append>select.input-group-text:not([size]):not([multiple]),.input-group-sm>.input-group-prepend>select.btn:not([size]):not([multiple]),.input-group-sm>.input-group-prepend>select.input-group-text:not([size]):not([multiple]),.input-group-sm>select.form-control:not([size]):not([multiple]),select.form-control-sm:not([size]):not([multiple]){height:calc(1.8125rem + 2px)}.form-control-lg,.input-group-lg>.form-control,.input-group-lg>.input-group-append>.btn,.input-group-lg>.input-group-append>.input-group-text,.input-group-lg>.input-group-prepend>.btn,.input-group-lg>.input-group-prepend>.input-group-text{padding:.5rem 1rem;font-size:1.25rem;line-height:1.5;border-radius:.3rem}.input-group-lg>.input-group-append>select.btn:not([size]):not([multiple]),.input-group-lg>.input-group-append>select.input-group-text:not([size]):not([multiple]),.input-group-lg>.input-group-prepend>select.btn:not([size]):not([multiple]),.input-group-lg>.input-group-prepend>select.input-group-text:not([size]):not([multiple]),.input-group-lg>select.form-control:not([size]):not([multiple]),select.form-control-lg:not([size]):not([multiple]){height:calc(2.875rem + 2px)}.form-group{margin-bottom:1rem}.form-text{display:block;margin-top:.25rem}.form-row{display:-webkit-box;display:-ms-flexbox;display:flex;-ms-flex-wrap:wrap;flex-wrap:wrap;margin-right:-5px;margin-left:-5px}.form-row>.col,.form-row>[class*=col-]{padding-right:5px;padding-left:5px}.form-check{position:relative;display:block;padding-left:1.25rem}.form-check-input{position:absolute;margin-top:.3rem;margin-left:-1.25rem}.form-check-input:disabled~.form-check-label{color:#6c757d}.form-check-label{margin-bottom:0}.form-check-inline{display:-webkit-inline-box;display:-ms-inline-flexbox;display:inline-flex;-webkit-box-align:center;-ms-flex-align:center;align-items:center;padding-left:0;margin-right:.75rem}.form-check-inline .form-check-input{position:static;margin-top:0;margin-right:.3125rem;margin-left:0}.valid-feedback{display:none;width:100%;margin-top:.25rem;font-size:80%;color:#28a745}.valid-tooltip{position:absolute;top:100%;z-index:5;display:none;max-width:100%;padding:.5rem;margin-top:.1rem;font-size:.875rem;line-height:1;color:#fff;background-color:rgba(40,167,69,.8);border-radius:.2rem}.custom-select.is-valid,.form-control.is-valid,.was-validated .custom-select:valid,.was-validated .form-control:valid{border-color:#28a745}.custom-select.is-valid:focus,.form-control.is-valid:focus,.was-validated .custom-select:valid:focus,.was-validated .form-control:valid:focus{border-color:#28a745;box-shadow:0 0 0 .2rem rgba(40,167,69,.25)}.custom-select.is-valid~.valid-feedback,.custom-select.is-valid~.valid-tooltip,.form-control.is-valid~.valid-feedback,.form-control.is-valid~.valid-tooltip,.was-validated .custom-select:valid~.valid-feedback,.was-validated .custom-select:valid~.valid-tooltip,.was-validated .form-control:valid~.valid-feedback,.was-validated .form-control:valid~.valid-tooltip{display:block}.form-check-input.is-valid~.form-check-label,.was-validated .form-check-input:valid~.form-check-label{color:#28a745}.form-check-input.is-valid~.valid-feedback,.form-check-input.is-valid~.valid-tooltip,.was-validated .form-check-input:valid~.valid-feedback,.was-validated .form-check-input:valid~.valid-tooltip{display:block}.custom-control-input.is-valid~.custom-control-label,.was-validated .custom-control-input:valid~.custom-control-label{color:#28a745}.custom-control-input.is-valid~.custom-control-label::before,.was-validated .custom-control-input:valid~.custom-control-label::before{background-color:#71dd8a}.custom-control-input.is-valid~.valid-feedback,.custom-control-input.is-valid~.valid-tooltip,.was-validated .custom-control-input:valid~.valid-feedback,.was-validated .custom-control-input:valid~.valid-tooltip{display:block}.custom-control-input.is-valid:checked~.custom-control-label::before,.was-validated .custom-control-input:valid:checked~.custom-control-label::before{background-color:#34ce57}.custom-control-input.is-valid:focus~.custom-control-label::before,.was-validated .custom-control-input:valid:focus~.custom-control-label::before{box-shadow:0 0 0 1px #fff,0 0 0 .2rem rgba(40,167,69,.25)}.custom-file-input.is-valid~.custom-file-label,.was-validated .custom-file-input:valid~.custom-file-label{border-color:#28a745}.custom-file-input.is-valid~.custom-file-label::before,.was-validated .custom-file-input:valid~.custom-file-label::before{border-color:inherit}.custom-file-input.is-valid~.valid-feedback,.custom-file-input.is-valid~.valid-tooltip,.was-validated .custom-file-input:valid~.valid-feedback,.was-validated .custom-file-input:valid~.valid-tooltip{display:block}.custom-file-input.is-valid:focus~.custom-file-label,.was-validated .custom-file-input:valid:focus~.custom-file-label{box-shadow:0 0 0 .2rem rgba(40,167,69,.25)}.invalid-feedback{display:none;width:100%;margin-top:.25rem;font-size:80%;color:#dc3545}.invalid-tooltip{position:absolute;top:100%;z-index:5;display:none;max-width:100%;padding:.5rem;margin-top:.1rem;font-size:.875rem;line-height:1;color:#fff;background-color:rgba(220,53,69,.8);border-radius:.2rem}.custom-select.is-invalid,.form-control.is-invalid,.was-validated .custom-select:invalid,.was-validated .form-control:invalid{border-color:#dc3545}.custom-select.is-invalid:focus,.form-control.is-invalid:focus,.was-validated .custom-select:invalid:focus,.was-validated .form-control:invalid:focus{border-color:#dc3545;box-shadow:0 0 0 .2rem rgba(220,53,69,.25)}.custom-select.is-invalid~.invalid-feedback,.custom-select.is-invalid~.invalid-tooltip,.form-control.is-invalid~.invalid-feedback,.form-control.is-invalid~.invalid-tooltip,.was-validated .custom-select:invalid~.invalid-feedback,.was-validated .custom-select:invalid~.invalid-tooltip,.was-validated .form-control:invalid~.invalid-feedback,.was-validated .form-control:invalid~.invalid-tooltip{display:block}.form-check-input.is-invalid~.form-check-label,.was-validated .form-check-input:invalid~.form-check-label{color:#dc3545}.form-check-input.is-invalid~.invalid-feedback,.form-check-input.is-invalid~.invalid-tooltip,.was-validated .form-check-input:invalid~.invalid-feedback,.was-validated .form-check-input:invalid~.invalid-tooltip{display:block}.custom-control-input.is-invalid~.custom-control-label,.was-validated .custom-control-input:invalid~.custom-control-label{color:#dc3545}.custom-control-input.is-invalid~.custom-control-label::before,.was-validated .custom-control-input:invalid~.custom-control-label::before{background-color:#efa2a9}.custom-control-input.is-invalid~.invalid-feedback,.custom-control-input.is-invalid~.invalid-tooltip,.was-validated .custom-control-input:invalid~.invalid-feedback,.was-validated .custom-control-input:invalid~.invalid-tooltip{display:block}.custom-control-input.is-invalid:checked~.custom-control-label::before,.was-validated .custom-control-input:invalid:checked~.custom-control-label::before{background-color:#e4606d}.custom-control-input.is-invalid:focus~.custom-control-label::before,.was-validated .custom-control-input:invalid:focus~.custom-control-label::before{box-shadow:0 0 0 1px #fff,0 0 0 .2rem rgba(220,53,69,.25)}.custom-file-input.is-invalid~.custom-file-label,.was-validated .custom-file-input:invalid~.custom-file-label{border-color:#dc3545}.custom-file-input.is-invalid~.custom-file-label::before,.was-validated .custom-file-input:invalid~.custom-file-label::before{border-color:inherit}.custom-file-input.is-invalid~.invalid-feedback,.custom-file-input.is-invalid~.invalid-tooltip,.was-validated .custom-file-input:invalid~.invalid-feedback,.was-validated .custom-file-input:invalid~.invalid-tooltip{display:block}.custom-file-input.is-invalid:focus~.custom-file-label,.was-validated .custom-file-input:invalid:focus~.custom-file-label{box-shadow:0 0 0 .2rem rgba(220,53,69,.25)}.form-inline{display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-orient:horizontal;-webkit-box-direction:normal;-ms-flex-flow:row wrap;flex-flow:row wrap;-webkit-box-align:center;-ms-flex-align:center;align-items:center}.form-inline .form-check{width:100%}@media (min-width:576px){.form-inline label{display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-align:center;-ms-flex-align:center;align-items:center;-webkit-box-pack:center;-ms-flex-pack:center;justify-content:center;margin-bottom:0}.form-inline .form-group{display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-flex:0;-ms-flex:0 0 auto;flex:0 0 auto;-webkit-box-orient:horizontal;-webkit-box-direction:normal;-ms-flex-flow:row wrap;flex-flow:row wrap;-webkit-box-align:center;-ms-flex-align:center;align-items:center;margin-bottom:0}.form-inline .form-control{display:inline-block;width:auto;vertical-align:middle}.form-inline .form-control-plaintext{display:inline-block}.form-inline .input-group{width:auto}.form-inline .form-check{display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-align:center;-ms-flex-align:center;align-items:center;-webkit-box-pack:center;-ms-flex-pack:center;justify-content:center;width:auto;padding-left:0}.form-inline .form-check-input{position:relative;margin-top:0;margin-right:.25rem;margin-left:0}.form-inline .custom-control{-webkit-box-align:center;-ms-flex-align:center;align-items:center;-webkit-box-pack:center;-ms-flex-pack:center;justify-content:center}.form-inline .custom-control-label{margin-bottom:0}}.btn{display:inline-block;font-weight:400;text-align:center;white-space:nowrap;vertical-align:middle;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;border:1px solid transparent;padding:.375rem .75rem;font-size:1rem;line-height:1.5;border-radius:.25rem;transition:color .15s ease-in-out,background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out}.btn:focus,.btn:hover{text-decoration:none}.btn.focus,.btn:focus{outline:0;box-shadow:0 0 0 .2rem rgba(0,123,255,.25)}.btn.disabled,.btn:disabled{opacity:.65}.btn:not(:disabled):not(.disabled){cursor:pointer}.btn:not(:disabled):not(.disabled).active,.btn:not(:disabled):not(.disabled):active{background-image:none}a.btn.disabled,fieldset:disabled a.btn{pointer-events:none}.btn-primary{color:#fff;background-color:#007bff;border-color:#007bff}.btn-primary:hover{color:#fff;background-color:#0069d9;border-color:#0062cc}.btn-primary.focus,.btn-primary:focus{box-shadow:0 0 0 .2rem rgba(0,123,255,.5)}.btn-primary.disabled,.btn-primary:disabled{color:#fff;background-color:#007bff;border-color:#007bff}.btn-primary:not(:disabled):not(.disabled).active,.btn-primary:not(:disabled):not(.disabled):active,.show>.btn-primary.dropdown-toggle{color:#fff;background-color:#0062cc;border-color:#005cbf}.btn-primary:not(:disabled):not(.disabled).active:focus,.btn-primary:not(:disabled):not(.disabled):active:focus,.show>.btn-primary.dropdown-toggle:focus{box-shadow:0 0 0 .2rem rgba(0,123,255,.5)}.btn-secondary{color:#fff;background-color:#6c757d;border-color:#6c757d}.btn-secondary:hover{color:#fff;background-color:#5a6268;border-color:#545b62}.btn-secondary.focus,.btn-secondary:focus{box-shadow:0 0 0 .2rem rgba(108,117,125,.5)}.btn-secondary.disabled,.btn-secondary:disabled{color:#fff;background-color:#6c757d;border-color:#6c757d}.btn-secondary:not(:disabled):not(.disabled).active,.btn-secondary:not(:disabled):not(.disabled):active,.show>.btn-secondary.dropdown-toggle{color:#fff;background-color:#545b62;border-color:#4e555b}.btn-secondary:not(:disabled):not(.disabled).active:focus,.btn-secondary:not(:disabled):not(.disabled):active:focus,.show>.btn-secondary.dropdown-toggle:focus{box-shadow:0 0 0 .2rem rgba(108,117,125,.5)}.btn-success{color:#fff;background-color:#28a745;border-color:#28a745}.btn-success:hover{color:#fff;background-color:#218838;border-color:#1e7e34}.btn-success.focus,.btn-success:focus{box-shadow:0 0 0 .2rem rgba(40,167,69,.5)}.btn-success.disabled,.btn-success:disabled{color:#fff;background-color:#28a745;border-color:#28a745}.btn-success:not(:disabled):not(.disabled).active,.btn-success:not(:disabled):not(.disabled):active,.show>.btn-success.dropdown-toggle{color:#fff;background-color:#1e7e34;border-color:#1c7430}.btn-success:not(:disabled):not(.disabled).active:focus,.btn-success:not(:disabled):not(.disabled):active:focus,.show>.btn-success.dropdown-toggle:focus{box-shadow:0 0 0 .2rem rgba(40,167,69,.5)}.btn-info{color:#fff;background-color:#17a2b8;border-color:#17a2b8}.btn-info:hover{color:#fff;background-color:#138496;border-color:#117a8b}.btn-info.focus,.btn-info:focus{box-shadow:0 0 0 .2rem rgba(23,162,184,.5)}.btn-info.disabled,.btn-info:disabled{color:#fff;background-color:#17a2b8;border-color:#17a2b8}.btn-info:not(:disabled):not(.disabled).active,.btn-info:not(:disabled):not(.disabled):active,.show>.btn-info.dropdown-toggle{color:#fff;background-color:#117a8b;border-color:#10707f}.btn-info:not(:disabled):not(.disabled).active:focus,.btn-info:not(:disabled):not(.disabled):active:focus,.show>.btn-info.dropdown-toggle:focus{box-shadow:0 0 0 .2rem rgba(23,162,184,.5)}.btn-warning{color:#212529;background-color:#ffc107;border-color:#ffc107}.btn-warning:hover{color:#212529;background-color:#e0a800;border-color:#d39e00}.btn-warning.focus,.btn-warning:focus{box-shadow:0 0 0 .2rem rgba(255,193,7,.5)}.btn-warning.disabled,.btn-warning:disabled{color:#212529;background-color:#ffc107;border-color:#ffc107}.btn-warning:not(:disabled):not(.disabled).active,.btn-warning:not(:disabled):not(.disabled):active,.show>.btn-warning.dropdown-toggle{color:#212529;background-color:#d39e00;border-color:#c69500}.btn-warning:not(:disabled):not(.disabled).active:focus,.btn-warning:not(:disabled):not(.disabled):active:focus,.show>.btn-warning.dropdown-toggle:focus{box-shadow:0 0 0 .2rem rgba(255,193,7,.5)}.btn-danger{color:#fff;background-color:#dc3545;border-color:#dc3545}.btn-danger:hover{color:#fff;background-color:#c82333;border-color:#bd2130}.btn-danger.focus,.btn-danger:focus{box-shadow:0 0 0 .2rem rgba(220,53,69,.5)}.btn-danger.disabled,.btn-danger:disabled{color:#fff;background-color:#dc3545;border-color:#dc3545}.btn-danger:not(:disabled):not(.disabled).active,.btn-danger:not(:disabled):not(.disabled):active,.show>.btn-danger.dropdown-toggle{color:#fff;background-color:#bd2130;border-color:#b21f2d}.btn-danger:not(:disabled):not(.disabled).active:focus,.btn-danger:not(:disabled):not(.disabled):active:focus,.show>.btn-danger.dropdown-toggle:focus{box-shadow:0 0 0 .2rem rgba(220,53,69,.5)}.btn-light{color:#212529;background-color:#f8f9fa;border-color:#f8f9fa}.btn-light:hover{color:#212529;background-color:#e2e6ea;border-color:#dae0e5}.btn-light.focus,.btn-light:focus{box-shadow:0 0 0 .2rem rgba(248,249,250,.5)}.btn-light.disabled,.btn-light:disabled{color:#212529;background-color:#f8f9fa;border-color:#f8f9fa}.btn-light:not(:disabled):not(.disabled).active,.btn-light:not(:disabled):not(.disabled):active,.show>.btn-light.dropdown-toggle{color:#212529;background-color:#dae0e5;border-color:#d3d9df}.btn-light:not(:disabled):not(.disabled).active:focus,.btn-light:not(:disabled):not(.disabled):active:focus,.show>.btn-light.dropdown-toggle:focus{box-shadow:0 0 0 .2rem rgba(248,249,250,.5)}.btn-dark{color:#fff;background-color:#343a40;border-color:#343a40}.btn-dark:hover{color:#fff;background-color:#23272b;border-color:#1d2124}.btn-dark.focus,.btn-dark:focus{box-shadow:0 0 0 .2rem rgba(52,58,64,.5)}.btn-dark.disabled,.btn-dark:disabled{color:#fff;background-color:#343a40;border-color:#343a40}.btn-dark:not(:disabled):not(.disabled).active,.btn-dark:not(:disabled):not(.disabled):active,.show>.btn-dark.dropdown-toggle{color:#fff;background-color:#1d2124;border-color:#171a1d}.btn-dark:not(:disabled):not(.disabled).active:focus,.btn-dark:not(:disabled):not(.disabled):active:focus,.show>.btn-dark.dropdown-toggle:focus{box-shadow:0 0 0 .2rem rgba(52,58,64,.5)}.btn-outline-primary{color:#007bff;background-color:transparent;background-image:none;border-color:#007bff}.btn-outline-primary:hover{color:#fff;background-color:#007bff;border-color:#007bff}.btn-outline-primary.focus,.btn-outline-primary:focus{box-shadow:0 0 0 .2rem rgba(0,123,255,.5)}.btn-outline-primary.disabled,.btn-outline-primary:disabled{color:#007bff;background-color:transparent}.btn-outline-primary:not(:disabled):not(.disabled).active,.btn-outline-primary:not(:disabled):not(.disabled):active,.show>.btn-outline-primary.dropdown-toggle{color:#fff;background-color:#007bff;border-color:#007bff}.btn-outline-primary:not(:disabled):not(.disabled).active:focus,.btn-outline-primary:not(:disabled):not(.disabled):active:focus,.show>.btn-outline-primary.dropdown-toggle:focus{box-shadow:0 0 0 .2rem rgba(0,123,255,.5)}.btn-outline-secondary{color:#6c757d;background-color:transparent;background-image:none;border-color:#6c757d}.btn-outline-secondary:hover{color:#fff;background-color:#6c757d;border-color:#6c757d}.btn-outline-secondary.focus,.btn-outline-secondary:focus{box-shadow:0 0 0 .2rem rgba(108,117,125,.5)}.btn-outline-secondary.disabled,.btn-outline-secondary:disabled{color:#6c757d;background-color:transparent}.btn-outline-secondary:not(:disabled):not(.disabled).active,.btn-outline-secondary:not(:disabled):not(.disabled):active,.show>.btn-outline-secondary.dropdown-toggle{color:#fff;background-color:#6c757d;border-color:#6c757d}.btn-outline-secondary:not(:disabled):not(.disabled).active:focus,.btn-outline-secondary:not(:disabled):not(.disabled):active:focus,.show>.btn-outline-secondary.dropdown-toggle:focus{box-shadow:0 0 0 .2rem rgba(108,117,125,.5)}.btn-outline-success{color:#28a745;background-color:transparent;background-image:none;border-color:#28a745}.btn-outline-success:hover{color:#fff;background-color:#28a745;border-color:#28a745}.btn-outline-success.focus,.btn-outline-success:focus{box-shadow:0 0 0 .2rem rgba(40,167,69,.5)}.btn-outline-success.disabled,.btn-outline-success:disabled{color:#28a745;background-color:transparent}.btn-outline-success:not(:disabled):not(.disabled).active,.btn-outline-success:not(:disabled):not(.disabled):active,.show>.btn-outline-success.dropdown-toggle{color:#fff;background-color:#28a745;border-color:#28a745}.btn-outline-success:not(:disabled):not(.disabled).active:focus,.btn-outline-success:not(:disabled):not(.disabled):active:focus,.show>.btn-outline-success.dropdown-toggle:focus{box-shadow:0 0 0 .2rem rgba(40,167,69,.5)}.btn-outline-info{color:#17a2b8;background-color:transparent;background-image:none;border-color:#17a2b8}.btn-outline-info:hover{color:#fff;background-color:#17a2b8;border-color:#17a2b8}.btn-outline-info.focus,.btn-outline-info:focus{box-shadow:0 0 0 .2rem rgba(23,162,184,.5)}.btn-outline-info.disabled,.btn-outline-info:disabled{color:#17a2b8;background-color:transparent}.btn-outline-info:not(:disabled):not(.disabled).active,.btn-outline-info:not(:disabled):not(.disabled):active,.show>.btn-outline-info.dropdown-toggle{color:#fff;background-color:#17a2b8;border-color:#17a2b8}.btn-outline-info:not(:disabled):not(.disabled).active:focus,.btn-outline-info:not(:disabled):not(.disabled):active:focus,.show>.btn-outline-info.dropdown-toggle:focus{box-shadow:0 0 0 .2rem rgba(23,162,184,.5)}.btn-outline-warning{color:#ffc107;background-color:transparent;background-image:none;border-color:#ffc107}.btn-outline-warning:hover{color:#212529;background-color:#ffc107;border-color:#ffc107}.btn-outline-warning.focus,.btn-outline-warning:focus{box-shadow:0 0 0 .2rem rgba(255,193,7,.5)}.btn-outline-warning.disabled,.btn-outline-warning:disabled{color:#ffc107;background-color:transparent}.btn-outline-warning:not(:disabled):not(.disabled).active,.btn-outline-warning:not(:disabled):not(.disabled):active,.show>.btn-outline-warning.dropdown-toggle{color:#212529;background-color:#ffc107;border-color:#ffc107}.btn-outline-warning:not(:disabled):not(.disabled).active:focus,.btn-outline-warning:not(:disabled):not(.disabled):active:focus,.show>.btn-outline-warning.dropdown-toggle:focus{box-shadow:0 0 0 .2rem rgba(255,193,7,.5)}.btn-outline-danger{color:#dc3545;background-color:transparent;background-image:none;border-color:#dc3545}.btn-outline-danger:hover{color:#fff;background-color:#dc3545;border-color:#dc3545}.btn-outline-danger.focus,.btn-outline-danger:focus{box-shadow:0 0 0 .2rem rgba(220,53,69,.5)}.btn-outline-danger.disabled,.btn-outline-danger:disabled{color:#dc3545;background-color:transparent}.btn-outline-danger:not(:disabled):not(.disabled).active,.btn-outline-danger:not(:disabled):not(.disabled):active,.show>.btn-outline-danger.dropdown-toggle{color:#fff;background-color:#dc3545;border-color:#dc3545}.btn-outline-danger:not(:disabled):not(.disabled).active:focus,.btn-outline-danger:not(:disabled):not(.disabled):active:focus,.show>.btn-outline-danger.dropdown-toggle:focus{box-shadow:0 0 0 .2rem rgba(220,53,69,.5)}.btn-outline-light{color:#f8f9fa;background-color:transparent;background-image:none;border-color:#f8f9fa}.btn-outline-light:hover{color:#212529;background-color:#f8f9fa;border-color:#f8f9fa}.btn-outline-light.focus,.btn-outline-light:focus{box-shadow:0 0 0 .2rem rgba(248,249,250,.5)}.btn-outline-light.disabled,.btn-outline-light:disabled{color:#f8f9fa;background-color:transparent}.btn-outline-light:not(:disabled):not(.disabled).active,.btn-outline-light:not(:disabled):not(.disabled):active,.show>.btn-outline-light.dropdown-toggle{color:#212529;background-color:#f8f9fa;border-color:#f8f9fa}.btn-outline-light:not(:disabled):not(.disabled).active:focus,.btn-outline-light:not(:disabled):not(.disabled):active:focus,.show>.btn-outline-light.dropdown-toggle:focus{box-shadow:0 0 0 .2rem rgba(248,249,250,.5)}.btn-outline-dark{color:#343a40;background-color:transparent;background-image:none;border-color:#343a40}.btn-outline-dark:hover{color:#fff;background-color:#343a40;border-color:#343a40}.btn-outline-dark.focus,.btn-outline-dark:focus{box-shadow:0 0 0 .2rem rgba(52,58,64,.5)}.btn-outline-dark.disabled,.btn-outline-dark:disabled{color:#343a40;background-color:transparent}.btn-outline-dark:not(:disabled):not(.disabled).active,.btn-outline-dark:not(:disabled):not(.disabled):active,.show>.btn-outline-dark.dropdown-toggle{color:#fff;background-color:#343a40;border-color:#343a40}.btn-outline-dark:not(:disabled):not(.disabled).active:focus,.btn-outline-dark:not(:disabled):not(.disabled):active:focus,.show>.btn-outline-dark.dropdown-toggle:focus{box-shadow:0 0 0 .2rem rgba(52,58,64,.5)}.btn-link{font-weight:400;color:#007bff;background-color:transparent}.btn-link:hover{color:#0056b3;text-decoration:underline;background-color:transparent;border-color:transparent}.btn-link.focus,.btn-link:focus{text-decoration:underline;border-color:transparent;box-shadow:none}.btn-link.disabled,.btn-link:disabled{color:#6c757d}.btn-group-lg>.btn,.btn-lg{padding:.5rem 1rem;font-size:1.25rem;line-height:1.5;border-radius:.3rem}.btn-group-sm>.btn,.btn-sm{padding:.25rem .5rem;font-size:.875rem;line-height:1.5;border-radius:.2rem}.btn-block{display:block;width:100%}.btn-block+.btn-block{margin-top:.5rem}input[type=button].btn-block,input[type=reset].btn-block,input[type=submit].btn-block{width:100%}.fade{opacity:0;transition:opacity .15s linear}.fade.show{opacity:1}.collapse{display:none}.collapse.show{display:block}tr.collapse.show{display:table-row}tbody.collapse.show{display:table-row-group}.collapsing{position:relative;height:0;overflow:hidden;transition:height .35s ease}.dropdown,.dropup{position:relative}.dropdown-toggle::after{display:inline-block;width:0;height:0;margin-left:.255em;vertical-align:.255em;content:"";border-top:.3em solid;border-right:.3em solid transparent;border-bottom:0;border-left:.3em solid transparent}.dropdown-toggle:empty::after{margin-left:0}.dropdown-menu{position:absolute;top:100%;left:0;z-index:1000;display:none;float:left;min-width:10rem;padding:.5rem 0;margin:.125rem 0 0;font-size:1rem;color:#212529;text-align:left;list-style:none;background-color:#fff;background-clip:padding-box;border:1px solid rgba(0,0,0,.15);border-radius:.25rem}.dropup .dropdown-menu{margin-top:0;margin-bottom:.125rem}.dropup .dropdown-toggle::after{display:inline-block;width:0;height:0;margin-left:.255em;vertical-align:.255em;content:"";border-top:0;border-right:.3em solid transparent;border-bottom:.3em solid;border-left:.3em solid transparent}.dropup .dropdown-toggle:empty::after{margin-left:0}.dropright .dropdown-menu{margin-top:0;margin-left:.125rem}.dropright .dropdown-toggle::after{display:inline-block;width:0;height:0;margin-left:.255em;vertical-align:.255em;content:"";border-top:.3em solid transparent;border-bottom:.3em solid transparent;border-left:.3em solid}.dropright .dropdown-toggle:empty::after{margin-left:0}.dropright .dropdown-toggle::after{vertical-align:0}.dropleft .dropdown-menu{margin-top:0;margin-right:.125rem}.dropleft .dropdown-toggle::after{display:inline-block;width:0;height:0;margin-left:.255em;vertical-align:.255em;content:""}.dropleft .dropdown-toggle::after{display:none}.dropleft .dropdown-toggle::before{display:inline-block;width:0;height:0;margin-right:.255em;vertical-align:.255em;content:"";border-top:.3em solid transparent;border-right:.3em solid;border-bottom:.3em solid transparent}.dropleft .dropdown-toggle:empty::after{margin-left:0}.dropleft .dropdown-toggle::before{vertical-align:0}.dropdown-divider{height:0;margin:.5rem 0;overflow:hidden;border-top:1px solid #e9ecef}.dropdown-item{display:block;width:100%;padding:.25rem 1.5rem;clear:both;font-weight:400;color:#212529;text-align:inherit;white-space:nowrap;background-color:transparent;border:0}.dropdown-item:focus,.dropdown-item:hover{color:#16181b;text-decoration:none;background-color:#f8f9fa}.dropdown-item.active,.dropdown-item:active{color:#fff;text-decoration:none;background-color:#007bff}.dropdown-item.disabled,.dropdown-item:disabled{color:#6c757d;background-color:transparent}.dropdown-menu.show{display:block}.dropdown-header{display:block;padding:.5rem 1.5rem;margin-bottom:0;font-size:.875rem;color:#6c757d;white-space:nowrap}.btn-group,.btn-group-vertical{position:relative;display:-webkit-inline-box;display:-ms-inline-flexbox;display:inline-flex;vertical-align:middle}.btn-group-vertical>.btn,.btn-group>.btn{position:relative;-webkit-box-flex:0;-ms-flex:0 1 auto;flex:0 1 auto}.btn-group-vertical>.btn:hover,.btn-group>.btn:hover{z-index:1}.btn-group-vertical>.btn.active,.btn-group-vertical>.btn:active,.btn-group-vertical>.btn:focus,.btn-group>.btn.active,.btn-group>.btn:active,.btn-group>.btn:focus{z-index:1}.btn-group .btn+.btn,.btn-group .btn+.btn-group,.btn-group .btn-group+.btn,.btn-group .btn-group+.btn-group,.btn-group-vertical .btn+.btn,.btn-group-vertical .btn+.btn-group,.btn-group-vertical .btn-group+.btn,.btn-group-vertical .btn-group+.btn-group{margin-left:-1px}.btn-toolbar{display:-webkit-box;display:-ms-flexbox;display:flex;-ms-flex-wrap:wrap;flex-wrap:wrap;-webkit-box-pack:start;-ms-flex-pack:start;justify-content:flex-start}.btn-toolbar .input-group{width:auto}.btn-group>.btn:first-child{margin-left:0}.btn-group>.btn-group:not(:last-child)>.btn,.btn-group>.btn:not(:last-child):not(.dropdown-toggle){border-top-right-radius:0;border-bottom-right-radius:0}.btn-group>.btn-group:not(:first-child)>.btn,.btn-group>.btn:not(:first-child){border-top-left-radius:0;border-bottom-left-radius:0}.dropdown-toggle-split{padding-right:.5625rem;padding-left:.5625rem}.dropdown-toggle-split::after{margin-left:0}.btn-group-sm>.btn+.dropdown-toggle-split,.btn-sm+.dropdown-toggle-split{padding-right:.375rem;padding-left:.375rem}.btn-group-lg>.btn+.dropdown-toggle-split,.btn-lg+.dropdown-toggle-split{padding-right:.75rem;padding-left:.75rem}.btn-group-vertical{-webkit-box-orient:vertical;-webkit-box-direction:normal;-ms-flex-direction:column;flex-direction:column;-webkit-box-align:start;-ms-flex-align:start;align-items:flex-start;-webkit-box-pack:center;-ms-flex-pack:center;justify-content:center}.btn-group-vertical .btn,.btn-group-vertical .btn-group{width:100%}.btn-group-vertical>.btn+.btn,.btn-group-vertical>.btn+.btn-group,.btn-group-vertical>.btn-group+.btn,.btn-group-vertical>.btn-group+.btn-group{margin-top:-1px;margin-left:0}.btn-group-vertical>.btn-group:not(:last-child)>.btn,.btn-group-vertical>.btn:not(:last-child):not(.dropdown-toggle){border-bottom-right-radius:0;border-bottom-left-radius:0}.btn-group-vertical>.btn-group:not(:first-child)>.btn,.btn-group-vertical>.btn:not(:first-child){border-top-left-radius:0;border-top-right-radius:0}.btn-group-toggle>.btn,.btn-group-toggle>.btn-group>.btn{margin-bottom:0}.btn-group-toggle>.btn input[type=checkbox],.btn-group-toggle>.btn input[type=radio],.btn-group-toggle>.btn-group>.btn input[type=checkbox],.btn-group-toggle>.btn-group>.btn input[type=radio]{position:absolute;clip:rect(0,0,0,0);pointer-events:none}.input-group{position:relative;display:-webkit-box;display:-ms-flexbox;display:flex;-ms-flex-wrap:wrap;flex-wrap:wrap;-webkit-box-align:stretch;-ms-flex-align:stretch;align-items:stretch;width:100%}.input-group>.custom-file,.input-group>.custom-select,.input-group>.form-control{position:relative;-webkit-box-flex:1;-ms-flex:1 1 auto;flex:1 1 auto;width:1%;margin-bottom:0}.input-group>.custom-file:focus,.input-group>.custom-select:focus,.input-group>.form-control:focus{z-index:3}.input-group>.custom-file+.custom-file,.input-group>.custom-file+.custom-select,.input-group>.custom-file+.form-control,.input-group>.custom-select+.custom-file,.input-group>.custom-select+.custom-select,.input-group>.custom-select+.form-control,.input-group>.form-control+.custom-file,.input-group>.form-control+.custom-select,.input-group>.form-control+.form-control{margin-left:-1px}.input-group>.custom-select:not(:last-child),.input-group>.form-control:not(:last-child){border-top-right-radius:0;border-bottom-right-radius:0}.input-group>.custom-select:not(:first-child),.input-group>.form-control:not(:first-child){border-top-left-radius:0;border-bottom-left-radius:0}.input-group>.custom-file{display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-align:center;-ms-flex-align:center;align-items:center}.input-group>.custom-file:not(:last-child) .custom-file-label,.input-group>.custom-file:not(:last-child) .custom-file-label::before{border-top-right-radius:0;border-bottom-right-radius:0}.input-group>.custom-file:not(:first-child) .custom-file-label,.input-group>.custom-file:not(:first-child) .custom-file-label::before{border-top-left-radius:0;border-bottom-left-radius:0}.input-group-append,.input-group-prepend{display:-webkit-box;display:-ms-flexbox;display:flex}.input-group-append .btn,.input-group-prepend .btn{position:relative;z-index:2}.input-group-append .btn+.btn,.input-group-append .btn+.input-group-text,.input-group-append .input-group-text+.btn,.input-group-append .input-group-text+.input-group-text,.input-group-prepend .btn+.btn,.input-group-prepend .btn+.input-group-text,.input-group-prepend .input-group-text+.btn,.input-group-prepend .input-group-text+.input-group-text{margin-left:-1px}.input-group-prepend{margin-right:-1px}.input-group-append{margin-left:-1px}.input-group-text{display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-align:center;-ms-flex-align:center;align-items:center;padding:.375rem .75rem;margin-bottom:0;font-size:1rem;font-weight:400;line-height:1.5;color:#495057;text-align:center;white-space:nowrap;background-color:#e9ecef;border:1px solid #ced4da;border-radius:.25rem}.input-group-text input[type=checkbox],.input-group-text input[type=radio]{margin-top:0}.input-group>.input-group-append:last-child>.btn:not(:last-child):not(.dropdown-toggle),.input-group>.input-group-append:last-child>.input-group-text:not(:last-child),.input-group>.input-group-append:not(:last-child)>.btn,.input-group>.input-group-append:not(:last-child)>.input-group-text,.input-group>.input-group-prepend>.btn,.input-group>.input-group-prepend>.input-group-text{border-top-right-radius:0;border-bottom-right-radius:0}.input-group>.input-group-append>.btn,.input-group>.input-group-append>.input-group-text,.input-group>.input-group-prepend:first-child>.btn:not(:first-child),.input-group>.input-group-prepend:first-child>.input-group-text:not(:first-child),.input-group>.input-group-prepend:not(:first-child)>.btn,.input-group>.input-group-prepend:not(:first-child)>.input-group-text{border-top-left-radius:0;border-bottom-left-radius:0}.custom-control{position:relative;display:block;min-height:1.5rem;padding-left:1.5rem}.custom-control-inline{display:-webkit-inline-box;display:-ms-inline-flexbox;display:inline-flex;margin-right:1rem}.custom-control-input{position:absolute;z-index:-1;opacity:0}.custom-control-input:checked~.custom-control-label::before{color:#fff;background-color:#007bff}.custom-control-input:focus~.custom-control-label::before{box-shadow:0 0 0 1px #fff,0 0 0 .2rem rgba(0,123,255,.25)}.custom-control-input:active~.custom-control-label::before{color:#fff;background-color:#b3d7ff}.custom-control-input:disabled~.custom-control-label{color:#6c757d}.custom-control-input:disabled~.custom-control-label::before{background-color:#e9ecef}.custom-control-label{margin-bottom:0}.custom-control-label::before{position:absolute;top:.25rem;left:0;display:block;width:1rem;height:1rem;pointer-events:none;content:"";-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;background-color:#dee2e6}.custom-control-label::after{position:absolute;top:.25rem;left:0;display:block;width:1rem;height:1rem;content:"";background-repeat:no-repeat;background-position:center center;background-size:50% 50%}.custom-checkbox .custom-control-label::before{border-radius:.25rem}.custom-checkbox .custom-control-input:checked~.custom-control-label::before{background-color:#007bff}.custom-checkbox .custom-control-input:checked~.custom-control-label::after{background-image:url("data:image/svg+xml;charset=utf8,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 8 8'%3E%3Cpath fill='%23fff' d='M6.564.75l-3.59 3.612-1.538-1.55L0 4.26 2.974 7.25 8 2.193z'/%3E%3C/svg%3E")}.custom-checkbox .custom-control-input:indeterminate~.custom-control-label::before{background-color:#007bff}.custom-checkbox .custom-control-input:indeterminate~.custom-control-label::after{background-image:url("data:image/svg+xml;charset=utf8,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 4 4'%3E%3Cpath stroke='%23fff' d='M0 2h4'/%3E%3C/svg%3E")}.custom-checkbox .custom-control-input:disabled:checked~.custom-control-label::before{background-color:rgba(0,123,255,.5)}.custom-checkbox .custom-control-input:disabled:indeterminate~.custom-control-label::before{background-color:rgba(0,123,255,.5)}.custom-radio .custom-control-label::before{border-radius:50%}.custom-radio .custom-control-input:checked~.custom-control-label::before{background-color:#007bff}.custom-radio .custom-control-input:checked~.custom-control-label::after{background-image:url("data:image/svg+xml;charset=utf8,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='-4 -4 8 8'%3E%3Ccircle r='3' fill='%23fff'/%3E%3C/svg%3E")}.custom-radio .custom-control-input:disabled:checked~.custom-control-label::before{background-color:rgba(0,123,255,.5)}.custom-select{display:inline-block;width:100%;height:calc(2.25rem + 2px);padding:.375rem 1.75rem .375rem .75rem;line-height:1.5;color:#495057;vertical-align:middle;background:#fff url("data:image/svg+xml;charset=utf8,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 4 5'%3E%3Cpath fill='%23343a40' d='M2 0L0 2h4zm0 5L0 3h4z'/%3E%3C/svg%3E") no-repeat right .75rem center;background-size:8px 10px;border:1px solid #ced4da;border-radius:.25rem;-webkit-appearance:none;-moz-appearance:none;appearance:none}.custom-select:focus{border-color:#80bdff;outline:0;box-shadow:inset 0 1px 2px rgba(0,0,0,.075),0 0 5px rgba(128,189,255,.5)}.custom-select:focus::-ms-value{color:#495057;background-color:#fff}.custom-select[multiple],.custom-select[size]:not([size="1"]){height:auto;padding-right:.75rem;background-image:none}.custom-select:disabled{color:#6c757d;background-color:#e9ecef}.custom-select::-ms-expand{opacity:0}.custom-select-sm{height:calc(1.8125rem + 2px);padding-top:.375rem;padding-bottom:.375rem;font-size:75%}.custom-select-lg{height:calc(2.875rem + 2px);padding-top:.375rem;padding-bottom:.375rem;font-size:125%}.custom-file{position:relative;display:inline-block;width:100%;height:calc(2.25rem + 2px);margin-bottom:0}.custom-file-input{position:relative;z-index:2;width:100%;height:calc(2.25rem + 2px);margin:0;opacity:0}.custom-file-input:focus~.custom-file-control{border-color:#80bdff;box-shadow:0 0 0 .2rem rgba(0,123,255,.25)}.custom-file-input:focus~.custom-file-control::before{border-color:#80bdff}.custom-file-input:lang(en)~.custom-file-label::after{content:"Browse"}.custom-file-label{position:absolute;top:0;right:0;left:0;z-index:1;height:calc(2.25rem + 2px);padding:.375rem .75rem;line-height:1.5;color:#495057;background-color:#fff;border:1px solid #ced4da;border-radius:.25rem}.custom-file-label::after{position:absolute;top:0;right:0;bottom:0;z-index:3;display:block;height:calc(calc(2.25rem + 2px) - 1px * 2);padding:.375rem .75rem;line-height:1.5;color:#495057;content:"Browse";background-color:#e9ecef;border-left:1px solid #ced4da;border-radius:0 .25rem .25rem 0}.nav{display:-webkit-box;display:-ms-flexbox;display:flex;-ms-flex-wrap:wrap;flex-wrap:wrap;padding-left:0;margin-bottom:0;list-style:none}.nav-link{display:block;padding:.5rem 1rem}.nav-link:focus,.nav-link:hover{text-decoration:none}.nav-link.disabled{color:#6c757d}.nav-tabs{border-bottom:1px solid #dee2e6}.nav-tabs .nav-item{margin-bottom:-1px}.nav-tabs .nav-link{border:1px solid transparent;border-top-left-radius:.25rem;border-top-right-radius:.25rem}.nav-tabs .nav-link:focus,.nav-tabs .nav-link:hover{border-color:#e9ecef #e9ecef #dee2e6}.nav-tabs .nav-link.disabled{color:#6c757d;background-color:transparent;border-color:transparent}.nav-tabs .nav-item.show .nav-link,.nav-tabs .nav-link.active{color:#495057;background-color:#fff;border-color:#dee2e6 #dee2e6 #fff}.nav-tabs .dropdown-menu{margin-top:-1px;border-top-left-radius:0;border-top-right-radius:0}.nav-pills .nav-link{border-radius:.25rem}.nav-pills .nav-link.active,.nav-pills .show>.nav-link{color:#fff;background-color:#007bff}.nav-fill .nav-item{-webkit-box-flex:1;-ms-flex:1 1 auto;flex:1 1 auto;text-align:center}.nav-justified .nav-item{-ms-flex-preferred-size:0;flex-basis:0;-webkit-box-flex:1;-ms-flex-positive:1;flex-grow:1;text-align:center}.tab-content>.tab-pane{display:none}.tab-content>.active{display:block}.navbar{position:relative;display:-webkit-box;display:-ms-flexbox;display:flex;-ms-flex-wrap:wrap;flex-wrap:wrap;-webkit-box-align:center;-ms-flex-align:center;align-items:center;-webkit-box-pack:justify;-ms-flex-pack:justify;justify-content:space-between;padding:.5rem 1rem}.navbar>.container,.navbar>.container-fluid{display:-webkit-box;display:-ms-flexbox;display:flex;-ms-flex-wrap:wrap;flex-wrap:wrap;-webkit-box-align:center;-ms-flex-align:center;align-items:center;-webkit-box-pack:justify;-ms-flex-pack:justify;justify-content:space-between}.navbar-brand{display:inline-block;padding-top:.3125rem;padding-bottom:.3125rem;margin-right:1rem;font-size:1.25rem;line-height:inherit;white-space:nowrap}.navbar-brand:focus,.navbar-brand:hover{text-decoration:none}.navbar-nav{display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-orient:vertical;-webkit-box-direction:normal;-ms-flex-direction:column;flex-direction:column;padding-left:0;margin-bottom:0;list-style:none}.navbar-nav .nav-link{padding-right:0;padding-left:0}.navbar-nav .dropdown-menu{position:static;float:none}.navbar-text{display:inline-block;padding-top:.5rem;padding-bottom:.5rem}.navbar-collapse{-ms-flex-preferred-size:100%;flex-basis:100%;-webkit-box-flex:1;-ms-flex-positive:1;flex-grow:1;-webkit-box-align:center;-ms-flex-align:center;align-items:center}.navbar-toggler{padding:.25rem .75rem;font-size:1.25rem;line-height:1;background-color:transparent;border:1px solid transparent;border-radius:.25rem}.navbar-toggler:focus,.navbar-toggler:hover{text-decoration:none}.navbar-toggler:not(:disabled):not(.disabled){cursor:pointer}.navbar-toggler-icon{display:inline-block;width:1.5em;height:1.5em;vertical-align:middle;content:"";background:no-repeat center center;background-size:100% 100%}@media (max-width:575.98px){.navbar-expand-sm>.container,.navbar-expand-sm>.container-fluid{padding-right:0;padding-left:0}}@media (min-width:576px){.navbar-expand-sm{-webkit-box-orient:horizontal;-webkit-box-direction:normal;-ms-flex-flow:row nowrap;flex-flow:row nowrap;-webkit-box-pack:start;-ms-flex-pack:start;justify-content:flex-start}.navbar-expand-sm .navbar-nav{-webkit-box-orient:horizontal;-webkit-box-direction:normal;-ms-flex-direction:row;flex-direction:row}.navbar-expand-sm .navbar-nav .dropdown-menu{position:absolute}.navbar-expand-sm .navbar-nav .dropdown-menu-right{right:0;left:auto}.navbar-expand-sm .navbar-nav .nav-link{padding-right:.5rem;padding-left:.5rem}.navbar-expand-sm>.container,.navbar-expand-sm>.container-fluid{-ms-flex-wrap:nowrap;flex-wrap:nowrap}.navbar-expand-sm .navbar-collapse{display:-webkit-box!important;display:-ms-flexbox!important;display:flex!important;-ms-flex-preferred-size:auto;flex-basis:auto}.navbar-expand-sm .navbar-toggler{display:none}.navbar-expand-sm .dropup .dropdown-menu{top:auto;bottom:100%}}@media (max-width:767.98px){.navbar-expand-md>.container,.navbar-expand-md>.container-fluid{padding-right:0;padding-left:0}}@media (min-width:768px){.navbar-expand-md{-webkit-box-orient:horizontal;-webkit-box-direction:normal;-ms-flex-flow:row nowrap;flex-flow:row nowrap;-webkit-box-pack:start;-ms-flex-pack:start;justify-content:flex-start}.navbar-expand-md .navbar-nav{-webkit-box-orient:horizontal;-webkit-box-direction:normal;-ms-flex-direction:row;flex-direction:row}.navbar-expand-md .navbar-nav .dropdown-menu{position:absolute}.navbar-expand-md .navbar-nav .dropdown-menu-right{right:0;left:auto}.navbar-expand-md .navbar-nav .nav-link{padding-right:.5rem;padding-left:.5rem}.navbar-expand-md>.container,.navbar-expand-md>.container-fluid{-ms-flex-wrap:nowrap;flex-wrap:nowrap}.navbar-expand-md .navbar-collapse{display:-webkit-box!important;display:-ms-flexbox!important;display:flex!important;-ms-flex-preferred-size:auto;flex-basis:auto}.navbar-expand-md .navbar-toggler{display:none}.navbar-expand-md .dropup .dropdown-menu{top:auto;bottom:100%}}@media (max-width:991.98px){.navbar-expand-lg>.container,.navbar-expand-lg>.container-fluid{padding-right:0;padding-left:0}}@media (min-width:992px){.navbar-expand-lg{-webkit-box-orient:horizontal;-webkit-box-direction:normal;-ms-flex-flow:row nowrap;flex-flow:row nowrap;-webkit-box-pack:start;-ms-flex-pack:start;justify-content:flex-start}.navbar-expand-lg .navbar-nav{-webkit-box-orient:horizontal;-webkit-box-direction:normal;-ms-flex-direction:row;flex-direction:row}.navbar-expand-lg .navbar-nav .dropdown-menu{position:absolute}.navbar-expand-lg .navbar-nav .dropdown-menu-right{right:0;left:auto}.navbar-expand-lg .navbar-nav .nav-link{padding-right:.5rem;padding-left:.5rem}.navbar-expand-lg>.container,.navbar-expand-lg>.container-fluid{-ms-flex-wrap:nowrap;flex-wrap:nowrap}.navbar-expand-lg .navbar-collapse{display:-webkit-box!important;display:-ms-flexbox!important;display:flex!important;-ms-flex-preferred-size:auto;flex-basis:auto}.navbar-expand-lg .navbar-toggler{display:none}.navbar-expand-lg .dropup .dropdown-menu{top:auto;bottom:100%}}@media (max-width:1199.98px){.navbar-expand-xl>.container,.navbar-expand-xl>.container-fluid{padding-right:0;padding-left:0}}@media (min-width:1200px){.navbar-expand-xl{-webkit-box-orient:horizontal;-webkit-box-direction:normal;-ms-flex-flow:row nowrap;flex-flow:row nowrap;-webkit-box-pack:start;-ms-flex-pack:start;justify-content:flex-start}.navbar-expand-xl .navbar-nav{-webkit-box-orient:horizontal;-webkit-box-direction:normal;-ms-flex-direction:row;flex-direction:row}.navbar-expand-xl .navbar-nav .dropdown-menu{position:absolute}.navbar-expand-xl .navbar-nav .dropdown-menu-right{right:0;left:auto}.navbar-expand-xl .navbar-nav .nav-link{padding-right:.5rem;padding-left:.5rem}.navbar-expand-xl>.container,.navbar-expand-xl>.container-fluid{-ms-flex-wrap:nowrap;flex-wrap:nowrap}.navbar-expand-xl .navbar-collapse{display:-webkit-box!important;display:-ms-flexbox!important;display:flex!important;-ms-flex-preferred-size:auto;flex-basis:auto}.navbar-expand-xl .navbar-toggler{display:none}.navbar-expand-xl .dropup .dropdown-menu{top:auto;bottom:100%}}.navbar-expand{-webkit-box-orient:horizontal;-webkit-box-direction:normal;-ms-flex-flow:row nowrap;flex-flow:row nowrap;-webkit-box-pack:start;-ms-flex-pack:start;justify-content:flex-start}.navbar-expand>.container,.navbar-expand>.container-fluid{padding-right:0;padding-left:0}.navbar-expand .navbar-nav{-webkit-box-orient:horizontal;-webkit-box-direction:normal;-ms-flex-direction:row;flex-direction:row}.navbar-expand .navbar-nav .dropdown-menu{position:absolute}.navbar-expand .navbar-nav .dropdown-menu-right{right:0;left:auto}.navbar-expand .navbar-nav .nav-link{padding-right:.5rem;padding-left:.5rem}.navbar-expand>.container,.navbar-expand>.container-fluid{-ms-flex-wrap:nowrap;flex-wrap:nowrap}.navbar-expand .navbar-collapse{display:-webkit-box!important;display:-ms-flexbox!important;display:flex!important;-ms-flex-preferred-size:auto;flex-basis:auto}.navbar-expand .navbar-toggler{display:none}.navbar-expand .dropup .dropdown-menu{top:auto;bottom:100%}.navbar-light .navbar-brand{color:rgba(0,0,0,.9)}.navbar-light .navbar-brand:focus,.navbar-light .navbar-brand:hover{color:rgba(0,0,0,.9)}.navbar-light .navbar-nav .nav-link{color:rgba(0,0,0,.5)}.navbar-light .navbar-nav .nav-link:focus,.navbar-light .navbar-nav .nav-link:hover{color:rgba(0,0,0,.7)}.navbar-light .navbar-nav .nav-link.disabled{color:rgba(0,0,0,.3)}.navbar-light .navbar-nav .active>.nav-link,.navbar-light .navbar-nav .nav-link.active,.navbar-light .navbar-nav .nav-link.show,.navbar-light .navbar-nav .show>.nav-link{color:rgba(0,0,0,.9)}.navbar-light .navbar-toggler{color:rgba(0,0,0,.5);border-color:rgba(0,0,0,.1)}.navbar-light .navbar-toggler-icon{background-image:url("data:image/svg+xml;charset=utf8,%3Csvg viewBox='0 0 30 30' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath stroke='rgba(0, 0, 0, 0.5)' stroke-width='2' stroke-linecap='round' stroke-miterlimit='10' d='M4 7h22M4 15h22M4 23h22'/%3E%3C/svg%3E")}.navbar-light .navbar-text{color:rgba(0,0,0,.5)}.navbar-light .navbar-text a{color:rgba(0,0,0,.9)}.navbar-light .navbar-text a:focus,.navbar-light .navbar-text a:hover{color:rgba(0,0,0,.9)}.navbar-dark .navbar-brand{color:#fff}.navbar-dark .navbar-brand:focus,.navbar-dark .navbar-brand:hover{color:#fff}.navbar-dark .navbar-nav .nav-link{color:rgba(255,255,255,.5)}.navbar-dark .navbar-nav .nav-link:focus,.navbar-dark .navbar-nav .nav-link:hover{color:rgba(255,255,255,.75)}.navbar-dark .navbar-nav .nav-link.disabled{color:rgba(255,255,255,.25)}.navbar-dark .navbar-nav .active>.nav-link,.navbar-dark .navbar-nav .nav-link.active,.navbar-dark .navbar-nav .nav-link.show,.navbar-dark .navbar-nav .show>.nav-link{color:#fff}.navbar-dark .navbar-toggler{color:rgba(255,255,255,.5);border-color:rgba(255,255,255,.1)}.navbar-dark .navbar-toggler-icon{background-image:url("data:image/svg+xml;charset=utf8,%3Csvg viewBox='0 0 30 30' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath stroke='rgba(255, 255, 255, 0.5)' stroke-width='2' stroke-linecap='round' stroke-miterlimit='10' d='M4 7h22M4 15h22M4 23h22'/%3E%3C/svg%3E")}.navbar-dark .navbar-text{color:rgba(255,255,255,.5)}.navbar-dark .navbar-text a{color:#fff}.navbar-dark .navbar-text a:focus,.navbar-dark .navbar-text a:hover{color:#fff}.card{position:relative;display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-orient:vertical;-webkit-box-direction:normal;-ms-flex-direction:column;flex-direction:column;min-width:0;word-wrap:break-word;background-color:#fff;background-clip:border-box;border:1px solid rgba(0,0,0,.125);border-radius:.25rem}.card>hr{margin-right:0;margin-left:0}.card>.list-group:first-child .list-group-item:first-child{border-top-left-radius:.25rem;border-top-right-radius:.25rem}.card>.list-group:last-child .list-group-item:last-child{border-bottom-right-radius:.25rem;border-bottom-left-radius:.25rem}.card-body{-webkit-box-flex:1;-ms-flex:1 1 auto;flex:1 1 auto;padding:1.25rem}.card-title{margin-bottom:.75rem}.card-subtitle{margin-top:-.375rem;margin-bottom:0}.card-text:last-child{margin-bottom:0}.card-link:hover{text-decoration:none}.card-link+.card-link{margin-left:1.25rem}.card-header{padding:.75rem 1.25rem;margin-bottom:0;background-color:rgba(0,0,0,.03);border-bottom:1px solid rgba(0,0,0,.125)}.card-header:first-child{border-radius:calc(.25rem - 1px) calc(.25rem - 1px) 0 0}.card-header+.list-group .list-group-item:first-child{border-top:0}.card-footer{padding:.75rem 1.25rem;background-color:rgba(0,0,0,.03);border-top:1px solid rgba(0,0,0,.125)}.card-footer:last-child{border-radius:0 0 calc(.25rem - 1px) calc(.25rem - 1px)}.card-header-tabs{margin-right:-.625rem;margin-bottom:-.75rem;margin-left:-.625rem;border-bottom:0}.card-header-pills{margin-right:-.625rem;margin-left:-.625rem}.card-img-overlay{position:absolute;top:0;right:0;bottom:0;left:0;padding:1.25rem}.card-img{width:100%;border-radius:calc(.25rem - 1px)}.card-img-top{width:100%;border-top-left-radius:calc(.25rem - 1px);border-top-right-radius:calc(.25rem - 1px)}.card-img-bottom{width:100%;border-bottom-right-radius:calc(.25rem - 1px);border-bottom-left-radius:calc(.25rem - 1px)}.card-deck{display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-orient:vertical;-webkit-box-direction:normal;-ms-flex-direction:column;flex-direction:column}.card-deck .card{margin-bottom:15px}@media (min-width:576px){.card-deck{-webkit-box-orient:horizontal;-webkit-box-direction:normal;-ms-flex-flow:row wrap;flex-flow:row wrap;margin-right:-15px;margin-left:-15px}.card-deck .card{display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-flex:1;-ms-flex:1 0 0%;flex:1 0 0%;-webkit-box-orient:vertical;-webkit-box-direction:normal;-ms-flex-direction:column;flex-direction:column;margin-right:15px;margin-bottom:0;margin-left:15px}}.card-group{display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-orient:vertical;-webkit-box-direction:normal;-ms-flex-direction:column;flex-direction:column}.card-group>.card{margin-bottom:15px}@media (min-width:576px){.card-group{-webkit-box-orient:horizontal;-webkit-box-direction:normal;-ms-flex-flow:row wrap;flex-flow:row wrap}.card-group>.card{-webkit-box-flex:1;-ms-flex:1 0 0%;flex:1 0 0%;margin-bottom:0}.card-group>.card+.card{margin-left:0;border-left:0}.card-group>.card:first-child{border-top-right-radius:0;border-bottom-right-radius:0}.card-group>.card:first-child .card-header,.card-group>.card:first-child .card-img-top{border-top-right-radius:0}.card-group>.card:first-child .card-footer,.card-group>.card:first-child .card-img-bottom{border-bottom-right-radius:0}.card-group>.card:last-child{border-top-left-radius:0;border-bottom-left-radius:0}.card-group>.card:last-child .card-header,.card-group>.card:last-child .card-img-top{border-top-left-radius:0}.card-group>.card:last-child .card-footer,.card-group>.card:last-child .card-img-bottom{border-bottom-left-radius:0}.card-group>.card:only-child{border-radius:.25rem}.card-group>.card:only-child .card-header,.card-group>.card:only-child .card-img-top{border-top-left-radius:.25rem;border-top-right-radius:.25rem}.card-group>.card:only-child .card-footer,.card-group>.card:only-child .card-img-bottom{border-bottom-right-radius:.25rem;border-bottom-left-radius:.25rem}.card-group>.card:not(:first-child):not(:last-child):not(:only-child){border-radius:0}.card-group>.card:not(:first-child):not(:last-child):not(:only-child) .card-footer,.card-group>.card:not(:first-child):not(:last-child):not(:only-child) .card-header,.card-group>.card:not(:first-child):not(:last-child):not(:only-child) .card-img-bottom,.card-group>.card:not(:first-child):not(:last-child):not(:only-child) .card-img-top{border-radius:0}}.card-columns .card{margin-bottom:.75rem}@media (min-width:576px){.card-columns{-webkit-column-count:3;-moz-column-count:3;column-count:3;-webkit-column-gap:1.25rem;-moz-column-gap:1.25rem;column-gap:1.25rem}.card-columns .card{display:inline-block;width:100%}}.breadcrumb{display:-webkit-box;display:-ms-flexbox;display:flex;-ms-flex-wrap:wrap;flex-wrap:wrap;padding:.75rem 1rem;margin-bottom:1rem;list-style:none;background-color:#e9ecef;border-radius:.25rem}.breadcrumb-item+.breadcrumb-item::before{display:inline-block;padding-right:.5rem;padding-left:.5rem;color:#6c757d;content:"/"}.breadcrumb-item+.breadcrumb-item:hover::before{text-decoration:underline}.breadcrumb-item+.breadcrumb-item:hover::before{text-decoration:none}.breadcrumb-item.active{color:#6c757d}.pagination{display:-webkit-box;display:-ms-flexbox;display:flex;padding-left:0;list-style:none;border-radius:.25rem}.page-link{position:relative;display:block;padding:.5rem .75rem;margin-left:-1px;line-height:1.25;color:#007bff;background-color:#fff;border:1px solid #dee2e6}.page-link:hover{color:#0056b3;text-decoration:none;background-color:#e9ecef;border-color:#dee2e6}.page-link:focus{z-index:2;outline:0;box-shadow:0 0 0 .2rem rgba(0,123,255,.25)}.page-link:not(:disabled):not(.disabled){cursor:pointer}.page-item:first-child .page-link{margin-left:0;border-top-left-radius:.25rem;border-bottom-left-radius:.25rem}.page-item:last-child .page-link{border-top-right-radius:.25rem;border-bottom-right-radius:.25rem}.page-item.active .page-link{z-index:1;color:#fff;background-color:#007bff;border-color:#007bff}.page-item.disabled .page-link{color:#6c757d;pointer-events:none;cursor:auto;background-color:#fff;border-color:#dee2e6}.pagination-lg .page-link{padding:.75rem 1.5rem;font-size:1.25rem;line-height:1.5}.pagination-lg .page-item:first-child .page-link{border-top-left-radius:.3rem;border-bottom-left-radius:.3rem}.pagination-lg .page-item:last-child .page-link{border-top-right-radius:.3rem;border-bottom-right-radius:.3rem}.pagination-sm .page-link{padding:.25rem .5rem;font-size:.875rem;line-height:1.5}.pagination-sm .page-item:first-child .page-link{border-top-left-radius:.2rem;border-bottom-left-radius:.2rem}.pagination-sm .page-item:last-child .page-link{border-top-right-radius:.2rem;border-bottom-right-radius:.2rem}.badge{display:inline-block;padding:.25em .4em;font-size:75%;font-weight:700;line-height:1;text-align:center;white-space:nowrap;vertical-align:baseline;border-radius:.25rem}.badge:empty{display:none}.btn .badge{position:relative;top:-1px}.badge-pill{padding-right:.6em;padding-left:.6em;border-radius:10rem}.badge-primary{color:#fff;background-color:#007bff}.badge-primary[href]:focus,.badge-primary[href]:hover{color:#fff;text-decoration:none;background-color:#0062cc}.badge-secondary{color:#fff;background-color:#6c757d}.badge-secondary[href]:focus,.badge-secondary[href]:hover{color:#fff;text-decoration:none;background-color:#545b62}.badge-success{color:#fff;background-color:#28a745}.badge-success[href]:focus,.badge-success[href]:hover{color:#fff;text-decoration:none;background-color:#1e7e34}.badge-info{color:#fff;background-color:#17a2b8}.badge-info[href]:focus,.badge-info[href]:hover{color:#fff;text-decoration:none;background-color:#117a8b}.badge-warning{color:#212529;background-color:#ffc107}.badge-warning[href]:focus,.badge-warning[href]:hover{color:#212529;text-decoration:none;background-color:#d39e00}.badge-danger{color:#fff;background-color:#dc3545}.badge-danger[href]:focus,.badge-danger[href]:hover{color:#fff;text-decoration:none;background-color:#bd2130}.badge-light{color:#212529;background-color:#f8f9fa}.badge-light[href]:focus,.badge-light[href]:hover{color:#212529;text-decoration:none;background-color:#dae0e5}.badge-dark{color:#fff;background-color:#343a40}.badge-dark[href]:focus,.badge-dark[href]:hover{color:#fff;text-decoration:none;background-color:#1d2124}.jumbotron{padding:2rem 1rem;margin-bottom:2rem;background-color:#e9ecef;border-radius:.3rem}@media (min-width:576px){.jumbotron{padding:4rem 2rem}}.jumbotron-fluid{padding-right:0;padding-left:0;border-radius:0}.alert{position:relative;padding:.75rem 1.25rem;margin-bottom:1rem;border:1px solid transparent;border-radius:.25rem}.alert-heading{color:inherit}.alert-link{font-weight:700}.alert-dismissible{padding-right:4rem}.alert-dismissible .close{position:absolute;top:0;right:0;padding:.75rem 1.25rem;color:inherit}.alert-primary{color:#004085;background-color:#cce5ff;border-color:#b8daff}.alert-primary hr{border-top-color:#9fcdff}.alert-primary .alert-link{color:#002752}.alert-secondary{color:#383d41;background-color:#e2e3e5;border-color:#d6d8db}.alert-secondary hr{border-top-color:#c8cbcf}.alert-secondary .alert-link{color:#202326}.alert-success{color:#155724;background-color:#d4edda;border-color:#c3e6cb}.alert-success hr{border-top-color:#b1dfbb}.alert-success .alert-link{color:#0b2e13}.alert-info{color:#0c5460;background-color:#d1ecf1;border-color:#bee5eb}.alert-info hr{border-top-color:#abdde5}.alert-info .alert-link{color:#062c33}.alert-warning{color:#856404;background-color:#fff3cd;border-color:#ffeeba}.alert-warning hr{border-top-color:#ffe8a1}.alert-warning .alert-link{color:#533f03}.alert-danger{color:#721c24;background-color:#f8d7da;border-color:#f5c6cb}.alert-danger hr{border-top-color:#f1b0b7}.alert-danger .alert-link{color:#491217}.alert-light{color:#818182;background-color:#fefefe;border-color:#fdfdfe}.alert-light hr{border-top-color:#ececf6}.alert-light .alert-link{color:#686868}.alert-dark{color:#1b1e21;background-color:#d6d8d9;border-color:#c6c8ca}.alert-dark hr{border-top-color:#b9bbbe}.alert-dark .alert-link{color:#040505}@-webkit-keyframes progress-bar-stripes{from{background-position:1rem 0}to{background-position:0 0}}@keyframes progress-bar-stripes{from{background-position:1rem 0}to{background-position:0 0}}.progress{display:-webkit-box;display:-ms-flexbox;display:flex;height:1rem;overflow:hidden;font-size:.75rem;background-color:#e9ecef;border-radius:.25rem}.progress-bar{display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-orient:vertical;-webkit-box-direction:normal;-ms-flex-direction:column;flex-direction:column;-webkit-box-pack:center;-ms-flex-pack:center;justify-content:center;color:#fff;text-align:center;background-color:#007bff;transition:width .6s ease}.progress-bar-striped{background-image:linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);background-size:1rem 1rem}.progress-bar-animated{-webkit-animation:progress-bar-stripes 1s linear infinite;animation:progress-bar-stripes 1s linear infinite}.media{display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-align:start;-ms-flex-align:start;align-items:flex-start}.media-body{-webkit-box-flex:1;-ms-flex:1;flex:1}.list-group{display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-orient:vertical;-webkit-box-direction:normal;-ms-flex-direction:column;flex-direction:column;padding-left:0;margin-bottom:0}.list-group-item-action{width:100%;color:#495057;text-align:inherit}.list-group-item-action:focus,.list-group-item-action:hover{color:#495057;text-decoration:none;background-color:#f8f9fa}.list-group-item-action:active{color:#212529;background-color:#e9ecef}.list-group-item{position:relative;display:block;padding:.75rem 1.25rem;margin-bottom:-1px;background-color:#fff;border:1px solid rgba(0,0,0,.125)}.list-group-item:first-child{border-top-left-radius:.25rem;border-top-right-radius:.25rem}.list-group-item:last-child{margin-bottom:0;border-bottom-right-radius:.25rem;border-bottom-left-radius:.25rem}.list-group-item:focus,.list-group-item:hover{z-index:1;text-decoration:none}.list-group-item.disabled,.list-group-item:disabled{color:#6c757d;background-color:#fff}.list-group-item.active{z-index:2;color:#fff;background-color:#007bff;border-color:#007bff}.list-group-flush .list-group-item{border-right:0;border-left:0;border-radius:0}.list-group-flush:first-child .list-group-item:first-child{border-top:0}.list-group-flush:last-child .list-group-item:last-child{border-bottom:0}.list-group-item-primary{color:#004085;background-color:#b8daff}.list-group-item-primary.list-group-item-action:focus,.list-group-item-primary.list-group-item-action:hover{color:#004085;background-color:#9fcdff}.list-group-item-primary.list-group-item-action.active{color:#fff;background-color:#004085;border-color:#004085}.list-group-item-secondary{color:#383d41;background-color:#d6d8db}.list-group-item-secondary.list-group-item-action:focus,.list-group-item-secondary.list-group-item-action:hover{color:#383d41;background-color:#c8cbcf}.list-group-item-secondary.list-group-item-action.active{color:#fff;background-color:#383d41;border-color:#383d41}.list-group-item-success{color:#155724;background-color:#c3e6cb}.list-group-item-success.list-group-item-action:focus,.list-group-item-success.list-group-item-action:hover{color:#155724;background-color:#b1dfbb}.list-group-item-success.list-group-item-action.active{color:#fff;background-color:#155724;border-color:#155724}.list-group-item-info{color:#0c5460;background-color:#bee5eb}.list-group-item-info.list-group-item-action:focus,.list-group-item-info.list-group-item-action:hover{color:#0c5460;background-color:#abdde5}.list-group-item-info.list-group-item-action.active{color:#fff;background-color:#0c5460;border-color:#0c5460}.list-group-item-warning{color:#856404;background-color:#ffeeba}.list-group-item-warning.list-group-item-action:focus,.list-group-item-warning.list-group-item-action:hover{color:#856404;background-color:#ffe8a1}.list-group-item-warning.list-group-item-action.active{color:#fff;background-color:#856404;border-color:#856404}.list-group-item-danger{color:#721c24;background-color:#f5c6cb}.list-group-item-danger.list-group-item-action:focus,.list-group-item-danger.list-group-item-action:hover{color:#721c24;background-color:#f1b0b7}.list-group-item-danger.list-group-item-action.active{color:#fff;background-color:#721c24;border-color:#721c24}.list-group-item-light{color:#818182;background-color:#fdfdfe}.list-group-item-light.list-group-item-action:focus,.list-group-item-light.list-group-item-action:hover{color:#818182;background-color:#ececf6}.list-group-item-light.list-group-item-action.active{color:#fff;background-color:#818182;border-color:#818182}.list-group-item-dark{color:#1b1e21;background-color:#c6c8ca}.list-group-item-dark.list-group-item-action:focus,.list-group-item-dark.list-group-item-action:hover{color:#1b1e21;background-color:#b9bbbe}.list-group-item-dark.list-group-item-action.active{color:#fff;background-color:#1b1e21;border-color:#1b1e21}.close{float:right;font-size:1.5rem;font-weight:700;line-height:1;color:#000;text-shadow:0 1px 0 #fff;opacity:.5}.close:focus,.close:hover{color:#000;text-decoration:none;opacity:.75}.close:not(:disabled):not(.disabled){cursor:pointer}button.close{padding:0;background-color:transparent;border:0;-webkit-appearance:none}.modal-open{overflow:hidden}.modal{position:fixed;top:0;right:0;bottom:0;left:0;z-index:1050;display:none;overflow:hidden;outline:0}.modal-open .modal{overflow-x:hidden;overflow-y:auto}.modal-dialog{position:relative;width:auto;margin:.5rem;pointer-events:none}.modal.fade .modal-dialog{transition:-webkit-transform .3s ease-out;transition:transform .3s ease-out;transition:transform .3s ease-out,-webkit-transform .3s ease-out;-webkit-transform:translate(0,-25%);transform:translate(0,-25%)}.modal.show .modal-dialog{-webkit-transform:translate(0,0);transform:translate(0,0)}.modal-dialog-centered{display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-align:center;-ms-flex-align:center;align-items:center;min-height:calc(100% - (.5rem * 2))}.modal-content{position:relative;display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-orient:vertical;-webkit-box-direction:normal;-ms-flex-direction:column;flex-direction:column;width:100%;pointer-events:auto;background-color:#fff;background-clip:padding-box;border:1px solid rgba(0,0,0,.2);border-radius:.3rem;outline:0}.modal-backdrop{position:fixed;top:0;right:0;bottom:0;left:0;z-index:1040;background-color:#000}.modal-backdrop.fade{opacity:0}.modal-backdrop.show{opacity:.5}.modal-header{display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-align:start;-ms-flex-align:start;align-items:flex-start;-webkit-box-pack:justify;-ms-flex-pack:justify;justify-content:space-between;padding:1rem;border-bottom:1px solid #e9ecef;border-top-left-radius:.3rem;border-top-right-radius:.3rem}.modal-header .close{padding:1rem;margin:-1rem -1rem -1rem auto}.modal-title{margin-bottom:0;line-height:1.5}.modal-body{position:relative;-webkit-box-flex:1;-ms-flex:1 1 auto;flex:1 1 auto;padding:1rem}.modal-footer{display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-align:center;-ms-flex-align:center;align-items:center;-webkit-box-pack:end;-ms-flex-pack:end;justify-content:flex-end;padding:1rem;border-top:1px solid #e9ecef}.modal-footer>:not(:first-child){margin-left:.25rem}.modal-footer>:not(:last-child){margin-right:.25rem}.modal-scrollbar-measure{position:absolute;top:-9999px;width:50px;height:50px;overflow:scroll}@media (min-width:576px){.modal-dialog{max-width:500px;margin:1.75rem auto}.modal-dialog-centered{min-height:calc(100% - (1.75rem * 2))}.modal-sm{max-width:300px}}@media (min-width:992px){.modal-lg{max-width:800px}}.tooltip{position:absolute;z-index:1070;display:block;margin:0;font-family:-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,"Helvetica Neue",Arial,sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol";font-style:normal;font-weight:400;line-height:1.5;text-align:left;text-align:start;text-decoration:none;text-shadow:none;text-transform:none;letter-spacing:normal;word-break:normal;word-spacing:normal;white-space:normal;line-break:auto;font-size:.875rem;word-wrap:break-word;opacity:0}.tooltip.show{opacity:.9}.tooltip .arrow{position:absolute;display:block;width:.8rem;height:.4rem}.tooltip .arrow::before{position:absolute;content:"";border-color:transparent;border-style:solid}.bs-tooltip-auto[x-placement^=top],.bs-tooltip-top{padding:.4rem 0}.bs-tooltip-auto[x-placement^=top] .arrow,.bs-tooltip-top .arrow{bottom:0}.bs-tooltip-auto[x-placement^=top] .arrow::before,.bs-tooltip-top .arrow::before{top:0;border-width:.4rem .4rem 0;border-top-color:#000}.bs-tooltip-auto[x-placement^=right],.bs-tooltip-right{padding:0 .4rem}.bs-tooltip-auto[x-placement^=right] .arrow,.bs-tooltip-right .arrow{left:0;width:.4rem;height:.8rem}.bs-tooltip-auto[x-placement^=right] .arrow::before,.bs-tooltip-right .arrow::before{right:0;border-width:.4rem .4rem .4rem 0;border-right-color:#000}.bs-tooltip-auto[x-placement^=bottom],.bs-tooltip-bottom{padding:.4rem 0}.bs-tooltip-auto[x-placement^=bottom] .arrow,.bs-tooltip-bottom .arrow{top:0}.bs-tooltip-auto[x-placement^=bottom] .arrow::before,.bs-tooltip-bottom .arrow::before{bottom:0;border-width:0 .4rem .4rem;border-bottom-color:#000}.bs-tooltip-auto[x-placement^=left],.bs-tooltip-left{padding:0 .4rem}.bs-tooltip-auto[x-placement^=left] .arrow,.bs-tooltip-left .arrow{right:0;width:.4rem;height:.8rem}.bs-tooltip-auto[x-placement^=left] .arrow::before,.bs-tooltip-left .arrow::before{left:0;border-width:.4rem 0 .4rem .4rem;border-left-color:#000}.tooltip-inner{max-width:200px;padding:.25rem .5rem;color:#fff;text-align:center;background-color:#000;border-radius:.25rem}.popover{position:absolute;top:0;left:0;z-index:1060;display:block;max-width:276px;font-family:-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,"Helvetica Neue",Arial,sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol";font-style:normal;font-weight:400;line-height:1.5;text-align:left;text-align:start;text-decoration:none;text-shadow:none;text-transform:none;letter-spacing:normal;word-break:normal;word-spacing:normal;white-space:normal;line-break:auto;font-size:.875rem;word-wrap:break-word;background-color:#fff;background-clip:padding-box;border:1px solid rgba(0,0,0,.2);border-radius:.3rem}.popover .arrow{position:absolute;display:block;width:1rem;height:.5rem;margin:0 .3rem}.popover .arrow::after,.popover .arrow::before{position:absolute;display:block;content:"";border-color:transparent;border-style:solid}.bs-popover-auto[x-placement^=top],.bs-popover-top{margin-bottom:.5rem}.bs-popover-auto[x-placement^=top] .arrow,.bs-popover-top .arrow{bottom:calc((.5rem + 1px) * -1)}.bs-popover-auto[x-placement^=top] .arrow::after,.bs-popover-auto[x-placement^=top] .arrow::before,.bs-popover-top .arrow::after,.bs-popover-top .arrow::before{border-width:.5rem .5rem 0}.bs-popover-auto[x-placement^=top] .arrow::before,.bs-popover-top .arrow::before{bottom:0;border-top-color:rgba(0,0,0,.25)}.bs-popover-auto[x-placement^=top] .arrow::after,.bs-popover-top .arrow::after{bottom:1px;border-top-color:#fff}.bs-popover-auto[x-placement^=right],.bs-popover-right{margin-left:.5rem}.bs-popover-auto[x-placement^=right] .arrow,.bs-popover-right .arrow{left:calc((.5rem + 1px) * -1);width:.5rem;height:1rem;margin:.3rem 0}.bs-popover-auto[x-placement^=right] .arrow::after,.bs-popover-auto[x-placement^=right] .arrow::before,.bs-popover-right .arrow::after,.bs-popover-right .arrow::before{border-width:.5rem .5rem .5rem 0}.bs-popover-auto[x-placement^=right] .arrow::before,.bs-popover-right .arrow::before{left:0;border-right-color:rgba(0,0,0,.25)}.bs-popover-auto[x-placement^=right] .arrow::after,.bs-popover-right .arrow::after{left:1px;border-right-color:#fff}.bs-popover-auto[x-placement^=bottom],.bs-popover-bottom{margin-top:.5rem}.bs-popover-auto[x-placement^=bottom] .arrow,.bs-popover-bottom .arrow{top:calc((.5rem + 1px) * -1)}.bs-popover-auto[x-placement^=bottom] .arrow::after,.bs-popover-auto[x-placement^=bottom] .arrow::before,.bs-popover-bottom .arrow::after,.bs-popover-bottom .arrow::before{border-width:0 .5rem .5rem .5rem}.bs-popover-auto[x-placement^=bottom] .arrow::before,.bs-popover-bottom .arrow::before{top:0;border-bottom-color:rgba(0,0,0,.25)}.bs-popover-auto[x-placement^=bottom] .arrow::after,.bs-popover-bottom .arrow::after{top:1px;border-bottom-color:#fff}.bs-popover-auto[x-placement^=bottom] .popover-header::before,.bs-popover-bottom .popover-header::before{position:absolute;top:0;left:50%;display:block;width:1rem;margin-left:-.5rem;content:"";border-bottom:1px solid #f7f7f7}.bs-popover-auto[x-placement^=left],.bs-popover-left{margin-right:.5rem}.bs-popover-auto[x-placement^=left] .arrow,.bs-popover-left .arrow{right:calc((.5rem + 1px) * -1);width:.5rem;height:1rem;margin:.3rem 0}.bs-popover-auto[x-placement^=left] .arrow::after,.bs-popover-auto[x-placement^=left] .arrow::before,.bs-popover-left .arrow::after,.bs-popover-left .arrow::before{border-width:.5rem 0 .5rem .5rem}.bs-popover-auto[x-placement^=left] .arrow::before,.bs-popover-left .arrow::before{right:0;border-left-color:rgba(0,0,0,.25)}.bs-popover-auto[x-placement^=left] .arrow::after,.bs-popover-left .arrow::after{right:1px;border-left-color:#fff}.popover-header{padding:.5rem .75rem;margin-bottom:0;font-size:1rem;color:inherit;background-color:#f7f7f7;border-bottom:1px solid #ebebeb;border-top-left-radius:calc(.3rem - 1px);border-top-right-radius:calc(.3rem - 1px)}.popover-header:empty{display:none}.popover-body{padding:.5rem .75rem;color:#212529}.carousel{position:relative}.carousel-inner{position:relative;width:100%;overflow:hidden}.carousel-item{position:relative;display:none;-webkit-box-align:center;-ms-flex-align:center;align-items:center;width:100%;transition:-webkit-transform .6s ease;transition:transform .6s ease;transition:transform .6s ease,-webkit-transform .6s ease;-webkit-backface-visibility:hidden;backface-visibility:hidden;-webkit-perspective:1000px;perspective:1000px}.carousel-item-next,.carousel-item-prev,.carousel-item.active{display:block}.carousel-item-next,.carousel-item-prev{position:absolute;top:0}.carousel-item-next.carousel-item-left,.carousel-item-prev.carousel-item-right{-webkit-transform:translateX(0);transform:translateX(0)}@supports ((-webkit-transform-style:preserve-3d) or (transform-style:preserve-3d)){.carousel-item-next.carousel-item-left,.carousel-item-prev.carousel-item-right{-webkit-transform:translate3d(0,0,0);transform:translate3d(0,0,0)}}.active.carousel-item-right,.carousel-item-next{-webkit-transform:translateX(100%);transform:translateX(100%)}@supports ((-webkit-transform-style:preserve-3d) or (transform-style:preserve-3d)){.active.carousel-item-right,.carousel-item-next{-webkit-transform:translate3d(100%,0,0);transform:translate3d(100%,0,0)}}.active.carousel-item-left,.carousel-item-prev{-webkit-transform:translateX(-100%);transform:translateX(-100%)}@supports ((-webkit-transform-style:preserve-3d) or (transform-style:preserve-3d)){.active.carousel-item-left,.carousel-item-prev{-webkit-transform:translate3d(-100%,0,0);transform:translate3d(-100%,0,0)}}.carousel-control-next,.carousel-control-prev{position:absolute;top:0;bottom:0;display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-align:center;-ms-flex-align:center;align-items:center;-webkit-box-pack:center;-ms-flex-pack:center;justify-content:center;width:15%;color:#fff;text-align:center;opacity:.5}.carousel-control-next:focus,.carousel-control-next:hover,.carousel-control-prev:focus,.carousel-control-prev:hover{color:#fff;text-decoration:none;outline:0;opacity:.9}.carousel-control-prev{left:0}.carousel-control-next{right:0}.carousel-control-next-icon,.carousel-control-prev-icon{display:inline-block;width:20px;height:20px;background:transparent no-repeat center center;background-size:100% 100%}.carousel-control-prev-icon{background-image:url("data:image/svg+xml;charset=utf8,%3Csvg xmlns='http://www.w3.org/2000/svg' fill='%23fff' viewBox='0 0 8 8'%3E%3Cpath d='M5.25 0l-4 4 4 4 1.5-1.5-2.5-2.5 2.5-2.5-1.5-1.5z'/%3E%3C/svg%3E")}.carousel-control-next-icon{background-image:url("data:image/svg+xml;charset=utf8,%3Csvg xmlns='http://www.w3.org/2000/svg' fill='%23fff' viewBox='0 0 8 8'%3E%3Cpath d='M2.75 0l-1.5 1.5 2.5 2.5-2.5 2.5 1.5 1.5 4-4-4-4z'/%3E%3C/svg%3E")}.carousel-indicators{position:absolute;right:0;bottom:10px;left:0;z-index:15;display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-pack:center;-ms-flex-pack:center;justify-content:center;padding-left:0;margin-right:15%;margin-left:15%;list-style:none}.carousel-indicators li{position:relative;-webkit-box-flex:0;-ms-flex:0 1 auto;flex:0 1 auto;width:30px;height:3px;margin-right:3px;margin-left:3px;text-indent:-999px;background-color:rgba(255,255,255,.5)}.carousel-indicators li::before{position:absolute;top:-10px;left:0;display:inline-block;width:100%;height:10px;content:""}.carousel-indicators li::after{position:absolute;bottom:-10px;left:0;display:inline-block;width:100%;height:10px;content:""}.carousel-indicators .active{background-color:#fff}.carousel-caption{position:absolute;right:15%;bottom:20px;left:15%;z-index:10;padding-top:20px;padding-bottom:20px;color:#fff;text-align:center}.align-baseline{vertical-align:baseline!important}.align-top{vertical-align:top!important}.align-middle{vertical-align:middle!important}.align-bottom{vertical-align:bottom!important}.align-text-bottom{vertical-align:text-bottom!important}.align-text-top{vertical-align:text-top!important}.bg-primary{background-color:#007bff!important}a.bg-primary:focus,a.bg-primary:hover,button.bg-primary:focus,button.bg-primary:hover{background-color:#0062cc!important}.bg-secondary{background-color:#6c757d!important}a.bg-secondary:focus,a.bg-secondary:hover,button.bg-secondary:focus,button.bg-secondary:hover{background-color:#545b62!important}.bg-success{background-color:#28a745!important}a.bg-success:focus,a.bg-success:hover,button.bg-success:focus,button.bg-success:hover{background-color:#1e7e34!important}.bg-info{background-color:#17a2b8!important}a.bg-info:focus,a.bg-info:hover,button.bg-info:focus,button.bg-info:hover{background-color:#117a8b!important}.bg-warning{background-color:#ffc107!important}a.bg-warning:focus,a.bg-warning:hover,button.bg-warning:focus,button.bg-warning:hover{background-color:#d39e00!important}.bg-danger{background-color:#dc3545!important}a.bg-danger:focus,a.bg-danger:hover,button.bg-danger:focus,button.bg-danger:hover{background-color:#bd2130!important}.bg-light{background-color:#f8f9fa!important}a.bg-light:focus,a.bg-light:hover,button.bg-light:focus,button.bg-light:hover{background-color:#dae0e5!important}.bg-dark{background-color:#343a40!important}a.bg-dark:focus,a.bg-dark:hover,button.bg-dark:focus,button.bg-dark:hover{background-color:#1d2124!important}.bg-white{background-color:#fff!important}.bg-transparent{background-color:transparent!important}.border{border:1px solid #dee2e6!important}.border-top{border-top:1px solid #dee2e6!important}.border-right{border-right:1px solid #dee2e6!important}.border-bottom{border-bottom:1px solid #dee2e6!important}.border-left{border-left:1px solid #dee2e6!important}.border-0{border:0!important}.border-top-0{border-top:0!important}.border-right-0{border-right:0!important}.border-bottom-0{border-bottom:0!important}.border-left-0{border-left:0!important}.border-primary{border-color:#007bff!important}.border-secondary{border-color:#6c757d!important}.border-success{border-color:#28a745!important}.border-info{border-color:#17a2b8!important}.border-warning{border-color:#ffc107!important}.border-danger{border-color:#dc3545!important}.border-light{border-color:#f8f9fa!important}.border-dark{border-color:#343a40!important}.border-white{border-color:#fff!important}.rounded{border-radius:.25rem!important}.rounded-top{border-top-left-radius:.25rem!important;border-top-right-radius:.25rem!important}.rounded-right{border-top-right-radius:.25rem!important;border-bottom-right-radius:.25rem!important}.rounded-bottom{border-bottom-right-radius:.25rem!important;border-bottom-left-radius:.25rem!important}.rounded-left{border-top-left-radius:.25rem!important;border-bottom-left-radius:.25rem!important}.rounded-circle{border-radius:50%!important}.rounded-0{border-radius:0!important}.clearfix::after{display:block;clear:both;content:""}.d-none{display:none!important}.d-inline{display:inline!important}.d-inline-block{display:inline-block!important}.d-block{display:block!important}.d-table{display:table!important}.d-table-row{display:table-row!important}.d-table-cell{display:table-cell!important}.d-flex{display:-webkit-box!important;display:-ms-flexbox!important;display:flex!important}.d-inline-flex{display:-webkit-inline-box!important;display:-ms-inline-flexbox!important;display:inline-flex!important}@media (min-width:576px){.d-sm-none{display:none!important}.d-sm-inline{display:inline!important}.d-sm-inline-block{display:inline-block!important}.d-sm-block{display:block!important}.d-sm-table{display:table!important}.d-sm-table-row{display:table-row!important}.d-sm-table-cell{display:table-cell!important}.d-sm-flex{display:-webkit-box!important;display:-ms-flexbox!important;display:flex!important}.d-sm-inline-flex{display:-webkit-inline-box!important;display:-ms-inline-flexbox!important;display:inline-flex!important}}@media (min-width:768px){.d-md-none{display:none!important}.d-md-inline{display:inline!important}.d-md-inline-block{display:inline-block!important}.d-md-block{display:block!important}.d-md-table{display:table!important}.d-md-table-row{display:table-row!important}.d-md-table-cell{display:table-cell!important}.d-md-flex{display:-webkit-box!important;display:-ms-flexbox!important;display:flex!important}.d-md-inline-flex{display:-webkit-inline-box!important;display:-ms-inline-flexbox!important;display:inline-flex!important}}@media (min-width:992px){.d-lg-none{display:none!important}.d-lg-inline{display:inline!important}.d-lg-inline-block{display:inline-block!important}.d-lg-block{display:block!important}.d-lg-table{display:table!important}.d-lg-table-row{display:table-row!important}.d-lg-table-cell{display:table-cell!important}.d-lg-flex{display:-webkit-box!important;display:-ms-flexbox!important;display:flex!important}.d-lg-inline-flex{display:-webkit-inline-box!important;display:-ms-inline-flexbox!important;display:inline-flex!important}}@media (min-width:1200px){.d-xl-none{display:none!important}.d-xl-inline{display:inline!important}.d-xl-inline-block{display:inline-block!important}.d-xl-block{display:block!important}.d-xl-table{display:table!important}.d-xl-table-row{display:table-row!important}.d-xl-table-cell{display:table-cell!important}.d-xl-flex{display:-webkit-box!important;display:-ms-flexbox!important;display:flex!important}.d-xl-inline-flex{display:-webkit-inline-box!important;display:-ms-inline-flexbox!important;display:inline-flex!important}}@media print{.d-print-none{display:none!important}.d-print-inline{display:inline!important}.d-print-inline-block{display:inline-block!important}.d-print-block{display:block!important}.d-print-table{display:table!important}.d-print-table-row{display:table-row!important}.d-print-table-cell{display:table-cell!important}.d-print-flex{display:-webkit-box!important;display:-ms-flexbox!important;display:flex!important}.d-print-inline-flex{display:-webkit-inline-box!important;display:-ms-inline-flexbox!important;display:inline-flex!important}}.embed-responsive{position:relative;display:block;width:100%;padding:0;overflow:hidden}.embed-responsive::before{display:block;content:""}.embed-responsive .embed-responsive-item,.embed-responsive embed,.embed-responsive iframe,.embed-responsive object,.embed-responsive video{position:absolute;top:0;bottom:0;left:0;width:100%;height:100%;border:0}.embed-responsive-21by9::before{padding-top:42.857143%}.embed-responsive-16by9::before{padding-top:56.25%}.embed-responsive-4by3::before{padding-top:75%}.embed-responsive-1by1::before{padding-top:100%}.flex-row{-webkit-box-orient:horizontal!important;-webkit-box-direction:normal!important;-ms-flex-direction:row!important;flex-direction:row!important}.flex-column{-webkit-box-orient:vertical!important;-webkit-box-direction:normal!important;-ms-flex-direction:column!important;flex-direction:column!important}.flex-row-reverse{-webkit-box-orient:horizontal!important;-webkit-box-direction:reverse!important;-ms-flex-direction:row-reverse!important;flex-direction:row-reverse!important}.flex-column-reverse{-webkit-box-orient:vertical!important;-webkit-box-direction:reverse!important;-ms-flex-direction:column-reverse!important;flex-direction:column-reverse!important}.flex-wrap{-ms-flex-wrap:wrap!important;flex-wrap:wrap!important}.flex-nowrap{-ms-flex-wrap:nowrap!important;flex-wrap:nowrap!important}.flex-wrap-reverse{-ms-flex-wrap:wrap-reverse!important;flex-wrap:wrap-reverse!important}.justify-content-start{-webkit-box-pack:start!important;-ms-flex-pack:start!important;justify-content:flex-start!important}.justify-content-end{-webkit-box-pack:end!important;-ms-flex-pack:end!important;justify-content:flex-end!important}.justify-content-center{-webkit-box-pack:center!important;-ms-flex-pack:center!important;justify-content:center!important}.justify-content-between{-webkit-box-pack:justify!important;-ms-flex-pack:justify!important;justify-content:space-between!important}.justify-content-around{-ms-flex-pack:distribute!important;justify-content:space-around!important}.align-items-start{-webkit-box-align:start!important;-ms-flex-align:start!important;align-items:flex-start!important}.align-items-end{-webkit-box-align:end!important;-ms-flex-align:end!important;align-items:flex-end!important}.align-items-center{-webkit-box-align:center!important;-ms-flex-align:center!important;align-items:center!important}.align-items-baseline{-webkit-box-align:baseline!important;-ms-flex-align:baseline!important;align-items:baseline!important}.align-items-stretch{-webkit-box-align:stretch!important;-ms-flex-align:stretch!important;align-items:stretch!important}.align-content-start{-ms-flex-line-pack:start!important;align-content:flex-start!important}.align-content-end{-ms-flex-line-pack:end!important;align-content:flex-end!important}.align-content-center{-ms-flex-line-pack:center!important;align-content:center!important}.align-content-between{-ms-flex-line-pack:justify!important;align-content:space-between!important}.align-content-around{-ms-flex-line-pack:distribute!important;align-content:space-around!important}.align-content-stretch{-ms-flex-line-pack:stretch!important;align-content:stretch!important}.align-self-auto{-ms-flex-item-align:auto!important;align-self:auto!important}.align-self-start{-ms-flex-item-align:start!important;align-self:flex-start!important}.align-self-end{-ms-flex-item-align:end!important;align-self:flex-end!important}.align-self-center{-ms-flex-item-align:center!important;align-self:center!important}.align-self-baseline{-ms-flex-item-align:baseline!important;align-self:baseline!important}.align-self-stretch{-ms-flex-item-align:stretch!important;align-self:stretch!important}@media (min-width:576px){.flex-sm-row{-webkit-box-orient:horizontal!important;-webkit-box-direction:normal!important;-ms-flex-direction:row!important;flex-direction:row!important}.flex-sm-column{-webkit-box-orient:vertical!important;-webkit-box-direction:normal!important;-ms-flex-direction:column!important;flex-direction:column!important}.flex-sm-row-reverse{-webkit-box-orient:horizontal!important;-webkit-box-direction:reverse!important;-ms-flex-direction:row-reverse!important;flex-direction:row-reverse!important}.flex-sm-column-reverse{-webkit-box-orient:vertical!important;-webkit-box-direction:reverse!important;-ms-flex-direction:column-reverse!important;flex-direction:column-reverse!important}.flex-sm-wrap{-ms-flex-wrap:wrap!important;flex-wrap:wrap!important}.flex-sm-nowrap{-ms-flex-wrap:nowrap!important;flex-wrap:nowrap!important}.flex-sm-wrap-reverse{-ms-flex-wrap:wrap-reverse!important;flex-wrap:wrap-reverse!important}.justify-content-sm-start{-webkit-box-pack:start!important;-ms-flex-pack:start!important;justify-content:flex-start!important}.justify-content-sm-end{-webkit-box-pack:end!important;-ms-flex-pack:end!important;justify-content:flex-end!important}.justify-content-sm-center{-webkit-box-pack:center!important;-ms-flex-pack:center!important;justify-content:center!important}.justify-content-sm-between{-webkit-box-pack:justify!important;-ms-flex-pack:justify!important;justify-content:space-between!important}.justify-content-sm-around{-ms-flex-pack:distribute!important;justify-content:space-around!important}.align-items-sm-start{-webkit-box-align:start!important;-ms-flex-align:start!important;align-items:flex-start!important}.align-items-sm-end{-webkit-box-align:end!important;-ms-flex-align:end!important;align-items:flex-end!important}.align-items-sm-center{-webkit-box-align:center!important;-ms-flex-align:center!important;align-items:center!important}.align-items-sm-baseline{-webkit-box-align:baseline!important;-ms-flex-align:baseline!important;align-items:baseline!important}.align-items-sm-stretch{-webkit-box-align:stretch!important;-ms-flex-align:stretch!important;align-items:stretch!important}.align-content-sm-start{-ms-flex-line-pack:start!important;align-content:flex-start!important}.align-content-sm-end{-ms-flex-line-pack:end!important;align-content:flex-end!important}.align-content-sm-center{-ms-flex-line-pack:center!important;align-content:center!important}.align-content-sm-between{-ms-flex-line-pack:justify!important;align-content:space-between!important}.align-content-sm-around{-ms-flex-line-pack:distribute!important;align-content:space-around!important}.align-content-sm-stretch{-ms-flex-line-pack:stretch!important;align-content:stretch!important}.align-self-sm-auto{-ms-flex-item-align:auto!important;align-self:auto!important}.align-self-sm-start{-ms-flex-item-align:start!important;align-self:flex-start!important}.align-self-sm-end{-ms-flex-item-align:end!important;align-self:flex-end!important}.align-self-sm-center{-ms-flex-item-align:center!important;align-self:center!important}.align-self-sm-baseline{-ms-flex-item-align:baseline!important;align-self:baseline!important}.align-self-sm-stretch{-ms-flex-item-align:stretch!important;align-self:stretch!important}}@media (min-width:768px){.flex-md-row{-webkit-box-orient:horizontal!important;-webkit-box-direction:normal!important;-ms-flex-direction:row!important;flex-direction:row!important}.flex-md-column{-webkit-box-orient:vertical!important;-webkit-box-direction:normal!important;-ms-flex-direction:column!important;flex-direction:column!important}.flex-md-row-reverse{-webkit-box-orient:horizontal!important;-webkit-box-direction:reverse!important;-ms-flex-direction:row-reverse!important;flex-direction:row-reverse!important}.flex-md-column-reverse{-webkit-box-orient:vertical!important;-webkit-box-direction:reverse!important;-ms-flex-direction:column-reverse!important;flex-direction:column-reverse!important}.flex-md-wrap{-ms-flex-wrap:wrap!important;flex-wrap:wrap!important}.flex-md-nowrap{-ms-flex-wrap:nowrap!important;flex-wrap:nowrap!important}.flex-md-wrap-reverse{-ms-flex-wrap:wrap-reverse!important;flex-wrap:wrap-reverse!important}.justify-content-md-start{-webkit-box-pack:start!important;-ms-flex-pack:start!important;justify-content:flex-start!important}.justify-content-md-end{-webkit-box-pack:end!important;-ms-flex-pack:end!important;justify-content:flex-end!important}.justify-content-md-center{-webkit-box-pack:center!important;-ms-flex-pack:center!important;justify-content:center!important}.justify-content-md-between{-webkit-box-pack:justify!important;-ms-flex-pack:justify!important;justify-content:space-between!important}.justify-content-md-around{-ms-flex-pack:distribute!important;justify-content:space-around!important}.align-items-md-start{-webkit-box-align:start!important;-ms-flex-align:start!important;align-items:flex-start!important}.align-items-md-end{-webkit-box-align:end!important;-ms-flex-align:end!important;align-items:flex-end!important}.align-items-md-center{-webkit-box-align:center!important;-ms-flex-align:center!important;align-items:center!important}.align-items-md-baseline{-webkit-box-align:baseline!important;-ms-flex-align:baseline!important;align-items:baseline!important}.align-items-md-stretch{-webkit-box-align:stretch!important;-ms-flex-align:stretch!important;align-items:stretch!important}.align-content-md-start{-ms-flex-line-pack:start!important;align-content:flex-start!important}.align-content-md-end{-ms-flex-line-pack:end!important;align-content:flex-end!important}.align-content-md-center{-ms-flex-line-pack:center!important;align-content:center!important}.align-content-md-between{-ms-flex-line-pack:justify!important;align-content:space-between!important}.align-content-md-around{-ms-flex-line-pack:distribute!important;align-content:space-around!important}.align-content-md-stretch{-ms-flex-line-pack:stretch!important;align-content:stretch!important}.align-self-md-auto{-ms-flex-item-align:auto!important;align-self:auto!important}.align-self-md-start{-ms-flex-item-align:start!important;align-self:flex-start!important}.align-self-md-end{-ms-flex-item-align:end!important;align-self:flex-end!important}.align-self-md-center{-ms-flex-item-align:center!important;align-self:center!important}.align-self-md-baseline{-ms-flex-item-align:baseline!important;align-self:baseline!important}.align-self-md-stretch{-ms-flex-item-align:stretch!important;align-self:stretch!important}}@media (min-width:992px){.flex-lg-row{-webkit-box-orient:horizontal!important;-webkit-box-direction:normal!important;-ms-flex-direction:row!important;flex-direction:row!important}.flex-lg-column{-webkit-box-orient:vertical!important;-webkit-box-direction:normal!important;-ms-flex-direction:column!important;flex-direction:column!important}.flex-lg-row-reverse{-webkit-box-orient:horizontal!important;-webkit-box-direction:reverse!important;-ms-flex-direction:row-reverse!important;flex-direction:row-reverse!important}.flex-lg-column-reverse{-webkit-box-orient:vertical!important;-webkit-box-direction:reverse!important;-ms-flex-direction:column-reverse!important;flex-direction:column-reverse!important}.flex-lg-wrap{-ms-flex-wrap:wrap!important;flex-wrap:wrap!important}.flex-lg-nowrap{-ms-flex-wrap:nowrap!important;flex-wrap:nowrap!important}.flex-lg-wrap-reverse{-ms-flex-wrap:wrap-reverse!important;flex-wrap:wrap-reverse!important}.justify-content-lg-start{-webkit-box-pack:start!important;-ms-flex-pack:start!important;justify-content:flex-start!important}.justify-content-lg-end{-webkit-box-pack:end!important;-ms-flex-pack:end!important;justify-content:flex-end!important}.justify-content-lg-center{-webkit-box-pack:center!important;-ms-flex-pack:center!important;justify-content:center!important}.justify-content-lg-between{-webkit-box-pack:justify!important;-ms-flex-pack:justify!important;justify-content:space-between!important}.justify-content-lg-around{-ms-flex-pack:distribute!important;justify-content:space-around!important}.align-items-lg-start{-webkit-box-align:start!important;-ms-flex-align:start!important;align-items:flex-start!important}.align-items-lg-end{-webkit-box-align:end!important;-ms-flex-align:end!important;align-items:flex-end!important}.align-items-lg-center{-webkit-box-align:center!important;-ms-flex-align:center!important;align-items:center!important}.align-items-lg-baseline{-webkit-box-align:baseline!important;-ms-flex-align:baseline!important;align-items:baseline!important}.align-items-lg-stretch{-webkit-box-align:stretch!important;-ms-flex-align:stretch!important;align-items:stretch!important}.align-content-lg-start{-ms-flex-line-pack:start!important;align-content:flex-start!important}.align-content-lg-end{-ms-flex-line-pack:end!important;align-content:flex-end!important}.align-content-lg-center{-ms-flex-line-pack:center!important;align-content:center!important}.align-content-lg-between{-ms-flex-line-pack:justify!important;align-content:space-between!important}.align-content-lg-around{-ms-flex-line-pack:distribute!important;align-content:space-around!important}.align-content-lg-stretch{-ms-flex-line-pack:stretch!important;align-content:stretch!important}.align-self-lg-auto{-ms-flex-item-align:auto!important;align-self:auto!important}.align-self-lg-start{-ms-flex-item-align:start!important;align-self:flex-start!important}.align-self-lg-end{-ms-flex-item-align:end!important;align-self:flex-end!important}.align-self-lg-center{-ms-flex-item-align:center!important;align-self:center!important}.align-self-lg-baseline{-ms-flex-item-align:baseline!important;align-self:baseline!important}.align-self-lg-stretch{-ms-flex-item-align:stretch!important;align-self:stretch!important}}@media (min-width:1200px){.flex-xl-row{-webkit-box-orient:horizontal!important;-webkit-box-direction:normal!important;-ms-flex-direction:row!important;flex-direction:row!important}.flex-xl-column{-webkit-box-orient:vertical!important;-webkit-box-direction:normal!important;-ms-flex-direction:column!important;flex-direction:column!important}.flex-xl-row-reverse{-webkit-box-orient:horizontal!important;-webkit-box-direction:reverse!important;-ms-flex-direction:row-reverse!important;flex-direction:row-reverse!important}.flex-xl-column-reverse{-webkit-box-orient:vertical!important;-webkit-box-direction:reverse!important;-ms-flex-direction:column-reverse!important;flex-direction:column-reverse!important}.flex-xl-wrap{-ms-flex-wrap:wrap!important;flex-wrap:wrap!important}.flex-xl-nowrap{-ms-flex-wrap:nowrap!important;flex-wrap:nowrap!important}.flex-xl-wrap-reverse{-ms-flex-wrap:wrap-reverse!important;flex-wrap:wrap-reverse!important}.justify-content-xl-start{-webkit-box-pack:start!important;-ms-flex-pack:start!important;justify-content:flex-start!important}.justify-content-xl-end{-webkit-box-pack:end!important;-ms-flex-pack:end!important;justify-content:flex-end!important}.justify-content-xl-center{-webkit-box-pack:center!important;-ms-flex-pack:center!important;justify-content:center!important}.justify-content-xl-between{-webkit-box-pack:justify!important;-ms-flex-pack:justify!important;justify-content:space-between!important}.justify-content-xl-around{-ms-flex-pack:distribute!important;justify-content:space-around!important}.align-items-xl-start{-webkit-box-align:start!important;-ms-flex-align:start!important;align-items:flex-start!important}.align-items-xl-end{-webkit-box-align:end!important;-ms-flex-align:end!important;align-items:flex-end!important}.align-items-xl-center{-webkit-box-align:center!important;-ms-flex-align:center!important;align-items:center!important}.align-items-xl-baseline{-webkit-box-align:baseline!important;-ms-flex-align:baseline!important;align-items:baseline!important}.align-items-xl-stretch{-webkit-box-align:stretch!important;-ms-flex-align:stretch!important;align-items:stretch!important}.align-content-xl-start{-ms-flex-line-pack:start!important;align-content:flex-start!important}.align-content-xl-end{-ms-flex-line-pack:end!important;align-content:flex-end!important}.align-content-xl-center{-ms-flex-line-pack:center!important;align-content:center!important}.align-content-xl-between{-ms-flex-line-pack:justify!important;align-content:space-between!important}.align-content-xl-around{-ms-flex-line-pack:distribute!important;align-content:space-around!important}.align-content-xl-stretch{-ms-flex-line-pack:stretch!important;align-content:stretch!important}.align-self-xl-auto{-ms-flex-item-align:auto!important;align-self:auto!important}.align-self-xl-start{-ms-flex-item-align:start!important;align-self:flex-start!important}.align-self-xl-end{-ms-flex-item-align:end!important;align-self:flex-end!important}.align-self-xl-center{-ms-flex-item-align:center!important;align-self:center!important}.align-self-xl-baseline{-ms-flex-item-align:baseline!important;align-self:baseline!important}.align-self-xl-stretch{-ms-flex-item-align:stretch!important;align-self:stretch!important}}.float-left{float:left!important}.float-right{float:right!important}.float-none{float:none!important}@media (min-width:576px){.float-sm-left{float:left!important}.float-sm-right{float:right!important}.float-sm-none{float:none!important}}@media (min-width:768px){.float-md-left{float:left!important}.float-md-right{float:right!important}.float-md-none{float:none!important}}@media (min-width:992px){.float-lg-left{float:left!important}.float-lg-right{float:right!important}.float-lg-none{float:none!important}}@media (min-width:1200px){.float-xl-left{float:left!important}.float-xl-right{float:right!important}.float-xl-none{float:none!important}}.position-static{position:static!important}.position-relative{position:relative!important}.position-absolute{position:absolute!important}.position-fixed{position:fixed!important}.position-sticky{position:-webkit-sticky!important;position:sticky!important}.fixed-top{position:fixed;top:0;right:0;left:0;z-index:1030}.fixed-bottom{position:fixed;right:0;bottom:0;left:0;z-index:1030}@supports ((position:-webkit-sticky) or (position:sticky)){.sticky-top{position:-webkit-sticky;position:sticky;top:0;z-index:1020}}.sr-only{position:absolute;width:1px;height:1px;padding:0;overflow:hidden;clip:rect(0,0,0,0);white-space:nowrap;-webkit-clip-path:inset(50%);clip-path:inset(50%);border:0}.sr-only-focusable:active,.sr-only-focusable:focus{position:static;width:auto;height:auto;overflow:visible;clip:auto;white-space:normal;-webkit-clip-path:none;clip-path:none}.w-25{width:25%!important}.w-50{width:50%!important}.w-75{width:75%!important}.w-100{width:100%!important}.h-25{height:25%!important}.h-50{height:50%!important}.h-75{height:75%!important}.h-100{height:100%!important}.mw-100{max-width:100%!important}.mh-100{max-height:100%!important}.m-0{margin:0!important}.mt-0,.my-0{margin-top:0!important}.mr-0,.mx-0{margin-right:0!important}.mb-0,.my-0{margin-bottom:0!important}.ml-0,.mx-0{margin-left:0!important}.m-1{margin:.25rem!important}.mt-1,.my-1{margin-top:.25rem!important}.mr-1,.mx-1{margin-right:.25rem!important}.mb-1,.my-1{margin-bottom:.25rem!important}.ml-1,.mx-1{margin-left:.25rem!important}.m-2{margin:.5rem!important}.mt-2,.my-2{margin-top:.5rem!important}.mr-2,.mx-2{margin-right:.5rem!important}.mb-2,.my-2{margin-bottom:.5rem!important}.ml-2,.mx-2{margin-left:.5rem!important}.m-3{margin:1rem!important}.mt-3,.my-3{margin-top:1rem!important}.mr-3,.mx-3{margin-right:1rem!important}.mb-3,.my-3{margin-bottom:1rem!important}.ml-3,.mx-3{margin-left:1rem!important}.m-4{margin:1.5rem!important}.mt-4,.my-4{margin-top:1.5rem!important}.mr-4,.mx-4{margin-right:1.5rem!important}.mb-4,.my-4{margin-bottom:1.5rem!important}.ml-4,.mx-4{margin-left:1.5rem!important}.m-5{margin:3rem!important}.mt-5,.my-5{margin-top:3rem!important}.mr-5,.mx-5{margin-right:3rem!important}.mb-5,.my-5{margin-bottom:3rem!important}.ml-5,.mx-5{margin-left:3rem!important}.p-0{padding:0!important}.pt-0,.py-0{padding-top:0!important}.pr-0,.px-0{padding-right:0!important}.pb-0,.py-0{padding-bottom:0!important}.pl-0,.px-0{padding-left:0!important}.p-1{padding:.25rem!important}.pt-1,.py-1{padding-top:.25rem!important}.pr-1,.px-1{padding-right:.25rem!important}.pb-1,.py-1{padding-bottom:.25rem!important}.pl-1,.px-1{padding-left:.25rem!important}.p-2{padding:.5rem!important}.pt-2,.py-2{padding-top:.5rem!important}.pr-2,.px-2{padding-right:.5rem!important}.pb-2,.py-2{padding-bottom:.5rem!important}.pl-2,.px-2{padding-left:.5rem!important}.p-3{padding:1rem!important}.pt-3,.py-3{padding-top:1rem!important}.pr-3,.px-3{padding-right:1rem!important}.pb-3,.py-3{padding-bottom:1rem!important}.pl-3,.px-3{padding-left:1rem!important}.p-4{padding:1.5rem!important}.pt-4,.py-4{padding-top:1.5rem!important}.pr-4,.px-4{padding-right:1.5rem!important}.pb-4,.py-4{padding-bottom:1.5rem!important}.pl-4,.px-4{padding-left:1.5rem!important}.p-5{padding:3rem!important}.pt-5,.py-5{padding-top:3rem!important}.pr-5,.px-5{padding-right:3rem!important}.pb-5,.py-5{padding-bottom:3rem!important}.pl-5,.px-5{padding-left:3rem!important}.m-auto{margin:auto!important}.mt-auto,.my-auto{margin-top:auto!important}.mr-auto,.mx-auto{margin-right:auto!important}.mb-auto,.my-auto{margin-bottom:auto!important}.ml-auto,.mx-auto{margin-left:auto!important}@media (min-width:576px){.m-sm-0{margin:0!important}.mt-sm-0,.my-sm-0{margin-top:0!important}.mr-sm-0,.mx-sm-0{margin-right:0!important}.mb-sm-0,.my-sm-0{margin-bottom:0!important}.ml-sm-0,.mx-sm-0{margin-left:0!important}.m-sm-1{margin:.25rem!important}.mt-sm-1,.my-sm-1{margin-top:.25rem!important}.mr-sm-1,.mx-sm-1{margin-right:.25rem!important}.mb-sm-1,.my-sm-1{margin-bottom:.25rem!important}.ml-sm-1,.mx-sm-1{margin-left:.25rem!important}.m-sm-2{margin:.5rem!important}.mt-sm-2,.my-sm-2{margin-top:.5rem!important}.mr-sm-2,.mx-sm-2{margin-right:.5rem!important}.mb-sm-2,.my-sm-2{margin-bottom:.5rem!important}.ml-sm-2,.mx-sm-2{margin-left:.5rem!important}.m-sm-3{margin:1rem!important}.mt-sm-3,.my-sm-3{margin-top:1rem!important}.mr-sm-3,.mx-sm-3{margin-right:1rem!important}.mb-sm-3,.my-sm-3{margin-bottom:1rem!important}.ml-sm-3,.mx-sm-3{margin-left:1rem!important}.m-sm-4{margin:1.5rem!important}.mt-sm-4,.my-sm-4{margin-top:1.5rem!important}.mr-sm-4,.mx-sm-4{margin-right:1.5rem!important}.mb-sm-4,.my-sm-4{margin-bottom:1.5rem!important}.ml-sm-4,.mx-sm-4{margin-left:1.5rem!important}.m-sm-5{margin:3rem!important}.mt-sm-5,.my-sm-5{margin-top:3rem!important}.mr-sm-5,.mx-sm-5{margin-right:3rem!important}.mb-sm-5,.my-sm-5{margin-bottom:3rem!important}.ml-sm-5,.mx-sm-5{margin-left:3rem!important}.p-sm-0{padding:0!important}.pt-sm-0,.py-sm-0{padding-top:0!important}.pr-sm-0,.px-sm-0{padding-right:0!important}.pb-sm-0,.py-sm-0{padding-bottom:0!important}.pl-sm-0,.px-sm-0{padding-left:0!important}.p-sm-1{padding:.25rem!important}.pt-sm-1,.py-sm-1{padding-top:.25rem!important}.pr-sm-1,.px-sm-1{padding-right:.25rem!important}.pb-sm-1,.py-sm-1{padding-bottom:.25rem!important}.pl-sm-1,.px-sm-1{padding-left:.25rem!important}.p-sm-2{padding:.5rem!important}.pt-sm-2,.py-sm-2{padding-top:.5rem!important}.pr-sm-2,.px-sm-2{padding-right:.5rem!important}.pb-sm-2,.py-sm-2{padding-bottom:.5rem!important}.pl-sm-2,.px-sm-2{padding-left:.5rem!important}.p-sm-3{padding:1rem!important}.pt-sm-3,.py-sm-3{padding-top:1rem!important}.pr-sm-3,.px-sm-3{padding-right:1rem!important}.pb-sm-3,.py-sm-3{padding-bottom:1rem!important}.pl-sm-3,.px-sm-3{padding-left:1rem!important}.p-sm-4{padding:1.5rem!important}.pt-sm-4,.py-sm-4{padding-top:1.5rem!important}.pr-sm-4,.px-sm-4{padding-right:1.5rem!important}.pb-sm-4,.py-sm-4{padding-bottom:1.5rem!important}.pl-sm-4,.px-sm-4{padding-left:1.5rem!important}.p-sm-5{padding:3rem!important}.pt-sm-5,.py-sm-5{padding-top:3rem!important}.pr-sm-5,.px-sm-5{padding-right:3rem!important}.pb-sm-5,.py-sm-5{padding-bottom:3rem!important}.pl-sm-5,.px-sm-5{padding-left:3rem!important}.m-sm-auto{margin:auto!important}.mt-sm-auto,.my-sm-auto{margin-top:auto!important}.mr-sm-auto,.mx-sm-auto{margin-right:auto!important}.mb-sm-auto,.my-sm-auto{margin-bottom:auto!important}.ml-sm-auto,.mx-sm-auto{margin-left:auto!important}}@media (min-width:768px){.m-md-0{margin:0!important}.mt-md-0,.my-md-0{margin-top:0!important}.mr-md-0,.mx-md-0{margin-right:0!important}.mb-md-0,.my-md-0{margin-bottom:0!important}.ml-md-0,.mx-md-0{margin-left:0!important}.m-md-1{margin:.25rem!important}.mt-md-1,.my-md-1{margin-top:.25rem!important}.mr-md-1,.mx-md-1{margin-right:.25rem!important}.mb-md-1,.my-md-1{margin-bottom:.25rem!important}.ml-md-1,.mx-md-1{margin-left:.25rem!important}.m-md-2{margin:.5rem!important}.mt-md-2,.my-md-2{margin-top:.5rem!important}.mr-md-2,.mx-md-2{margin-right:.5rem!important}.mb-md-2,.my-md-2{margin-bottom:.5rem!important}.ml-md-2,.mx-md-2{margin-left:.5rem!important}.m-md-3{margin:1rem!important}.mt-md-3,.my-md-3{margin-top:1rem!important}.mr-md-3,.mx-md-3{margin-right:1rem!important}.mb-md-3,.my-md-3{margin-bottom:1rem!important}.ml-md-3,.mx-md-3{margin-left:1rem!important}.m-md-4{margin:1.5rem!important}.mt-md-4,.my-md-4{margin-top:1.5rem!important}.mr-md-4,.mx-md-4{margin-right:1.5rem!important}.mb-md-4,.my-md-4{margin-bottom:1.5rem!important}.ml-md-4,.mx-md-4{margin-left:1.5rem!important}.m-md-5{margin:3rem!important}.mt-md-5,.my-md-5{margin-top:3rem!important}.mr-md-5,.mx-md-5{margin-right:3rem!important}.mb-md-5,.my-md-5{margin-bottom:3rem!important}.ml-md-5,.mx-md-5{margin-left:3rem!important}.p-md-0{padding:0!important}.pt-md-0,.py-md-0{padding-top:0!important}.pr-md-0,.px-md-0{padding-right:0!important}.pb-md-0,.py-md-0{padding-bottom:0!important}.pl-md-0,.px-md-0{padding-left:0!important}.p-md-1{padding:.25rem!important}.pt-md-1,.py-md-1{padding-top:.25rem!important}.pr-md-1,.px-md-1{padding-right:.25rem!important}.pb-md-1,.py-md-1{padding-bottom:.25rem!important}.pl-md-1,.px-md-1{padding-left:.25rem!important}.p-md-2{padding:.5rem!important}.pt-md-2,.py-md-2{padding-top:.5rem!important}.pr-md-2,.px-md-2{padding-right:.5rem!important}.pb-md-2,.py-md-2{padding-bottom:.5rem!important}.pl-md-2,.px-md-2{padding-left:.5rem!important}.p-md-3{padding:1rem!important}.pt-md-3,.py-md-3{padding-top:1rem!important}.pr-md-3,.px-md-3{padding-right:1rem!important}.pb-md-3,.py-md-3{padding-bottom:1rem!important}.pl-md-3,.px-md-3{padding-left:1rem!important}.p-md-4{padding:1.5rem!important}.pt-md-4,.py-md-4{padding-top:1.5rem!important}.pr-md-4,.px-md-4{padding-right:1.5rem!important}.pb-md-4,.py-md-4{padding-bottom:1.5rem!important}.pl-md-4,.px-md-4{padding-left:1.5rem!important}.p-md-5{padding:3rem!important}.pt-md-5,.py-md-5{padding-top:3rem!important}.pr-md-5,.px-md-5{padding-right:3rem!important}.pb-md-5,.py-md-5{padding-bottom:3rem!important}.pl-md-5,.px-md-5{padding-left:3rem!important}.m-md-auto{margin:auto!important}.mt-md-auto,.my-md-auto{margin-top:auto!important}.mr-md-auto,.mx-md-auto{margin-right:auto!important}.mb-md-auto,.my-md-auto{margin-bottom:auto!important}.ml-md-auto,.mx-md-auto{margin-left:auto!important}}@media (min-width:992px){.m-lg-0{margin:0!important}.mt-lg-0,.my-lg-0{margin-top:0!important}.mr-lg-0,.mx-lg-0{margin-right:0!important}.mb-lg-0,.my-lg-0{margin-bottom:0!important}.ml-lg-0,.mx-lg-0{margin-left:0!important}.m-lg-1{margin:.25rem!important}.mt-lg-1,.my-lg-1{margin-top:.25rem!important}.mr-lg-1,.mx-lg-1{margin-right:.25rem!important}.mb-lg-1,.my-lg-1{margin-bottom:.25rem!important}.ml-lg-1,.mx-lg-1{margin-left:.25rem!important}.m-lg-2{margin:.5rem!important}.mt-lg-2,.my-lg-2{margin-top:.5rem!important}.mr-lg-2,.mx-lg-2{margin-right:.5rem!important}.mb-lg-2,.my-lg-2{margin-bottom:.5rem!important}.ml-lg-2,.mx-lg-2{margin-left:.5rem!important}.m-lg-3{margin:1rem!important}.mt-lg-3,.my-lg-3{margin-top:1rem!important}.mr-lg-3,.mx-lg-3{margin-right:1rem!important}.mb-lg-3,.my-lg-3{margin-bottom:1rem!important}.ml-lg-3,.mx-lg-3{margin-left:1rem!important}.m-lg-4{margin:1.5rem!important}.mt-lg-4,.my-lg-4{margin-top:1.5rem!important}.mr-lg-4,.mx-lg-4{margin-right:1.5rem!important}.mb-lg-4,.my-lg-4{margin-bottom:1.5rem!important}.ml-lg-4,.mx-lg-4{margin-left:1.5rem!important}.m-lg-5{margin:3rem!important}.mt-lg-5,.my-lg-5{margin-top:3rem!important}.mr-lg-5,.mx-lg-5{margin-right:3rem!important}.mb-lg-5,.my-lg-5{margin-bottom:3rem!important}.ml-lg-5,.mx-lg-5{margin-left:3rem!important}.p-lg-0{padding:0!important}.pt-lg-0,.py-lg-0{padding-top:0!important}.pr-lg-0,.px-lg-0{padding-right:0!important}.pb-lg-0,.py-lg-0{padding-bottom:0!important}.pl-lg-0,.px-lg-0{padding-left:0!important}.p-lg-1{padding:.25rem!important}.pt-lg-1,.py-lg-1{padding-top:.25rem!important}.pr-lg-1,.px-lg-1{padding-right:.25rem!important}.pb-lg-1,.py-lg-1{padding-bottom:.25rem!important}.pl-lg-1,.px-lg-1{padding-left:.25rem!important}.p-lg-2{padding:.5rem!important}.pt-lg-2,.py-lg-2{padding-top:.5rem!important}.pr-lg-2,.px-lg-2{padding-right:.5rem!important}.pb-lg-2,.py-lg-2{padding-bottom:.5rem!important}.pl-lg-2,.px-lg-2{padding-left:.5rem!important}.p-lg-3{padding:1rem!important}.pt-lg-3,.py-lg-3{padding-top:1rem!important}.pr-lg-3,.px-lg-3{padding-right:1rem!important}.pb-lg-3,.py-lg-3{padding-bottom:1rem!important}.pl-lg-3,.px-lg-3{padding-left:1rem!important}.p-lg-4{padding:1.5rem!important}.pt-lg-4,.py-lg-4{padding-top:1.5rem!important}.pr-lg-4,.px-lg-4{padding-right:1.5rem!important}.pb-lg-4,.py-lg-4{padding-bottom:1.5rem!important}.pl-lg-4,.px-lg-4{padding-left:1.5rem!important}.p-lg-5{padding:3rem!important}.pt-lg-5,.py-lg-5{padding-top:3rem!important}.pr-lg-5,.px-lg-5{padding-right:3rem!important}.pb-lg-5,.py-lg-5{padding-bottom:3rem!important}.pl-lg-5,.px-lg-5{padding-left:3rem!important}.m-lg-auto{margin:auto!important}.mt-lg-auto,.my-lg-auto{margin-top:auto!important}.mr-lg-auto,.mx-lg-auto{margin-right:auto!important}.mb-lg-auto,.my-lg-auto{margin-bottom:auto!important}.ml-lg-auto,.mx-lg-auto{margin-left:auto!important}}@media (min-width:1200px){.m-xl-0{margin:0!important}.mt-xl-0,.my-xl-0{margin-top:0!important}.mr-xl-0,.mx-xl-0{margin-right:0!important}.mb-xl-0,.my-xl-0{margin-bottom:0!important}.ml-xl-0,.mx-xl-0{margin-left:0!important}.m-xl-1{margin:.25rem!important}.mt-xl-1,.my-xl-1{margin-top:.25rem!important}.mr-xl-1,.mx-xl-1{margin-right:.25rem!important}.mb-xl-1,.my-xl-1{margin-bottom:.25rem!important}.ml-xl-1,.mx-xl-1{margin-left:.25rem!important}.m-xl-2{margin:.5rem!important}.mt-xl-2,.my-xl-2{margin-top:.5rem!important}.mr-xl-2,.mx-xl-2{margin-right:.5rem!important}.mb-xl-2,.my-xl-2{margin-bottom:.5rem!important}.ml-xl-2,.mx-xl-2{margin-left:.5rem!important}.m-xl-3{margin:1rem!important}.mt-xl-3,.my-xl-3{margin-top:1rem!important}.mr-xl-3,.mx-xl-3{margin-right:1rem!important}.mb-xl-3,.my-xl-3{margin-bottom:1rem!important}.ml-xl-3,.mx-xl-3{margin-left:1rem!important}.m-xl-4{margin:1.5rem!important}.mt-xl-4,.my-xl-4{margin-top:1.5rem!important}.mr-xl-4,.mx-xl-4{margin-right:1.5rem!important}.mb-xl-4,.my-xl-4{margin-bottom:1.5rem!important}.ml-xl-4,.mx-xl-4{margin-left:1.5rem!important}.m-xl-5{margin:3rem!important}.mt-xl-5,.my-xl-5{margin-top:3rem!important}.mr-xl-5,.mx-xl-5{margin-right:3rem!important}.mb-xl-5,.my-xl-5{margin-bottom:3rem!important}.ml-xl-5,.mx-xl-5{margin-left:3rem!important}.p-xl-0{padding:0!important}.pt-xl-0,.py-xl-0{padding-top:0!important}.pr-xl-0,.px-xl-0{padding-right:0!important}.pb-xl-0,.py-xl-0{padding-bottom:0!important}.pl-xl-0,.px-xl-0{padding-left:0!important}.p-xl-1{padding:.25rem!important}.pt-xl-1,.py-xl-1{padding-top:.25rem!important}.pr-xl-1,.px-xl-1{padding-right:.25rem!important}.pb-xl-1,.py-xl-1{padding-bottom:.25rem!important}.pl-xl-1,.px-xl-1{padding-left:.25rem!important}.p-xl-2{padding:.5rem!important}.pt-xl-2,.py-xl-2{padding-top:.5rem!important}.pr-xl-2,.px-xl-2{padding-right:.5rem!important}.pb-xl-2,.py-xl-2{padding-bottom:.5rem!important}.pl-xl-2,.px-xl-2{padding-left:.5rem!important}.p-xl-3{padding:1rem!important}.pt-xl-3,.py-xl-3{padding-top:1rem!important}.pr-xl-3,.px-xl-3{padding-right:1rem!important}.pb-xl-3,.py-xl-3{padding-bottom:1rem!important}.pl-xl-3,.px-xl-3{padding-left:1rem!important}.p-xl-4{padding:1.5rem!important}.pt-xl-4,.py-xl-4{padding-top:1.5rem!important}.pr-xl-4,.px-xl-4{padding-right:1.5rem!important}.pb-xl-4,.py-xl-4{padding-bottom:1.5rem!important}.pl-xl-4,.px-xl-4{padding-left:1.5rem!important}.p-xl-5{padding:3rem!important}.pt-xl-5,.py-xl-5{padding-top:3rem!important}.pr-xl-5,.px-xl-5{padding-right:3rem!important}.pb-xl-5,.py-xl-5{padding-bottom:3rem!important}.pl-xl-5,.px-xl-5{padding-left:3rem!important}.m-xl-auto{margin:auto!important}.mt-xl-auto,.my-xl-auto{margin-top:auto!important}.mr-xl-auto,.mx-xl-auto{margin-right:auto!important}.mb-xl-auto,.my-xl-auto{margin-bottom:auto!important}.ml-xl-auto,.mx-xl-auto{margin-left:auto!important}}.text-justify{text-align:justify!important}.text-nowrap{white-space:nowrap!important}.text-truncate{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.text-left{text-align:left!important}.text-right{text-align:right!important}.text-center{text-align:center!important}@media (min-width:576px){.text-sm-left{text-align:left!important}.text-sm-right{text-align:right!important}.text-sm-center{text-align:center!important}}@media (min-width:768px){.text-md-left{text-align:left!important}.text-md-right{text-align:right!important}.text-md-center{text-align:center!important}}@media (min-width:992px){.text-lg-left{text-align:left!important}.text-lg-right{text-align:right!important}.text-lg-center{text-align:center!important}}@media (min-width:1200px){.text-xl-left{text-align:left!important}.text-xl-right{text-align:right!important}.text-xl-center{text-align:center!important}}.text-lowercase{text-transform:lowercase!important}.text-uppercase{text-transform:uppercase!important}.text-capitalize{text-transform:capitalize!important}.font-weight-light{font-weight:300!important}.font-weight-normal{font-weight:400!important}.font-weight-bold{font-weight:700!important}.font-italic{font-style:italic!important}.text-white{color:#fff!important}.text-primary{color:#007bff!important}a.text-primary:focus,a.text-primary:hover{color:#0062cc!important}.text-secondary{color:#6c757d!important}a.text-secondary:focus,a.text-secondary:hover{color:#545b62!important}.text-success{color:#28a745!important}a.text-success:focus,a.text-success:hover{color:#1e7e34!important}.text-info{color:#17a2b8!important}a.text-info:focus,a.text-info:hover{color:#117a8b!important}.text-warning{color:#ffc107!important}a.text-warning:focus,a.text-warning:hover{color:#d39e00!important}.text-danger{color:#dc3545!important}a.text-danger:focus,a.text-danger:hover{color:#bd2130!important}.text-light{color:#f8f9fa!important}a.text-light:focus,a.text-light:hover{color:#dae0e5!important}.text-dark{color:#343a40!important}a.text-dark:focus,a.text-dark:hover{color:#1d2124!important}.text-muted{color:#6c757d!important}.text-hide{font:0/0 a;color:transparent;text-shadow:none;background-color:transparent;border:0}.visible{visibility:visible!important}.invisible{visibility:hidden!important}@media print{*,::after,::before{text-shadow:none!important;box-shadow:none!important}a:not(.btn){text-decoration:underline}abbr[title]::after{content:" (" attr(title) ")"}pre{white-space:pre-wrap!important}blockquote,pre{border:1px solid #999;page-break-inside:avoid}thead{display:table-header-group}img,tr{page-break-inside:avoid}h2,h3,p{orphans:3;widows:3}h2,h3{page-break-after:avoid}@page{size:a3}body{min-width:992px!important}.container{min-width:992px!important}.navbar{display:none}.badge{border:1px solid #000}.table{border-collapse:collapse!important}.table td,.table th{background-color:#fff!important}.table-bordered td,.table-bordered th{border:1px solid #ddd!important}}
doorkeeper-5.0.2/config/ 0000755 0000041 0000041 00000000000 13410042500 015137 5 ustar www-data www-data doorkeeper-5.0.2/config/locales/ 0000755 0000041 0000041 00000000000 13410042500 016561 5 ustar www-data www-data doorkeeper-5.0.2/config/locales/en.yml 0000644 0000041 0000041 00000012516 13410042500 017713 0 ustar www-data www-data 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.'
scopes:
not_match_configured: "doesn't match configured on 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:
confidential: 'Application will be used where the client secret can be kept confidential. Native mobile apps and Single Page Apps are considered non-confidential.'
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'
confidential: 'Confidential?'
actions: 'Actions'
confidentiality:
'yes': 'Yes'
'no': 'No'
new:
title: 'New Application'
show:
title: 'Application: %{name}'
application_id: 'Application UID'
secret: 'Secret'
scopes: 'Scopes'
confidential: 'Confidential'
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'
pre_authorization:
status: 'Pre-authorization'
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.'
invalid_code_challenge_method: 'The code challenge method must be plain or S256.'
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.'
admin_authenticator_not_configured: 'Access to admin panel is forbidden due to Doorkeeper.configure.admin_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:
title: 'Doorkeeper'
nav:
oauth2_provider: 'OAuth2 Provider'
applications: 'Applications'
home: 'Home'
application:
title: 'OAuth authorization required'
doorkeeper-5.0.2/NEWS.md 0000644 0000041 0000041 00000072433 13410042500 015001 0 ustar www-data www-data # News
See https://github.com/doorkeeper-gem/doorkeeper/wiki/Migration-from-old-versions for
upgrade guides.
User-visible changes worth mentioning.
## master
- [#] Add your description here.
## 5.0.2
- [#1158] Fix initializer template: change `handle_auth_errors` option
- [#1157] Remove redundant index from migration template.
## 5.0.1
- [#1140] Allow rendering custom errors from exceptions (issue #844). Originally opened as [#944].
- [#1138] Revert regression bug (check for token expiration in Authorizations controller so authorization
triggers every time)
- [#1149] Fix for `URIChecker#valid_for_authorization?` false negative when query is blank, but `?` present.
- [#1151] Fix Refresh Token strategy: add proper validation of client credentials both for Public & Private clients.
- [#1152] Fix migration template: change resource owner data type from integer to Rails generic `references`
- [#1154] Refactor `StaleRecordsCleaner` to be ORM agnostic.
## 5.0.0
- [#1127] Change the token_type initials of the Banner Token to uppercase to comply with the RFC6750 specification.
## 5.0.0.rc2
- [#1106] Restrict access to AdminController with 'Forbidden 403' if admin_authenticator is not
configured by developers..
- [#1108] Simple formating of callback URLs when listing oauth applications
- [#1116] `AccessGrant`s will now be revoked along with `AccessToken`s when
hitting the `AuthorizedApplicationController#destroy` route.
- [#1114] Make token info endpoint's attributes consistent with token creation
- [#1119] Fix token revocation for OAuth apps using "implicit" grant flow
- [#1122] Fix AuthorizationsController#new error response to be in JSON format
## 5.0.0.rc1
- [#1103] Allow customizing use_refresh_token
- [#1089] Removed enable_pkce_without_secret configuration option
- [#1102] Expiration time based on scopes
- [#1099] All the configuration variables in `Doorkeeper.configuration` now
always return a non-nil value (`true` or `false`)
- [#1099] ORM / Query optimization: Do not revoke the refresh token if it is not enabled
in `doorkeeper.rb`
- [#996] Expiration Time Base On Grant Type
- [#997] Allow PKCE authorization_code flow as specified in RFC7636
- [#907] Fix lookup for matching tokens in certain edge-cases
- [#992] Add API option to use Doorkeeper without management views for API only
Rails applications (`api_only`)
- [#1045] Validate redirect_uri as the native URI when making authorization code requests
- [#1048] Remove deprecated `Doorkeeper#configured?`, `Doorkeeper#database_installed?`, and
`Doorkeeper#installed?` method
- [#1031] Allow public clients to authenticate without `client_secret`. Define an app as
either public or private/confidential
**[IMPORTANT]**: all the applications (clients) now are considered as private by default.
You need to manually change `confidential` column to `false` if you are using public clients,
in other case your mobile (or other) applications will not be able to authorize.
See [#1142](https://github.com/doorkeeper-gem/doorkeeper/issues/1142) for more details.
- [#1010] Add configuration to enforce configured scopes (`default_scopes` and
`optional_scopes`) for applications
- [#1060] Ensure that the native redirect_uri parameter matches with redirect_uri of the client
- [#1064] Add :before_successful_authorization and :after_successful_authorization hooks
- [#1069] Upgrade Bootstrap to 4 for Admin
- [#1068] Add rake task to cleanup databases that can become large over time
- [#1072] AuthorizationsController: Memoize strategy.authorize_response result to enable
subclasses to use the response object.
- [#1075] Call `before_successful_authorization` and `after_successful_authorization` hooks
on `create` action as well as `new`
- [#1082] Fix #916: remember routes mapping and use it required places (fix error with
customized Token Info route).
- [#1086, #1088] Fix bug with receiving default scopes in the token even if they are
not present in the application scopes (use scopes intersection).
- [#1076] Add config to enforce content type to application/x-www-form-urlencoded
- Fix bug with `force_ssl_in_redirect_uri` when it breaks existing applications with an
SSL redirect_uri.
## 4.4.3
- [#1143] Adds a config option `opt_out_native_route_change` to opt out of the breaking api
changed introduced in https://github.com/doorkeeper-gem/doorkeeper/pull/1003
## 4.4.2
- [#1130] Backport fix for native redirect_uri from 5.x.
## 4.4.1
- [#1127] Backport token type to comply with the RFC6750 specification.
- [#1125] Backport Quote surround I18n yes/no keys
## 4.4.0
- [#1120] Backport security fix from 5.x for token revocation when using public clients
**[IMPORTANT]**: all the applications (clients) now are considered as private by default.
You need to manually change `confidential` column to `false` if you are using public clients,
in other case your mobile (or other) applications will not be able to authorize.
See [#1142](https://github.com/doorkeeper-gem/doorkeeper/issues/1142) for more details.
## 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.
**[IMPORTANT]**: Previously authorization code response route was `/oauth/authorize/`,
now it is `oauth/authorize/native?code=` (in order to help applications to automatically find the code value).
- [#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
doorkeeper-5.0.2/Gemfile 0000644 0000041 0000041 00000000400 13410042500 015157 0 ustar www-data www-data source "https://rubygems.org"
gem "rails", "~> 5.2"
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
doorkeeper-5.0.2/Dangerfile 0000644 0000041 0000041 00000005477 13410042500 015672 0 ustar www-data www-data CHANGELOG_FILE = 'NEWS.md'
GITHUB_REPO = 'https://github.com/doorkeeper-gem/doorkeeper'
def changelog_changed?
git.modified_files.include?(CHANGELOG_FILE) || git.added_files.include?(CHANGELOG_FILE)
end
def changelog_entry_example
pr_number = github.pr_json['number']
pr_title = github.pr_title
.sub(/[?.!,;]?$/, '')
.capitalize
"- [##{pr_number}]: #{pr_title}."
end
# --------------------------------------------------------------------------------------------------------------------
# Has any changes happened inside the actual library code?
# --------------------------------------------------------------------------------------------------------------------
has_app_changes = !git.modified_files.grep(/lib/).empty?
has_spec_changes = !git.modified_files.grep(/spec/).empty?
# --------------------------------------------------------------------------------------------------------------------
# You've made changes to lib, but didn't write any tests?
# --------------------------------------------------------------------------------------------------------------------
if has_app_changes && !has_spec_changes
warn("There're library changes, but not tests. That's OK as long as you're refactoring existing code.", sticky: false)
end
# --------------------------------------------------------------------------------------------------------------------
# You've made changes to specs, but no library code has changed?
# --------------------------------------------------------------------------------------------------------------------
if !has_app_changes && has_spec_changes
message('We really appreciate pull requests that demonstrate issues, even without a fix. That said, the next step is to try and fix the failing tests!', sticky: false)
end
# Mainly to encourage writing up some reasoning about the PR, rather than
# just leaving a title
if github.pr_body.length < 10
fail "Please provide a summary in the Pull Request description"
end
# --------------------------------------------------------------------------------------------------------------------
# Have you updated CHANGELOG.md?
# --------------------------------------------------------------------------------------------------------------------
# Add a CHANGELOG entry for app changes
if has_app_changes && !changelog_changed?
markdown <<-MARKDOWN
Here's an example of a #{CHANGELOG_FILE} entry:
```markdown
#{changelog_entry_example}
```
MARKDOWN
fail("Please include a changelog entry. \nYou can find it at [#{CHANGELOG_FILE}](#{GITHUB_REPO}/blob/master/#{CHANGELOG_FILE}).")
end
if git.commits.any? { |commit| commit.message =~ /^Merge branch '#{github.branch_for_base}'/ }
warn('Please rebase to get rid of the merge commits in this PR')
end
if git.commits.length > 1
warn('Please squash all your commits to a single one')
end
doorkeeper-5.0.2/.coveralls.yml 0000644 0000041 0000041 00000000030 13410042500 016456 0 ustar www-data www-data service_name: travis-ci
doorkeeper-5.0.2/.github/ 0000755 0000041 0000041 00000000000 13410042500 015232 5 ustar www-data www-data doorkeeper-5.0.2/.github/PULL_REQUEST_TEMPLATE.md 0000644 0000041 0000041 00000001175 13410042500 021037 0 ustar www-data www-data ### 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!
doorkeeper-5.0.2/.github/ISSUE_TEMPLATE.md 0000644 0000041 0000041 00000001322 13410042500 017735 0 ustar www-data www-data ### 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**:
doorkeeper-5.0.2/UPGRADE.md 0000644 0000041 0000041 00000000171 13410042500 015302 0 ustar www-data www-data See [Upgrade Guides](https://github.com/doorkeeper-gem/doorkeeper/wiki/Migration-from-old-versions)
in the project Wiki.
doorkeeper-5.0.2/MIT-LICENSE 0000644 0000041 0000041 00000002057 13410042500 015332 0 ustar www-data www-data 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.
doorkeeper-5.0.2/SECURITY.md 0000644 0000041 0000041 00000001264 13410042500 015466 0 ustar www-data www-data # 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!
doorkeeper-5.0.2/.gitlab-ci.yml 0000644 0000041 0000041 00000001141 13410042500 016323 0 ustar www-data www-data dependency_scanning:
image: docker:stable
variables:
DOCKER_DRIVER: overlay2
allow_failure: true
services:
- docker:stable-dind
script:
- export SP_VERSION=$(echo "$CI_SERVER_VERSION" | sed 's/^\([0-9]*\)\.\([0-9]*\).*/\1-\2-stable/')
- docker run
--env DEP_SCAN_DISABLE_REMOTE_CHECKS="${DEP_SCAN_DISABLE_REMOTE_CHECKS:-false}"
--volume "$PWD:/code"
--volume /var/run/docker.sock:/var/run/docker.sock
"registry.gitlab.com/gitlab-org/security-products/dependency-scanning:$SP_VERSION" /code
artifacts:
paths: [gl-dependency-scanning-report.json]