Rails/InverseOf:
Enabled: false
+Rails/ReflectionClassName:
+ Enabled: false
+
Rails/SkipsModelValidations:
Exclude:
- 'db/migrate/*.rb'
Exclude:
- 'app/helpers/title_helper.rb'
+Rails/LexicallyScopedActionFilter:
+ Exclude:
+ - 'app/controllers/oauth2_applications_controller.rb'
+
# Offense count: 5
# Configuration parameters: Include.
# Include: db/migrate/*.rb
gem "omniauth-rails_csrf_protection", "~> 1.0"
gem "omniauth-windowslive"
+# Doorkeeper for OAuth2
+gem "doorkeeper"
+gem "doorkeeper-i18n"
+
# Markdown formatting support
gem "kramdown"
activerecord (>= 3.0, < 6.2)
delayed_job (>= 3.0, < 5)
docile (1.4.0)
+ doorkeeper (5.5.1)
+ railties (>= 5)
+ doorkeeper-i18n (5.2.2)
+ doorkeeper (>= 5.2)
dry-configurable (0.12.1)
concurrent-ruby (~> 1.0)
dry-core (~> 0.5, >= 0.5.0)
dalli
debug_inspector
delayed_job_active_record
+ doorkeeper
+ doorkeeper-i18n
erb_lint
factory_bot_rails
faraday
if Settings.status != "database_offline"
can [:index, :new, :create, :show, :edit, :update, :destroy], ClientApplication
+ can [:index, :new, :create, :show, :edit, :update, :destroy], :oauth2_application
+ can [:index, :destroy], :oauth2_authorized_application
+ can [:new, :show, :create, :destroy], :oauth2_authorization
can [:new, :create, :edit, :update, :comment, :subscribe, :unsubscribe], DiaryEntry
can [:make_friend, :remove_friend], Friendship
can [:new, :create, :reply, :show, :inbox, :outbox, :mark, :destroy], Message
def initialize(token)
if Settings.status != "database_offline"
- can [:create, :comment, :close, :reopen], Note if capability?(token, :allow_write_notes)
- can [:show, :data], Trace if capability?(token, :allow_read_gpx)
- can [:create, :update, :destroy], Trace if capability?(token, :allow_write_gpx)
- can [:details], User if capability?(token, :allow_read_prefs)
- can [:gpx_files], User if capability?(token, :allow_read_gpx)
- can [:index, :show], UserPreference if capability?(token, :allow_read_prefs)
- can [:update, :update_all, :destroy], UserPreference if capability?(token, :allow_write_prefs)
+ user = if token.respond_to?(:resource_owner_id)
+ User.find(token.resource_owner_id)
+ elsif token.respond_to?(:user)
+ token.user
+ end
- if token&.user&.terms_agreed?
- can [:create, :update, :upload, :close, :subscribe, :unsubscribe], Changeset if capability?(token, :allow_write_api)
- can :create, ChangesetComment if capability?(token, :allow_write_api)
- can [:create, :update, :delete], Node if capability?(token, :allow_write_api)
- can [:create, :update, :delete], Way if capability?(token, :allow_write_api)
- can [:create, :update, :delete], Relation if capability?(token, :allow_write_api)
+ can [:create, :comment, :close, :reopen], Note if scope?(token, :write_notes)
+ can [:show, :data], Trace if scope?(token, :read_gpx)
+ can [:create, :update, :destroy], Trace if scope?(token, :write_gpx)
+ can [:details], User if scope?(token, :read_prefs)
+ can [:gpx_files], User if scope?(token, :read_gpx)
+ can [:index, :show], UserPreference if scope?(token, :read_prefs)
+ can [:update, :update_all, :destroy], UserPreference if scope?(token, :write_prefs)
+
+ if user&.terms_agreed?
+ can [:create, :update, :upload, :close, :subscribe, :unsubscribe], Changeset if scope?(token, :write_api)
+ can :create, ChangesetComment if scope?(token, :write_api)
+ can [:create, :update, :delete], Node if scope?(token, :write_api)
+ can [:create, :update, :delete], Way if scope?(token, :write_api)
+ can [:create, :update, :delete], Relation if scope?(token, :write_api)
end
- if token&.user&.moderator?
- can [:destroy, :restore], ChangesetComment if capability?(token, :allow_write_api)
- can :destroy, Note if capability?(token, :allow_write_notes)
- if token&.user&.terms_agreed?
- can :redact, OldNode if capability?(token, :allow_write_api)
- can :redact, OldWay if capability?(token, :allow_write_api)
- can :redact, OldRelation if capability?(token, :allow_write_api)
+ if user&.moderator?
+ can [:destroy, :restore], ChangesetComment if scope?(token, :write_api)
+ can :destroy, Note if scope?(token, :write_notes)
+ if user&.terms_agreed?
+ can :redact, OldNode if scope?(token, :write_api)
+ can :redact, OldWay if scope?(token, :write_api)
+ can :redact, OldRelation if scope?(token, :write_api)
end
end
end
private
- def capability?(token, cap)
- token&.read_attribute(cap)
+ def scope?(token, scope)
+ token&.includes_scope?(scope)
end
end
def current_ability
# Use capabilities from the oauth token if it exists and is a valid access token
- if Authenticator.new(self, [:token]).allow?
+ if doorkeeper_token&.accessible?
+ ApiAbility.new(nil).merge(ApiCapability.new(doorkeeper_token))
+ elsif Authenticator.new(self, [:token]).allow?
ApiAbility.new(nil).merge(ApiCapability.new(current_token))
else
ApiAbility.new(current_user)
end
def deny_access(_exception)
- if current_token
+ if doorkeeper_token || current_token
set_locale
report_error t("oauth.permissions.missing"), :forbidden
elsif current_user
# is optional.
def setup_user_auth
# try and setup using OAuth
- unless Authenticator.new(self, [:token]).allow?
+ if doorkeeper_token&.accessible?
+ self.current_user = User.find(doorkeeper_token.resource_owner_id)
+ elsif Authenticator.new(self, [:token]).allow?
+ # self.current_user setup by OAuth
+ else
username, passwd = get_auth_data # parse from headers
# authenticate per-scheme
self.current_user = if username.nil?
end
def deny_access(_exception)
- if current_token
+ if doorkeeper_token || current_token
set_locale
report_error t("oauth.permissions.missing"), :forbidden
elsif current_user
--- /dev/null
+class Oauth2ApplicationsController < Doorkeeper::ApplicationsController
+ layout "site"
+
+ prepend_before_action :authorize_web
+ before_action :set_locale
+ before_action :set_application, :only => [:show, :edit, :update, :destroy]
+
+ authorize_resource :class => false
+
+ def index
+ @applications = current_resource_owner.oauth2_applications.ordered_by(:created_at)
+ end
+
+ private
+
+ def set_application
+ @application = current_resource_owner&.oauth2_applications&.find(params[:id])
+ rescue ActiveRecord::RecordNotFound
+ render :action => "not_found", :status => :not_found
+ end
+
+ def application_params
+ params[:doorkeeper_application][:scopes]&.delete("")
+ params.require(:doorkeeper_application)
+ .permit(:name, :redirect_uri, :confidential, :scopes => [])
+ .merge(:owner => current_resource_owner)
+ end
+end
--- /dev/null
+class Oauth2AuthorizationsController < Doorkeeper::AuthorizationsController
+ layout "site"
+
+ prepend_before_action :authorize_web
+ before_action :set_locale
+
+ authorize_resource :class => false
+
+ def new
+ override_content_security_policy_directives(:form_action => []) if Settings.csp_enforce || Settings.key?(:csp_report_url)
+
+ super
+ end
+end
--- /dev/null
+class Oauth2AuthorizedApplicationsController < Doorkeeper::AuthorizedApplicationsController
+ layout "site"
+
+ prepend_before_action :authorize_web
+ before_action :set_locale
+
+ authorize_resource :class => false
+end
before_create :set_authorized_at
+ def includes_scope?(scope)
+ self[:"allow_#{scope}"]
+ end
+
protected
def set_authorized_at
end
def self.all_permissions
- PERMISSIONS
+ Oauth.scopes.collect { |s| :"allow_#{s.name}" }
end
def oauth_server
protected
- # this is the set of permissions that the client can ask for. clients
- # have to say up-front what permissions they want and when users sign up they
- # can agree or not agree to each of them.
- PERMISSIONS = [:allow_read_prefs, :allow_write_prefs, :allow_write_diary, :allow_write_api, :allow_read_gpx, :allow_write_gpx, :allow_write_notes].freeze
-
def generate_keys
self.key = OAuth::Helper.generate_key(40)[0, 40]
self.secret = OAuth::Helper.generate_key(40)[0, 40]
has_many :client_applications
has_many :oauth_tokens, -> { order(:authorized_at => :desc).preload(:client_application) }, :class_name => "OauthToken"
+ has_many :oauth2_applications, :class_name => Doorkeeper.config.application_model.name, :foreign_key => :owner_id
+ has_many :access_grants, :class_name => Doorkeeper.config.access_grant_model.name, :foreign_key => :resource_owner_id
+ has_many :access_tokens, :class_name => Doorkeeper.config.access_token_model.name, :foreign_key => :resource_owner_id
+
has_many :blocks, :class_name => "UserBlock"
has_many :blocks_created, :class_name => "UserBlock", :foreign_key => :creator_id
has_many :blocks_revoked, :class_name => "UserBlock", :foreign_key => :revoker_id
--- /dev/null
+<tr>
+ <td class="align-middle">
+ <ul class="list-unstyled mb-0">
+ <li><%= link_to application.name, oauth_application_path(application) %></li>
+ <% application.redirect_uri.split.each do |uri| -%>
+ <li class="text-muted"><%= uri %></li>
+ <% end -%>
+ </ul>
+ </td>
+ <td class="align-middle">
+ <ul class="list-unstyled mb-0">
+ <% application.scopes.each do |scope| -%>
+ <li><%= t "oauth.scopes.#{scope}" %> <code class="text-muted">(<%= scope %>)</code></li>
+ <% end -%>
+ </ul>
+ </td>
+ <td class="align-middle">
+ <%= link_to t(".edit"), edit_oauth_application_path(application), :class => "btn btn-outline-primary" %>
+ </td>
+ <td class="align-middle">
+ <%= link_to t(".delete"), oauth_application_path(application), { :method => :delete, :class => "btn btn-outline-danger", :data => { :confirm => t(".confirm_delete") } } %>
+ </td>
+</tr>
--- /dev/null
+<%= f.text_field :name %>
+<%= f.text_area :redirect_uri %>
+<%= f.form_group :confidential do %>
+ <%= f.check_box :confidential %>
+<% end %>
+<%= f.collection_check_boxes :scopes, Oauth.scopes, :name, :description %>
+<%= f.primary %>
--- /dev/null
+<% content_for :heading do %>
+ <h1><%= t ".title" %></h1>
+<% end %>
+
+<%= bootstrap_form_for @application, :url => oauth_application_path(@application), :html => { :method => :put } do |f| %>
+ <%= render :partial => "form", :locals => { :f => f } %>
+<% end %>
--- /dev/null
+<% content_for :heading do %>
+ <h1><%= t ".title" %></h1>
+<% end %>
+
+<% if @applications.length > 0 %>
+ <table class="table table-borderless table-striped">
+ <thead>
+ <th><%= t ".name" %></th>
+ <th><%= t ".permissions" %></th>
+ <th></th>
+ <th></th>
+ </thead>
+ <tbody>
+ <%= render :partial => "application", :collection => @applications %>
+ </tbody>
+ </table>
+<% else %>
+ <p><%= t ".no_applications_html", :oauth2 => link_to(t(".oauth_2"), "https://oauth.net/2/") %></p>
+<% end %>
+
+<p>
+ <%= link_to t(".new"), new_oauth_application_path, :class => "btn btn-outline-primary" %>
+</p>
--- /dev/null
+<% content_for :heading do %>
+ <h1><%= t ".title" %></h1>
+<% end %>
+
+<%= bootstrap_form_for @application, :url => { :action => :create } do |f| %>
+ <%= render :partial => "form", :locals => { :f => f } %>
+<% end %>
--- /dev/null
+<p><%= t ".sorry" %></p>
--- /dev/null
+<% content_for :heading do %>
+ <h1><%= @application.name %></h1>
+<% end %>
+
+<% secret = flash[:application_secret].presence || @application.plaintext_secret %>
+
+<table class="table table-borderless">
+ <tr>
+ <th><%= t ".client_id" %></th>
+ <td><code><%= @application.uid %></code></td>
+ </tr>
+ <% unless secret.blank? && Doorkeeper.config.application_secret_hashed? -%>
+ <tr>
+ <th><%= t ".client_secret" %></th>
+ <td>
+ <code><%= secret %></code>
+ <% if Doorkeeper.config.application_secret_hashed? -%>
+ <br />
+ <small class="text-danger"><%= t ".client_secret_warning" %></small>
+ <% end -%>
+ </td>
+ </tr>
+ <% end -%>
+ <tr>
+ <th><%= t ".permissions" %></th>
+ <td>
+ <ul class="list-unstyled mb-0">
+ <% @application.scopes.each do |scope| -%>
+ <li><%= t "oauth.scopes.#{scope}" %> <code class="text-muted">(<%= scope %>)</code></li>
+ <% end -%>
+ </ul>
+ </td>
+ </tr>
+ <tr>
+ <th><%= t ".redirect_uris" %></th>
+ <td>
+ <ul class="list-unstyled mb-0">
+ <% @application.redirect_uri.split.each do |uri| -%>
+ <li><%= uri %></li>
+ <% end -%>
+ </ul>
+ </td>
+ </tr>
+</table>
+
+<div>
+ <%= link_to t(".edit"), edit_oauth_application_path(@application), :class => "btn btn-outline-primary" %>
+ <%= link_to t(".delete"), oauth_application_path(@application), { :method => :delete, :class => "btn btn-outline-danger", :data => { :confirm => t(".confirm_delete") } } %>
+</td>
--- /dev/null
+<% content_for :heading do %>
+ <h1><%= t ".title" %></h1>
+<% end %>
+
+<p><%= @pre_auth.error_response.body[:error_description] %></p>
--- /dev/null
+<% content_for :heading do %>
+ <h1><%= t ".title" %></h1>
+<% end %>
+
+<p><%= t ".introduction", :application => @pre_auth.client.name %></p>
+
+<ul>
+ <% @pre_auth.scopes.each do |scope| -%>
+ <li><%= t "oauth.scopes.#{scope}" %></li>
+ <% end -%>
+</ul>
+
+<div class="row justify-content-start no-gutters mx-n1">
+ <div class="col-auto mx-1">
+ <%= bootstrap_form_tag :action => :create do |f| %>
+ <%= f.hidden_field :client_id, :value => @pre_auth.client.uid %>
+ <%= f.hidden_field :redirect_uri, :value => @pre_auth.redirect_uri %>
+ <%= f.hidden_field :state, :value => @pre_auth.state %>
+ <%= f.hidden_field :response_type, :value => @pre_auth.response_type %>
+ <%= f.hidden_field :scope, :value => @pre_auth.scope %>
+ <%= f.hidden_field :code_challenge, :value => @pre_auth.code_challenge %>
+ <%= f.hidden_field :code_challenge_method, :value => @pre_auth.code_challenge_method %>
+ <%= f.primary t(".authorize") %>
+ <% end %>
+ </div>
+ <div class="col-auto mx-1">
+ <%= bootstrap_form_tag :action => :destroy, :html => { :method => :delete } do |f| %>
+ <%= f.hidden_field :client_id, :value => @pre_auth.client.uid %>
+ <%= f.hidden_field :redirect_uri, :value => @pre_auth.redirect_uri %>
+ <%= f.hidden_field :state, :value => @pre_auth.state %>
+ <%= f.hidden_field :response_type, :value => @pre_auth.response_type %>
+ <%= f.hidden_field :scope, :value => @pre_auth.scope %>
+ <%= f.hidden_field :code_challenge, :value => @pre_auth.code_challenge %>
+ <%= f.hidden_field :code_challenge_method, :value => @pre_auth.code_challenge_method %>
+ <%= f.submit t(".deny") %>
+ <% end %>
+ </div>
+</div>
--- /dev/null
+<% content_for :heading do %>
+ <h1><%= t ".title" %></h1>
+<% end %>
+
+<code id="authorization_code"><%= params[:code] %></code>
--- /dev/null
+<tr>
+ <td class="align-middle">
+ <%= link_to application.name, oauth_application_path(application) %>
+ </td>
+ <td class="align-middle">
+ <ul class="list-unstyled mb-0">
+ <% application.scopes.each do |scope| -%>
+ <li><%= t "oauth.scopes.#{scope}" %></li>
+ <% end -%>
+ </ul>
+ </td>
+ <td class="align-middle text-right">
+ <%= link_to t(".revoke"), oauth_authorized_application_path(application), { :method => :delete, :class => "btn btn-outline-danger", :data => { :confirm => t(".confirm_revoke") } } %>
+ </td>
+</tr>
--- /dev/null
+<% content_for :heading do %>
+ <h1><%= t ".title" %></h1>
+<% end %>
+
+<% if @applications.length > 0 %>
+ <table class="table table-borderless table-striped">
+ <thead>
+ <th><%= t ".application" %></th>
+ <th><%= t ".permissions" %></th>
+ <th></th>
+ </thead>
+ <tbody>
+ <%= render :partial => "application", :collection => @applications %>
+ </tbody>
+ </table>
+<% else %>
+ <p><%= t ".no_applications_html", :oauth2 => link_to(t(".oauth_2"), "https://oauth.net/2/") %></p>
+<% end %>
<h1><%= t ".my settings" %></h1>
<ul class='secondary-actions clearfix'>
<li><%= link_to t(".return to profile"), user_path(current_user) %></li>
- <li><%= link_to t("users.show.oauth settings"), :controller => "oauth_clients", :action => "index" %></li>
+ <li><%= link_to t(".oauth1 settings"), oauth_clients_path %></li>
+ <li><%= link_to t(".oauth2 applications"), oauth_applications_path %></li>
+ <li><%= link_to t(".oauth2 authorizations"), oauth_authorized_applications_path %></li>
</ul>
<% end %>
--- /dev/null
+# 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
+ current_user
+ 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
+ current_user
+ 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 nil
+
+ # 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)
+ #
+ # custom_access_token_expires_in do |context|
+ # context.client.application.additional_settings.implicit_oauth_expiration
+ # end
+
+ # Use a custom class for generating the access token.
+ # See https://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-base-controller
+
+ base_controller "ApplicationController"
+
+ # Reuse access token for the same resource owner within an application (disabled by default).
+ #
+ # This option protects your application from creating new tokens before old valid one becomes
+ # expired so your database doesn't bloat. Keep in mind that when this option is `on` Doorkeeper
+ # doesn't updates existing token expiration time, it will create a new token instead.
+ # Rationale: https://github.com/doorkeeper-gem/doorkeeper/issues/383
+ #
+ # 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 enabling the additional
+ # setting `fallback_to_plain_secrets` below.
+
+ hash_token_secrets
+
+ # Hash application secrets before persisting them.
+
+ hash_application_secrets
+
+ # 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.
+ #
+ # fallback_to_plain_secrets
+
+ # 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 => true
+
+ # 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(*Oauth::SCOPES)
+
+ # 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
+
+ # 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 do |uri|
+ !Rails.env.development? && uri.host != "127.0.0.1"
+ end
+
+ # Specify what redirect URI's you want to block during Application creation.
+ # Any redirect URI is whitelisted by default.
+ #
+ # You can use this option in order to forbid URI's with 'javascript' scheme
+ # for example.
+ #
+ # forbid_redirect_uri { |uri| uri.scheme.to_s.downcase == 'javascript' }
+
+ # 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:
+ # http://tools.ietf.org/html/rfc6819#section-4.4.2
+ # http://tools.ietf.org/html/rfc6819#section-4.4.3
+
+ grant_flows %w[authorization_code]
+
+ # 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_whitelist.include?(resource_owner)
+ # end
+
+ # 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
client_application:
create: Register
update: Update
+ doorkeeper_application:
+ create: Register
+ update: Update
redaction:
create: Create redaction
update: Save redaction
latitude: "Latitude"
longitude: "Longitude"
language: "Language"
+ doorkeeper/application:
+ name: Name
+ redirect_uri: Redirect URIs
+ confidential: Confidential application?
+ scopes: Permissions
friend:
user: "User"
friend: "Friend"
pass_crypt: "Password"
pass_crypt_confirmation: "Confirm Password"
help:
+ doorkeeper/application:
+ confidential: "Application will be used where the client secret can be kept confidential (native mobile apps and single page apps are not confidential)"
+ redirect_uri: "Use one line per URI"
trace:
tagstring: comma delimited
user_block:
comment: Comment
newer_comments: "Newer Comments"
older_comments: "Older Comments"
+ doorkeeper:
+ flash:
+ applications:
+ create:
+ notice: Application Registered.
friendships:
make_friend:
heading: "Add %{user} as a friend?"
flash: "You've revoked the token for %{application}"
permissions:
missing: "You have not permitted the application access to this facility"
+ scopes:
+ read_prefs: Read user preferences
+ write_prefs: Modify user preferences
+ write_diary: Create diary entries, comments and make friends
+ write_api: Modify the map
+ read_gpx: Read private GPS traces
+ write_gpx: Upload GPS traces
+ write_notes: Modify notes
oauth_clients:
new:
title: "Register a new application"
flash: "Updated the client information successfully"
destroy:
flash: "Destroyed the client application registration"
+ oauth2_applications:
+ index:
+ title: "My client applications"
+ no_applications_html: "Do you have an application you would like to register for use with us using the %{oauth2} standard? You must register your application before it can make OAuth requests to this service."
+ oauth_2: "OAuth 2"
+ new: "Register new application"
+ name: "Name"
+ permissions: "Permissions"
+ application:
+ edit: "Edit"
+ delete: "Delete"
+ confirm_delete: "Delete this application?"
+ new:
+ title: "Register a new application"
+ edit:
+ title: "Edit your application"
+ show:
+ edit: "Edit"
+ delete: "Delete"
+ confirm_delete: "Delete this application?"
+ client_id: "Client ID"
+ client_secret: "Client Secret"
+ client_secret_warning: "Make sure to save this secret - it will not be accessible again"
+ permissions: "Permissions"
+ redirect_uris: "Redirect URIs"
+ not_found:
+ sorry: "Sorry, that application could not be found."
+ oauth2_authorizations:
+ new:
+ title: "Authorization required"
+ introduction: "Authorize %{application} to access your account with the following permissions?"
+ authorize: "Authorize"
+ deny: "Deny"
+ error:
+ title: "An error has occurred"
+ show:
+ title: "Authorization code"
+ oauth2_authorized_applications:
+ index:
+ title: "My authorized applications"
+ application: "Application"
+ permissions: "Permissions"
+ no_applications_html: "You have not yet authorized any %{oauth2} applications."
+ application:
+ revoke: "Revoke Access"
+ confirm_revoke: "Revoke access for this application?"
users:
new:
title: "Sign Up"
my profile: My Profile
my settings: My Settings
my comments: My Comments
- oauth settings: oauth settings
blocks on me: Blocks on Me
blocks by me: Blocks by Me
send message: Send Message
save changes button: Save Changes
make edits public button: Make all my edits public
return to profile: Return to profile
+ oauth1 settings: OAuth 1 settings
+ oauth2 applications: OAuth 2 applications
+ oauth2 authorizations: OAuth 2 authorizations
flash update success confirm needed: "User information updated successfully. Check your email for a note to confirm your new email address."
flash update success: "User information updated successfully."
set_home:
OpenStreetMap::Application.routes.draw do
+ use_doorkeeper :scope => "oauth2" do
+ controllers :authorizations => "oauth2_authorizations",
+ :applications => "oauth2_applications",
+ :authorized_applications => "oauth2_authorized_applications"
+ end
+
# API
namespace :api do
get "capabilities" => "capabilities#show" # Deprecated, remove when 0.6 support is removed
--- /dev/null
+# frozen_string_literal: true
+
+class CreateDoorkeeperTables < ActiveRecord::Migration[6.0]
+ def change
+ create_table :oauth_applications do |t|
+ t.references :owner, :null => false, :type => :bigint, :polymorphic => true
+ t.string :name, :null => false
+ t.string :uid, :null => false
+ t.string :secret, :null => false
+ t.text :redirect_uri, :null => false
+ t.string :scopes, :null => false, :default => ""
+ t.boolean :confidential, :null => false, :default => true
+ t.timestamps :null => false
+ end
+
+ add_index :oauth_applications, :uid, :unique => true
+ add_foreign_key :oauth_applications, :users, :column => :owner_id, :validate => false
+
+ create_table :oauth_access_grants do |t|
+ t.references :resource_owner, :null => false, :type => :bigint
+ t.references :application, :null => false
+ t.string :token, :null => false
+ t.integer :expires_in, :null => false
+ t.text :redirect_uri, :null => false
+ t.datetime :created_at, :null => false
+ t.datetime :revoked_at
+ t.string :scopes, :null => false, :default => ""
+ t.column :code_challenge, :string, :null => true
+ t.column :code_challenge_method, :string, :null => true
+ end
+
+ add_index :oauth_access_grants, :token, :unique => true
+ add_foreign_key :oauth_access_grants, :users, :column => :resource_owner_id, :validate => false
+ add_foreign_key :oauth_access_grants, :oauth_applications, :column => :application_id, :validate => false
+
+ create_table :oauth_access_tokens do |t|
+ t.references :resource_owner, :index => true, :type => :bigint
+ t.references :application, :null => false
+ t.string :token, :null => false
+ t.string :refresh_token
+ t.integer :expires_in
+ t.datetime :revoked_at
+ t.datetime :created_at, :null => false
+ t.string :scopes
+ t.string :previous_refresh_token, :null => false, :default => ""
+ end
+
+ add_index :oauth_access_tokens, :token, :unique => true
+ add_index :oauth_access_tokens, :refresh_token, :unique => true
+ add_foreign_key :oauth_access_tokens, :users, :column => :resource_owner_id, :validate => false
+ add_foreign_key :oauth_access_tokens, :oauth_applications, :column => :application_id, :validate => false
+ end
+end
ALTER SEQUENCE public.notes_id_seq OWNED BY public.notes.id;
+--
+-- Name: oauth_access_grants; Type: TABLE; Schema: public; Owner: -
+--
+
+CREATE TABLE public.oauth_access_grants (
+ id bigint NOT NULL,
+ resource_owner_id bigint NOT NULL,
+ application_id bigint NOT NULL,
+ token character varying NOT NULL,
+ expires_in integer NOT NULL,
+ redirect_uri text NOT NULL,
+ created_at timestamp without time zone NOT NULL,
+ revoked_at timestamp without time zone,
+ scopes character varying DEFAULT ''::character varying NOT NULL,
+ code_challenge character varying,
+ code_challenge_method character varying
+);
+
+
+--
+-- Name: oauth_access_grants_id_seq; Type: SEQUENCE; Schema: public; Owner: -
+--
+
+CREATE SEQUENCE public.oauth_access_grants_id_seq
+ START WITH 1
+ INCREMENT BY 1
+ NO MINVALUE
+ NO MAXVALUE
+ CACHE 1;
+
+
+--
+-- Name: oauth_access_grants_id_seq; Type: SEQUENCE OWNED BY; Schema: public; Owner: -
+--
+
+ALTER SEQUENCE public.oauth_access_grants_id_seq OWNED BY public.oauth_access_grants.id;
+
+
+--
+-- Name: oauth_access_tokens; Type: TABLE; Schema: public; Owner: -
+--
+
+CREATE TABLE public.oauth_access_tokens (
+ id bigint NOT NULL,
+ resource_owner_id bigint,
+ application_id bigint NOT NULL,
+ token character varying NOT NULL,
+ refresh_token character varying,
+ expires_in integer,
+ revoked_at timestamp without time zone,
+ created_at timestamp without time zone NOT NULL,
+ scopes character varying,
+ previous_refresh_token character varying DEFAULT ''::character varying NOT NULL
+);
+
+
+--
+-- Name: oauth_access_tokens_id_seq; Type: SEQUENCE; Schema: public; Owner: -
+--
+
+CREATE SEQUENCE public.oauth_access_tokens_id_seq
+ START WITH 1
+ INCREMENT BY 1
+ NO MINVALUE
+ NO MAXVALUE
+ CACHE 1;
+
+
+--
+-- Name: oauth_access_tokens_id_seq; Type: SEQUENCE OWNED BY; Schema: public; Owner: -
+--
+
+ALTER SEQUENCE public.oauth_access_tokens_id_seq OWNED BY public.oauth_access_tokens.id;
+
+
+--
+-- Name: oauth_applications; Type: TABLE; Schema: public; Owner: -
+--
+
+CREATE TABLE public.oauth_applications (
+ id bigint NOT NULL,
+ owner_type character varying NOT NULL,
+ owner_id bigint NOT NULL,
+ name character varying NOT NULL,
+ uid character varying NOT NULL,
+ secret character varying NOT NULL,
+ redirect_uri text NOT NULL,
+ scopes character varying DEFAULT ''::character varying NOT NULL,
+ confidential boolean DEFAULT true NOT NULL,
+ created_at timestamp(6) without time zone NOT NULL,
+ updated_at timestamp(6) without time zone NOT NULL
+);
+
+
+--
+-- Name: oauth_applications_id_seq; Type: SEQUENCE; Schema: public; Owner: -
+--
+
+CREATE SEQUENCE public.oauth_applications_id_seq
+ START WITH 1
+ INCREMENT BY 1
+ NO MINVALUE
+ NO MAXVALUE
+ CACHE 1;
+
+
+--
+-- Name: oauth_applications_id_seq; Type: SEQUENCE OWNED BY; Schema: public; Owner: -
+--
+
+ALTER SEQUENCE public.oauth_applications_id_seq OWNED BY public.oauth_applications.id;
+
+
--
-- Name: oauth_nonces; Type: TABLE; Schema: public; Owner: -
--
ALTER TABLE ONLY public.notes ALTER COLUMN id SET DEFAULT nextval('public.notes_id_seq'::regclass);
+--
+-- Name: oauth_access_grants id; Type: DEFAULT; Schema: public; Owner: -
+--
+
+ALTER TABLE ONLY public.oauth_access_grants ALTER COLUMN id SET DEFAULT nextval('public.oauth_access_grants_id_seq'::regclass);
+
+
+--
+-- Name: oauth_access_tokens id; Type: DEFAULT; Schema: public; Owner: -
+--
+
+ALTER TABLE ONLY public.oauth_access_tokens ALTER COLUMN id SET DEFAULT nextval('public.oauth_access_tokens_id_seq'::regclass);
+
+
+--
+-- Name: oauth_applications id; Type: DEFAULT; Schema: public; Owner: -
+--
+
+ALTER TABLE ONLY public.oauth_applications ALTER COLUMN id SET DEFAULT nextval('public.oauth_applications_id_seq'::regclass);
+
+
--
-- Name: oauth_nonces id; Type: DEFAULT; Schema: public; Owner: -
--
ADD CONSTRAINT notes_pkey PRIMARY KEY (id);
+--
+-- Name: oauth_access_grants oauth_access_grants_pkey; Type: CONSTRAINT; Schema: public; Owner: -
+--
+
+ALTER TABLE ONLY public.oauth_access_grants
+ ADD CONSTRAINT oauth_access_grants_pkey PRIMARY KEY (id);
+
+
+--
+-- Name: oauth_access_tokens oauth_access_tokens_pkey; Type: CONSTRAINT; Schema: public; Owner: -
+--
+
+ALTER TABLE ONLY public.oauth_access_tokens
+ ADD CONSTRAINT oauth_access_tokens_pkey PRIMARY KEY (id);
+
+
+--
+-- Name: oauth_applications oauth_applications_pkey; Type: CONSTRAINT; Schema: public; Owner: -
+--
+
+ALTER TABLE ONLY public.oauth_applications
+ ADD CONSTRAINT oauth_applications_pkey PRIMARY KEY (id);
+
+
--
-- Name: oauth_nonces oauth_nonces_pkey; Type: CONSTRAINT; Schema: public; Owner: -
--
CREATE INDEX index_note_comments_on_created_at ON public.note_comments USING btree (created_at);
+--
+-- Name: index_oauth_access_grants_on_application_id; Type: INDEX; Schema: public; Owner: -
+--
+
+CREATE INDEX index_oauth_access_grants_on_application_id ON public.oauth_access_grants USING btree (application_id);
+
+
+--
+-- Name: index_oauth_access_grants_on_resource_owner_id; Type: INDEX; Schema: public; Owner: -
+--
+
+CREATE INDEX index_oauth_access_grants_on_resource_owner_id ON public.oauth_access_grants USING btree (resource_owner_id);
+
+
+--
+-- Name: index_oauth_access_grants_on_token; Type: INDEX; Schema: public; Owner: -
+--
+
+CREATE UNIQUE INDEX index_oauth_access_grants_on_token ON public.oauth_access_grants USING btree (token);
+
+
+--
+-- Name: index_oauth_access_tokens_on_application_id; Type: INDEX; Schema: public; Owner: -
+--
+
+CREATE INDEX index_oauth_access_tokens_on_application_id ON public.oauth_access_tokens USING btree (application_id);
+
+
+--
+-- Name: index_oauth_access_tokens_on_refresh_token; Type: INDEX; Schema: public; Owner: -
+--
+
+CREATE UNIQUE INDEX index_oauth_access_tokens_on_refresh_token ON public.oauth_access_tokens USING btree (refresh_token);
+
+
+--
+-- Name: index_oauth_access_tokens_on_resource_owner_id; Type: INDEX; Schema: public; Owner: -
+--
+
+CREATE INDEX index_oauth_access_tokens_on_resource_owner_id ON public.oauth_access_tokens USING btree (resource_owner_id);
+
+
+--
+-- Name: index_oauth_access_tokens_on_token; Type: INDEX; Schema: public; Owner: -
+--
+
+CREATE UNIQUE INDEX index_oauth_access_tokens_on_token ON public.oauth_access_tokens USING btree (token);
+
+
+--
+-- Name: index_oauth_applications_on_owner_type_and_owner_id; Type: INDEX; Schema: public; Owner: -
+--
+
+CREATE INDEX index_oauth_applications_on_owner_type_and_owner_id ON public.oauth_applications USING btree (owner_type, owner_id);
+
+
+--
+-- Name: index_oauth_applications_on_uid; Type: INDEX; Schema: public; Owner: -
+--
+
+CREATE UNIQUE INDEX index_oauth_applications_on_uid ON public.oauth_applications USING btree (uid);
+
+
--
-- Name: index_oauth_nonces_on_nonce_and_timestamp; Type: INDEX; Schema: public; Owner: -
--
ADD CONSTRAINT diary_entry_subscriptions_user_id_fkey FOREIGN KEY (user_id) REFERENCES public.users(id);
+--
+-- Name: oauth_access_grants fk_rails_330c32d8d9; Type: FK CONSTRAINT; Schema: public; Owner: -
+--
+
+ALTER TABLE ONLY public.oauth_access_grants
+ ADD CONSTRAINT fk_rails_330c32d8d9 FOREIGN KEY (resource_owner_id) REFERENCES public.users(id) NOT VALID;
+
+
+--
+-- Name: oauth_access_tokens fk_rails_732cb83ab7; Type: FK CONSTRAINT; Schema: public; Owner: -
+--
+
+ALTER TABLE ONLY public.oauth_access_tokens
+ ADD CONSTRAINT fk_rails_732cb83ab7 FOREIGN KEY (application_id) REFERENCES public.oauth_applications(id) NOT VALID;
+
+
--
-- Name: active_storage_variant_records fk_rails_993965df05; Type: FK CONSTRAINT; Schema: public; Owner: -
--
ADD CONSTRAINT fk_rails_993965df05 FOREIGN KEY (blob_id) REFERENCES public.active_storage_blobs(id);
+--
+-- Name: oauth_access_grants fk_rails_b4b53e07b8; Type: FK CONSTRAINT; Schema: public; Owner: -
+--
+
+ALTER TABLE ONLY public.oauth_access_grants
+ ADD CONSTRAINT fk_rails_b4b53e07b8 FOREIGN KEY (application_id) REFERENCES public.oauth_applications(id) NOT VALID;
+
+
--
-- Name: active_storage_attachments fk_rails_c3b3935057; Type: FK CONSTRAINT; Schema: public; Owner: -
--
ADD CONSTRAINT fk_rails_c3b3935057 FOREIGN KEY (blob_id) REFERENCES public.active_storage_blobs(id);
+--
+-- Name: oauth_applications fk_rails_cc886e315a; Type: FK CONSTRAINT; Schema: public; Owner: -
+--
+
+ALTER TABLE ONLY public.oauth_applications
+ ADD CONSTRAINT fk_rails_cc886e315a FOREIGN KEY (owner_id) REFERENCES public.users(id) NOT VALID;
+
+
+--
+-- Name: oauth_access_tokens fk_rails_ee63f25419; Type: FK CONSTRAINT; Schema: public; Owner: -
+--
+
+ALTER TABLE ONLY public.oauth_access_tokens
+ ADD CONSTRAINT fk_rails_ee63f25419 FOREIGN KEY (resource_owner_id) REFERENCES public.users(id) NOT VALID;
+
+
--
-- Name: friends friends_friend_user_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: -
--
('20190702193519'),
('20190716173946'),
('20191120140058'),
+('20201004105659'),
('20201006213836'),
('20201006220807'),
('20201214144017'),
--- /dev/null
+module Oauth
+ SCOPES = %w[read_prefs write_prefs write_diary write_api read_gpx write_gpx write_notes].freeze
+
+ class Scope
+ attr_reader :name
+
+ def initialize(name)
+ @name = name
+ end
+
+ def description
+ I18n.t("oauth.scopes.#{name}")
+ end
+ end
+
+ def self.scopes
+ SCOPES.collect { |s| Scope.new(s) }
+ end
+end
--- /dev/null
+require "test_helper"
+
+class Oauth2ApplicationsControllerTest < ActionDispatch::IntegrationTest
+ ##
+ # test all routes which lead to this controller
+ def test_routes
+ assert_routing(
+ { :path => "/oauth2/applications", :method => :get },
+ { :controller => "oauth2_applications", :action => "index" }
+ )
+ assert_routing(
+ { :path => "/oauth2/applications", :method => :post },
+ { :controller => "oauth2_applications", :action => "create" }
+ )
+ assert_routing(
+ { :path => "/oauth2/applications/new", :method => :get },
+ { :controller => "oauth2_applications", :action => "new" }
+ )
+ assert_routing(
+ { :path => "/oauth2/applications/1/edit", :method => :get },
+ { :controller => "oauth2_applications", :action => "edit", :id => "1" }
+ )
+ assert_routing(
+ { :path => "/oauth2/applications/1", :method => :get },
+ { :controller => "oauth2_applications", :action => "show", :id => "1" }
+ )
+ assert_routing(
+ { :path => "/oauth2/applications/1", :method => :patch },
+ { :controller => "oauth2_applications", :action => "update", :id => "1" }
+ )
+ assert_routing(
+ { :path => "/oauth2/applications/1", :method => :put },
+ { :controller => "oauth2_applications", :action => "update", :id => "1" }
+ )
+ assert_routing(
+ { :path => "/oauth2/applications/1", :method => :delete },
+ { :controller => "oauth2_applications", :action => "destroy", :id => "1" }
+ )
+ end
+
+ def test_index
+ user = create(:user)
+ create_list(:oauth_application, 2, :owner => user)
+
+ get oauth_applications_path
+ assert_response :redirect
+ assert_redirected_to login_path(:referer => oauth_applications_path)
+
+ session_for(user)
+
+ get oauth_applications_path
+ assert_response :success
+ assert_template "oauth2_applications/index"
+ assert_select "tr", 2
+ end
+
+ def test_new
+ user = create(:user)
+
+ get new_oauth_application_path
+ assert_response :redirect
+ assert_redirected_to login_path(:referer => new_oauth_application_path)
+
+ session_for(user)
+
+ get new_oauth_application_path
+ assert_response :success
+ assert_template "oauth2_applications/new"
+ assert_select "form", 1 do
+ assert_select "input#doorkeeper_application_name", 1
+ assert_select "textarea#doorkeeper_application_redirect_uri", 1
+ assert_select "input#doorkeeper_application_confidential", 1
+ Oauth.scopes.each do |scope|
+ assert_select "input#doorkeeper_application_scopes_#{scope.name}", 1
+ end
+ end
+ end
+
+ def test_create
+ user = create(:user)
+
+ assert_difference "Doorkeeper::Application.count", 0 do
+ post oauth_applications_path
+ end
+ assert_response :forbidden
+
+ session_for(user)
+
+ assert_difference "Doorkeeper::Application.count", 0 do
+ post oauth_applications_path(:doorkeeper_application => {
+ :name => "Test Application"
+ })
+ end
+ assert_response :success
+ assert_template "oauth2_applications/new"
+
+ assert_difference "Doorkeeper::Application.count", 0 do
+ post oauth_applications_path(:doorkeeper_application => {
+ :name => "Test Application",
+ :redirect_uri => "https://test.example.com/",
+ :scopes => ["bad_scope"]
+ })
+ end
+ assert_response :success
+ assert_template "oauth2_applications/new"
+
+ assert_difference "Doorkeeper::Application.count", 1 do
+ post oauth_applications_path(:doorkeeper_application => {
+ :name => "Test Application",
+ :redirect_uri => "https://test.example.com/",
+ :scopes => ["read_prefs"]
+ })
+ end
+ assert_response :redirect
+ assert_redirected_to oauth_application_path(:id => Doorkeeper::Application.find_by(:name => "Test Application").id)
+ end
+
+ def test_show
+ user = create(:user)
+ client = create(:oauth_application, :owner => user)
+ other_client = create(:oauth_application)
+
+ get oauth_application_path(:id => client)
+ assert_response :redirect
+ assert_redirected_to login_path(:referer => oauth_application_path(:id => client.id))
+
+ session_for(user)
+
+ get oauth_application_path(:id => other_client)
+ assert_response :not_found
+ assert_template "oauth2_applications/not_found"
+
+ get oauth_application_path(:id => client)
+ assert_response :success
+ assert_template "oauth2_applications/show"
+ end
+
+ def test_edit
+ user = create(:user)
+ client = create(:oauth_application, :owner => user)
+ other_client = create(:oauth_application)
+
+ get edit_oauth_application_path(:id => client)
+ assert_response :redirect
+ assert_redirected_to login_path(:referer => edit_oauth_application_path(:id => client.id))
+
+ session_for(user)
+
+ get edit_oauth_application_path(:id => other_client)
+ assert_response :not_found
+ assert_template "oauth2_applications/not_found"
+
+ get edit_oauth_application_path(:id => client)
+ assert_response :success
+ assert_template "oauth2_applications/edit"
+ assert_select "form", 1 do
+ assert_select "input#doorkeeper_application_name", 1
+ assert_select "textarea#doorkeeper_application_redirect_uri", 1
+ assert_select "input#doorkeeper_application_confidential", 1
+ Oauth.scopes.each do |scope|
+ assert_select "input#doorkeeper_application_scopes_#{scope.name}", 1
+ end
+ end
+ end
+
+ def test_update
+ user = create(:user)
+ client = create(:oauth_application, :owner => user)
+ other_client = create(:oauth_application)
+
+ put oauth_application_path(:id => client)
+ assert_response :forbidden
+
+ session_for(user)
+
+ put oauth_application_path(:id => other_client)
+ assert_response :not_found
+ assert_template "oauth2_applications/not_found"
+
+ put oauth_application_path(:id => client,
+ :doorkeeper_application => {
+ :name => "New Name",
+ :redirect_uri => nil
+ })
+ assert_response :success
+ assert_template "oauth2_applications/edit"
+
+ put oauth_application_path(:id => client,
+ :doorkeeper_application => {
+ :name => "New Name",
+ :redirect_uri => "https://new.example.com/url"
+ })
+ assert_response :redirect
+ assert_redirected_to oauth_application_path(:id => client.id)
+ end
+
+ def test_destroy
+ user = create(:user)
+ client = create(:oauth_application, :owner => user)
+ other_client = create(:oauth_application)
+
+ assert_difference "Doorkeeper::Application.count", 0 do
+ delete oauth_application_path(:id => client)
+ end
+ assert_response :forbidden
+
+ session_for(user)
+
+ assert_difference "Doorkeeper::Application.count", 0 do
+ delete oauth_application_path(:id => other_client)
+ end
+ assert_response :not_found
+ assert_template "oauth2_applications/not_found"
+
+ assert_difference "Doorkeeper::Application.count", -1 do
+ delete oauth_application_path(:id => client)
+ end
+ assert_response :redirect
+ assert_redirected_to oauth_applications_path
+ end
+end
--- /dev/null
+require "test_helper"
+
+class Oauth2AuthorizationsControllerTest < ActionDispatch::IntegrationTest
+ ##
+ # test all routes which lead to this controller
+ def test_routes
+ assert_routing(
+ { :path => "/oauth2/authorize", :method => :get },
+ { :controller => "oauth2_authorizations", :action => "new" }
+ )
+ assert_routing(
+ { :path => "/oauth2/authorize", :method => :post },
+ { :controller => "oauth2_authorizations", :action => "create" }
+ )
+ assert_routing(
+ { :path => "/oauth2/authorize", :method => :delete },
+ { :controller => "oauth2_authorizations", :action => "destroy" }
+ )
+ assert_routing(
+ { :path => "/oauth2/authorize/native", :method => :get },
+ { :controller => "oauth2_authorizations", :action => "show" }
+ )
+ end
+
+ def test_new
+ application = create(:oauth_application, :scopes => "write_api")
+
+ get oauth_authorization_path(:client_id => application.uid,
+ :redirect_uri => application.redirect_uri,
+ :response_type => "code",
+ :scope => "write_api")
+ assert_response :redirect
+ assert_redirected_to login_path(:referer => oauth_authorization_path(:client_id => application.uid,
+ :redirect_uri => application.redirect_uri,
+ :response_type => "code",
+ :scope => "write_api"))
+
+ session_for(create(:user))
+
+ get oauth_authorization_path(:client_id => application.uid,
+ :redirect_uri => application.redirect_uri,
+ :response_type => "code",
+ :scope => "write_api")
+ assert_response :success
+ assert_template "oauth2_authorizations/new"
+ end
+
+ def test_new_native
+ application = create(:oauth_application, :scopes => "write_api", :redirect_uri => "urn:ietf:wg:oauth:2.0:oob")
+
+ get oauth_authorization_path(:client_id => application.uid,
+ :redirect_uri => application.redirect_uri,
+ :response_type => "code",
+ :scope => "write_api")
+ assert_response :redirect
+ assert_redirected_to login_path(:referer => oauth_authorization_path(:client_id => application.uid,
+ :redirect_uri => application.redirect_uri,
+ :response_type => "code",
+ :scope => "write_api"))
+
+ session_for(create(:user))
+
+ get oauth_authorization_path(:client_id => application.uid,
+ :redirect_uri => application.redirect_uri,
+ :response_type => "code",
+ :scope => "write_api")
+ assert_response :success
+ assert_template "oauth2_authorizations/new"
+ end
+
+ def test_new_bad_uri
+ application = create(:oauth_application, :scopes => "write_api")
+
+ session_for(create(:user))
+
+ get oauth_authorization_path(:client_id => application.uid,
+ :redirect_uri => "https://bad.example.com/",
+ :response_type => "code",
+ :scope => "write_api")
+ assert_response :success
+ assert_template "oauth2_authorizations/error"
+ assert_select "p", "The requested redirect uri is malformed or doesn't match client redirect URI."
+ end
+
+ def test_new_bad_scope
+ application = create(:oauth_application, :scopes => "write_api")
+
+ session_for(create(:user))
+
+ get oauth_authorization_path(:client_id => application.uid,
+ :redirect_uri => application.redirect_uri,
+ :response_type => "code",
+ :scope => "bad_scope")
+ assert_response :success
+ assert_template "oauth2_authorizations/error"
+ assert_select "p", "The requested scope is invalid, unknown, or malformed."
+
+ get oauth_authorization_path(:client_id => application.uid,
+ :redirect_uri => application.redirect_uri,
+ :response_type => "code",
+ :scope => "write_prefs")
+ assert_response :success
+ assert_template "oauth2_authorizations/error"
+ assert_select "p", "The requested scope is invalid, unknown, or malformed."
+ end
+
+ def test_create
+ application = create(:oauth_application, :scopes => "write_api")
+
+ post oauth_authorization_path(:client_id => application.uid,
+ :redirect_uri => application.redirect_uri,
+ :response_type => "code",
+ :scope => "write_api")
+ assert_response :forbidden
+
+ session_for(create(:user))
+
+ post oauth_authorization_path(:client_id => application.uid,
+ :redirect_uri => application.redirect_uri,
+ :response_type => "code",
+ :scope => "write_api")
+ assert_response :redirect
+ assert_redirected_to(/^#{Regexp.escape(application.redirect_uri)}\?code=/)
+ end
+
+ def test_create_native
+ application = create(:oauth_application, :scopes => "write_api", :redirect_uri => "urn:ietf:wg:oauth:2.0:oob")
+
+ post oauth_authorization_path(:client_id => application.uid,
+ :redirect_uri => application.redirect_uri,
+ :response_type => "code",
+ :scope => "write_api")
+ assert_response :forbidden
+
+ session_for(create(:user))
+
+ post oauth_authorization_path(:client_id => application.uid,
+ :redirect_uri => application.redirect_uri,
+ :response_type => "code",
+ :scope => "write_api")
+ assert_response :redirect
+ assert_equal native_oauth_authorization_path, URI.parse(response.location).path
+ follow_redirect!
+ assert_response :success
+ assert_template "oauth2_authorizations/show"
+ end
+
+ def test_destroy
+ application = create(:oauth_application)
+
+ delete oauth_authorization_path(:client_id => application.uid,
+ :redirect_uri => application.redirect_uri,
+ :response_type => "code",
+ :scope => "write_api")
+ assert_response :forbidden
+
+ session_for(create(:user))
+
+ delete oauth_authorization_path(:client_id => application.uid,
+ :redirect_uri => application.redirect_uri,
+ :response_type => "code",
+ :scope => "write_api")
+ assert_response :redirect
+ assert_redirected_to(/^#{Regexp.escape(application.redirect_uri)}\?error=access_denied/)
+ end
+
+ def test_destroy_native
+ application = create(:oauth_application, :redirect_uri => "urn:ietf:wg:oauth:2.0:oob")
+
+ delete oauth_authorization_path(:client_id => application.uid,
+ :redirect_uri => application.redirect_uri,
+ :response_type => "code",
+ :scope => "write_api")
+ assert_response :forbidden
+
+ session_for(create(:user))
+
+ delete oauth_authorization_path(:client_id => application.uid,
+ :redirect_uri => application.redirect_uri,
+ :response_type => "code",
+ :scope => "write_api")
+ assert_response :bad_request
+ end
+end
--- /dev/null
+require "test_helper"
+
+class Oauth2AuthorizedApplicationsControllerTest < ActionDispatch::IntegrationTest
+ ##
+ # test all routes which lead to this controller
+ def test_routes
+ assert_routing(
+ { :path => "/oauth2/authorized_applications", :method => :get },
+ { :controller => "oauth2_authorized_applications", :action => "index" }
+ )
+ assert_routing(
+ { :path => "/oauth2/authorized_applications/1", :method => :delete },
+ { :controller => "oauth2_authorized_applications", :action => "destroy", :id => "1" }
+ )
+ end
+
+ def test_index
+ user = create(:user)
+ application1 = create(:oauth_application)
+ create(:oauth_access_grant, :resource_owner_id => user.id, :application => application1)
+ create(:oauth_access_token, :resource_owner_id => user.id, :application => application1)
+ application2 = create(:oauth_application)
+ create(:oauth_access_grant, :resource_owner_id => user.id, :application => application2)
+ create(:oauth_access_token, :resource_owner_id => user.id, :application => application2)
+ create(:oauth_application)
+
+ get oauth_authorized_applications_path
+ assert_response :redirect
+ assert_redirected_to login_path(:referer => oauth_authorized_applications_path)
+
+ session_for(user)
+
+ get oauth_authorized_applications_path
+ assert_response :success
+ assert_template "oauth2_authorized_applications/index"
+ assert_select "tr", 2
+ end
+
+ def test_destroy
+ user = create(:user)
+ application1 = create(:oauth_application)
+ create(:oauth_access_grant, :resource_owner_id => user.id, :application => application1)
+ create(:oauth_access_token, :resource_owner_id => user.id, :application => application1)
+ application2 = create(:oauth_application)
+ create(:oauth_access_grant, :resource_owner_id => user.id, :application => application2)
+ create(:oauth_access_token, :resource_owner_id => user.id, :application => application2)
+ create(:oauth_application)
+
+ delete oauth_authorized_application_path(:id => application1.id)
+ assert_response :forbidden
+
+ session_for(user)
+
+ delete oauth_authorized_application_path(:id => application1.id)
+ assert_response :redirect
+ assert_redirected_to oauth_authorized_applications_path
+
+ get oauth_authorized_applications_path
+ assert_response :success
+ assert_template "oauth2_authorized_applications/index"
+ assert_select "tr", 1
+ end
+end
--- /dev/null
+FactoryBot.define do
+ factory :oauth_access_grant, :class => "Doorkeeper::AccessGrant" do
+ association :resource_owner_id, :factory => :user
+ association :application, :factory => :oauth_application
+
+ expires_in { 86400 }
+ redirect_uri { application.redirect_uri }
+ end
+end
--- /dev/null
+FactoryBot.define do
+ factory :oauth_access_token, :class => "Doorkeeper::AccessToken" do
+ association :resource_owner_id, :factory => :user
+ association :application, :factory => :oauth_application
+ end
+end
--- /dev/null
+FactoryBot.define do
+ factory :oauth_application, :class => "Doorkeeper::Application" do
+ sequence(:name) { |n| "OAuth application #{n}" }
+ sequence(:redirect_uri) { |n| "https://example.com/app/#{n}" }
+
+ association :owner, :factory => :user
+ end
+end
--- /dev/null
+require "test_helper"
+
+class OAuth2Test < ActionDispatch::IntegrationTest
+ def test_oauth2
+ client = create(:oauth_application, :redirect_uri => "https://some.web.app.example.org/callback", :scopes => "read_prefs write_api read_gpx")
+ state = SecureRandom.urlsafe_base64(16)
+
+ authorize_client(client, :state => state)
+ assert_response :redirect
+ code = validate_redirect(client, state)
+
+ token = request_token(client, code)
+
+ test_token(token, client)
+ end
+
+ def test_oauth2_oob
+ client = create(:oauth_application, :redirect_uri => "urn:ietf:wg:oauth:2.0:oob", :scopes => "read_prefs write_api read_gpx")
+
+ authorize_client(client)
+ assert_response :redirect
+ follow_redirect!
+ assert_response :success
+ assert_template "oauth2_authorizations/show"
+ m = response.body.match(%r{<code id="authorization_code">([A-Za-z0-9_-]+)</code>})
+ assert_not_nil m
+ code = m[1]
+
+ token = request_token(client, code)
+
+ test_token(token, client)
+ end
+
+ def test_oauth2_pkce_plain
+ client = create(:oauth_application, :redirect_uri => "https://some.web.app.example.org/callback", :scopes => "read_prefs write_api read_gpx")
+ state = SecureRandom.urlsafe_base64(16)
+ verifier = SecureRandom.urlsafe_base64(48)
+ challenge = verifier
+
+ authorize_client(client, :state => state, :code_challenge => challenge, :code_challenge_method => "plain")
+ assert_response :redirect
+ code = validate_redirect(client, state)
+
+ token = request_token(client, code, verifier)
+
+ test_token(token, client)
+ end
+
+ def test_oauth2_pkce_s256
+ client = create(:oauth_application, :redirect_uri => "https://some.web.app.example.org/callback", :scopes => "read_prefs write_api read_gpx")
+ state = SecureRandom.urlsafe_base64(16)
+ verifier = SecureRandom.urlsafe_base64(48)
+ challenge = Base64.urlsafe_encode64(Digest::SHA256.digest(verifier), :padding => false)
+
+ authorize_client(client, :state => state, :code_challenge => challenge, :code_challenge_method => "S256")
+ assert_response :redirect
+ code = validate_redirect(client, state)
+
+ token = request_token(client, code, verifier)
+
+ test_token(token, client)
+ end
+
+ private
+
+ def authorize_client(client, options = {})
+ options = options.merge(:client_id => client.uid,
+ :redirect_uri => client.redirect_uri,
+ :response_type => "code",
+ :scope => "read_prefs")
+
+ get oauth_authorization_path(options)
+ assert_response :redirect
+ assert_redirected_to login_path(:referer => request.fullpath)
+
+ user = create(:user)
+
+ post login_path(:username => user.email, :password => "test")
+ follow_redirect!
+ assert_response :success
+
+ get oauth_authorization_path(options)
+ assert_response :success
+ assert_template "oauth2_authorizations/new"
+
+ delete oauth_authorization_path(options)
+
+ validate_deny(client, options)
+
+ post oauth_authorization_path(options)
+ end
+
+ def validate_deny(client, options)
+ if client.redirect_uri == "urn:ietf:wg:oauth:2.0:oob"
+ assert_response :bad_request
+ else
+ assert_response :redirect
+ location = URI.parse(response.location)
+ assert_match(/^#{Regexp.escape(client.redirect_uri)}/, location.to_s)
+ query = Rack::Utils.parse_query(location.query)
+ assert_equal "access_denied", query["error"]
+ assert_equal "The resource owner or authorization server denied the request.", query["error_description"]
+ assert_equal options[:state], query["state"]
+ end
+ end
+
+ def validate_redirect(client, state)
+ location = URI.parse(response.location)
+ assert_match(/^#{Regexp.escape(client.redirect_uri)}/, location.to_s)
+ query = Rack::Utils.parse_query(location.query)
+ assert_equal state, query["state"]
+
+ query["code"]
+ end
+
+ def request_token(client, code, verifier = nil)
+ options = {
+ :client_id => client.uid,
+ :client_secret => client.plaintext_secret,
+ :code => code,
+ :grant_type => "authorization_code",
+ :redirect_uri => client.redirect_uri
+ }
+
+ if verifier
+ post oauth_token_path(options)
+ assert_response :bad_request
+
+ options = options.merge(:code_verifier => verifier)
+ end
+
+ post oauth_token_path(options)
+ assert_response :success
+ token = JSON.parse(response.body)
+ assert_equal "Bearer", token["token_type"]
+ assert_equal "read_prefs", token["scope"]
+
+ token["access_token"]
+ end
+
+ def test_token(token, client)
+ get user_preferences_path
+ assert_response :unauthorized
+
+ auth_header = bearer_authorization_header(token)
+
+ get user_preferences_path, :headers => auth_header
+ assert_response :success
+
+ get user_preferences_path(:access_token => token)
+ assert_response :unauthorized
+
+ get user_preferences_path(:bearer_token => token)
+ assert_response :unauthorized
+
+ get api_trace_path(:id => 2), :headers => auth_header
+ assert_response :forbidden
+
+ post oauth_revoke_path(:token => token)
+ assert_response :forbidden
+
+ post oauth_revoke_path(:token => token,
+ :client_id => client.uid,
+ :client_secret => client.plaintext_secret)
+ assert_response :success
+
+ get user_preferences_path, :headers => auth_header
+ assert_response :unauthorized
+ end
+end
{ "Authorization" => format("Basic %<auth>s", :auth => Base64.encode64("#{user}:#{pass}")) }
end
+ ##
+ # return request header for HTTP Bearer Authorization
+ def bearer_authorization_header(token)
+ { "Authorization" => "Bearer #{token}" }
+ end
+
##
# make an OAuth signed request
def signed_request(method, uri, options = {})