├── .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 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | <% @applications.each do |application| %> 17 | 18 | 19 | 20 | 21 | 22 | <% end %> 23 | 24 |
<%= t('doorkeeper.authorized_applications.index.application') %><%= t('doorkeeper.authorized_applications.index.created_at') %>
<%= application.name %><%= application.created_at.strftime(t('doorkeeper.authorized_applications.index.date_format')) %><%= render 'delete_form', application: application %>
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 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | <% @applications.each do |application| %> 18 | 19 | 20 | 21 | 22 | 23 | 24 | <% end %> 25 | 26 |
<%= t('.name') %><%= t('.callback_url') %>
<%= link_to application.name, oauth_application_path(application) %><%= application.redirect_uri %><%= link_to t('doorkeeper.applications.buttons.edit'), edit_oauth_application_path(application), class: 'btn btn-link' %><%= render 'delete_form', application: application %>
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 | 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 | 24 | 27 | 28 | <% end %> 29 |
22 | <%= uri %> 23 | 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 |
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 | 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 |
42 |
43 | <%= f.submit t('doorkeeper.applications.buttons.submit'), class: "btn btn-primary" %> 44 | <%= link_to t('doorkeeper.applications.buttons.cancel'), oauth_applications_path, :class => "btn btn-default" %> 45 |
46 |
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 | --------------------------------------------------------------------------------