doorkeeper-5.0.2/0000755000004100000410000000000013410042500013672 5ustar www-datawww-datadoorkeeper-5.0.2/.travis.yml0000644000004100000410000000176413410042500016013 0ustar www-datawww-datacache: 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/.rspec0000644000004100000410000000001113410042500014777 0ustar www-datawww-data--colour doorkeeper-5.0.2/README.md0000644000004100000410000004713413410042500015162 0ustar www-datawww-data# Doorkeeper — awesome OAuth 2 provider for your Rails / Grape app. [![Gem Version](https://badge.fury.io/rb/doorkeeper.svg)](https://rubygems.org/gems/doorkeeper) [![Build Status](https://travis-ci.org/doorkeeper-gem/doorkeeper.svg?branch=master)](https://travis-ci.org/doorkeeper-gem/doorkeeper) [![Code Climate](https://codeclimate.com/github/doorkeeper-gem/doorkeeper.svg)](https://codeclimate.com/github/doorkeeper-gem/doorkeeper) [![Coverage Status](https://coveralls.io/repos/github/doorkeeper-gem/doorkeeper/badge.svg?branch=master)](https://coveralls.io/github/doorkeeper-gem/doorkeeper?branch=master) [![Security](https://hakiri.io/github/doorkeeper-gem/doorkeeper/master.svg)](https://hakiri.io/github/doorkeeper-gem/doorkeeper/master) [![Reviewed by Hound](https://img.shields.io/badge/Reviewed_by-Hound-8E64B0.svg)](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/0000755000004100000410000000000013410042500015465 5ustar www-datawww-datadoorkeeper-5.0.2/gemfiles/rails_4_2.gemfile0000644000004100000410000000057513410042500020604 0ustar www-datawww-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.gemfile0000644000004100000410000000104513410042500021504 0ustar www-datawww-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.gemfile0000644000004100000410000000052013410042500020573 0ustar www-datawww-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.gemfile0000644000004100000410000000052313410042500020574 0ustar www-datawww-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.gemfile0000644000004100000410000000052313410042500020575 0ustar www-datawww-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/0000755000004100000410000000000013410042500014624 5ustar www-datawww-datadoorkeeper-5.0.2/spec/grape/0000755000004100000410000000000013410042500015722 5ustar www-datawww-datadoorkeeper-5.0.2/spec/grape/grape_integration_spec.rb0000644000004100000410000000703413410042500022766 0ustar www-datawww-datarequire '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.rb0000644000004100000410000000150413410042500017130 0ustar www-datawww-dataFactoryBot.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/0000755000004100000410000000000013410042500016477 5ustar www-datawww-datadoorkeeper-5.0.2/spec/requests/protected_resources/0000755000004100000410000000000013410042500022562 5ustar www-datawww-datadoorkeeper-5.0.2/spec/requests/protected_resources/metal_spec.rb0000644000004100000410000000062613410042500025227 0ustar www-datawww-datarequire '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.rb0000644000004100000410000000535213410042500026431 0ustar www-datawww-datarequire '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/0000755000004100000410000000000013410042500017631 5ustar www-datawww-datadoorkeeper-5.0.2/spec/requests/flows/client_credentials_spec.rb0000644000004100000410000001030413410042500025021 0ustar www-datawww-datarequire '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.rb0000644000004100000410000002057113410042500023037 0ustar www-datawww-datarequire '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.rb0000644000004100000410000000464413410042500025126 0ustar www-datawww-datarequire '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.rb0000644000004100000410000000174213410042500025575 0ustar www-datawww-datarequire '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.rb0000644000004100000410000003453713410042500025076 0ustar www-datawww-datarequire '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.rb0000644000004100000410000000454713410042500026470 0ustar www-datawww-datarequire '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.rb0000644000004100000410000001620013410042500024025 0ustar www-datawww-datarequire '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.rb0000644000004100000410000001264113410042500023667 0ustar www-datawww-datarequire '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.rb0000644000004100000410000000532013410042500024175 0ustar www-datawww-datarequire '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/0000755000004100000410000000000013410042500021165 5ustar www-datawww-datadoorkeeper-5.0.2/spec/requests/applications/authorized_applications_spec.rb0000644000004100000410000000170413410042500027452 0ustar www-datawww-datarequire '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.rb0000644000004100000410000001374613410042500026775 0ustar www-datawww-datarequire '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/0000755000004100000410000000000013410042500020502 5ustar www-datawww-datadoorkeeper-5.0.2/spec/requests/endpoints/token_spec.rb0000644000004100000410000000540413410042500023164 0ustar www-datawww-datarequire '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.rb0000644000004100000410000000424413410042500024745 0ustar www-datawww-datarequire '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/0000755000004100000410000000000013410042500016266 5ustar www-datawww-datadoorkeeper-5.0.2/spec/helpers/doorkeeper/0000755000004100000410000000000013410042500020425 5ustar www-datawww-datadoorkeeper-5.0.2/spec/helpers/doorkeeper/dashboard_helper_spec.rb0000644000004100000410000000122213410042500025247 0ustar www-datawww-datarequire '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/0000755000004100000410000000000013410042500017172 5ustar www-datawww-datadoorkeeper-5.0.2/spec/controllers/protected_resources_controller_spec.rb0000644000004100000410000002517013410042500027064 0ustar www-datawww-datarequire '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.rb0000644000004100000410000000270013410042500025126 0ustar www-datawww-datarequire '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.rb0000644000004100000410000000233413410042500026463 0ustar www-datawww-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.rb0000644000004100000410000001234013410042500025462 0ustar www-datawww-datarequire '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.rb0000644000004100000410000002147713410042500024312 0ustar www-datawww-datarequire '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.rb0000644000004100000410000004036313410042500026065 0ustar www-datawww-datarequire '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/0000755000004100000410000000000013410042500016774 5ustar www-datawww-datadoorkeeper-5.0.2/spec/validators/redirect_uri_validator_spec.rb0000644000004100000410000001026713410042500025066 0ustar www-datawww-datarequire '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/0000755000004100000410000000000013410042500016107 5ustar www-datawww-datadoorkeeper-5.0.2/spec/models/doorkeeper/0000755000004100000410000000000013410042500020246 5ustar www-datawww-datadoorkeeper-5.0.2/spec/models/doorkeeper/application_spec.rb0000644000004100000410000002231713410042500024115 0ustar www-datawww-datarequire '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.rb0000644000004100000410000003771313410042500024261 0ustar www-datawww-datarequire '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.rb0000644000004100000410000000410113410042500024235 0ustar www-datawww-datarequire '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/0000755000004100000410000000000013410042500016311 5ustar www-datawww-datadoorkeeper-5.0.2/spec/version/version_spec.rb0000644000004100000410000000056213410042500021340 0ustar www-datawww-datarequire '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/0000755000004100000410000000000013410042500016313 5ustar www-datawww-datadoorkeeper-5.0.2/spec/routing/scoped_routes_spec.rb0000644000004100000410000000271413410042500022534 0ustar www-datawww-datarequire '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.rb0000644000004100000410000000271013410042500022677 0ustar www-datawww-datarequire '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.rb0000644000004100000410000001103713410042500025032 0ustar www-datawww-datarequire '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/0000755000004100000410000000000013410042500016775 5ustar www-datawww-datadoorkeeper-5.0.2/spec/generators/pkce_generator_spec.rb0000644000004100000410000000221113410042500023320 0ustar www-datawww-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.rb0000644000004100000410000000175413410042500024057 0ustar www-datawww-datarequire '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.rb0000644000004100000410000000225113410042500026117 0ustar www-datawww-datarequire '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/0000755000004100000410000000000013410042500020773 5ustar www-datawww-datadoorkeeper-5.0.2/spec/generators/templates/routes.rb0000644000004100000410000000004513410042500022640 0ustar www-datawww-dataRails.application.routes.draw do end doorkeeper-5.0.2/spec/generators/views_generator_spec.rb0000644000004100000410000000161513410042500023542 0ustar www-datawww-datarequire '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.rb0000644000004100000410000000334013410042500027174 0ustar www-datawww-datarequire '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.rb0000644000004100000410000000253213410042500027611 0ustar www-datawww-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.rb0000644000004100000410000000222313410042500024372 0ustar www-datawww-datarequire '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/0000755000004100000410000000000013410042500015372 5ustar www-datawww-datadoorkeeper-5.0.2/spec/lib/doorkeeper_spec.rb0000644000004100000410000000122013410042500021063 0ustar www-datawww-datarequire '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/0000755000004100000410000000000013410042500016655 5ustar www-datawww-datadoorkeeper-5.0.2/spec/lib/models/revocable_spec.rb0000644000004100000410000000316113410042500022157 0ustar www-datawww-datarequire '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.rb0000644000004100000410000000246613410042500022177 0ustar www-datawww-datarequire '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.rb0000644000004100000410000000157613410042500021521 0ustar www-datawww-datarequire '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.rb0000644000004100000410000000457713410042500023270 0ustar www-datawww-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.rb0000644000004100000410000003314613410042500020205 0ustar www-datawww-datarequire '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/0000755000004100000410000000000013410042500017062 5ustar www-datawww-datadoorkeeper-5.0.2/spec/lib/request/strategy_spec.rb0000644000004100000410000000232013410042500022260 0ustar www-datawww-datarequire '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.rb0000644000004100000410000000346313410042500020245 0ustar www-datawww-datarequire '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/0000755000004100000410000000000013410042500016512 5ustar www-datawww-datadoorkeeper-5.0.2/spec/lib/oauth/token_spec.rb0000644000004100000410000001142413410042500021173 0ustar www-datawww-datarequire '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/0000755000004100000410000000000013410042500017770 5ustar www-datawww-datadoorkeeper-5.0.2/spec/lib/oauth/client/credentials_spec.rb0000644000004100000410000000546413410042500023635 0ustar www-datawww-datarequire '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/0000755000004100000410000000000013410042500022345 5ustar www-datawww-datadoorkeeper-5.0.2/spec/lib/oauth/client_credentials/validation_spec.rb0000644000004100000410000000414513410042500026042 0ustar www-datawww-datarequire '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.rb0000644000004100000410000000571113410042500025222 0ustar www-datawww-datarequire '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.rb0000644000004100000410000000267013410042500025350 0ustar www-datawww-datarequire '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.rb0000644000004100000410000000444113410042500023112 0ustar www-datawww-datarequire '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/0000755000004100000410000000000013410042500020154 5ustar www-datawww-datadoorkeeper-5.0.2/spec/lib/oauth/helpers/uri_checker_spec.rb0000644000004100000410000001751613410042500024010 0ustar www-datawww-datarequire '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.rb0000644000004100000410000000346513410042500024320 0ustar www-datawww-datarequire '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.rb0000644000004100000410000000072713410042500024227 0ustar www-datawww-datarequire '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/0000755000004100000410000000000013410042500021412 5ustar www-datawww-datadoorkeeper-5.0.2/spec/lib/oauth/authorization/uri_builder_spec.rb0000644000004100000410000000236313410042500025262 0ustar www-datawww-datarequire '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.rb0000644000004100000410000000557013410042500022750 0ustar www-datawww-datarequire '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.rb0000644000004100000410000000765113410042500025524 0ustar www-datawww-datarequire '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.rb0000644000004100000410000000246613410042500024625 0ustar www-datawww-datarequire '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.rb0000644000004100000410000000076213410042500025130 0ustar www-datawww-datarequire '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.rb0000644000004100000410000000102113410042500021174 0ustar www-datawww-datarequire '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.rb0000644000004100000410000000177213410042500022710 0ustar www-datawww-datarequire '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.rb0000644000004100000410000000236113410042500021331 0ustar www-datawww-datarequire '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.rb0000644000004100000410000001074513410042500026213 0ustar www-datawww-datarequire '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.rb0000644000004100000410000000156713410042500022712 0ustar www-datawww-datarequire '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.rb0000644000004100000410000000354413410042500023126 0ustar www-datawww-datarequire '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.rb0000644000004100000410000000216213410042500022534 0ustar www-datawww-datarequire '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.rb0000644000004100000410000000135013410042500026306 0ustar www-datawww-datarequire '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.rb0000644000004100000410000001243613410042500022541 0ustar www-datawww-datarequire '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.rb0000644000004100000410000000654413410042500025465 0ustar www-datawww-datarequire '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.rb0000644000004100000410000001302413410042500023617 0ustar www-datawww-datarequire '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.rb0000644000004100000410000001072213410042500021347 0ustar www-datawww-datarequire '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.rb0000644000004100000410000001340313410042500024460 0ustar www-datawww-datarequire '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/0000755000004100000410000000000013410042500015757 5ustar www-datawww-datadoorkeeper-5.0.2/spec/dummy/config.ru0000644000004100000410000000023213410042500017571 0ustar www-datawww-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/0000755000004100000410000000000013410042500017263 5ustar www-datawww-datadoorkeeper-5.0.2/spec/dummy/script/rails0000755000004100000410000000043713410042500020327 0ustar www-datawww-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/0000755000004100000410000000000013410042500016537 5ustar www-datawww-datadoorkeeper-5.0.2/spec/dummy/app/helpers/0000755000004100000410000000000013410042500020201 5ustar www-datawww-datadoorkeeper-5.0.2/spec/dummy/app/helpers/application_helper.rb0000644000004100000410000000015713410042500024373 0ustar www-datawww-datamodule ApplicationHelper def current_user @current_user ||= User.find_by_id(session[:user_id]) end end doorkeeper-5.0.2/spec/dummy/app/controllers/0000755000004100000410000000000013410042500021105 5ustar www-datawww-datadoorkeeper-5.0.2/spec/dummy/app/controllers/application_controller.rb0000644000004100000410000000012013410042500026171 0ustar www-datawww-dataclass ApplicationController < ActionController::Base protect_from_forgery end doorkeeper-5.0.2/spec/dummy/app/controllers/metal_controller.rb0000644000004100000410000000041313410042500024775 0ustar www-datawww-dataclass 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.rb0000644000004100000410000000057613410042500024635 0ustar www-datawww-dataclass 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.rb0000644000004100000410000000043113410042500031000 0ustar www-datawww-dataclass 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.rb0000644000004100000410000000035013410042500030773 0ustar www-datawww-dataclass 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.rb0000644000004100000410000000031113410042500030345 0ustar www-datawww-dataclass 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/0000755000004100000410000000000013410042500020022 5ustar www-datawww-datadoorkeeper-5.0.2/spec/dummy/app/models/user.rb0000644000004100000410000000021013410042500021316 0ustar www-datawww-dataclass 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/0000755000004100000410000000000013410042500020041 5ustar www-datawww-datadoorkeeper-5.0.2/spec/dummy/app/assets/config/0000755000004100000410000000000013410042500021306 5ustar www-datawww-datadoorkeeper-5.0.2/spec/dummy/app/assets/config/manifest.js0000644000004100000410000000003113410042500023444 0ustar www-datawww-data// JS and CSS bundles // doorkeeper-5.0.2/spec/dummy/app/views/0000755000004100000410000000000013410042500017674 5ustar www-datawww-datadoorkeeper-5.0.2/spec/dummy/app/views/layouts/0000755000004100000410000000000013410042500021374 5ustar www-datawww-datadoorkeeper-5.0.2/spec/dummy/app/views/layouts/application.html.erb0000644000004100000410000000024113410042500025331 0ustar www-datawww-data Dummy <%= csrf_meta_tags %> <%= link_to "Sign in", '/sign_in' %> <%= yield %> doorkeeper-5.0.2/spec/dummy/app/views/home/0000755000004100000410000000000013410042500020624 5ustar www-datawww-datadoorkeeper-5.0.2/spec/dummy/app/views/home/index.html.erb0000644000004100000410000000000013410042500023356 0ustar www-datawww-datadoorkeeper-5.0.2/spec/dummy/db/0000755000004100000410000000000013410042500016344 5ustar www-datawww-datadoorkeeper-5.0.2/spec/dummy/db/schema.rb0000644000004100000410000000530613410042500020135 0ustar www-datawww-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/0000755000004100000410000000000013410042500017774 5ustar www-datawww-datadoorkeeper-5.0.2/spec/dummy/db/migrate/20160320211015_add_previous_refresh_token_to_access_tokens.rb0000644000004100000410000000040613410042500032760 0ustar www-datawww-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.rb0000644000004100000410000000046513410042500026776 0ustar www-datawww-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.rb0000644000004100000410000000034213410042500024221 0ustar www-datawww-dataclass 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.rb0000644000004100000410000000045513410042500030514 0ustar www-datawww-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.rb0000644000004100000410000000026713410042500024447 0ustar www-datawww-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.rb0000644000004100000410000000023013410042500026335 0ustar www-datawww-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.rb0000644000004100000410000000377113410042500027010 0ustar www-datawww-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/Rakefile0000755000004100000410000000041213410042500017424 0ustar www-datawww-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/0000755000004100000410000000000013410042500017224 5ustar www-datawww-datadoorkeeper-5.0.2/spec/dummy/config/boot.rb0000644000004100000410000000036413410042500020517 0ustar www-datawww-datarequire '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.rb0000644000004100000410000000041713410042500021074 0ustar www-datawww-dataRails.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/0000755000004100000410000000000013410042500021732 5ustar www-datawww-datadoorkeeper-5.0.2/spec/dummy/config/initializers/doorkeeper.rb0000644000004100000410000001222713410042500024422 0ustar www-datawww-dataDoorkeeper.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.rb0000644000004100000410000000062413410042500026247 0ustar www-datawww-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.rb0000644000004100000410000000062713410042500025163 0ustar www-datawww-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.rb0000644000004100000410000000067713410042500027026 0ustar www-datawww-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.rb0000644000004100000410000000064613410042500024752 0ustar www-datawww-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.rb0000644000004100000410000000072113410042500025453 0ustar www-datawww-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.rb0000644000004100000410000000116213410042500022054 0ustar www-datawww-datarequire 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/0000755000004100000410000000000013410042500020646 5ustar www-datawww-datadoorkeeper-5.0.2/spec/dummy/config/locales/doorkeeper.en.yml0000644000004100000410000000014713410042500024133 0ustar www-datawww-dataen: doorkeeper: scopes: public: "Access your public data" write: "Update your data" doorkeeper-5.0.2/spec/dummy/config/environment.rb0000644000004100000410000000022013410042500022107 0ustar www-datawww-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/0000755000004100000410000000000013410042500021753 5ustar www-datawww-datadoorkeeper-5.0.2/spec/dummy/config/environments/test.rb0000644000004100000410000000340313410042500023257 0ustar www-datawww-dataDummy::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.rb0000644000004100000410000000420513410042500024467 0ustar www-datawww-dataDummy::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.rb0000644000004100000410000000175713410042500024634 0ustar www-datawww-dataDummy::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.yml0000644000004100000410000000034313410042500021513 0ustar www-datawww-datadevelopment: 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/0000755000004100000410000000000013410042500017235 5ustar www-datawww-datadoorkeeper-5.0.2/spec/dummy/public/422.html0000644000004100000410000000130713410042500020433 0ustar www-datawww-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.html0000644000004100000410000000133013410042500020424 0ustar www-datawww-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.html0000644000004100000410000000133013410042500020427 0ustar www-datawww-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.ico0000644000004100000410000000000013410042500021344 0ustar www-datawww-datadoorkeeper-5.0.2/spec/spec_helper.rb0000644000004100000410000000242013410042500017440 0ustar www-datawww-datarequire '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.rb0000644000004100000410000000005713410042500022047 0ustar www-datawww-data# For compatibility only require 'spec_helper' doorkeeper-5.0.2/spec/support/0000755000004100000410000000000013410042500016340 5ustar www-datawww-datadoorkeeper-5.0.2/spec/support/doorkeeper_rspec.rb0000644000004100000410000000125513410042500022223 0ustar www-datawww-datamodule 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/0000755000004100000410000000000013410042500020002 5ustar www-datawww-datadoorkeeper-5.0.2/spec/support/helpers/url_helper.rb0000644000004100000410000000425313410042500022474 0ustar www-datawww-datamodule 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.rb0000644000004100000410000000463013410042500022771 0ustar www-datawww-datamodule 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.rb0000644000004100000410000000262713410042500026345 0ustar www-datawww-datamodule 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.rb0000644000004100000410000000434013410042500024371 0ustar www-datawww-datamodule 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.rb0000644000004100000410000000054713410042500026105 0ustar www-datawww-datamodule 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.rb0000644000004100000410000000042313410042500023132 0ustar www-datawww-datamodule 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.rb0000644000004100000410000000176213410042500022232 0ustar www-datawww-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/0000755000004100000410000000000013410042500017606 5ustar www-datawww-datadoorkeeper-5.0.2/spec/support/shared/controllers_shared_context.rb0000644000004100000410000000627513410042500025605 0ustar www-datawww-datashared_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.rb0000644000004100000410000000257713410042500024655 0ustar www-datawww-datashared_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/0000755000004100000410000000000013410042500017135 5ustar www-datawww-datadoorkeeper-5.0.2/spec/support/orm/active_record.rb0000644000004100000410000000015313410042500022272 0ustar www-datawww-data# load schema to in memory sqlite ActiveRecord::Migration.verbose = false load Rails.root + 'db/schema.rb' doorkeeper-5.0.2/spec/support/dependencies/0000755000004100000410000000000013410042500020766 5ustar www-datawww-datadoorkeeper-5.0.2/spec/support/dependencies/factory_bot.rb0000644000004100000410000000006213410042500023624 0ustar www-datawww-datarequire 'factory_bot' FactoryBot.find_definitions doorkeeper-5.0.2/RELEASING.md0000644000004100000410000000060113410042500015522 0ustar www-datawww-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.yml0000644000004100000410000000031413410042500016142 0ustar www-datawww-dataAllCops: Exclude: - "spec/dummy/db/*" Metrics/BlockLength: Exclude: - spec/**/* LineLength: Exclude: - spec/**/* StringLiterals: Enabled: false TrailingBlankLines: Enabled: true doorkeeper-5.0.2/Appraisals0000644000004100000410000000055013410042500015714 0ustar www-datawww-dataappraise "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.gemspec0000644000004100000410000000234713410042500017404 0ustar www-datawww-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/.gitignore0000644000004100000410000000032413410042500015661 0ustar www-datawww-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.md0000644000004100000410000000634113410042500016475 0ustar www-datawww-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/0000755000004100000410000000000013410042500014452 5ustar www-datawww-datadoorkeeper-5.0.2/app/helpers/0000755000004100000410000000000013410042500016114 5ustar www-datawww-datadoorkeeper-5.0.2/app/helpers/doorkeeper/0000755000004100000410000000000013410042500020253 5ustar www-datawww-datadoorkeeper-5.0.2/app/helpers/doorkeeper/dashboard_helper.rb0000644000004100000410000000076613410042500024077 0ustar www-datawww-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/0000755000004100000410000000000013410042500017020 5ustar www-datawww-datadoorkeeper-5.0.2/app/controllers/doorkeeper/0000755000004100000410000000000013410042500021157 5ustar www-datawww-datadoorkeeper-5.0.2/app/controllers/doorkeeper/authorizations_controller.rb0000644000004100000410000000456113410042500027040 0ustar www-datawww-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.rb0000644000004100000410000000047313410042500026256 0ustar www-datawww-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.rb0000644000004100000410000000113513410042500027434 0ustar www-datawww-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.rb0000644000004100000410000000067513410042500026112 0ustar www-datawww-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.rb0000644000004100000410000000611013410042500025250 0ustar www-datawww-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.rb0000644000004100000410000000145113410042500030674 0ustar www-datawww-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.rb0000644000004100000410000000446413410042500026445 0ustar www-datawww-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/0000755000004100000410000000000013410042500016622 5ustar www-datawww-datadoorkeeper-5.0.2/app/validators/redirect_uri_validator.rb0000644000004100000410000000250213410042500023673 0ustar www-datawww-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/0000755000004100000410000000000013410042500015754 5ustar www-datawww-datadoorkeeper-5.0.2/app/assets/stylesheets/0000755000004100000410000000000013410042500020330 5ustar www-datawww-datadoorkeeper-5.0.2/app/assets/stylesheets/doorkeeper/0000755000004100000410000000000013410042500022467 5ustar www-datawww-datadoorkeeper-5.0.2/app/assets/stylesheets/doorkeeper/application.css0000644000004100000410000000162013410042500025503 0ustar www-datawww-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/0000755000004100000410000000000013410042500023557 5ustar www-datawww-datadoorkeeper-5.0.2/app/assets/stylesheets/doorkeeper/admin/application.css0000644000004100000410000000023613410042500026575 0ustar www-datawww-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/0000755000004100000410000000000013410042500015607 5ustar www-datawww-datadoorkeeper-5.0.2/app/views/doorkeeper/0000755000004100000410000000000013410042500017746 5ustar www-datawww-datadoorkeeper-5.0.2/app/views/doorkeeper/authorized_applications/0000755000004100000410000000000013410042500024672 5ustar www-datawww-datadoorkeeper-5.0.2/app/views/doorkeeper/authorized_applications/_delete_form.html.erb0000644000004100000410000000051013410042500030747 0ustar www-datawww-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.erb0000644000004100000410000000134413410042500027440 0ustar www-datawww-data
<% @applications.each do |application| %> <% end %>
<%= t('doorkeeper.authorized_applications.index.application') %> <%= t('doorkeeper.authorized_applications.index.created_at') %>
<%= application.name %> <%= application.created_at.strftime(t('doorkeeper.authorized_applications.index.date_format')) %> <%= render 'delete_form', application: application %>
doorkeeper-5.0.2/app/views/doorkeeper/authorizations/0000755000004100000410000000000013410042500023031 5ustar www-datawww-datadoorkeeper-5.0.2/app/views/doorkeeper/authorizations/new.html.erb0000644000004100000410000000355013410042500025262 0ustar www-datawww-data

<%= raw t('.prompt', client_name: content_tag(:strong, class: 'text-info') { @pre_auth.client.name }) %>

<% if @pre_auth.scopes.count > 0 %>

<%= t('.able_to') %>:

<% end %>
<%= form_tag oauth_authorization_path, method: :post do %> <%= hidden_field_tag :client_id, @pre_auth.client.uid %> <%= hidden_field_tag :redirect_uri, @pre_auth.redirect_uri %> <%= hidden_field_tag :state, @pre_auth.state %> <%= hidden_field_tag :response_type, @pre_auth.response_type %> <%= hidden_field_tag :scope, @pre_auth.scope %> <%= hidden_field_tag :code_challenge, @pre_auth.code_challenge %> <%= hidden_field_tag :code_challenge_method, @pre_auth.code_challenge_method %> <%= submit_tag t('doorkeeper.authorizations.buttons.authorize'), class: "btn btn-success btn-lg btn-block" %> <% end %> <%= form_tag oauth_authorization_path, method: :delete do %> <%= hidden_field_tag :client_id, @pre_auth.client.uid %> <%= hidden_field_tag :redirect_uri, @pre_auth.redirect_uri %> <%= hidden_field_tag :state, @pre_auth.state %> <%= hidden_field_tag :response_type, @pre_auth.response_type %> <%= hidden_field_tag :scope, @pre_auth.scope %> <%= hidden_field_tag :code_challenge, @pre_auth.code_challenge %> <%= hidden_field_tag :code_challenge_method, @pre_auth.code_challenge_method %> <%= submit_tag t('doorkeeper.authorizations.buttons.deny'), class: "btn btn-danger btn-lg btn-block" %> <% end %>
doorkeeper-5.0.2/app/views/doorkeeper/authorizations/show.html.erb0000644000004100000410000000023613410042500025447 0ustar www-datawww-data
<%= params[:code] %>
doorkeeper-5.0.2/app/views/doorkeeper/authorizations/error.html.erb0000644000004100000410000000030713410042500025617 0ustar www-datawww-data

<%= t('doorkeeper.authorizations.error.title') %>

<%= @pre_auth.error_response.body[:error_description] %>
doorkeeper-5.0.2/app/views/doorkeeper/applications/0000755000004100000410000000000013410042500022434 5ustar www-datawww-datadoorkeeper-5.0.2/app/views/doorkeeper/applications/_delete_form.html.erb0000644000004100000410000000051313410042500026514 0ustar www-datawww-data<%- submit_btn_css ||= 'btn btn-link' %> <%= form_tag oauth_application_path(application), method: :delete do %> <%= submit_tag t('doorkeeper.applications.buttons.destroy'), onclick: "return confirm('#{ t('doorkeeper.applications.confirmations.destroy') }')", class: submit_btn_css %> <% end %> doorkeeper-5.0.2/app/views/doorkeeper/applications/new.html.erb0000644000004100000410000000016713410042500024666 0ustar www-datawww-data

<%= t('.title') %>

<%= render 'form', application: @application %> doorkeeper-5.0.2/app/views/doorkeeper/applications/index.html.erb0000644000004100000410000000231513410042500025201 0ustar www-datawww-data

<%= t('.title') %>

<%= link_to t('.new'), new_oauth_application_path, class: 'btn btn-success' %>

<% @applications.each do |application| %> <% end %>
<%= t('.name') %> <%= t('.callback_url') %> <%= t('.confidential') %> <%= t('.actions') %>
<%= link_to application.name, oauth_application_path(application) %> <%= simple_format(application.redirect_uri) %> <%= application.confidential? ? t('doorkeeper.applications.index.confidentiality.yes') : t('doorkeeper.applications.index.confidentiality.no') %> <%= link_to t('doorkeeper.applications.buttons.edit'), edit_oauth_application_path(application), class: 'btn btn-link' %> <%= render 'delete_form', application: application %>
doorkeeper-5.0.2/app/views/doorkeeper/applications/show.html.erb0000644000004100000410000000274313410042500025057 0ustar www-datawww-data

<%= t('.title', name: @application.name) %>

<%= t('.application_id') %>:

<%= @application.uid %>

<%= t('.secret') %>:

<%= @application.secret %>

<%= t('.scopes') %>:

<%= @application.scopes.presence || raw(' ') %>

<%= t('.confidential') %>:

<%= @application.confidential? %>

<%= t('.callback_urls') %>:

<% @application.redirect_uri.split.each do |uri| %> <% end %>
<%= uri %> <%= link_to t('doorkeeper.applications.buttons.authorize'), oauth_authorization_path(client_id: @application.uid, redirect_uri: uri, response_type: 'code', scope: @application.scopes), class: 'btn btn-success', target: '_blank' %>

<%= t('.actions') %>

<%= link_to t('doorkeeper.applications.buttons.edit'), edit_oauth_application_path(@application), class: 'btn btn-primary' %>

<%= render 'delete_form', application: @application, submit_btn_css: 'btn btn-danger' %>

doorkeeper-5.0.2/app/views/doorkeeper/applications/edit.html.erb0000644000004100000410000000016713410042500025022 0ustar www-datawww-data

<%= t('.title') %>

<%= render 'form', application: @application %> doorkeeper-5.0.2/app/views/doorkeeper/applications/_form.html.erb0000644000004100000410000000510113410042500025170 0ustar www-datawww-data<%= form_for application, url: doorkeeper_submit_path(application), html: { role: 'form' } do |f| %> <% if application.errors.any? %>

<%= t('doorkeeper.applications.form.error') %>

<% end %>
<%= f.label :name, class: 'col-sm-2 col-form-label font-weight-bold' %>
<%= f.text_field :name, class: "form-control #{ 'is-invalid' if application.errors[:name].present? }", required: true %> <%= doorkeeper_errors_for application, :name %>
<%= f.label :redirect_uri, class: 'col-sm-2 col-form-label font-weight-bold' %>
<%= f.text_area :redirect_uri, class: "form-control #{ 'is-invalid' if application.errors[:redirect_uri].present? }" %> <%= doorkeeper_errors_for application, :redirect_uri %> <%= t('doorkeeper.applications.help.redirect_uri') %> <% if Doorkeeper.configuration.native_redirect_uri %> <%= raw t('doorkeeper.applications.help.native_redirect_uri', native_redirect_uri: content_tag(:code, class: 'bg-light') { Doorkeeper.configuration.native_redirect_uri }) %> <% end %>
<%= f.label :confidential, class: 'col-sm-2 form-check-label font-weight-bold' %>
<%= f.check_box :confidential, class: "checkbox #{ 'is-invalid' if application.errors[:confidential].present? }" %> <%= doorkeeper_errors_for application, :confidential %> <%= t('doorkeeper.applications.help.confidential') %>
<%= f.label :scopes, class: 'col-sm-2 col-form-label font-weight-bold' %>
<%= f.text_field :scopes, class: "form-control #{ 'has-error' if application.errors[:scopes].present? }" %> <%= doorkeeper_errors_for application, :scopes %> <%= t('doorkeeper.applications.help.scopes') %>
<%= f.submit t('doorkeeper.applications.buttons.submit'), class: 'btn btn-primary' %> <%= link_to t('doorkeeper.applications.buttons.cancel'), oauth_applications_path, class: 'btn btn-secondary' %>
<% end %> doorkeeper-5.0.2/app/views/layouts/0000755000004100000410000000000013410042500017307 5ustar www-datawww-datadoorkeeper-5.0.2/app/views/layouts/doorkeeper/0000755000004100000410000000000013410042500021446 5ustar www-datawww-datadoorkeeper-5.0.2/app/views/layouts/doorkeeper/application.html.erb0000644000004100000410000000101713410042500025405 0ustar www-datawww-data <%= t('doorkeeper.layouts.application.title') %> <%= stylesheet_link_tag "doorkeeper/application" %> <%= csrf_meta_tags %>
<%- if flash[:notice].present? %>
<%= flash[:notice] %>
<% end -%> <%= yield %>
doorkeeper-5.0.2/app/views/layouts/doorkeeper/admin.html.erb0000644000004100000410000000222613410042500024175 0ustar www-datawww-data <%= t('doorkeeper.layouts.admin.title') %> <%= stylesheet_link_tag "doorkeeper/admin/application" %> <%= csrf_meta_tags %>
<%- if flash[:notice].present? %>
<%= flash[:notice] %>
<% end -%> <%= yield %>
doorkeeper-5.0.2/.hound.yml0000644000004100000410000000004213410042500015604 0ustar www-datawww-dataruby: config_file: .rubocop.yml doorkeeper-5.0.2/Rakefile0000644000004100000410000000101413410042500015333 0ustar www-datawww-datarequire '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/0000755000004100000410000000000013410042500014440 5ustar www-datawww-datadoorkeeper-5.0.2/lib/doorkeeper/0000755000004100000410000000000013410042500016577 5ustar www-datawww-datadoorkeeper-5.0.2/lib/doorkeeper/grape/0000755000004100000410000000000013410042500017675 5ustar www-datawww-datadoorkeeper-5.0.2/lib/doorkeeper/grape/helpers.rb0000644000004100000410000000260113410042500021663 0ustar www-datawww-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.rb0000644000004100000410000000063113410042500025164 0ustar www-datawww-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.rb0000644000004100000410000000046413410042500020615 0ustar www-datawww-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.rb0000644000004100000410000000042513410042500020047 0ustar www-datawww-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/0000755000004100000410000000000013410042500020241 5ustar www-datawww-datadoorkeeper-5.0.2/lib/doorkeeper/helpers/controller.rb0000644000004100000410000000337713410042500022763 0ustar www-datawww-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/0000755000004100000410000000000013410042500020062 5ustar www-datawww-datadoorkeeper-5.0.2/lib/doorkeeper/models/access_token_mixin.rb0000644000004100000410000002170213410042500024256 0ustar www-datawww-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/0000755000004100000410000000000013410042500021674 5ustar www-datawww-datadoorkeeper-5.0.2/lib/doorkeeper/models/concerns/accessible.rb0000644000004100000410000000052513410042500024320 0ustar www-datawww-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.rb0000644000004100000410000000106313410042500024237 0ustar www-datawww-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.rb0000644000004100000410000000260413410042500024165 0ustar www-datawww-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.rb0000644000004100000410000000043113410042500024156 0ustar www-datawww-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.rb0000644000004100000410000000057713410042500023526 0ustar www-datawww-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.rb0000644000004100000410000000166413410042500024203 0ustar www-datawww-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.rb0000644000004100000410000000701713410042500024254 0ustar www-datawww-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.rb0000644000004100000410000000310413410042500024114 0ustar www-datawww-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.rb0000644000004100000410000000234713410042500020446 0ustar www-datawww-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/0000755000004100000410000000000013410042500020267 5ustar www-datawww-datadoorkeeper-5.0.2/lib/doorkeeper/request/authorization_code.rb0000644000004100000410000000070313410042500024506 0ustar www-datawww-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.rb0000644000004100000410000000071313410042500023453 0ustar www-datawww-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.rb0000644000004100000410000000053313410042500022457 0ustar www-datawww-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.rb0000644000004100000410000000052413410042500021735 0ustar www-datawww-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.rb0000644000004100000410000000053013410042500024445 0ustar www-datawww-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.rb0000644000004100000410000000052213410042500021525 0ustar www-datawww-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.rb0000644000004100000410000000061213410042500022455 0ustar www-datawww-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.rb0000644000004100000410000000222113410042500020611 0ustar www-datawww-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.rb0000644000004100000410000000177413410042500020402 0ustar www-datawww-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.rb0000644000004100000410000000103613410042500023446 0ustar www-datawww-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/0000755000004100000410000000000013410042500017374 5ustar www-datawww-datadoorkeeper-5.0.2/lib/doorkeeper/orm/active_record.rb0000644000004100000410000000221013410042500022525 0ustar www-datawww-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/0000755000004100000410000000000013410042500022205 5ustar www-datawww-datadoorkeeper-5.0.2/lib/doorkeeper/orm/active_record/stale_records_cleaner.rb0000644000004100000410000000120413410042500027051 0ustar www-datawww-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.rb0000644000004100000410000000165113410042500025171 0ustar www-datawww-datamodule 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.rb0000644000004100000410000000454313410042500025043 0ustar www-datawww-datamodule 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.rb0000644000004100000410000000273413410042500025201 0ustar www-datawww-datamodule 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/0000755000004100000410000000000013410042500017711 5ustar www-datawww-datadoorkeeper-5.0.2/lib/doorkeeper/rails/routes.rb0000644000004100000410000000574213410042500021567 0ustar www-datawww-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.rb0000644000004100000410000000436613410042500021711 0ustar www-datawww-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/0000755000004100000410000000000013410042500021232 5ustar www-datawww-datadoorkeeper-5.0.2/lib/doorkeeper/rails/routes/mapper.rb0000644000004100000410000000114413410042500023043 0ustar www-datawww-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.rb0000644000004100000410000000162513410042500023216 0ustar www-datawww-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.rb0000644000004100000410000000117513410042500021445 0ustar www-datawww-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/0000755000004100000410000000000013410042500017521 5ustar www-datawww-datadoorkeeper-5.0.2/lib/doorkeeper/rake/db.rake0000644000004100000410000000264413410042500020760 0ustar www-datawww-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.rake0000644000004100000410000000014013410042500021520 0ustar www-datawww-data# frozen_string_literal: true namespace :doorkeeper do task setup: :environment do end end doorkeeper-5.0.2/lib/doorkeeper/oauth.rb0000644000004100000410000000052013410042500020241 0ustar www-datawww-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.rb0000644000004100000410000000212413410042500020431 0ustar www-datawww-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/0000755000004100000410000000000013410042500017717 5ustar www-datawww-datadoorkeeper-5.0.2/lib/doorkeeper/oauth/client/0000755000004100000410000000000013410042500021175 5ustar www-datawww-datadoorkeeper-5.0.2/lib/doorkeeper/oauth/client/credentials.rb0000644000004100000410000000204113410042500024014 0ustar www-datawww-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.rb0000644000004100000410000000054713410042500023102 0ustar www-datawww-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.rb0000644000004100000410000000141713410042500025321 0ustar www-datawww-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/0000755000004100000410000000000013410042500023552 5ustar www-datawww-datadoorkeeper-5.0.2/lib/doorkeeper/oauth/client_credentials/validation.rb0000644000004100000410000000176713410042500026244 0ustar www-datawww-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.rb0000644000004100000410000000057213410042500025542 0ustar www-datawww-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.rb0000644000004100000410000000212513410042500025411 0ustar www-datawww-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.rb0000644000004100000410000000236013410042500026400 0ustar www-datawww-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/0000755000004100000410000000000013410042500021361 5ustar www-datawww-datadoorkeeper-5.0.2/lib/doorkeeper/oauth/helpers/unique_token.rb0000644000004100000410000000057313410042500024421 0ustar www-datawww-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.rb0000644000004100000410000000273213410042500024175 0ustar www-datawww-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.rb0000644000004100000410000000203213410042500024500 0ustar www-datawww-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/0000755000004100000410000000000013410042500022617 5ustar www-datawww-datadoorkeeper-5.0.2/lib/doorkeeper/oauth/authorization/uri_builder.rb0000644000004100000410000000147213410042500025455 0ustar www-datawww-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.rb0000644000004100000410000000053413410042500024632 0ustar www-datawww-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.rb0000644000004100000410000000437013410042500024270 0ustar www-datawww-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.rb0000644000004100000410000000277613410042500024072 0ustar www-datawww-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.rb0000644000004100000410000000373613410042500023324 0ustar www-datawww-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.rb0000644000004100000410000000143513410042500023305 0ustar www-datawww-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.rb0000644000004100000410000000130113410042500021515 0ustar www-datawww-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.rb0000644000004100000410000000043713410042500021401 0ustar www-datawww-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.rb0000644000004100000410000000510513410042500024013 0ustar www-datawww-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.rb0000644000004100000410000000220613410042500025010 0ustar www-datawww-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.rb0000644000004100000410000000316513410042500022733 0ustar www-datawww-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.rb0000644000004100000410000000477613410042500025724 0ustar www-datawww-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.rb0000644000004100000410000000247013410042500021543 0ustar www-datawww-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.rb0000644000004100000410000000603213410042500024653 0ustar www-datawww-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.rb0000644000004100000410000000173313410042500023140 0ustar www-datawww-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.rb0000644000004100000410000000366413410042500021375 0ustar www-datawww-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.rb0000644000004100000410000000205113410042500023072 0ustar www-datawww-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.rb0000644000004100000410000000134513410042500025652 0ustar www-datawww-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.rb0000644000004100000410000000145613410042500022734 0ustar www-datawww-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.rb0000644000004100000410000001035213410042500024345 0ustar www-datawww-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.rb0000644000004100000410000003073113410042500020375 0ustar www-datawww-datamodule 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.rb0000644000004100000410000000523713410042500017133 0ustar www-datawww-datarequire '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/0000755000004100000410000000000013410042500016611 5ustar www-datawww-datadoorkeeper-5.0.2/lib/generators/doorkeeper/0000755000004100000410000000000013410042500020750 5ustar www-datawww-datadoorkeeper-5.0.2/lib/generators/doorkeeper/confidential_applications_generator.rb0000644000004100000410000000154213410042500030552 0ustar www-datawww-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.rb0000644000004100000410000000067113410042500024504 0ustar www-datawww-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/0000755000004100000410000000000013410042500022746 5ustar www-datawww-datadoorkeeper-5.0.2/lib/generators/doorkeeper/templates/add_owner_to_application_migration.rb.erb0000644000004100000410000000045213410042500033143 0ustar www-datawww-dataclass 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.erb0000644000004100000410000000036513410042500030347 0ustar www-datawww-dataclass 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.erb0000644000004100000410000000041113410042500032555 0ustar www-datawww-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.erb0000644000004100000410000000470013410042500026034 0ustar www-datawww-dataclass 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.rb0000644000004100000410000002212113410042500025614 0ustar www-datawww-dataDoorkeeper.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/README0000644000004100000410000000076313410042500023634 0ustar www-datawww-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! =============================================================================== ././@LongLink0000644000000000000000000000015000000000000011577 Lustar rootrootdoorkeeper-5.0.2/lib/generators/doorkeeper/templates/add_previous_refresh_token_to_access_tokens.rb.erbdoorkeeper-5.0.2/lib/generators/doorkeeper/templates/add_previous_refresh_token_to_access_tokens.rb.0000644000004100000410000000037213410042500034363 0ustar www-datawww-dataclass 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.rb0000644000004100000410000000154013410042500027060 0ustar www-datawww-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.rb0000644000004100000410000000142513410042500024267 0ustar www-datawww-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.rb0000644000004100000410000000145013410042500025334 0ustar www-datawww-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.rb0000644000004100000410000000111013410042500025002 0ustar www-datawww-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.rb0000644000004100000410000000207313410042500030137 0ustar www-datawww-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.md0000644000004100000410000000203513410042500016123 0ustar www-datawww-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/0000755000004100000410000000000013410042500015167 5ustar www-datawww-datadoorkeeper-5.0.2/vendor/assets/0000755000004100000410000000000013410042500016471 5ustar www-datawww-datadoorkeeper-5.0.2/vendor/assets/stylesheets/0000755000004100000410000000000013410042500021045 5ustar www-datawww-datadoorkeeper-5.0.2/vendor/assets/stylesheets/doorkeeper/0000755000004100000410000000000013410042500023204 5ustar www-datawww-datadoorkeeper-5.0.2/vendor/assets/stylesheets/doorkeeper/bootstrap.min.css0000644000004100000410000043270013410042500026523 0ustar www-datawww-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/0000755000004100000410000000000013410042500015137 5ustar www-datawww-datadoorkeeper-5.0.2/config/locales/0000755000004100000410000000000013410042500016561 5ustar www-datawww-datadoorkeeper-5.0.2/config/locales/en.yml0000644000004100000410000001251613410042500017713 0ustar www-datawww-dataen: 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.md0000644000004100000410000007243313410042500015001 0ustar www-datawww-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/Gemfile0000644000004100000410000000040013410042500015157 0ustar www-datawww-datasource "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/Dangerfile0000644000004100000410000000547713410042500015672 0ustar www-datawww-dataCHANGELOG_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.yml0000644000004100000410000000003013410042500016456 0ustar www-datawww-dataservice_name: travis-ci doorkeeper-5.0.2/.github/0000755000004100000410000000000013410042500015232 5ustar www-datawww-datadoorkeeper-5.0.2/.github/PULL_REQUEST_TEMPLATE.md0000644000004100000410000000117513410042500021037 0ustar www-datawww-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.md0000644000004100000410000000132213410042500017735 0ustar www-datawww-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.md0000644000004100000410000000017113410042500015302 0ustar www-datawww-dataSee [Upgrade Guides](https://github.com/doorkeeper-gem/doorkeeper/wiki/Migration-from-old-versions) in the project Wiki. doorkeeper-5.0.2/MIT-LICENSE0000644000004100000410000000205713410042500015332 0ustar www-datawww-dataCopyright 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.md0000644000004100000410000000126413410042500015466 0ustar www-datawww-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.yml0000644000004100000410000000114113410042500016323 0ustar www-datawww-datadependency_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]