├── .rspec
├── spec
├── dummy
│ ├── public
│ │ ├── favicon.ico
│ │ ├── 422.html
│ │ ├── 404.html
│ │ └── 500.html
│ ├── app
│ │ ├── views
│ │ │ ├── home
│ │ │ │ └── index.html.erb
│ │ │ └── layouts
│ │ │ │ └── application.html.erb
│ │ ├── controllers
│ │ │ ├── application_controller.rb
│ │ │ ├── custom_authorizations_controller.rb
│ │ │ ├── semi_protected_resources_controller.rb
│ │ │ ├── metal_controller.rb
│ │ │ ├── full_protected_resources_controller.rb
│ │ │ └── home_controller.rb
│ │ ├── helpers
│ │ │ └── application_helper.rb
│ │ └── models
│ │ │ └── user.rb
│ ├── config
│ │ ├── locales
│ │ │ └── doorkeeper.en.yml
│ │ ├── environment.rb
│ │ ├── boot.rb
│ │ ├── database.yml
│ │ ├── initializers
│ │ │ ├── active_record_belongs_to_required_by_default.rb
│ │ │ ├── backtrace_silencers.rb
│ │ │ ├── session_store.rb
│ │ │ ├── wrap_parameters.rb
│ │ │ └── secret_token.rb
│ │ ├── application.rb
│ │ ├── environments
│ │ │ ├── development.rb
│ │ │ ├── test.rb
│ │ │ └── production.rb
│ │ └── routes.rb
│ ├── config.ru
│ ├── db
│ │ └── migrate
│ │ │ ├── 20120312140401_add_password_to_users.rb
│ │ │ ├── 20111122132257_create_users.rb
│ │ │ ├── 20151223200000_add_owner_to_application.rb
│ │ │ ├── 20160320211015_add_previous_refresh_token_to_access_tokens.rb
│ │ │ └── 20151223192035_create_doorkeeper_tables.rb
│ ├── Rakefile
│ └── script
│ │ └── rails
├── generators
│ ├── templates
│ │ └── routes.rb
│ ├── application_owner_generator_spec.rb
│ ├── views_generator_spec.rb
│ ├── install_generator_spec.rb
│ └── migration_generator_spec.rb
├── support
│ ├── dependencies
│ │ └── factory_girl.rb
│ ├── orm
│ │ └── active_record.rb
│ ├── helpers
│ │ ├── config_helper.rb
│ │ ├── access_token_request_helper.rb
│ │ ├── authorization_request_helper.rb
│ │ ├── model_helper.rb
│ │ ├── url_helper.rb
│ │ └── request_spec_helper.rb
│ ├── http_method_shim.rb
│ └── shared
│ │ ├── models_shared_examples.rb
│ │ └── controllers_shared_context.rb
├── spec_helper.rb
├── controllers
│ ├── application_metal_controller.rb
│ ├── token_info_controller_spec.rb
│ └── applications_controller_spec.rb
├── requests
│ ├── protected_resources
│ │ └── metal_spec.rb
│ ├── applications
│ │ ├── authorized_applications_spec.rb
│ │ └── applications_request_spec.rb
│ ├── flows
│ │ ├── implicit_grant_errors_spec.rb
│ │ ├── implicit_grant_spec.rb
│ │ ├── client_credentials_spec.rb
│ │ ├── skip_authorization_spec.rb
│ │ └── authorization_code_errors_spec.rb
│ └── endpoints
│ │ └── authorization_spec.rb
├── lib
│ ├── oauth
│ │ ├── helpers
│ │ │ ├── unique_token_spec.rb
│ │ │ └── scope_checker_spec.rb
│ │ ├── error_spec.rb
│ │ ├── forbidden_token_response_spec.rb
│ │ ├── client_credentials_integration_spec.rb
│ │ ├── base_response_spec.rb
│ │ ├── code_response_spec.rb
│ │ ├── code_request_spec.rb
│ │ ├── client_spec.rb
│ │ ├── authorization
│ │ │ └── uri_builder_spec.rb
│ │ ├── client_credentials
│ │ │ ├── creator_spec.rb
│ │ │ ├── validation_spec.rb
│ │ │ └── issuer_spec.rb
│ │ ├── invalid_token_response_spec.rb
│ │ ├── error_response_spec.rb
│ │ ├── authorization_code_request_spec.rb
│ │ └── token_response_spec.rb
│ ├── models
│ │ ├── scopes_spec.rb
│ │ ├── expirable_spec.rb
│ │ └── revocable_spec.rb
│ ├── request
│ │ └── strategy_spec.rb
│ └── server_spec.rb
├── helpers
│ └── doorkeeper
│ │ └── dashboard_helper_spec.rb
├── factories.rb
├── models
│ └── doorkeeper
│ │ └── access_grant_spec.rb
├── routing
│ ├── scoped_routes_spec.rb
│ └── default_routes_spec.rb
└── spec_helper_integration.rb
├── .coveralls.yml
├── lib
├── doorkeeper
│ ├── version.rb
│ ├── orm
│ │ ├── active_record
│ │ │ ├── access_grant.rb
│ │ │ ├── application.rb
│ │ │ └── access_token.rb
│ │ └── active_record.rb
│ ├── oauth
│ │ ├── error.rb
│ │ ├── client_credentials
│ │ │ ├── creator.rb
│ │ │ ├── issuer.rb
│ │ │ └── validation.rb
│ │ ├── helpers
│ │ │ ├── unique_token.rb
│ │ │ ├── uri_checker.rb
│ │ │ └── scope_checker.rb
│ │ ├── base_response.rb
│ │ ├── forbidden_token_response.rb
│ │ ├── client.rb
│ │ ├── token_response.rb
│ │ ├── code_request.rb
│ │ ├── authorization
│ │ │ ├── uri_builder.rb
│ │ │ ├── code.rb
│ │ │ └── token.rb
│ │ ├── invalid_token_response.rb
│ │ ├── client_credentials_request.rb
│ │ ├── client
│ │ │ └── credentials.rb
│ │ ├── token_request.rb
│ │ ├── code_response.rb
│ │ ├── password_access_token_request.rb
│ │ ├── base_request.rb
│ │ ├── scopes.rb
│ │ ├── authorization_code_request.rb
│ │ ├── error_response.rb
│ │ ├── pre_authorization.rb
│ │ └── token.rb
│ ├── models
│ │ ├── concerns
│ │ │ ├── accessible.rb
│ │ │ ├── scopes.rb
│ │ │ ├── ownership.rb
│ │ │ ├── expirable.rb
│ │ │ └── revocable.rb
│ │ ├── access_grant_mixin.rb
│ │ └── application_mixin.rb
│ ├── request
│ │ ├── strategy.rb
│ │ ├── code.rb
│ │ ├── token.rb
│ │ ├── client_credentials.rb
│ │ ├── refresh_token.rb
│ │ ├── authorization_code.rb
│ │ └── password.rb
│ ├── grape
│ │ ├── authorization_decorator.rb
│ │ └── helpers.rb
│ ├── validations.rb
│ ├── rails
│ │ ├── routes
│ │ │ ├── mapper.rb
│ │ │ └── mapping.rb
│ │ └── helpers.rb
│ ├── engine.rb
│ ├── errors.rb
│ ├── server.rb
│ ├── request.rb
│ └── helpers
│ │ └── controller.rb
├── generators
│ └── doorkeeper
│ │ ├── templates
│ │ ├── add_previous_refresh_token_to_access_tokens.rb
│ │ ├── add_owner_to_application_migration.rb
│ │ ├── README
│ │ └── migration.rb
│ │ ├── views_generator.rb
│ │ ├── install_generator.rb
│ │ ├── application_owner_generator.rb
│ │ ├── migration_generator.rb
│ │ └── previous_refresh_token_generator.rb
└── doorkeeper.rb
├── app
├── views
│ ├── doorkeeper
│ │ ├── applications
│ │ │ ├── new.html.erb
│ │ │ ├── edit.html.erb
│ │ │ ├── _delete_form.html.erb
│ │ │ ├── index.html.erb
│ │ │ ├── show.html.erb
│ │ │ └── _form.html.erb
│ │ ├── authorizations
│ │ │ ├── show.html.erb
│ │ │ ├── error.html.erb
│ │ │ └── new.html.erb
│ │ └── authorized_applications
│ │ │ ├── _delete_form.html.erb
│ │ │ └── index.html.erb
│ └── layouts
│ │ └── doorkeeper
│ │ ├── application.html.erb
│ │ └── admin.html.erb
├── assets
│ └── stylesheets
│ │ └── doorkeeper
│ │ ├── admin
│ │ └── application.css
│ │ └── application.css
├── controllers
│ └── doorkeeper
│ │ ├── application_controller.rb
│ │ ├── token_info_controller.rb
│ │ ├── application_metal_controller.rb
│ │ ├── authorized_applications_controller.rb
│ │ ├── applications_controller.rb
│ │ └── authorizations_controller.rb
├── helpers
│ └── doorkeeper
│ │ └── dashboard_helper.rb
└── validators
│ └── redirect_uri_validator.rb
├── .hound.yml
├── .gitignore
├── Gemfile
├── Appraisals
├── gemfiles
├── rails_4_2.gemfile
├── rails_5_0.gemfile
└── rails_5_1.gemfile
├── RELEASING.md
├── Rakefile
├── .travis.yml
├── SECURITY.md
├── MIT-LICENSE
├── doorkeeper.gemspec
└── CONTRIBUTING.md
/.rspec:
--------------------------------------------------------------------------------
1 | --colour
2 |
--------------------------------------------------------------------------------
/spec/dummy/public/favicon.ico:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/.coveralls.yml:
--------------------------------------------------------------------------------
1 | service_name: travis-ci
2 |
--------------------------------------------------------------------------------
/spec/dummy/app/views/home/index.html.erb:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/lib/doorkeeper/version.rb:
--------------------------------------------------------------------------------
1 | module Doorkeeper
2 | VERSION = "4.2.6".freeze
3 | end
4 |
--------------------------------------------------------------------------------
/spec/generators/templates/routes.rb:
--------------------------------------------------------------------------------
1 | Rails.application.routes.draw do
2 |
3 | end
4 |
--------------------------------------------------------------------------------
/spec/support/dependencies/factory_girl.rb:
--------------------------------------------------------------------------------
1 | require 'factory_girl'
2 | FactoryGirl.find_definitions
3 |
--------------------------------------------------------------------------------
/spec/dummy/app/controllers/application_controller.rb:
--------------------------------------------------------------------------------
1 | class ApplicationController < ActionController::Base
2 | protect_from_forgery
3 | end
4 |
--------------------------------------------------------------------------------
/spec/support/orm/active_record.rb:
--------------------------------------------------------------------------------
1 | # load schema to in memory sqlite
2 | ActiveRecord::Migration.verbose = false
3 | load Rails.root + 'db/schema.rb'
4 |
--------------------------------------------------------------------------------
/spec/dummy/config/locales/doorkeeper.en.yml:
--------------------------------------------------------------------------------
1 | en:
2 | doorkeeper:
3 | scopes:
4 | public: "Access your public data"
5 | write: "Update your data"
6 |
--------------------------------------------------------------------------------
/app/views/doorkeeper/applications/new.html.erb:
--------------------------------------------------------------------------------
1 |
4 |
5 | <%= render 'form', application: @application %>
6 |
--------------------------------------------------------------------------------
/spec/dummy/app/helpers/application_helper.rb:
--------------------------------------------------------------------------------
1 | module ApplicationHelper
2 | def current_user
3 | @current_user ||= User.find_by_id(session[:user_id])
4 | end
5 | end
6 |
--------------------------------------------------------------------------------
/app/views/doorkeeper/applications/edit.html.erb:
--------------------------------------------------------------------------------
1 |
4 |
5 | <%= render 'form', application: @application %>
6 |
--------------------------------------------------------------------------------
/spec/dummy/app/models/user.rb:
--------------------------------------------------------------------------------
1 | class User < ActiveRecord::Base
2 | def self.authenticate!(name, password)
3 | User.where(name: name, password: password).first
4 | end
5 | end
6 |
--------------------------------------------------------------------------------
/spec/dummy/config.ru:
--------------------------------------------------------------------------------
1 | # This file is used by Rack-based servers to start the application.
2 |
3 | require ::File.expand_path('../config/environment', __FILE__)
4 | run Dummy::Application
5 |
--------------------------------------------------------------------------------
/spec/dummy/db/migrate/20120312140401_add_password_to_users.rb:
--------------------------------------------------------------------------------
1 | class AddPasswordToUsers < ActiveRecord::Migration
2 | def change
3 | add_column :users, :password, :string
4 | end
5 | end
6 |
--------------------------------------------------------------------------------
/spec/dummy/config/environment.rb:
--------------------------------------------------------------------------------
1 | # Load the rails application
2 | require File.expand_path('../application', __FILE__)
3 |
4 | # Initialize the rails application
5 | Rails.application.initialize!
6 |
--------------------------------------------------------------------------------
/spec/spec_helper.rb:
--------------------------------------------------------------------------------
1 | $LOAD_PATH.unshift File.expand_path(File.join(File.dirname(__FILE__), '../lib'))
2 | $LOAD_PATH.unshift File.expand_path(File.join(File.dirname(__FILE__), '../app'))
3 |
4 | require 'doorkeeper'
5 |
--------------------------------------------------------------------------------
/.hound.yml:
--------------------------------------------------------------------------------
1 | AllCops:
2 | Exclude:
3 | - "spec/dummy/db/*"
4 |
5 | LineLength:
6 | Exclude:
7 | - spec/**/*
8 |
9 | StringLiterals:
10 | Enabled: false
11 |
12 | TrailingBlankLines:
13 | Enabled: true
14 |
--------------------------------------------------------------------------------
/app/assets/stylesheets/doorkeeper/admin/application.css:
--------------------------------------------------------------------------------
1 | /*
2 | *= require doorkeeper/bootstrap.min
3 | *
4 | *= require_self
5 | *= require_tree .
6 | */
7 |
8 | td {
9 | vertical-align: middle !important;
10 | }
11 |
--------------------------------------------------------------------------------
/app/views/doorkeeper/authorizations/show.html.erb:
--------------------------------------------------------------------------------
1 |
4 |
5 |
6 | <%= params[:code] %>
7 |
8 |
--------------------------------------------------------------------------------
/spec/dummy/db/migrate/20111122132257_create_users.rb:
--------------------------------------------------------------------------------
1 | class CreateUsers < ActiveRecord::Migration
2 | def change
3 | create_table :users do |t|
4 | t.string :name
5 |
6 | t.timestamps
7 | end
8 | end
9 | end
10 |
--------------------------------------------------------------------------------
/lib/doorkeeper/orm/active_record/access_grant.rb:
--------------------------------------------------------------------------------
1 | module Doorkeeper
2 | class AccessGrant < ActiveRecord::Base
3 | self.table_name = "#{table_name_prefix}oauth_access_grants#{table_name_suffix}".to_sym
4 |
5 | include AccessGrantMixin
6 | end
7 | end
8 |
--------------------------------------------------------------------------------
/app/views/doorkeeper/authorizations/error.html.erb:
--------------------------------------------------------------------------------
1 |
4 |
5 |
6 | <%= @pre_auth.error_response.body[:error_description] %>
7 |
8 |
--------------------------------------------------------------------------------
/spec/dummy/app/views/layouts/application.html.erb:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Dummy
5 | <%= csrf_meta_tags %>
6 |
7 |
8 |
9 | <%= link_to "Sign in", '/sign_in' %>
10 |
11 | <%= yield %>
12 |
13 |
14 |
15 |
--------------------------------------------------------------------------------
/spec/dummy/app/controllers/custom_authorizations_controller.rb:
--------------------------------------------------------------------------------
1 | class CustomAuthorizationsController < ::ApplicationController
2 | %w(index show new create edit update destroy).each do |action|
3 | define_method action do
4 | render nothing: true
5 | end
6 | end
7 | end
8 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | .bundle/
2 | .rbx
3 | *.rbc
4 | log/*.log
5 | pkg/
6 | spec/dummy/db/*.sqlite3
7 | spec/dummy/log/*.log
8 | spec/dummy/tmp/
9 | Gemfile.lock
10 | gemfiles/*.lock
11 | spec/generators/tmp
12 | .rvmrc
13 | *.swp
14 | .idea
15 | /.yardoc/
16 | /_yardoc/
17 | /doc/
18 | /rdoc/
19 | coverage
20 |
--------------------------------------------------------------------------------
/Gemfile:
--------------------------------------------------------------------------------
1 | source "https://rubygems.org"
2 |
3 | gem "rails", "~> 4.2.0"
4 |
5 | gem "appraisal"
6 |
7 | gem "activerecord-jdbcsqlite3-adapter", platform: :jruby
8 | gem "sqlite3", platform: [:ruby, :mswin, :mingw, :x64_mingw]
9 | gem 'tzinfo-data', platforms: [:mingw, :mswin, :x64_mingw]
10 | gemspec
11 |
--------------------------------------------------------------------------------
/spec/dummy/Rakefile:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env rake
2 | # Add your own tasks in files placed in lib/tasks ending in .rake,
3 | # for example lib/tasks/capistrano.rake, and they will automatically be available to Rake.
4 |
5 | require File.expand_path('../config/application', __FILE__)
6 |
7 | Dummy::Application.load_tasks
8 |
--------------------------------------------------------------------------------
/spec/controllers/application_metal_controller.rb:
--------------------------------------------------------------------------------
1 | require "spec_helper_integration"
2 |
3 | describe Doorkeeper::ApplicationMetalController do
4 | it "lazy run hooks" do
5 | i = 0
6 | ActiveSupport.on_load(:doorkeeper_metal_controller) { i += 1 }
7 |
8 | expect(i).to eq 1
9 | end
10 | end
11 |
--------------------------------------------------------------------------------
/spec/dummy/config/boot.rb:
--------------------------------------------------------------------------------
1 | require 'rubygems'
2 | require 'bundler/setup'
3 |
4 | orm = ENV['BUNDLE_GEMFILE'].match(/Gemfile\.(.+)\.rb/)
5 | unless defined?(DOORKEEPER_ORM)
6 | DOORKEEPER_ORM = (orm && orm[1]) || :active_record
7 | end
8 |
9 | $LOAD_PATH.unshift File.expand_path('../../../../lib', __FILE__)
10 |
--------------------------------------------------------------------------------
/spec/dummy/config/database.yml:
--------------------------------------------------------------------------------
1 | development:
2 | adapter: sqlite3
3 | database: db/development.sqlite3
4 | pool: 5
5 | timeout: 5000
6 |
7 | test:
8 | adapter: sqlite3
9 | database: ":memory:"
10 | timeout: 500
11 |
12 | production:
13 | adapter: sqlite3
14 | database: ":memory:"
15 | timeout: 500
16 |
--------------------------------------------------------------------------------
/app/controllers/doorkeeper/application_controller.rb:
--------------------------------------------------------------------------------
1 | module Doorkeeper
2 | class ApplicationController <
3 | Doorkeeper.configuration.base_controller.constantize
4 |
5 | include Helpers::Controller
6 |
7 | protect_from_forgery with: :exception
8 |
9 | helper 'doorkeeper/dashboard'
10 | end
11 | end
12 |
--------------------------------------------------------------------------------
/spec/dummy/script/rails:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env ruby
2 | # This command will automatically be run when you run "rails" with Rails 3 gems installed from the root of your application.
3 |
4 | APP_PATH = File.expand_path('../../config/application', __FILE__)
5 | require File.expand_path('../../config/boot', __FILE__)
6 | require 'rails/commands'
7 |
--------------------------------------------------------------------------------
/Appraisals:
--------------------------------------------------------------------------------
1 | appraise "rails-4-2" do
2 | gem "rails", "~> 4.2.0"
3 | end
4 |
5 | appraise "rails-5-0" do
6 | gem "rails", "~> 5.0.0"
7 | gem "rspec-rails", "~> 3.5"
8 | end
9 |
10 | appraise "rails-5-1" do
11 | gem "rails", github: "rails/rails"
12 | gem "arel", github: "rails/arel"
13 | gem "rspec-rails", "~> 3.5"
14 | end
15 |
--------------------------------------------------------------------------------
/spec/dummy/app/controllers/semi_protected_resources_controller.rb:
--------------------------------------------------------------------------------
1 | class SemiProtectedResourcesController < ApplicationController
2 | before_action :doorkeeper_authorize!, only: :index
3 |
4 | def index
5 | render plain: 'protected index'
6 | end
7 |
8 | def show
9 | render plain: 'non protected show'
10 | end
11 | end
12 |
--------------------------------------------------------------------------------
/lib/doorkeeper/oauth/error.rb:
--------------------------------------------------------------------------------
1 | module Doorkeeper
2 | module OAuth
3 | class Error < Struct.new(:name, :state)
4 | def description
5 | I18n.translate(
6 | name,
7 | scope: [:doorkeeper, :errors, :messages],
8 | default: :server_error
9 | )
10 | end
11 | end
12 | end
13 | end
14 |
--------------------------------------------------------------------------------
/spec/support/helpers/config_helper.rb:
--------------------------------------------------------------------------------
1 | module ConfigHelper
2 | def config_is_set(setting, value = nil, &block)
3 | setting_ivar = "@#{setting}"
4 | value = block_given? ? block : value
5 | Doorkeeper.configuration.instance_variable_set(setting_ivar, value)
6 | end
7 | end
8 |
9 | RSpec.configuration.send :include, ConfigHelper
10 |
--------------------------------------------------------------------------------
/app/views/doorkeeper/applications/_delete_form.html.erb:
--------------------------------------------------------------------------------
1 | <%- submit_btn_css ||= 'btn btn-link' %>
2 | <%= form_tag oauth_application_path(application), method: :delete do %>
3 | <%= submit_tag t('doorkeeper.applications.buttons.destroy'), onclick: "return confirm('#{ t('doorkeeper.applications.confirmations.destroy') }')", class: submit_btn_css %>
4 | <% end %>
5 |
--------------------------------------------------------------------------------
/lib/generators/doorkeeper/templates/add_previous_refresh_token_to_access_tokens.rb:
--------------------------------------------------------------------------------
1 | class AddPreviousRefreshTokenToAccessTokens < ActiveRecord::Migration
2 | def change
3 | add_column(
4 | :oauth_access_tokens,
5 | :previous_refresh_token,
6 | :string,
7 | default: "",
8 | null: false
9 | )
10 | end
11 | end
12 |
--------------------------------------------------------------------------------
/spec/dummy/db/migrate/20151223200000_add_owner_to_application.rb:
--------------------------------------------------------------------------------
1 | class AddOwnerToApplication < ActiveRecord::Migration
2 | def change
3 | add_column :oauth_applications, :owner_id, :integer, null: true
4 | add_column :oauth_applications, :owner_type, :string, null: true
5 | add_index :oauth_applications, [:owner_id, :owner_type]
6 | end
7 | end
8 |
--------------------------------------------------------------------------------
/spec/dummy/db/migrate/20160320211015_add_previous_refresh_token_to_access_tokens.rb:
--------------------------------------------------------------------------------
1 | class AddPreviousRefreshTokenToAccessTokens < ActiveRecord::Migration
2 | def change
3 | add_column(
4 | :oauth_access_tokens,
5 | :previous_refresh_token,
6 | :string,
7 | default: "",
8 | null: false
9 | )
10 | end
11 | end
12 |
--------------------------------------------------------------------------------
/spec/dummy/app/controllers/metal_controller.rb:
--------------------------------------------------------------------------------
1 | class MetalController < ActionController::Metal
2 | include AbstractController::Callbacks
3 | include ActionController::Head
4 | include Doorkeeper::Rails::Helpers
5 |
6 | before_action :doorkeeper_authorize!
7 |
8 | def index
9 | self.response_body = { ok: true }.to_json
10 | end
11 | end
12 |
--------------------------------------------------------------------------------
/lib/generators/doorkeeper/templates/add_owner_to_application_migration.rb:
--------------------------------------------------------------------------------
1 | class AddOwnerToApplication < ActiveRecord::Migration
2 | def change
3 | add_column :oauth_applications, :owner_id, :integer, null: true
4 | add_column :oauth_applications, :owner_type, :string, null: true
5 | add_index :oauth_applications, [:owner_id, :owner_type]
6 | end
7 | end
8 |
--------------------------------------------------------------------------------
/gemfiles/rails_4_2.gemfile:
--------------------------------------------------------------------------------
1 | # This file was generated by Appraisal
2 |
3 | source "https://rubygems.org"
4 |
5 | gem "rails", "~> 4.2.0"
6 | gem "appraisal"
7 | gem "activerecord-jdbcsqlite3-adapter", :platform => :jruby
8 | gem "sqlite3", :platform => [:ruby, :mswin, :mingw, :x64_mingw]
9 | gem "tzinfo-data", :platforms => [:mingw, :mswin, :x64_mingw]
10 |
11 | gemspec :path => "../"
12 |
--------------------------------------------------------------------------------
/spec/dummy/app/controllers/full_protected_resources_controller.rb:
--------------------------------------------------------------------------------
1 | class FullProtectedResourcesController < ApplicationController
2 | before_action -> { doorkeeper_authorize! :write, :admin }, only: :show
3 | before_action :doorkeeper_authorize!, only: :index
4 |
5 | def index
6 | render plain: 'index'
7 | end
8 |
9 | def show
10 | render plain: 'show'
11 | end
12 | end
13 |
--------------------------------------------------------------------------------
/app/views/doorkeeper/authorized_applications/_delete_form.html.erb:
--------------------------------------------------------------------------------
1 | <%- submit_btn_css ||= 'btn btn-link' %>
2 | <%= form_tag oauth_authorized_application_path(application), method: :delete do %>
3 | <%= submit_tag t('doorkeeper.authorized_applications.buttons.revoke'), onclick: "return confirm('#{ t('doorkeeper.authorized_applications.confirmations.revoke') }')", class: submit_btn_css %>
4 | <% end %>
5 |
--------------------------------------------------------------------------------
/lib/doorkeeper/models/concerns/accessible.rb:
--------------------------------------------------------------------------------
1 | module Doorkeeper
2 | module Models
3 | module Accessible
4 | # Indicates whether the object is accessible (not expired and not revoked).
5 | #
6 | # @return [Boolean] true if object accessible or false in other case
7 | #
8 | def accessible?
9 | !expired? && !revoked?
10 | end
11 | end
12 | end
13 | end
14 |
--------------------------------------------------------------------------------
/spec/dummy/config/initializers/active_record_belongs_to_required_by_default.rb:
--------------------------------------------------------------------------------
1 | # Require `belongs_to` associations by default. This is a new Rails 5.0
2 | # default, so it is introduced as a configuration option to ensure that apps
3 | # made on earlier versions of Rails are not affected when upgrading.
4 | if Rails.version.to_i >= 5
5 | Rails.application.config.active_record.belongs_to_required_by_default = true
6 | end
7 |
--------------------------------------------------------------------------------
/gemfiles/rails_5_0.gemfile:
--------------------------------------------------------------------------------
1 | # This file was generated by Appraisal
2 |
3 | source "https://rubygems.org"
4 |
5 | gem "rails", "~> 5.0.0"
6 | gem "appraisal"
7 | gem "activerecord-jdbcsqlite3-adapter", :platform => :jruby
8 | gem "sqlite3", :platform => [:ruby, :mswin, :mingw, :x64_mingw]
9 | gem "tzinfo-data", :platforms => [:mingw, :mswin, :x64_mingw]
10 | gem "rspec-rails", "~> 3.5"
11 |
12 | gemspec :path => "../"
13 |
--------------------------------------------------------------------------------
/lib/doorkeeper/request/strategy.rb:
--------------------------------------------------------------------------------
1 | module Doorkeeper
2 | module Request
3 | class Strategy
4 | attr_accessor :server
5 |
6 | delegate :authorize, to: :request
7 |
8 | def initialize(server)
9 | self.server = server
10 | end
11 |
12 | def request
13 | raise NotImplementedError, "request strategies must define #request"
14 | end
15 | end
16 | end
17 | end
18 |
--------------------------------------------------------------------------------
/RELEASING.md:
--------------------------------------------------------------------------------
1 | # Releasing doorkeeper
2 |
3 | How to release doorkeeper in five easy steps!
4 |
5 | 1. Update `lib/doorkeeper/version.rb` file accordingly.
6 | 2. Update `NEWS.md` to reflect the changes since last release.
7 | 3. Commit changes: `git commit -am 'Bump to vVERSION'`
8 | 4. Run `rake release`
9 | 5. Announce the new release, making sure to say “thank you” to the contributors
10 | who helped shape this version!
11 |
--------------------------------------------------------------------------------
/lib/doorkeeper/oauth/client_credentials/creator.rb:
--------------------------------------------------------------------------------
1 | module Doorkeeper
2 | module OAuth
3 | class ClientCredentialsRequest < BaseRequest
4 | class Creator
5 | def call(client, scopes, attributes = {})
6 | AccessToken.find_or_create_for(
7 | client, nil, scopes, attributes[:expires_in],
8 | attributes[:use_refresh_token])
9 | end
10 | end
11 | end
12 | end
13 | end
14 |
--------------------------------------------------------------------------------
/lib/doorkeeper/oauth/helpers/unique_token.rb:
--------------------------------------------------------------------------------
1 | module Doorkeeper
2 | module OAuth
3 | module Helpers
4 | module UniqueToken
5 | def self.generate(options = {})
6 | generator_method = options.delete(:generator) || SecureRandom.method(:hex)
7 | token_size = options.delete(:size) || 32
8 | generator_method.call(token_size)
9 | end
10 | end
11 | end
12 | end
13 | end
14 |
--------------------------------------------------------------------------------
/lib/doorkeeper/request/code.rb:
--------------------------------------------------------------------------------
1 | require 'doorkeeper/request/strategy'
2 |
3 | module Doorkeeper
4 | module Request
5 | class Code < Strategy
6 | delegate :current_resource_owner, to: :server
7 |
8 | def pre_auth
9 | server.context.send(:pre_auth)
10 | end
11 |
12 | def request
13 | @request ||= OAuth::CodeRequest.new(pre_auth, current_resource_owner)
14 | end
15 | end
16 | end
17 | end
18 |
--------------------------------------------------------------------------------
/lib/doorkeeper/request/token.rb:
--------------------------------------------------------------------------------
1 | require 'doorkeeper/request/strategy'
2 |
3 | module Doorkeeper
4 | module Request
5 | class Token < Strategy
6 | delegate :current_resource_owner, to: :server
7 |
8 | def pre_auth
9 | server.context.send(:pre_auth)
10 | end
11 |
12 | def request
13 | @request ||= OAuth::TokenRequest.new(pre_auth, current_resource_owner)
14 | end
15 | end
16 | end
17 | end
18 |
--------------------------------------------------------------------------------
/spec/support/helpers/access_token_request_helper.rb:
--------------------------------------------------------------------------------
1 | module AccessTokenRequestHelper
2 | def client_is_authorized(client, resource_owner, access_token_attributes = {})
3 | attributes = {
4 | application: client,
5 | resource_owner_id: resource_owner.id
6 | }.merge(access_token_attributes)
7 | FactoryGirl.create(:access_token, attributes)
8 | end
9 | end
10 |
11 | RSpec.configuration.send :include, AccessTokenRequestHelper
12 |
--------------------------------------------------------------------------------
/lib/doorkeeper/models/concerns/scopes.rb:
--------------------------------------------------------------------------------
1 | module Doorkeeper
2 | module Models
3 | module Scopes
4 | def scopes
5 | OAuth::Scopes.from_string(self[:scopes])
6 | end
7 |
8 | def scopes_string
9 | self[:scopes]
10 | end
11 |
12 | def includes_scope?(*required_scopes)
13 | required_scopes.blank? || required_scopes.any? { |s| scopes.exists?(s.to_s) }
14 | end
15 | end
16 | end
17 | end
18 |
--------------------------------------------------------------------------------
/lib/doorkeeper/request/client_credentials.rb:
--------------------------------------------------------------------------------
1 | require 'doorkeeper/request/strategy'
2 |
3 | module Doorkeeper
4 | module Request
5 | class ClientCredentials < Strategy
6 | delegate :client, :parameters, to: :server
7 |
8 | def request
9 | @request ||= OAuth::ClientCredentialsRequest.new(
10 | Doorkeeper.configuration,
11 | client,
12 | parameters
13 | )
14 | end
15 | end
16 | end
17 | end
18 |
--------------------------------------------------------------------------------
/spec/dummy/config/initializers/backtrace_silencers.rb:
--------------------------------------------------------------------------------
1 | # Be sure to restart your server when you modify this file.
2 |
3 | # You can add backtrace silencers for libraries that you're using but don't wish to see in your backtraces.
4 | # Rails.backtrace_cleaner.add_silencer { |line| line =~ /my_noisy_library/ }
5 |
6 | # You can also remove all the silencers if you're trying to debug a problem that might stem from framework code.
7 | # Rails.backtrace_cleaner.remove_silencers!
8 |
--------------------------------------------------------------------------------
/spec/dummy/config/initializers/session_store.rb:
--------------------------------------------------------------------------------
1 | # Be sure to restart your server when you modify this file.
2 |
3 | Dummy::Application.config.session_store :cookie_store, key: '_dummy_session'
4 |
5 | # Use the database for sessions instead of the cookie-based default,
6 | # which shouldn't be used to store highly confidential information
7 | # (create the session table with "rails generate session_migration")
8 | # Dummy::Application.config.session_store :active_record_store
9 |
--------------------------------------------------------------------------------
/gemfiles/rails_5_1.gemfile:
--------------------------------------------------------------------------------
1 | # This file was generated by Appraisal
2 |
3 | source "https://rubygems.org"
4 |
5 | gem "rails", :github => "rails/rails"
6 | gem "appraisal"
7 | gem "activerecord-jdbcsqlite3-adapter", :platform => :jruby
8 | gem "sqlite3", :platform => [:ruby, :mswin, :mingw, :x64_mingw]
9 | gem "tzinfo-data", :platforms => [:mingw, :mswin, :x64_mingw]
10 | gem "arel", :github => "rails/arel"
11 | gem "rspec-rails", "~> 3.5"
12 |
13 | gemspec :path => "../"
14 |
--------------------------------------------------------------------------------
/lib/doorkeeper/grape/authorization_decorator.rb:
--------------------------------------------------------------------------------
1 | module Doorkeeper
2 | module Grape
3 | class AuthorizationDecorator < SimpleDelegator
4 | def parameters
5 | params
6 | end
7 |
8 | def authorization
9 | env = __getobj__.env
10 | env['HTTP_AUTHORIZATION'] ||
11 | env['X-HTTP_AUTHORIZATION'] ||
12 | env['X_HTTP_AUTHORIZATION'] ||
13 | env['REDIRECT_X_HTTP_AUTHORIZATION']
14 | end
15 | end
16 | end
17 | end
18 |
--------------------------------------------------------------------------------
/lib/doorkeeper/oauth/base_response.rb:
--------------------------------------------------------------------------------
1 | module Doorkeeper
2 | module OAuth
3 | class BaseResponse
4 | def body
5 | {}
6 | end
7 |
8 | def description
9 | ""
10 | end
11 |
12 | def headers
13 | {}
14 | end
15 |
16 | def redirectable?
17 | false
18 | end
19 |
20 | def redirect_uri
21 | ""
22 | end
23 |
24 | def status
25 | :ok
26 | end
27 | end
28 | end
29 | end
30 |
--------------------------------------------------------------------------------
/Rakefile:
--------------------------------------------------------------------------------
1 | require 'bundler/setup'
2 | require 'rspec/core/rake_task'
3 |
4 | desc 'Default: run specs.'
5 | task default: :spec
6 |
7 | desc "Run all specs"
8 | RSpec::Core::RakeTask.new(:spec) do |config|
9 | config.verbose = false
10 | end
11 |
12 | namespace :doorkeeper do
13 | desc "Install doorkeeper in dummy app"
14 | task :install do
15 | cd 'spec/dummy'
16 | system 'bundle exec rails g doorkeeper:install --force'
17 | end
18 | end
19 |
20 | Bundler::GemHelper.install_tasks
21 |
--------------------------------------------------------------------------------
/spec/dummy/app/controllers/home_controller.rb:
--------------------------------------------------------------------------------
1 | class HomeController < ApplicationController
2 | def index
3 | end
4 |
5 | def sign_in
6 | session[:user_id] = if Rails.env.development?
7 | User.first || User.create!(name: 'Joe', password: 'sekret')
8 | else
9 | User.first
10 | end
11 | redirect_to '/'
12 | end
13 |
14 | def callback
15 | render plain: 'ok'
16 | end
17 | end
18 |
--------------------------------------------------------------------------------
/app/controllers/doorkeeper/token_info_controller.rb:
--------------------------------------------------------------------------------
1 | module Doorkeeper
2 | class TokenInfoController < Doorkeeper::ApplicationMetalController
3 | def show
4 | if doorkeeper_token && doorkeeper_token.accessible?
5 | render json: doorkeeper_token, status: :ok
6 | else
7 | error = OAuth::ErrorResponse.new(name: :invalid_request)
8 | response.headers.merge!(error.headers)
9 | render json: error.body, status: error.status
10 | end
11 | end
12 | end
13 | end
14 |
--------------------------------------------------------------------------------
/lib/generators/doorkeeper/views_generator.rb:
--------------------------------------------------------------------------------
1 | module Doorkeeper
2 | module Generators
3 | class ViewsGenerator < ::Rails::Generators::Base
4 | source_root File.expand_path('../../../../app/views', __FILE__)
5 |
6 | desc 'Copies default Doorkeeper views and layouts to your application.'
7 |
8 | def manifest
9 | directory 'doorkeeper', 'app/views/doorkeeper'
10 | directory 'layouts/doorkeeper', 'app/views/layouts/doorkeeper'
11 | end
12 | end
13 | end
14 | end
15 |
--------------------------------------------------------------------------------
/spec/requests/protected_resources/metal_spec.rb:
--------------------------------------------------------------------------------
1 | require 'spec_helper_integration'
2 |
3 | describe 'ActionController::Metal API' do
4 | before do
5 | @client = FactoryGirl.create(:application)
6 | @resource = User.create!(name: 'Joe', password: 'sekret')
7 | @token = client_is_authorized(@client, @resource)
8 | end
9 |
10 | it 'client requests protected resource with valid token' do
11 | get "/metal.json?access_token=#{@token.token}"
12 | should_have_json 'ok', true
13 | end
14 | end
15 |
--------------------------------------------------------------------------------
/lib/generators/doorkeeper/install_generator.rb:
--------------------------------------------------------------------------------
1 | class Doorkeeper::InstallGenerator < ::Rails::Generators::Base
2 | include Rails::Generators::Migration
3 | source_root File.expand_path('../templates', __FILE__)
4 | desc 'Installs Doorkeeper.'
5 |
6 | def install
7 | template 'initializer.rb', 'config/initializers/doorkeeper.rb'
8 | copy_file File.expand_path('../../../../config/locales/en.yml', __FILE__), 'config/locales/doorkeeper.en.yml'
9 | route 'use_doorkeeper'
10 | readme 'README'
11 | end
12 | end
13 |
--------------------------------------------------------------------------------
/app/controllers/doorkeeper/application_metal_controller.rb:
--------------------------------------------------------------------------------
1 | module Doorkeeper
2 | class ApplicationMetalController < ActionController::Metal
3 | MODULES = [
4 | ActionController::Instrumentation,
5 | AbstractController::Rendering,
6 | ActionController::Rendering,
7 | ActionController::Renderers::All,
8 | Helpers::Controller
9 | ].freeze
10 |
11 | MODULES.each do |mod|
12 | include mod
13 | end
14 |
15 | ActiveSupport.run_load_hooks(:doorkeeper_metal_controller, self)
16 | end
17 | end
18 |
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | cache: bundler
2 | language: ruby
3 | sudo: false
4 |
5 | rvm:
6 | - 2.1
7 | - 2.2.6
8 | - 2.3.3
9 | - 2.4.0
10 |
11 | before_install:
12 | - gem install bundler -v '~> 1.10'
13 |
14 | gemfile:
15 | - gemfiles/rails_4_2.gemfile
16 | - gemfiles/rails_5_0.gemfile
17 | - gemfiles/rails_5_1.gemfile
18 |
19 | matrix:
20 | exclude:
21 | - gemfile: gemfiles/rails_5_0.gemfile
22 | rvm: 2.1
23 | - gemfile: gemfiles/rails_5_1.gemfile
24 | rvm: 2.1
25 | allowed_failures:
26 | - gemfile: gemfiles/rails_5_1.gemfile
27 |
--------------------------------------------------------------------------------
/spec/dummy/config/initializers/wrap_parameters.rb:
--------------------------------------------------------------------------------
1 | # Be sure to restart your server when you modify this file.
2 | #
3 | # This file contains settings for ActionController::ParamsWrapper which
4 | # is enabled by default.
5 |
6 | # Enable parameter wrapping for JSON. You can disable this by setting :format to an empty array.
7 | ActiveSupport.on_load(:action_controller) do
8 | wrap_parameters format: [:json]
9 | end
10 |
11 | # Disable root element in JSON by default.
12 | ActiveSupport.on_load(:active_record) do
13 | self.include_root_in_json = false
14 | end
15 |
--------------------------------------------------------------------------------
/app/helpers/doorkeeper/dashboard_helper.rb:
--------------------------------------------------------------------------------
1 | module Doorkeeper
2 | module DashboardHelper
3 | def doorkeeper_errors_for(object, method)
4 | if object.errors[method].present?
5 | object.errors[method].map do |msg|
6 | content_tag(:span, class: 'help-block') do
7 | msg.capitalize
8 | end
9 | end.join.html_safe
10 | end
11 | end
12 |
13 | def doorkeeper_submit_path(application)
14 | application.persisted? ? oauth_application_path(application) : oauth_applications_path
15 | end
16 | end
17 | end
18 |
--------------------------------------------------------------------------------
/lib/doorkeeper/request/refresh_token.rb:
--------------------------------------------------------------------------------
1 | require 'doorkeeper/request/strategy'
2 |
3 | module Doorkeeper
4 | module Request
5 | class RefreshToken < Strategy
6 | delegate :credentials, :parameters, to: :server
7 |
8 | def refresh_token
9 | AccessToken.by_refresh_token(parameters[:refresh_token])
10 | end
11 |
12 | def request
13 | @request ||= OAuth::RefreshTokenRequest.new(
14 | Doorkeeper.configuration,
15 | refresh_token, credentials,
16 | parameters
17 | )
18 | end
19 | end
20 | end
21 | end
22 |
--------------------------------------------------------------------------------
/app/controllers/doorkeeper/authorized_applications_controller.rb:
--------------------------------------------------------------------------------
1 | module Doorkeeper
2 | class AuthorizedApplicationsController < Doorkeeper::ApplicationController
3 | before_action :authenticate_resource_owner!
4 |
5 | def index
6 | @applications = Application.authorized_for(current_resource_owner)
7 | end
8 |
9 | def destroy
10 | AccessToken.revoke_all_for params[:id], current_resource_owner
11 | redirect_to oauth_authorized_applications_url, notice: I18n.t(:notice, scope: [:doorkeeper, :flash, :authorized_applications, :destroy])
12 | end
13 | end
14 | end
15 |
--------------------------------------------------------------------------------
/lib/doorkeeper/request/authorization_code.rb:
--------------------------------------------------------------------------------
1 | require 'doorkeeper/request/strategy'
2 |
3 | module Doorkeeper
4 | module Request
5 | class AuthorizationCode < Strategy
6 | delegate :client, :parameters, to: :server
7 |
8 | def request
9 | @request ||= OAuth::AuthorizationCodeRequest.new(
10 | Doorkeeper.configuration,
11 | grant,
12 | client,
13 | parameters
14 | )
15 | end
16 |
17 | private
18 |
19 | def grant
20 | AccessGrant.by_token(parameters[:code])
21 | end
22 | end
23 | end
24 | end
25 |
--------------------------------------------------------------------------------
/spec/dummy/config/initializers/secret_token.rb:
--------------------------------------------------------------------------------
1 | # Be sure to restart your server when you modify this file.
2 |
3 | # Your secret key for verifying the integrity of signed cookies.
4 | # If you change this key, all old signed cookies will become invalid!
5 | # Make sure the secret is at least 30 characters and all random,
6 | # no regular words or you'll be exposed to dictionary attacks.
7 | Dummy::Application.config.secret_key_base =
8 | Dummy::Application.config.secret_token =
9 | 'c00157b5a1bb6181792f0f4a8a080485de7bab9987e6cf159dc74c4f0573345c1bfa713b5d756e1491fc0b098567e8a619e2f8d268eda86a20a720d05d633780'
10 |
--------------------------------------------------------------------------------
/lib/generators/doorkeeper/templates/README:
--------------------------------------------------------------------------------
1 | ===============================================================================
2 |
3 | There is a setup that you need to do before you can use doorkeeper.
4 |
5 | Step 1.
6 | Go to config/initializers/doorkeeper.rb and configure
7 | resource_owner_authenticator block.
8 |
9 | Step 2.
10 | Choose the ORM:
11 |
12 | If you want to use ActiveRecord run:
13 |
14 | rails generate doorkeeper:migration
15 |
16 | And run
17 |
18 | rake db:migrate
19 |
20 | Step 3.
21 | That's it, that's all. Enjoy!
22 |
23 | ===============================================================================
24 |
25 |
--------------------------------------------------------------------------------
/spec/lib/oauth/helpers/unique_token_spec.rb:
--------------------------------------------------------------------------------
1 | require 'spec_helper'
2 | require 'doorkeeper/oauth/helpers/unique_token'
3 |
4 | module Doorkeeper::OAuth::Helpers
5 | describe UniqueToken do
6 | let :generator do
7 | ->(size) { 'a' * size }
8 | end
9 |
10 | it 'is able to customize the generator method' do
11 | token = UniqueToken.generate(generator: generator)
12 | expect(token).to eq('a' * 32)
13 | end
14 |
15 | it 'is able to customize the size of the token' do
16 | token = UniqueToken.generate(generator: generator, size: 2)
17 | expect(token).to eq('aa')
18 | end
19 | end
20 | end
21 |
--------------------------------------------------------------------------------
/lib/doorkeeper/models/concerns/ownership.rb:
--------------------------------------------------------------------------------
1 | module Doorkeeper
2 | module Models
3 | module Ownership
4 | extend ActiveSupport::Concern
5 |
6 | included do
7 | belongs_to_options = { polymorphic: true }
8 | if defined?(ActiveRecord::Base) && ActiveRecord::VERSION::MAJOR >= 5
9 | belongs_to_options[:optional] = true
10 | end
11 |
12 | belongs_to :owner, belongs_to_options
13 | validates :owner, presence: true, if: :validate_owner?
14 | end
15 |
16 | def validate_owner?
17 | Doorkeeper.configuration.confirm_application_owner?
18 | end
19 | end
20 | end
21 | end
22 |
--------------------------------------------------------------------------------
/lib/generators/doorkeeper/application_owner_generator.rb:
--------------------------------------------------------------------------------
1 | require 'rails/generators/active_record'
2 |
3 | class Doorkeeper::ApplicationOwnerGenerator < Rails::Generators::Base
4 | include Rails::Generators::Migration
5 | source_root File.expand_path('../templates', __FILE__)
6 | desc 'Provide support for client application ownership.'
7 |
8 | def application_owner
9 | migration_template(
10 | 'add_owner_to_application_migration.rb',
11 | 'db/migrate/add_owner_to_application.rb'
12 | )
13 | end
14 |
15 | def self.next_migration_number(dirname)
16 | ActiveRecord::Generators::Base.next_migration_number(dirname)
17 | end
18 | end
19 |
--------------------------------------------------------------------------------
/app/views/layouts/doorkeeper/application.html.erb:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | <%= t('doorkeeper.layouts.application.title') %>
5 |
6 |
7 |
8 |
9 | <%= stylesheet_link_tag "doorkeeper/application" %>
10 | <%= csrf_meta_tags %>
11 |
12 |
13 |
14 | <%- if flash[:notice].present? %>
15 |
16 | <%= flash[:notice] %>
17 |
18 | <% end -%>
19 |
20 | <%= yield %>
21 |
22 |
23 |
24 |
--------------------------------------------------------------------------------
/SECURITY.md:
--------------------------------------------------------------------------------
1 | # Reporting security issues in Doorkeeper
2 |
3 | Hello! Thank you for wanting to disclose a possible security
4 | vulnerability within the Doorkeeper gem! Please follow our disclosure
5 | policy as outlined below:
6 |
7 | 1. Do NOT open up a GitHub issue with your report. Security reports
8 | should be kept private until a possible fix is determined.
9 | 2. Send an email to Jon Moss, Doorkeeper's maintainer, at doorkeeper AT jonathanmoss.me. You should receive a prompt response.
10 | 3. Be patient. Since Doorkeeper is in a stable maintenance phase, we want to
11 | do as little as possible to rock the boat of the project.
12 |
13 | Thank you very much for adhering for these policies!
14 |
--------------------------------------------------------------------------------
/lib/doorkeeper/request/password.rb:
--------------------------------------------------------------------------------
1 | require 'doorkeeper/request/strategy'
2 |
3 | module Doorkeeper
4 | module Request
5 | class Password < Strategy
6 | delegate :credentials, :resource_owner, :parameters, to: :server
7 |
8 | def request
9 | @request ||= OAuth::PasswordAccessTokenRequest.new(
10 | Doorkeeper.configuration,
11 | client,
12 | resource_owner,
13 | parameters
14 | )
15 | end
16 |
17 | private
18 |
19 | def client
20 | if credentials
21 | server.client
22 | elsif parameters[:client_id]
23 | server.client_via_uid
24 | end
25 | end
26 | end
27 | end
28 | end
29 |
--------------------------------------------------------------------------------
/spec/lib/oauth/error_spec.rb:
--------------------------------------------------------------------------------
1 | require 'spec_helper'
2 | require 'active_support/i18n'
3 | require 'doorkeeper/oauth/error'
4 |
5 | module Doorkeeper::OAuth
6 | describe Error do
7 | subject(:error) { Error.new(:some_error, :some_state) }
8 |
9 | it { expect(subject).to respond_to(:name) }
10 | it { expect(subject).to respond_to(:state) }
11 |
12 | describe :description do
13 | it 'is translated from translation messages' do
14 | expect(I18n).to receive(:translate).with(
15 | :some_error,
16 | scope: [:doorkeeper, :errors, :messages],
17 | default: :server_error
18 | )
19 | error.description
20 | end
21 | end
22 | end
23 | end
24 |
--------------------------------------------------------------------------------
/lib/doorkeeper/validations.rb:
--------------------------------------------------------------------------------
1 | module Doorkeeper
2 | module Validations
3 | extend ActiveSupport::Concern
4 |
5 | attr_accessor :error
6 |
7 | def validate
8 | @error = nil
9 | self.class.validations.each do |validation|
10 | break if @error
11 | @error = validation.last unless send("validate_#{validation.first}")
12 | end
13 | end
14 |
15 | def valid?
16 | validate
17 | @error.nil?
18 | end
19 |
20 | module ClassMethods
21 | def validate(attribute, options = {})
22 | validations << [attribute, options[:error]]
23 | end
24 |
25 | def validations
26 | @validations ||= []
27 | end
28 | end
29 | end
30 | end
31 |
--------------------------------------------------------------------------------
/spec/lib/oauth/forbidden_token_response_spec.rb:
--------------------------------------------------------------------------------
1 | require 'spec_helper'
2 | require 'active_model'
3 | require 'doorkeeper'
4 | require 'doorkeeper/oauth/forbidden_token_response'
5 |
6 | module Doorkeeper::OAuth
7 | describe ForbiddenTokenResponse do
8 | describe '#name' do
9 | it { expect(subject.name).to eq(:invalid_scope) }
10 | end
11 |
12 | describe '#status' do
13 | it { expect(subject.status).to eq(:forbidden) }
14 | end
15 |
16 | describe :from_scopes do
17 | it 'should have a list of acceptable scopes' do
18 | response = ForbiddenTokenResponse.from_scopes(["public"])
19 | expect(response.description).to include('public')
20 | end
21 | end
22 | end
23 | end
24 |
--------------------------------------------------------------------------------
/lib/doorkeeper/rails/routes/mapper.rb:
--------------------------------------------------------------------------------
1 | module Doorkeeper
2 | module Rails
3 | class Routes # :nodoc:
4 | class Mapper
5 | def initialize
6 | @mapping = Mapping.new
7 | end
8 |
9 | def map(&block)
10 | instance_eval(&block) if block
11 | @mapping
12 | end
13 |
14 | def controllers(controller_names = {})
15 | @mapping.controllers.merge!(controller_names)
16 | end
17 |
18 | def skip_controllers(*controller_names)
19 | @mapping.skips = controller_names
20 | end
21 |
22 | def as(alias_names = {})
23 | @mapping.as.merge!(alias_names)
24 | end
25 | end
26 | end
27 | end
28 | end
29 |
--------------------------------------------------------------------------------
/spec/dummy/config/application.rb:
--------------------------------------------------------------------------------
1 | require File.expand_path('../boot', __FILE__)
2 |
3 | require 'rails/all'
4 |
5 | Bundler.require(*Rails.groups)
6 |
7 | require 'yaml'
8 |
9 | orm = if DOORKEEPER_ORM =~ /mongoid/
10 | Mongoid.load!(File.join(File.dirname(File.expand_path(__FILE__)), "#{DOORKEEPER_ORM}.yml"))
11 | :mongoid
12 | else
13 | DOORKEEPER_ORM
14 | end
15 | require "#{orm}/railtie"
16 |
17 | module Dummy
18 | class Application < Rails::Application
19 | # Settings in config/environments/* take precedence over those specified here.
20 | # Application configuration should go into files in config/initializers
21 | # -- all .rb files in that directory are automatically loaded.
22 | end
23 | end
24 |
--------------------------------------------------------------------------------
/lib/generators/doorkeeper/migration_generator.rb:
--------------------------------------------------------------------------------
1 | require 'rails/generators/active_record'
2 |
3 | class Doorkeeper::MigrationGenerator < ::Rails::Generators::Base
4 | include Rails::Generators::Migration
5 | source_root File.expand_path('../templates', __FILE__)
6 | desc 'Installs Doorkeeper migration file.'
7 |
8 | def install
9 | migration_template(
10 | 'migration.rb',
11 | 'db/migrate/create_doorkeeper_tables.rb',
12 | migration_version: migration_version
13 | )
14 | end
15 |
16 | def self.next_migration_number(dirname)
17 | ActiveRecord::Generators::Base.next_migration_number(dirname)
18 | end
19 |
20 | def migration_version
21 | if Rails.version >= "5.0.0"
22 | "[#{Rails::VERSION::MAJOR}.#{Rails::VERSION::MINOR}]"
23 | end
24 | end
25 | end
26 |
--------------------------------------------------------------------------------
/spec/helpers/doorkeeper/dashboard_helper_spec.rb:
--------------------------------------------------------------------------------
1 | require 'spec_helper_integration'
2 |
3 | describe Doorkeeper::DashboardHelper do
4 | describe '.doorkeeper_errors_for' do
5 | let(:object) { double errors: { method: messages } }
6 | let(:messages) { ['first message', 'second message'] }
7 |
8 | context 'when object has errors' do
9 | it 'returns error messages' do
10 | messages.each do |message|
11 | expect(helper.doorkeeper_errors_for(object, :method)).to include(
12 | message.capitalize
13 | )
14 | end
15 | end
16 | end
17 |
18 | context 'when object has no errors' do
19 | it 'returns nil' do
20 | expect(helper.doorkeeper_errors_for(object, :amonter_method)).to be_nil
21 | end
22 | end
23 | end
24 | end
25 |
--------------------------------------------------------------------------------
/spec/dummy/public/422.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | The change you wanted was rejected (422)
5 |
17 |
18 |
19 |
20 |
21 |
22 |
The change you wanted was rejected.
23 |
Maybe you tried to change something you didn't have access to.
24 |
25 |
26 |
27 |
--------------------------------------------------------------------------------
/lib/doorkeeper/oauth/forbidden_token_response.rb:
--------------------------------------------------------------------------------
1 | module Doorkeeper
2 | module OAuth
3 | class ForbiddenTokenResponse < ErrorResponse
4 | def self.from_scopes(scopes, attributes = {})
5 | new(attributes.merge(scopes: scopes))
6 | end
7 |
8 | def initialize(attributes = {})
9 | super(attributes.merge(name: :invalid_scope, state: :forbidden))
10 | @scopes = attributes[:scopes]
11 | end
12 |
13 | def status
14 | :forbidden
15 | end
16 |
17 | def headers
18 | headers = super
19 | headers.delete 'WWW-Authenticate'
20 | headers
21 | end
22 |
23 | def description
24 | scope = { scope: [:doorkeeper, :scopes] }
25 | @description ||= @scopes.map { |r| I18n.translate r, scope }.join('\n')
26 | end
27 | end
28 | end
29 | end
30 |
--------------------------------------------------------------------------------
/spec/dummy/public/404.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | The page you were looking for doesn't exist (404)
5 |
17 |
18 |
19 |
20 |
21 |
22 |
The page you were looking for doesn't exist.
23 |
You may have mistyped the address or the page may have moved.
24 |
25 |
26 |
27 |
--------------------------------------------------------------------------------
/spec/dummy/public/500.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | We're sorry, but something went wrong (500)
5 |
17 |
18 |
19 |
20 |
21 |
22 |
We're sorry, but something went wrong.
23 |
We've been notified about this issue and we'll take a look at it shortly.
24 |
25 |
26 |
27 |
--------------------------------------------------------------------------------
/lib/doorkeeper/oauth/client.rb:
--------------------------------------------------------------------------------
1 | require 'doorkeeper/oauth/client/credentials'
2 |
3 | module Doorkeeper
4 | module OAuth
5 | class Client
6 | attr_accessor :application
7 |
8 | delegate :id, :name, :uid, :redirect_uri, :scopes, to: :@application
9 |
10 | def initialize(application)
11 | @application = application
12 | end
13 |
14 | def self.find(uid, method = Application.method(:by_uid))
15 | if application = method.call(uid)
16 | new(application)
17 | end
18 | end
19 |
20 | def self.authenticate(credentials, method = Application.method(:by_uid_and_secret))
21 | return false if credentials.blank?
22 |
23 | if application = method.call(credentials.uid, credentials.secret)
24 | new(application)
25 | end
26 | end
27 | end
28 | end
29 | end
30 |
--------------------------------------------------------------------------------
/spec/generators/application_owner_generator_spec.rb:
--------------------------------------------------------------------------------
1 | require 'spec_helper_integration'
2 | require 'generators/doorkeeper/application_owner_generator'
3 |
4 | describe 'Doorkeeper::ApplicationOwnerGenerator' do
5 | include GeneratorSpec::TestCase
6 |
7 | tests Doorkeeper::ApplicationOwnerGenerator
8 | destination ::File.expand_path('../tmp/dummy', __FILE__)
9 |
10 | describe 'after running the generator' do
11 | before :each do
12 | prepare_destination
13 | FileUtils.mkdir(::File.expand_path('config', Pathname(destination_root)))
14 | FileUtils.copy_file(::File.expand_path('../templates/routes.rb', __FILE__), ::File.expand_path('config/routes.rb', Pathname.new(destination_root)))
15 | run_generator
16 | end
17 |
18 | it 'creates a migration' do
19 | assert_migration 'db/migrate/add_owner_to_application.rb'
20 | end
21 | end
22 | end
23 |
--------------------------------------------------------------------------------
/app/views/doorkeeper/authorized_applications/index.html.erb:
--------------------------------------------------------------------------------
1 |
4 |
5 |
6 |
7 |
8 |
9 | | <%= t('doorkeeper.authorized_applications.index.application') %> |
10 | <%= t('doorkeeper.authorized_applications.index.created_at') %> |
11 | |
12 | |
13 |
14 |
15 |
16 | <% @applications.each do |application| %>
17 |
18 | | <%= application.name %> |
19 | <%= application.created_at.strftime(t('doorkeeper.authorized_applications.index.date_format')) %> |
20 | <%= render 'delete_form', application: application %> |
21 |
22 | <% end %>
23 |
24 |
25 |
26 |
--------------------------------------------------------------------------------
/lib/doorkeeper/oauth/token_response.rb:
--------------------------------------------------------------------------------
1 | module Doorkeeper
2 | module OAuth
3 | class TokenResponse
4 | attr_accessor :token
5 |
6 | def initialize(token)
7 | @token = token
8 | end
9 |
10 | def body
11 | {
12 | 'access_token' => token.token,
13 | 'token_type' => token.token_type,
14 | 'expires_in' => token.expires_in_seconds,
15 | 'refresh_token' => token.refresh_token,
16 | 'scope' => token.scopes_string,
17 | 'created_at' => token.created_at.to_i
18 | }.reject { |_, value| value.blank? }
19 | end
20 |
21 | def status
22 | :ok
23 | end
24 |
25 | def headers
26 | { 'Cache-Control' => 'no-store',
27 | 'Pragma' => 'no-cache',
28 | 'Content-Type' => 'application/json; charset=utf-8' }
29 | end
30 | end
31 | end
32 | end
33 |
--------------------------------------------------------------------------------
/spec/lib/oauth/client_credentials_integration_spec.rb:
--------------------------------------------------------------------------------
1 | require 'spec_helper_integration'
2 |
3 | module Doorkeeper::OAuth
4 | describe ClientCredentialsRequest do
5 | let(:server) { Doorkeeper.configuration }
6 |
7 | context 'with a valid request' do
8 | let(:client) { FactoryGirl.create :application }
9 |
10 | it 'issues an access token' do
11 | request = ClientCredentialsRequest.new(server, client, {})
12 | expect do
13 | request.authorize
14 | end.to change { Doorkeeper::AccessToken.count }.by(1)
15 | end
16 | end
17 |
18 | describe 'with an invalid request' do
19 | it 'does not issue an access token' do
20 | request = ClientCredentialsRequest.new(server, nil, {})
21 | expect do
22 | request.authorize
23 | end.to_not change { Doorkeeper::AccessToken.count }
24 | end
25 | end
26 | end
27 | end
28 |
--------------------------------------------------------------------------------
/lib/doorkeeper/engine.rb:
--------------------------------------------------------------------------------
1 | module Doorkeeper
2 | class Engine < Rails::Engine
3 | initializer "doorkeeper.params.filter" do |app|
4 | parameters = %w(client_secret code authentication_token access_token refresh_token)
5 | app.config.filter_parameters << /^(#{Regexp.union parameters})$/
6 | end
7 |
8 | initializer "doorkeeper.routes" do
9 | Doorkeeper::Rails::Routes.install!
10 | end
11 |
12 | initializer "doorkeeper.helpers" do
13 | ActiveSupport.on_load(:action_controller) do
14 | include Doorkeeper::Rails::Helpers
15 | end
16 | end
17 |
18 | if defined?(Sprockets) && Sprockets::VERSION.chr.to_i >= 4
19 | initializer 'doorkeeper.assets.precompile' do |app|
20 | app.config.assets.precompile += %w(
21 | doorkeeper/application.css
22 | doorkeeper/admin/application.css
23 | )
24 | end
25 | end
26 | end
27 | end
28 |
--------------------------------------------------------------------------------
/lib/doorkeeper/orm/active_record.rb:
--------------------------------------------------------------------------------
1 | module Doorkeeper
2 | module Orm
3 | module ActiveRecord
4 | def self.initialize_models!
5 | require 'doorkeeper/orm/active_record/access_grant'
6 | require 'doorkeeper/orm/active_record/access_token'
7 | require 'doorkeeper/orm/active_record/application'
8 |
9 | if Doorkeeper.configuration.active_record_options[:establish_connection]
10 | [Doorkeeper::AccessGrant, Doorkeeper::AccessToken, Doorkeeper::Application].each do |c|
11 | c.send :establish_connection, Doorkeeper.configuration.active_record_options[:establish_connection]
12 | end
13 | end
14 | end
15 |
16 | def self.initialize_application_owner!
17 | require 'doorkeeper/models/concerns/ownership'
18 |
19 | Doorkeeper::Application.send :include, Doorkeeper::Models::Ownership
20 | end
21 | end
22 | end
23 | end
24 |
--------------------------------------------------------------------------------
/lib/doorkeeper/oauth/code_request.rb:
--------------------------------------------------------------------------------
1 | module Doorkeeper
2 | module OAuth
3 | class CodeRequest
4 | attr_accessor :pre_auth, :resource_owner, :client
5 |
6 | def initialize(pre_auth, resource_owner)
7 | @pre_auth = pre_auth
8 | @client = pre_auth.client
9 | @resource_owner = resource_owner
10 | end
11 |
12 | def authorize
13 | if pre_auth.authorizable?
14 | auth = Authorization::Code.new(pre_auth, resource_owner)
15 | auth.issue_token
16 | @response = CodeResponse.new pre_auth, auth
17 | else
18 | @response = ErrorResponse.from_request pre_auth
19 | end
20 | end
21 |
22 | def deny
23 | pre_auth.error = :access_denied
24 | ErrorResponse.from_request pre_auth,
25 | redirect_uri: pre_auth.redirect_uri
26 | end
27 | end
28 | end
29 | end
30 |
--------------------------------------------------------------------------------
/spec/factories.rb:
--------------------------------------------------------------------------------
1 | FactoryGirl.define do
2 | factory :access_grant, class: Doorkeeper::AccessGrant do
3 | sequence(:resource_owner_id) { |n| n }
4 | application
5 | redirect_uri 'https://app.com/callback'
6 | expires_in 100
7 | scopes 'public write'
8 | end
9 |
10 | factory :access_token, class: Doorkeeper::AccessToken do
11 | sequence(:resource_owner_id) { |n| n }
12 | application
13 | expires_in 2.hours
14 |
15 | factory :clientless_access_token do
16 | application nil
17 | end
18 | end
19 |
20 | factory :application, class: Doorkeeper::Application do
21 | sequence(:name) { |n| "Application #{n}" }
22 | redirect_uri 'https://app.com/callback'
23 | end
24 |
25 | # do not name this factory :user, otherwise it will conflict with factories
26 | # from applications that use doorkeeper factories in their own tests
27 | factory :doorkeeper_testing_user, class: :user
28 | end
29 |
--------------------------------------------------------------------------------
/app/views/doorkeeper/applications/index.html.erb:
--------------------------------------------------------------------------------
1 |
4 |
5 | <%= link_to t('.new'), new_oauth_application_path, class: 'btn btn-success' %>
6 |
7 |
8 |
9 |
10 | | <%= t('.name') %> |
11 | <%= t('.callback_url') %> |
12 | |
13 | |
14 |
15 |
16 |
17 | <% @applications.each do |application| %>
18 |
19 | | <%= link_to application.name, oauth_application_path(application) %> |
20 | <%= application.redirect_uri %> |
21 | <%= link_to t('doorkeeper.applications.buttons.edit'), edit_oauth_application_path(application), class: 'btn btn-link' %> |
22 | <%= render 'delete_form', application: application %> |
23 |
24 | <% end %>
25 |
26 |
27 |
--------------------------------------------------------------------------------
/lib/doorkeeper/oauth/authorization/uri_builder.rb:
--------------------------------------------------------------------------------
1 | require 'rack/utils'
2 |
3 | module Doorkeeper
4 | module OAuth
5 | module Authorization
6 | class URIBuilder
7 | class << self
8 | def uri_with_query(url, parameters = {})
9 | uri = URI.parse(url)
10 | original_query = Rack::Utils.parse_query(uri.query)
11 | uri.query = build_query(original_query.merge(parameters))
12 | uri.to_s
13 | end
14 |
15 | def uri_with_fragment(url, parameters = {})
16 | uri = URI.parse(url)
17 | uri.fragment = build_query(parameters)
18 | uri.to_s
19 | end
20 |
21 | private
22 |
23 | def build_query(parameters = {})
24 | parameters = parameters.reject { |_, v| v.blank? }
25 | Rack::Utils.build_query parameters
26 | end
27 | end
28 | end
29 | end
30 | end
31 | end
32 |
--------------------------------------------------------------------------------
/lib/generators/doorkeeper/previous_refresh_token_generator.rb:
--------------------------------------------------------------------------------
1 | require 'rails/generators/active_record'
2 |
3 | class Doorkeeper::PreviousRefreshTokenGenerator < Rails::Generators::Base
4 | include Rails::Generators::Migration
5 | source_root File.expand_path('../templates', __FILE__)
6 | desc 'Support revoke refresh token on access token use'
7 |
8 | def self.next_migration_number(path)
9 | ActiveRecord::Generators::Base.next_migration_number(path)
10 | end
11 |
12 | def previous_refresh_token
13 | if no_previous_refresh_token_column?
14 | migration_template(
15 | 'add_previous_refresh_token_to_access_tokens.rb',
16 | 'db/migrate/add_previous_refresh_token_to_access_tokens.rb'
17 | )
18 | end
19 | end
20 |
21 | private
22 |
23 | def no_previous_refresh_token_column?
24 | !ActiveRecord::Base.connection.column_exists?(
25 | :oauth_access_tokens,
26 | :previous_refresh_token
27 | )
28 | end
29 | end
30 |
--------------------------------------------------------------------------------
/lib/doorkeeper/oauth/authorization/code.rb:
--------------------------------------------------------------------------------
1 | module Doorkeeper
2 | module OAuth
3 | module Authorization
4 | class Code
5 | attr_accessor :pre_auth, :resource_owner, :token
6 |
7 | def initialize(pre_auth, resource_owner)
8 | @pre_auth = pre_auth
9 | @resource_owner = resource_owner
10 | end
11 |
12 | def issue_token
13 | @token ||= AccessGrant.create!(
14 | application_id: pre_auth.client.id,
15 | resource_owner_id: resource_owner.id,
16 | expires_in: configuration.authorization_code_expires_in,
17 | redirect_uri: pre_auth.redirect_uri,
18 | scopes: pre_auth.scopes.to_s
19 | )
20 | end
21 |
22 | def native_redirect
23 | { action: :show, code: token.token }
24 | end
25 |
26 | def configuration
27 | Doorkeeper.configuration
28 | end
29 | end
30 | end
31 | end
32 | end
33 |
--------------------------------------------------------------------------------
/lib/doorkeeper/oauth/helpers/uri_checker.rb:
--------------------------------------------------------------------------------
1 | module Doorkeeper
2 | module OAuth
3 | module Helpers
4 | module URIChecker
5 | def self.valid?(url)
6 | uri = as_uri(url)
7 | uri.fragment.nil? && !uri.host.nil? && !uri.scheme.nil?
8 | rescue URI::InvalidURIError
9 | false
10 | end
11 |
12 | def self.matches?(url, client_url)
13 | url = as_uri(url)
14 | client_url = as_uri(client_url)
15 | url.query = nil
16 | url == client_url
17 | end
18 |
19 | def self.valid_for_authorization?(url, client_url)
20 | valid?(url) && client_url.split.any? { |other_url| matches?(url, other_url) }
21 | end
22 |
23 | def self.as_uri(url)
24 | URI.parse(url)
25 | end
26 |
27 | def self.native_uri?(url)
28 | url == Doorkeeper.configuration.native_redirect_uri
29 | end
30 | end
31 | end
32 | end
33 | end
34 |
--------------------------------------------------------------------------------
/lib/doorkeeper/oauth/invalid_token_response.rb:
--------------------------------------------------------------------------------
1 | module Doorkeeper
2 | module OAuth
3 | class InvalidTokenResponse < ErrorResponse
4 | attr_reader :reason
5 |
6 | def self.from_access_token(access_token, attributes = {})
7 | reason = case
8 | when access_token.try(:revoked?)
9 | :revoked
10 | when access_token.try(:expired?)
11 | :expired
12 | else
13 | :unknown
14 | end
15 |
16 | new(attributes.merge(reason: reason))
17 | end
18 |
19 | def initialize(attributes = {})
20 | super(attributes.merge(name: :invalid_token, state: :unauthorized))
21 | @reason = attributes[:reason] || :unknown
22 | end
23 |
24 | def description
25 | scope = { scope: [:doorkeeper, :errors, :messages, :invalid_token] }
26 | @description ||= I18n.translate @reason, scope
27 | end
28 | end
29 | end
30 | end
31 |
--------------------------------------------------------------------------------
/lib/doorkeeper/models/concerns/expirable.rb:
--------------------------------------------------------------------------------
1 | module Doorkeeper
2 | module Models
3 | module Expirable
4 | # Indicates whether the object is expired (`#expires_in` present and
5 | # expiration time has come).
6 | #
7 | # @return [Boolean] true if object expired and false in other case
8 | def expired?
9 | expires_in && Time.now.utc > expired_time
10 | end
11 |
12 | # Calculates expiration time in seconds.
13 | #
14 | # @return [Integer, nil] number of seconds if object has expiration time
15 | # or nil if object never expires.
16 | def expires_in_seconds
17 | return nil if expires_in.nil?
18 | expires = (created_at + expires_in.seconds) - Time.now.utc
19 | expires_sec = expires.seconds.round(0)
20 | expires_sec > 0 ? expires_sec : 0
21 | end
22 |
23 | private
24 |
25 | def expired_time
26 | created_at + expires_in.seconds
27 | end
28 | end
29 | end
30 | end
31 |
--------------------------------------------------------------------------------
/lib/doorkeeper/errors.rb:
--------------------------------------------------------------------------------
1 | module Doorkeeper
2 | module Errors
3 | class DoorkeeperError < StandardError
4 | def type
5 | message
6 | end
7 | end
8 |
9 | class InvalidAuthorizationStrategy < DoorkeeperError
10 | def type
11 | :unsupported_response_type
12 | end
13 | end
14 |
15 | class InvalidTokenReuse < DoorkeeperError
16 | def type
17 | :invalid_request
18 | end
19 | end
20 |
21 | class InvalidGrantReuse < DoorkeeperError
22 | def type
23 | :invalid_grant
24 | end
25 | end
26 |
27 | class InvalidTokenStrategy < DoorkeeperError
28 | def type
29 | :unsupported_grant_type
30 | end
31 | end
32 |
33 | class MissingRequestStrategy < DoorkeeperError
34 | def type
35 | :invalid_request
36 | end
37 | end
38 |
39 | class UnableToGenerateToken < DoorkeeperError
40 | end
41 |
42 | class TokenGeneratorNotFound < DoorkeeperError
43 | end
44 | end
45 | end
46 |
--------------------------------------------------------------------------------
/lib/doorkeeper/orm/active_record/application.rb:
--------------------------------------------------------------------------------
1 | module Doorkeeper
2 | class Application < ActiveRecord::Base
3 | self.table_name = "#{table_name_prefix}oauth_applications#{table_name_suffix}".to_sym
4 |
5 | include ApplicationMixin
6 |
7 | has_many :authorized_tokens, -> { where(revoked_at: nil) }, class_name: 'AccessToken'
8 | has_many :authorized_applications, through: :authorized_tokens, source: :application
9 |
10 | # Returns Applications associated with active (not revoked) Access Tokens
11 | # that are owned by the specific Resource Owner.
12 | #
13 | # @param resource_owner [ActiveRecord::Base]
14 | # Resource Owner model instance
15 | #
16 | # @return [ActiveRecord::Relation]
17 | # Applications authorized for the Resource Owner
18 | #
19 | def self.authorized_for(resource_owner)
20 | resource_access_tokens = AccessToken.active_for(resource_owner)
21 | where(id: resource_access_tokens.select(:application_id).distinct)
22 | end
23 | end
24 | end
25 |
--------------------------------------------------------------------------------
/spec/models/doorkeeper/access_grant_spec.rb:
--------------------------------------------------------------------------------
1 | require 'spec_helper_integration'
2 |
3 | describe Doorkeeper::AccessGrant do
4 | subject { FactoryGirl.build(:access_grant) }
5 |
6 | it { expect(subject).to be_valid }
7 |
8 | it_behaves_like 'an accessible token'
9 | it_behaves_like 'a revocable token'
10 | it_behaves_like 'a unique token' do
11 | let(:factory_name) { :access_grant }
12 | end
13 |
14 | describe 'validations' do
15 | it 'is invalid without resource_owner_id' do
16 | subject.resource_owner_id = nil
17 | expect(subject).not_to be_valid
18 | end
19 |
20 | it 'is invalid without application_id' do
21 | subject.application_id = nil
22 | expect(subject).not_to be_valid
23 | end
24 |
25 | it 'is invalid without token' do
26 | subject.save
27 | subject.token = nil
28 | expect(subject).not_to be_valid
29 | end
30 |
31 | it 'is invalid without expires_in' do
32 | subject.expires_in = nil
33 | expect(subject).not_to be_valid
34 | end
35 | end
36 | end
37 |
--------------------------------------------------------------------------------
/spec/generators/views_generator_spec.rb:
--------------------------------------------------------------------------------
1 | require 'spec_helper_integration'
2 | require 'generators/doorkeeper/views_generator'
3 |
4 | describe Doorkeeper::Generators::ViewsGenerator do
5 | include GeneratorSpec::TestCase
6 |
7 | tests Doorkeeper::Generators::ViewsGenerator
8 | destination File.expand_path('../tmp/dummy', __FILE__)
9 |
10 | before :each do
11 | prepare_destination
12 | end
13 |
14 | it 'create all views' do
15 | run_generator
16 | assert_file 'app/views/doorkeeper/applications/_form.html.erb'
17 | assert_file 'app/views/doorkeeper/applications/edit.html.erb'
18 | assert_file 'app/views/doorkeeper/applications/index.html.erb'
19 | assert_file 'app/views/doorkeeper/applications/new.html.erb'
20 | assert_file 'app/views/doorkeeper/applications/show.html.erb'
21 |
22 | assert_file 'app/views/doorkeeper/authorizations/error.html.erb'
23 | assert_file 'app/views/doorkeeper/authorizations/new.html.erb'
24 |
25 | assert_file 'app/views/doorkeeper/authorized_applications/index.html.erb'
26 | end
27 | end
28 |
--------------------------------------------------------------------------------
/lib/doorkeeper/oauth/client_credentials_request.rb:
--------------------------------------------------------------------------------
1 | require 'doorkeeper/oauth/client_credentials/creator'
2 | require 'doorkeeper/oauth/client_credentials/issuer'
3 | require 'doorkeeper/oauth/client_credentials/validation'
4 |
5 | module Doorkeeper
6 | module OAuth
7 | class ClientCredentialsRequest < BaseRequest
8 | attr_accessor :server, :client, :original_scopes
9 | attr_reader :response
10 | attr_writer :issuer
11 |
12 | alias_method :error_response, :response
13 |
14 | delegate :error, to: :issuer
15 |
16 | def issuer
17 | @issuer ||= Issuer.new(server, Validation.new(server, self))
18 | end
19 |
20 | def initialize(server, client, parameters = {})
21 | @client = client
22 | @server = server
23 | @response = nil
24 | @original_scopes = parameters[:scope]
25 | end
26 |
27 | def access_token
28 | issuer.token
29 | end
30 |
31 | private
32 |
33 | def valid?
34 | issuer.create(client, scopes)
35 | end
36 | end
37 | end
38 | end
39 |
--------------------------------------------------------------------------------
/lib/doorkeeper/rails/routes/mapping.rb:
--------------------------------------------------------------------------------
1 | module Doorkeeper
2 | module Rails
3 | class Routes # :nodoc:
4 | class Mapping
5 | attr_accessor :controllers, :as, :skips
6 |
7 | def initialize
8 | @controllers = {
9 | authorizations: 'doorkeeper/authorizations',
10 | applications: 'doorkeeper/applications',
11 | authorized_applications: 'doorkeeper/authorized_applications',
12 | tokens: 'doorkeeper/tokens',
13 | token_info: 'doorkeeper/token_info'
14 | }
15 |
16 | @as = {
17 | authorizations: :authorization,
18 | tokens: :token,
19 | token_info: :token_info
20 | }
21 |
22 | @skips = []
23 | end
24 |
25 | def [](routes)
26 | {
27 | controllers: @controllers[routes],
28 | as: @as[routes]
29 | }
30 | end
31 |
32 | def skipped?(controller)
33 | @skips.include?(controller)
34 | end
35 | end
36 | end
37 | end
38 | end
39 |
--------------------------------------------------------------------------------
/lib/doorkeeper/oauth/client/credentials.rb:
--------------------------------------------------------------------------------
1 | module Doorkeeper
2 | module OAuth
3 | class Client
4 | class Credentials < Struct.new(:uid, :secret)
5 | class << self
6 | def from_request(request, *credentials_methods)
7 | credentials_methods.inject(nil) do |credentials, method|
8 | method = self.method(method) if method.is_a?(Symbol)
9 | credentials = Credentials.new(*method.call(request))
10 | break credentials unless credentials.blank?
11 | end
12 | end
13 |
14 | def from_params(request)
15 | request.parameters.values_at(:client_id, :client_secret)
16 | end
17 |
18 | def from_basic(request)
19 | authorization = request.authorization
20 | if authorization.present? && authorization =~ /^Basic (.*)/m
21 | Base64.decode64($1).split(/:/, 2)
22 | end
23 | end
24 | end
25 |
26 | def blank?
27 | uid.blank? || secret.blank?
28 | end
29 | end
30 | end
31 | end
32 | end
33 |
--------------------------------------------------------------------------------
/spec/lib/oauth/base_response_spec.rb:
--------------------------------------------------------------------------------
1 | require 'spec_helper_integration'
2 |
3 | module Doorkeeper::OAuth
4 | describe BaseResponse do
5 | subject do
6 | BaseResponse.new
7 | end
8 |
9 | describe "#body" do
10 | it "returns an empty Hash" do
11 | expect(subject.body).to eq({})
12 | end
13 | end
14 |
15 | describe "#description" do
16 | it "returns an empty String" do
17 | expect(subject.description).to eq("")
18 | end
19 | end
20 |
21 | describe "#headers" do
22 | it "returns an empty Hash" do
23 | expect(subject.headers).to eq({})
24 | end
25 | end
26 |
27 | describe "#redirectable?" do
28 | it "returns false" do
29 | expect(subject.redirectable?).to eq(false)
30 | end
31 | end
32 |
33 | describe "#redirect_uri" do
34 | it "returns an empty String" do
35 | expect(subject.redirect_uri).to eq("")
36 | end
37 | end
38 |
39 | describe "#status" do
40 | it "returns :ok" do
41 | expect(subject.status).to eq(:ok)
42 | end
43 | end
44 | end
45 | end
46 |
--------------------------------------------------------------------------------
/MIT-LICENSE:
--------------------------------------------------------------------------------
1 | Copyright 2011 Applicake. http://applicake.com
2 |
3 | Permission is hereby granted, free of charge, to any person obtaining
4 | a copy of this software and associated documentation files (the
5 | "Software"), to deal in the Software without restriction, including
6 | without limitation the rights to use, copy, modify, merge, publish,
7 | distribute, sublicense, and/or sell copies of the Software, and to
8 | permit persons to whom the Software is furnished to do so, subject to
9 | the following conditions:
10 |
11 | The above copyright notice and this permission notice shall be
12 | included in all copies or substantial portions of the Software.
13 |
14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
21 |
--------------------------------------------------------------------------------
/lib/doorkeeper/oauth/token_request.rb:
--------------------------------------------------------------------------------
1 | module Doorkeeper
2 | module OAuth
3 | class TokenRequest
4 | attr_accessor :pre_auth, :resource_owner
5 |
6 | def initialize(pre_auth, resource_owner)
7 | @pre_auth = pre_auth
8 | @resource_owner = resource_owner
9 | end
10 |
11 | def authorize
12 | if pre_auth.authorizable?
13 | auth = Authorization::Token.new(pre_auth, resource_owner)
14 | auth.issue_token
15 | @response = CodeResponse.new pre_auth,
16 | auth,
17 | response_on_fragment: true
18 | else
19 | @response = error_response
20 | end
21 | end
22 |
23 | def deny
24 | pre_auth.error = :access_denied
25 | error_response
26 | end
27 |
28 | private
29 |
30 | def error_response
31 | ErrorResponse.from_request pre_auth,
32 | redirect_uri: pre_auth.redirect_uri,
33 | response_on_fragment: true
34 | end
35 | end
36 | end
37 | end
38 |
--------------------------------------------------------------------------------
/spec/requests/applications/authorized_applications_spec.rb:
--------------------------------------------------------------------------------
1 | require 'spec_helper_integration'
2 |
3 | feature 'Authorized applications' do
4 | background do
5 | @user = User.create!(name: 'Joe', password: 'sekret')
6 | @client = client_exists(name: 'Amazing Client App')
7 | resource_owner_is_authenticated @user
8 | client_is_authorized @client, @user
9 | end
10 |
11 | scenario 'display user\'s authorized applications' do
12 | visit '/oauth/authorized_applications'
13 | i_should_see 'Amazing Client App'
14 | end
15 |
16 | scenario 'do not display other user\'s authorized applications' do
17 | client = client_exists(name: 'Another Client App')
18 | client_is_authorized client, User.create!(name: 'Joe', password: 'sekret')
19 | visit '/oauth/authorized_applications'
20 | i_should_not_see 'Another Client App'
21 | end
22 |
23 | scenario 'user revoke access to application' do
24 | visit '/oauth/authorized_applications'
25 | i_should_see 'Amazing Client App'
26 | click_on 'Revoke'
27 | i_should_see 'Application revoked'
28 | i_should_not_see 'Amazing Client App'
29 | end
30 | end
31 |
--------------------------------------------------------------------------------
/spec/generators/install_generator_spec.rb:
--------------------------------------------------------------------------------
1 | require 'spec_helper_integration'
2 | require 'generators/doorkeeper/install_generator'
3 |
4 | describe 'Doorkeeper::InstallGenerator' do
5 | include GeneratorSpec::TestCase
6 |
7 | tests Doorkeeper::InstallGenerator
8 | destination ::File.expand_path('../tmp/dummy', __FILE__)
9 |
10 | describe 'after running the generator' do
11 | before :each do
12 | prepare_destination
13 | FileUtils.mkdir(::File.expand_path('config', Pathname(destination_root)))
14 | FileUtils.mkdir(::File.expand_path('db', Pathname(destination_root)))
15 | FileUtils.copy_file(::File.expand_path('../templates/routes.rb', __FILE__), ::File.expand_path('config/routes.rb', Pathname.new(destination_root)))
16 | run_generator
17 | end
18 |
19 | it 'creates an initializer file' do
20 | assert_file 'config/initializers/doorkeeper.rb'
21 | end
22 |
23 | it 'copies the locale file' do
24 | assert_file 'config/locales/doorkeeper.en.yml'
25 | end
26 |
27 | it 'adds sample route' do
28 | assert_file 'config/routes.rb', /use_doorkeeper/
29 | end
30 | end
31 | end
32 |
--------------------------------------------------------------------------------
/spec/dummy/config/environments/development.rb:
--------------------------------------------------------------------------------
1 | Dummy::Application.configure do
2 | # Settings specified here will take precedence over those in config/application.rb
3 |
4 | # In the development environment your application's code is reloaded on
5 | # every request. This slows down response time but is perfect for development
6 | # since you don't have to restart the web server when you make code changes.
7 | config.cache_classes = false
8 |
9 | # Show full error reports and disable caching
10 | config.consider_all_requests_local = true
11 | config.action_controller.perform_caching = false
12 |
13 | # Don't care if the mailer can't send
14 | # config.action_mailer.raise_delivery_errors = false
15 |
16 | # Print deprecation notices to the Rails logger
17 | config.active_support.deprecation = :log
18 |
19 | # Only use best-standards-support built into browsers
20 | config.action_dispatch.best_standards_support = :builtin
21 |
22 | # Do not compress assets
23 | config.assets.compress = false
24 |
25 | # Expands the lines which load the assets
26 | config.assets.debug = true
27 |
28 | config.eager_load = false
29 | end
30 |
--------------------------------------------------------------------------------
/lib/doorkeeper/oauth/client_credentials/issuer.rb:
--------------------------------------------------------------------------------
1 | require 'doorkeeper/oauth/client_credentials/validation'
2 |
3 | module Doorkeeper
4 | module OAuth
5 | class ClientCredentialsRequest < BaseRequest
6 | class Issuer
7 | attr_accessor :token, :validation, :error
8 |
9 | def initialize(server, validation)
10 | @server = server
11 | @validation = validation
12 | end
13 |
14 | def create(client, scopes, creator = Creator.new)
15 | if validation.valid?
16 | @token = create_token(client, scopes, creator)
17 | @error = :server_error unless @token
18 | else
19 | @token = false
20 | @error = validation.error
21 | end
22 | @token
23 | end
24 |
25 | private
26 |
27 | def create_token(client, scopes, creator)
28 | ttl = Authorization::Token.access_token_expires_in(@server, client)
29 |
30 | creator.call(
31 | client,
32 | scopes,
33 | use_refresh_token: false,
34 | expires_in: ttl
35 | )
36 | end
37 | end
38 | end
39 | end
40 | end
41 |
--------------------------------------------------------------------------------
/spec/lib/oauth/code_response_spec.rb:
--------------------------------------------------------------------------------
1 | require 'spec_helper'
2 |
3 | module Doorkeeper
4 | module OAuth
5 | describe CodeResponse do
6 | describe '.redirect_uri' do
7 | context 'when generating the redirect URI for an implicit grant' do
8 | let :pre_auth do
9 | double(
10 | :pre_auth,
11 | client: double(:application, id: 1),
12 | redirect_uri: 'http://tst.com/cb',
13 | state: nil,
14 | scopes: Scopes.from_string('public'),
15 | )
16 | end
17 |
18 | let :auth do
19 | Authorization::Token.new(pre_auth, double(id: 1)).tap do |c|
20 | c.issue_token
21 | allow(c.token).to receive(:expires_in_seconds).and_return(3600)
22 | end
23 | end
24 |
25 | subject { CodeResponse.new(pre_auth, auth, response_on_fragment: true).redirect_uri }
26 |
27 | it 'includes the remaining TTL of the token relative to the time the token was generated' do
28 | expect(subject).to include('expires_in=3600')
29 | end
30 | end
31 | end
32 | end
33 | end
34 | end
35 |
--------------------------------------------------------------------------------
/app/validators/redirect_uri_validator.rb:
--------------------------------------------------------------------------------
1 | require 'uri'
2 |
3 | class RedirectUriValidator < ActiveModel::EachValidator
4 | def self.native_redirect_uri
5 | Doorkeeper.configuration.native_redirect_uri
6 | end
7 |
8 | def validate_each(record, attribute, value)
9 | if value.blank?
10 | record.errors.add(attribute, :blank)
11 | else
12 | value.split.each do |val|
13 | uri = ::URI.parse(val)
14 | return if native_redirect_uri?(uri)
15 | record.errors.add(attribute, :fragment_present) unless uri.fragment.nil?
16 | record.errors.add(attribute, :relative_uri) if uri.scheme.nil? || uri.host.nil?
17 | record.errors.add(attribute, :secured_uri) if invalid_ssl_uri?(uri)
18 | end
19 | end
20 | rescue URI::InvalidURIError
21 | record.errors.add(attribute, :invalid_uri)
22 | end
23 |
24 | private
25 |
26 | def native_redirect_uri?(uri)
27 | self.class.native_redirect_uri.present? && uri.to_s == self.class.native_redirect_uri.to_s
28 | end
29 |
30 | def invalid_ssl_uri?(uri)
31 | forces_ssl = Doorkeeper.configuration.force_ssl_in_redirect_uri
32 | forces_ssl && uri.try(:scheme) == 'http'
33 | end
34 | end
35 |
--------------------------------------------------------------------------------
/doorkeeper.gemspec:
--------------------------------------------------------------------------------
1 | $LOAD_PATH.push File.expand_path("../lib", __FILE__)
2 |
3 | require "doorkeeper/version"
4 |
5 | Gem::Specification.new do |s|
6 | s.name = "doorkeeper"
7 | s.version = Doorkeeper::VERSION
8 | s.authors = ["Felipe Elias Philipp", "Tute Costa", "Jon Moss"]
9 | s.email = %w(me@jonathanmoss.me)
10 | s.homepage = "https://github.com/doorkeeper-gem/doorkeeper"
11 | s.summary = "OAuth 2 provider for Rails and Grape"
12 | s.description = "Doorkeeper is an OAuth 2 provider for Rails and Grape."
13 | s.license = 'MIT'
14 |
15 | s.files = `git ls-files`.split("\n")
16 | s.test_files = `git ls-files -- spec/*`.split("\n")
17 | s.require_paths = ["lib"]
18 |
19 | s.add_dependency "railties", ">= 4.2"
20 | s.required_ruby_version = ">= 2.1"
21 |
22 | s.add_development_dependency "capybara"
23 | s.add_development_dependency "coveralls"
24 | s.add_development_dependency "database_cleaner", "~> 1.5.3"
25 | s.add_development_dependency "factory_girl", "~> 4.7.0"
26 | s.add_development_dependency "generator_spec", "~> 0.9.3"
27 | s.add_development_dependency "rake", ">= 11.3.0"
28 | s.add_development_dependency "rspec-rails"
29 | end
30 |
--------------------------------------------------------------------------------
/spec/requests/flows/implicit_grant_errors_spec.rb:
--------------------------------------------------------------------------------
1 | require 'spec_helper_integration'
2 |
3 | feature 'Implicit Grant Flow Errors' do
4 | background do
5 | config_is_set(:authenticate_resource_owner) { User.first || redirect_to('/sign_in') }
6 | config_is_set(:grant_flows, ["implicit"])
7 | client_exists
8 | create_resource_owner
9 | sign_in
10 | end
11 |
12 | after do
13 | access_token_should_not_exist
14 | end
15 |
16 | [
17 | [:client_id, :invalid_client],
18 | [:redirect_uri, :invalid_redirect_uri]
19 | ].each do |error|
20 | scenario "displays #{error.last.inspect} error for invalid #{error.first.inspect}" do
21 | visit authorization_endpoint_url(client: @client, error.first => 'invalid', response_type: 'token')
22 | i_should_not_see 'Authorize'
23 | i_should_see_translated_error_message error.last
24 | end
25 |
26 | scenario "displays #{error.last.inspect} error when #{error.first.inspect} is missing" do
27 | visit authorization_endpoint_url(client: @client, error.first => '', response_type: 'token')
28 | i_should_not_see 'Authorize'
29 | i_should_see_translated_error_message error.last
30 | end
31 | end
32 | end
33 |
--------------------------------------------------------------------------------
/lib/doorkeeper/oauth/code_response.rb:
--------------------------------------------------------------------------------
1 | module Doorkeeper
2 | module OAuth
3 | class CodeResponse < BaseResponse
4 | include OAuth::Helpers
5 |
6 | attr_accessor :pre_auth, :auth, :response_on_fragment
7 |
8 | def initialize(pre_auth, auth, options = {})
9 | @pre_auth = pre_auth
10 | @auth = auth
11 | @response_on_fragment = options[:response_on_fragment]
12 | end
13 |
14 | def redirectable?
15 | true
16 | end
17 |
18 | def redirect_uri
19 | if URIChecker.native_uri? pre_auth.redirect_uri
20 | auth.native_redirect
21 | elsif response_on_fragment
22 | Authorization::URIBuilder.uri_with_fragment(
23 | pre_auth.redirect_uri,
24 | access_token: auth.token.token,
25 | token_type: auth.token.token_type,
26 | expires_in: auth.token.expires_in_seconds,
27 | state: pre_auth.state
28 | )
29 | else
30 | Authorization::URIBuilder.uri_with_query(
31 | pre_auth.redirect_uri,
32 | code: auth.token.token,
33 | state: pre_auth.state
34 | )
35 | end
36 | end
37 | end
38 | end
39 | end
40 |
--------------------------------------------------------------------------------
/app/assets/stylesheets/doorkeeper/application.css:
--------------------------------------------------------------------------------
1 | /*
2 | *= require doorkeeper/bootstrap.min
3 | *
4 | *= require_self
5 | *= require_tree .
6 | */
7 |
8 | body {
9 | background-color: #eee;
10 | font-size: 14px;
11 | }
12 |
13 | #container {
14 | background-color: #fff;
15 | border: 1px solid #999;
16 | border: 1px solid rgba(0, 0, 0, 0.2);
17 | border-radius: 6px;
18 | -webkit-box-shadow: 0 3px 9px rgba(0, 0, 0, 0.5);
19 | box-shadow: 0 3px 20px rgba(0, 0, 0, 0.3);
20 | margin: 2em auto;
21 | max-width: 600px;
22 | outline: 0;
23 | padding: 1em;
24 | width: 80%;
25 | }
26 |
27 | .page-header {
28 | margin-top: 20px;
29 | }
30 |
31 | #oauth-permissions {
32 | width: 260px;
33 | }
34 |
35 | .actions {
36 | border-top: 1px solid #eee;
37 | margin-top: 1em;
38 | padding-top: 9px;
39 | }
40 |
41 | .actions > form > .btn {
42 | margin-top: 5px;
43 | }
44 |
45 | .separator {
46 | color: #eee;
47 | padding: 0 .5em;
48 | }
49 |
50 | .inline_block {
51 | display: inline-block;
52 | }
53 |
54 | #oauth {
55 | margin-bottom: 1em;
56 | }
57 |
58 | #oauth > .btn {
59 | width: 7em;
60 | }
61 |
62 | td {
63 | vertical-align: middle !important;
64 | }
65 |
--------------------------------------------------------------------------------
/spec/support/http_method_shim.rb:
--------------------------------------------------------------------------------
1 | # Rails 5 deprecates calling HTTP action methods with positional arguments
2 | # in favor of keyword arguments. However, the keyword argument form is only
3 | # supported in Rails 5+. Since we support back to 4, we need some sort of shim
4 | # to avoid super noisy deprecations when running tests.
5 | module RoutingHTTPMethodShim
6 | def get(path, params = {}, headers = nil)
7 | super(path, params: params, headers: headers)
8 | end
9 |
10 | def post(path, params = {}, headers = nil)
11 | super(path, params: params, headers: headers)
12 | end
13 |
14 | def put(path, params = {}, headers = nil)
15 | super(path, params: params, headers: headers)
16 | end
17 | end
18 |
19 | module ControllerHTTPMethodShim
20 | def get(path, params = {})
21 | super(path, params: params)
22 | end
23 |
24 | def post(path, params = {})
25 | super(path, params: params)
26 | end
27 |
28 | def put(path, params = {})
29 | super(path, params: params)
30 | end
31 | end
32 |
33 | if ::Rails::VERSION::MAJOR >= 5
34 | RSpec.configure do |config|
35 | config.include ControllerHTTPMethodShim, type: :controller
36 | config.include RoutingHTTPMethodShim, type: :request
37 | end
38 | end
39 |
--------------------------------------------------------------------------------
/CONTRIBUTING.md:
--------------------------------------------------------------------------------
1 | # Contributing
2 |
3 | We love pull requests from everyone. By participating in this project, you agree
4 | to abide by the thoughtbot [code of conduct].
5 |
6 | [code of conduct]: https://thoughtbot.com/open-source-code-of-conduct
7 |
8 | Fork, then clone the repo:
9 |
10 | git clone git@github.com:your-username/doorkeeper.git
11 |
12 | Set up Ruby dependencies via Bundler
13 |
14 | bundle install
15 |
16 | Make sure the tests pass:
17 |
18 | rake
19 |
20 | Make your change.
21 | Write tests.
22 | Follow our [style guide][style].
23 | Make the tests pass:
24 |
25 | [style]: https://github.com/thoughtbot/guides/tree/master/style
26 |
27 | rake
28 |
29 | Add notes on your change to the `NEWS.md` file.
30 |
31 | Write a [good commit message][commit].
32 | Push to your fork.
33 | [Submit a pull request][pr].
34 |
35 | [commit]: http://tbaggery.com/2008/04/19/a-note-about-git-commit-messages.html
36 | [pr]: https://github.com/doorkeeper-gem/doorkeeper/compare/
37 |
38 | If [Hound] catches style violations,
39 | fix them.
40 |
41 | [hound]: https://houndci.com
42 |
43 | Wait for us.
44 | We try to at least comment on pull requests within one business day.
45 | We may suggest changes.
46 |
47 | Thank you for your contribution!
48 |
--------------------------------------------------------------------------------
/spec/lib/models/scopes_spec.rb:
--------------------------------------------------------------------------------
1 | require 'spec_helper'
2 | require 'active_support/core_ext/module/delegation'
3 | require 'active_support/core_ext/object/blank'
4 | require 'doorkeeper/oauth/scopes'
5 | require 'doorkeeper/models/concerns/scopes'
6 |
7 | describe 'Doorkeeper::Models::Scopes' do
8 | subject do
9 | Class.new(Hash) do
10 | include Doorkeeper::Models::Scopes
11 | end.new
12 | end
13 |
14 | before do
15 | subject[:scopes] = 'public admin'
16 | end
17 |
18 | describe :scopes do
19 | it 'is a `Scopes` class' do
20 | expect(subject.scopes).to be_a(Doorkeeper::OAuth::Scopes)
21 | end
22 |
23 | it 'includes scopes' do
24 | expect(subject.scopes).to include('public')
25 | end
26 | end
27 |
28 | describe :scopes_string do
29 | it 'is a `Scopes` class' do
30 | expect(subject.scopes_string).to eq('public admin')
31 | end
32 | end
33 |
34 | describe :includes_scope? do
35 | it 'should return true if at least one scope is included' do
36 | expect(subject.includes_scope?('public', 'private')).to be true
37 | end
38 |
39 | it 'should return false if no scopes are included' do
40 | expect(subject.includes_scope?('teacher', 'student')).to be false
41 | end
42 | end
43 | end
44 |
--------------------------------------------------------------------------------
/lib/doorkeeper/server.rb:
--------------------------------------------------------------------------------
1 | module Doorkeeper
2 | class Server
3 | attr_accessor :context
4 |
5 | def initialize(context = nil)
6 | @context = context
7 | end
8 |
9 | def authorization_request(strategy)
10 | klass = Request.authorization_strategy strategy
11 | klass.new self
12 | end
13 |
14 | def token_request(strategy)
15 | klass = Request.token_strategy strategy
16 | klass.new self
17 | end
18 |
19 | # TODO: context should be the request
20 | def parameters
21 | context.request.parameters
22 | end
23 |
24 | def client
25 | @client ||= OAuth::Client.authenticate(credentials)
26 | end
27 |
28 | def client_via_uid
29 | @client_via_uid ||= OAuth::Client.find(parameters[:client_id])
30 | end
31 |
32 | def current_resource_owner
33 | context.send :current_resource_owner
34 | end
35 |
36 | # TODO: Use configuration and evaluate proper context on block
37 | def resource_owner
38 | context.send :resource_owner_from_credentials
39 | end
40 |
41 | def credentials
42 | methods = Doorkeeper.configuration.client_credentials_methods
43 | @credentials ||= OAuth::Client::Credentials.from_request(context.request, *methods)
44 | end
45 | end
46 | end
47 |
--------------------------------------------------------------------------------
/spec/lib/oauth/code_request_spec.rb:
--------------------------------------------------------------------------------
1 | require 'spec_helper_integration'
2 |
3 | module Doorkeeper::OAuth
4 | describe CodeRequest do
5 | let(:pre_auth) do
6 | double(
7 | :pre_auth,
8 | client: double(:application, id: 9990),
9 | redirect_uri: 'http://tst.com/cb',
10 | scopes: nil,
11 | state: nil,
12 | error: nil,
13 | authorizable?: true
14 | )
15 | end
16 |
17 | let(:owner) { double :owner, id: 8900 }
18 |
19 | subject do
20 | CodeRequest.new(pre_auth, owner)
21 | end
22 |
23 | it 'creates an access grant' do
24 | expect do
25 | subject.authorize
26 | end.to change { Doorkeeper::AccessGrant.count }.by(1)
27 | end
28 |
29 | it 'returns a code response' do
30 | expect(subject.authorize).to be_a(CodeResponse)
31 | end
32 |
33 | it 'does not create grant when not authorizable' do
34 | allow(pre_auth).to receive(:authorizable?).and_return(false)
35 | expect do
36 | subject.authorize
37 | end.to_not change { Doorkeeper::AccessGrant.count }
38 | end
39 |
40 | it 'returns a error response' do
41 | allow(pre_auth).to receive(:authorizable?).and_return(false)
42 | expect(subject.authorize).to be_a(ErrorResponse)
43 | end
44 | end
45 | end
46 |
--------------------------------------------------------------------------------
/lib/doorkeeper/oauth/client_credentials/validation.rb:
--------------------------------------------------------------------------------
1 | require 'doorkeeper/validations'
2 | require 'doorkeeper/oauth/scopes'
3 | require 'doorkeeper/oauth/helpers/scope_checker'
4 |
5 | module Doorkeeper
6 | module OAuth
7 | class ClientCredentialsRequest < BaseRequest
8 | class Validation
9 | include Validations
10 | include OAuth::Helpers
11 |
12 | validate :client, error: :invalid_client
13 | validate :scopes, error: :invalid_scope
14 |
15 | def initialize(server, request)
16 | @server, @request, @client = server, request, request.client
17 |
18 | validate
19 | end
20 |
21 | private
22 |
23 | def validate_client
24 | @client.present?
25 | end
26 |
27 | def validate_scopes
28 | return true unless @request.scopes.present?
29 |
30 | application_scopes = if @client.present?
31 | @client.application.scopes
32 | else
33 | ''
34 | end
35 |
36 | ScopeChecker.valid?(
37 | @request.scopes.to_s,
38 | @server.scopes,
39 | application_scopes
40 | )
41 | end
42 | end
43 | end
44 | end
45 | end
46 |
--------------------------------------------------------------------------------
/app/views/layouts/doorkeeper/admin.html.erb:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | Doorkeeper
8 | <%= stylesheet_link_tag "doorkeeper/admin/application" %>
9 | <%= csrf_meta_tags %>
10 |
11 |
12 |
13 |
14 |
17 |
18 | <%= content_tag :li, class: "#{'active' if request.path == oauth_applications_path}" do %>
19 | <%= link_to t('doorkeeper.layouts.admin.nav.applications'), oauth_applications_path %>
20 | <% end %>
21 | <%= content_tag :li do %>
22 | <%= link_to t('doorkeeper.layouts.admin.nav.home'), root_path %>
23 | <% end %>
24 |
25 |
26 |
27 |
28 | <%- if flash[:notice].present? %>
29 |
30 | <%= flash[:notice] %>
31 |
32 | <% end -%>
33 |
34 | <%= yield %>
35 |
36 |
37 |
38 |
--------------------------------------------------------------------------------
/spec/routing/scoped_routes_spec.rb:
--------------------------------------------------------------------------------
1 | require 'spec_helper_integration'
2 |
3 | describe 'Scoped routes' do
4 | it 'GET /scope/authorize routes to authorizations controller' do
5 | expect(get('/scope/authorize')).to route_to('doorkeeper/authorizations#new')
6 | end
7 |
8 | it 'POST /scope/authorize routes to authorizations controller' do
9 | expect(post('/scope/authorize')).to route_to('doorkeeper/authorizations#create')
10 | end
11 |
12 | it 'DELETE /scope/authorize routes to authorizations controller' do
13 | expect(delete('/scope/authorize')).to route_to('doorkeeper/authorizations#destroy')
14 | end
15 |
16 | it 'POST /scope/token routes to tokens controller' do
17 | expect(post('/scope/token')).to route_to('doorkeeper/tokens#create')
18 | end
19 |
20 | it 'GET /scope/applications routes to applications controller' do
21 | expect(get('/scope/applications')).to route_to('doorkeeper/applications#index')
22 | end
23 |
24 | it 'GET /scope/authorized_applications routes to authorized applications controller' do
25 | expect(get('/scope/authorized_applications')).to route_to('doorkeeper/authorized_applications#index')
26 | end
27 |
28 | it 'GET /scope/token/info route to authorzed tokeninfo controller' do
29 | expect(get('/scope/token/info')).to route_to('doorkeeper/token_info#show')
30 | end
31 | end
32 |
--------------------------------------------------------------------------------
/lib/doorkeeper/oauth/password_access_token_request.rb:
--------------------------------------------------------------------------------
1 | module Doorkeeper
2 | module OAuth
3 | class PasswordAccessTokenRequest < BaseRequest
4 | include OAuth::Helpers
5 |
6 | validate :client, error: :invalid_client
7 | validate :resource_owner, error: :invalid_grant
8 | validate :scopes, error: :invalid_scope
9 |
10 | attr_accessor :server, :client, :resource_owner, :parameters,
11 | :access_token
12 |
13 | def initialize(server, client, resource_owner, parameters = {})
14 | @server = server
15 | @resource_owner = resource_owner
16 | @client = client
17 | @parameters = parameters
18 | @original_scopes = parameters[:scope]
19 | end
20 |
21 | private
22 |
23 | def before_successful_response
24 | find_or_create_access_token(client, resource_owner.id, scopes, server)
25 | end
26 |
27 | def validate_scopes
28 | return true unless @original_scopes.present?
29 | ScopeChecker.valid? @original_scopes, server.scopes, client.try(:scopes)
30 | end
31 |
32 | def validate_resource_owner
33 | !!resource_owner
34 | end
35 |
36 | def validate_client
37 | !parameters[:client_id] || !!client
38 | end
39 | end
40 | end
41 | end
42 |
--------------------------------------------------------------------------------
/lib/doorkeeper/oauth/base_request.rb:
--------------------------------------------------------------------------------
1 | module Doorkeeper
2 | module OAuth
3 | class BaseRequest
4 | include Validations
5 |
6 | def authorize
7 | validate
8 | if valid?
9 | before_successful_response
10 | @response = TokenResponse.new(access_token)
11 | after_successful_response
12 | @response
13 | else
14 | @response = ErrorResponse.from_request(self)
15 | end
16 | end
17 |
18 | def scopes
19 | @scopes ||= if @original_scopes.present?
20 | OAuth::Scopes.from_string(@original_scopes)
21 | else
22 | default_scopes
23 | end
24 | end
25 |
26 | def default_scopes
27 | server.default_scopes
28 | end
29 |
30 | def valid?
31 | error.nil?
32 | end
33 |
34 | def find_or_create_access_token(client, resource_owner_id, scopes, server)
35 | @access_token = AccessToken.find_or_create_for(
36 | client,
37 | resource_owner_id,
38 | scopes,
39 | Authorization::Token.access_token_expires_in(server, client),
40 | server.refresh_token_enabled?)
41 | end
42 |
43 | def before_successful_response
44 | end
45 |
46 | def after_successful_response
47 | end
48 | end
49 | end
50 | end
51 |
--------------------------------------------------------------------------------
/lib/doorkeeper/request.rb:
--------------------------------------------------------------------------------
1 | require 'doorkeeper/request/authorization_code'
2 | require 'doorkeeper/request/client_credentials'
3 | require 'doorkeeper/request/code'
4 | require 'doorkeeper/request/password'
5 | require 'doorkeeper/request/refresh_token'
6 | require 'doorkeeper/request/token'
7 |
8 | module Doorkeeper
9 | module Request
10 | module_function
11 |
12 | def authorization_strategy(response_type)
13 | get_strategy response_type, authorization_response_types
14 | rescue NameError
15 | raise Errors::InvalidAuthorizationStrategy
16 | end
17 |
18 | def token_strategy(grant_type)
19 | get_strategy grant_type, token_grant_types
20 | rescue NameError
21 | raise Errors::InvalidTokenStrategy
22 | end
23 |
24 | def get_strategy(grant_or_request_type, available)
25 | fail Errors::MissingRequestStrategy unless grant_or_request_type.present?
26 | fail NameError unless available.include?(grant_or_request_type.to_s)
27 | "Doorkeeper::Request::#{grant_or_request_type.to_s.camelize}".constantize
28 | end
29 |
30 | def authorization_response_types
31 | Doorkeeper.configuration.authorization_response_types
32 | end
33 | private_class_method :authorization_response_types
34 |
35 | def token_grant_types
36 | Doorkeeper.configuration.token_grant_types
37 | end
38 | private_class_method :token_grant_types
39 | end
40 | end
41 |
--------------------------------------------------------------------------------
/app/views/doorkeeper/applications/show.html.erb:
--------------------------------------------------------------------------------
1 |
4 |
5 |
6 |
7 |
<%= t('.application_id') %>:
8 |
<%= @application.uid %>
9 |
10 |
<%= t('.secret') %>:
11 |
<%= @application.secret %>
12 |
13 |
<%= t('.scopes') %>:
14 |
<%= @application.scopes %>
15 |
16 |
<%= t('.callback_urls') %>:
17 |
18 |
19 | <% @application.redirect_uri.split.each do |uri| %>
20 |
21 |
22 | <%= uri %>
23 | |
24 |
25 | <%= 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' %>
26 | |
27 |
28 | <% end %>
29 |
30 |
31 |
32 |
33 |
<%= t('.actions') %>
34 |
35 |
<%= link_to t('doorkeeper.applications.buttons.edit'), edit_oauth_application_path(@application), class: 'btn btn-primary' %>
36 |
37 |
<%= render 'delete_form', application: @application, submit_btn_css: 'btn btn-danger' %>
38 |
39 |
40 |
--------------------------------------------------------------------------------
/lib/doorkeeper/oauth/scopes.rb:
--------------------------------------------------------------------------------
1 | module Doorkeeper
2 | module OAuth
3 | class Scopes
4 | include Enumerable
5 | include Comparable
6 |
7 | def self.from_string(string)
8 | string ||= ''
9 | new.tap do |scope|
10 | scope.add(*string.split)
11 | end
12 | end
13 |
14 | def self.from_array(array)
15 | new.tap do |scope|
16 | scope.add(*array)
17 | end
18 | end
19 |
20 | delegate :each, :empty?, to: :@scopes
21 |
22 | def initialize
23 | @scopes = []
24 | end
25 |
26 | def exists?(scope)
27 | @scopes.include? scope.to_s
28 | end
29 |
30 | def add(*scopes)
31 | @scopes.push(*scopes.map(&:to_s))
32 | @scopes.uniq!
33 | end
34 |
35 | def all
36 | @scopes
37 | end
38 |
39 | def to_s
40 | @scopes.join(' ')
41 | end
42 |
43 | def has_scopes?(scopes)
44 | scopes.all? { |s| exists?(s) }
45 | end
46 |
47 | def +(other)
48 | if other.is_a? Scopes
49 | self.class.from_array(all + other.all)
50 | else
51 | super(other)
52 | end
53 | end
54 |
55 | def <=>(other)
56 | map(&:to_s).sort <=> other.map(&:to_s).sort
57 | end
58 |
59 | def &(other)
60 | other_array = other.present? ? other.all : []
61 | self.class.from_array(all & other_array)
62 | end
63 | end
64 | end
65 | end
66 |
--------------------------------------------------------------------------------
/spec/routing/default_routes_spec.rb:
--------------------------------------------------------------------------------
1 | require 'spec_helper_integration'
2 |
3 | describe 'Default routes' do
4 | it 'GET /oauth/authorize routes to authorizations controller' do
5 | expect(get('/oauth/authorize')).to route_to('doorkeeper/authorizations#new')
6 | end
7 |
8 | it 'POST /oauth/authorize routes to authorizations controller' do
9 | expect(post('/oauth/authorize')).to route_to('doorkeeper/authorizations#create')
10 | end
11 |
12 | it 'DELETE /oauth/authorize routes to authorizations controller' do
13 | expect(delete('/oauth/authorize')).to route_to('doorkeeper/authorizations#destroy')
14 | end
15 |
16 | it 'POST /oauth/token routes to tokens controller' do
17 | expect(post('/oauth/token')).to route_to('doorkeeper/tokens#create')
18 | end
19 |
20 | it 'POST /oauth/revoke routes to tokens controller' do
21 | expect(post('/oauth/revoke')).to route_to('doorkeeper/tokens#revoke')
22 | end
23 |
24 | it 'GET /oauth/applications routes to applications controller' do
25 | expect(get('/oauth/applications')).to route_to('doorkeeper/applications#index')
26 | end
27 |
28 | it 'GET /oauth/authorized_applications routes to authorized applications controller' do
29 | expect(get('/oauth/authorized_applications')).to route_to('doorkeeper/authorized_applications#index')
30 | end
31 |
32 | it 'GET /oauth/token/info route to authorzed tokeninfo controller' do
33 | expect(get('/oauth/token/info')).to route_to('doorkeeper/token_info#show')
34 | end
35 | end
36 |
--------------------------------------------------------------------------------
/lib/doorkeeper/oauth/helpers/scope_checker.rb:
--------------------------------------------------------------------------------
1 | module Doorkeeper
2 | module OAuth
3 | module Helpers
4 | module ScopeChecker
5 | class Validator
6 | attr_reader :parsed_scopes, :scope_str
7 |
8 | def initialize(scope_str, server_scopes, application_scopes)
9 | @parsed_scopes = OAuth::Scopes.from_string(scope_str)
10 | @scope_str = scope_str
11 | @valid_scopes = valid_scopes(server_scopes, application_scopes)
12 | end
13 |
14 | def valid?
15 | scope_str.present? &&
16 | scope_str !~ /[\n\r\t]/ &&
17 | @valid_scopes.has_scopes?(parsed_scopes)
18 | end
19 |
20 | def match?
21 | valid? && parsed_scopes.has_scopes?(@valid_scopes)
22 | end
23 |
24 | private
25 |
26 | def valid_scopes(server_scopes, application_scopes)
27 | if application_scopes.present?
28 | application_scopes
29 | else
30 | server_scopes
31 | end
32 | end
33 | end
34 |
35 | def self.valid?(scope_str, server_scopes, application_scopes = nil)
36 | Validator.new(scope_str, server_scopes, application_scopes).valid?
37 | end
38 |
39 | def self.match?(scope_str, server_scopes, application_scopes = nil)
40 | Validator.new(scope_str, server_scopes, application_scopes).match?
41 | end
42 | end
43 | end
44 | end
45 | end
46 |
--------------------------------------------------------------------------------
/spec/generators/migration_generator_spec.rb:
--------------------------------------------------------------------------------
1 | require 'spec_helper_integration'
2 | require 'generators/doorkeeper/migration_generator'
3 |
4 | describe 'Doorkeeper::MigrationGenerator' do
5 | include GeneratorSpec::TestCase
6 |
7 | tests Doorkeeper::MigrationGenerator
8 | destination ::File.expand_path('../tmp/dummy', __FILE__)
9 |
10 | describe 'after running the generator' do
11 | before :each do
12 | prepare_destination
13 | end
14 |
15 | context 'pre Rails 5.0.0' do
16 | it 'creates a migration with no version specifier' do
17 | expect(Rails).to receive(:version).at_least(:once).and_return '4.2.0'
18 | stub_const("Rails::VERSION::MAJOR", 4)
19 | stub_const("Rails::VERSION::MINOR", 2)
20 |
21 | run_generator
22 |
23 | assert_migration 'db/migrate/create_doorkeeper_tables.rb' do |migration|
24 | assert migration.include?("ActiveRecord::Migration\n")
25 | end
26 | end
27 | end
28 |
29 | context 'post Rails 5.0.0' do
30 | it 'creates a migration with a version specifier' do
31 | expect(Rails).to receive(:version).at_least(:once).and_return '5.0.0'
32 | stub_const("Rails::VERSION::MAJOR", 5)
33 | stub_const("Rails::VERSION::MINOR", 0)
34 |
35 | run_generator
36 |
37 | assert_migration 'db/migrate/create_doorkeeper_tables.rb' do |migration|
38 | assert migration.include?("ActiveRecord::Migration[5.0]\n")
39 | end
40 | end
41 | end
42 | end
43 | end
44 |
--------------------------------------------------------------------------------
/spec/lib/request/strategy_spec.rb:
--------------------------------------------------------------------------------
1 | require 'spec_helper'
2 | require 'doorkeeper/request/strategy'
3 |
4 | module Doorkeeper
5 | module Request
6 | describe Strategy do
7 | let(:server) { double }
8 | subject(:strategy) { Strategy.new(server) }
9 |
10 | describe :initialize do
11 | it "sets the server attribute" do
12 | expect(strategy.server).to eq server
13 | end
14 | end
15 |
16 | describe :request do
17 | it "requires an implementation" do
18 | expect { strategy.request }.to raise_exception NotImplementedError
19 | end
20 | end
21 |
22 | describe "a sample Strategy subclass" do
23 | let(:fake_request) { double }
24 |
25 | let(:strategy_class) do
26 | subclass = Class.new(Strategy) do
27 | class << self
28 | attr_accessor :fake_request
29 | end
30 |
31 | def request
32 | self.class.fake_request
33 | end
34 | end
35 |
36 | subclass.fake_request = fake_request
37 | subclass
38 | end
39 |
40 | subject(:strategy) { strategy_class.new(server) }
41 |
42 | it "provides a request implementation" do
43 | expect(strategy.request).to eq fake_request
44 | end
45 |
46 | it "authorizes the request" do
47 | expect(fake_request).to receive :authorize
48 | strategy.authorize
49 | end
50 | end
51 | end
52 | end
53 | end
54 |
--------------------------------------------------------------------------------
/lib/doorkeeper/grape/helpers.rb:
--------------------------------------------------------------------------------
1 | require 'doorkeeper/grape/authorization_decorator'
2 |
3 | module Doorkeeper
4 | module Grape
5 | module Helpers
6 | # These helpers are for grape >= 0.10
7 | extend ::Grape::API::Helpers
8 | include Doorkeeper::Rails::Helpers
9 |
10 | # endpoint specific scopes > parameter scopes > default scopes
11 | def doorkeeper_authorize!(*scopes)
12 | endpoint_scopes = env["api.endpoint"].route_setting(:scopes)
13 | scopes = if endpoint_scopes
14 | Doorkeeper::OAuth::Scopes.from_array(endpoint_scopes)
15 | elsif scopes && !scopes.empty?
16 | Doorkeeper::OAuth::Scopes.from_array(scopes)
17 | end
18 |
19 | super(*scopes)
20 | end
21 |
22 | def doorkeeper_render_error_with(error)
23 | status_code = case error.status
24 | when :unauthorized
25 | 401
26 | when :forbidden
27 | 403
28 | end
29 |
30 | error!({ error: error.description }, status_code, error.headers)
31 | end
32 |
33 | private
34 |
35 | def doorkeeper_token
36 | @_doorkeeper_token ||= OAuth::Token.authenticate(
37 | decorated_request,
38 | *Doorkeeper.configuration.access_token_methods
39 | )
40 | end
41 |
42 | def decorated_request
43 | AuthorizationDecorator.new(request)
44 | end
45 | end
46 | end
47 | end
48 |
--------------------------------------------------------------------------------
/spec/lib/oauth/client_spec.rb:
--------------------------------------------------------------------------------
1 | require 'spec_helper'
2 | require 'active_support/core_ext/module/delegation'
3 | require 'active_support/core_ext/string'
4 | require 'doorkeeper/oauth/client'
5 |
6 | module Doorkeeper::OAuth
7 | describe Client do
8 | describe :find do
9 | let(:method) { double }
10 |
11 | it 'finds the client via uid' do
12 | client = double
13 | expect(method).to receive(:call).with('uid').and_return(client)
14 | expect(Client.find('uid', method)).to be_a(Client)
15 | end
16 |
17 | it 'returns nil if client was not found' do
18 | expect(method).to receive(:call).with('uid').and_return(nil)
19 | expect(Client.find('uid', method)).to be_nil
20 | end
21 | end
22 |
23 | describe :authenticate do
24 | it 'returns the authenticated client via credentials' do
25 | credentials = Client::Credentials.new('some-uid', 'some-secret')
26 | authenticator = double
27 | expect(authenticator).to receive(:call).with('some-uid', 'some-secret').and_return(double)
28 | expect(Client.authenticate(credentials, authenticator)).to be_a(Client)
29 | end
30 |
31 | it 'returns nil if client was not authenticated' do
32 | credentials = Client::Credentials.new('some-uid', 'some-secret')
33 | authenticator = double
34 | expect(authenticator).to receive(:call).with('some-uid', 'some-secret').and_return(nil)
35 | expect(Client.authenticate(credentials, authenticator)).to be_nil
36 | end
37 | end
38 | end
39 | end
40 |
--------------------------------------------------------------------------------
/lib/doorkeeper/orm/active_record/access_token.rb:
--------------------------------------------------------------------------------
1 | module Doorkeeper
2 | class AccessToken < ActiveRecord::Base
3 | self.table_name = "#{table_name_prefix}oauth_access_tokens#{table_name_suffix}".to_sym
4 |
5 | include AccessTokenMixin
6 |
7 | # Deletes all the Access Tokens created for the specific
8 | # Application and Resource Owner.
9 | #
10 | # @param application_id [Integer] Application ID
11 | # @param resource_owner [ActiveRecord::Base] Resource Owner model instance
12 | #
13 | def self.delete_all_for(application_id, resource_owner)
14 | where(application_id: application_id,
15 | resource_owner_id: resource_owner.id).delete_all
16 | end
17 | private_class_method :delete_all_for
18 |
19 | # Searches for not revoked Access Tokens associated with the
20 | # specific Resource Owner.
21 | #
22 | # @param resource_owner [ActiveRecord::Base]
23 | # Resource Owner model instance
24 | #
25 | # @return [ActiveRecord::Relation]
26 | # active Access Tokens for Resource Owner
27 | #
28 | def self.active_for(resource_owner)
29 | where(resource_owner_id: resource_owner.id, revoked_at: nil)
30 | end
31 |
32 | # ORM-specific order method.
33 | def self.order_method
34 | :order
35 | end
36 |
37 | def self.refresh_token_revoked_on_use?
38 | column_names.include?('previous_refresh_token')
39 | end
40 |
41 | # ORM-specific DESC order for `:created_at` column.
42 | def self.created_at_desc
43 | 'created_at desc'
44 | end
45 | end
46 | end
47 |
--------------------------------------------------------------------------------
/spec/lib/oauth/authorization/uri_builder_spec.rb:
--------------------------------------------------------------------------------
1 | require 'spec_helper'
2 | require 'active_support/core_ext/string'
3 | require 'uri'
4 | require 'rack/utils'
5 | require 'doorkeeper/oauth/authorization/uri_builder'
6 |
7 | module Doorkeeper::OAuth::Authorization
8 | describe URIBuilder do
9 | subject { URIBuilder }
10 |
11 | describe :uri_with_query do
12 | it 'returns the uri with query' do
13 | uri = subject.uri_with_query 'http://example.com/', parameter: 'value'
14 | expect(uri).to eq('http://example.com/?parameter=value')
15 | end
16 |
17 | it 'rejects nil values' do
18 | uri = subject.uri_with_query 'http://example.com/', parameter: ''
19 | expect(uri).to eq('http://example.com/?')
20 | end
21 |
22 | it 'preserves original query parameters' do
23 | uri = subject.uri_with_query 'http://example.com/?query1=value', parameter: 'value'
24 | expect(uri).to match(/query1=value/)
25 | expect(uri).to match(/parameter=value/)
26 | end
27 | end
28 |
29 | describe :uri_with_fragment do
30 | it 'returns uri with parameters as fragments' do
31 | uri = subject.uri_with_fragment 'http://example.com/', parameter: 'value'
32 | expect(uri).to eq('http://example.com/#parameter=value')
33 | end
34 |
35 | it 'preserves original query parameters' do
36 | uri = subject.uri_with_fragment 'http://example.com/?query1=value1', parameter: 'value'
37 | expect(uri).to eq('http://example.com/?query1=value1#parameter=value')
38 | end
39 | end
40 | end
41 | end
42 |
--------------------------------------------------------------------------------
/lib/doorkeeper/oauth/authorization_code_request.rb:
--------------------------------------------------------------------------------
1 | module Doorkeeper
2 | module OAuth
3 | class AuthorizationCodeRequest < BaseRequest
4 | validate :attributes, error: :invalid_request
5 | validate :client, error: :invalid_client
6 | validate :grant, error: :invalid_grant
7 | validate :redirect_uri, error: :invalid_grant
8 |
9 | attr_accessor :server, :grant, :client, :redirect_uri, :access_token
10 |
11 | def initialize(server, grant, client, parameters = {})
12 | @server = server
13 | @client = client
14 | @grant = grant
15 | @redirect_uri = parameters[:redirect_uri]
16 | end
17 |
18 | private
19 |
20 | def before_successful_response
21 | grant.transaction do
22 | grant.lock!
23 | raise Errors::InvalidGrantReuse if grant.revoked?
24 |
25 | grant.revoke
26 | find_or_create_access_token(grant.application,
27 | grant.resource_owner_id,
28 | grant.scopes,
29 | server)
30 | end
31 | end
32 |
33 | def validate_attributes
34 | redirect_uri.present?
35 | end
36 |
37 | def validate_client
38 | !!client
39 | end
40 |
41 | def validate_grant
42 | return false unless grant && grant.application_id == client.id
43 | grant.accessible?
44 | end
45 |
46 | def validate_redirect_uri
47 | grant.redirect_uri == redirect_uri
48 | end
49 | end
50 | end
51 | end
52 |
--------------------------------------------------------------------------------
/lib/doorkeeper/models/concerns/revocable.rb:
--------------------------------------------------------------------------------
1 | module Doorkeeper
2 | module Models
3 | module Revocable
4 | # Revokes the object (updates `:revoked_at` attribute setting its value
5 | # to the specific time).
6 | #
7 | # @param clock [Time] time object
8 | #
9 | def revoke(clock = Time)
10 | update_attribute :revoked_at, clock.now.utc
11 | end
12 |
13 | # Indicates whether the object has been revoked.
14 | #
15 | # @return [Boolean] true if revoked, false in other case
16 | #
17 | def revoked?
18 | !!(revoked_at && revoked_at <= Time.now.utc)
19 | end
20 |
21 | # Revokes token with `:refresh_token` equal to `:previous_refresh_token`
22 | # and clears `:previous_refresh_token` attribute.
23 | #
24 | def revoke_previous_refresh_token!
25 | return unless refresh_token_revoked_on_use?
26 | old_refresh_token.revoke if old_refresh_token
27 | update_attribute :previous_refresh_token, ""
28 | end
29 |
30 | private
31 |
32 | # Searches for Access Token record with `:refresh_token` equal to
33 | # `:previous_refresh_token` value.
34 | #
35 | # @return [Doorkeeper::AccessToken, nil]
36 | # Access Token record or nil if nothing found
37 | #
38 | def old_refresh_token
39 | @old_refresh_token ||=
40 | AccessToken.by_refresh_token(previous_refresh_token)
41 | end
42 |
43 | def refresh_token_revoked_on_use?
44 | AccessToken.refresh_token_revoked_on_use?
45 | end
46 | end
47 | end
48 | end
49 |
--------------------------------------------------------------------------------
/spec/lib/oauth/client_credentials/creator_spec.rb:
--------------------------------------------------------------------------------
1 | require 'spec_helper_integration'
2 |
3 | class Doorkeeper::OAuth::ClientCredentialsRequest
4 | describe Creator do
5 | let(:client) { FactoryGirl.create :application }
6 | let(:scopes) { Doorkeeper::OAuth::Scopes.from_string('public') }
7 |
8 | it 'creates a new token' do
9 | expect do
10 | subject.call(client, scopes)
11 | end.to change { Doorkeeper::AccessToken.count }.by(1)
12 | end
13 |
14 | context "when reuse_access_token is true" do
15 | it "returns the existing valid token" do
16 | allow(Doorkeeper.configuration).to receive(:reuse_access_token).and_return(true)
17 | existing_token = subject.call(client, scopes)
18 |
19 | result = subject.call(client, scopes)
20 |
21 | expect(Doorkeeper::AccessToken.count).to eq(1)
22 | expect(result).to eq(existing_token)
23 | end
24 | end
25 |
26 | context "when reuse_access_token is false" do
27 | it "returns a new token" do
28 | allow(Doorkeeper.configuration).to receive(:reuse_access_token).and_return(false)
29 | existing_token = subject.call(client, scopes)
30 |
31 | result = subject.call(client, scopes)
32 |
33 | expect(Doorkeeper::AccessToken.count).to eq(2)
34 | expect(result).not_to eq(existing_token)
35 | end
36 | end
37 |
38 | it 'returns false if creation fails' do
39 | expect(Doorkeeper::AccessToken).to receive(:find_or_create_for).and_return(false)
40 | created = subject.call(client, scopes)
41 | expect(created).to be_falsey
42 | end
43 | end
44 | end
45 |
--------------------------------------------------------------------------------
/spec/lib/models/expirable_spec.rb:
--------------------------------------------------------------------------------
1 | require 'spec_helper'
2 | require 'active_support/time'
3 | require 'doorkeeper/models/concerns/expirable'
4 |
5 | describe 'Expirable' do
6 | subject do
7 | Class.new do
8 | include Doorkeeper::Models::Expirable
9 | end.new
10 | end
11 |
12 | before do
13 | allow(subject).to receive(:created_at).and_return(1.minute.ago)
14 | end
15 |
16 | describe :expired? do
17 | it 'is not expired if time has not passed' do
18 | allow(subject).to receive(:expires_in).and_return(2.minutes)
19 | expect(subject).not_to be_expired
20 | end
21 |
22 | it 'is expired if time has passed' do
23 | allow(subject).to receive(:expires_in).and_return(10.seconds)
24 | expect(subject).to be_expired
25 | end
26 |
27 | it 'is not expired if expires_in is not set' do
28 | allow(subject).to receive(:expires_in).and_return(nil)
29 | expect(subject).not_to be_expired
30 | end
31 | end
32 |
33 | describe :expires_in_seconds do
34 | it 'should return the amount of time remaining until the token is expired' do
35 | allow(subject).to receive(:expires_in).and_return(2.minutes)
36 | expect(subject.expires_in_seconds).to eq(60)
37 | end
38 |
39 | it 'should return 0 when expired' do
40 | allow(subject).to receive(:expires_in).and_return(30.seconds)
41 | expect(subject.expires_in_seconds).to eq(0)
42 | end
43 |
44 | it 'should return nil when expires_in is nil' do
45 | allow(subject).to receive(:expires_in).and_return(nil)
46 | expect(subject.expires_in_seconds).to be_nil
47 | end
48 |
49 | end
50 | end
51 |
--------------------------------------------------------------------------------
/spec/support/helpers/authorization_request_helper.rb:
--------------------------------------------------------------------------------
1 | module AuthorizationRequestHelper
2 | def resource_owner_is_authenticated(resource_owner = nil)
3 | resource_owner ||= User.create!(name: 'Joe', password: 'sekret')
4 | Doorkeeper.configuration.instance_variable_set(:@authenticate_resource_owner, proc { resource_owner })
5 | end
6 |
7 | def resource_owner_is_not_authenticated
8 | Doorkeeper.configuration.instance_variable_set(:@authenticate_resource_owner, proc { redirect_to('/sign_in') })
9 | end
10 |
11 | def default_scopes_exist(*scopes)
12 | Doorkeeper.configuration.instance_variable_set(:@default_scopes, Doorkeeper::OAuth::Scopes.from_array(scopes))
13 | end
14 |
15 | def optional_scopes_exist(*scopes)
16 | Doorkeeper.configuration.instance_variable_set(:@optional_scopes, Doorkeeper::OAuth::Scopes.from_array(scopes))
17 | end
18 |
19 | def client_should_be_authorized(client)
20 | expect(client.access_grants.size).to eq(1)
21 | end
22 |
23 | def client_should_not_be_authorized(client)
24 | expect(client.size).to eq(0)
25 | end
26 |
27 | def i_should_be_on_client_callback(client)
28 | expect(client.redirect_uri).to eq("#{current_uri.scheme}://#{current_uri.host}#{current_uri.path}")
29 | end
30 |
31 | def allowing_forgery_protection(&block)
32 | _original_value = ActionController::Base.allow_forgery_protection
33 | ActionController::Base.allow_forgery_protection = true
34 |
35 | block.call
36 | ensure
37 | ActionController::Base.allow_forgery_protection = _original_value
38 | end
39 | end
40 |
41 | RSpec.configuration.send :include, AuthorizationRequestHelper
42 |
--------------------------------------------------------------------------------
/app/controllers/doorkeeper/applications_controller.rb:
--------------------------------------------------------------------------------
1 | module Doorkeeper
2 | class ApplicationsController < Doorkeeper::ApplicationController
3 | layout 'doorkeeper/admin'
4 |
5 | before_action :authenticate_admin!
6 | before_action :set_application, only: [:show, :edit, :update, :destroy]
7 |
8 | def index
9 | @applications = Application.all
10 | end
11 |
12 | def show; end
13 |
14 | def new
15 | @application = Application.new
16 | end
17 |
18 | def create
19 | @application = Application.new(application_params)
20 | if @application.save
21 | flash[:notice] = I18n.t(:notice, scope: [:doorkeeper, :flash, :applications, :create])
22 | redirect_to oauth_application_url(@application)
23 | else
24 | render :new
25 | end
26 | end
27 |
28 | def edit; end
29 |
30 | def update
31 | if @application.update_attributes(application_params)
32 | flash[:notice] = I18n.t(:notice, scope: [:doorkeeper, :flash, :applications, :update])
33 | redirect_to oauth_application_url(@application)
34 | else
35 | render :edit
36 | end
37 | end
38 |
39 | def destroy
40 | flash[:notice] = I18n.t(:notice, scope: [:doorkeeper, :flash, :applications, :destroy]) if @application.destroy
41 | redirect_to oauth_applications_url
42 | end
43 |
44 | private
45 |
46 | def set_application
47 | @application = Application.find(params[:id])
48 | end
49 |
50 | def application_params
51 | params.require(:doorkeeper_application).permit(:name, :redirect_uri, :scopes)
52 | end
53 | end
54 | end
55 |
--------------------------------------------------------------------------------
/spec/support/shared/models_shared_examples.rb:
--------------------------------------------------------------------------------
1 | shared_examples 'an accessible token' do
2 | describe :accessible? do
3 | it 'is accessible if token is not expired' do
4 | allow(subject).to receive(:expired?).and_return(false)
5 | should be_accessible
6 | end
7 |
8 | it 'is not accessible if token is expired' do
9 | allow(subject).to receive(:expired?).and_return(true)
10 | should_not be_accessible
11 | end
12 | end
13 | end
14 |
15 | shared_examples 'a revocable token' do
16 | describe :accessible? do
17 | before { subject.save! }
18 |
19 | it 'is accessible if token is not revoked' do
20 | expect(subject).to be_accessible
21 | end
22 |
23 | it 'is not accessible if token is revoked' do
24 | subject.revoke
25 | expect(subject).not_to be_accessible
26 | end
27 | end
28 | end
29 |
30 | shared_examples 'a unique token' do
31 | describe :token do
32 | it 'is generated before validation' do
33 | expect { subject.valid? }.to change { subject.token }.from(nil)
34 | end
35 |
36 | it 'is not valid if token exists' do
37 | token1 = FactoryGirl.create factory_name
38 | token2 = FactoryGirl.create factory_name
39 | token2.token = token1.token
40 | expect(token2).not_to be_valid
41 | end
42 |
43 | it 'expects database to throw an error when tokens are the same' do
44 | token1 = FactoryGirl.create factory_name
45 | token2 = FactoryGirl.create factory_name
46 | token2.token = token1.token
47 | expect do
48 | token2.save!(validate: false)
49 | end.to raise_error(uniqueness_error)
50 | end
51 | end
52 | end
53 |
--------------------------------------------------------------------------------
/spec/lib/server_spec.rb:
--------------------------------------------------------------------------------
1 | require 'spec_helper'
2 |
3 | describe Doorkeeper::Server do
4 | let(:fake_class) { double :fake_class }
5 |
6 | subject do
7 | described_class.new
8 | end
9 |
10 | describe '.authorization_request' do
11 | it 'raises error when strategy does not exist' do
12 | expect do
13 | subject.authorization_request(:duh)
14 | end.to raise_error(Doorkeeper::Errors::InvalidAuthorizationStrategy)
15 | end
16 |
17 | it 'raises error when strategy does not match phase' do
18 | expect do
19 | subject.token_request(:code)
20 | end.to raise_error(Doorkeeper::Errors::InvalidTokenStrategy)
21 | end
22 |
23 | context 'when only Authorization Code strategy is enabled' do
24 | before do
25 | allow(Doorkeeper.configuration).
26 | to receive(:grant_flows).
27 | and_return(['authorization_code'])
28 | end
29 |
30 | it 'raises error when using the disabled Implicit strategy' do
31 | expect do
32 | subject.authorization_request(:token)
33 | end.to raise_error(Doorkeeper::Errors::InvalidAuthorizationStrategy)
34 | end
35 |
36 | it 'raises error when using the disabled Client Credentials strategy' do
37 | expect do
38 | subject.token_request(:client_credentials)
39 | end.to raise_error(Doorkeeper::Errors::InvalidTokenStrategy)
40 | end
41 | end
42 |
43 | it 'builds the request with selected strategy' do
44 | stub_const 'Doorkeeper::Request::Code', fake_class
45 | expect(fake_class).to receive(:new).with(subject)
46 | subject.authorization_request :code
47 | end
48 | end
49 | end
50 |
--------------------------------------------------------------------------------
/app/controllers/doorkeeper/authorizations_controller.rb:
--------------------------------------------------------------------------------
1 | module Doorkeeper
2 | class AuthorizationsController < Doorkeeper::ApplicationController
3 | before_action :authenticate_resource_owner!
4 |
5 | def new
6 | if pre_auth.authorizable?
7 | if skip_authorization? || matching_token?
8 | auth = authorization.authorize
9 | redirect_to auth.redirect_uri
10 | else
11 | render :new
12 | end
13 | else
14 | render :error
15 | end
16 | end
17 |
18 | # TODO: Handle raise invalid authorization
19 | def create
20 | redirect_or_render authorization.authorize
21 | end
22 |
23 | def destroy
24 | redirect_or_render authorization.deny
25 | end
26 |
27 | private
28 |
29 | def matching_token?
30 | AccessToken.matching_token_for pre_auth.client,
31 | current_resource_owner.id,
32 | pre_auth.scopes
33 | end
34 |
35 | def redirect_or_render(auth)
36 | if auth.redirectable?
37 | redirect_to auth.redirect_uri
38 | else
39 | render json: auth.body, status: auth.status
40 | end
41 | end
42 |
43 | def pre_auth
44 | @pre_auth ||= OAuth::PreAuthorization.new(Doorkeeper.configuration,
45 | server.client_via_uid,
46 | params)
47 | end
48 |
49 | def authorization
50 | @authorization ||= strategy.request
51 | end
52 |
53 | def strategy
54 | @strategy ||= server.authorization_request pre_auth.response_type
55 | end
56 | end
57 | end
58 |
--------------------------------------------------------------------------------
/lib/doorkeeper/models/access_grant_mixin.rb:
--------------------------------------------------------------------------------
1 | module Doorkeeper
2 | module AccessGrantMixin
3 | extend ActiveSupport::Concern
4 |
5 | include OAuth::Helpers
6 | include Models::Expirable
7 | include Models::Revocable
8 | include Models::Accessible
9 | include Models::Scopes
10 | include ActiveModel::MassAssignmentSecurity if defined?(::ProtectedAttributes)
11 |
12 | included do
13 | belongs_to_options = {
14 | class_name: 'Doorkeeper::Application',
15 | inverse_of: :access_grants
16 | }
17 | if defined?(ActiveRecord::Base) && ActiveRecord::VERSION::MAJOR >= 5
18 | belongs_to_options[:optional] = true
19 | end
20 |
21 | belongs_to :application, belongs_to_options
22 |
23 | validates :resource_owner_id, :application_id, :token, :expires_in, :redirect_uri, presence: true
24 | validates :token, uniqueness: true
25 |
26 | before_validation :generate_token, on: :create
27 | end
28 |
29 | module ClassMethods
30 | # Searches for Doorkeeper::AccessGrant record with the
31 | # specific token value.
32 | #
33 | # @param token [#to_s] token value (any object that responds to `#to_s`)
34 | #
35 | # @return [Doorkeeper::AccessGrant, nil] AccessGrant object or nil
36 | # if there is no record with such token
37 | #
38 | def by_token(token)
39 | find_by(token: token.to_s)
40 | end
41 | end
42 |
43 | private
44 |
45 | # Generates token value with UniqueToken class.
46 | #
47 | # @return [String] token value
48 | #
49 | def generate_token
50 | self.token = UniqueToken.generate
51 | end
52 | end
53 | end
54 |
--------------------------------------------------------------------------------
/spec/lib/oauth/invalid_token_response_spec.rb:
--------------------------------------------------------------------------------
1 | require 'spec_helper'
2 | require 'active_model'
3 | require 'doorkeeper'
4 | require 'doorkeeper/oauth/invalid_token_response'
5 |
6 | module Doorkeeper::OAuth
7 | describe InvalidTokenResponse do
8 | describe "#name" do
9 | it { expect(subject.name).to eq(:invalid_token) }
10 | end
11 |
12 | describe "#status" do
13 | it { expect(subject.status).to eq(:unauthorized) }
14 | end
15 |
16 | describe :from_access_token do
17 | let(:response) { InvalidTokenResponse.from_access_token(access_token) }
18 |
19 | context "revoked" do
20 | let(:access_token) { double(revoked?: true, expired?: true) }
21 |
22 | it "sets a description" do
23 | expect(response.description).to include("revoked")
24 | end
25 |
26 | it "sets the reason" do
27 | expect(response.reason).to eq(:revoked)
28 | end
29 | end
30 |
31 | context "expired" do
32 | let(:access_token) { double(revoked?: false, expired?: true) }
33 |
34 | it "sets a description" do
35 | expect(response.description).to include("expired")
36 | end
37 |
38 | it "sets the reason" do
39 | expect(response.reason).to eq(:expired)
40 | end
41 | end
42 |
43 | context "unknown" do
44 | let(:access_token) { double(revoked?: false, expired?: false) }
45 |
46 | it "sets a description" do
47 | expect(response.description).to include("invalid")
48 | end
49 |
50 | it "sets the reason" do
51 | expect(response.reason).to eq(:unknown)
52 | end
53 | end
54 | end
55 | end
56 | end
57 |
--------------------------------------------------------------------------------
/spec/dummy/config/routes.rb:
--------------------------------------------------------------------------------
1 | Rails.application.routes.draw do
2 | use_doorkeeper
3 | use_doorkeeper scope: 'scope'
4 |
5 | scope 'inner_space' do
6 | use_doorkeeper scope: 'scope' do
7 | controllers authorizations: 'custom_authorizations',
8 | tokens: 'custom_authorizations',
9 | applications: 'custom_authorizations',
10 | token_info: 'custom_authorizations'
11 |
12 | as authorizations: 'custom_auth',
13 | tokens: 'custom_token',
14 | token_info: 'custom_token_info'
15 | end
16 | end
17 |
18 | scope 'space' do
19 | use_doorkeeper do
20 | controllers authorizations: 'custom_authorizations',
21 | tokens: 'custom_authorizations',
22 | applications: 'custom_authorizations',
23 | token_info: 'custom_authorizations'
24 |
25 | as authorizations: 'custom_auth',
26 | tokens: 'custom_token',
27 | token_info: 'custom_token_info'
28 | end
29 | end
30 |
31 | scope 'outer_space' do
32 | use_doorkeeper do
33 | controllers authorizations: 'custom_authorizations',
34 | tokens: 'custom_authorizations',
35 | token_info: 'custom_authorizations'
36 |
37 | as authorizations: 'custom_auth',
38 | tokens: 'custom_token',
39 | token_info: 'custom_token_info'
40 |
41 | skip_controllers :tokens, :applications, :token_info
42 | end
43 | end
44 |
45 | get 'metal.json' => 'metal#index'
46 |
47 | get '/callback', to: 'home#callback'
48 | get '/sign_in', to: 'home#sign_in'
49 | resources :semi_protected_resources
50 | resources :full_protected_resources
51 | root to: 'home#index'
52 | end
53 |
--------------------------------------------------------------------------------
/app/views/doorkeeper/authorizations/new.html.erb:
--------------------------------------------------------------------------------
1 |
4 |
5 |
6 |
7 | <%= raw t('.prompt', client_name: content_tag(:strong, class: 'text-info') { @pre_auth.client.name }) %>
8 |
9 |
10 | <% if @pre_auth.scopes.count > 0 %>
11 |
12 |
<%= t('.able_to') %>:
13 |
14 |
15 | <% @pre_auth.scopes.each do |scope| %>
16 | - <%= t scope, scope: [:doorkeeper, :scopes] %>
17 | <% end %>
18 |
19 |
20 | <% end %>
21 |
22 |
23 | <%= form_tag oauth_authorization_path, method: :post do %>
24 | <%= hidden_field_tag :client_id, @pre_auth.client.uid %>
25 | <%= hidden_field_tag :redirect_uri, @pre_auth.redirect_uri %>
26 | <%= hidden_field_tag :state, @pre_auth.state %>
27 | <%= hidden_field_tag :response_type, @pre_auth.response_type %>
28 | <%= hidden_field_tag :scope, @pre_auth.scope %>
29 | <%= submit_tag t('doorkeeper.authorizations.buttons.authorize'), class: "btn btn-success btn-lg btn-block" %>
30 | <% end %>
31 | <%= form_tag oauth_authorization_path, method: :delete do %>
32 | <%= hidden_field_tag :client_id, @pre_auth.client.uid %>
33 | <%= hidden_field_tag :redirect_uri, @pre_auth.redirect_uri %>
34 | <%= hidden_field_tag :state, @pre_auth.state %>
35 | <%= hidden_field_tag :response_type, @pre_auth.response_type %>
36 | <%= hidden_field_tag :scope, @pre_auth.scope %>
37 | <%= submit_tag t('doorkeeper.authorizations.buttons.deny'), class: "btn btn-danger btn-lg btn-block" %>
38 | <% end %>
39 |
40 |
41 |
--------------------------------------------------------------------------------
/lib/doorkeeper/helpers/controller.rb:
--------------------------------------------------------------------------------
1 | # Define methods that can be called in any controller that inherits from
2 | # Doorkeeper::ApplicationMetalController or Doorkeeper::ApplicationController
3 | module Doorkeeper
4 | module Helpers
5 | module Controller
6 | private
7 |
8 | def authenticate_resource_owner! # :doc:
9 | current_resource_owner
10 | end
11 |
12 | def current_resource_owner # :doc:
13 | instance_eval(&Doorkeeper.configuration.authenticate_resource_owner)
14 | end
15 |
16 | def resource_owner_from_credentials
17 | instance_eval(&Doorkeeper.configuration.resource_owner_from_credentials)
18 | end
19 |
20 | def authenticate_admin! # :doc:
21 | instance_eval(&Doorkeeper.configuration.authenticate_admin)
22 | end
23 |
24 | def server
25 | @server ||= Server.new(self)
26 | end
27 |
28 | def doorkeeper_token # :doc:
29 | @token ||= OAuth::Token.authenticate request, *config_methods
30 | end
31 |
32 | def config_methods
33 | @methods ||= Doorkeeper.configuration.access_token_methods
34 | end
35 |
36 | def get_error_response_from_exception(exception)
37 | OAuth::ErrorResponse.new name: exception.type, state: params[:state]
38 | end
39 |
40 | def handle_token_exception(exception)
41 | error = get_error_response_from_exception exception
42 | headers.merge! error.headers
43 | self.response_body = error.body.to_json
44 | self.status = error.status
45 | end
46 |
47 | def skip_authorization?
48 | !!instance_exec([@server.current_resource_owner, @pre_auth.client], &Doorkeeper.configuration.skip_authorization)
49 | end
50 | end
51 | end
52 | end
53 |
--------------------------------------------------------------------------------
/lib/doorkeeper/oauth/authorization/token.rb:
--------------------------------------------------------------------------------
1 | module Doorkeeper
2 | module OAuth
3 | module Authorization
4 | class Token
5 | attr_accessor :pre_auth, :resource_owner, :token
6 |
7 | def initialize(pre_auth, resource_owner)
8 | @pre_auth = pre_auth
9 | @resource_owner = resource_owner
10 | end
11 |
12 | def self.access_token_expires_in(server, pre_auth_or_oauth_client)
13 | if expiration = custom_expiration(server, pre_auth_or_oauth_client)
14 | expiration
15 | else
16 | server.access_token_expires_in
17 | end
18 | end
19 |
20 | def issue_token
21 | @token ||= AccessToken.find_or_create_for(
22 | pre_auth.client,
23 | resource_owner.id,
24 | pre_auth.scopes,
25 | self.class.access_token_expires_in(configuration, pre_auth),
26 | false
27 | )
28 | end
29 |
30 | def native_redirect
31 | {
32 | controller: 'doorkeeper/token_info',
33 | action: :show,
34 | access_token: token.token
35 | }
36 | end
37 |
38 | private
39 |
40 | def self.custom_expiration(server, pre_auth_or_oauth_client)
41 | oauth_client = if pre_auth_or_oauth_client.respond_to?(:client)
42 | pre_auth_or_oauth_client.client
43 | else
44 | pre_auth_or_oauth_client
45 | end
46 |
47 | server.custom_access_token_expires_in.call(oauth_client)
48 | end
49 |
50 | def configuration
51 | Doorkeeper.configuration
52 | end
53 | end
54 | end
55 | end
56 | end
57 |
--------------------------------------------------------------------------------
/spec/controllers/token_info_controller_spec.rb:
--------------------------------------------------------------------------------
1 | require 'spec_helper_integration'
2 |
3 | describe Doorkeeper::TokenInfoController do
4 | describe 'when requesting tokeninfo with valid token' do
5 | let(:doorkeeper_token) { FactoryGirl.create(:access_token) }
6 |
7 | before(:each) do
8 | allow(controller).to receive(:doorkeeper_token) { doorkeeper_token }
9 | end
10 |
11 | def do_get
12 | get :show
13 | end
14 |
15 | describe 'successful request' do
16 |
17 | it 'responds with tokeninfo' do
18 | do_get
19 | expect(response.body).to eq(doorkeeper_token.to_json)
20 | end
21 |
22 | it 'responds with a 200 status' do
23 | do_get
24 | expect(response.status).to eq 200
25 | end
26 | end
27 |
28 | describe 'invalid token response' do
29 | before(:each) do
30 | allow(controller).to receive(:doorkeeper_token).and_return(nil)
31 | end
32 | it 'responds with 401 when doorkeeper_token is not valid' do
33 | do_get
34 | expect(response.status).to eq 401
35 | expect(response.headers['WWW-Authenticate']).to match(/^Bearer/)
36 | end
37 |
38 | it 'responds with 401 when doorkeeper_token is invalid, expired or revoked' do
39 | allow(controller).to receive(:doorkeeper_token).and_return(doorkeeper_token)
40 | allow(doorkeeper_token).to receive(:accessible?).and_return(false)
41 | do_get
42 | expect(response.status).to eq 401
43 | expect(response.headers['WWW-Authenticate']).to match(/^Bearer/)
44 | end
45 |
46 | it 'responds body message for error' do
47 | do_get
48 | expect(response.body).to eq(Doorkeeper::OAuth::ErrorResponse.new(name: :invalid_request, status: :unauthorized).body.to_json)
49 | end
50 | end
51 | end
52 | end
53 |
--------------------------------------------------------------------------------
/lib/doorkeeper/oauth/error_response.rb:
--------------------------------------------------------------------------------
1 | module Doorkeeper
2 | module OAuth
3 | class ErrorResponse < BaseResponse
4 | include OAuth::Helpers
5 |
6 | def self.from_request(request, attributes = {})
7 | state = request.state if request.respond_to?(:state)
8 | new(attributes.merge(name: request.error, state: state))
9 | end
10 |
11 | delegate :name, :description, :state, to: :@error
12 |
13 | def initialize(attributes = {})
14 | @error = OAuth::Error.new(*attributes.values_at(:name, :state))
15 | @redirect_uri = attributes[:redirect_uri]
16 | @response_on_fragment = attributes[:response_on_fragment]
17 | end
18 |
19 | def body
20 | {
21 | error: name,
22 | error_description: description,
23 | state: state
24 | }.reject { |_, v| v.blank? }
25 | end
26 |
27 | def status
28 | :unauthorized
29 | end
30 |
31 | def redirectable?
32 | name != :invalid_redirect_uri && name != :invalid_client &&
33 | !URIChecker.native_uri?(@redirect_uri)
34 | end
35 |
36 | def redirect_uri
37 | if @response_on_fragment
38 | Authorization::URIBuilder.uri_with_fragment @redirect_uri, body
39 | else
40 | Authorization::URIBuilder.uri_with_query @redirect_uri, body
41 | end
42 | end
43 |
44 | def headers
45 | { 'Cache-Control' => 'no-store',
46 | 'Pragma' => 'no-cache',
47 | 'Content-Type' => 'application/json; charset=utf-8',
48 | 'WWW-Authenticate' => authenticate_info }
49 | end
50 |
51 | protected
52 |
53 | delegate :realm, to: :configuration
54 |
55 | def configuration
56 | Doorkeeper.configuration
57 | end
58 |
59 | private
60 |
61 | def authenticate_info
62 | %(Bearer realm="#{realm}", error="#{name}", error_description="#{description}")
63 | end
64 | end
65 | end
66 | end
67 |
--------------------------------------------------------------------------------
/lib/doorkeeper/oauth/pre_authorization.rb:
--------------------------------------------------------------------------------
1 | module Doorkeeper
2 | module OAuth
3 | class PreAuthorization
4 | include Validations
5 |
6 | validate :response_type, error: :unsupported_response_type
7 | validate :client, error: :invalid_client
8 | validate :scopes, error: :invalid_scope
9 | validate :redirect_uri, error: :invalid_redirect_uri
10 |
11 | attr_accessor :server, :client, :response_type, :redirect_uri, :state
12 | attr_writer :scope
13 |
14 | def initialize(server, client, attrs = {})
15 | @server = server
16 | @client = client
17 | @response_type = attrs[:response_type]
18 | @redirect_uri = attrs[:redirect_uri]
19 | @scope = attrs[:scope]
20 | @state = attrs[:state]
21 | end
22 |
23 | def authorizable?
24 | valid?
25 | end
26 |
27 | def scopes
28 | Scopes.from_string scope
29 | end
30 |
31 | def scope
32 | @scope.presence || server.default_scopes.to_s
33 | end
34 |
35 | def error_response
36 | OAuth::ErrorResponse.from_request(self)
37 | end
38 |
39 | private
40 |
41 | def validate_response_type
42 | server.authorization_response_types.include? response_type
43 | end
44 |
45 | def validate_client
46 | client.present?
47 | end
48 |
49 | def validate_scopes
50 | return true unless scope.present?
51 | Helpers::ScopeChecker.valid?(
52 | scope,
53 | server.scopes,
54 | client.application.scopes
55 | )
56 | end
57 |
58 | # TODO: test uri should be matched against the client's one
59 | def validate_redirect_uri
60 | return false unless redirect_uri.present?
61 | Helpers::URIChecker.native_uri?(redirect_uri) ||
62 | Helpers::URIChecker.valid_for_authorization?(redirect_uri, client.redirect_uri)
63 | end
64 | end
65 | end
66 | end
67 |
--------------------------------------------------------------------------------
/spec/lib/models/revocable_spec.rb:
--------------------------------------------------------------------------------
1 | require 'spec_helper'
2 | require 'active_support/core_ext/object/blank'
3 | require 'doorkeeper/models/concerns/revocable'
4 |
5 | describe 'Revocable' do
6 | subject do
7 | Class.new do
8 | include Doorkeeper::Models::Revocable
9 | end.new
10 | end
11 |
12 | describe :revoke do
13 | it 'updates :revoked_at attribute with current time' do
14 | utc = double utc: double
15 | clock = double now: utc
16 | expect(subject).to receive(:update_attribute).with(:revoked_at, clock.now.utc)
17 | subject.revoke(clock)
18 | end
19 | end
20 |
21 | describe :revoked? do
22 | it 'is revoked if :revoked_at has passed' do
23 | allow(subject).to receive(:revoked_at).and_return(Time.now.utc - 1000)
24 | expect(subject).to be_revoked
25 | end
26 |
27 | it 'is not revoked if :revoked_at has not passed' do
28 | allow(subject).to receive(:revoked_at).and_return(Time.now.utc + 1000)
29 | expect(subject).not_to be_revoked
30 | end
31 |
32 | it 'is not revoked if :revoked_at is not set' do
33 | allow(subject).to receive(:revoked_at).and_return(nil)
34 | expect(subject).not_to be_revoked
35 | end
36 | end
37 |
38 | describe :revoke_previous_refresh_token! do
39 | it "revokes the previous token if existing, and resets the
40 | `previous_refresh_token` attribute" do
41 | previous_token = FactoryGirl.create(
42 | :access_token,
43 | refresh_token: "refresh_token"
44 | )
45 | current_token = FactoryGirl.create(
46 | :access_token,
47 | previous_refresh_token: previous_token.refresh_token
48 | )
49 |
50 | expect_any_instance_of(
51 | Doorkeeper::AccessToken
52 | ).to receive(:revoke).and_call_original
53 | current_token.revoke_previous_refresh_token!
54 |
55 | expect(current_token.previous_refresh_token).to be_empty
56 | expect(previous_token.reload).to be_revoked
57 | end
58 | end
59 | end
60 |
--------------------------------------------------------------------------------
/lib/doorkeeper/oauth/token.rb:
--------------------------------------------------------------------------------
1 | module Doorkeeper
2 | module OAuth
3 | class Token
4 | class << self
5 | def from_request(request, *methods)
6 | methods.inject(nil) do |credentials, method|
7 | method = self.method(method) if method.is_a?(Symbol)
8 | credentials = method.call(request)
9 | break credentials unless credentials.blank?
10 | end
11 | end
12 |
13 | def authenticate(request, *methods)
14 | if token = from_request(request, *methods)
15 | access_token = AccessToken.by_token(token)
16 | access_token.revoke_previous_refresh_token! if access_token
17 | access_token
18 | end
19 | end
20 |
21 | def from_access_token_param(request)
22 | request.parameters[:access_token]
23 | end
24 |
25 | def from_bearer_param(request)
26 | request.parameters[:bearer_token]
27 | end
28 |
29 | def from_bearer_authorization(request)
30 | pattern = /^Bearer /i
31 | header = request.authorization
32 | token_from_header(header, pattern) if match?(header, pattern)
33 | end
34 |
35 | def from_basic_authorization(request)
36 | pattern = /^Basic /i
37 | header = request.authorization
38 | token_from_basic_header(header, pattern) if match?(header, pattern)
39 | end
40 |
41 | private
42 |
43 | def token_from_basic_header(header, pattern)
44 | encoded_header = token_from_header(header, pattern)
45 | decode_basic_credentials_token(encoded_header)
46 | end
47 |
48 | def decode_basic_credentials_token(encoded_header)
49 | Base64.decode64(encoded_header).split(/:/, 2).first
50 | end
51 |
52 | def token_from_header(header, pattern)
53 | header.gsub pattern, ''
54 | end
55 |
56 | def match?(header, pattern)
57 | header && header.match(pattern)
58 | end
59 | end
60 | end
61 | end
62 | end
63 |
--------------------------------------------------------------------------------
/spec/dummy/config/environments/test.rb:
--------------------------------------------------------------------------------
1 | Dummy::Application.configure do
2 | # Settings specified here will take precedence over those in config/application.rb
3 |
4 | # The test environment is used exclusively to run your application's
5 | # test suite. You never need to work with it otherwise. Remember that
6 | # your test database is "scratch space" for the test suite and is wiped
7 | # and recreated between test runs. Don't rely on the data there!
8 | config.cache_classes = true
9 |
10 | # Do not eager load code on boot. This avoids loading your whole application
11 | # just for the purpose of running a single test. If you are using a tool that
12 | # preloads Rails for running tests, you may have to set it to true.
13 | config.eager_load = false
14 |
15 | # Show full error reports and disable caching
16 | config.consider_all_requests_local = true
17 | config.action_controller.perform_caching = false
18 |
19 | # Raise exceptions instead of rendering exception templates
20 | config.action_dispatch.show_exceptions = false
21 |
22 | # Disable request forgery protection in test environment
23 | config.action_controller.allow_forgery_protection = false
24 |
25 | # Tell Action Mailer not to deliver emails to the real world.
26 | # The :test delivery method accumulates sent emails in the
27 | # ActionMailer::Base.deliveries array.
28 | # config.action_mailer.delivery_method = :test
29 |
30 | # Use SQL instead of Active Record's schema dumper when creating the test database.
31 | # This is necessary if your schema can't be completely dumped by the schema dumper,
32 | # like if you have constraints or database-specific column types
33 | # config.active_record.schema_format = :sql
34 |
35 | # Print deprecation notices to the stderr
36 | config.active_support.deprecation = :stderr
37 |
38 | config.eager_load = true
39 |
40 | if DOORKEEPER_ORM == :active_record
41 | config.active_record.table_name_prefix = TABLE_NAME_PREFIX.to_s
42 | config.active_record.table_name_suffix = TABLE_NAME_SUFFIX.to_s
43 | end
44 | end
45 |
--------------------------------------------------------------------------------
/spec/requests/flows/implicit_grant_spec.rb:
--------------------------------------------------------------------------------
1 | require 'spec_helper_integration'
2 |
3 | feature 'Implicit Grant Flow (feature spec)' do
4 | background do
5 | config_is_set(:authenticate_resource_owner) { User.first || redirect_to('/sign_in') }
6 | config_is_set(:grant_flows, ["implicit"])
7 | client_exists
8 | create_resource_owner
9 | sign_in
10 | end
11 |
12 | scenario 'resource owner authorizes the client' do
13 | visit authorization_endpoint_url(client: @client, response_type: 'token')
14 | click_on 'Authorize'
15 |
16 | access_token_should_exist_for @client, @resource_owner
17 |
18 | i_should_be_on_client_callback @client
19 | end
20 | end
21 |
22 | describe 'Implicit Grant Flow (request spec)' do
23 | before do
24 | config_is_set(:authenticate_resource_owner) { User.first || redirect_to('/sign_in') }
25 | config_is_set(:grant_flows, ["implicit"])
26 | client_exists
27 | create_resource_owner
28 | end
29 |
30 | context 'token reuse' do
31 | it 'should return a new token each request' do
32 | allow(Doorkeeper.configuration).to receive(:reuse_access_token).and_return(false)
33 |
34 | token = client_is_authorized(@client, @resource_owner)
35 |
36 | post "/oauth/authorize",
37 | client_id: @client.uid,
38 | state: '',
39 | redirect_uri: @client.redirect_uri,
40 | response_type: 'token',
41 | commit: 'Authorize'
42 |
43 | expect(response.location).not_to include(token.token)
44 | end
45 |
46 | it 'should return the same token if it is still accessible' do
47 | allow(Doorkeeper.configuration).to receive(:reuse_access_token).and_return(true)
48 |
49 | token = client_is_authorized(@client, @resource_owner)
50 |
51 | post "/oauth/authorize",
52 | client_id: @client.uid,
53 | state: '',
54 | redirect_uri: @client.redirect_uri,
55 | response_type: 'token',
56 | commit: 'Authorize'
57 |
58 | expect(response.location).to include(token.token)
59 | end
60 | end
61 | end
62 |
--------------------------------------------------------------------------------
/spec/controllers/applications_controller_spec.rb:
--------------------------------------------------------------------------------
1 | require 'spec_helper_integration'
2 |
3 | module Doorkeeper
4 | describe ApplicationsController do
5 | context 'when admin is not authenticated' do
6 | before do
7 | allow(Doorkeeper.configuration).to receive(:authenticate_admin).and_return(proc do
8 | redirect_to main_app.root_url
9 | end)
10 | end
11 |
12 | it 'redirects as set in Doorkeeper.authenticate_admin' do
13 | get :index
14 | expect(response).to redirect_to(controller.main_app.root_url)
15 | end
16 |
17 | it 'does not create application' do
18 | expect do
19 | post :create, doorkeeper_application: {
20 | name: 'Example',
21 | redirect_uri: 'https://example.com' }
22 | end.to_not change { Doorkeeper::Application.count }
23 | end
24 | end
25 |
26 | context 'when admin is authenticated' do
27 | before do
28 | allow(Doorkeeper.configuration).to receive(:authenticate_admin).and_return(->(arg) { true })
29 | end
30 |
31 | it 'creates application' do
32 | expect do
33 | post :create, doorkeeper_application: {
34 | name: 'Example',
35 | redirect_uri: 'https://example.com' }
36 | end.to change { Doorkeeper::Application.count }.by(1)
37 | expect(response).to be_redirect
38 | end
39 |
40 | it 'does not allow mass assignment of uid or secret' do
41 | application = FactoryGirl.create(:application)
42 | put :update, id: application.id, doorkeeper_application: {
43 | uid: '1A2B3C4D',
44 | secret: '1A2B3C4D' }
45 |
46 | expect(application.reload.uid).not_to eq '1A2B3C4D'
47 | end
48 |
49 | it 'updates application' do
50 | application = FactoryGirl.create(:application)
51 | put :update, id: application.id, doorkeeper_application: {
52 | name: 'Example',
53 | redirect_uri: 'https://example.com' }
54 | expect(application.reload.name).to eq 'Example'
55 | end
56 | end
57 | end
58 | end
59 |
--------------------------------------------------------------------------------
/spec/requests/flows/client_credentials_spec.rb:
--------------------------------------------------------------------------------
1 | require 'spec_helper_integration'
2 |
3 | describe 'Client Credentials Request' do
4 | let(:client) { FactoryGirl.create :application }
5 |
6 | context 'a valid request' do
7 | it 'authorizes the client and returns the token response' do
8 | headers = authorization client.uid, client.secret
9 | params = { grant_type: 'client_credentials' }
10 |
11 | post '/oauth/token', params, headers
12 |
13 | should_have_json 'access_token', Doorkeeper::AccessToken.first.token
14 | should_have_json_within 'expires_in', Doorkeeper.configuration.access_token_expires_in, 1
15 | should_not_have_json 'scope'
16 | should_not_have_json 'refresh_token'
17 |
18 | should_not_have_json 'error'
19 | should_not_have_json 'error_description'
20 | end
21 |
22 | context 'with scopes' do
23 | before do
24 | optional_scopes_exist :write
25 | end
26 |
27 | it 'adds the scope to the token an returns in the response' do
28 | headers = authorization client.uid, client.secret
29 | params = { grant_type: 'client_credentials', scope: 'write' }
30 |
31 | post '/oauth/token', params, headers
32 |
33 | should_have_json 'access_token', Doorkeeper::AccessToken.first.token
34 | should_have_json 'scope', 'write'
35 | end
36 | end
37 | end
38 |
39 | context 'an invalid request' do
40 | it 'does not authorize the client and returns the error' do
41 | headers = {}
42 | params = { grant_type: 'client_credentials' }
43 |
44 | post '/oauth/token', params, headers
45 |
46 | should_have_json 'error', 'invalid_client'
47 | should_have_json 'error_description', translated_error_message(:invalid_client)
48 | should_not_have_json 'access_token'
49 |
50 | expect(response.status).to eq(401)
51 | end
52 | end
53 |
54 | def authorization(username, password)
55 | credentials = ActionController::HttpAuthentication::Basic.encode_credentials username, password
56 | { 'HTTP_AUTHORIZATION' => credentials }
57 | end
58 | end
59 |
--------------------------------------------------------------------------------
/spec/spec_helper_integration.rb:
--------------------------------------------------------------------------------
1 | if ENV['TRAVIS']
2 | require 'coveralls'
3 | Coveralls.wear!('rails') { add_filter('/spec/') }
4 | end
5 |
6 | ENV['RAILS_ENV'] ||= 'test'
7 | TABLE_NAME_PREFIX = ENV['table_name_prefix'] || nil
8 | TABLE_NAME_SUFFIX = ENV['table_name_suffix'] || nil
9 |
10 | orm = (ENV['BUNDLE_GEMFILE'] || '').match(/Gemfile\.(.+)\.rb/)
11 | DOORKEEPER_ORM = (orm && orm[1] || :active_record).to_sym
12 |
13 | $LOAD_PATH.unshift File.dirname(__FILE__)
14 |
15 | require 'capybara/rspec'
16 | require 'dummy/config/environment'
17 | require 'rspec/rails'
18 | require 'generator_spec/test_case'
19 | require 'database_cleaner'
20 |
21 | # Load JRuby SQLite3 if in that platform
22 | begin
23 | require 'jdbc/sqlite3'
24 | Jdbc::SQLite3.load_driver
25 | rescue LoadError
26 | end
27 |
28 | Rails.logger.info "====> Doorkeeper.orm = #{Doorkeeper.configuration.orm.inspect}"
29 | if Doorkeeper.configuration.orm == :active_record
30 | Rails.logger.info "======> active_record.table_name_prefix = #{Rails.configuration.active_record.table_name_prefix.inspect}"
31 | Rails.logger.info "======> active_record.table_name_suffix = #{Rails.configuration.active_record.table_name_suffix.inspect}"
32 | end
33 | Rails.logger.info "====> Rails version: #{Rails.version}"
34 | Rails.logger.info "====> Ruby version: #{RUBY_VERSION}"
35 |
36 | require "support/orm/#{DOORKEEPER_ORM}"
37 |
38 | ENGINE_RAILS_ROOT = File.join(File.dirname(__FILE__), '../')
39 |
40 | Dir["#{File.dirname(__FILE__)}/support/{dependencies,helpers,shared}/*.rb"].each { |f| require f }
41 |
42 | # Remove after dropping support of Rails 4.2
43 | require "#{File.dirname(__FILE__)}/support/http_method_shim.rb"
44 |
45 | RSpec.configure do |config|
46 | config.infer_spec_type_from_file_location!
47 | config.mock_with :rspec
48 |
49 | config.infer_base_class_for_anonymous_controllers = false
50 |
51 | config.include RSpec::Rails::RequestExampleGroup, type: :request
52 |
53 | config.before do
54 | DatabaseCleaner.start
55 | Doorkeeper.configure { orm DOORKEEPER_ORM }
56 | end
57 |
58 | config.after do
59 | DatabaseCleaner.clean
60 | end
61 |
62 | config.order = 'random'
63 | end
64 |
--------------------------------------------------------------------------------
/spec/support/shared/controllers_shared_context.rb:
--------------------------------------------------------------------------------
1 | shared_context 'valid token', token: :valid do
2 | let :token_string do
3 | '1A2B3C4D'
4 | end
5 |
6 | let :token do
7 | double(Doorkeeper::AccessToken,
8 | accessible?: true, includes_scope?: true, acceptable?: true,
9 | previous_refresh_token: "", revoke_previous_refresh_token!: true)
10 | end
11 |
12 | before :each do
13 | allow(
14 | Doorkeeper::AccessToken
15 | ).to receive(:by_token).with(token_string).and_return(token)
16 | end
17 | end
18 |
19 | shared_context 'invalid token', token: :invalid do
20 | let :token_string do
21 | '1A2B3C4D'
22 | end
23 |
24 | let :token do
25 | double(Doorkeeper::AccessToken,
26 | accessible?: false, revoked?: false, expired?: false,
27 | includes_scope?: false, acceptable?: false,
28 | previous_refresh_token: "", revoke_previous_refresh_token!: true)
29 | end
30 |
31 | before :each do
32 | allow(
33 | Doorkeeper::AccessToken
34 | ).to receive(:by_token).with(token_string).and_return(token)
35 | end
36 | end
37 |
38 | shared_context 'authenticated resource owner' do
39 | before do
40 | user = double(:resource, id: 1)
41 | allow(Doorkeeper.configuration).to receive(:authenticate_resource_owner) { proc { user } }
42 | end
43 | end
44 |
45 | shared_context 'not authenticated resource owner' do
46 | before do
47 | allow(Doorkeeper.configuration).to receive(:authenticate_resource_owner) { proc { redirect_to '/' } }
48 | end
49 | end
50 |
51 | shared_context 'valid authorization request' do
52 | let :authorization do
53 | double(:authorization, valid?: true, authorize: true, success_redirect_uri: 'http://something.com/cb?code=token')
54 | end
55 |
56 | before do
57 | allow(controller).to receive(:authorization) { authorization }
58 | end
59 | end
60 |
61 | shared_context 'invalid authorization request' do
62 | let :authorization do
63 | double(:authorization, valid?: false, authorize: false, redirect_on_error?: false)
64 | end
65 |
66 | before do
67 | allow(controller).to receive(:authorization) { authorization }
68 | end
69 | end
70 |
--------------------------------------------------------------------------------
/lib/doorkeeper/models/application_mixin.rb:
--------------------------------------------------------------------------------
1 | module Doorkeeper
2 | module ApplicationMixin
3 | extend ActiveSupport::Concern
4 |
5 | include OAuth::Helpers
6 | include Models::Scopes
7 | include ActiveModel::MassAssignmentSecurity if defined?(::ProtectedAttributes)
8 |
9 | included do
10 | has_many :access_grants, dependent: :delete_all, class_name: 'Doorkeeper::AccessGrant'
11 | has_many :access_tokens, dependent: :delete_all, class_name: 'Doorkeeper::AccessToken'
12 |
13 | validates :name, :secret, :uid, presence: true
14 | validates :uid, uniqueness: true
15 | validates :redirect_uri, redirect_uri: true
16 |
17 | before_validation :generate_uid, :generate_secret, on: :create
18 | end
19 |
20 | module ClassMethods
21 | # Returns an instance of the Doorkeeper::Application with
22 | # specific UID and secret.
23 | #
24 | # @param uid [#to_s] UID (any object that responds to `#to_s`)
25 | # @param secret [#to_s] secret (any object that responds to `#to_s`)
26 | #
27 | # @return [Doorkeeper::Application, nil] Application instance or nil
28 | # if there is no record with such credentials
29 | #
30 | def by_uid_and_secret(uid, secret)
31 | find_by(uid: uid.to_s, secret: secret.to_s)
32 | end
33 |
34 | # Returns an instance of the Doorkeeper::Application with specific UID.
35 | #
36 | # @param uid [#to_s] UID (any object that responds to `#to_s`)
37 | #
38 | # @return [Doorkeeper::Application, nil] Application instance or nil
39 | # if there is no record with such UID
40 | #
41 | def by_uid(uid)
42 | find_by(uid: uid.to_s)
43 | end
44 | end
45 |
46 | private
47 |
48 | def has_scopes?
49 | Doorkeeper.configuration.orm != :active_record ||
50 | Doorkeeper::Application.column_names.include?("scopes")
51 | end
52 |
53 | def generate_uid
54 | if uid.blank?
55 | self.uid = UniqueToken.generate
56 | end
57 | end
58 |
59 | def generate_secret
60 | if secret.blank?
61 | self.secret = UniqueToken.generate
62 | end
63 | end
64 | end
65 | end
66 |
--------------------------------------------------------------------------------
/spec/lib/oauth/helpers/scope_checker_spec.rb:
--------------------------------------------------------------------------------
1 | require 'spec_helper'
2 | require 'active_support/core_ext/string'
3 | require 'doorkeeper/oauth/helpers/scope_checker'
4 | require 'doorkeeper/oauth/scopes'
5 |
6 | module Doorkeeper::OAuth::Helpers
7 | describe ScopeChecker, '.valid?' do
8 | let(:server_scopes) { Doorkeeper::OAuth::Scopes.new }
9 |
10 | it 'is valid if scope is present' do
11 | server_scopes.add :scope
12 | expect(ScopeChecker.valid?('scope', server_scopes)).to be_truthy
13 | end
14 |
15 | it 'is invalid if includes tabs space' do
16 | expect(ScopeChecker.valid?("\tsomething", server_scopes)).to be_falsey
17 | end
18 |
19 | it 'is invalid if scope is not present' do
20 | expect(ScopeChecker.valid?(nil, server_scopes)).to be_falsey
21 | end
22 |
23 | it 'is invalid if scope is blank' do
24 | expect(ScopeChecker.valid?(' ', server_scopes)).to be_falsey
25 | end
26 |
27 | it 'is invalid if includes return space' do
28 | expect(ScopeChecker.valid?("scope\r", server_scopes)).to be_falsey
29 | end
30 |
31 | it 'is invalid if includes new lines' do
32 | expect(ScopeChecker.valid?("scope\nanother", server_scopes)).to be_falsey
33 | end
34 |
35 | it 'is invalid if any scope is not included in server scopes' do
36 | expect(ScopeChecker.valid?('scope another', server_scopes)).to be_falsey
37 | end
38 |
39 | context 'with application_scopes' do
40 | let(:server_scopes) do
41 | Doorkeeper::OAuth::Scopes.from_string 'common svr'
42 | end
43 | let(:application_scopes) do
44 | Doorkeeper::OAuth::Scopes.from_string 'app123'
45 | end
46 |
47 | it 'is valid if scope is included in the application scope list' do
48 | expect(ScopeChecker.valid?(
49 | 'app123',
50 | server_scopes,
51 | application_scopes
52 | )).to be_truthy
53 | end
54 |
55 | it 'is invalid if any scope is not included in the application' do
56 | expect(ScopeChecker.valid?(
57 | 'svr',
58 | server_scopes,
59 | application_scopes
60 | )).to be_falsey
61 | end
62 | end
63 | end
64 | end
65 |
--------------------------------------------------------------------------------
/spec/lib/oauth/error_response_spec.rb:
--------------------------------------------------------------------------------
1 | require 'spec_helper'
2 | require 'active_model'
3 | require 'doorkeeper/oauth/error'
4 | require 'doorkeeper/oauth/error_response'
5 |
6 | module Doorkeeper::OAuth
7 | describe ErrorResponse do
8 | describe '#status' do
9 | it 'should have a status of unauthorized' do
10 | expect(subject.status).to eq(:unauthorized)
11 | end
12 | end
13 |
14 | describe :from_request do
15 | it 'has the error from request' do
16 | error = ErrorResponse.from_request double(error: :some_error)
17 | expect(error.name).to eq(:some_error)
18 | end
19 |
20 | it 'ignores state if request does not respond to state' do
21 | error = ErrorResponse.from_request double(error: :some_error)
22 | expect(error.state).to be_nil
23 | end
24 |
25 | it 'has state if request responds to state' do
26 | error = ErrorResponse.from_request double(error: :some_error, state: :hello)
27 | expect(error.state).to eq(:hello)
28 | end
29 | end
30 |
31 | it 'ignores empty error values' do
32 | subject = ErrorResponse.new(error: :some_error, state: nil)
33 | expect(subject.body).not_to have_key(:state)
34 | end
35 |
36 | describe '.body' do
37 | subject { ErrorResponse.new(name: :some_error, state: :some_state).body }
38 |
39 | describe '#body' do
40 | it { expect(subject).to have_key(:error) }
41 | it { expect(subject).to have_key(:error_description) }
42 | it { expect(subject).to have_key(:state) }
43 | end
44 | end
45 |
46 | describe '.headers' do
47 | let(:error_response) { ErrorResponse.new(name: :some_error, state: :some_state) }
48 | subject { error_response.headers }
49 |
50 | it { expect(subject).to include 'WWW-Authenticate' }
51 |
52 | describe "WWW-Authenticate header" do
53 | subject { error_response.headers["WWW-Authenticate"] }
54 |
55 | it { expect(subject).to include("realm=\"#{error_response.realm}\"") }
56 | it { expect(subject).to include("error=\"#{error_response.name}\"") }
57 | it { expect(subject).to include("error_description=\"#{error_response.description}\"") }
58 | end
59 | end
60 | end
61 | end
62 |
--------------------------------------------------------------------------------
/spec/support/helpers/model_helper.rb:
--------------------------------------------------------------------------------
1 | module ModelHelper
2 | def client_exists(client_attributes = {})
3 | @client = FactoryGirl.create(:application, client_attributes)
4 | end
5 |
6 | def create_resource_owner
7 | @resource_owner = User.create!(name: 'Joe', password: 'sekret')
8 | end
9 |
10 | def authorization_code_exists(options = {})
11 | @authorization = FactoryGirl.create(:access_grant, options)
12 | end
13 |
14 | def access_grant_should_exist_for(client, resource_owner)
15 | grant = Doorkeeper::AccessGrant.first
16 |
17 | expect(grant.application).to have_attributes(id: client.id).
18 | and(be_instance_of(Doorkeeper::Application))
19 |
20 | expect(grant.resource_owner_id).to eq(resource_owner.id)
21 | end
22 |
23 | def access_token_should_exist_for(client, resource_owner)
24 | token = Doorkeeper::AccessToken.first
25 |
26 | expect(token.application).to have_attributes(id: client.id).
27 | and(be_instance_of(Doorkeeper::Application))
28 |
29 | expect(token.resource_owner_id).to eq(resource_owner.id)
30 | end
31 |
32 | def access_grant_should_not_exist
33 | expect(Doorkeeper::AccessGrant.all).to be_empty
34 | end
35 |
36 | def access_token_should_not_exist
37 | expect(Doorkeeper::AccessToken.all).to be_empty
38 | end
39 |
40 | def access_grant_should_have_scopes(*args)
41 | grant = Doorkeeper::AccessGrant.first
42 | expect(grant.scopes).to eq(Doorkeeper::OAuth::Scopes.from_array(args))
43 | end
44 |
45 | def access_token_should_have_scopes(*args)
46 | grant = Doorkeeper::AccessToken.last
47 | expect(grant.scopes).to eq(Doorkeeper::OAuth::Scopes.from_array(args))
48 | end
49 |
50 | def uniqueness_error
51 | case DOORKEEPER_ORM
52 | when :active_record
53 | ActiveRecord::RecordNotUnique
54 | when :sequel
55 | error_classes = [Sequel::UniqueConstraintViolation, Sequel::ValidationFailed]
56 | proc { |error| expect(error.class).to be_in(error_classes) }
57 | when :mongo_mapper
58 | MongoMapper::DocumentNotValid
59 | when /mongoid/
60 | Mongoid::Errors::Validations
61 | else
62 | raise "'#{DOORKEEPER_ORM}' ORM is not supported!"
63 | end
64 | end
65 | end
66 |
67 | RSpec.configuration.send :include, ModelHelper
68 |
--------------------------------------------------------------------------------
/app/views/doorkeeper/applications/_form.html.erb:
--------------------------------------------------------------------------------
1 | <%= form_for application, url: doorkeeper_submit_path(application), html: {class: 'form-horizontal', role: 'form'} do |f| %>
2 | <% if application.errors.any? %>
3 | <%= t('doorkeeper.applications.form.error') %>
4 | <% end %>
5 |
6 | <%= content_tag :div, class: "form-group#{' has-error' if application.errors[:name].present?}" do %>
7 | <%= f.label :name, class: 'col-sm-2 control-label' %>
8 |
9 | <%= f.text_field :name, class: 'form-control' %>
10 | <%= doorkeeper_errors_for application, :name %>
11 |
12 | <% end %>
13 |
14 | <%= content_tag :div, class: "form-group#{' has-error' if application.errors[:redirect_uri].present?}" do %>
15 | <%= f.label :redirect_uri, class: 'col-sm-2 control-label' %>
16 |
17 | <%= f.text_area :redirect_uri, class: 'form-control' %>
18 | <%= doorkeeper_errors_for application, :redirect_uri %>
19 |
20 | <%= t('doorkeeper.applications.help.redirect_uri') %>
21 |
22 | <% if Doorkeeper.configuration.native_redirect_uri %>
23 |
24 | <%= raw t('doorkeeper.applications.help.native_redirect_uri', native_redirect_uri: content_tag(:code) { Doorkeeper.configuration.native_redirect_uri }) %>
25 |
26 | <% end %>
27 |
28 | <% end %>
29 |
30 | <%= content_tag :div, class: "form-group#{' has-error' if application.errors[:scopes].present?}" do %>
31 | <%= f.label :scopes, class: 'col-sm-2 control-label' %>
32 |
33 | <%= f.text_field :scopes, class: 'form-control' %>
34 | <%= doorkeeper_errors_for application, :scopes %>
35 |
36 | <%= t('doorkeeper.applications.help.scopes') %>
37 |
38 |
39 | <% end %>
40 |
41 |
47 | <% end %>
48 |
--------------------------------------------------------------------------------
/spec/dummy/db/migrate/20151223192035_create_doorkeeper_tables.rb:
--------------------------------------------------------------------------------
1 | class CreateDoorkeeperTables < ActiveRecord::Migration
2 | def change
3 | create_table :oauth_applications do |t|
4 | t.string :name, null: false
5 | t.string :uid, null: false
6 | t.string :secret, null: false
7 | t.text :redirect_uri, null: false
8 | t.string :scopes, null: false, default: ''
9 | t.timestamps null: false
10 | end
11 |
12 | add_index :oauth_applications, :uid, unique: true
13 |
14 | create_table :oauth_access_grants do |t|
15 | t.integer :resource_owner_id, null: false
16 | t.references :application, null: false
17 | t.string :token, null: false
18 | t.integer :expires_in, null: false
19 | t.text :redirect_uri, null: false
20 | t.datetime :created_at, null: false
21 | t.datetime :revoked_at
22 | t.string :scopes
23 | end
24 |
25 | add_index :oauth_access_grants, :token, unique: true
26 | add_foreign_key(
27 | :oauth_access_grants,
28 | :oauth_applications,
29 | column: :application_id,
30 | )
31 |
32 | create_table :oauth_access_tokens do |t|
33 | t.integer :resource_owner_id
34 | t.references :application
35 |
36 | # If you use a custom token generator you may need to change this column
37 | # from string to text, so that it accepts tokens larger than 255
38 | # characters. More info on custom token generators in:
39 | # https://github.com/doorkeeper-gem/doorkeeper/tree/v3.0.0.rc1#custom-access-token-generator
40 | #
41 | # t.text :token, null: false
42 | t.string :token, null: false
43 |
44 | t.string :refresh_token
45 | t.integer :expires_in
46 | t.datetime :revoked_at
47 | t.datetime :created_at, null: false
48 | t.string :scopes
49 | end
50 |
51 | add_index :oauth_access_tokens, :token, unique: true
52 | add_index :oauth_access_tokens, :resource_owner_id
53 | add_index :oauth_access_tokens, :refresh_token, unique: true
54 | add_foreign_key(
55 | :oauth_access_tokens,
56 | :oauth_applications,
57 | column: :application_id,
58 | )
59 | end
60 | end
61 |
--------------------------------------------------------------------------------
/spec/support/helpers/url_helper.rb:
--------------------------------------------------------------------------------
1 | module UrlHelper
2 | def token_endpoint_url(options = {})
3 | parameters = {
4 | code: options[:code],
5 | client_id: options[:client_id] || (options[:client] ? options[:client].uid : nil),
6 | client_secret: options[:client_secret] || (options[:client] ? options[:client].secret : nil),
7 | redirect_uri: options[:redirect_uri] || (options[:client] ? options[:client].redirect_uri : nil),
8 | grant_type: options[:grant_type] || 'authorization_code'
9 | }
10 | "/oauth/token?#{build_query(parameters)}"
11 | end
12 |
13 | def password_token_endpoint_url(options = {})
14 | parameters = {
15 | code: options[:code],
16 | client_id: options[:client_id] || (options[:client] ? options[:client].uid : nil),
17 | client_secret: options[:client_secret] || (options[:client] ? options[:client].secret : nil),
18 | username: options[:resource_owner_username] || (options[:resource_owner] ? options[:resource_owner].name : nil),
19 | password: options[:resource_owner_password] || (options[:resource_owner] ? options[:resource_owner].password : nil),
20 | grant_type: 'password'
21 | }
22 | "/oauth/token?#{build_query(parameters)}"
23 | end
24 |
25 | def authorization_endpoint_url(options = {})
26 | parameters = {
27 | client_id: options[:client_id] || options[:client].uid,
28 | redirect_uri: options[:redirect_uri] || options[:client].redirect_uri,
29 | response_type: options[:response_type] || 'code',
30 | scope: options[:scope],
31 | state: options[:state]
32 | }.reject { |k, v| v.blank? }
33 | "/oauth/authorize?#{build_query(parameters)}"
34 | end
35 |
36 | def refresh_token_endpoint_url(options = {})
37 | parameters = {
38 | refresh_token: options[:refresh_token],
39 | client_id: options[:client_id] || options[:client].uid,
40 | client_secret: options[:client_secret] || options[:client].secret,
41 | grant_type: options[:grant_type] || 'refresh_token'
42 | }
43 | "/oauth/token?#{build_query(parameters)}"
44 | end
45 |
46 | def revocation_token_endpoint_url
47 | '/oauth/revoke'
48 | end
49 |
50 | def build_query(hash)
51 | Rack::Utils.build_query(hash)
52 | end
53 | end
54 |
55 | RSpec.configuration.send :include, UrlHelper
56 |
--------------------------------------------------------------------------------
/spec/support/helpers/request_spec_helper.rb:
--------------------------------------------------------------------------------
1 | module RequestSpecHelper
2 | def i_should_see(content)
3 | expect(page).to have_content(content)
4 | end
5 |
6 | def i_should_not_see(content)
7 | expect(page).to have_no_content(content)
8 | end
9 |
10 | def i_should_be_on(path)
11 | expect(current_path).to eq(path)
12 | end
13 |
14 | def url_should_have_param(param, value)
15 | expect(current_params[param]).to eq(value)
16 | end
17 |
18 | def url_should_not_have_param(param)
19 | expect(current_params).not_to have_key(param)
20 | end
21 |
22 | def current_params
23 | Rack::Utils.parse_query(current_uri.query)
24 | end
25 |
26 | def current_uri
27 | URI.parse(page.current_url)
28 | end
29 |
30 | def request_response
31 | respond_to?(:response) ? response : page.driver.response
32 | end
33 |
34 | def should_have_header(header, value)
35 | expect(headers[header]).to eq(value)
36 | end
37 |
38 | def with_access_token_header(token)
39 | with_header 'Authorization', "Bearer #{token}"
40 | end
41 |
42 | def with_header(header, value)
43 | page.driver.header header, value
44 | end
45 |
46 | def basic_auth_header_for_client(client)
47 | ActionController::HttpAuthentication::Basic.encode_credentials client.uid, client.secret
48 | end
49 |
50 | def should_have_json(key, value)
51 | expect(JSON.parse(request_response.body).fetch(key)).to eq(value)
52 | end
53 |
54 | def should_have_json_within(key, value, range)
55 | expect(JSON.parse(request_response.body).fetch(key)).to be_within(range).of(value)
56 | end
57 |
58 | def should_not_have_json(key)
59 | expect(JSON.parse(request_response.body)).not_to have_key(key)
60 | end
61 |
62 | def sign_in
63 | visit '/'
64 | click_on 'Sign in'
65 | end
66 |
67 | def create_access_token(authorization_code, client)
68 | page.driver.post token_endpoint_url(code: authorization_code, client: client)
69 | end
70 |
71 | def i_should_see_translated_error_message(key)
72 | i_should_see translated_error_message(key)
73 | end
74 |
75 | def translated_error_message(key)
76 | I18n.translate key, scope: [:doorkeeper, :errors, :messages]
77 | end
78 |
79 | def response_status_should_be(status)
80 | expect(request_response.status.to_i).to eq(status)
81 | end
82 | end
83 |
84 | RSpec.configuration.send :include, RequestSpecHelper
85 |
--------------------------------------------------------------------------------
/spec/dummy/config/environments/production.rb:
--------------------------------------------------------------------------------
1 | Dummy::Application.configure do
2 | # Settings specified here will take precedence over those in config/application.rb
3 |
4 | # Code is not reloaded between requests
5 | config.cache_classes = true
6 |
7 | # Full error reports are disabled and caching is turned on
8 | config.consider_all_requests_local = false
9 | config.action_controller.perform_caching = true
10 |
11 | # Disable Rails's static asset server (Apache or nginx will already do this)
12 | config.serve_static_assets = false
13 |
14 | # Compress JavaScripts and CSS
15 | config.assets.compress = true
16 |
17 | # Don't fallback to assets pipeline if a precompiled asset is missed
18 | config.assets.compile = false
19 |
20 | # Generate digests for assets URLs
21 | config.assets.digest = true
22 |
23 | # Defaults to Rails.root.join("public/assets")
24 | # config.assets.manifest = YOUR_PATH
25 |
26 | # Specifies the header that your server uses for sending files
27 | # config.action_dispatch.x_sendfile_header = "X-Sendfile" # for apache
28 | # config.action_dispatch.x_sendfile_header = 'X-Accel-Redirect' # for nginx
29 |
30 | # Force all access to the app over SSL, use Strict-Transport-Security, and use secure cookies.
31 | # config.force_ssl = true
32 |
33 | # See everything in the log (default is :info)
34 | # config.log_level = :debug
35 |
36 | # Use a different logger for distributed setups
37 | # config.logger = SyslogLogger.new
38 |
39 | # Use a different cache store in production
40 | # config.cache_store = :mem_cache_store
41 |
42 | # Enable serving of images, stylesheets, and JavaScripts from an asset server
43 | # config.action_controller.asset_host = "http://assets.example.com"
44 |
45 | # Precompile additional assets (application.js, application.css, and all non-JS/CSS are already added)
46 | # config.assets.precompile += %w( search.js )
47 |
48 | # Disable delivery errors, bad email addresses will be ignored
49 | # config.action_mailer.raise_delivery_errors = false
50 |
51 | # Enable threaded mode
52 | # config.threadsafe!
53 |
54 | # Enable locale fallbacks for I18n (makes lookups for any locale fall back to
55 | # the I18n.default_locale when a translation can not be found)
56 | config.i18n.fallbacks = true
57 |
58 | # Send deprecation notices to registered listeners
59 | config.active_support.deprecation = :notify
60 |
61 | config.eager_load = true
62 | end
63 |
--------------------------------------------------------------------------------
/spec/lib/oauth/client_credentials/validation_spec.rb:
--------------------------------------------------------------------------------
1 | require 'spec_helper'
2 | require 'active_support/all'
3 | require 'doorkeeper/oauth/client_credentials/validation'
4 |
5 | class Doorkeeper::OAuth::ClientCredentialsRequest
6 | describe Validation do
7 | let(:server) { double :server, scopes: nil }
8 | let(:application) { double scopes: nil }
9 | let(:client) { double application: application }
10 | let(:request) { double :request, client: client, scopes: nil }
11 |
12 | subject { Validation.new(server, request) }
13 |
14 | it 'is valid with valid request' do
15 | expect(subject).to be_valid
16 | end
17 |
18 | it 'is invalid when client is not present' do
19 | allow(request).to receive(:client).and_return(nil)
20 | expect(subject).not_to be_valid
21 | end
22 |
23 | context 'with scopes' do
24 | it 'is invalid when scopes are not included in the server' do
25 | server_scopes = Doorkeeper::OAuth::Scopes.from_string 'email'
26 | allow(server).to receive(:scopes).and_return(server_scopes)
27 | allow(request).to receive(:scopes).and_return(
28 | Doorkeeper::OAuth::Scopes.from_string 'invalid')
29 | expect(subject).not_to be_valid
30 | end
31 |
32 | context 'with application scopes' do
33 | it 'is valid when scopes are included in the application' do
34 | application_scopes = Doorkeeper::OAuth::Scopes.from_string 'app'
35 | server_scopes = Doorkeeper::OAuth::Scopes.from_string 'email app'
36 | allow(application).to receive(:scopes).and_return(application_scopes)
37 | allow(server).to receive(:scopes).and_return(server_scopes)
38 | allow(request).to receive(:scopes).and_return(application_scopes)
39 | expect(subject).to be_valid
40 | end
41 |
42 | it 'is invalid when scopes are not included in the application' do
43 | application_scopes = Doorkeeper::OAuth::Scopes.from_string 'app'
44 | server_scopes = Doorkeeper::OAuth::Scopes.from_string 'email app'
45 | allow(application).to receive(:scopes).and_return(application_scopes)
46 | allow(server).to receive(:scopes).and_return(server_scopes)
47 | allow(request).to receive(:scopes).and_return(
48 | Doorkeeper::OAuth::Scopes.from_string 'email')
49 | expect(subject).not_to be_valid
50 | end
51 | end
52 | end
53 | end
54 | end
55 |
--------------------------------------------------------------------------------
/spec/requests/flows/skip_authorization_spec.rb:
--------------------------------------------------------------------------------
1 | require 'spec_helper_integration'
2 |
3 | feature 'Skip authorization form' do
4 | background do
5 | config_is_set(:authenticate_resource_owner) { User.first || redirect_to('/sign_in') }
6 | client_exists
7 | default_scopes_exist :public
8 | optional_scopes_exist :write
9 | end
10 |
11 | context 'for previously authorized clients' do
12 | background do
13 | create_resource_owner
14 | sign_in
15 | end
16 |
17 | scenario 'skips the authorization and return a new grant code' do
18 | client_is_authorized(@client, @resource_owner, scopes: 'public')
19 | visit authorization_endpoint_url(client: @client)
20 |
21 | i_should_not_see 'Authorize'
22 | client_should_be_authorized @client
23 | i_should_be_on_client_callback @client
24 | url_should_have_param 'code', Doorkeeper::AccessGrant.first.token
25 | end
26 |
27 | scenario 'does not skip authorization when scopes differ (new request has fewer scopes)' do
28 | client_is_authorized(@client, @resource_owner, scopes: 'public write')
29 | visit authorization_endpoint_url(client: @client, scope: 'public')
30 | i_should_see 'Authorize'
31 | end
32 |
33 | scenario 'does not skip authorization when scopes differ (new request has more scopes)' do
34 | client_is_authorized(@client, @resource_owner, scopes: 'public write')
35 | visit authorization_endpoint_url(client: @client, scopes: 'public write email')
36 | i_should_see 'Authorize'
37 | end
38 |
39 | scenario 'creates grant with new scope when scopes differ' do
40 | client_is_authorized(@client, @resource_owner, scopes: 'public write')
41 | visit authorization_endpoint_url(client: @client, scope: 'public')
42 | click_on 'Authorize'
43 | access_grant_should_have_scopes :public
44 | end
45 |
46 | scenario 'doesn not skip authorization when scopes are greater' do
47 | client_is_authorized(@client, @resource_owner, scopes: 'public')
48 | visit authorization_endpoint_url(client: @client, scope: 'public write')
49 | i_should_see 'Authorize'
50 | end
51 |
52 | scenario 'creates grant with new scope when scopes are greater' do
53 | client_is_authorized(@client, @resource_owner, scopes: 'public')
54 | visit authorization_endpoint_url(client: @client, scope: 'public write')
55 | click_on 'Authorize'
56 | access_grant_should_have_scopes :public, :write
57 | end
58 | end
59 | end
60 |
--------------------------------------------------------------------------------
/spec/requests/endpoints/authorization_spec.rb:
--------------------------------------------------------------------------------
1 | require 'spec_helper_integration'
2 |
3 | feature 'Authorization endpoint' do
4 | background do
5 | config_is_set(:authenticate_resource_owner) { User.first || redirect_to('/sign_in') }
6 | client_exists(name: 'MyApp')
7 | end
8 |
9 | scenario 'requires resource owner to be authenticated' do
10 | visit authorization_endpoint_url(client: @client)
11 | i_should_see 'Sign in'
12 | i_should_be_on '/'
13 | end
14 |
15 | context 'with authenticated resource owner' do
16 | background do
17 | create_resource_owner
18 | sign_in
19 | end
20 |
21 | scenario 'displays the authorization form' do
22 | visit authorization_endpoint_url(client: @client)
23 | i_should_see 'Authorize MyApp to use your account?'
24 | end
25 |
26 | scenario 'displays all requested scopes' do
27 | default_scopes_exist :public
28 | optional_scopes_exist :write
29 | visit authorization_endpoint_url(client: @client, scope: 'public write')
30 | i_should_see 'Access your public data'
31 | i_should_see 'Update your data'
32 | end
33 | end
34 |
35 | context 'with a invalid request' do
36 | background do
37 | create_resource_owner
38 | sign_in
39 | end
40 |
41 | scenario 'displays the related error' do
42 | visit authorization_endpoint_url(client: @client, response_type: '')
43 | i_should_not_see 'Authorize'
44 | i_should_see_translated_error_message :unsupported_response_type
45 | end
46 |
47 | scenario "displays unsupported_response_type error when using a disabled response type" do
48 | config_is_set(:grant_flows, ['implicit'])
49 | visit authorization_endpoint_url(client: @client, response_type: 'code')
50 | i_should_not_see "Authorize"
51 | i_should_see_translated_error_message :unsupported_response_type
52 | end
53 | end
54 |
55 | context 'forgery protection enabled' do
56 | background do
57 | create_resource_owner
58 | sign_in
59 | end
60 |
61 | scenario 'raises exception on forged requests' do
62 | allowing_forgery_protection do
63 | expect {
64 | page.driver.post authorization_endpoint_url(client_id: @client.uid,
65 | redirect_uri: @client.redirect_uri,
66 | response_type: 'code')
67 | }.to raise_error(ActionController::InvalidAuthenticityToken)
68 | end
69 | end
70 | end
71 | end
72 |
--------------------------------------------------------------------------------
/lib/doorkeeper/rails/helpers.rb:
--------------------------------------------------------------------------------
1 | module Doorkeeper
2 | module Rails
3 | module Helpers
4 | def doorkeeper_authorize!(*scopes)
5 | @_doorkeeper_scopes = scopes.presence || Doorkeeper.configuration.default_scopes
6 |
7 | unless valid_doorkeeper_token?
8 | doorkeeper_render_error
9 | end
10 | end
11 |
12 | def doorkeeper_unauthorized_render_options(error: nil)
13 | end
14 |
15 | def doorkeeper_forbidden_render_options(error: nil)
16 | end
17 |
18 | def valid_doorkeeper_token?
19 | doorkeeper_token && doorkeeper_token.acceptable?(@_doorkeeper_scopes)
20 | end
21 |
22 | private
23 |
24 | def doorkeeper_render_error
25 | error = doorkeeper_error
26 | headers.merge! error.headers.reject { |k| "Content-Type" == k }
27 | doorkeeper_render_error_with(error)
28 | end
29 |
30 | def doorkeeper_render_error_with(error)
31 | options = doorkeeper_render_options(error) || {}
32 | status = doorkeeper_status_for_error(
33 | error, options.delete(:respond_not_found_when_forbidden))
34 | if options.blank?
35 | head status
36 | else
37 | options[:status] = status
38 | options[:layout] = false if options[:layout].nil?
39 | render options
40 | end
41 | end
42 |
43 | def doorkeeper_error
44 | if doorkeeper_invalid_token_response?
45 | OAuth::InvalidTokenResponse.from_access_token(doorkeeper_token)
46 | else
47 | OAuth::ForbiddenTokenResponse.from_scopes(@_doorkeeper_scopes)
48 | end
49 | end
50 |
51 | def doorkeeper_render_options(error)
52 | if doorkeeper_invalid_token_response?
53 | doorkeeper_unauthorized_render_options(error: error)
54 | else
55 | doorkeeper_forbidden_render_options(error: error)
56 | end
57 | end
58 |
59 | def doorkeeper_status_for_error(error, respond_not_found_when_forbidden)
60 | if respond_not_found_when_forbidden && error.status == :forbidden
61 | :not_found
62 | else
63 | error.status
64 | end
65 | end
66 |
67 | def doorkeeper_invalid_token_response?
68 | !doorkeeper_token || !doorkeeper_token.accessible?
69 | end
70 |
71 | def doorkeeper_token
72 | @_doorkeeper_token ||= OAuth::Token.authenticate(
73 | request,
74 | *Doorkeeper.configuration.access_token_methods
75 | )
76 | end
77 | end
78 | end
79 | end
80 |
--------------------------------------------------------------------------------
/spec/lib/oauth/authorization_code_request_spec.rb:
--------------------------------------------------------------------------------
1 | require 'spec_helper_integration'
2 |
3 | module Doorkeeper::OAuth
4 | describe AuthorizationCodeRequest do
5 | let(:server) do
6 | double :server,
7 | access_token_expires_in: 2.days,
8 | refresh_token_enabled?: false,
9 | custom_access_token_expires_in: ->(_app) { nil }
10 | end
11 | let(:grant) { FactoryGirl.create :access_grant }
12 | let(:client) { grant.application }
13 |
14 | subject do
15 | AuthorizationCodeRequest.new server, grant, client, redirect_uri: client.redirect_uri
16 | end
17 |
18 | it 'issues a new token for the client' do
19 | expect do
20 | subject.authorize
21 | end.to change { client.reload.access_tokens.count }.by(1)
22 | end
23 |
24 | it "issues the token with same grant's scopes" do
25 | subject.authorize
26 | expect(Doorkeeper::AccessToken.last.scopes).to eq(grant.scopes)
27 | end
28 |
29 | it 'revokes the grant' do
30 | expect do
31 | subject.authorize
32 | end.to change { grant.reload.accessible? }
33 | end
34 |
35 | it 'requires the grant to be accessible' do
36 | grant.revoke
37 | subject.validate
38 | expect(subject.error).to eq(:invalid_grant)
39 | end
40 |
41 | it 'requires the grant' do
42 | subject.grant = nil
43 | subject.validate
44 | expect(subject.error).to eq(:invalid_grant)
45 | end
46 |
47 | it 'requires the client' do
48 | subject.client = nil
49 | subject.validate
50 | expect(subject.error).to eq(:invalid_client)
51 | end
52 |
53 | it 'requires the redirect_uri' do
54 | subject.redirect_uri = nil
55 | subject.validate
56 | expect(subject.error).to eq(:invalid_request)
57 | end
58 |
59 | it "matches the redirect_uri with grant's one" do
60 | subject.redirect_uri = 'http://other.com'
61 | subject.validate
62 | expect(subject.error).to eq(:invalid_grant)
63 | end
64 |
65 | it "matches the client with grant's one" do
66 | subject.client = FactoryGirl.create :application
67 | subject.validate
68 | expect(subject.error).to eq(:invalid_grant)
69 | end
70 |
71 | it 'skips token creation if there is a matching one' do
72 | allow(Doorkeeper.configuration).to receive(:reuse_access_token).and_return(true)
73 | FactoryGirl.create(:access_token, application_id: client.id,
74 | resource_owner_id: grant.resource_owner_id, scopes: grant.scopes.to_s)
75 | expect do
76 | subject.authorize
77 | end.to_not change { Doorkeeper::AccessToken.count }
78 | end
79 | end
80 | end
81 |
--------------------------------------------------------------------------------
/spec/lib/oauth/token_response_spec.rb:
--------------------------------------------------------------------------------
1 | require 'spec_helper'
2 | require 'doorkeeper/oauth/token_response'
3 |
4 | module Doorkeeper::OAuth
5 | describe TokenResponse do
6 | subject { TokenResponse.new(double.as_null_object) }
7 |
8 | it 'includes access token response headers' do
9 | headers = subject.headers
10 | expect(headers.fetch('Cache-Control')).to eq('no-store')
11 | expect(headers.fetch('Pragma')).to eq('no-cache')
12 | end
13 |
14 | it 'status is ok' do
15 | expect(subject.status).to eq(:ok)
16 | end
17 |
18 | describe '.body' do
19 | let(:access_token) do
20 | double :access_token,
21 | token: 'some-token',
22 | expires_in: '3600',
23 | expires_in_seconds: '300',
24 | scopes_string: 'two scopes',
25 | refresh_token: 'some-refresh-token',
26 | token_type: 'bearer',
27 | created_at: 0
28 | end
29 |
30 | subject { TokenResponse.new(access_token).body }
31 |
32 | it 'includes :access_token' do
33 | expect(subject['access_token']).to eq('some-token')
34 | end
35 |
36 | it 'includes :token_type' do
37 | expect(subject['token_type']).to eq('bearer')
38 | end
39 |
40 | # expires_in_seconds is returned as `expires_in` in order to match
41 | # the OAuth spec (section 4.2.2)
42 | it 'includes :expires_in' do
43 | expect(subject['expires_in']).to eq('300')
44 | end
45 |
46 | it 'includes :scope' do
47 | expect(subject['scope']).to eq('two scopes')
48 | end
49 |
50 | it 'includes :refresh_token' do
51 | expect(subject['refresh_token']).to eq('some-refresh-token')
52 | end
53 |
54 | it 'includes :created_at' do
55 | expect(subject['created_at']).to eq(0)
56 | end
57 | end
58 |
59 | describe '.body filters out empty values' do
60 | let(:access_token) do
61 | double :access_token,
62 | token: 'some-token',
63 | expires_in_seconds: '',
64 | scopes_string: '',
65 | refresh_token: '',
66 | token_type: 'bearer',
67 | created_at: 0
68 | end
69 |
70 | subject { TokenResponse.new(access_token).body }
71 |
72 | it 'includes :expires_in' do
73 | expect(subject['expires_in']).to be_nil
74 | end
75 |
76 | it 'includes :scope' do
77 | expect(subject['scope']).to be_nil
78 | end
79 |
80 | it 'includes :refresh_token' do
81 | expect(subject['refresh_token']).to be_nil
82 | end
83 | end
84 | end
85 | end
86 |
--------------------------------------------------------------------------------
/spec/requests/flows/authorization_code_errors_spec.rb:
--------------------------------------------------------------------------------
1 | require 'spec_helper_integration'
2 |
3 | feature 'Authorization Code Flow Errors' do
4 | let(:client_params) { {} }
5 | background do
6 | config_is_set(:authenticate_resource_owner) { User.first || redirect_to('/sign_in') }
7 | client_exists client_params
8 | create_resource_owner
9 | sign_in
10 | end
11 |
12 | after do
13 | access_grant_should_not_exist
14 | end
15 |
16 | context "with a client trying to xss resource owner" do
17 | let(:client_name) { "XSS
" }
18 | let(:client_params) { { name: client_name } }
19 | scenario "resource owner visit authorization endpoint" do
20 | visit authorization_endpoint_url(client: @client)
21 | expect(page).not_to have_css("#xss")
22 | end
23 | end
24 |
25 | context 'when access was denied' do
26 | scenario 'redirects with error' do
27 | visit authorization_endpoint_url(client: @client)
28 | click_on 'Deny'
29 |
30 | i_should_be_on_client_callback @client
31 | url_should_not_have_param 'code'
32 | url_should_have_param 'error', 'access_denied'
33 | url_should_have_param 'error_description', translated_error_message(:access_denied)
34 | end
35 |
36 | scenario 'redirects with state parameter' do
37 | visit authorization_endpoint_url(client: @client, state: 'return-this')
38 | click_on 'Deny'
39 |
40 | i_should_be_on_client_callback @client
41 | url_should_not_have_param 'code'
42 | url_should_have_param 'state', 'return-this'
43 | end
44 | end
45 | end
46 |
47 | describe 'Authorization Code Flow Errors', 'after authorization' do
48 | before do
49 | client_exists
50 | authorization_code_exists application: @client
51 | end
52 |
53 | it 'returns :invalid_grant error when posting an already revoked grant code' do
54 | # First successful request
55 | post token_endpoint_url(code: @authorization.token, client: @client)
56 |
57 | # Second attempt with same token
58 | expect do
59 | post token_endpoint_url(code: @authorization.token, client: @client)
60 | end.to_not change { Doorkeeper::AccessToken.count }
61 |
62 | should_not_have_json 'access_token'
63 | should_have_json 'error', 'invalid_grant'
64 | should_have_json 'error_description', translated_error_message('invalid_grant')
65 | end
66 |
67 | it 'returns :invalid_grant error for invalid grant code' do
68 | post token_endpoint_url(code: 'invalid', client: @client)
69 |
70 | access_token_should_not_exist
71 |
72 | should_not_have_json 'access_token'
73 | should_have_json 'error', 'invalid_grant'
74 | should_have_json 'error_description', translated_error_message('invalid_grant')
75 | end
76 | end
77 |
--------------------------------------------------------------------------------
/lib/generators/doorkeeper/templates/migration.rb:
--------------------------------------------------------------------------------
1 | class CreateDoorkeeperTables < ActiveRecord::Migration<%= migration_version %>
2 | def change
3 | create_table :oauth_applications do |t|
4 | t.string :name, null: false
5 | t.string :uid, null: false
6 | t.string :secret, null: false
7 | t.text :redirect_uri, null: false
8 | t.string :scopes, null: false, default: ''
9 | t.timestamps null: false
10 | end
11 |
12 | add_index :oauth_applications, :uid, unique: true
13 |
14 | create_table :oauth_access_grants do |t|
15 | t.integer :resource_owner_id, null: false
16 | t.references :application, null: false
17 | t.string :token, null: false
18 | t.integer :expires_in, null: false
19 | t.text :redirect_uri, null: false
20 | t.datetime :created_at, null: false
21 | t.datetime :revoked_at
22 | t.string :scopes
23 | end
24 |
25 | add_index :oauth_access_grants, :token, unique: true
26 | add_foreign_key(
27 | :oauth_access_grants,
28 | :oauth_applications,
29 | column: :application_id
30 | )
31 |
32 | create_table :oauth_access_tokens do |t|
33 | t.integer :resource_owner_id
34 | t.references :application
35 |
36 | # If you use a custom token generator you may need to change this column
37 | # from string to text, so that it accepts tokens larger than 255
38 | # characters. More info on custom token generators in:
39 | # https://github.com/doorkeeper-gem/doorkeeper/tree/v3.0.0.rc1#custom-access-token-generator
40 | #
41 | # t.text :token, null: false
42 | t.string :token, null: false
43 |
44 | t.string :refresh_token
45 | t.integer :expires_in
46 | t.datetime :revoked_at
47 | t.datetime :created_at, null: false
48 | t.string :scopes
49 |
50 | # If there is a previous_refresh_token column,
51 | # refresh tokens will be revoked after a related access token is used.
52 | # If there is no previous_refresh_token column,
53 | # previous tokens are revoked as soon as a new access token is created.
54 | # Comment out this line if you'd rather have refresh tokens
55 | # instantly revoked.
56 | t.string :previous_refresh_token, null: false, default: ""
57 | end
58 |
59 | add_index :oauth_access_tokens, :token, unique: true
60 | add_index :oauth_access_tokens, :resource_owner_id
61 | add_index :oauth_access_tokens, :refresh_token, unique: true
62 | add_foreign_key(
63 | :oauth_access_tokens,
64 | :oauth_applications,
65 | column: :application_id
66 | )
67 | end
68 | end
69 |
--------------------------------------------------------------------------------
/spec/requests/applications/applications_request_spec.rb:
--------------------------------------------------------------------------------
1 | require 'spec_helper_integration'
2 |
3 | feature 'Adding applications' do
4 | context 'in application form' do
5 | background do
6 | visit '/oauth/applications/new'
7 | end
8 |
9 | scenario 'adding a valid app' do
10 | fill_in 'doorkeeper_application[name]', with: 'My Application'
11 | fill_in 'doorkeeper_application[redirect_uri]',
12 | with: 'https://example.com'
13 |
14 | click_button 'Submit'
15 | i_should_see 'Application created'
16 | i_should_see 'My Application'
17 | end
18 |
19 | scenario 'adding invalid app' do
20 | click_button 'Submit'
21 | i_should_see 'Whoops! Check your form for possible errors'
22 | end
23 | end
24 | end
25 |
26 | feature 'Listing applications' do
27 | background do
28 | FactoryGirl.create :application, name: 'Oauth Dude'
29 | FactoryGirl.create :application, name: 'Awesome App'
30 | end
31 |
32 | scenario 'application list' do
33 | visit '/oauth/applications'
34 | i_should_see 'Awesome App'
35 | i_should_see 'Oauth Dude'
36 | end
37 | end
38 |
39 | feature 'Show application' do
40 | given :app do
41 | FactoryGirl.create :application, name: 'Just another oauth app'
42 | end
43 |
44 | scenario 'visiting application page' do
45 | visit "/oauth/applications/#{app.id}"
46 | i_should_see 'Just another oauth app'
47 | end
48 | end
49 |
50 | feature 'Edit application' do
51 | let :app do
52 | FactoryGirl.create :application, name: 'OMG my app'
53 | end
54 |
55 | background do
56 | visit "/oauth/applications/#{app.id}/edit"
57 | end
58 |
59 | scenario 'updating a valid app' do
60 | fill_in 'doorkeeper_application[name]', with: 'Serious app'
61 | click_button 'Submit'
62 | i_should_see 'Application updated'
63 | i_should_see 'Serious app'
64 | i_should_not_see 'OMG my app'
65 | end
66 |
67 | scenario 'updating an invalid app' do
68 | fill_in 'doorkeeper_application[name]', with: ''
69 | click_button 'Submit'
70 | i_should_see 'Whoops! Check your form for possible errors'
71 | end
72 | end
73 |
74 | feature 'Remove application' do
75 | background do
76 | @app = FactoryGirl.create :application
77 | end
78 |
79 | scenario 'deleting an application from list' do
80 | visit '/oauth/applications'
81 | i_should_see @app.name
82 | within(:css, "tr#application_#{@app.id}") do
83 | click_button 'Destroy'
84 | end
85 | i_should_see 'Application deleted'
86 | i_should_not_see @app.name
87 | end
88 |
89 | scenario 'deleting an application from show' do
90 | visit "/oauth/applications/#{@app.id}"
91 | click_button 'Destroy'
92 | i_should_see 'Application deleted'
93 | end
94 | end
95 |
--------------------------------------------------------------------------------
/spec/lib/oauth/client_credentials/issuer_spec.rb:
--------------------------------------------------------------------------------
1 | require 'spec_helper'
2 | require 'active_support/all'
3 | require 'doorkeeper/oauth/client_credentials/issuer'
4 |
5 | class Doorkeeper::OAuth::ClientCredentialsRequest
6 | describe Issuer do
7 | let(:creator) { double :acces_token_creator }
8 | let(:server) do
9 | double(
10 | :server,
11 | access_token_expires_in: 100,
12 | custom_access_token_expires_in: ->(_app) { nil }
13 | )
14 | end
15 | let(:validation) { double :validation, valid?: true }
16 |
17 | subject { Issuer.new(server, validation) }
18 |
19 | describe :create do
20 | let(:client) { double :client, id: 'some-id' }
21 | let(:scopes) { 'some scope' }
22 |
23 | it 'creates and sets the token' do
24 | expect(creator).to receive(:call).and_return('token')
25 | subject.create client, scopes, creator
26 |
27 | expect(subject.token).to eq('token')
28 | end
29 |
30 | it 'creates with correct token parameters' do
31 | expect(creator).to receive(:call).with(
32 | client,
33 | scopes,
34 | expires_in: 100,
35 | use_refresh_token: false
36 | )
37 |
38 | subject.create client, scopes, creator
39 | end
40 |
41 | it 'has error set to :server_error if creator fails' do
42 | expect(creator).to receive(:call).and_return(false)
43 | subject.create client, scopes, creator
44 |
45 | expect(subject.error).to eq(:server_error)
46 | end
47 |
48 | context 'when validation fails' do
49 | before do
50 | allow(validation).to receive(:valid?).and_return(false)
51 | allow(validation).to receive(:error).and_return(:validation_error)
52 | expect(creator).not_to receive(:create)
53 | end
54 |
55 | it 'has error set from validation' do
56 | subject.create client, scopes, creator
57 | expect(subject.error).to eq(:validation_error)
58 | end
59 |
60 | it 'returns false' do
61 | expect(subject.create(client, scopes, creator)).to be_falsey
62 | end
63 | end
64 |
65 | context 'with custom expirations' do
66 | let(:custom_ttl) { 1233 }
67 | let(:server) do
68 | double(
69 | :server,
70 | custom_access_token_expires_in: ->(_app) { custom_ttl }
71 | )
72 | end
73 |
74 | it 'creates with correct token parameters' do
75 | expect(creator).to receive(:call).with(
76 | client,
77 | scopes,
78 | expires_in: custom_ttl,
79 | use_refresh_token: false
80 | )
81 | subject.create client, scopes, creator
82 | end
83 | end
84 | end
85 | end
86 | end
87 |
--------------------------------------------------------------------------------
/lib/doorkeeper.rb:
--------------------------------------------------------------------------------
1 | require 'doorkeeper/version'
2 | require 'doorkeeper/engine'
3 | require 'doorkeeper/config'
4 |
5 | require 'doorkeeper/errors'
6 | require 'doorkeeper/server'
7 | require 'doorkeeper/request'
8 | require 'doorkeeper/validations'
9 |
10 | require 'doorkeeper/oauth/authorization/code'
11 | require 'doorkeeper/oauth/authorization/token'
12 | require 'doorkeeper/oauth/authorization/uri_builder'
13 | require 'doorkeeper/oauth/helpers/scope_checker'
14 | require 'doorkeeper/oauth/helpers/uri_checker'
15 | require 'doorkeeper/oauth/helpers/unique_token'
16 |
17 | require 'doorkeeper/oauth/scopes'
18 | require 'doorkeeper/oauth/error'
19 | require 'doorkeeper/oauth/base_response'
20 | require 'doorkeeper/oauth/code_response'
21 | require 'doorkeeper/oauth/token_response'
22 | require 'doorkeeper/oauth/error_response'
23 | require 'doorkeeper/oauth/pre_authorization'
24 | require 'doorkeeper/oauth/base_request'
25 | require 'doorkeeper/oauth/authorization_code_request'
26 | require 'doorkeeper/oauth/refresh_token_request'
27 | require 'doorkeeper/oauth/password_access_token_request'
28 | require 'doorkeeper/oauth/client_credentials_request'
29 | require 'doorkeeper/oauth/code_request'
30 | require 'doorkeeper/oauth/token_request'
31 | require 'doorkeeper/oauth/client'
32 | require 'doorkeeper/oauth/token'
33 | require 'doorkeeper/oauth/invalid_token_response'
34 | require 'doorkeeper/oauth/forbidden_token_response'
35 |
36 | require 'doorkeeper/models/concerns/scopes'
37 | require 'doorkeeper/models/concerns/expirable'
38 | require 'doorkeeper/models/concerns/revocable'
39 | require 'doorkeeper/models/concerns/accessible'
40 |
41 | require 'doorkeeper/models/access_grant_mixin'
42 | require 'doorkeeper/models/access_token_mixin'
43 | require 'doorkeeper/models/application_mixin'
44 |
45 | require 'doorkeeper/helpers/controller'
46 |
47 | require 'doorkeeper/rails/routes'
48 | require 'doorkeeper/rails/helpers'
49 |
50 | require 'doorkeeper/orm/active_record'
51 |
52 | require 'active_support/deprecation'
53 |
54 | module Doorkeeper
55 | def self.configured?
56 | ActiveSupport::Deprecation.warn "Method `Doorkeeper#configured?` has been deprecated without replacement."
57 | @config.present?
58 | end
59 |
60 | def self.database_installed?
61 | ActiveSupport::Deprecation.warn "Method `Doorkeeper#database_installed?` has been deprecated without replacement."
62 | [AccessToken, AccessGrant, Application].all?(&:table_exists?)
63 | end
64 |
65 | def self.installed?
66 | ActiveSupport::Deprecation.warn "Method `Doorkeeper#installed?` has been deprecated without replacement."
67 | configured? && database_installed?
68 | end
69 |
70 | def self.authenticate(request, methods = Doorkeeper.configuration.access_token_methods)
71 | OAuth::Token.authenticate(request, *methods)
72 | end
73 | end
74 |
--------------------------------------------------------------------------------