doorkeeper-5.6.6/0000755000004100000410000000000014422352653013725 5ustar www-datawww-datadoorkeeper-5.6.6/README.md0000644000004100000410000002334714422352653015215 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) [![CI](https://github.com/doorkeeper-gem/doorkeeper/actions/workflows/ci.yml/badge.svg)](https://github.com/doorkeeper-gem/doorkeeper/actions/workflows/ci.yml) [![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=main)](https://coveralls.io/github/doorkeeper-gem/doorkeeper?branch=main) [![Reviewed by Hound](https://img.shields.io/badge/Reviewed_by-Hound-8E64B0.svg)](https://houndci.com) [![GuardRails badge](https://badges.guardrails.io/doorkeeper-gem/doorkeeper.svg?token=66768ce8f6995814df81f65a2cff40f739f688492704f973e62809e15599bb62)](https://dashboard.guardrails.io/default/gh/doorkeeper-gem/doorkeeper) [![Dependabot](https://img.shields.io/badge/dependabot-enabled-success.svg)](https://dependabot.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://datatracker.ietf.org/doc/html/rfc6749) - [Authorization Code Flow](https://datatracker.ietf.org/doc/html/rfc6749#section-4.1) - [Access Token Scopes](https://datatracker.ietf.org/doc/html/rfc6749#section-3.3) - [Refresh token](https://datatracker.ietf.org/doc/html/rfc6749#section-1.5) - [Implicit grant](https://datatracker.ietf.org/doc/html/rfc6749#section-4.2) - [Resource Owner Password Credentials](https://datatracker.ietf.org/doc/html/rfc6749#section-4.3) - [Client Credentials](https://datatracker.ietf.org/doc/html/rfc6749#section-4.4) - [OAuth 2.0 Token Revocation](https://datatracker.ietf.org/doc/html/rfc7009) - [OAuth 2.0 Token Introspection](https://datatracker.ietf.org/doc/html/rfc7662) - [OAuth 2.0 Threat Model and Security Considerations](https://datatracker.ietf.org/doc/html/rfc6819) - [OAuth 2.0 for Native Apps](https://datatracker.ietf.org/doc/html/rfc8252) - [Proof Key for Code Exchange by OAuth Public Clients](https://datatracker.ietf.org/doc/html/rfc7636) ## Table of Contents - [Documentation](#documentation) - [Installation](#installation) - [Ruby on Rails](#ruby-on-rails) - [Grape](#grape) - [ORMs](#orms) - [Extensions](#extensions) - [Example Applications](#example-applications) - [Tutorials](#tutorials) - [Sponsors](#sponsors) - [Development](#development) - [Contributing](#contributing) - [Contributors](#contributors) - [License](#license) ## Documentation This documentation is valid for `main` branch. Please check the documentation for the version of doorkeeper you are using in: https://github.com/doorkeeper-gem/doorkeeper/releases. Additionally, other resources can be found on: - [Guides](https://doorkeeper.gitbook.io/guides/) with how-to get started and configuration documentation - See the [Wiki](https://github.com/doorkeeper-gem/doorkeeper/wiki) with articles and other documentation - Screencast from [railscasts.com](http://railscasts.com/): [#353 OAuth with Doorkeeper](http://railscasts.com/episodes/353-oauth-with-doorkeeper) - See [upgrade guides](https://github.com/doorkeeper-gem/doorkeeper/wiki/Migration-from-old-versions) - For general questions, please post on [Stack Overflow](http://stackoverflow.com/questions/tagged/doorkeeper) - See [SECURITY.md](SECURITY.md) for this project's security disclose policy ## Installation Installation depends on the framework you're using. The first step is to add the following to your Gemfile: ```ruby gem 'doorkeeper' ``` And run `bundle install`. After this, check out the guide related to the framework you're using. ### Ruby on Rails Doorkeeper currently supports Ruby on Rails >= 5.0. See the guide [here](https://doorkeeper.gitbook.io/guides/ruby-on-rails/getting-started). ### Grape Guide for integration with Grape framework can be found [here](https://doorkeeper.gitbook.io/guides/grape/grape). ## ORMs Doorkeeper supports Active Record by default, but can be configured to work with the following ORMs: | ORM | Support via | | :--- | :--- | | Active Record | by default | | MongoDB | [doorkeeper-gem/doorkeeper-mongodb](https://github.com/doorkeeper-gem/doorkeeper-mongodb) | | Sequel | [nbulaj/doorkeeper-sequel](https://github.com/nbulaj/doorkeeper-sequel) | | Couchbase | [acaprojects/doorkeeper-couchbase](https://github.com/acaprojects/doorkeeper-couchbase) | | RethinkDB | [aca-labs/doorkeeper-rethinkdb](https://github.com/aca-labs/doorkeeper-rethinkdb) | ## Extensions Extensions that are not included by default and can be installed separately. | | Link | | :--- | :--- | | OpenID Connect extension | [doorkeeper-gem/doorkeeper-openid\_connect](https://github.com/doorkeeper-gem/doorkeeper-openid_connect) | | JWT Token support | [doorkeeper-gem/doorkeeper-jwt](https://github.com/doorkeeper-gem/doorkeeper-jwt) | | Assertion grant extension | [doorkeeper-gem/doorkeeper-grants\_assertion](https://github.com/doorkeeper-gem/doorkeeper-grants_assertion) | | I18n translations | [doorkeeper-gem/doorkeeper-i18n](https://github.com/doorkeeper-gem/doorkeeper-i18n) | | CIBA - Client Initiated Backchannel Authentication Flow extension | [doorkeeper-ciba](https://github.com/autoseg/doorkeeper-ciba) | | Device Authorization Grant | [doorkeeper-device_authorization_grant](https://github.com/exop-group/doorkeeper-device_authorization_grant) | ## Example Applications These applications show how Doorkeeper works and how to integrate with it. Start with the oAuth2 server and use the clients to connect with the server. | Application | Link | | :--- | :--- | | OAuth2 Server with Doorkeeper | [doorkeeper-gem/doorkeeper-provider-app](https://github.com/doorkeeper-gem/doorkeeper-provider-app) | | Sinatra Client connected to Provider App | [doorkeeper-gem/doorkeeper-sinatra-client](https://github.com/doorkeeper-gem/doorkeeper-sinatra-client) | | Devise + Omniauth Client | [doorkeeper-gem/doorkeeper-devise-client](https://github.com/doorkeeper-gem/doorkeeper-devise-client) | 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). ## Tutorials 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. ## Sponsors [![OpenCollective](https://opencollective.com/doorkeeper-gem/backers/badge.svg)](#backers) [![OpenCollective](https://opencollective.com/doorkeeper-gem/sponsors/badge.svg)](#sponsors) Support this project by becoming a sponsor. Your logo will show up here with a link to your website. [[Become a sponsor](https://opencollective.com/doorkeeper-gem#sponsor)] > Codecademy supports open source as part of its mission to democratize tech. Come help us build the education the world deserves: [https://codecademy.com/about/careers](https://codecademy.com/about/careers?utm_source=doorkeeper-gem)
> If you prefer not to deal with the gory details of OAuth 2, need dedicated customer support & consulting, try the cloud-based SaaS version: [https://oauth.io](https://oauth.io/?utm_source=doorkeeper-gem)
> Wealthsimple is a financial company on a mission to help everyone achieve financial freedom by providing products and advice that are accessible and affordable. Using smart technology, Wealthsimple takes financial services that are often confusing, opaque and expensive and makes them simple, transparent, and low-cost. See what Investing on Autopilot is all about: [https://www.wealthsimple.com](https://www.wealthsimple.com/?utm_source=doorkeeper-gem) ## 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 Rails version: ``` BUNDLE_GEMFILE=gemfiles/rails_6_0.gemfile bundle exec rake ``` You can also experiment with the changes using `bin/console`. It uses in-memory SQLite database and default Doorkeeper config, but you can reestablish connection or reconfigure the gem if you need. ## 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](CONTRIBUTING.md). ## Contributors Thanks to all our [awesome contributors](https://github.com/doorkeeper-gem/doorkeeper/graphs/contributors)! ## License MIT License. Created in Applicake. Maintained by the community. doorkeeper-5.6.6/CHANGELOG.md0000644000004100000410000013566614422352653015557 0ustar www-datawww-data# Changelog See https://github.com/doorkeeper-gem/doorkeeper/wiki/Migration-from-old-versions for upgrade guides. User-visible changes worth mentioning. ## main - [#ID] Add your PR description here. ## 5.6.6 - [#1644] Update HTTP headers. - [#1646] Block public clients automatic authorization skip. - [#1648] Add custom token attributes to Refresh Token Request. - [#1649] Fixed custom_access_token_attributes related errors. # 5.6.5 - [#1602] Allow custom data to be stored inside access grants/tokens. - [#1634] Code refactoring for custom token attributes. - [#1639] Add grant type validation to avoid Internal Server Error for DELETE /oauth/authorize endpoint. # 5.6.4 - [#1633] Apply ORM configuration in #to_prepare block to avoid autoloading errors. # 5.6.3 - [#1622] Drop support for Rubies 2.5 and 2.6 - [#1605] Fix URI validation for Ruby 3.2+. - [#1625] Exclude endless access tokens from `StaleRecordsCleaner`. - [#1626] Remove deprecated `active_record_options` config option. - [#1631] Fix regression with redirect behavior after token lookup optimizations (redirect to app URI when found). - [#1630] Special case unique index creation for refresh_token on SQL Server. - [#1627] Lazy evaluate Doorkeeper config when loading files and executing initializers. ## 5.6.2 - [#1604] Fix fetching of the application when custom application_class defined. ## 5.6.1 - [#1593] Add support for Trilogy ActiveRecord adapter. - [#1597] Add optional support to use the url path for the native authorization code flow. Ports forward [#1143] from 4.4.3 - [#1599] Remove unnecessarily re-fetch of application object when creating an access token. ## 5.6.0 - [#1581] Consider `token_type_hint` when searching for access token in TokensController to avoid extra database calls. ## 5.6.0.rc2 - [#1558] Fixed bug: able to obtain a token with default scopes even if they are not present in the application scopes when using client credentials. - [#1567] Only filter `code` parameter if authorization_code grant flow is enabled. ## 5.6.0.rc1 - [#1551] Change lazy loading for ORM to be Ruby standard autoload. - [#1552] Remove duplicate IDs on Auth form to improve accessibility. - [#1542] Improve performance of `Doorkeeper::AccessToken#matching_token_for` using database specific SQL time math. **[IMPORTANT]**: API of the `Doorkeeper::AccessToken#matching_token_for` method has changed and now it returns only **active** access tokens (previously they were just not revoked). Please remember that the idea of the `reuse_access_token` option is to check for existing _active_ token (see configuration option description). ## 5.5.4 - [#1535] Revert changes introduced in #1528 to allow query params in `redirect_uri` as per the spec. ## 5.5.3 - [#1528] Don't allow extra query params in redirect_uri. - [#1525] I18n source for forbidden token error is now `doorkeeper.errors.messages.forbidden_token.missing_scope`. - [#1531] Disable `strict-loading` for Doorkeeper models by default. - [#1532] Add support for Rails 7. ## 5.5.2 - [#1502] Drop support for Ruby 2.4 because of EOL. - [#1504] Updated the url fragment in the comment for code documentation. - [#1512] Fix form behavior when response mode is form_post. - [#1511] Fix that authorization code is returned by fragment if response_mode is fragment. ## 5.5.1 - [#1496] Revoke `old_refresh_token` if `previous_refresh_token` is present. - [#1495] Fix `respond_to` undefined in API-only mode - [#1488] Verify client authentication for Resource Owner Password Grant when `config.skip_client_authentication_for_password_grant` is set and the client credentials are sent in a HTTP Basic auth header. ## 5.5.0 - [#1482] Simplify `TokenInfoController` to be overridable (extract response rendering). - [#1478] Fix ownership association and Rake tasks when custom models configured. - [#1477] Respect `ActiveRecord::Base.pluralize_table_names` for Doorkeeper table names. ## 5.5.0.rc2 - [#1473] Enable `Applications` and `AuthorizedApplications` controllers in API mode. **[IMPORTANT]** you can still skip these controllers using `skip_controllers` in `use_doorkeeper` inside `routes.rb`. Please do it in case you don't need them. - [#1472] Fix `establish_connection` configuration for custom defined models. - [#1471] Add support for Ruby 3.0. - [#1469] Check if `redirect_uri` exists. - [#1465] Memoize nil doorkeeper_token. - [#1459] Use built-in Ruby option to remove padding in PKCE code challenge value. - [#1457] Make owner_id a bigint for newly-generated owner migrations - [#1452] Empty previous_refresh_token only if present. - [#1440] Validate empty host in redirect_uri. - [#1438] Add form post response mode. - [#1458] Make `config.skip_client_authentication_for_password_grant` a long term configuration option. ## 5.5.0.rc1 - [#1435] Make error response not redirectable when client is unauthorized - [#1426] Ensure ActiveRecord callbacks are executed on token revocation. - [#1407] Remove redundant and complex to support helpers froms tests (`should_have_json`, etc). - [#1416] Don't add introspection route if token introspection completely disabled. - [#1410] Properly memoize `current_resource_owner` value (consider `nil` and `false` values). - [#1415] Ignore PKCE params for non-PKCE grants. - [#1418] Add ability to register custom OAuth Grant Flows. - [#1420] Require client authentication for Resource Owner Password Grant as stated in OAuth RFC. **[IMPORTANT]** you need to create a new OAuth client (`Doorkeeper::Application`) if you didn't have it before and use client credentials in HTTP Basic auth if you previously used this grant flow without client authentication. To opt out of this you could set the `skip_client_authentication_for_password_grant` configuration option to `true`, but note that this is in violation of the OAuth spec and represents a security risk. All the users of your provider application now need to include client credentials when they use this grant flow. - [#1421] Add Resource Owner instance to authorization hook context for `custom_access_token_expires_in` configuration option to allow resource owner based Access Tokens TTL. ## 5.4.0 - [#1404] Make `Doorkeeper::Application#read_attribute_for_serialization` public. ## 5.4.0.rc2 - [#1371] Add `#as_json` method and attributes serialization restriction for Application model. Fixes information disclosure vulnerability (CVE-2020-10187). **[IMPORTANT]** you need to re-implement `#as_json` method for Doorkeeper Application model if you previously used `#to_json` serialization with custom options or attributes or rely on JSON response from /oauth/applications.json or /oauth/authorized_applications.json. This change is a breaking change which restricts serialized attributes to a very small set of columns. - [#1395] Fix `NameError: uninitialized constant Doorkeeper::AccessToken` for Rake tasks. - [#1397] Add `as: :doorkeeper_application` on Doorkeeper application form in order to support custom configured application model. - [#1400] Correctly yield the application instance to `allow_grant_flow_for_client?` config option (fixes #1398). - [#1402] Handle trying authorization with client credentials. ## 5.4.0.rc1 - [#1366] Sets expiry of token generated using `refresh_token` to that of original token. (Fixes #1364) - [#1354] Add `authorize_resource_owner_for_client` option to authorize the calling user to access an application. - [#1355] Allow to enable polymorphic Resource Owner association for Access Token & Grant models (`use_polymorphic_resource_owner` configuration option). **[IMPORTANT]** Review your custom patches or extensions for Doorkeeper internals if you have such - since now Doorkeeper passes Resource Owner instance to every objects and not just it's ID. See PR description for details. - [#1356] Remove duplicated scopes from Access Tokens and Grants on attribute assignment. - [#1357] Fix `Doorkeeper::OAuth::PreAuthorization#as_json` method causing `Stack level too deep` error with AMS (fix #1312). - [#1358] Deprecate `active_record_options` configuration option. - [#1359] Refactor Doorkeeper configuration options DSL to make it easy to reuse it in external extensions. - [#1360] Increase `matching_token_for` lookup size to 10 000 and make it configurable. - [#1371] Fix controllers to use valid classes in case Doorkeeper has custom models configured. - [#1370] Fix revocation response for invalid token and unauthorized requests to conform with RFC 7009 (fixes #1362). **[IMPORTANT]** now fully according to RFC 7009 nobody can do a revocation request without `client_id` (for public clients) and `client_secret` (for private clients). Please update your apps to include that info in the revocation request payload. - [#1373] Make Doorkeeper routes mapper reusable in extensions. - [#1374] Revoke and issue client credentials token in a transaction with a row lock. - [#1384] Add context object with auth/pre_auth and issued_token for authorization hooks. - [#1387] Add `AccessToken#create_for` and use in `RefreshTokenRequest`. - [#1392] Fix `enable_polymorphic_resource_owner` migration template to have proper index name. - [#1393] Improve Applications #show page with more informative data on client secret and scopes. - [#1394] Use Ruby `autoload` feature to load Doorkeeper files. ## 5.3.3 - [#1404] Backport: Make `Doorkeeper::Application#read_attribute_for_serialization` public. ## 5.3.2 - [#1371] Backport: add `#as_json` method and attributes serialization restriction for Application model. Fixes information disclosure vulnerability (CVE-2020-10187). ## 5.3.1 - [#1360] Backport: Increase `matching_token_for` batch lookup size to 10 000 and make it configurable. ## 5.3.0 - [#1339] Validate Resource Owner in `PasswordAccessTokenRequest` against `nil` and `false` values. - [#1341] Fix `refresh_token_revoked_on_use` with `hash_token_secrets` enabled. - [#1343] Fix ruby 2.7 kwargs warning in InvalidTokenResponse. - [#1345] Allow to set custom classes for Doorkeeper models, extract reusable AR mixins. - [#1346] Refactor `Doorkeeper::Application#to_json` into convenient `#as_json` (fix #1344). - [#1349] Fix `Doorkeeper::Application` AR associations using an incorrect foreign key name when using a custom class. - [#1318] Make existing token revocation for client credentials optional and disable it by default. **[IMPORTANT]** This is a change compared to the behaviour of version 5.2. If you were relying on access tokens being revoked once the same client requested a new access token, reenable it with `revoke_previous_client_credentials_token` in Doorkeeper initialization file. ## 5.2.6 - [#1404] Backport: Make `Doorkeeper::Application#read_attribute_for_serialization` public. ## 5.2.5 - [#1371] Backport: add `#as_json` method and attributes serialization restriction for Application model. Fixes information disclosure vulnerability (CVE-2020-10187). ## 5.2.4 - [#1360] Backport: Increase `matching_token_for` batch lookup size to 10 000 and make it configurable. ## 5.2.3 - [#1334] Remove `application_secret` flash helper and `redirect_to` keyword. - [#1331] Move redirect_uri_validator to where it is used (`Application` model). - [#1326] Move response_type check in pre_authorization to a method to be easily to override. - [#1329] Fix `find_in_batches` order warning. ## 5.2.2 - [#1320] Call configured `authenticate_resource_owner` method once per request. - [#1315] Allow generation of new secret with `Doorkeeper::Application#renew_secret`. - [#1309] Allow `Doorkeeper::Application#to_json` to work without arguments. ## 5.2.1 - [#1308] Fix flash types for `api_only` mode (no flashes for `ActionController::API`). - [#1306] Fix interpolation of `missing_param` I18n. ## 5.2.0 - [#1305] Make `Doorkeeper::ApplicationController` to inherit from `ActionController::API` in cases when `api_mode` enabled (fixes #1302). ## 5.2.0.rc3 - [#1298] Slice strong params so doesn't error with Rails forms. - [#1300] Limiting access to attributes of pre_authorization. - [#1296] Adding client_id to strong parameters. **[IMPORTANT]** `Doorkeeper::Server#client_via_uid` was removed. - [#1293] Move ar specific redirect uri validator to ar orm directory. - [#1288] Allow to pass attributes to the `Doorkeeper::OAuth::PreAuthorization#as_json` method to customize the PreAuthorization response. - [#1286] Add ability to customize grant flows per application (OAuth client) (#1245 , #1207) - [#1283] Allow to customize base class for `Doorkeeper::ApplicationMetalController` (new configuration option called `base_metal_controller` (fix #1273). - [#1277] Prevent requested scope be empty on authorization request, handle and add description for invalid request. ## 5.2.0.rc2 - [#1270] Find matching tokens in batches for `reuse_access_token` option (fix #1193). - [#1271] Reintroduce existing token revocation for client credentials. **[IMPORTANT]** If you rely on being able to fetch multiple access tokens from the same client using client credentials flow, you should skip to version 5.3, where this behaviour is deactivated by default. - [#1269] Update initializer template documentation. - [#1266] Use strong parameters within pre-authorization. - [#1264] Add :before_successful_authorization and :after_successful_authorization hooks in TokensController - [#1263] Response properly when introspection fails and fix configurations's user guide. ## 5.2.0.rc1 - [#1260], [#1262] Improve Token Introspection configuration option (access to tokens, client). - [#1257] Add constraint configuration when using client authentication on introspection endpoint. - [#1252] Returning `unauthorized` when the revocation of the token should not be performed due to wrong permissions. - [#1249] Specify case sensitive uniqueness to remove Rails 6 deprecation message - [#1248] Display the Application Secret in HTML after creating a new application even when `hash_application_secrets` is used. - [#1248] Return the unhashed Application Secret in the JSON response after creating new application even when `hash_application_secrets` is used. - [#1238] Better support for native app with support for custom scheme and localhost redirection. ## 5.1.2 - [#1404] Backport: Make `Doorkeeper::Application#read_attribute_for_serialization` public. ## 5.1.1 - [#1371] Backport: add `#as_json` method and attributes serialization restriction for Application model. Fixes information disclosure vulnerability (CVE-2020-10187). ## 5.1.0 - [#1243] Add nil check operator in token checking at token introspection. - [#1241] Explaining foreign key options for resource owner in a single place - [#1237] Allow to set blank redirect URI if Doorkeeper configured to use redirect URI-less grant flows. - [#1234] Fix `StaleRecordsCleaner` to properly work with big amount of records. - [#1228] Allow to explicitly set non-expiring tokens in `custom_access_token_expires_in` configuration option using `Float::INFINITY` return value. - [#1224] Do not try to store token if not found by fallback hashing strategy. - [#1223] Update Hound/Rubocop rules, correct Doorkeeper codebase to follow style-guides. - [#1220] Drop Rails 4.2 & Ruby < 2.4 support. ## 5.1.0.rc2 - [#1208] Unify hashing implementation into secret storing strategies **[IMPORTANT]** If you have been using the master branch of doorkeeper with bcrypt in your Gemfile.lock, your application secrets have been hashed using BCrypt. To restore this behavior, use the initializer option `hash_application_secrets using: 'Doorkeeper::SecretStoring::BCrypt`. - [#1216] Add nil check to `expires_at` method. - [#1215] Fix deprecates for Rails 6. - [#1214] Scopes field accepts array. - [#1209] Fix tokens validation for Token Introspection request. - [#1202] Use correct HTTP status codes for error responses. **[IMPORTANT]**: this change might break your application if you were relying on the previous 401 status codes, this is now a 400 by default, or a 401 for `invalid_client` and `invalid_token` errors. - [#1201] Fix custom TTL block `client` parameter to always be an `Doorkeeper::Application` instance. **[IMPORTANT]**: those who defined `custom_access_token_expires_in` configuration option need to check their block implementation: if you are using `oauth_client.application` to get `Doorkeeper::Application` instance, then you need to replace it with just `oauth_client`. - [#1200] Increase default Doorkeeper access token value complexity (`urlsafe_base64` instead of just `hex`) matching RFC6749/RFC6750. **[IMPORTANT]**: this change have possible side-effects in case you have custom database constraints for access token value, application secrets, refresh tokens or you patched Doorkeeper models and introduced token value validations, or you are using database with case-insensitive WHERE clause like MySQL (you can face some collisions). Before this change access token value matched `[a-f0-9]` regex, and now it matches `[a-zA-Z0-9\-_]`. In case you have such restrictions and your don't use custom token generator please change configuration option `default_generator_method` to `:hex`. - [#1195] Allow to customize Token Introspection response (fixes #1194). - [#1189] Option to set `token_reuse_limit`. - [#1191] Try to load bcrypt for hashing of application secrets, but add fallback. ## 5.1.0.rc1 - [#1188] Use `params` instead of `request.POST` in tokens controller (fixes #1183). - [#1182] Fix loopback IP redirect URIs to conform with RFC8252, p. 7.3 (fixes #1170). - [#1179] Authorization Code Grant Flow without client id returns invalid_client error. - [#1177] Allow to limit `scopes` for certain `grant_types` - [#1176] Fix test factory support for `factory_bot_rails` - [#1175] Internal refactor: use `scopes_string` inside `scopes`. - [#1168] Allow optional hashing of tokens and secrets. - [#1164] Fix error when `root_path` is not defined. - [#1162] Fix `enforce_content_type` for requests without body. ## 5.0.3 - [#1371] Backport: add `#as_json` method and attributes serialization restriction for Application model. Fixes information disclosure vulnerability (CVE-2020-10187). ## 5.0.2 - [#1158] Fix initializer template: change `handle_auth_errors` option - [#1157] Remove redundant index from migration template. ## 5.0.1 - [#1154] Refactor `StaleRecordsCleaner` to be ORM agnostic. - [#1152] Fix migration template: change resource owner data type from integer to Rails generic `references` - [#1151] Fix Refresh Token strategy: add proper validation of client credentials both for Public & Private clients. - [#1149] Fix for `URIChecker#valid_for_authorization?` false negative when query is blank, but `?` present. - [#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) ## 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 - [#1122] Fix AuthorizationsController#new error response to be in JSON format - [#1119] Fix token revocation for OAuth apps using "implicit" grant flow - [#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 - [#1108] Simple formatting of callback URLs when listing oauth applications - [#1106] Restrict access to AdminController with 'Forbidden 403' if admin_authenticator is not configured by developers. ## 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.6.6/doorkeeper.gemspec0000644000004100000410000002600314422352653017432 0ustar www-datawww-data######################################################### # This file has been automatically generated by gem2tgz # ######################################################### # -*- encoding: utf-8 -*- # stub: doorkeeper 5.6.6 ruby lib Gem::Specification.new do |s| s.name = "doorkeeper".freeze s.version = "5.6.6" s.required_rubygems_version = Gem::Requirement.new(">= 0".freeze) if s.respond_to? :required_rubygems_version= s.metadata = { "bug_tracker_uri" => "https://github.com/doorkeeper-gem/doorkeeper/issues", "changelog_uri" => "https://github.com/doorkeeper-gem/doorkeeper/blob/main/CHANGELOG.md", "documentation_uri" => "https://doorkeeper.gitbook.io/guides/", "homepage_uri" => "https://github.com/doorkeeper-gem/doorkeeper", "source_code_uri" => "https://github.com/doorkeeper-gem/doorkeeper" } if s.respond_to? :metadata= s.require_paths = ["lib".freeze] s.authors = ["Felipe Elias Philipp".freeze, "Tute Costa".freeze, "Jon Moss".freeze, "Nikita Bulai".freeze] s.date = "2023-03-29" s.description = "Doorkeeper is an OAuth 2 provider for Rails and Grape.".freeze s.email = ["bulaj.nikita@gmail.com".freeze] s.files = ["CHANGELOG.md".freeze, "MIT-LICENSE".freeze, "README.md".freeze, "app/assets/stylesheets/doorkeeper/admin/application.css".freeze, "app/assets/stylesheets/doorkeeper/application.css".freeze, "app/controllers/doorkeeper/application_controller.rb".freeze, "app/controllers/doorkeeper/application_metal_controller.rb".freeze, "app/controllers/doorkeeper/applications_controller.rb".freeze, "app/controllers/doorkeeper/authorizations_controller.rb".freeze, "app/controllers/doorkeeper/authorized_applications_controller.rb".freeze, "app/controllers/doorkeeper/token_info_controller.rb".freeze, "app/controllers/doorkeeper/tokens_controller.rb".freeze, "app/helpers/doorkeeper/dashboard_helper.rb".freeze, "app/views/doorkeeper/applications/_delete_form.html.erb".freeze, "app/views/doorkeeper/applications/_form.html.erb".freeze, "app/views/doorkeeper/applications/edit.html.erb".freeze, "app/views/doorkeeper/applications/index.html.erb".freeze, "app/views/doorkeeper/applications/new.html.erb".freeze, "app/views/doorkeeper/applications/show.html.erb".freeze, "app/views/doorkeeper/authorizations/error.html.erb".freeze, "app/views/doorkeeper/authorizations/form_post.html.erb".freeze, "app/views/doorkeeper/authorizations/new.html.erb".freeze, "app/views/doorkeeper/authorizations/show.html.erb".freeze, "app/views/doorkeeper/authorized_applications/_delete_form.html.erb".freeze, "app/views/doorkeeper/authorized_applications/index.html.erb".freeze, "app/views/layouts/doorkeeper/admin.html.erb".freeze, "app/views/layouts/doorkeeper/application.html.erb".freeze, "config/locales/en.yml".freeze, "lib/doorkeeper.rb".freeze, "lib/doorkeeper/config.rb".freeze, "lib/doorkeeper/config/abstract_builder.rb".freeze, "lib/doorkeeper/config/option.rb".freeze, "lib/doorkeeper/config/validations.rb".freeze, "lib/doorkeeper/engine.rb".freeze, "lib/doorkeeper/errors.rb".freeze, "lib/doorkeeper/grant_flow.rb".freeze, "lib/doorkeeper/grant_flow/fallback_flow.rb".freeze, "lib/doorkeeper/grant_flow/flow.rb".freeze, "lib/doorkeeper/grant_flow/registry.rb".freeze, "lib/doorkeeper/grape/authorization_decorator.rb".freeze, "lib/doorkeeper/grape/helpers.rb".freeze, "lib/doorkeeper/helpers/controller.rb".freeze, "lib/doorkeeper/models/access_grant_mixin.rb".freeze, "lib/doorkeeper/models/access_token_mixin.rb".freeze, "lib/doorkeeper/models/application_mixin.rb".freeze, "lib/doorkeeper/models/concerns/accessible.rb".freeze, "lib/doorkeeper/models/concerns/expirable.rb".freeze, "lib/doorkeeper/models/concerns/expiration_time_sql_math.rb".freeze, "lib/doorkeeper/models/concerns/orderable.rb".freeze, "lib/doorkeeper/models/concerns/ownership.rb".freeze, "lib/doorkeeper/models/concerns/polymorphic_resource_owner.rb".freeze, "lib/doorkeeper/models/concerns/resource_ownerable.rb".freeze, "lib/doorkeeper/models/concerns/reusable.rb".freeze, "lib/doorkeeper/models/concerns/revocable.rb".freeze, "lib/doorkeeper/models/concerns/scopes.rb".freeze, "lib/doorkeeper/models/concerns/secret_storable.rb".freeze, "lib/doorkeeper/oauth.rb".freeze, "lib/doorkeeper/oauth/authorization/code.rb".freeze, "lib/doorkeeper/oauth/authorization/context.rb".freeze, "lib/doorkeeper/oauth/authorization/token.rb".freeze, "lib/doorkeeper/oauth/authorization/uri_builder.rb".freeze, "lib/doorkeeper/oauth/authorization_code_request.rb".freeze, "lib/doorkeeper/oauth/base_request.rb".freeze, "lib/doorkeeper/oauth/base_response.rb".freeze, "lib/doorkeeper/oauth/client.rb".freeze, "lib/doorkeeper/oauth/client/credentials.rb".freeze, "lib/doorkeeper/oauth/client_credentials/creator.rb".freeze, "lib/doorkeeper/oauth/client_credentials/issuer.rb".freeze, "lib/doorkeeper/oauth/client_credentials/validator.rb".freeze, "lib/doorkeeper/oauth/client_credentials_request.rb".freeze, "lib/doorkeeper/oauth/code_request.rb".freeze, "lib/doorkeeper/oauth/code_response.rb".freeze, "lib/doorkeeper/oauth/error.rb".freeze, "lib/doorkeeper/oauth/error_response.rb".freeze, "lib/doorkeeper/oauth/forbidden_token_response.rb".freeze, "lib/doorkeeper/oauth/helpers/scope_checker.rb".freeze, "lib/doorkeeper/oauth/helpers/unique_token.rb".freeze, "lib/doorkeeper/oauth/helpers/uri_checker.rb".freeze, "lib/doorkeeper/oauth/hooks/context.rb".freeze, "lib/doorkeeper/oauth/invalid_request_response.rb".freeze, "lib/doorkeeper/oauth/invalid_token_response.rb".freeze, "lib/doorkeeper/oauth/nonstandard.rb".freeze, "lib/doorkeeper/oauth/password_access_token_request.rb".freeze, "lib/doorkeeper/oauth/pre_authorization.rb".freeze, "lib/doorkeeper/oauth/refresh_token_request.rb".freeze, "lib/doorkeeper/oauth/scopes.rb".freeze, "lib/doorkeeper/oauth/token.rb".freeze, "lib/doorkeeper/oauth/token_introspection.rb".freeze, "lib/doorkeeper/oauth/token_request.rb".freeze, "lib/doorkeeper/oauth/token_response.rb".freeze, "lib/doorkeeper/orm/active_record.rb".freeze, "lib/doorkeeper/orm/active_record/access_grant.rb".freeze, "lib/doorkeeper/orm/active_record/access_token.rb".freeze, "lib/doorkeeper/orm/active_record/application.rb".freeze, "lib/doorkeeper/orm/active_record/mixins/access_grant.rb".freeze, "lib/doorkeeper/orm/active_record/mixins/access_token.rb".freeze, "lib/doorkeeper/orm/active_record/mixins/application.rb".freeze, "lib/doorkeeper/orm/active_record/redirect_uri_validator.rb".freeze, "lib/doorkeeper/orm/active_record/stale_records_cleaner.rb".freeze, "lib/doorkeeper/rails/helpers.rb".freeze, "lib/doorkeeper/rails/routes.rb".freeze, "lib/doorkeeper/rails/routes/abstract_router.rb".freeze, "lib/doorkeeper/rails/routes/mapper.rb".freeze, "lib/doorkeeper/rails/routes/mapping.rb".freeze, "lib/doorkeeper/rails/routes/registry.rb".freeze, "lib/doorkeeper/rake.rb".freeze, "lib/doorkeeper/rake/db.rake".freeze, "lib/doorkeeper/rake/setup.rake".freeze, "lib/doorkeeper/request.rb".freeze, "lib/doorkeeper/request/authorization_code.rb".freeze, "lib/doorkeeper/request/client_credentials.rb".freeze, "lib/doorkeeper/request/code.rb".freeze, "lib/doorkeeper/request/password.rb".freeze, "lib/doorkeeper/request/refresh_token.rb".freeze, "lib/doorkeeper/request/strategy.rb".freeze, "lib/doorkeeper/request/token.rb".freeze, "lib/doorkeeper/secret_storing/base.rb".freeze, "lib/doorkeeper/secret_storing/bcrypt.rb".freeze, "lib/doorkeeper/secret_storing/plain.rb".freeze, "lib/doorkeeper/secret_storing/sha256_hash.rb".freeze, "lib/doorkeeper/server.rb".freeze, "lib/doorkeeper/stale_records_cleaner.rb".freeze, "lib/doorkeeper/validations.rb".freeze, "lib/doorkeeper/version.rb".freeze, "lib/generators/doorkeeper/application_owner_generator.rb".freeze, "lib/generators/doorkeeper/confidential_applications_generator.rb".freeze, "lib/generators/doorkeeper/enable_polymorphic_resource_owner_generator.rb".freeze, "lib/generators/doorkeeper/install_generator.rb".freeze, "lib/generators/doorkeeper/migration_generator.rb".freeze, "lib/generators/doorkeeper/pkce_generator.rb".freeze, "lib/generators/doorkeeper/previous_refresh_token_generator.rb".freeze, "lib/generators/doorkeeper/templates/README".freeze, "lib/generators/doorkeeper/templates/add_confidential_to_applications.rb.erb".freeze, "lib/generators/doorkeeper/templates/add_owner_to_application_migration.rb.erb".freeze, "lib/generators/doorkeeper/templates/add_previous_refresh_token_to_access_tokens.rb.erb".freeze, "lib/generators/doorkeeper/templates/enable_pkce_migration.rb.erb".freeze, "lib/generators/doorkeeper/templates/enable_polymorphic_resource_owner_migration.rb.erb".freeze, "lib/generators/doorkeeper/templates/initializer.rb".freeze, "lib/generators/doorkeeper/templates/migration.rb.erb".freeze, "lib/generators/doorkeeper/views_generator.rb".freeze, "vendor/assets/stylesheets/doorkeeper/bootstrap.min.css".freeze] s.homepage = "https://github.com/doorkeeper-gem/doorkeeper".freeze s.licenses = ["MIT".freeze] s.post_install_message = "Starting from 5.5.0 RC1 Doorkeeper requires client authentication for Resource Owner Password Grant\nas stated in the OAuth RFC. You have to create a new OAuth client (Doorkeeper::Application) if you didn't\nhave it before and use client credentials in HTTP Basic auth if you previously used this grant flow without\nclient authentication. \n\nTo opt out of this you could set the \"skip_client_authentication_for_password_grant\" configuration option\nto \"true\", but note that this is in violation of the OAuth spec and represents a security risk.\n\nRead https://github.com/doorkeeper-gem/doorkeeper/issues/561#issuecomment-612857163 for more details.".freeze s.required_ruby_version = Gem::Requirement.new(">= 2.7".freeze) s.rubygems_version = "3.2.5".freeze s.summary = "OAuth 2 provider for Rails and Grape".freeze if s.respond_to? :specification_version then s.specification_version = 4 end if s.respond_to? :add_runtime_dependency then s.add_development_dependency(%q.freeze, [">= 0"]) s.add_development_dependency(%q.freeze, [">= 0"]) s.add_development_dependency(%q.freeze, [">= 0"]) s.add_development_dependency(%q.freeze, ["~> 2.0"]) s.add_development_dependency(%q.freeze, ["~> 6.0"]) s.add_development_dependency(%q.freeze, ["~> 0.9.3"]) s.add_development_dependency(%q.freeze, [">= 0"]) s.add_runtime_dependency(%q.freeze, [">= 5"]) s.add_development_dependency(%q.freeze, [">= 11.3.0"]) s.add_development_dependency(%q.freeze, [">= 0"]) s.add_development_dependency(%q.freeze, [">= 0"]) else s.add_dependency(%q.freeze, [">= 0"]) s.add_dependency(%q.freeze, [">= 0"]) s.add_dependency(%q.freeze, [">= 0"]) s.add_dependency(%q.freeze, ["~> 2.0"]) s.add_dependency(%q.freeze, ["~> 6.0"]) s.add_dependency(%q.freeze, ["~> 0.9.3"]) s.add_dependency(%q.freeze, [">= 0"]) s.add_dependency(%q.freeze, [">= 5"]) s.add_dependency(%q.freeze, [">= 11.3.0"]) s.add_dependency(%q.freeze, [">= 0"]) s.add_dependency(%q.freeze, [">= 0"]) end end doorkeeper-5.6.6/app/0000755000004100000410000000000014422352653014505 5ustar www-datawww-datadoorkeeper-5.6.6/app/helpers/0000755000004100000410000000000014422352653016147 5ustar www-datawww-datadoorkeeper-5.6.6/app/helpers/doorkeeper/0000755000004100000410000000000014422352653020306 5ustar www-datawww-datadoorkeeper-5.6.6/app/helpers/doorkeeper/dashboard_helper.rb0000644000004100000410000000077514422352653024132 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: "invalid-feedback") 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.6.6/app/controllers/0000755000004100000410000000000014422352653017053 5ustar www-datawww-datadoorkeeper-5.6.6/app/controllers/doorkeeper/0000755000004100000410000000000014422352653021212 5ustar www-datawww-datadoorkeeper-5.6.6/app/controllers/doorkeeper/authorizations_controller.rb0000644000004100000410000000753014422352653027072 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 def create redirect_or_render(authorize_response) end def destroy redirect_or_render(authorization.deny) rescue Doorkeeper::Errors::InvalidTokenStrategy => e error_response = get_error_response_from_exception(e) if Doorkeeper.configuration.api_only render json: error_response.body, status: :bad_request else render :error, locals: { error_response: error_response } end end private def render_success if skip_authorization? || (matching_token? && pre_auth.client.application.confidential?) 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, locals: { error_response: pre_auth.error_response } end end # Active access token issued for the same client and resource owner with # the same set of the scopes exists? def matching_token? Doorkeeper.config.access_token_model.matching_token_for( pre_auth.client, current_resource_owner, pre_auth.scopes, ) end def redirect_or_render(auth) if auth.redirectable? if Doorkeeper.configuration.api_only if pre_auth.form_post_response? render( json: { status: :post, redirect_uri: pre_auth.redirect_uri, body: auth.body }, status: auth.status, ) else render( json: { status: :redirect, redirect_uri: auth.redirect_uri }, status: auth.status, ) end elsif pre_auth.form_post_response? render :form_post else redirect_to auth.redirect_uri, allow_other_host: true end else render json: auth.body, status: auth.status end end def pre_auth @pre_auth ||= OAuth::PreAuthorization.new( Doorkeeper.configuration, pre_auth_params, current_resource_owner, ) end def pre_auth_params params.slice(*pre_auth_param_fields).permit(*pre_auth_param_fields) end def pre_auth_param_fields custom_access_token_attributes + %i[ client_id code_challenge code_challenge_method response_type response_mode redirect_uri scope state ] end def custom_access_token_attributes Doorkeeper.config.custom_access_token_attributes.map(&:to_sym) end def authorization @authorization ||= strategy.request end def strategy @strategy ||= server.authorization_request(pre_auth.response_type) end def authorize_response @authorize_response ||= begin return pre_auth.error_response unless pre_auth.authorizable? context = build_context(pre_auth: pre_auth) before_successful_authorization(context) auth = strategy.authorize context = build_context(auth: auth) after_successful_authorization(context) auth end end def build_context(**attributes) Doorkeeper::OAuth::Hooks::Context.new(**attributes) end def before_successful_authorization(context = nil) Doorkeeper.config.before_successful_authorization.call(self, context) end def after_successful_authorization(context) Doorkeeper.config.after_successful_authorization.call(self, context) end end end doorkeeper-5.6.6/app/controllers/doorkeeper/application_controller.rb0000644000004100000410000000056314422352653026311 0ustar www-datawww-data# frozen_string_literal: true module Doorkeeper class ApplicationController < Doorkeeper.config.resolve_controller(:base) include Helpers::Controller include ActionController::MimeResponds if Doorkeeper.config.api_only unless Doorkeeper.config.api_only protect_from_forgery with: :exception helper "doorkeeper/dashboard" end end end doorkeeper-5.6.6/app/controllers/doorkeeper/application_metal_controller.rb0000644000004100000410000000055214422352653027471 0ustar www-datawww-data# frozen_string_literal: true module Doorkeeper class ApplicationMetalController < Doorkeeper.config.resolve_controller(:base_metal) include Helpers::Controller before_action :enforce_content_type, if: -> { Doorkeeper.config.enforce_content_type } ActiveSupport.run_load_hooks(:doorkeeper_metal_controller, self) end end doorkeeper-5.6.6/app/controllers/doorkeeper/token_info_controller.rb0000644000004100000410000000106214422352653026134 0ustar www-datawww-data# frozen_string_literal: true module Doorkeeper class TokenInfoController < Doorkeeper::ApplicationMetalController def show if doorkeeper_token&.accessible? render json: doorkeeper_token_to_json, status: :ok else error = OAuth::InvalidTokenResponse.new response.headers.merge!(error.headers) render json: error_to_json(error), status: error.status end end protected def doorkeeper_token_to_json doorkeeper_token end def error_to_json(error) error.body end end end doorkeeper-5.6.6/app/controllers/doorkeeper/tokens_controller.rb0000644000004100000410000001374414422352653025316 0ustar www-datawww-data# frozen_string_literal: true module Doorkeeper class TokensController < Doorkeeper::ApplicationMetalController before_action :validate_presence_of_client, only: [:revoke] def create headers.merge!(authorize_response.headers) render json: authorize_response.body, status: authorize_response.status rescue Errors::DoorkeeperError => e handle_token_exception(e) end # OAuth 2.0 Token Revocation - https://datatracker.ietf.org/doc/html/rfc7009 def revoke # The authorization server responds with HTTP status code 200 if the client # submitted an invalid token or the token has been revoked successfully. if token.blank? render json: {}, status: 200 # The authorization server validates [...] and whether the token # was issued to the client making the revocation request. If this # validation fails, the request is refused and the client is informed # of the error by the authorization server as described below. elsif authorized? revoke_token render json: {}, status: 200 else render json: revocation_error_response, status: :forbidden end end # OAuth 2.0 Token Introspection - https://datatracker.ietf.org/doc/html/rfc7662 def introspect introspection = OAuth::TokenIntrospection.new(server, token) if introspection.authorized? render json: introspection.to_json, status: 200 else error = introspection.error_response headers.merge!(error.headers) render json: error.body, status: error.status end end private def validate_presence_of_client return if Doorkeeper.config.skip_client_authentication_for_password_grant # @see 2.1. Revocation Request # # The client constructs the request by including the following # parameters using the "application/x-www-form-urlencoded" format in # the HTTP request entity-body: # token REQUIRED. # token_type_hint OPTIONAL. # # The client also includes its authentication credentials as described # in Section 2.3. of [RFC6749]. # # The authorization server first validates the client credentials (in # case of a confidential client) and then verifies whether the token # was issued to the client making the revocation request. return if server.client # If this validation [client credentials / token ownership] fails, the request is # refused and the client is informed of the error by the authorization server as # described below. # # @see 2.2.1. Error Response # # The error presentation conforms to the definition in Section 5.2 of [RFC6749]. render json: revocation_error_response, status: :forbidden end # OAuth 2.0 Section 2.1 defines two client types, "public" & "confidential". # # RFC7009 # Section 5. Security Considerations # A malicious client may attempt to guess valid tokens on this endpoint # by making revocation requests against potential token strings. # According to this specification, a client's request must contain a # valid client_id, in the case of a public client, or valid client # credentials, in the case of a confidential client. The token being # revoked must also belong to the requesting client. # # 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://datatracker.ietf.org/doc/html/rfc6749#section-2.1 # https://datatracker.ietf.org/doc/html/rfc7009 def authorized? # Token belongs to specific client, so we need to check if # authenticated client could access it. if token.application_id? && token.application.confidential? # We authorize client by checking token's application server.client && server.client.application == token.application else # Token was issued without client, authorization unnecessary true end end def revoke_token # The authorization server responds with HTTP status code 200 if the token # has been revoked successfully or if the client submitted an invalid # token token.revoke if token&.accessible? end def token @token ||= if params[:token_type_hint] == "refresh_token" Doorkeeper.config.access_token_model.by_refresh_token(params["token"]) else Doorkeeper.config.access_token_model.by_token(params["token"]) || Doorkeeper.config.access_token_model.by_refresh_token(params["token"]) end end def strategy @strategy ||= server.token_request(params[:grant_type]) end def authorize_response @authorize_response ||= begin before_successful_authorization auth = strategy.authorize context = build_context(auth: auth) after_successful_authorization(context) unless auth.is_a?(Doorkeeper::OAuth::ErrorResponse) auth end end def build_context(**attributes) Doorkeeper::OAuth::Hooks::Context.new(**attributes) end def before_successful_authorization(context = nil) Doorkeeper.config.before_successful_authorization.call(self, context) end def after_successful_authorization(context) Doorkeeper.config.after_successful_authorization.call(self, context) end def revocation_error_response error_description = I18n.t(:unauthorized, scope: %i[doorkeeper errors messages revoke]) { error: :unauthorized_client, error_description: error_description } end end end doorkeeper-5.6.6/app/controllers/doorkeeper/authorized_applications_controller.rb0000644000004100000410000000161114422352653030725 0ustar www-datawww-data# frozen_string_literal: true module Doorkeeper class AuthorizedApplicationsController < Doorkeeper::ApplicationController before_action :authenticate_resource_owner! def index @applications = Doorkeeper.config.application_model.authorized_for(current_resource_owner) respond_to do |format| format.html format.json { render json: @applications, current_resource_owner: current_resource_owner } end end def destroy Doorkeeper.config.application_model.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 { head :no_content } end end end end doorkeeper-5.6.6/app/controllers/doorkeeper/applications_controller.rb0000644000004100000410000000523714422352653026477 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 = Doorkeeper.config.application_model.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, as_owner: true } end end def new @application = Doorkeeper.config.application_model.new end def create @application = Doorkeeper.config.application_model.new(application_params) if @application.save flash[:notice] = I18n.t(:notice, scope: %i[doorkeeper flash applications create]) flash[:application_secret] = @application.plaintext_secret respond_to do |format| format.html { redirect_to oauth_application_url(@application) } format.json { render json: @application, as_owner: true } end else respond_to do |format| format.html { render :new } format.json do errors = @application.errors.full_messages render json: { errors: errors }, status: :unprocessable_entity end end end end def edit; end def update if @application.update(application_params) flash[:notice] = I18n.t(:notice, scope: i18n_scope(:update)) respond_to do |format| format.html { redirect_to oauth_application_url(@application) } format.json { render json: @application, as_owner: true } end else respond_to do |format| format.html { render :edit } format.json do errors = @application.errors.full_messages render json: { errors: errors }, status: :unprocessable_entity end end end end def destroy flash[:notice] = I18n.t(:notice, scope: i18n_scope(: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 = Doorkeeper.config.application_model.find(params[:id]) end def application_params params.require(:doorkeeper_application) .permit(:name, :redirect_uri, :scopes, :confidential) end def i18n_scope(action) %i[doorkeeper flash applications] << action end end end doorkeeper-5.6.6/app/assets/0000755000004100000410000000000014422352653016007 5ustar www-datawww-datadoorkeeper-5.6.6/app/assets/stylesheets/0000755000004100000410000000000014422352653020363 5ustar www-datawww-datadoorkeeper-5.6.6/app/assets/stylesheets/doorkeeper/0000755000004100000410000000000014422352653022522 5ustar www-datawww-datadoorkeeper-5.6.6/app/assets/stylesheets/doorkeeper/application.css0000644000004100000410000000162014422352653025536 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.6.6/app/assets/stylesheets/doorkeeper/admin/0000755000004100000410000000000014422352653023612 5ustar www-datawww-datadoorkeeper-5.6.6/app/assets/stylesheets/doorkeeper/admin/application.css0000644000004100000410000000023614422352653026630 0ustar www-datawww-data/* *= require doorkeeper/bootstrap.min * *= require_self *= require_tree . */ .doorkeeper-admin .form-group > .field_with_errors { width: 16.66667%; } doorkeeper-5.6.6/app/views/0000755000004100000410000000000014422352653015642 5ustar www-datawww-datadoorkeeper-5.6.6/app/views/doorkeeper/0000755000004100000410000000000014422352653020001 5ustar www-datawww-datadoorkeeper-5.6.6/app/views/doorkeeper/authorized_applications/0000755000004100000410000000000014422352653024725 5ustar www-datawww-datadoorkeeper-5.6.6/app/views/doorkeeper/authorized_applications/_delete_form.html.erb0000644000004100000410000000051014422352653031002 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.6.6/app/views/doorkeeper/authorized_applications/index.html.erb0000644000004100000410000000134414422352653027473 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.6.6/app/views/doorkeeper/authorizations/0000755000004100000410000000000014422352653023064 5ustar www-datawww-datadoorkeeper-5.6.6/app/views/doorkeeper/authorizations/form_post.html.erb0000644000004100000410000000062214422352653026531 0ustar www-datawww-data <%= form_tag @pre_auth.redirect_uri, method: :post, name: :redirect_form, authenticity_token: false do %> <% @authorize_response.body.compact.each do |key, value| %> <%= hidden_field_tag key, value %> <% end %> <% end %> doorkeeper-5.6.6/app/views/doorkeeper/authorizations/new.html.erb0000644000004100000410000000420414422352653025312 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') %>:

    <% @pre_auth.scopes.each do |scope| %>
  • <%= t scope, scope: [:doorkeeper, :scopes] %>
  • <% end %>
<% end %>
<%= form_tag oauth_authorization_path, method: :post do %> <%= hidden_field_tag :client_id, @pre_auth.client.uid, id: nil %> <%= hidden_field_tag :redirect_uri, @pre_auth.redirect_uri, id: nil %> <%= hidden_field_tag :state, @pre_auth.state, id: nil %> <%= hidden_field_tag :response_type, @pre_auth.response_type, id: nil %> <%= hidden_field_tag :response_mode, @pre_auth.response_mode, id: nil %> <%= hidden_field_tag :scope, @pre_auth.scope, id: nil %> <%= hidden_field_tag :code_challenge, @pre_auth.code_challenge, id: nil %> <%= hidden_field_tag :code_challenge_method, @pre_auth.code_challenge_method, id: nil %> <%= 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, id: nil %> <%= hidden_field_tag :redirect_uri, @pre_auth.redirect_uri, id: nil %> <%= hidden_field_tag :state, @pre_auth.state, id: nil %> <%= hidden_field_tag :response_type, @pre_auth.response_type, id: nil %> <%= hidden_field_tag :response_mode, @pre_auth.response_mode, id: nil %> <%= hidden_field_tag :scope, @pre_auth.scope, id: nil %> <%= hidden_field_tag :code_challenge, @pre_auth.code_challenge, id: nil %> <%= hidden_field_tag :code_challenge_method, @pre_auth.code_challenge_method, id: nil %> <%= submit_tag t('doorkeeper.authorizations.buttons.deny'), class: "btn btn-danger btn-lg btn-block" %> <% end %>
doorkeeper-5.6.6/app/views/doorkeeper/authorizations/show.html.erb0000644000004100000410000000023614422352653025502 0ustar www-datawww-data
<%= params[:code] %>
doorkeeper-5.6.6/app/views/doorkeeper/authorizations/error.html.erb0000644000004100000410000000040114422352653025645 0ustar www-datawww-data

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

    <%= (respond_to?(:error_response) ? error_response : @pre_auth.error_response).body[:error_description] %>
  
doorkeeper-5.6.6/app/views/doorkeeper/applications/0000755000004100000410000000000014422352653022467 5ustar www-datawww-datadoorkeeper-5.6.6/app/views/doorkeeper/applications/_delete_form.html.erb0000644000004100000410000000051314422352653026547 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.6.6/app/views/doorkeeper/applications/new.html.erb0000644000004100000410000000016714422352653024721 0ustar www-datawww-data

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

<%= render 'form', application: @application %> doorkeeper-5.6.6/app/views/doorkeeper/applications/index.html.erb0000644000004100000410000000231514422352653025234 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.6.6/app/views/doorkeeper/applications/show.html.erb0000644000004100000410000000426314422352653025111 0ustar www-datawww-data

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

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

<%= @application.uid %>

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

<% secret = flash[:application_secret].presence || @application.plaintext_secret %> <% if secret.blank? && Doorkeeper.config.application_secret_hashed? %> <%= t('.secret_hashed') %> <% else %> <%= secret %> <% end %>

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

<% if @application.scopes.present? %> <%= @application.scopes %> <% else %> <%= t('.not_defined') %> <% end %>

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

<%= @application.confidential? %>

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

<% if @application.redirect_uri.present? %> <% @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' %>
<% else %> <%= t('.not_defined') %> <% end %>

<%= 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.6.6/app/views/doorkeeper/applications/edit.html.erb0000644000004100000410000000016714422352653025055 0ustar www-datawww-data

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

<%= render 'form', application: @application %> doorkeeper-5.6.6/app/views/doorkeeper/applications/_form.html.erb0000644000004100000410000000477114422352653025237 0ustar www-datawww-data<%= form_for application, url: doorkeeper_submit_path(application), as: :doorkeeper_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.allow_blank_redirect_uri?(application) %> <%= t('doorkeeper.applications.help.blank_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.6.6/app/views/layouts/0000755000004100000410000000000014422352653017342 5ustar www-datawww-datadoorkeeper-5.6.6/app/views/layouts/doorkeeper/0000755000004100000410000000000014422352653021501 5ustar www-datawww-datadoorkeeper-5.6.6/app/views/layouts/doorkeeper/application.html.erb0000644000004100000410000000101714422352653025440 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.6.6/app/views/layouts/doorkeeper/admin.html.erb0000644000004100000410000000232314422352653024226 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.6.6/lib/0000755000004100000410000000000014422352653014473 5ustar www-datawww-datadoorkeeper-5.6.6/lib/doorkeeper/0000755000004100000410000000000014422352653016632 5ustar www-datawww-datadoorkeeper-5.6.6/lib/doorkeeper/grape/0000755000004100000410000000000014422352653017730 5ustar www-datawww-datadoorkeeper-5.6.6/lib/doorkeeper/grape/helpers.rb0000644000004100000410000000302214422352653021714 0ustar www-datawww-data# frozen_string_literal: true require "doorkeeper/grape/authorization_decorator" module Doorkeeper module Grape # Doorkeeper helpers for Grape applications. # Provides helpers for endpoints authorization based on defined set of scopes. 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.present? 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.config.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.6.6/lib/doorkeeper/grape/authorization_decorator.rb0000644000004100000410000000063114422352653025217 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.6.6/lib/doorkeeper/grant_flow.rb0000644000004100000410000000222314422352653021320 0ustar www-datawww-data# frozen_string_literal: true require "doorkeeper/grant_flow/flow" require "doorkeeper/grant_flow/fallback_flow" require "doorkeeper/grant_flow/registry" module Doorkeeper module GrantFlow extend Registry register( :implicit, response_type_matches: "token", response_mode_matches: %w[fragment form_post], response_type_strategy: Doorkeeper::Request::Token, ) register( :authorization_code, response_type_matches: "code", response_mode_matches: %w[query fragment form_post], response_type_strategy: Doorkeeper::Request::Code, grant_type_matches: "authorization_code", grant_type_strategy: Doorkeeper::Request::AuthorizationCode, ) register( :client_credentials, grant_type_matches: "client_credentials", grant_type_strategy: Doorkeeper::Request::ClientCredentials, ) register( :password, grant_type_matches: "password", grant_type_strategy: Doorkeeper::Request::Password, ) register( :refresh_token, grant_type_matches: "refresh_token", grant_type_strategy: Doorkeeper::Request::RefreshToken, ) end end doorkeeper-5.6.6/lib/doorkeeper/version.rb0000644000004100000410000000036114422352653020644 0ustar www-datawww-data# frozen_string_literal: true module Doorkeeper module VERSION # Semantic versioning MAJOR = 5 MINOR = 6 TINY = 6 PRE = nil # Full version number STRING = [MAJOR, MINOR, TINY, PRE].compact.join(".") end end doorkeeper-5.6.6/lib/doorkeeper/rake.rb0000644000004100000410000000042514422352653020102 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.6.6/lib/doorkeeper/helpers/0000755000004100000410000000000014422352653020274 5ustar www-datawww-datadoorkeeper-5.6.6/lib/doorkeeper/helpers/controller.rb0000644000004100000410000000465714422352653023020 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 # Rails controller helpers. # module Controller private # :doc: def authenticate_resource_owner! current_resource_owner end # :doc: def current_resource_owner return @current_resource_owner if defined?(@current_resource_owner) @current_resource_owner ||= begin instance_eval(&Doorkeeper.config.authenticate_resource_owner) end end def resource_owner_from_credentials instance_eval(&Doorkeeper.config.resource_owner_from_credentials) end # :doc: def authenticate_admin! instance_eval(&Doorkeeper.config.authenticate_admin) end def server @server ||= Server.new(self) end # :doc: def doorkeeper_token return @doorkeeper_token if defined?(@doorkeeper_token) @doorkeeper_token ||= OAuth::Token.authenticate(request, *config_methods) end def config_methods @config_methods ||= Doorkeeper.config.access_token_methods end def get_error_response_from_exception(exception) if exception.respond_to?(:response) exception.response elsif exception.type == :invalid_request OAuth::InvalidRequestResponse.new( name: exception.type, state: params[:state], missing_param: exception.missing_param, ) else OAuth::ErrorResponse.new(name: exception.type, state: params[:state]) end 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.config.skip_authorization ) end def enforce_content_type if (request.put? || request.post? || request.patch?) && !x_www_form_urlencoded? render json: {}, status: :unsupported_media_type end end def x_www_form_urlencoded? request.media_type == "application/x-www-form-urlencoded" end end end end doorkeeper-5.6.6/lib/doorkeeper/secret_storing/0000755000004100000410000000000014422352653021664 5ustar www-datawww-datadoorkeeper-5.6.6/lib/doorkeeper/secret_storing/base.rb0000644000004100000410000000412114422352653023121 0ustar www-datawww-data# frozen_string_literal: true module Doorkeeper module SecretStoring ## # Base class for secret storing, including common helpers class Base ## # Return the value to be stored by the database # used for looking up a database value. # @param plain_secret The plain secret input / generated def self.transform_secret(_plain_secret) raise NotImplementedError end ## # Transform and store the given secret attribute => value # pair used for safely storing the attribute # @param resource The model instance being modified # @param attribute The secret attribute # @param plain_secret The plain secret input / generated def self.store_secret(resource, attribute, plain_secret) transformed_value = transform_secret(plain_secret) resource.public_send(:"#{attribute}=", transformed_value) transformed_value end ## # Return the restored value from the database # @param resource The resource instance to act on # @param attribute The secret attribute to restore # as retrieved from the database. def self.restore_secret(_resource, _attribute) raise NotImplementedError end ## # Determines whether this strategy supports restoring # secrets from the database. This allows detecting users # trying to use a non-restorable strategy with +reuse_access_tokens+. def self.allows_restoring_secrets? false end ## # Determines what secrets this strategy is applicable for def self.validate_for(model) valid = %i[token application] return true if valid.include?(model.to_sym) raise ArgumentError, "'#{name}' can not be used for #{model}." end ## # Securely compare the given +input+ value with a +stored+ value # processed by +transform_secret+. def self.secret_matches?(input, stored) transformed_input = transform_secret(input) ActiveSupport::SecurityUtils.secure_compare transformed_input, stored end end end end doorkeeper-5.6.6/lib/doorkeeper/secret_storing/sha256_hash.rb0000644000004100000410000000141114422352653024221 0ustar www-datawww-data# frozen_string_literal: true module Doorkeeper module SecretStoring ## # Plain text secret storing, which is the default # but also provides fallback lookup if # other secret storing mechanisms are enabled. class Sha256Hash < Base ## # Return the value to be stored by the database # @param plain_secret The plain secret input / generated def self.transform_secret(plain_secret) ::Digest::SHA256.hexdigest plain_secret end ## # Determines whether this strategy supports restoring # secrets from the database. This allows detecting users # trying to use a non-restorable strategy with +reuse_access_tokens+. def self.allows_restoring_secrets? false end end end end doorkeeper-5.6.6/lib/doorkeeper/secret_storing/bcrypt.rb0000644000004100000410000000322414422352653023515 0ustar www-datawww-data# frozen_string_literal: true module Doorkeeper module SecretStoring ## # Plain text secret storing, which is the default # but also provides fallback lookup if # other secret storing mechanisms are enabled. class BCrypt < Base ## # Return the value to be stored by the database # @param plain_secret The plain secret input / generated def self.transform_secret(plain_secret) ::BCrypt::Password.create(plain_secret.to_s) end ## # Securely compare the given +input+ value with a +stored+ value # processed by +transform_secret+. def self.secret_matches?(input, stored) ::BCrypt::Password.new(stored.to_s) == input.to_s rescue ::BCrypt::Errors::InvalidHash false end ## # Determines whether this strategy supports restoring # secrets from the database. This allows detecting users # trying to use a non-restorable strategy with +reuse_access_tokens+. def self.allows_restoring_secrets? false end ## # Determines what secrets this strategy is applicable for def self.validate_for(model) unless model.to_sym == :application raise ArgumentError, "'#{name}' can only be used for storing application secrets." end unless bcrypt_present? raise ArgumentError, "'#{name}' requires the 'bcrypt' gem being loaded." end true end ## # Test if we can require the BCrypt gem def self.bcrypt_present? require "bcrypt" true rescue LoadError false end end end end doorkeeper-5.6.6/lib/doorkeeper/secret_storing/plain.rb0000644000004100000410000000161414422352653023316 0ustar www-datawww-data# frozen_string_literal: true module Doorkeeper module SecretStoring ## # Plain text secret storing, which is the default # but also provides fallback lookup if # other secret storing mechanisms are enabled. class Plain < Base ## # Return the value to be stored by the database # @param plain_secret The plain secret input / generated def self.transform_secret(plain_secret) plain_secret end ## # Return the restored value from the database # @param resource The resource instance to act on # @param attribute The secret attribute to restore # as retrieved from the database. def self.restore_secret(resource, attribute) resource.public_send(attribute) end ## # Plain values obviously allow restoring def self.allows_restoring_secrets? true end end end end doorkeeper-5.6.6/lib/doorkeeper/models/0000755000004100000410000000000014422352653020115 5ustar www-datawww-datadoorkeeper-5.6.6/lib/doorkeeper/models/access_token_mixin.rb0000644000004100000410000003716514422352653024323 0ustar www-datawww-data# frozen_string_literal: true module Doorkeeper module AccessTokenMixin extend ActiveSupport::Concern include OAuth::Helpers include Models::Expirable include Models::Reusable include Models::Revocable include Models::Accessible include Models::Orderable include Models::SecretStorable include Models::Scopes include Models::ResourceOwnerable include Models::ExpirationTimeSqlMath module ClassMethods # Returns an instance of the Doorkeeper::AccessToken with # specific plain text token value. # # @param token [#to_s] # Plain text 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_plaintext_token(:token, token) 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_plaintext_token(:refresh_token, refresh_token) end # Returns an instance of the Doorkeeper::AccessToken # found by previous refresh token. Keep in mind that value # of the previous_refresh_token isn't encrypted using # secrets strategy. # # @param previous_refresh_token [#to_s] # previous 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_previous_refresh_token(previous_refresh_token) find_by(refresh_token: previous_refresh_token) 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, Integer] # instance of the Resource Owner model or it's ID # def revoke_all_for(application_id, resource_owner, clock = Time) by_resource_owner(resource_owner) .where( application_id: application_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 [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, scopes, include_expired: true) tokens = authorized_tokens_for(application&.id, resource_owner) tokens = tokens.not_expired unless include_expired find_matching_token(tokens, application, scopes) end # Interface to enumerate access token records in batches in order not # to bloat the memory. Could be overloaded in any ORM extension. # def find_access_token_in_batches(relation, **args, &block) relation.find_in_batches(**args, &block) end # Enumerates AccessToken records in batches to find a matching token. # Batching is required in order not to pollute the memory if Application # has huge amount of associated records. # # ActiveRecord 5.x - 6.x ignores custom ordering so we can't perform a # database sort by created_at, so we need to load all the matching records, # sort them and find latest one. # # @param relation [ActiveRecord::Relation] # Access tokens relation # @param application [Doorkeeper::Application] # Application instance # @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 find_matching_token(relation, application, scopes) return nil unless relation matching_tokens = [] batch_size = Doorkeeper.configuration.token_lookup_batch_size find_access_token_in_batches(relation, batch_size: batch_size) do |batch| tokens = batch.select do |token| scopes_match?(token.scopes, scopes, application&.scopes) end matching_tokens.concat(tokens) end matching_tokens.max_by(&:created_at) 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?( scope_str: param_scopes.to_s, server_scopes: Doorkeeper.config.scopes, app_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 [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 token_attributes [Hash] # Additional attributes to use when creating a token # @option token_attributes [Integer] :expires_in # token lifetime in seconds # @option token_attributes [Boolean] :use_refresh_token # whether to use the refresh token # # @return [Doorkeeper::AccessToken] existing record or a new one # def find_or_create_for(application:, resource_owner:, scopes:, **token_attributes) if Doorkeeper.config.reuse_access_token access_token = matching_token_for(application, resource_owner, scopes, include_expired: false) return access_token if access_token&.reusable? end create_for( application: application, resource_owner: resource_owner, scopes: scopes, **token_attributes, ) end # Creates a not expired AccessToken record with a matching set of # scopes that belongs to specific Application and Resource Owner. # # @param application [Doorkeeper::Application] # Application instance # @param resource_owner [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 token_attributes [Hash] # Additional attributes to use when creating a token # @option token_attributes [Integer] :expires_in # token lifetime in seconds # @option token_attributes [Boolean] :use_refresh_token # whether to use the refresh token # # @return [Doorkeeper::AccessToken] new access token # def create_for(application:, resource_owner:, scopes:, **token_attributes) token_attributes[:application] = application token_attributes[:scopes] = scopes.to_s if Doorkeeper.config.polymorphic_resource_owner? token_attributes[:resource_owner] = resource_owner else token_attributes[:resource_owner_id] = resource_owner_id_for(resource_owner) end create!(token_attributes) 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 [ActiveRecord::Base, Integer] # Resource Owner model instance or it's ID # # @return [ActiveRecord::Relation] # collection of matching AccessToken objects # def authorized_tokens_for(application_id, resource_owner) by_resource_owner(resource_owner).where( application_id: application_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 [ActiveRecord::Base, 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) authorized_tokens_for(application_id, resource_owner) .ordered_by(:created_at, :desc) .first end ## # Determines the secret storing transformer # Unless configured otherwise, uses the plain secret strategy # # @return [Doorkeeper::SecretStoring::Base] # def secret_strategy ::Doorkeeper.config.token_secret_strategy end ## # Determine the fallback storing strategy # Unless configured, there will be no fallback def fallback_secret_strategy ::Doorkeeper.config.token_secret_fallback_strategy end end # Access Token type: Bearer. # @see https://datatracker.ietf.org/doc/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, }.tap do |json| if Doorkeeper.configuration.polymorphic_resource_owner? json[:resource_owner_type] = resource_owner_type end end 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 && same_resource_owner?(access_token) 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_resource_owner?(access_token) if Doorkeeper.configuration.polymorphic_resource_owner? resource_owner == access_token.resource_owner else resource_owner_id == access_token.resource_owner_id end 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 # We keep a volatile copy of the raw refresh token for initial communication # The stored refresh_token may be mapped and not available in cleartext. def plaintext_refresh_token if secret_strategy.allows_restoring_secrets? secret_strategy.restore_secret(self, :refresh_token) else @raw_refresh_token end end # We keep a volatile copy of the raw token for initial communication # The stored refresh_token may be mapped and not available in cleartext. # # Some strategies allow restoring stored secrets (e.g. symmetric encryption) # while hashing strategies do not, so you cannot rely on this value # returning a present value for persisted tokens. def plaintext_token if secret_strategy.allows_restoring_secrets? secret_strategy.restore_secret(self, :token) else @raw_token end end # Revokes token with `:refresh_token` equal to `:previous_refresh_token` # and clears `:previous_refresh_token` attribute. # def revoke_previous_refresh_token! return if !self.class.refresh_token_revoked_on_use? || previous_refresh_token.blank? old_refresh_token&.revoke 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 ||= self.class.by_previous_refresh_token(previous_refresh_token) end # Generates refresh token with UniqueToken generator. # # @return [String] refresh token value # def generate_refresh_token @raw_refresh_token = UniqueToken.generate secret_strategy.store_secret(self, :refresh_token, @raw_refresh_token) end # Generates and sets the token value with the # configured Generator class (see Doorkeeper.config). # # @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 @raw_token = token_generator.generate(attributes_for_token_generator) secret_strategy.store_secret(self, :token, @raw_token) @raw_token end # Set of attributes that would be passed to token generator to # generate unique token based on them. # # @return [Hash] set of attributes # def attributes_for_token_generator { resource_owner_id: resource_owner_id, scopes: scopes, application: application, expires_in: expires_in, created_at: created_at, }.tap do |attributes| if Doorkeeper.config.polymorphic_resource_owner? attributes[:resource_owner] = resource_owner end end end def token_generator generator_name = Doorkeeper.config.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.6.6/lib/doorkeeper/models/concerns/0000755000004100000410000000000014422352653021727 5ustar www-datawww-datadoorkeeper-5.6.6/lib/doorkeeper/models/concerns/accessible.rb0000644000004100000410000000052514422352653024353 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.6.6/lib/doorkeeper/models/concerns/ownership.rb0000644000004100000410000000057714422352653024303 0ustar www-datawww-data# frozen_string_literal: true module Doorkeeper module Models module Ownership extend ActiveSupport::Concern included do belongs_to :owner, polymorphic: true, optional: true validates :owner, presence: true, if: :validate_owner? end def validate_owner? Doorkeeper.config.confirm_application_owner? end end end end doorkeeper-5.6.6/lib/doorkeeper/models/concerns/revocable.rb0000644000004100000410000000110414422352653024212 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 end end end doorkeeper-5.6.6/lib/doorkeeper/models/concerns/orderable.rb0000644000004100000410000000043114422352653024211 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.6.6/lib/doorkeeper/models/concerns/scopes.rb0000644000004100000410000000114414422352653023550 0ustar www-datawww-data# frozen_string_literal: true module Doorkeeper module Models module Scopes def scopes OAuth::Scopes.from_string(scopes_string) end def scopes=(value) if value.is_a?(Array) super(Doorkeeper::OAuth::Scopes.from_array(value).to_s) else super(Doorkeeper::OAuth::Scopes.from_string(value.to_s).to_s) end 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.6.6/lib/doorkeeper/models/concerns/expirable.rb0000644000004100000410000000177214422352653024236 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, nil] expiration time in UTC # or nil if the object never expires. # def expires_at expires_in && created_at + expires_in.seconds end end end end doorkeeper-5.6.6/lib/doorkeeper/models/concerns/polymorphic_resource_owner.rb0000644000004100000410000000130214422352653027736 0ustar www-datawww-data# frozen_string_literal: true module Doorkeeper module Models module PolymorphicResourceOwner module ForAccessGrant extend ActiveSupport::Concern included do if Doorkeeper.config.polymorphic_resource_owner? belongs_to :resource_owner, polymorphic: true, optional: false else validates :resource_owner_id, presence: true end end end module ForAccessToken extend ActiveSupport::Concern included do if Doorkeeper.config.polymorphic_resource_owner? belongs_to :resource_owner, polymorphic: true, optional: true end end end end end end doorkeeper-5.6.6/lib/doorkeeper/models/concerns/secret_storable.rb0000644000004100000410000000652414422352653025443 0ustar www-datawww-data# frozen_string_literal: true module Doorkeeper module Models ## # Storable finder to provide lookups for input plaintext values which are # mapped to their stored versions (e.g., hashing, encryption) before lookup. module SecretStorable extend ActiveSupport::Concern delegate :secret_strategy, :fallback_secret_strategy, to: :class # :nodoc module ClassMethods # Compare the given plaintext with the secret # # @param input [String] # The plain input to compare. # # @param secret [String] # The secret value to compare with. # # @return [Boolean] # Whether input matches secret as per the secret strategy # delegate :secret_matches?, to: :secret_strategy # Returns an instance of the Doorkeeper::AccessToken with # specific token value. # # @param attr [Symbol] # The token attribute we're looking with. # # @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 find_by_plaintext_token(attr, token) token = token.to_s find_by(attr => secret_strategy.transform_secret(token)) || find_by_fallback_token(attr, token) end # Allow looking up previously plain tokens as a fallback # IFF a fallback strategy has been defined # # @param attr [Symbol] # The token attribute we're looking with. # # @param plain_secret [#to_s] # plain secret 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 find_by_fallback_token(attr, plain_secret) return nil unless fallback_secret_strategy # Use the previous strategy to look up stored_token = fallback_secret_strategy.transform_secret(plain_secret) find_by(attr => stored_token).tap do |resource| return nil unless resource upgrade_fallback_value resource, attr, plain_secret end end # Allow implementations in ORMs to replace a plain # value falling back to to avoid it remaining as plain text. # # @param instance # An instance of this model with a plain value token. # # @param attr # The secret attribute name to upgrade. # # @param plain_secret # The plain secret to upgrade. # def upgrade_fallback_value(instance, attr, plain_secret) upgraded = secret_strategy.store_secret(instance, attr, plain_secret) instance.update(attr => upgraded) end ## # Determines the secret storing transformer # Unless configured otherwise, uses the plain secret strategy def secret_strategy ::Doorkeeper::SecretStoring::Plain end ## # Determine the fallback storing strategy # Unless configured, there will be no fallback def fallback_secret_strategy nil end end end end end doorkeeper-5.6.6/lib/doorkeeper/models/concerns/reusable.rb0000644000004100000410000000102514422352653024054 0ustar www-datawww-data# frozen_string_literal: true module Doorkeeper module Models module Reusable # Indicates whether the object is reusable (i.e. It is not expired and # has not crossed reuse_limit). # # @return [Boolean] true if can be reused and false in other case def reusable? return false if expired? return true unless expires_in threshold_limit = 100 - Doorkeeper.config.token_reuse_limit expires_in_seconds >= threshold_limit * expires_in / 100 end end end end doorkeeper-5.6.6/lib/doorkeeper/models/concerns/resource_ownerable.rb0000644000004100000410000000254314422352653026145 0ustar www-datawww-data# frozen_string_literal: true module Doorkeeper module Models module ResourceOwnerable extend ActiveSupport::Concern module ClassMethods # Searches for record by Resource Owner considering Doorkeeper # configuration for resource owner association. # # @param resource_owner [ActiveRecord::Base, Integer] # resource owner # # @return [Doorkeeper::AccessGrant, Doorkeeper::AccessToken] # collection of records # def by_resource_owner(resource_owner) if Doorkeeper.configuration.polymorphic_resource_owner? where(resource_owner: resource_owner) else where(resource_owner_id: resource_owner_id_for(resource_owner)) end end protected # Backward compatible way to retrieve resource owner itself (if # polymorphic association enabled) or just it's ID. # # @param resource_owner [ActiveRecord::Base, Integer] # resource owner # # @return [ActiveRecord::Base, Integer] # instance of Resource Owner or it's ID # def resource_owner_id_for(resource_owner) if resource_owner.respond_to?(:to_key) resource_owner.id else resource_owner end end end end end end doorkeeper-5.6.6/lib/doorkeeper/models/concerns/expiration_time_sql_math.rb0000644000004100000410000000532514422352653027351 0ustar www-datawww-data# frozen_string_literal: true module Doorkeeper module Models module ExpirationTimeSqlMath extend ::ActiveSupport::Concern class ExpirationTimeSqlGenerator attr_reader :model delegate :table_name, to: :@model def initialize(model) @model = model end def generate_sql raise "`generate_sql` should be overridden for a #{self.class.name}!" end end class MySqlExpirationTimeSqlGenerator < ExpirationTimeSqlGenerator def generate_sql Arel.sql("DATE_ADD(#{table_name}.created_at, INTERVAL #{table_name}.expires_in SECOND)") end end class SqlLiteExpirationTimeSqlGenerator < ExpirationTimeSqlGenerator def generate_sql Arel.sql("DATETIME(#{table_name}.created_at, '+' || #{table_name}.expires_in || ' SECONDS')") end end class SqlServerExpirationTimeSqlGenerator < ExpirationTimeSqlGenerator def generate_sql Arel.sql("DATEADD(second, #{table_name}.expires_in, #{table_name}.created_at) AT TIME ZONE 'UTC'") end end class OracleExpirationTimeSqlGenerator < ExpirationTimeSqlGenerator def generate_sql Arel.sql("#{table_name}.created_at + INTERVAL to_char(#{table_name}.expires_in) second") end end class PostgresExpirationTimeSqlGenerator < ExpirationTimeSqlGenerator def generate_sql Arel.sql("#{table_name}.created_at + #{table_name}.expires_in * INTERVAL '1 SECOND'") end end ADAPTERS_MAPPING = { "sqlite" => SqlLiteExpirationTimeSqlGenerator, "sqlite3" => SqlLiteExpirationTimeSqlGenerator, "postgis" => PostgresExpirationTimeSqlGenerator, "postgresql" => PostgresExpirationTimeSqlGenerator, "mysql" => MySqlExpirationTimeSqlGenerator, "mysql2" => MySqlExpirationTimeSqlGenerator, "trilogy" => MySqlExpirationTimeSqlGenerator, "sqlserver" => SqlServerExpirationTimeSqlGenerator, "oracleenhanced" => OracleExpirationTimeSqlGenerator, }.freeze module ClassMethods def supports_expiration_time_math? ADAPTERS_MAPPING.key?(adapter_name.downcase) || respond_to?(:custom_expiration_time_sql) end def expiration_time_sql if respond_to?(:custom_expiration_time_sql) custom_expiration_time_sql else expiration_time_sql_expression end end def expiration_time_sql_expression ADAPTERS_MAPPING.fetch(adapter_name.downcase).new(self).generate_sql end def adapter_name ActiveRecord::Base.connection.adapter_name end end end end end doorkeeper-5.6.6/lib/doorkeeper/models/access_grant_mixin.rb0000644000004100000410000001016014422352653024300 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::SecretStorable include Models::Scopes include Models::ResourceOwnerable # Never uses PKCE if PKCE migrations were not generated def uses_pkce? self.class.pkce_supported? && code_challenge.present? 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_plaintext_token(:token, token) 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, Integer] # instance of the Resource Owner model or it's ID # def revoke_all_for(application_id, resource_owner, clock = Time) by_resource_owner(resource_owner) .where( application_id: application_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://datatracker.ietf.org/doc/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) Base64.urlsafe_encode64(Digest::SHA256.digest(code_verifier), padding: false) end def pkce_supported? column_names.include?("code_challenge") end ## # Determines the secret storing transformer # Unless configured otherwise, uses the plain secret strategy # # @return [Doorkeeper::SecretStoring::Base] # def secret_strategy ::Doorkeeper.config.token_secret_strategy end ## # Determine the fallback storing strategy # Unless configured, there will be no fallback # # @return [Doorkeeper::SecretStoring::Base] # def fallback_secret_strategy ::Doorkeeper.config.token_secret_fallback_strategy end end end end doorkeeper-5.6.6/lib/doorkeeper/models/application_mixin.rb0000644000004100000410000000575214422352653024162 0ustar www-datawww-data# frozen_string_literal: true module Doorkeeper module ApplicationMixin extend ActiveSupport::Concern include OAuth::Helpers include Models::Orderable include Models::SecretStorable include Models::Scopes # :nodoc 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_matches?(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 ## # Determines the secret storing transformer # Unless configured otherwise, uses the plain secret strategy def secret_strategy ::Doorkeeper.config.application_secret_strategy end ## # Determine the fallback storing strategy # Unless configured, there will be no fallback def fallback_secret_strategy ::Doorkeeper.config.application_secret_fallback_strategy 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) separated by newlines. # def redirect_uri=(uris) super(uris.is_a?(Array) ? uris.join("\n") : uris) end # Check whether the given plain text secret matches our stored secret # # @param input [#to_s] Plain secret provided by user # (any object that responds to `#to_s`) # # @return [Boolean] Whether the given secret matches the stored secret # of this application. # def secret_matches?(input) # return false if either is nil, since secure_compare depends on strings # but Application secrets MAY be nil depending on confidentiality. return false if input.nil? || secret.nil? # When matching the secret by comparer function, all is well. return true if secret_strategy.secret_matches?(input, secret) # When fallback lookup is enabled, ensure applications # with plain secrets can still be found if fallback_secret_strategy fallback_secret_strategy.secret_matches?(input, secret) else false end end end end doorkeeper-5.6.6/lib/doorkeeper/errors.rb0000644000004100000410000000221514422352653020473 0ustar www-datawww-data# frozen_string_literal: true module Doorkeeper module Errors class DoorkeeperError < StandardError def type message end end class InvalidGrantReuse < DoorkeeperError def type :invalid_grant end end class InvalidTokenStrategy < DoorkeeperError def type :unsupported_grant_type end end class MissingRequiredParameter < DoorkeeperError attr_reader :missing_param def initialize(missing_param) super @missing_param = missing_param end 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.6.6/lib/doorkeeper/request/0000755000004100000410000000000014422352653020322 5ustar www-datawww-datadoorkeeper-5.6.6/lib/doorkeeper/request/authorization_code.rb0000644000004100000410000000105114422352653024536 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.config, grant, client, parameters, ) end private def grant raise Errors::MissingRequiredParameter, :code if parameters[:code].blank? Doorkeeper.config.access_grant_model.by_token(parameters[:code]) end end end end doorkeeper-5.6.6/lib/doorkeeper/request/refresh_token.rb0000644000004100000410000000075014422352653023507 0ustar www-datawww-data# frozen_string_literal: true module Doorkeeper module Request class RefreshToken < Strategy delegate :credentials, :parameters, to: :server def refresh_token Doorkeeper.config.access_token_model.by_refresh_token(parameters[:refresh_token]) end def request @request ||= OAuth::RefreshTokenRequest.new( Doorkeeper.config, refresh_token, credentials, parameters, ) end end end end doorkeeper-5.6.6/lib/doorkeeper/request/strategy.rb0000644000004100000410000000052514422352653022513 0ustar www-datawww-data# frozen_string_literal: true module Doorkeeper module Request class Strategy attr_reader :server delegate :authorize, to: :request def initialize(server) @server = server end def request raise NotImplementedError, "request strategies must define #request" end end end end doorkeeper-5.6.6/lib/doorkeeper/request/token.rb0000644000004100000410000000052414422352653021770 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.6.6/lib/doorkeeper/request/client_credentials.rb0000644000004100000410000000052214422352653024501 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.config, client, parameters, ) end end end end doorkeeper-5.6.6/lib/doorkeeper/request/code.rb0000644000004100000410000000052214422352653021560 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.6.6/lib/doorkeeper/request/password.rb0000644000004100000410000000063314422352653022513 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.config, client, credentials, resource_owner, parameters, ) end end end end doorkeeper-5.6.6/lib/doorkeeper/request.rb0000644000004100000410000000434514422352653020655 0ustar www-datawww-data# frozen_string_literal: true module Doorkeeper module Request class << self def authorization_strategy(response_type) grant_flow = authorization_flows.detect do |flow| flow.matches_response_type?(response_type) end if grant_flow grant_flow.response_type_strategy else # [NOTE]: this will be removed in a newer versions of Doorkeeper. # For retro-compatibility only build_fallback_strategy_class(response_type) end end def token_strategy(grant_type) raise Errors::MissingRequiredParameter, :grant_type if grant_type.blank? grant_flow = token_flows.detect do |flow| flow.matches_grant_type?(grant_type) end if grant_flow grant_flow.grant_type_strategy else # [NOTE]: this will be removed in a newer versions of Doorkeeper. # For retro-compatibility only raise Errors::InvalidTokenStrategy unless available.include?(grant_type.to_s) strategy_class = build_fallback_strategy_class(grant_type) raise Errors::InvalidTokenStrategy unless strategy_class strategy_class end end private def authorization_flows Doorkeeper.configuration.authorization_response_flows end def token_flows Doorkeeper.configuration.token_grant_flows end # [NOTE]: this will be removed in a newer versions of Doorkeeper. # For retro-compatibility only def available Doorkeeper.config.deprecated_token_grant_types_resolver end def build_fallback_strategy_class(grant_or_request_type) strategy_class_name = grant_or_request_type.to_s.tr(" ", "_").camelize fallback_strategy = "Doorkeeper::Request::#{strategy_class_name}".constantize ::Kernel.warn <<~WARNING [DOORKEEPER] #{fallback_strategy} found using fallback, it must be registered using `Doorkeeper::GrantFlow.register(grant_flow_name, **options)`. This functionality will be removed in a newer versions of Doorkeeper. WARNING fallback_strategy rescue NameError raise Errors::InvalidTokenStrategy end end end end doorkeeper-5.6.6/lib/doorkeeper/grant_flow/0000755000004100000410000000000014422352653020774 5ustar www-datawww-datadoorkeeper-5.6.6/lib/doorkeeper/grant_flow/fallback_flow.rb0000644000004100000410000000034414422352653024110 0ustar www-datawww-data# frozen_string_literal: true module Doorkeeper module GrantFlow class FallbackFlow < Flow def handles_grant_type? false end def handles_response_type? false end end end end doorkeeper-5.6.6/lib/doorkeeper/grant_flow/flow.rb0000644000004100000410000000222514422352653022271 0ustar www-datawww-data# frozen_string_literal: true module Doorkeeper module GrantFlow class Flow attr_reader :name, :grant_type_matches, :grant_type_strategy, :response_type_matches, :response_type_strategy, :response_mode_matches def initialize(name, **options) @name = name @grant_type_matches = options[:grant_type_matches] @grant_type_strategy = options[:grant_type_strategy] @response_type_matches = options[:response_type_matches] @response_type_strategy = options[:response_type_strategy] @response_mode_matches = options[:response_mode_matches] end def handles_grant_type? grant_type_matches.present? end def handles_response_type? response_type_matches.present? end def matches_grant_type?(value) grant_type_matches === value end def matches_response_type?(value) response_type_matches === value end def default_response_mode response_mode_matches[0] end def matches_response_mode?(value) response_mode_matches.include?(value) end end end end doorkeeper-5.6.6/lib/doorkeeper/grant_flow/registry.rb0000644000004100000410000000256714422352653023203 0ustar www-datawww-data# frozen_string_literal: true module Doorkeeper module GrantFlow module Registry mattr_accessor :flows self.flows = {} mattr_accessor :aliases self.aliases = {} # Allows to register custom OAuth grant flow so that Doorkeeper # could recognize and process it. # def register(name_or_flow, **options) unless name_or_flow.is_a?(Doorkeeper::GrantFlow::Flow) name_or_flow = Flow.new(name_or_flow, **options) end flow_key = name_or_flow.name.to_sym if flows.key?(flow_key) ::Kernel.warn <<~WARNING [DOORKEEPER] '#{flow_key}' grant flow already registered and will be overridden in #{caller(1..1).first} WARNING end flows[flow_key] = name_or_flow end # Allows to register aliases that could be used in `grant_flows` # configuration option. It is possible to have aliases like 1:1 or # 1:N, i.e. "implicit_oidc" => ['token', 'id_token', 'id_token token']. # def register_alias(alias_name, **options) aliases[alias_name.to_sym] = Array.wrap(options.fetch(:as)) end def expand_alias(alias_name) aliases.fetch(alias_name.to_sym, []) end # [NOTE]: make it to use #fetch after removing fallbacks def get(name) flows[name.to_sym] end end end end doorkeeper-5.6.6/lib/doorkeeper/engine.rb0000644000004100000410000000234614422352653020431 0ustar www-datawww-data# frozen_string_literal: true module Doorkeeper class Engine < Rails::Engine initializer "doorkeeper.params.filter", after: :load_config_initializers do |app| if Doorkeeper.configured? parameters = %w[client_secret authentication_token access_token refresh_token] parameters << "code" if Doorkeeper.config.grant_flows.include?("authorization_code") app.config.filter_parameters << /^(#{Regexp.union(parameters)})$/ end 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 config.to_prepare do Doorkeeper.run_orm_hooks 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.6.6/lib/doorkeeper/stale_records_cleaner.rb0000644000004100000410000000107414422352653023503 0ustar www-datawww-data# frozen_string_literal: true module Doorkeeper class StaleRecordsCleaner CLEANER_CLASS = "StaleRecordsCleaner" def self.for(base_scope) orm_adapter = "doorkeeper/orm/#{configured_orm}".classify orm_cleaner = "#{orm_adapter}::#{CLEANER_CLASS}".constantize orm_cleaner.new(base_scope) rescue NameError raise Doorkeeper::Errors::NoOrmCleaner, "'#{configured_orm}' ORM has no cleaner!" end def self.new(base_scope) self.for(base_scope) end def self.configured_orm Doorkeeper.config.orm end end end doorkeeper-5.6.6/lib/doorkeeper/orm/0000755000004100000410000000000014422352653017427 5ustar www-datawww-datadoorkeeper-5.6.6/lib/doorkeeper/orm/active_record.rb0000644000004100000410000000311714422352653022567 0ustar www-datawww-data# frozen_string_literal: true module Doorkeeper autoload :AccessGrant, "doorkeeper/orm/active_record/access_grant" autoload :AccessToken, "doorkeeper/orm/active_record/access_token" autoload :Application, "doorkeeper/orm/active_record/application" autoload :RedirectUriValidator, "doorkeeper/orm/active_record/redirect_uri_validator" module Models autoload :Ownership, "doorkeeper/models/concerns/ownership" end # ActiveRecord ORM for Doorkeeper entity models. # Consists of three main OAuth entities: # * Access Token # * Access Grant # * Application (client) # # Do a lazy loading of all the required and configured stuff. # module Orm module ActiveRecord autoload :StaleRecordsCleaner, "doorkeeper/orm/active_record/stale_records_cleaner" module Mixins autoload :AccessGrant, "doorkeeper/orm/active_record/mixins/access_grant" autoload :AccessToken, "doorkeeper/orm/active_record/mixins/access_token" autoload :Application, "doorkeeper/orm/active_record/mixins/application" end def self.run_hooks initialize_configured_associations end def self.initialize_configured_associations if Doorkeeper.config.enable_application_owner? Doorkeeper.config.application_model.include ::Doorkeeper::Models::Ownership end Doorkeeper.config.access_grant_model.include ::Doorkeeper::Models::PolymorphicResourceOwner::ForAccessGrant Doorkeeper.config.access_token_model.include ::Doorkeeper::Models::PolymorphicResourceOwner::ForAccessToken end end end end doorkeeper-5.6.6/lib/doorkeeper/orm/active_record/0000755000004100000410000000000014422352653022240 5ustar www-datawww-datadoorkeeper-5.6.6/lib/doorkeeper/orm/active_record/redirect_uri_validator.rb0000644000004100000410000000363614422352653027322 0ustar www-datawww-data# frozen_string_literal: true require "uri" module Doorkeeper # ActiveModel validator for redirect URI validation in according # to OAuth standards and Doorkeeper configuration. class RedirectUriValidator < ActiveModel::EachValidator def validate_each(record, attribute, value) if value.blank? return if Doorkeeper.config.allow_blank_redirect_uri?(record) record.errors.add(attribute, :blank) else value.split.each do |val| next if oob_redirect_uri?(val) uri = ::URI.parse(val) record.errors.add(attribute, :forbidden_uri) if forbidden_uri?(uri) record.errors.add(attribute, :fragment_present) unless uri.fragment.nil? record.errors.add(attribute, :unspecified_scheme) if unspecified_scheme?(uri) record.errors.add(attribute, :relative_uri) if relative_uri?(uri) record.errors.add(attribute, :secured_uri) if invalid_ssl_uri?(uri) record.errors.add(attribute, :invalid_uri) if unspecified_host?(uri) end end rescue URI::InvalidURIError record.errors.add(attribute, :invalid_uri) end private def oob_redirect_uri?(uri) Doorkeeper::OAuth::NonStandard::IETF_WG_OAUTH2_OOB_METHODS.include?(uri) end def forbidden_uri?(uri) Doorkeeper.config.forbid_redirect_uri.call(uri) end def unspecified_scheme?(uri) return true if uri.opaque.present? %w[localhost].include?(uri.try(:scheme)) end def unspecified_host?(uri) uri.is_a?(URI::HTTP) && uri.host.blank? end def relative_uri?(uri) uri.scheme.nil? && uri.host.blank? end def invalid_ssl_uri?(uri) forces_ssl = Doorkeeper.config.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 end doorkeeper-5.6.6/lib/doorkeeper/orm/active_record/stale_records_cleaner.rb0000644000004100000410000000160014422352653027104 0ustar www-datawww-data# frozen_string_literal: true module Doorkeeper module Orm module ActiveRecord # Helper class to clear stale and non-active tokens and grants. # Used by Doorkeeper Rake tasks. # class StaleRecordsCleaner def initialize(base_scope) @base_scope = base_scope end # Clears revoked records def clean_revoked table = @base_scope.arel_table @base_scope .where.not(revoked_at: nil) .where(table[:revoked_at].lt(Time.current)) .in_batches(&:delete_all) end # Clears expired records def clean_expired(ttl) table = @base_scope.arel_table @base_scope .where.not(expires_in: nil) .where(table[:created_at].lt(Time.current - ttl)) .in_batches(&:delete_all) end end end end end doorkeeper-5.6.6/lib/doorkeeper/orm/active_record/access_grant.rb0000644000004100000410000000034114422352653025217 0ustar www-datawww-data# frozen_string_literal: true require "doorkeeper/orm/active_record/mixins/access_grant" module Doorkeeper class AccessGrant < ::ActiveRecord::Base include Doorkeeper::Orm::ActiveRecord::Mixins::AccessGrant end end doorkeeper-5.6.6/lib/doorkeeper/orm/active_record/application.rb0000644000004100000410000000044014422352653025066 0ustar www-datawww-data# frozen_string_literal: true require "doorkeeper/orm/active_record/redirect_uri_validator" require "doorkeeper/orm/active_record/mixins/application" module Doorkeeper class Application < ::ActiveRecord::Base include ::Doorkeeper::Orm::ActiveRecord::Mixins::Application end end doorkeeper-5.6.6/lib/doorkeeper/orm/active_record/mixins/0000755000004100000410000000000014422352653023547 5ustar www-datawww-datadoorkeeper-5.6.6/lib/doorkeeper/orm/active_record/mixins/access_grant.rb0000644000004100000410000000362214422352653026533 0ustar www-datawww-data# frozen_string_literal: true module Doorkeeper::Orm::ActiveRecord::Mixins module AccessGrant extend ActiveSupport::Concern included do self.table_name = compute_doorkeeper_table_name self.strict_loading_by_default = false if respond_to?(:strict_loading_by_default) include ::Doorkeeper::AccessGrantMixin belongs_to :application, class_name: Doorkeeper.config.application_class.to_s, optional: true, inverse_of: :access_grants validates :application_id, :token, :expires_in, :redirect_uri, presence: true validates :token, uniqueness: { case_sensitive: true } before_validation :generate_token, on: :create # We keep a volatile copy of the raw token for initial communication # The stored refresh_token may be mapped and not available in cleartext. # # Some strategies allow restoring stored secrets (e.g. symmetric encryption) # while hashing strategies do not, so you cannot rely on this value # returning a present value for persisted tokens. def plaintext_token if secret_strategy.allows_restoring_secrets? secret_strategy.restore_secret(self, :token) else @raw_token end end private # Generates token value with UniqueToken class. # # @return [String] token value # def generate_token @raw_token = Doorkeeper::OAuth::Helpers::UniqueToken.generate secret_strategy.store_secret(self, :token, @raw_token) end end module ClassMethods private def compute_doorkeeper_table_name table_name = "oauth_access_grant" table_name = table_name.pluralize if pluralize_table_names "#{table_name_prefix}#{table_name}#{table_name_suffix}" end end end end doorkeeper-5.6.6/lib/doorkeeper/orm/active_record/mixins/application.rb0000644000004100000410000001652714422352653026412 0ustar www-datawww-data# frozen_string_literal: true module Doorkeeper::Orm::ActiveRecord::Mixins module Application extend ActiveSupport::Concern included do self.table_name = compute_doorkeeper_table_name self.strict_loading_by_default = false if respond_to?(:strict_loading_by_default) include ::Doorkeeper::ApplicationMixin has_many :access_grants, foreign_key: :application_id, dependent: :delete_all, class_name: Doorkeeper.config.access_grant_class.to_s has_many :access_tokens, foreign_key: :application_id, dependent: :delete_all, class_name: Doorkeeper.config.access_token_class.to_s validates :name, :secret, :uid, presence: true validates :uid, uniqueness: { case_sensitive: true } validates :redirect_uri, "doorkeeper/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) }, foreign_key: :application_id, class_name: Doorkeeper.config.access_token_class.to_s has_many :authorized_applications, through: :authorized_tokens, source: :application # Generates a new secret for this application, intended to be used # for rotating the secret or in case of compromise. # # @return [String] new transformed secret value # def renew_secret @raw_secret = secret_generator.generate secret_strategy.store_secret(self, :secret, @raw_secret) end # We keep a volatile copy of the raw secret for initial communication # The stored refresh_token may be mapped and not available in cleartext. # # Some strategies allow restoring stored secrets (e.g. symmetric encryption) # while hashing strategies do not, so you cannot rely on this value # returning a present value for persisted tokens. def plaintext_secret if secret_strategy.allows_restoring_secrets? secret_strategy.restore_secret(self, :secret) else @raw_secret end end # Represents client as set of it's attributes in JSON format. # This is the right way how we want to override ActiveRecord #to_json. # # Respects privacy settings and serializes minimum set of attributes # for public/private clients and full set for authorized owners. # # @return [Hash] entity attributes for JSON # def as_json(options = {}) # if application belongs to some owner we need to check if it's the same as # the one passed in the options or check if we render the client as an owner if (respond_to?(:owner) && owner && owner == options[:current_resource_owner]) || options[:as_owner] # Owners can see all the client attributes, fallback to ActiveModel serialization super else # if application has no owner or it's owner doesn't match one from the options # we render only minimum set of attributes that could be exposed to a public only = extract_serializable_attributes(options) super(options.merge(only: only)) end end def authorized_for_resource_owner?(resource_owner) Doorkeeper.configuration.authorize_resource_owner_for_client.call(self, resource_owner) end # We need to hook into this method to allow serializing plan-text secrets # when secrets hashing enabled. # # @param key [String] attribute name # def read_attribute_for_serialization(key) return super unless key.to_s == "secret" plaintext_secret || secret end private def secret_generator generator_name = Doorkeeper.config.application_secret_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 def generate_uid self.uid = Doorkeeper::OAuth::Helpers::UniqueToken.generate if uid.blank? end def generate_secret return if secret.present? renew_secret end def scopes_match_configured if scopes.present? && !Doorkeeper::OAuth::Helpers::ScopeChecker.valid?( scope_str: scopes.to_s, server_scopes: Doorkeeper.config.scopes, ) errors.add(:scopes, :not_match_configured) end end def enforce_scopes? Doorkeeper.config.enforce_configured_scopes? end # Helper method to extract collection of serializable attribute names # considering serialization options (like `only`, `except` and so on). # # @param options [Hash] serialization options # # @return [Array] # collection of attributes to be serialized using #as_json # def extract_serializable_attributes(options = {}) opts = options.try(:dup) || {} only = Array.wrap(opts[:only]).map(&:to_s) only = if only.blank? client_serializable_attributes else only & client_serializable_attributes end only -= Array.wrap(opts[:except]).map(&:to_s) if opts.key?(:except) only.uniq end # Collection of attributes that could be serialized for public. # Override this method if you need additional attributes to be serialized. # # @return [Array] collection of serializable attributes # # NOTE: `serializable_attributes` method already taken by Rails >= 6 # def client_serializable_attributes attributes = %w[id name created_at] attributes << "uid" unless confidential? attributes end end module ClassMethods # 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 authorized_for(resource_owner) resource_access_tokens = Doorkeeper.config.access_token_model.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 revoke_tokens_and_grants_for(id, resource_owner) Doorkeeper.config.access_token_model.revoke_all_for(id, resource_owner) Doorkeeper.config.access_grant_model.revoke_all_for(id, resource_owner) end private def compute_doorkeeper_table_name table_name = "oauth_application" table_name = table_name.pluralize if pluralize_table_names "#{table_name_prefix}#{table_name}#{table_name_suffix}" end end end end doorkeeper-5.6.6/lib/doorkeeper/orm/active_record/mixins/access_token.rb0000644000004100000410000000522214422352653026536 0ustar www-datawww-data# frozen_string_literal: true module Doorkeeper::Orm::ActiveRecord::Mixins module AccessToken extend ActiveSupport::Concern included do self.table_name = compute_doorkeeper_table_name self.strict_loading_by_default = false if respond_to?(:strict_loading_by_default) include ::Doorkeeper::AccessTokenMixin belongs_to :application, class_name: Doorkeeper.config.application_class.to_s, inverse_of: :access_tokens, optional: true validates :token, presence: true, uniqueness: { case_sensitive: true } validates :refresh_token, uniqueness: { case_sensitive: 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? end module ClassMethods # 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 active_for(resource_owner) by_resource_owner(resource_owner).where(revoked_at: nil) end def refresh_token_revoked_on_use? column_names.include?("previous_refresh_token") end # Returns non-expired and non-revoked access tokens def not_expired relation = where(revoked_at: nil) if supports_expiration_time_math? # have not reached the expiration time or it never expires relation.where("#{expiration_time_sql} > ?", Time.now.utc).or( relation.where(expires_in: nil) ) else ::Kernel.warn <<~WARNING.squish [DOORKEEPER] Doorkeeper doesn't support expiration time math for your database adapter (#{adapter_name}). Please add a class method `custom_expiration_time_sql` for your AccessToken class/mixin to provide a custom SQL expression to calculate access token expiration time. See lib/doorkeeper/orm/active_record/mixins/access_token.rb for more details. WARNING relation end end private def compute_doorkeeper_table_name table_name = "oauth_access_token" table_name = table_name.pluralize if pluralize_table_names "#{table_name_prefix}#{table_name}#{table_name_suffix}" end end end end doorkeeper-5.6.6/lib/doorkeeper/orm/active_record/access_token.rb0000644000004100000410000000034114422352653025224 0ustar www-datawww-data# frozen_string_literal: true require "doorkeeper/orm/active_record/mixins/access_token" module Doorkeeper class AccessToken < ::ActiveRecord::Base include Doorkeeper::Orm::ActiveRecord::Mixins::AccessToken end end doorkeeper-5.6.6/lib/doorkeeper/rails/0000755000004100000410000000000014422352653017744 5ustar www-datawww-datadoorkeeper-5.6.6/lib/doorkeeper/rails/routes.rb0000644000004100000410000000616514422352653021622 0ustar www-datawww-data# frozen_string_literal: true require "doorkeeper/rails/routes/mapping" require "doorkeeper/rails/routes/mapper" require "doorkeeper/rails/routes/abstract_router" require "doorkeeper/rails/routes/registry" module Doorkeeper module Rails class Routes # :nodoc: module Helper def use_doorkeeper(options = {}, &block) Doorkeeper::Rails::Routes.new(self, &block).generate_routes!(options) end end include AbstractRouter extend Registry mattr_reader :mapping do {} end def self.install! ActionDispatch::Routing::Mapper.include Doorkeeper::Rails::Routes::Helper registered_routes.each(&:install!) end def initialize(routes, mapper = Mapper.new, &block) super 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) if introspection_routes? map_route(:applications, :application_routes) map_route(:authorized_applications, :authorized_applications_routes) map_route(:token_info, :token_info_routes) end end private def authorization_routes(mapping) routes.resource( :authorization, path: "authorize", only: %i[create destroy], as: mapping[:as], controller: mapping[:controllers], ) do routes.get native_authorization_code_route, 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 def native_authorization_code_route Doorkeeper.configuration.native_authorization_code_route end def introspection_routes? Doorkeeper.configured? && !Doorkeeper.config.allow_token_introspection.is_a?(FalseClass) end end end end doorkeeper-5.6.6/lib/doorkeeper/rails/helpers.rb0000644000004100000410000000432014422352653021732 0ustar www-datawww-data# frozen_string_literal: true module Doorkeeper module Rails module Helpers def doorkeeper_authorize!(*scopes) @_doorkeeper_scopes = scopes.presence || Doorkeeper.config.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&.acceptable?(@_doorkeeper_scopes) end private def doorkeeper_render_error error = doorkeeper_error error.raise_exception! if Doorkeeper.config.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.config.access_token_methods, ) end end end end doorkeeper-5.6.6/lib/doorkeeper/rails/routes/0000755000004100000410000000000014422352653021265 5ustar www-datawww-datadoorkeeper-5.6.6/lib/doorkeeper/rails/routes/abstract_router.rb0000644000004100000410000000141214422352653025013 0ustar www-datawww-data# frozen_string_literal: true module Doorkeeper module Rails # Abstract router module that implements base behavior # for generating and mapping Rails routes. # # Could be reused in Doorkeeper extensions. # module AbstractRouter extend ActiveSupport::Concern attr_reader :routes def initialize(routes, mapper = Mapper.new, &block) @routes = routes @mapping = mapper.map(&block) end def generate_routes!(**_options) raise NotImplementedError, "must be redefined for #{self.class.name}!" end private def map_route(name, method) return if @mapping.skipped?(name) send(method, @mapping[name]) mapping[name] = @mapping[name] end end end end doorkeeper-5.6.6/lib/doorkeeper/rails/routes/mapper.rb0000644000004100000410000000116714422352653023103 0ustar www-datawww-data# frozen_string_literal: true module Doorkeeper module Rails class Routes # :nodoc: class Mapper def initialize(mapping = Mapping.new) @mapping = mapping 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.6.6/lib/doorkeeper/rails/routes/mapping.rb0000644000004100000410000000163014422352653023245 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.6.6/lib/doorkeeper/rails/routes/registry.rb0000644000004100000410000000234014422352653023461 0ustar www-datawww-data# frozen_string_literal: true module Doorkeeper module Rails class Routes # Thread-safe registry of any Doorkeeper additional routes. # Used to allow implementing of Doorkeeper extensions that must # use their own routes. # module Registry ROUTES_ACCESS_LOCK = Mutex.new ROUTES_DEFINITION_LOCK = Mutex.new InvalidRouterClass = Class.new(StandardError) # Collection of additional registered routes for Doorkeeper. # # @return [Array] set of registered routes # def registered_routes ROUTES_DEFINITION_LOCK.synchronize do @registered_routes ||= Set.new end end # Registers additional routes in the Doorkeeper registry # # @param [Object] routes # routes class # def register_routes(routes) if !routes.is_a?(Module) || !(routes < AbstractRouter) raise InvalidRouterClass, "routes class must include Doorkeeper::Rails::AbstractRouter" end ROUTES_ACCESS_LOCK.synchronize do registered_routes << routes end end alias register register_routes end end end end doorkeeper-5.6.6/lib/doorkeeper/config/0000755000004100000410000000000014422352653020077 5ustar www-datawww-datadoorkeeper-5.6.6/lib/doorkeeper/config/abstract_builder.rb0000644000004100000410000000123514422352653023736 0ustar www-datawww-data# frozen_string_literal: true module Doorkeeper class Config # Abstract base class for Doorkeeper and it's extensions configuration # builder. Instantiates and validates gem configuration. # class AbstractBuilder attr_reader :config # @param [Class] config class # def initialize(config = Config.new, &block) @config = config instance_eval(&block) if block_given? end # Builds and validates configuration. # # @return [Doorkeeper::Config] config instance # def build @config.validate! if @config.respond_to?(:validate!) @config end end end end doorkeeper-5.6.6/lib/doorkeeper/config/option.rb0000644000004100000410000000546614422352653021747 0ustar www-datawww-data# frozen_string_literal: true module Doorkeeper class Config # Doorkeeper configuration option DSL 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 # * [+:builder_class+] Configuration option builder class # # ==== 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_class.instance_eval do if method_defined?(name) Kernel.warn "[DOORKEEPER] Option #{name} already defined and will be overridden" remove_method name end define_method name do |*args, &block| if (deprecation_opts = options[:deprecated]) warning = "[DOORKEEPER] #{name} has been deprecated and will soon be removed" warning = "#{warning}\n#{deprecation_opts.fetch(:message)}" if deprecation_opts.is_a?(Hash) Kernel.warn(warning) end 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 def self.extended(base) return if base.respond_to?(:builder_class) raise Doorkeeper::MissingConfigurationBuilderClass, "Define `self.builder_class` method " \ "for #{base} that returns your custom Builder class to use options DSL!" end end end end doorkeeper-5.6.6/lib/doorkeeper/config/validations.rb0000644000004100000410000000322314422352653022741 0ustar www-datawww-data# frozen_string_literal: true module Doorkeeper class Config # Doorkeeper configuration validator. # module Validations # Validates configuration options to be set properly. # def validate! validate_reuse_access_token_value validate_token_reuse_limit validate_secret_strategies end private # Determine whether +reuse_access_token+ and a non-restorable # +token_secret_strategy+ have both been activated. # # In that case, disable reuse_access_token value and warn the user. def validate_reuse_access_token_value strategy = token_secret_strategy return if !reuse_access_token || strategy.allows_restoring_secrets? ::Rails.logger.warn( "[DOORKEEPER] You have configured both reuse_access_token " \ "AND '#{strategy}' strategy which cannot restore tokens. " \ "This combination is unsupported. reuse_access_token will be disabled", ) @reuse_access_token = false end # Validate that the provided strategies are valid for # tokens and applications def validate_secret_strategies token_secret_strategy.validate_for(:token) application_secret_strategy.validate_for(:application) end def validate_token_reuse_limit return if !reuse_access_token || (token_reuse_limit > 0 && token_reuse_limit <= 100) ::Rails.logger.warn( "[DOORKEEPER] You have configured an invalid value for token_reuse_limit option. " \ "It will be set to default 100", ) @token_reuse_limit = 100 end end end end doorkeeper-5.6.6/lib/doorkeeper/validations.rb0000644000004100000410000000117514422352653021500 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.6.6/lib/doorkeeper/rake/0000755000004100000410000000000014422352653017554 5ustar www-datawww-datadoorkeeper-5.6.6/lib/doorkeeper/rake/db.rake0000644000004100000410000000271314422352653021010 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.config.access_token_model) cleaner.clean_revoked end desc "Removes expired (TTL passed) access tokens" task expired_tokens: "doorkeeper:setup" do expirable_tokens = Doorkeeper.config.access_token_model.where(refresh_token: nil) cleaner = Doorkeeper::StaleRecordsCleaner.new(expirable_tokens) cleaner.clean_expired(Doorkeeper.config.access_token_expires_in) end desc "Removes stale access grants" task revoked_grants: "doorkeeper:setup" do cleaner = Doorkeeper::StaleRecordsCleaner.new(Doorkeeper.config.access_grant_model) cleaner.clean_revoked end desc "Removes expired (TTL passed) access grants" task expired_grants: "doorkeeper:setup" do cleaner = Doorkeeper::StaleRecordsCleaner.new(Doorkeeper.config.access_grant_model) cleaner.clean_expired(Doorkeeper.config.authorization_code_expires_in) end end end end doorkeeper-5.6.6/lib/doorkeeper/rake/setup.rake0000644000004100000410000000014014422352653021553 0ustar www-datawww-data# frozen_string_literal: true namespace :doorkeeper do task setup: :environment do end end doorkeeper-5.6.6/lib/doorkeeper/oauth.rb0000644000004100000410000000045614422352653020304 0ustar www-datawww-data# frozen_string_literal: true module Doorkeeper module OAuth GRANT_TYPES = [ AUTHORIZATION_CODE = "authorization_code", IMPLICIT = "implicit", PASSWORD = "password", CLIENT_CREDENTIALS = "client_credentials", REFRESH_TOKEN = "refresh_token", ].freeze end end doorkeeper-5.6.6/lib/doorkeeper/server.rb0000644000004100000410000000174414422352653020473 0ustar www-datawww-data# frozen_string_literal: true module Doorkeeper class Server attr_reader :context def initialize(context) @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 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.config.client_credentials_methods @credentials ||= OAuth::Client::Credentials.from_request(context.request, *methods) end end end doorkeeper-5.6.6/lib/doorkeeper/oauth/0000755000004100000410000000000014422352653017752 5ustar www-datawww-datadoorkeeper-5.6.6/lib/doorkeeper/oauth/client/0000755000004100000410000000000014422352653021230 5ustar www-datawww-datadoorkeeper-5.6.6/lib/doorkeeper/oauth/client/credentials.rb0000644000004100000410000000201614422352653024051 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 if credentials.present? 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 delegate :blank?, to: :uid end end end end doorkeeper-5.6.6/lib/doorkeeper/oauth/base_response.rb0000644000004100000410000000054714422352653023135 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.6.6/lib/doorkeeper/oauth/forbidden_token_response.rb0000644000004100000410000000153114422352653025351 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 ||= I18n.t("doorkeeper.errors.messages.forbidden_token.missing_scope", oauth_scopes: @scopes.map(&:to_s).join(" "),) end protected def exception_class Doorkeeper::Errors::TokenForbidden end end end end doorkeeper-5.6.6/lib/doorkeeper/oauth/client_credentials/0000755000004100000410000000000014422352653023605 5ustar www-datawww-datadoorkeeper-5.6.6/lib/doorkeeper/oauth/client_credentials/creator.rb0000644000004100000410000000313414422352653025572 0ustar www-datawww-data# frozen_string_literal: true module Doorkeeper module OAuth module ClientCredentials class Creator def call(client, scopes, attributes = {}) existing_token = nil if lookup_existing_token? existing_token = find_active_existing_token_for(client, scopes) return existing_token if Doorkeeper.config.reuse_access_token && existing_token&.reusable? end with_revocation(existing_token: existing_token) do application = client.is_a?(Doorkeeper.config.application_model) ? client : client&.application Doorkeeper.config.access_token_model.create_for( application: application, resource_owner: nil, scopes: scopes, **attributes, ) end end private def with_revocation(existing_token:) if existing_token && Doorkeeper.config.revoke_previous_client_credentials_token? existing_token.with_lock do raise Errors::DoorkeeperError, :invalid_token_reuse if existing_token.revoked? existing_token.revoke yield end else yield end end def lookup_existing_token? Doorkeeper.config.reuse_access_token || Doorkeeper.config.revoke_previous_client_credentials_token? end def find_active_existing_token_for(client, scopes) Doorkeeper.config.access_token_model.matching_token_for(client, nil, scopes, include_expired: false) end end end end end doorkeeper-5.6.6/lib/doorkeeper/oauth/client_credentials/validator.rb0000644000004100000410000000265114422352653026123 0ustar www-datawww-data# frozen_string_literal: true module Doorkeeper module OAuth module ClientCredentials class Validator include Validations include OAuth::Helpers validate :client, error: :invalid_client validate :client_supports_grant_flow, error: :unauthorized_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_client_supports_grant_flow return if @client.blank? Doorkeeper.config.allow_grant_flow_for_client?( Doorkeeper::OAuth::CLIENT_CREDENTIALS, @client.application, ) end def validate_scopes application_scopes = if @client.present? @client.application.scopes else "" end return true if @request.scopes.blank? && application_scopes.blank? ScopeChecker.valid?( scope_str: @request.scopes.to_s, server_scopes: @server.scopes, app_scopes: application_scopes, grant_type: Doorkeeper::OAuth::CLIENT_CREDENTIALS, ) end end end end end doorkeeper-5.6.6/lib/doorkeeper/oauth/client_credentials/issuer.rb0000644000004100000410000000211514422352653025443 0ustar www-datawww-data# frozen_string_literal: true module Doorkeeper module OAuth module ClientCredentials class Issuer attr_reader :token, :validator, :error def initialize(server, validator) @server = server @validator = validator end def create(client, scopes, creator = Creator.new) if validator.valid? @token = create_token(client, scopes, creator) @error = :server_error unless @token else @token = false @error = validator.error end @token end private def create_token(client, scopes, creator) context = Authorization::Token.build_context( client, Doorkeeper::OAuth::CLIENT_CREDENTIALS, scopes, nil, ) 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.6.6/lib/doorkeeper/oauth/password_access_token_request.rb0000644000004100000410000000467414422352653026445 0ustar www-datawww-data# frozen_string_literal: true module Doorkeeper module OAuth class PasswordAccessTokenRequest < BaseRequest include OAuth::Helpers validate :client, error: :invalid_client validate :client_supports_grant_flow, error: :unauthorized_client validate :resource_owner, error: :invalid_grant validate :scopes, error: :invalid_scope attr_reader :client, :credentials, :resource_owner, :parameters, :access_token def initialize(server, client, credentials, resource_owner, parameters = {}) @server = server @resource_owner = resource_owner @client = client @credentials = credentials @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, scopes, {}, server) super end def validate_scopes return true if scopes.blank? ScopeChecker.valid?( scope_str: scopes.to_s, server_scopes: server.scopes, app_scopes: client.try(:scopes), grant_type: grant_type, ) end def validate_resource_owner resource_owner.present? end # Section 4.3.2. Access Token Request for Resource Owner Password Credentials Grant: # # If the client type is confidential or the client was issued client credentials (or assigned # other authentication requirements), the client MUST authenticate with the authorization # server as described in Section 3.2.1. # # The authorization server MUST: # # o require client authentication for confidential clients or for any client that was # issued client credentials (or with other authentication requirements) # # o authenticate the client if client authentication is included, # # @see https://datatracker.ietf.org/doc/html/rfc6749#section-4.3 # def validate_client if Doorkeeper.config.skip_client_authentication_for_password_grant client.present? || (!parameters[:client_id] && credentials.blank?) else client.present? end end def validate_client_supports_grant_flow Doorkeeper.config.allow_grant_flow_for_client?(grant_type, client&.application) end end end end doorkeeper-5.6.6/lib/doorkeeper/oauth/helpers/0000755000004100000410000000000014422352653021414 5ustar www-datawww-datadoorkeeper-5.6.6/lib/doorkeeper/oauth/helpers/unique_token.rb0000644000004100000410000000176714422352653024462 0ustar www-datawww-data# frozen_string_literal: true module Doorkeeper module OAuth module Helpers # Default Doorkeeper token generator. Follows OAuth RFC and # could be customized using `default_generator_method` in # configuration. module UniqueToken def self.generate(options = {}) # Access Token value must be 1*VSCHAR or # 1*( ALPHA / DIGIT / "-" / "." / "_" / "~" / "+" / "/" ) *"=" # # @see https://datatracker.ietf.org/doc/html/rfc6749#appendix-A.12 # @see https://datatracker.ietf.org/doc/html/rfc6750#section-2.1 # generator = options.delete(:generator) || SecureRandom.method(default_generator_method) token_size = options.delete(:size) || 32 generator.call(token_size) end # Generator method for default generator class (SecureRandom) # def self.default_generator_method Doorkeeper.config.default_generator_method end end end end end doorkeeper-5.6.6/lib/doorkeeper/oauth/helpers/uri_checker.rb0000644000004100000410000000432014422352653024223 0ustar www-datawww-data# frozen_string_literal: true require "ipaddr" module Doorkeeper module OAuth module Helpers module URIChecker def self.valid?(url) return true if oob_uri?(url) uri = as_uri(url) valid_scheme?(uri) && iff_host?(uri) && uri.fragment.nil? && uri.opaque.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 # RFC8252, Paragraph 7.3 # @see https://datatracker.ietf.org/doc/html/rfc8252#section-7.3 if loopback_uri?(url) && loopback_uri?(client_url) url.port = nil client_url.port = nil end url.query = nil url == client_url end def self.loopback_uri?(uri) IPAddr.new(uri.host).loopback? rescue IPAddr::Error false 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.valid_scheme?(uri) return false if uri.scheme.blank? %w[localhost].exclude?(uri.scheme) end def self.hypertext_scheme?(uri) %w[http https].include?(uri.scheme) end def self.iff_host?(uri) !(hypertext_scheme?(uri) && uri.host.blank?) end def self.oob_uri?(uri) NonStandard::IETF_WG_OAUTH2_OOB_METHODS.include?(uri) end end end end end doorkeeper-5.6.6/lib/doorkeeper/oauth/helpers/scope_checker.rb0000644000004100000410000000255214422352653024542 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, app_scopes, grant_type) @parsed_scopes = OAuth::Scopes.from_string(scope_str) @scope_str = scope_str @valid_scopes = valid_scopes(server_scopes, app_scopes) @scopes_by_grant_type = Doorkeeper.config.scopes_by_grant_type[grant_type.to_sym] if grant_type end def valid? scope_str.present? && scope_str !~ /[\n\r\t]/ && @valid_scopes.has_scopes?(parsed_scopes) && permitted_to_grant_type? end private def valid_scopes(server_scopes, app_scopes) app_scopes.presence || server_scopes end def permitted_to_grant_type? return true unless @scopes_by_grant_type OAuth::Scopes.from_array(@scopes_by_grant_type) .has_scopes?(parsed_scopes) end end def self.valid?(scope_str:, server_scopes:, app_scopes: nil, grant_type: nil) Validator.new( scope_str, server_scopes, app_scopes, grant_type, ).valid? end end end end end doorkeeper-5.6.6/lib/doorkeeper/oauth/authorization/0000755000004100000410000000000014422352653022652 5ustar www-datawww-datadoorkeeper-5.6.6/lib/doorkeeper/oauth/authorization/uri_builder.rb0000644000004100000410000000144714422352653025512 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.reject! { |_, value| value.blank? } Rack::Utils.build_query(parameters) end end end end end end doorkeeper-5.6.6/lib/doorkeeper/oauth/authorization/context.rb0000644000004100000410000000060214422352653024661 0ustar www-datawww-data# frozen_string_literal: true module Doorkeeper module OAuth module Authorization class Context attr_reader :client, :grant_type, :resource_owner, :scopes def initialize(**attributes) attributes.each do |name, value| instance_variable_set(:"@#{name}", value) if respond_to?(name) end end end end end end doorkeeper-5.6.6/lib/doorkeeper/oauth/authorization/token.rb0000644000004100000410000000601014422352653024314 0ustar www-datawww-data# frozen_string_literal: true module Doorkeeper module OAuth module Authorization class Token attr_reader :pre_auth, :resource_owner, :token class << self def build_context(pre_auth_or_oauth_client, grant_type, scopes, resource_owner) oauth_client = if pre_auth_or_oauth_client.respond_to?(:application) pre_auth_or_oauth_client.application elsif 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( client: oauth_client, grant_type: grant_type, scopes: scopes, resource_owner: resource_owner, ) end def access_token_expires_in(configuration, context) if configuration.option_defined?(:custom_access_token_expires_in) expiration = configuration.custom_access_token_expires_in.call(context) return nil if expiration == Float::INFINITY expiration || configuration.access_token_expires_in else configuration.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! return @token if defined?(@token) context = self.class.build_context( pre_auth.client, Doorkeeper::OAuth::IMPLICIT, pre_auth.scopes, resource_owner, ) @token = Doorkeeper.config.access_token_model.find_or_create_for( application: application, resource_owner: resource_owner, scopes: pre_auth.scopes, expires_in: self.class.access_token_expires_in(Doorkeeper.config, context), use_refresh_token: false, ) end def application return unless pre_auth.client pre_auth.client.is_a?(Doorkeeper.config.application_model) ? pre_auth.client : pre_auth.client.application end def oob_redirect { controller: controller, action: :show, access_token: token.plaintext_token, } end def access_token? true end private def controller @controller ||= begin mapping = Doorkeeper::Rails::Routes.mapping[:token_info] || {} mapping[:controllers] || "doorkeeper/token_info" end end end end end end doorkeeper-5.6.6/lib/doorkeeper/oauth/authorization/code.rb0000644000004100000410000000377414422352653024124 0ustar www-datawww-data# frozen_string_literal: true module Doorkeeper module OAuth module Authorization class Code attr_reader :pre_auth, :resource_owner, :token def initialize(pre_auth, resource_owner) @pre_auth = pre_auth @resource_owner = resource_owner end def issue_token! return @token if defined?(@token) @token = Doorkeeper.config.access_grant_model.create!(access_grant_attributes) end def oob_redirect { action: :show, code: token.plaintext_token } end def access_grant? true end private def authorization_code_expires_in Doorkeeper.config.authorization_code_expires_in end def access_grant_attributes attributes = { application_id: pre_auth.client.id, expires_in: authorization_code_expires_in, redirect_uri: pre_auth.redirect_uri, scopes: pre_auth.scopes.to_s, } if Doorkeeper.config.polymorphic_resource_owner? attributes[:resource_owner] = resource_owner else attributes[:resource_owner_id] = resource_owner.id end pkce_attributes.merge(attributes).merge(custom_attributes) end def custom_attributes # Custom access token attributes are saved into the access grant, # and then included in subsequently generated access tokens. @pre_auth.custom_access_token_attributes.to_h.with_indifferent_access 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 PKCE columns was # generated and migrated def pkce_supported? Doorkeeper.config.access_grant_model.pkce_supported? end end end end end doorkeeper-5.6.6/lib/doorkeeper/oauth/error_response.rb0000644000004100000410000000412314422352653023346 0ustar www-datawww-data# frozen_string_literal: true module Doorkeeper module OAuth class ErrorResponse < BaseResponse include OAuth::Helpers NON_REDIRECTABLE_STATES = %i[invalid_redirect_uri invalid_client unauthorized_client].freeze 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 if name == :invalid_client || name == :unauthorized_client :unauthorized else :bad_request end end def redirectable? !NON_REDIRECTABLE_STATES.include?(name) && !URIChecker.oob_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, 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 def realm Doorkeeper.config.realm 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.6.6/lib/doorkeeper/oauth/token_response.rb0000644000004100000410000000140714422352653023337 0ustar www-datawww-data# frozen_string_literal: true module Doorkeeper module OAuth class TokenResponse attr_reader :token def initialize(token) @token = token end def body { "access_token" => token.plaintext_token, "token_type" => token.token_type, "expires_in" => token.expires_in_seconds, "refresh_token" => token.plaintext_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, no-cache", "Content-Type" => "application/json; charset=utf-8", } end end end end doorkeeper-5.6.6/lib/doorkeeper/oauth/client.rb0000644000004100000410000000134414422352653021557 0ustar www-datawww-data# frozen_string_literal: true module Doorkeeper module OAuth class Client attr_reader :application delegate :id, :name, :uid, :redirect_uri, :scopes, to: :@application def initialize(application) @application = application end def self.find(uid, method = Doorkeeper.config.application_model.method(:by_uid)) return unless (application = method.call(uid)) new(application) end def self.authenticate(credentials, method = Doorkeeper.config.application_model.method(:by_uid_and_secret)) return if credentials.blank? return unless (application = method.call(credentials.uid, credentials.secret)) new(application) end end end end doorkeeper-5.6.6/lib/doorkeeper/oauth/error.rb0000644000004100000410000000044014422352653021426 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.6.6/lib/doorkeeper/oauth/pre_authorization.rb0000644000004100000410000001223114422352653024044 0ustar www-datawww-data# frozen_string_literal: true module Doorkeeper module OAuth class PreAuthorization include Validations validate :client_id, error: :invalid_request validate :client, error: :invalid_client validate :client_supports_grant_flow, error: :unauthorized_client validate :resource_owner_authorize_for_client, error: :invalid_client validate :redirect_uri, error: :invalid_redirect_uri validate :params, error: :invalid_request validate :response_type, error: :unsupported_response_type validate :response_mode, error: :unsupported_response_mode validate :scopes, error: :invalid_scope validate :code_challenge_method, error: :invalid_code_challenge_method attr_reader :client, :code_challenge, :code_challenge_method, :missing_param, :redirect_uri, :resource_owner, :response_type, :state, :authorization_response_flow, :response_mode, :custom_access_token_attributes def initialize(server, parameters = {}, resource_owner = nil) @server = server @client_id = parameters[:client_id] @response_type = parameters[:response_type] @response_mode = parameters[:response_mode] @redirect_uri = parameters[:redirect_uri] @scope = parameters[:scope] @state = parameters[:state] @code_challenge = parameters[:code_challenge] @code_challenge_method = parameters[:code_challenge_method] @resource_owner = resource_owner @custom_access_token_attributes = parameters.slice(*Doorkeeper.config.custom_access_token_attributes) end def authorizable? valid? end def scopes Scopes.from_string(scope) end def scope @scope.presence || (server.default_scopes.presence && build_scopes) end def error_response if error == :invalid_request OAuth::InvalidRequestResponse.from_request( self, response_on_fragment: response_on_fragment?, ) else OAuth::ErrorResponse.from_request(self, response_on_fragment: response_on_fragment?) end end def as_json(_options = nil) pre_auth_hash end def form_post_response? response_mode == "form_post" end private attr_reader :client_id, :server def build_scopes client_scopes = client.scopes if client_scopes.blank? server.default_scopes.to_s else (server.default_scopes & client_scopes).to_s end end def validate_client_id @missing_param = :client_id if client_id.blank? @missing_param.nil? end def validate_client @client = OAuth::Client.find(client_id) @client.present? end def validate_client_supports_grant_flow Doorkeeper.config.allow_grant_flow_for_client?(grant_type, client.application) end def validate_resource_owner_authorize_for_client # The `authorize_resource_owner_for_client` config option is used for this validation client.application.authorized_for_resource_owner?(@resource_owner) end def validate_redirect_uri return false if redirect_uri.blank? Helpers::URIChecker.valid_for_authorization?( redirect_uri, client.redirect_uri, ) end def validate_params @missing_param = if response_type.blank? :response_type elsif @scope.blank? && server.default_scopes.blank? :scope end @missing_param.nil? end def validate_response_type server.authorization_response_flows.any? do |flow| if flow.matches_response_type?(response_type) @authorization_response_flow = flow true end end end def validate_response_mode if response_mode.blank? @response_mode = authorization_response_flow.default_response_mode return true end authorization_response_flow.matches_response_mode?(response_mode) end def validate_scopes Helpers::ScopeChecker.valid?( scope_str: scope, server_scopes: server.scopes, app_scopes: client.scopes, grant_type: grant_type, ) end def validate_code_challenge_method return true unless Doorkeeper.config.access_grant_model.pkce_supported? code_challenge.blank? || (code_challenge_method.present? && code_challenge_method =~ /^plain$|^S256$/) end def response_on_fragment? return response_type == "token" if response_mode.nil? response_mode == "fragment" end def grant_type response_type == "code" ? AUTHORIZATION_CODE : IMPLICIT end def pre_auth_hash { 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 end end end doorkeeper-5.6.6/lib/doorkeeper/oauth/nonstandard.rb0000644000004100000410000000412714422352653022616 0ustar www-datawww-data# frozen_string_literal: true module Doorkeeper module OAuth class NonStandard # These are not part of the OAuth 2 specification but are still in use by Google # and in some other implementations. Native applications should use one of the # approaches discussed in RFC8252. OOB is 'Out of Band' # This value signals to the Google Authorization Server that the authorization # code should be returned in the title bar of the browser, with the page text # prompting the user to copy the code and paste it in the application. # This is useful when the client (such as a Windows application) cannot listen # on an HTTP port without significant client configuration. # When you use this value, your application can then detect that the page has loaded, and can # read the title of the HTML page to obtain the authorization code. It is then up to your # application to close the browser window if you want to ensure that the user never sees the # page that contains the authorization code. The mechanism for doing this varies from platform # to platform. # # If your platform doesn't allow you to detect that the page has loaded or read the title of # the page, you can have the user paste the code back to your application, as prompted by the # text in the confirmation page that the OAuth 2.0 server generates. IETF_WG_OAUTH2_OOB = "urn:ietf:wg:oauth:2.0:oob" # This is identical to urn:ietf:wg:oauth:2.0:oob, but the text in the confirmation page that # the OAuth 2.0 server generates won't instruct the user to copy the authorization code, but # instead will simply ask the user to close the window. # # This is useful when your application reads the title of the HTML page (by checking window # titles on the desktop, for example) to obtain the authorization code, but can't close the # page on its own. IETF_WG_OAUTH2_OOB_AUTO = "urn:ietf:wg:oauth:2.0:oob:auto" IETF_WG_OAUTH2_OOB_METHODS = [IETF_WG_OAUTH2_OOB, IETF_WG_OAUTH2_OOB_AUTO].freeze end end end doorkeeper-5.6.6/lib/doorkeeper/oauth/invalid_token_response.rb0000644000004100000410000000230514422352653025043 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&.revoked? :revoked elsif access_token&.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 status :unauthorized end def description @description ||= I18n.translate( @reason, scope: %i[doorkeeper errors messages invalid_token], ) 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.6.6/lib/doorkeeper/oauth/base_request.rb0000644000004100000410000000367014422352653022767 0ustar www-datawww-data# frozen_string_literal: true module Doorkeeper module OAuth class BaseRequest include Validations attr_reader :grant_type, :server delegate :default_scopes, to: :server def authorize if valid? before_successful_response @response = TokenResponse.new(access_token) after_successful_response @response elsif error == :invalid_request @response = InvalidRequestResponse.from_request(self) else @response = ErrorResponse.from_request(self) end end def scopes @scopes ||= build_scopes end def find_or_create_access_token(client, resource_owner, scopes, custom_attributes, server) context = Authorization::Token.build_context(client, grant_type, scopes, resource_owner) application = client.is_a?(Doorkeeper.config.application_model) ? client : client&.application token_attributes = { application: application, resource_owner: resource_owner, scopes: scopes, expires_in: Authorization::Token.access_token_expires_in(server, context), use_refresh_token: Authorization::Token.refresh_token_enabled?(server, context), } @access_token = Doorkeeper.config.access_token_model.find_or_create_for(**token_attributes.merge(custom_attributes)) end def before_successful_response Doorkeeper.config.before_successful_strategy_response.call(self) end def after_successful_response Doorkeeper.config.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&.scopes return default_scopes if client_scopes.blank? default_scopes & client_scopes end end end end end doorkeeper-5.6.6/lib/doorkeeper/oauth/authorization_code_request.rb0000644000004100000410000000607514422352653025751 0ustar www-datawww-data# frozen_string_literal: true module Doorkeeper module OAuth class AuthorizationCodeRequest < BaseRequest validate :params, error: :invalid_request validate :client, error: :invalid_client validate :grant, error: :invalid_grant # @see https://datatracker.ietf.org/doc/html/rfc6749#section-5.2 validate :redirect_uri, error: :invalid_grant validate :code_verifier, error: :invalid_grant attr_reader :grant, :client, :redirect_uri, :access_token, :code_verifier, :invalid_request_reason, :missing_param 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 before_successful_response grant.transaction do grant.lock! raise Errors::InvalidGrantReuse if grant.revoked? grant.revoke find_or_create_access_token( grant.application, resource_owner, grant.scopes, custom_token_attributes_with_data, server, ) end super end def resource_owner if Doorkeeper.config.polymorphic_resource_owner? grant.resource_owner else grant.resource_owner_id end end def pkce_supported? Doorkeeper.config.access_grant_model.pkce_supported? end def validate_params @missing_param = if grant&.uses_pkce? && code_verifier.blank? :code_verifier elsif redirect_uri.blank? :redirect_uri end @missing_param.nil? end def validate_client client.present? 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 pkce_supported? return grant.code_challenge.blank? if code_verifier.blank? if grant.code_challenge_method == "S256" grant.code_challenge == generate_code_challenge(code_verifier) elsif grant.code_challenge_method == "plain" grant.code_challenge == code_verifier else false end end def generate_code_challenge(code_verifier) Doorkeeper.config.access_grant_model.generate_code_challenge(code_verifier) end def custom_token_attributes_with_data grant .attributes .with_indifferent_access .slice(*Doorkeeper.config.custom_access_token_attributes) .symbolize_keys end end end end doorkeeper-5.6.6/lib/doorkeeper/oauth/scopes.rb0000644000004100000410000000252514422352653021577 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 scopes?(scopes) scopes.all? { |scope| exists?(scope) } end alias has_scopes? scopes? 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.6.6/lib/doorkeeper/oauth/refresh_token_request.rb0000644000004100000410000000763414422352653024717 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_reader :access_token, :client, :credentials, :refresh_token attr_reader :missing_param 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] @client = load_client(credentials) if credentials end private def load_client(credentials) Doorkeeper.config.application_model.by_uid_and_secret(credentials.uid, credentials.secret) end def before_successful_response refresh_token.transaction do refresh_token.lock! raise Errors::InvalidGrantReuse 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.config.access_token_model.refresh_token_revoked_on_use? end def default_scopes refresh_token.scopes end def create_access_token attributes = {}.merge(custom_token_attributes_with_data) resource_owner = if Doorkeeper.config.polymorphic_resource_owner? refresh_token.resource_owner else refresh_token.resource_owner_id end if refresh_token_revoked_on_use? attributes[:previous_refresh_token] = refresh_token.refresh_token end # RFC6749 # 1.5. Refresh Token # # Refresh tokens are issued to the client by the authorization server and are # used to obtain a new access token when the current access token # becomes invalid or expires, or to obtain additional access tokens # with identical or narrower scope (access tokens may have a shorter # lifetime and fewer permissions than authorized by the resource # owner). # # Here we assume that TTL of the token received after refreshing should be # the same as that of the original token. # @access_token = Doorkeeper.config.access_token_model.create_for( application: refresh_token.application, resource_owner: resource_owner, scopes: scopes, expires_in: refresh_token.expires_in, use_refresh_token: true, **attributes, ) end def validate_token_presence @missing_param = :refresh_token if refresh_token.blank? && @refresh_token_parameter.blank? @missing_param.nil? end def validate_token refresh_token.present? && !refresh_token.revoked? end def validate_client return true if credentials.blank? client.present? end # @see https://datatracker.ietf.org/doc/html/rfc6749#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?( scope_str: @original_scopes, server_scopes: refresh_token.scopes, ) else true end end def custom_token_attributes_with_data refresh_token .attributes .with_indifferent_access .slice(*Doorkeeper.config.custom_access_token_attributes) .symbolize_keys end end end end doorkeeper-5.6.6/lib/doorkeeper/oauth/invalid_request_response.rb0000644000004100000410000000205614422352653025416 0ustar www-datawww-data# frozen_string_literal: true module Doorkeeper module OAuth class InvalidRequestResponse < ErrorResponse attr_reader :reason def self.from_request(request, attributes = {}) new( attributes.merge( state: request.try(:state), redirect_uri: request.try(:redirect_uri), missing_param: request.try(:missing_param), reason: request.try(:invalid_request_reason), ), ) end def initialize(attributes = {}) super(attributes.merge(name: :invalid_request)) @missing_param = attributes[:missing_param] @reason = @missing_param.nil? ? attributes[:reason] : :missing_param end def status :bad_request end def description I18n.translate( reason, scope: %i[doorkeeper errors messages invalid_request], default: :unknown, value: @missing_param, ) end def redirectable? super && @missing_param != :client_id end end end end doorkeeper-5.6.6/lib/doorkeeper/oauth/token_request.rb0000644000004100000410000000107014422352653023165 0ustar www-datawww-data# frozen_string_literal: true module Doorkeeper module OAuth class TokenRequest attr_reader :pre_auth, :resource_owner def initialize(pre_auth, resource_owner) @pre_auth = pre_auth @resource_owner = resource_owner end def authorize auth = Authorization::Token.new(pre_auth, resource_owner) auth.issue_token! CodeResponse.new(pre_auth, auth, response_on_fragment: true) end def deny pre_auth.error = :access_denied pre_auth.error_response end end end end doorkeeper-5.6.6/lib/doorkeeper/oauth/token.rb0000644000004100000410000000360014422352653021416 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 if credentials.present? end end def authenticate(request, *methods) if (token = from_request(request, *methods)) access_token = Doorkeeper.config.access_token_model.by_token(token) if access_token.present? && Doorkeeper.config.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&.match(pattern) end end end end end doorkeeper-5.6.6/lib/doorkeeper/oauth/code_response.rb0000644000004100000410000000235014422352653023127 0ustar www-datawww-data# frozen_string_literal: true module Doorkeeper module OAuth class CodeResponse < BaseResponse include OAuth::Helpers attr_reader :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 issued_token auth.token end def body if auth.try(:access_token?) { access_token: auth.token.plaintext_token, token_type: auth.token.token_type, expires_in: auth.token.expires_in_seconds, state: pre_auth.state, } elsif auth.try(:access_grant?) { code: auth.token.plaintext_token, state: pre_auth.state, } end end def redirect_uri if URIChecker.oob_uri?(pre_auth.redirect_uri) auth.oob_redirect elsif response_on_fragment Authorization::URIBuilder.uri_with_fragment(pre_auth.redirect_uri, body) else Authorization::URIBuilder.uri_with_query(pre_auth.redirect_uri, body) end end end end end doorkeeper-5.6.6/lib/doorkeeper/oauth/hooks/0000755000004100000410000000000014422352653021075 5ustar www-datawww-datadoorkeeper-5.6.6/lib/doorkeeper/oauth/hooks/context.rb0000644000004100000410000000063714422352653023114 0ustar www-datawww-data# frozen_string_literal: true module Doorkeeper module OAuth module Hooks class Context attr_reader :auth, :pre_auth def initialize(**attributes) attributes.each do |name, value| instance_variable_set(:"@#{name}", value) if respond_to?(name) end end def issued_token auth&.issued_token end end end end end doorkeeper-5.6.6/lib/doorkeeper/oauth/client_credentials_request.rb0000644000004100000410000000135114422352653025702 0ustar www-datawww-data# frozen_string_literal: true module Doorkeeper module OAuth class ClientCredentialsRequest < BaseRequest attr_reader :client, :original_scopes, :response alias error_response response delegate :error, to: :issuer def initialize(server, client, parameters = {}) @client = client @server = server @response = nil @original_scopes = parameters[:scope] end def access_token issuer.token end def issuer @issuer ||= ClientCredentials::Issuer.new( server, ClientCredentials::Validator.new(server, self), ) end private def valid? issuer.create(client, scopes) end end end end doorkeeper-5.6.6/lib/doorkeeper/oauth/code_request.rb0000644000004100000410000000112614422352653022761 0ustar www-datawww-data# frozen_string_literal: true module Doorkeeper module OAuth class CodeRequest attr_reader :pre_auth, :resource_owner def initialize(pre_auth, resource_owner) @pre_auth = pre_auth @resource_owner = resource_owner end def authorize auth = Authorization::Code.new(pre_auth, resource_owner) auth.issue_token! CodeResponse.new(pre_auth, auth, response_on_fragment: pre_auth.response_mode == "fragment") end def deny pre_auth.error = :access_denied pre_auth.error_response end end end end doorkeeper-5.6.6/lib/doorkeeper/oauth/token_introspection.rb0000644000004100000410000001712114422352653024401 0ustar www-datawww-data# frozen_string_literal: true module Doorkeeper module OAuth # RFC7662 OAuth 2.0 Token Introspection # # @see https://datatracker.ietf.org/doc/html/rfc7662 class TokenIntrospection def initialize(server, token) @server = server @token = token authorize! end def authorized? @error.blank? end def error_response return if @error.blank? if @error == :invalid_token OAuth::InvalidTokenResponse.from_access_token(authorized_token) elsif @error == :invalid_request OAuth::InvalidRequestResponse.from_request(self) else OAuth::ErrorResponse.new(name: @error) end end def to_json(*) active? ? success_response : failure_response end private attr_reader :server, :token attr_reader :error, :invalid_request_reason # 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/ # # To prevent token scanning attacks, the endpoint MUST also require # some form of authorization to access this endpoint, such as client # authentication as described in OAuth 2.0 [RFC6749] or a separate # OAuth 2.0 access token such as the bearer token described in OAuth # 2.0 Bearer Token Usage [RFC6750]. # def authorize! # Requested client authorization if server.credentials @error = :invalid_client unless authorized_client elsif authorized_token # Requested bearer token authorization # # If the protected resource uses an OAuth 2.0 bearer token to authorize # its call to the introspection endpoint and the token used for # authorization does not contain sufficient privileges or is otherwise # invalid for this request, the authorization server responds with an # HTTP 401 code as described in Section 3 of OAuth 2.0 Bearer Token # Usage [RFC6750]. # @error = :invalid_token unless valid_authorized_token? else @error = :invalid_request @invalid_request_reason = :request_not_authorized end end # Client Authentication def authorized_client @authorized_client ||= server.credentials && server.client end # Bearer Token Authentication def authorized_token @authorized_token ||= Doorkeeper.authenticate(server.context.request) end # 2.2. Introspection Response def success_response customize_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://datatracker.ietf.org/doc/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 # # Since resource servers using token introspection rely on the # authorization server to determine the state of a token, the # authorization server MUST perform all applicable checks against a # token's state. For instance, these tests include the following: # # o If the token can expire, the authorization server MUST determine # whether or not the token has expired. # o If the token can be issued before it is able to be used, the # authorization server MUST determine whether or not a token's valid # period has started yet. # o If the token can be revoked after it was issued, the authorization # server MUST determine whether or not such a revocation has taken # place. # o If the token has been signed, the authorization server MUST # validate the signature. # o If the token can be used only at certain resource servers, the # authorization server MUST determine whether or not the token can # be used at the resource server making the introspection call. # def active? if authorized_client valid_token? && token_introspection_allowed?(auth_client: authorized_client.application) else valid_token? end end # Token can be valid only if it is not expired or revoked. def valid_token? @token&.accessible? end def valid_authorized_token? !authorized_token_matches_introspected? && authorized_token.accessible? && token_introspection_allowed?(auth_token: authorized_token) end # RFC7662 Section 2.1 def authorized_token_matches_introspected? authorized_token.token == @token&.token end # Config constraints for introspection in Doorkeeper.config.allow_token_introspection def token_introspection_allowed?(auth_client: nil, auth_token: nil) allow_introspection = Doorkeeper.config.allow_token_introspection return allow_introspection unless allow_introspection.respond_to?(:call) allow_introspection.call(@token, auth_client, auth_token) end # Allows to customize introspection response. # Provides context (controller) and token for generating developer-specific # response. # # @see https://datatracker.ietf.org/doc/html/rfc7662#section-2.2 # def customize_response(response) customized_response = Doorkeeper.config.custom_introspection_response.call( token, server.context, ) return response if customized_response.blank? response.merge(customized_response) end end end end doorkeeper-5.6.6/lib/doorkeeper/config.rb0000644000004100000410000005734314422352653020440 0ustar www-datawww-data# frozen_string_literal: true require "doorkeeper/config/abstract_builder" require "doorkeeper/config/option" require "doorkeeper/config/validations" module Doorkeeper # Doorkeeper option DSL could be reused in extensions to build their own # configurations. To use the Option DSL gems need to define `builder_class` method # that returns configuration Builder class. This exception raises when they don't # define it. # class Config # Default Doorkeeper configuration builder class Builder < AbstractBuilder # 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 # Define scopes_by_grant_type to limit certain scope to certain grant_type # @param { Hash } with grant_types as keys. # Default set to {} i.e. no limitation on scopes usage def scopes_by_grant_type(hash = {}) @config.instance_variable_set(:@scopes_by_grant_type, hash) 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 # Choose to use the url path for native autorization codes # Enabling this flag sets the authorization code response route for # native redirect uris to oauth/authorize/. The default is # oauth/authorize/native?code=. # Rationale: https://github.com/doorkeeper-gem/doorkeeper/issues/1143 def use_url_path_for_native_authorization @config.instance_variable_set(:@use_url_path_for_native_authorization, true) end # TODO: maybe make it more generic for other flows too? # Only allow one valid access token obtained via client credentials # per client. If a new access token is obtained before the old one # expired, the old one gets revoked (disabled by default) def revoke_previous_client_credentials_token @config.instance_variable_set(:@revoke_previous_client_credentials_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 # Enables polymorphic Resource Owner association for Access Grant and # Access Token models. Requires additional database columns to be setup. def use_polymorphic_resource_owner @config.instance_variable_set(:@polymorphic_resource_owner, 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 # Allow optional hashing of input tokens before persisting them. # Will be used for hashing of input token and grants. # # @param using # Provide a different secret storage implementation class for tokens # @param fallback # Provide a fallback secret storage implementation class for tokens # or use :plain to fallback to plain tokens def hash_token_secrets(using: nil, fallback: nil) default = "::Doorkeeper::SecretStoring::Sha256Hash" configure_secrets_for :token, using: using || default, fallback: fallback end # Allow optional hashing of application secrets before persisting them. # Will be used for hashing of input token and grants. # # @param using # Provide a different secret storage implementation for applications # @param fallback # Provide a fallback secret storage implementation for applications # or use :plain to fallback to plain application secrets def hash_application_secrets(using: nil, fallback: nil) default = "::Doorkeeper::SecretStoring::Sha256Hash" configure_secrets_for :application, using: using || default, fallback: fallback end private # Configure the secret storing functionality def configure_secrets_for(type, using:, fallback:) raise ArgumentError, "Invalid type #{type}" if %i[application token].exclude?(type) @config.instance_variable_set(:"@#{type}_secret_strategy", using.constantize) if fallback.nil? return elsif fallback.to_sym == :plain fallback = "::Doorkeeper::SecretStoring::Plain" end @config.instance_variable_set(:"@#{type}_secret_fallback_strategy", fallback.constantize) end end # Replace with `default: Builder` when we drop support of Rails < 5.2 mattr_reader(:builder_class) { Builder } extend Option include Validations 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) # Hooks for authorization option :before_successful_authorization, default: ->(_controller, _context = nil) {} option :after_successful_authorization, default: ->(_controller, _context = nil) {} # Hooks for strategies responses option :before_successful_strategy_response, default: ->(_request) {} option :after_successful_strategy_response, default: ->(_request, _response) {} # Allows to customize Token Introspection response option :custom_introspection_response, default: ->(_token, _context) { {} } 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", deprecated: true option :grant_flows, default: %w[authorization_code client_credentials] option :handle_auth_errors, default: :render option :token_lookup_batch_size, default: 10_000 # Sets the token_reuse_limit # It will be used only when reuse_access_token option in enabled # By default it will be 100 # It will be used for token reusablity to some threshold percentage # Rationale: https://github.com/doorkeeper-gem/doorkeeper/issues/1189 option :token_reuse_limit, default: 100 # Don't require client authentication for password grants. If client credentials # are present they will still be validated, and the grant rejected if the credentials # are invalid. # # This is discouraged. Spec says that password grants always require a client. # # See https://github.com/doorkeeper-gem/doorkeeper/issues/1412#issuecomment-632750422 # and https://github.com/doorkeeper-gem/doorkeeper/pull/1420 # # Since many applications use this unsafe behavior in the wild, this is kept as a # not-recommended option. You should be aware that you are not following the OAuth # spec, and understand the security implications of doing so. option :skip_client_authentication_for_password_grant, default: false # Hook to allow arbitrary user-client authorization option :authorize_resource_owner_for_client, default: ->(_client, _resource_owner) { true } # Allows to customize OAuth grant flows that +each+ application support. # You can configure a custom block (or use a class respond to `#call`) that must # return `true` in case Application instance supports requested OAuth grant flow # during the authorization request to the server. This configuration +doesn't+ # set flows per application, it only allows to check if application supports # specific grant flow. # # For example you can add an additional database column to `oauth_applications` table, # say `t.array :grant_flows, default: []`, and store allowed grant flows that can # be used with this application there. Then when authorization requested Doorkeeper # will call this block to check if specific Application (passed with client_id and/or # client_secret) is allowed to perform the request for the specific grant type # (authorization, password, client_credentials, etc). # # Example of the block: # # ->(flow, client) { client.grant_flows.include?(flow) } # # In case this option invocation result is `false`, Doorkeeper server returns # :unauthorized_client error and stops the request. # # @param allow_grant_flow_for_client [Proc] Block or any object respond to #call # @return [Boolean] `true` if allow or `false` if forbid the request # option :allow_grant_flow_for_client, default: ->(_grant_flow, _client) { true } # 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://doorkeeper.gitbook.io/guides/configuration/other-configurations#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" # Allows additional data to be received when granting access to an Application, and for this # additional data to be sent with subsequently generated access tokens. The access grant and # access token models will both need to respond to the specified attribute names. # # @param attributes [Array] The array of custom attribute names to be saved # option :custom_access_token_attributes, default: [] # Use a custom class for generating the application secret. # https://doorkeeper.gitbook.io/guides/configuration/other-configurations#custom-application-secret-generator # # @param application_secret_generator [String] # the name of the application secret generator class # option :application_secret_generator, default: "Doorkeeper::OAuth::Helpers::UniqueToken" # Default access token generator is a SecureRandom class from Ruby stdlib. # This option defines which method will be used to generate a unique token value. # # @param default_generator_method [Symbol] # the method name of the default access token generator # option :default_generator_method, default: :urlsafe_base64 # The controller Doorkeeper::ApplicationController inherits from. # Defaults to ActionController::Base. # https://doorkeeper.gitbook.io/guides/configuration/other-configurations#custom-controllers # # @param base_controller [String] the name of the base controller option :base_controller, default: (lambda do api_only ? "ActionController::API" : "ActionController::Base" end) # The controller Doorkeeper::ApplicationMetalController inherits from. # Defaults to ActionController::API. # # @param base_metal_controller [String] the name of the base controller option :base_metal_controller, default: "ActionController::API" option :access_token_class, default: "Doorkeeper::AccessToken" option :access_grant_class, default: "Doorkeeper::AccessGrant" option :application_class, default: "Doorkeeper::Application" # Allows to set blank redirect URIs for Applications in case # server configured to use URI-less grant flows. # option :allow_blank_redirect_uri, default: (lambda do |grant_flows, _application| grant_flows.exclude?("authorization_code") && grant_flows.exclude?("implicit") end) # Configure protection of token introspection request. # By default this configuration allows to introspect a token by # another token of the same application, or to introspect the token # that belongs to authorized client, or access token has been introspected # is a public one (doesn't belong to any client) # # You can define any custom rule you need or just disable token # introspection at all. # # @param token [Doorkeeper::AccessToken] # token to be introspected # # @param authorized_client [Doorkeeper::Application] # authorized client (if request is authorized using Basic auth with # Client Credentials for example) # # @param authorized_token [Doorkeeper::AccessToken] # Bearer token used to authorize the request # option :allow_token_introspection, default: (lambda do |token, authorized_client, authorized_token| if authorized_token authorized_token.application == token&.application elsif token.application authorized_client == token.application else true end end) attr_reader :reuse_access_token, :token_secret_fallback_strategy, :application_secret_fallback_strategy def clear_cache! %i[ application_model access_token_model access_grant_model ].each do |var| remove_instance_variable("@#{var}") if instance_variable_defined?("@#{var}") end end # Doorkeeper Access Token model class. # # @return [ActiveRecord::Base, Mongoid::Document, Sequel::Model] # def access_token_model @access_token_model ||= access_token_class.constantize end # Doorkeeper Access Grant model class. # # @return [ActiveRecord::Base, Mongoid::Document, Sequel::Model] # def access_grant_model @access_grant_model ||= access_grant_class.constantize end # Doorkeeper Application model class. # # @return [ActiveRecord::Base, Mongoid::Document, Sequel::Model] # def application_model @application_model ||= application_class.constantize end 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 resolve_controller(name) config_option = public_send(:"#{name}_controller") controller_name = if config_option.respond_to?(:call) instance_exec(&config_option) else config_option end controller_name.constantize end def revoke_previous_client_credentials_token? option_set? :revoke_previous_client_credentials_token end def enforce_configured_scopes? option_set? :enforce_configured_scopes end def enable_application_owner? option_set? :enable_application_owner end def polymorphic_resource_owner? option_set? :polymorphic_resource_owner end def confirm_application_owner? option_set? :confirm_application_owner end def raise_on_errors? handle_auth_errors == :raise end def application_secret_hashed? instance_variable_defined?(:"@application_secret_strategy") end def token_secret_strategy @token_secret_strategy ||= ::Doorkeeper::SecretStoring::Plain end def application_secret_strategy @application_secret_strategy ||= ::Doorkeeper::SecretStoring::Plain 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 scopes_by_grant_type @scopes_by_grant_type ||= {} 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 enabled_grant_flows @enabled_grant_flows ||= calculate_grant_flows.map { |name| Doorkeeper::GrantFlow.get(name) }.compact end def authorization_response_flows @authorization_response_flows ||= enabled_grant_flows.select(&:handles_response_type?) + deprecated_authorization_flows end def token_grant_flows @token_grant_flows ||= calculate_token_grant_flows end def authorization_response_types authorization_response_flows.map(&:response_type_matches) end def token_grant_types token_grant_flows.map(&:grant_type_matches) end # [NOTE]: deprecated and will be removed soon def deprecated_token_grant_types_resolver @deprecated_token_grant_types ||= calculate_token_grant_types end def native_authorization_code_route @use_url_path_for_native_authorization = false unless defined?(@use_url_path_for_native_authorization) @use_url_path_for_native_authorization ? '/:code' : '/native' end # [NOTE]: deprecated and will be removed soon def deprecated_authorization_flows response_types = calculate_authorization_response_types if response_types.any? ::Kernel.warn <<~WARNING Please, don't patch Doorkeeper::Config#calculate_authorization_response_types method. Register your custom grant flows using the public API: `Doorkeeper::GrantFlow.register(grant_flow_name, **options)`. WARNING end response_types.map do |response_type| Doorkeeper::GrantFlow::FallbackFlow.new(response_type, response_type_matches: response_type) end end # [NOTE]: deprecated and will be removed soon def calculate_authorization_response_types [] end # [NOTE]: deprecated and will be removed soon def calculate_token_grant_types types = grant_flows - ["implicit"] types << "refresh_token" if refresh_token_enabled? types end # Calculates grant flows configured by the user in Doorkeeper # configuration considering registered aliases that is exposed # to single or multiple other flows. # def calculate_grant_flows configured_flows = grant_flows.map(&:to_s) aliases = Doorkeeper::GrantFlow.aliases.keys.map(&:to_s) flows = configured_flows - aliases aliases.each do |flow_alias| next unless configured_flows.include?(flow_alias) flows.concat(Doorkeeper::GrantFlow.expand_alias(flow_alias)) end flows.flatten.uniq end def allow_blank_redirect_uri?(application = nil) if allow_blank_redirect_uri.respond_to?(:call) allow_blank_redirect_uri.call(grant_flows, application) else allow_blank_redirect_uri end end def allow_grant_flow_for_client?(grant_flow, client) return true unless option_defined?(:allow_grant_flow_for_client) allow_grant_flow_for_client.call(grant_flow, client) end def option_defined?(name) instance_variable_defined?("@#{name}") end private # Helper to read boolearized configuration option def option_set?(instance_key) var = instance_variable_get("@#{instance_key}") !!(defined?(var) && var) end def calculate_token_grant_flows flows = enabled_grant_flows.select(&:handles_grant_type?) flows << Doorkeeper::GrantFlow.get("refresh_token") if refresh_token_enabled? flows end end end doorkeeper-5.6.6/lib/doorkeeper.rb0000644000004100000410000001513314422352653017162 0ustar www-datawww-data# frozen_string_literal: true require "doorkeeper/config" require "doorkeeper/engine" # Main Doorkeeper namespace. # module Doorkeeper autoload :Errors, "doorkeeper/errors" autoload :GrantFlow, "doorkeeper/grant_flow" autoload :OAuth, "doorkeeper/oauth" autoload :Rake, "doorkeeper/rake" autoload :Request, "doorkeeper/request" autoload :Server, "doorkeeper/server" autoload :StaleRecordsCleaner, "doorkeeper/stale_records_cleaner" autoload :Validations, "doorkeeper/validations" autoload :VERSION, "doorkeeper/version" autoload :AccessGrantMixin, "doorkeeper/models/access_grant_mixin" autoload :AccessTokenMixin, "doorkeeper/models/access_token_mixin" autoload :ApplicationMixin, "doorkeeper/models/application_mixin" module Helpers autoload :Controller, "doorkeeper/helpers/controller" end module Request autoload :Strategy, "doorkeeper/request/strategy" autoload :AuthorizationCode, "doorkeeper/request/authorization_code" autoload :ClientCredentials, "doorkeeper/request/client_credentials" autoload :Code, "doorkeeper/request/code" autoload :Password, "doorkeeper/request/password" autoload :RefreshToken, "doorkeeper/request/refresh_token" autoload :Token, "doorkeeper/request/token" end module OAuth autoload :BaseRequest, "doorkeeper/oauth/base_request" autoload :AuthorizationCodeRequest, "doorkeeper/oauth/authorization_code_request" autoload :BaseResponse, "doorkeeper/oauth/base_response" autoload :CodeResponse, "doorkeeper/oauth/code_response" autoload :Client, "doorkeeper/oauth/client" autoload :ClientCredentialsRequest, "doorkeeper/oauth/client_credentials_request" autoload :CodeRequest, "doorkeeper/oauth/code_request" autoload :ErrorResponse, "doorkeeper/oauth/error_response" autoload :Error, "doorkeeper/oauth/error" autoload :InvalidTokenResponse, "doorkeeper/oauth/invalid_token_response" autoload :InvalidRequestResponse, "doorkeeper/oauth/invalid_request_response" autoload :ForbiddenTokenResponse, "doorkeeper/oauth/forbidden_token_response" autoload :NonStandard, "doorkeeper/oauth/nonstandard" autoload :PasswordAccessTokenRequest, "doorkeeper/oauth/password_access_token_request" autoload :PreAuthorization, "doorkeeper/oauth/pre_authorization" autoload :RefreshTokenRequest, "doorkeeper/oauth/refresh_token_request" autoload :Scopes, "doorkeeper/oauth/scopes" autoload :Token, "doorkeeper/oauth/token" autoload :TokenIntrospection, "doorkeeper/oauth/token_introspection" autoload :TokenRequest, "doorkeeper/oauth/token_request" autoload :TokenResponse, "doorkeeper/oauth/token_response" module Authorization autoload :Code, "doorkeeper/oauth/authorization/code" autoload :Context, "doorkeeper/oauth/authorization/context" autoload :Token, "doorkeeper/oauth/authorization/token" autoload :URIBuilder, "doorkeeper/oauth/authorization/uri_builder" end class Client autoload :Credentials, "doorkeeper/oauth/client/credentials" end module ClientCredentials autoload :Validator, "doorkeeper/oauth/client_credentials/validator" autoload :Creator, "doorkeeper/oauth/client_credentials/creator" autoload :Issuer, "doorkeeper/oauth/client_credentials/issuer" end module Helpers autoload :ScopeChecker, "doorkeeper/oauth/helpers/scope_checker" autoload :URIChecker, "doorkeeper/oauth/helpers/uri_checker" autoload :UniqueToken, "doorkeeper/oauth/helpers/unique_token" end module Hooks autoload :Context, "doorkeeper/oauth/hooks/context" end end module Models autoload :Accessible, "doorkeeper/models/concerns/accessible" autoload :Expirable, "doorkeeper/models/concerns/expirable" autoload :ExpirationTimeSqlMath, "doorkeeper/models/concerns/expiration_time_sql_math" autoload :Orderable, "doorkeeper/models/concerns/orderable" autoload :PolymorphicResourceOwner, "doorkeeper/models/concerns/polymorphic_resource_owner" autoload :Scopes, "doorkeeper/models/concerns/scopes" autoload :Reusable, "doorkeeper/models/concerns/reusable" autoload :ResourceOwnerable, "doorkeeper/models/concerns/resource_ownerable" autoload :Revocable, "doorkeeper/models/concerns/revocable" autoload :SecretStorable, "doorkeeper/models/concerns/secret_storable" end module Orm autoload :ActiveRecord, "doorkeeper/orm/active_record" end module Rails autoload :Helpers, "doorkeeper/rails/helpers" autoload :Routes, "doorkeeper/rails/routes" end module SecretStoring autoload :Base, "doorkeeper/secret_storing/base" autoload :Plain, "doorkeeper/secret_storing/plain" autoload :Sha256Hash, "doorkeeper/secret_storing/sha256_hash" autoload :BCrypt, "doorkeeper/secret_storing/bcrypt" end class << self attr_reader :orm_adapter def configure(&block) @config = Config::Builder.new(&block).build setup @config end # @return [Doorkeeper::Config] configuration instance # def configuration @config || configure end def configured? !@config.nil? end alias config configuration def setup setup_orm_adapter # Deprecated, will be removed soon unless configuration.orm == :active_record setup_orm_models setup_application_owner end end def setup_orm_adapter @orm_adapter = "doorkeeper/orm/#{configuration.orm}".classify.constantize rescue NameError => e raise e, "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 run_orm_hooks config.clear_cache! if @orm_adapter.respond_to?(:run_hooks) @orm_adapter.run_hooks else ::Kernel.warn <<~MSG.strip_heredoc [DOORKEEPER] ORM "#{configuration.orm}" should move all it's setup logic under `#run_hooks` method for the #{@orm_adapter.name}. Later versions of Doorkeeper will no longer support `setup_orm_models` and `setup_application_owner` API. MSG end end def setup_orm_models @orm_adapter.initialize_models! end def setup_application_owner @orm_adapter.initialize_application_owner! end def authenticate(request, methods = Doorkeeper.config.access_token_methods) OAuth::Token.authenticate(request, *methods) end def gem_version ::Gem::Version.new(::Doorkeeper::VERSION::STRING) end end end doorkeeper-5.6.6/lib/generators/0000755000004100000410000000000014422352653016644 5ustar www-datawww-datadoorkeeper-5.6.6/lib/generators/doorkeeper/0000755000004100000410000000000014422352653021003 5ustar www-datawww-datadoorkeeper-5.6.6/lib/generators/doorkeeper/confidential_applications_generator.rb0000644000004100000410000000163614422352653030611 0ustar www-datawww-data# frozen_string_literal: true require "rails/generators" require "rails/generators/active_record" module Doorkeeper # Generates migration to add confidential column to Doorkeeper # applications table. # class ConfidentialApplicationsGenerator < ::Rails::Generators::Base include ::Rails::Generators::Migration source_root File.expand_path("templates", __dir__) desc "Add confidential column to Doorkeeper applications" def confidential_applications 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 "[#{ActiveRecord::VERSION::MAJOR}.#{ActiveRecord::VERSION::MINOR}]" end end end doorkeeper-5.6.6/lib/generators/doorkeeper/views_generator.rb0000644000004100000410000000076614422352653024544 0ustar www-datawww-data# frozen_string_literal: true module Doorkeeper module Generators # Generates doorkeeper views for Rails application # 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.6.6/lib/generators/doorkeeper/templates/0000755000004100000410000000000014422352653023001 5ustar www-datawww-datadoorkeeper-5.6.6/lib/generators/doorkeeper/templates/add_owner_to_application_migration.rb.erb0000644000004100000410000000051014422352653033171 0ustar www-datawww-data# frozen_string_literal: true class AddOwnerToApplication < ActiveRecord::Migration<%= migration_version %> def change add_column :oauth_applications, :owner_id, :bigint, null: true add_column :oauth_applications, :owner_type, :string, null: true add_index :oauth_applications, [:owner_id, :owner_type] end end doorkeeper-5.6.6/lib/generators/doorkeeper/templates/enable_pkce_migration.rb.erb0000644000004100000410000000042414422352653030376 0ustar www-datawww-data# frozen_string_literal: true class EnablePkce < ActiveRecord::Migration<%= migration_version %> def change add_column :oauth_access_grants, :code_challenge, :string, null: true add_column :oauth_access_grants, :code_challenge_method, :string, null: true end end doorkeeper-5.6.6/lib/generators/doorkeeper/templates/add_confidential_to_applications.rb.erb0000644000004100000410000000041114422352653032610 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.6.6/lib/generators/doorkeeper/templates/migration.rb.erb0000644000004100000410000000744514422352653026100 0ustar www-datawww-data# frozen_string_literal: true class CreateDoorkeeperTables < ActiveRecord::Migration<%= migration_version %> def change create_table :oauth_applications do |t| t.string :name, null: false t.string :uid, null: false t.string :secret, null: false # Remove `null: false` if you are planning to use grant flows # that doesn't require redirect URI to be used during authorization # like Client Credentials flow or Resource Owner Password. 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.string :scopes, null: false, default: '' t.datetime :created_at, null: false t.datetime :revoked_at 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 # Remove `null: false` if you are planning to use Password # Credentials Grant flow that doesn't require an application. t.references :application, null: false # 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.string :scopes t.datetime :created_at, null: false t.datetime :revoked_at # The authorization server MAY issue a new refresh token, in which case # *the client MUST discard the old refresh token* and replace it with the # new refresh token. The authorization server MAY revoke the old # refresh token after issuing a new refresh token to the client. # @see https://datatracker.ietf.org/doc/html/rfc6749#section-6 # # Doorkeeper implementation: 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 want refresh tokens to be instantly # revoked after use. t.string :previous_refresh_token, null: false, default: "" end add_index :oauth_access_tokens, :token, unique: true # See https://github.com/doorkeeper-gem/doorkeeper/issues/1592 if ActiveRecord::Base.connection.adapter_name == "SQLServer" execute <<~SQL.squish CREATE UNIQUE NONCLUSTERED INDEX index_oauth_access_tokens_on_refresh_token ON oauth_access_tokens(refresh_token) WHERE refresh_token IS NOT NULL SQL else add_index :oauth_access_tokens, :refresh_token, unique: true end add_foreign_key( :oauth_access_tokens, :oauth_applications, column: :application_id ) # Uncomment below to ensure a valid reference to the resource owner's table # add_foreign_key :oauth_access_grants, , column: :resource_owner_id # add_foreign_key :oauth_access_tokens, , column: :resource_owner_id end end doorkeeper-5.6.6/lib/generators/doorkeeper/templates/initializer.rb0000644000004100000410000005326214422352653025661 0ustar www-datawww-data# frozen_string_literal: true Doorkeeper.configure do # Change the ORM that doorkeeper will use (requires ORM extensions installed). # Check the list of supported ORMs here: https://github.com/doorkeeper-gem/doorkeeper#orms 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 # You can use your own model classes if you need to extend (or even override) default # Doorkeeper models such as `Application`, `AccessToken` and `AccessGrant. # # Be default Doorkeeper ActiveRecord ORM uses it's own classes: # # access_token_class "Doorkeeper::AccessToken" # access_grant_class "Doorkeeper::AccessGrant" # application_class "Doorkeeper::Application" # # Don't forget to include Doorkeeper ORM mixins into your custom models: # # * ::Doorkeeper::Orm::ActiveRecord::Mixins::AccessToken - for access token # * ::Doorkeeper::Orm::ActiveRecord::Mixins::AccessGrant - for access grant # * ::Doorkeeper::Orm::ActiveRecord::Mixins::Application - for application (OAuth2 clients) # # For example: # # access_token_class "MyAccessToken" # # class MyAccessToken < ApplicationRecord # include ::Doorkeeper::Orm::ActiveRecord::Mixins::AccessToken # # self.table_name = "hey_i_wanna_my_name" # # def destroy_me! # destroy # end # end # Enables polymorphic Resource Owner association for Access Tokens and Access Grants. # By default this option is disabled. # # Make sure you properly setup you database and have all the required columns (run # `bundle exec rails generate doorkeeper:enable_polymorphic_resource_owner` and execute Rails # migrations). # # If this option enabled, Doorkeeper will store not only Resource Owner primary key # value, but also it's type (class name). See "Polymorphic Associations" section of # Rails guides: https://guides.rubyonrails.org/association_basics.html#polymorphic-associations # # [NOTE] If you apply this option on already existing project don't forget to manually # update `resource_owner_type` column in the database and fix migration template as it will # set NOT NULL constraint for Access Grants table. # # use_polymorphic_resource_owner # 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. In case the block returns `nil` value Doorkeeper fallbacks to # +access_token_expires_in+ configuration option value. If you really need to issue a # non-expiring access token (which is not recommended) then you need to return # Float::INFINITY from this block. # # `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) # * `resource_owner` - authorized resource owner instance (if present) # # custom_access_token_expires_in do |context| # context.client.additional_settings.implicit_oauth_expiration # end # Use a custom class for generating the access token. # See https://doorkeeper.gitbook.io/guides/configuration/other-configurations#custom-access-token-generator # # access_token_generator '::Doorkeeper::JWT' # The controller +Doorkeeper::ApplicationController+ inherits from. # Defaults to +ActionController::Base+ unless +api_only+ is set, which changes the default to # +ActionController::API+. The return value of this option must be a stringified class name. # See https://doorkeeper.gitbook.io/guides/configuration/other-configurations#custom-controllers # # 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 enabled Doorkeeper # doesn't update existing token expiration time, it will create a new token instead if no active matching # token found for the application, resources owner and/or set of scopes. # Rationale: https://github.com/doorkeeper-gem/doorkeeper/issues/383 # # You can not enable this option together with +hash_token_secrets+. # # reuse_access_token # In case you enabled `reuse_access_token` option Doorkeeper will try to find matching # token using `matching_token_for` Access Token API that searches for valid records # in batches in order not to pollute the memory with all the database records. By default # Doorkeeper uses batch size of 10 000 records. You can increase or decrease this value # depending on your needs and server capabilities. # # token_lookup_batch_size 10_000 # Set a limit for token_reuse if using reuse_access_token option # # This option limits token_reusability to some extent. # If not set then access_token will be reused unless it expires. # Rationale: https://github.com/doorkeeper-gem/doorkeeper/issues/1189 # # This option should be a percentage(i.e. (0,100]) # # token_reuse_limit 100 # Only allow one valid access token obtained via client credentials # per client. If a new access token is obtained before the old one # expired, the old one gets revoked (disabled by default) # # When enabling this option, make sure that you do not expect multiple processes # using the same credentials at the same time (e.g. web servers spanning # multiple machines and/or processes). # # revoke_previous_client_credentials_token # Hash access and refresh tokens before persisting them. # This will disable the possibility to use +reuse_access_token+ # since plain values can no longer be retrieved. # # Note: If you are already a user of doorkeeper and have existing tokens # in your installation, they will be invalid without adding 'fallback: :plain'. # # hash_token_secrets # By default, token secrets will be hashed using the # +Doorkeeper::Hashing::SHA256+ strategy. # # If you wish to use another hashing implementation, you can override # this strategy as follows: # # hash_token_secrets using: '::Doorkeeper::Hashing::MyCustomHashImpl' # # Keep in mind that changing the hashing function will invalidate all existing # secrets, if there are any. # Hash application secrets before persisting them. # # hash_application_secrets # # By default, applications will be hashed # with the +Doorkeeper::SecretStoring::SHA256+ strategy. # # If you wish to use bcrypt for application secret hashing, uncomment # this line instead: # # hash_application_secrets using: '::Doorkeeper::SecretStoring::BCrypt' # When the above option is enabled, and a hashed token or secret is not found, # you can allow to fall back to another strategy. For users upgrading # doorkeeper and wishing to enable hashing, you will probably want to enable # the fallback to plain tokens. # # This will ensure that old access tokens and secrets # will remain valid even if the hashing above is enabled. # # This can be done by adding 'fallback: plain', e.g. : # # hash_application_secrets using: '::Doorkeeper::SecretStoring::BCrypt', fallback: :plain # 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 following 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 # 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://doorkeeper.gitbook.io/guides/ruby-on-rails/scopes # # default_scopes :public # optional_scopes :write, :update # Allows to restrict only certain scopes for grant_type. # By default, all the scopes will be available for all the grant types. # # Keys to this hash should be the name of grant_type and # values should be the array of scopes for that grant type. # Note: scopes should be from configured_scopes (i.e. default or optional) # # scopes_by_grant_type password: [:write], client_credentials: [:update] # 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 # 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 # 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 allowed 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' } # Allows to set blank redirect URIs for Applications in case Doorkeeper configured # to use URI-less OAuth grant flows like Client Credentials or Resource Owner # Password Credentials. The option is on by default and checks configured grant # types, but you **need** to manually drop `NOT NULL` constraint from `redirect_uri` # column for `oauth_applications` database table. # # You can completely disable this feature with: # # allow_blank_redirect_uri false # # Or you can define your custom check: # # allow_blank_redirect_uri do |grant_flows, client| # client.superapp? # end # 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 # Customize token introspection response. # Allows to add your own fields to default one that are required by the OAuth spec # for the introspection response. It could be `sub`, `aud` and so on. # This configuration option can be a proc, lambda or any Ruby object responds # to `.call` method and result of it's invocation must be a Hash. # # custom_introspection_response do |token, context| # { # "sub": "Z5O3upPC88QrAjx00dis", # "aud": "https://protected.example.net/resource", # "username": User.find(token.resource_owner_id).username # } # end # # or # # custom_introspection_response CustomIntrospectionResponder # 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: # https://datatracker.ietf.org/doc/html/rfc6819#section-4.4.2 # https://datatracker.ietf.org/doc/html/rfc6819#section-4.4.3 # # grant_flows %w[authorization_code client_credentials] # Allows to customize OAuth grant flows that +each+ application support. # You can configure a custom block (or use a class respond to `#call`) that must # return `true` in case Application instance supports requested OAuth grant flow # during the authorization request to the server. This configuration +doesn't+ # set flows per application, it only allows to check if application supports # specific grant flow. # # For example you can add an additional database column to `oauth_applications` table, # say `t.array :grant_flows, default: []`, and store allowed grant flows that can # be used with this application there. Then when authorization requested Doorkeeper # will call this block to check if specific Application (passed with client_id and/or # client_secret) is allowed to perform the request for the specific grant type # (authorization, password, client_credentials, etc). # # Example of the block: # # ->(flow, client) { client.grant_flows.include?(flow) } # # In case this option invocation result is `false`, Doorkeeper server returns # :unauthorized_client error and stops the request. # # @param allow_grant_flow_for_client [Proc] Block or any object respond to #call # @return [Boolean] `true` if allow or `false` if forbid the request # # allow_grant_flow_for_client do |grant_flow, client| # # `grant_flows` is an Array column with grant # # flows that application supports # # client.grant_flows.include?(grant_flow) # end # If you need arbitrary Resource Owner-Client authorization you can enable this option # and implement the check your need. Config option must respond to #call and return # true in case resource owner authorized for the specific application or false in other # cases. # # Be default all Resource Owners are authorized to any Client (application). # # authorize_resource_owner_for_client do |client, resource_owner| # resource_owner.admin? || client.owners_allowlist.include?(resource_owner) # end # Allows additional data fields to be sent while granting access to an application, # and for this additional data to be included in subsequently generated access tokens. # The 'authorizations/new' page will need to be overridden to include this additional data # in the request params when granting access. The access grant and access token models # will both need to respond to these additional data fields, and have a database column # to store them in. # # Example: # You have a multi-tenanted platform and want to be able to grant access to a specific # tenant, rather than all the tenants a user has access to. You can use this config # option to specify that a ':tenant_id' will be passed when authorizing. This tenant_id # will be included in the access tokens. When a request is made with one of these access # tokens, you can check that the requested data belongs to the specified tenant. # # Default value is an empty Array: [] # custom_access_token_attributes [:tenant_id] # 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 any other functionality. Inside the block you have an access # to `controller` (authorizations controller instance) and `context` # (Doorkeeper::OAuth::Hooks::Context instance) which provides pre auth # or auth objects with issued token based on hook type (before or after). # # before_successful_authorization do |controller, context| # Rails.logger.info(controller.request.params.inspect) # # Rails.logger.info(context.pre_auth.inspect) # end # # after_successful_authorization do |controller, context| # controller.session[:logout_urls] << # Doorkeeper::Application # .find_by(controller.request.params.slice(:redirect_uri)) # .logout_uri # # Rails.logger.info(context.auth.inspect) # Rails.logger.info(context.issued_token) # 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 # Configure custom constraints for the Token Introspection request. # By default this configuration option allows to introspect a token by another # token of the same application, OR to introspect the token that belongs to # authorized client (from authenticated client) OR when token doesn't # belong to any client (public token). Otherwise requester has no access to the # introspection and it will return response as stated in the RFC. # # Block arguments: # # @param token [Doorkeeper::AccessToken] # token to be introspected # # @param authorized_client [Doorkeeper::Application] # authorized client (if request is authorized using Basic auth with # Client Credentials for example) # # @param authorized_token [Doorkeeper::AccessToken] # Bearer token used to authorize the request # # In case the block returns `nil` or `false` introspection responses with 401 status code # when using authorized token to introspect, or you'll get 200 with { "active": false } body # when using authorized client to introspect as stated in the # RFC 7662 section 2.2. Introspection Response. # # Using with caution: # Keep in mind that these three parameters pass to block can be nil as following case: # `authorized_client` is nil if and only if `authorized_token` is present, and vice versa. # `token` will be nil if and only if `authorized_token` is present. # So remember to use `&` or check if it is present before calling method on # them to make sure you doesn't get NoMethodError exception. # # You can define your custom check: # # allow_token_introspection do |token, authorized_client, authorized_token| # if authorized_token # # customize: require `introspection` scope # authorized_token.application == token&.application || # authorized_token.scopes.include?("introspection") # elsif token.application # # `protected_resource` is a new database boolean column, for example # authorized_client == token.application || authorized_client.protected_resource? # else # # public token (when token.application is nil, token doesn't belong to any application) # true # end # end # # Or you can completely disable any token introspection: # # allow_token_introspection false # # If you need to block the request at all, then configure your routes.rb or web-server # like nginx to forbid the request. # WWW-Authenticate Realm (default: "Doorkeeper"). # # realm "Doorkeeper" end doorkeeper-5.6.6/lib/generators/doorkeeper/templates/README0000644000004100000410000000076314422352653023667 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.6.6/lib/generators/doorkeeper/templates/add_previous_refresh_token_to_access_tokens.rb.erbdoorkeeper-5.6.6/lib/generators/doorkeeper/templates/add_previous_refresh_token_to_access_tokens.rb.0000644000004100000410000000043114422352653034412 0ustar www-datawww-data# frozen_string_literal: true class AddPreviousRefreshTokenToAccessTokens < ActiveRecord::Migration<%= migration_version %> def change add_column( :oauth_access_tokens, :previous_refresh_token, :string, default: "", null: false ) end end ././@LongLink0000644000000000000000000000015000000000000011577 Lustar rootrootdoorkeeper-5.6.6/lib/generators/doorkeeper/templates/enable_polymorphic_resource_owner_migration.rb.erbdoorkeeper-5.6.6/lib/generators/doorkeeper/templates/enable_polymorphic_resource_owner_migration.rb.0000644000004100000410000000122214422352653034446 0ustar www-datawww-data# frozen_string_literal: true class EnablePolymorphicResourceOwner < ActiveRecord::Migration<%= migration_version %> def change add_column :oauth_access_tokens, :resource_owner_type, :string add_column :oauth_access_grants, :resource_owner_type, :string change_column_null :oauth_access_grants, :resource_owner_type, false add_index :oauth_access_tokens, [:resource_owner_id, :resource_owner_type], name: 'polymorphic_owner_oauth_access_tokens' add_index :oauth_access_grants, [:resource_owner_id, :resource_owner_type], name: 'polymorphic_owner_oauth_access_grants' end end doorkeeper-5.6.6/lib/generators/doorkeeper/application_owner_generator.rb0000644000004100000410000000160314422352653027113 0ustar www-datawww-data# frozen_string_literal: true require "rails/generators" require "rails/generators/active_record" module Doorkeeper # Generates migration to add reference to owner of the # Doorkeeper application. # 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 "[#{ActiveRecord::VERSION::MAJOR}.#{ActiveRecord::VERSION::MINOR}]" end end end doorkeeper-5.6.6/lib/generators/doorkeeper/pkce_generator.rb0000644000004100000410000000147214422352653024324 0ustar www-datawww-data# frozen_string_literal: true require "rails/generators" require "rails/generators/active_record" module Doorkeeper # Generates migration with PKCE required database columns for # Doorkeeper tables. # 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 "[#{ActiveRecord::VERSION::MAJOR}.#{ActiveRecord::VERSION::MINOR}]" end end end doorkeeper-5.6.6/lib/generators/doorkeeper/enable_polymorphic_resource_owner_generator.rb0000644000004100000410000000220714422352653032373 0ustar www-datawww-data# frozen_string_literal: true require "rails/generators" require "rails/generators/active_record" module Doorkeeper # Generates migration with polymorphic resource owner required # database columns for Doorkeeper Access Token and Access Grant # models. # class EnablePolymorphicResourceOwnerGenerator < ::Rails::Generators::Base include ::Rails::Generators::Migration source_root File.expand_path("templates", __dir__) desc "Provide support for polymorphic Resource Owner." def enable_polymorphic_resource_owner migration_template( "enable_polymorphic_resource_owner_migration.rb.erb", "db/migrate/enable_polymorphic_resource_owner.rb", migration_version: migration_version, ) gsub_file( "config/initializers/doorkeeper.rb", "# use_polymorphic_resource_owner", "use_polymorphic_resource_owner", ) end def self.next_migration_number(dirname) ActiveRecord::Generators::Base.next_migration_number(dirname) end private def migration_version "[#{ActiveRecord::VERSION::MAJOR}.#{ActiveRecord::VERSION::MINOR}]" end end end doorkeeper-5.6.6/lib/generators/doorkeeper/migration_generator.rb0000644000004100000410000000147214422352653025373 0ustar www-datawww-data# frozen_string_literal: true require "rails/generators" require "rails/generators/active_record" module Doorkeeper # Copies main Doorkeeper migration into parent Rails application. # 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 "[#{ActiveRecord::VERSION::MAJOR}.#{ActiveRecord::VERSION::MINOR}]" end end end doorkeeper-5.6.6/lib/generators/doorkeeper/install_generator.rb0000644000004100000410000000121714422352653025045 0ustar www-datawww-data# frozen_string_literal: true require "rails/generators" require "rails/generators/active_record" module Doorkeeper # Setup doorkeeper into Rails application: locales, routes, etc. # 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.6.6/lib/generators/doorkeeper/previous_refresh_token_generator.rb0000644000004100000410000000215414422352653030172 0ustar www-datawww-data# frozen_string_literal: true require "rails/generators" require "rails/generators/active_record" module Doorkeeper # Generates migration to add previous refresh token column to the # database for Doorkeeper tables. # 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 return unless 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 private def migration_version "[#{ActiveRecord::VERSION::MAJOR}.#{ActiveRecord::VERSION::MINOR}]" end def no_previous_refresh_token_column? !ActiveRecord::Base.connection.column_exists?( :oauth_access_tokens, :previous_refresh_token, ) end end end doorkeeper-5.6.6/vendor/0000755000004100000410000000000014422352653015222 5ustar www-datawww-datadoorkeeper-5.6.6/vendor/assets/0000755000004100000410000000000014422352653016524 5ustar www-datawww-datadoorkeeper-5.6.6/vendor/assets/stylesheets/0000755000004100000410000000000014422352653021100 5ustar www-datawww-datadoorkeeper-5.6.6/vendor/assets/stylesheets/doorkeeper/0000755000004100000410000000000014422352653023237 5ustar www-datawww-datadoorkeeper-5.6.6/vendor/assets/stylesheets/doorkeeper/bootstrap.min.css0000644000004100000410000043270014422352653026556 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.6.6/config/0000755000004100000410000000000014422352653015172 5ustar www-datawww-datadoorkeeper-5.6.6/config/locales/0000755000004100000410000000000014422352653016614 5ustar www-datawww-datadoorkeeper-5.6.6/config/locales/en.yml0000644000004100000410000001411514422352653017743 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.' unspecified_scheme: 'must specify a scheme.' 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' blank_redirect_uri: "Leave it blank if you configured your provider to use Client Credentials, Resource Owner Password Credentials or any other grant type that doesn't require redirect URI." 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: 'UID' secret: 'Secret' secret_hashed: 'Secret hashed' scopes: 'Scopes' confidential: 'Confidential' callback_urls: 'Callback urls' actions: 'Actions' not_defined: 'Not defined' 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' form_post: title: 'Submit this form' 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: unknown: 'The request is missing a required parameter, includes an unsupported parameter value, or is otherwise malformed.' missing_param: 'Missing required parameter: %{value}.' request_not_authorized: 'Request need to be authorized. Required parameter for authorizing request is missing or invalid.' 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.' unsupported_response_mode: 'The authorization server does not support this response mode.' # 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" revoke: unauthorized: "You are not authorized to revoke this token" forbidden_token: missing_scope: 'Access to this resource requires scope "%{oauth_scopes}".' 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.6.6/MIT-LICENSE0000644000004100000410000000205714422352653015365 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.