├── .gitignore ├── .travis.yml ├── Gemfile ├── LICENSE ├── README.md ├── Rakefile ├── examples ├── client │ ├── Gemfile │ ├── Gemfile.lock │ ├── README │ ├── app.rb │ ├── config.ru │ └── views │ │ ├── home.haml │ │ └── response.haml └── rails3-example │ ├── .gitignore │ ├── Gemfile │ ├── Gemfile.lock │ ├── README │ ├── Rakefile │ ├── app │ ├── controllers │ │ ├── account_controller.rb │ │ ├── application_controller.rb │ │ ├── authorization_controller.rb │ │ ├── home_controller.rb │ │ └── session_controller.rb │ ├── helpers │ │ └── application_helper.rb │ ├── models │ │ └── account.rb │ └── views │ │ ├── authorization │ │ └── new.html.erb │ │ ├── home │ │ └── show.html.erb │ │ ├── layouts │ │ └── application.html.erb │ │ └── session │ │ └── new.html.erb │ ├── config.ru │ ├── config │ ├── application.rb │ ├── boot.rb │ ├── database.yml │ ├── environment.rb │ ├── environments │ │ ├── development.rb │ │ ├── production.rb │ │ └── test.rb │ ├── initializers │ │ ├── backtrace_silencers.rb │ │ ├── inflections.rb │ │ ├── mime_types.rb │ │ ├── secret_token.rb │ │ └── session_store.rb │ ├── locales │ │ └── en.yml │ └── routes.rb │ ├── db │ ├── migrate │ │ ├── 20110508151935_add_account_table.rb │ │ └── 20110508151948_add_oauth2_tables.rb │ ├── schema.rb │ └── seeds.rb │ ├── doc │ └── README_FOR_APP │ ├── lib │ └── tasks │ │ └── .gitkeep │ ├── public │ ├── 404.html │ ├── 422.html │ ├── 500.html │ ├── favicon.ico │ ├── images │ │ └── rails.png │ ├── robots.txt │ └── stylesheets │ │ └── .gitkeep │ └── script │ └── rails ├── lib ├── oauth2-provider.rb └── oauth2 │ ├── provider.rb │ └── provider │ ├── models.rb │ ├── models │ ├── access_token.rb │ ├── active_record.rb │ ├── active_record │ │ ├── access_token.rb │ │ ├── authorization.rb │ │ ├── authorization_code.rb │ │ └── client.rb │ ├── authorization.rb │ ├── authorization_code.rb │ ├── client.rb │ ├── mongoid.rb │ └── mongoid │ │ ├── access_token.rb │ │ ├── authorization.rb │ │ ├── authorization_code.rb │ │ └── client.rb │ ├── rack.rb │ ├── rack │ ├── access_token_handler.rb │ ├── authorization_code_request.rb │ ├── authorization_codes_support.rb │ ├── middleware.rb │ ├── resource_request.rb │ └── responses.rb │ ├── rails.rb │ ├── rails │ └── controller_authentication.rb │ ├── random.rb │ └── version.rb ├── oauth2-provider.gemspec └── spec ├── models ├── access_token_spec.rb ├── authorization_code_spec.rb ├── authorization_spec.rb ├── client_spec.rb └── random_token_spec.rb ├── requests ├── access_tokens_controller_spec.rb ├── authentication_spec.rb ├── authorization_code_request_spec.rb └── middleware_spec.rb ├── schema.rb ├── set_backend_env_to_mongoid.rb ├── spec_helper.rb └── support ├── activerecord_backend.rb ├── factories.rb ├── macros.rb ├── mongoid_backend.rb └── rack.rb /.gitignore: -------------------------------------------------------------------------------- 1 | /.bundle 2 | /log 3 | /pkg 4 | /spec/*.db 5 | /*.db 6 | /doc 7 | /Gemfile.lock 8 | .idea 9 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: ruby 2 | rvm: 3 | - 1.8.7 4 | - 1.9.3 -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | source :rubygems 2 | 3 | gemspec -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (C) 2011 by Tom Ward 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to deal 5 | in the Software without restriction, including without limitation the rights 6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | copies of the Software, and to permit persons to whom the Software is 8 | furnished to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in 11 | all copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | THE SOFTWARE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | **DEPRECATION NOTICE**: This project is no longer supported. [Doorkeeper](https://github.com/doorkeeper-gem/doorkeeper) or [`Songkick::Oauth2::Provider`](https://github.com/songkick/oauth2-provider) might offer the functionality you're looking for. We're keeping this repository around in case anyone is still relying on it, but note that there are a number of security vulnerabilities in the gem's dependencies as things stand, so use it at your own risk. If anyone wants to take over ownership of the repo, please [get in touch](http://gofreerange.com/contact). 2 | 3 | oauth2-provider 4 | == 5 | 6 | Simple OAuth2 provider code extracted from [hashblue.com](https://hashblue.com/) 7 | 8 | Details 9 | -- 10 | 11 | * Implements [draft 11](http://tools.ietf.org/html/draft-ietf-oauth-v2-11) of the oauth2 spec 12 | * Handles the authorization_code, password, and client_credential grant types 13 | * Supports ActiveRecord and Mongoid 14 | 15 | Usage Instructions 16 | -- 17 | 18 | In your Gemfile: 19 | 20 | gem 'oauth2-provider', :git => 'git@github.com:freerange/oauth2-provider.git' 21 | 22 | If you're using ActiveRecord, grab the schema out of `spec/schema.rb`, and run the migration. 23 | 24 | To dish out authorization codes you will need to implement something like this: 25 | 26 | class AuthorizationController < ApplicationController 27 | include OAuth2::Provider::Rack::AuthorizationCodesSupport 28 | 29 | before_filter :authenticate_user! 30 | before_filter :block_invalid_authorization_code_requests 31 | 32 | def new 33 | @client = oauth2_authorization_request.client 34 | end 35 | 36 | def create 37 | if params[:yes].present? 38 | grant_authorization_code(current_user) 39 | else 40 | deny_authorization_code 41 | end 42 | end 43 | 44 | end 45 | 46 | And add a couple of routes: 47 | 48 | match "/oauth/authorize", :via => :get, :to => "authorization#new" 49 | match "/oauth/authorize", :via => :post, :to => "authorization#create" 50 | 51 | oauth2-provider will handle requests to `/oauth/access_token` to handle conversion of authorization codes to access tokens. 52 | -------------------------------------------------------------------------------- /Rakefile: -------------------------------------------------------------------------------- 1 | require 'bundler' 2 | Bundler::GemHelper.install_tasks 3 | 4 | require 'bundler/setup' 5 | require 'rspec/core/rake_task' 6 | 7 | namespace :spec do 8 | desc "Run specs using the active_record backend" 9 | RSpec::Core::RakeTask.new(:activerecord) do |t| 10 | t.rspec_opts = "-f n -c" 11 | t.pattern = "spec/**/*_spec.rb" 12 | end 13 | 14 | desc "Run specs using the mongoid backend" 15 | RSpec::Core::RakeTask.new(:mongoid) do |t| 16 | t.rspec_opts = "-f n -c" 17 | t.pattern = "spec/**/*_spec.rb" 18 | t.ruby_opts = "-Ispec -rset_backend_env_to_mongoid" 19 | end 20 | 21 | desc "Run specs using both backends" 22 | task :all => ['spec:activerecord', 'spec:mongoid'] 23 | end 24 | 25 | task :default => 'spec:all' -------------------------------------------------------------------------------- /examples/client/Gemfile: -------------------------------------------------------------------------------- 1 | source :rubygems 2 | 3 | gem 'sinatra' 4 | gem 'haml' 5 | 6 | gem 'httparty' -------------------------------------------------------------------------------- /examples/client/Gemfile.lock: -------------------------------------------------------------------------------- 1 | GEM 2 | remote: http://rubygems.org/ 3 | specs: 4 | crack (0.1.8) 5 | haml (3.0.18) 6 | httparty (0.7.4) 7 | crack (= 0.1.8) 8 | rack (1.2.2) 9 | sinatra (1.2.6) 10 | rack (~> 1.1) 11 | tilt (>= 1.2.2, < 2.0) 12 | tilt (1.3) 13 | 14 | PLATFORMS 15 | ruby 16 | 17 | DEPENDENCIES 18 | haml 19 | httparty 20 | sinatra 21 | -------------------------------------------------------------------------------- /examples/client/README: -------------------------------------------------------------------------------- 1 | This is a (very very) simple OAuth2 client, designed to work with the oauth2-provider examples. To get it running, cd to the client folder, then run: 2 | 3 | 1) bundle install 4 | 2) bundle exec rackup 5 | 6 | This should start the client on port 9292 7 | 8 | Assuming an example server is running (such as the one in examples/rails3-example), visit http://localhost:9292. To read content from the server you'll be asked to login (tomafro/secret) and then authorize the client. Finally some very simple content from the server will be shown. -------------------------------------------------------------------------------- /examples/client/app.rb: -------------------------------------------------------------------------------- 1 | require 'rubygems' 2 | require 'bundler/setup' 3 | require 'sinatra' 4 | require 'haml' 5 | require 'httparty' 6 | 7 | CLIENT_ID = 'abcdefgh12345678' 8 | CLIENT_SECRET = 'secret' 9 | RESOURCE_HOST = 'http://localhost:3000' 10 | 11 | enable :sessions 12 | 13 | helpers do 14 | def redirect_uri 15 | "http://" + request.host_with_port + "/callback" 16 | end 17 | 18 | def access_token 19 | session[:access_token] 20 | end 21 | 22 | def get_with_access_token(path) 23 | HTTParty.get(RESOURCE_HOST + path, :query => {:oauth_token => access_token}) 24 | end 25 | 26 | def authorize_url 27 | RESOURCE_HOST + "/oauth/authorize?client_id=#{CLIENT_ID}&client_secret=#{CLIENT_SECRET}&redirect_uri=#{redirect_uri}" 28 | end 29 | 30 | def access_token_url 31 | RESOURCE_HOST + "/oauth/access_token" 32 | end 33 | end 34 | 35 | get '/' do 36 | haml :home 37 | end 38 | 39 | get '/callback' do 40 | response = HTTParty.post(access_token_url, :body => { 41 | :client_id => CLIENT_ID, 42 | :client_secret => CLIENT_SECRET, 43 | :redirect_uri => redirect_uri, 44 | :code => params["code"], 45 | :grant_type => 'authorization_code'} 46 | ) 47 | 48 | session[:access_token] = response["access_token"] 49 | redirect '/account' 50 | end 51 | 52 | get '/account' do 53 | if access_token 54 | @resource_response = get_with_access_token("/account.json") 55 | haml :response 56 | else 57 | redirect authorize_url 58 | end 59 | end -------------------------------------------------------------------------------- /examples/client/config.ru: -------------------------------------------------------------------------------- 1 | require File.dirname(__FILE__) + '/app' 2 | 3 | run Sinatra::Application -------------------------------------------------------------------------------- /examples/client/views/home.haml: -------------------------------------------------------------------------------- 1 | To use this client, 2 | %a{:href => '/account'} 3 | click here -------------------------------------------------------------------------------- /examples/client/views/response.haml: -------------------------------------------------------------------------------- 1 | %div 2 | The response from the RESOURCE SERVER loading "/account.json :" 3 | %h2 4 | Headers 5 | %pre 6 | =@resource_response.headers.inspect 7 | %h2 8 | Body 9 | %pre 10 | =@resource_response.body.inspect 11 | -------------------------------------------------------------------------------- /examples/rails3-example/.gitignore: -------------------------------------------------------------------------------- 1 | .bundle 2 | db/*.sqlite3 3 | log/*.log 4 | tmp/ 5 | -------------------------------------------------------------------------------- /examples/rails3-example/Gemfile: -------------------------------------------------------------------------------- 1 | source 'http://rubygems.org' 2 | 3 | gem 'rails', '3.0.7' 4 | 5 | # Bundle edge Rails instead: 6 | # gem 'rails', :git => 'git://github.com/rails/rails.git' 7 | 8 | gem 'sqlite3' 9 | 10 | gem 'oauth2-provider', :path => '../..' -------------------------------------------------------------------------------- /examples/rails3-example/Gemfile.lock: -------------------------------------------------------------------------------- 1 | PATH 2 | remote: ../.. 3 | specs: 4 | oauth2-provider (0.0.18) 5 | activesupport (~> 3.0) 6 | addressable (~> 2.2) 7 | 8 | GEM 9 | remote: http://rubygems.org/ 10 | specs: 11 | abstract (1.0.0) 12 | actionmailer (3.0.7) 13 | actionpack (= 3.0.7) 14 | mail (~> 2.2.15) 15 | actionpack (3.0.7) 16 | activemodel (= 3.0.7) 17 | activesupport (= 3.0.7) 18 | builder (~> 2.1.2) 19 | erubis (~> 2.6.6) 20 | i18n (~> 0.5.0) 21 | rack (~> 1.2.1) 22 | rack-mount (~> 0.6.14) 23 | rack-test (~> 0.5.7) 24 | tzinfo (~> 0.3.23) 25 | activemodel (3.0.7) 26 | activesupport (= 3.0.7) 27 | builder (~> 2.1.2) 28 | i18n (~> 0.5.0) 29 | activerecord (3.0.7) 30 | activemodel (= 3.0.7) 31 | activesupport (= 3.0.7) 32 | arel (~> 2.0.2) 33 | tzinfo (~> 0.3.23) 34 | activeresource (3.0.7) 35 | activemodel (= 3.0.7) 36 | activesupport (= 3.0.7) 37 | activesupport (3.0.7) 38 | addressable (2.2.6) 39 | arel (2.0.9) 40 | builder (2.1.2) 41 | erubis (2.6.6) 42 | abstract (>= 1.0.0) 43 | i18n (0.5.0) 44 | mail (2.2.19) 45 | activesupport (>= 2.3.6) 46 | i18n (>= 0.4.0) 47 | mime-types (~> 1.16) 48 | treetop (~> 1.4.8) 49 | mime-types (1.16) 50 | polyglot (0.3.1) 51 | rack (1.2.2) 52 | rack-mount (0.6.14) 53 | rack (>= 1.0.0) 54 | rack-test (0.5.7) 55 | rack (>= 1.0) 56 | rails (3.0.7) 57 | actionmailer (= 3.0.7) 58 | actionpack (= 3.0.7) 59 | activerecord (= 3.0.7) 60 | activeresource (= 3.0.7) 61 | activesupport (= 3.0.7) 62 | bundler (~> 1.0) 63 | railties (= 3.0.7) 64 | railties (3.0.7) 65 | actionpack (= 3.0.7) 66 | activesupport (= 3.0.7) 67 | rake (>= 0.8.7) 68 | thor (~> 0.14.4) 69 | rake (0.8.7) 70 | sqlite3 (1.3.3) 71 | thor (0.14.6) 72 | treetop (1.4.9) 73 | polyglot (>= 0.3.1) 74 | tzinfo (0.3.27) 75 | 76 | PLATFORMS 77 | ruby 78 | 79 | DEPENDENCIES 80 | oauth2-provider! 81 | rails (= 3.0.7) 82 | sqlite3 83 | -------------------------------------------------------------------------------- /examples/rails3-example/README: -------------------------------------------------------------------------------- 1 | This is a (very) basic app demonstrating the oauth2-provider library. To get it going, cd to the app folder, then: 2 | 3 | 1) Run `bundle install` 4 | 2) Run `bundle exec rake db:reset db:seed` 5 | 3) Run `bundle exec rails server` 6 | 7 | This should start the app on port 3000 8 | 9 | To try it out, you need an oauth client. Luckily there's a (very very) simple one in the examples/client folder. Keep this example running, follow the instructions to start the client, then have a play. -------------------------------------------------------------------------------- /examples/rails3-example/Rakefile: -------------------------------------------------------------------------------- 1 | # Add your own tasks in files placed in lib/tasks ending in .rake, 2 | # for example lib/tasks/capistrano.rake, and they will automatically be available to Rake. 3 | 4 | require File.expand_path('../config/application', __FILE__) 5 | require 'rake' 6 | 7 | Rails3Example::Application.load_tasks 8 | -------------------------------------------------------------------------------- /examples/rails3-example/app/controllers/account_controller.rb: -------------------------------------------------------------------------------- 1 | class AccountController < ApplicationController 2 | authenticate_with_oauth 3 | before_filter :set_current_account_from_oauth 4 | 5 | def show 6 | render :json => {:login => current_account.login} 7 | end 8 | 9 | private 10 | 11 | def set_current_account_from_oauth 12 | @current_account = request.env['oauth2'].resource_owner 13 | end 14 | end -------------------------------------------------------------------------------- /examples/rails3-example/app/controllers/application_controller.rb: -------------------------------------------------------------------------------- 1 | class ApplicationController < ActionController::Base 2 | protect_from_forgery 3 | 4 | def current_account 5 | @current_account ||= session[:account_id] && Account.find_by_id(session[:account_id]) 6 | end 7 | 8 | helper_method :current_account 9 | 10 | private 11 | 12 | def authenticate_account 13 | unless current_account 14 | session[:return_url] = request.fullpath 15 | redirect_to new_session_url 16 | end 17 | end 18 | end 19 | -------------------------------------------------------------------------------- /examples/rails3-example/app/controllers/authorization_controller.rb: -------------------------------------------------------------------------------- 1 | class AuthorizationController < ApplicationController 2 | include OAuth2::Provider::Rack::AuthorizationCodesSupport 3 | 4 | before_filter :authenticate_account 5 | before_filter :block_invalid_authorization_code_requests 6 | before_filter :regrant_existing_authorization 7 | 8 | def new 9 | @client = oauth2_authorization_request.client 10 | end 11 | 12 | def create 13 | if params[:commit] == "Yes" 14 | grant_authorization_code(current_account) 15 | else 16 | deny_authorization_code 17 | end 18 | end 19 | 20 | private 21 | 22 | def regrant_existing_authorization 23 | oauth2_authorization_request.grant_existing! current_account 24 | end 25 | end -------------------------------------------------------------------------------- /examples/rails3-example/app/controllers/home_controller.rb: -------------------------------------------------------------------------------- 1 | class HomeController < ApplicationController 2 | def show 3 | end 4 | end -------------------------------------------------------------------------------- /examples/rails3-example/app/controllers/session_controller.rb: -------------------------------------------------------------------------------- 1 | class SessionController < ApplicationController 2 | class Session 3 | attr_accessor :login, :password 4 | end 5 | 6 | def new 7 | @session = Session.new 8 | end 9 | 10 | def create 11 | if account = Account.authenticate(params[:session][:login], params[:session][:password]) 12 | session[:account_id] = account.id 13 | redirect_to return_url 14 | else 15 | redirect_to :action => :new 16 | end 17 | end 18 | 19 | private 20 | 21 | def return_url 22 | session[:return_url] || root_url 23 | end 24 | end -------------------------------------------------------------------------------- /examples/rails3-example/app/helpers/application_helper.rb: -------------------------------------------------------------------------------- 1 | module ApplicationHelper 2 | end 3 | -------------------------------------------------------------------------------- /examples/rails3-example/app/models/account.rb: -------------------------------------------------------------------------------- 1 | class Account < ActiveRecord::Base 2 | def self.authenticate(login, password) 3 | # N.B. Don't use this for authentication in a real app 4 | find_by_login_and_password(login, password) 5 | end 6 | end -------------------------------------------------------------------------------- /examples/rails3-example/app/views/authorization/new.html.erb: -------------------------------------------------------------------------------- 1 | <%= form_for :authorization, :url => oauth_authorize_path(params.slice(:redirect_uri, :client_id, :client_secret)) do |form| %> 2 | Authorize 3 | <%= form.submit "Yes", :value => 'Yes' %> 4 | <%= form.submit "No", :value => 'No' %> 5 | <% end %> -------------------------------------------------------------------------------- /examples/rails3-example/app/views/home/show.html.erb: -------------------------------------------------------------------------------- 1 | Hello <%= current_account ? current_account.login : "stranger" %>! -------------------------------------------------------------------------------- /examples/rails3-example/app/views/layouts/application.html.erb: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 9 | Rails3Example 10 | <%= csrf_meta_tag %> 11 | 12 | 13 | 14 | <%= yield %> 15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /examples/rails3-example/app/views/session/new.html.erb: -------------------------------------------------------------------------------- 1 | <%= form_for :session, :url => session_path do |form| %> 2 | <%= form.label :login %> 3 | <%= form.text_field :login %> 4 | <%= form.label :password %> 5 | <%= form.password_field :password %> 6 | <%= form.submit "Login" %> 7 | <% end %> -------------------------------------------------------------------------------- /examples/rails3-example/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 Rails3Example::Application 5 | -------------------------------------------------------------------------------- /examples/rails3-example/config/application.rb: -------------------------------------------------------------------------------- 1 | require File.expand_path('../boot', __FILE__) 2 | 3 | require 'rails/all' 4 | 5 | # If you have a Gemfile, require the gems listed there, including any gems 6 | # you've limited to :test, :development, or :production. 7 | Bundler.require(:default, Rails.env) if defined?(Bundler) 8 | 9 | module Rails3Example 10 | class Application < Rails::Application 11 | # Settings in config/environments/* take precedence over those specified here. 12 | # Application configuration should go into files in config/initializers 13 | # -- all .rb files in that directory are automatically loaded. 14 | 15 | # Custom directories with classes and modules you want to be autoloadable. 16 | # config.autoload_paths += %W(#{config.root}/extras) 17 | 18 | # Only load the plugins named here, in the order given (default is alphabetical). 19 | # :all can be used as a placeholder for all plugins not explicitly named. 20 | # config.plugins = [ :exception_notification, :ssl_requirement, :all ] 21 | 22 | # Activate observers that should always be running. 23 | # config.active_record.observers = :cacher, :garbage_collector, :forum_observer 24 | 25 | # Set Time.zone default to the specified zone and make Active Record auto-convert to this zone. 26 | # Run "rake -D time" for a list of tasks for finding time zone names. Default is UTC. 27 | # config.time_zone = 'Central Time (US & Canada)' 28 | 29 | # The default locale is :en and all translations from config/locales/*.rb,yml are auto loaded. 30 | # config.i18n.load_path += Dir[Rails.root.join('my', 'locales', '*.{rb,yml}').to_s] 31 | # config.i18n.default_locale = :de 32 | 33 | # JavaScript files you want as :defaults (application.js is always included). 34 | # config.action_view.javascript_expansions[:defaults] = %w(jquery rails) 35 | 36 | # Configure the default encoding used in templates for Ruby 1.9. 37 | config.encoding = "utf-8" 38 | 39 | # Configure sensitive parameters which will be filtered from the log file. 40 | config.filter_parameters += [:password] 41 | end 42 | end 43 | -------------------------------------------------------------------------------- /examples/rails3-example/config/boot.rb: -------------------------------------------------------------------------------- 1 | require 'rubygems' 2 | 3 | # Set up gems listed in the Gemfile. 4 | ENV['BUNDLE_GEMFILE'] ||= File.expand_path('../../Gemfile', __FILE__) 5 | 6 | require 'bundler/setup' if File.exists?(ENV['BUNDLE_GEMFILE']) 7 | -------------------------------------------------------------------------------- /examples/rails3-example/config/database.yml: -------------------------------------------------------------------------------- 1 | # SQLite version 3.x 2 | # gem install sqlite3 3 | development: 4 | adapter: sqlite3 5 | database: db/development.sqlite3 6 | pool: 5 7 | timeout: 5000 8 | 9 | # Warning: The database defined as "test" will be erased and 10 | # re-generated from your development database when you run "rake". 11 | # Do not set this db to the same as development or production. 12 | test: 13 | adapter: sqlite3 14 | database: db/test.sqlite3 15 | pool: 5 16 | timeout: 5000 17 | 18 | production: 19 | adapter: sqlite3 20 | database: db/production.sqlite3 21 | pool: 5 22 | timeout: 5000 23 | -------------------------------------------------------------------------------- /examples/rails3-example/config/environment.rb: -------------------------------------------------------------------------------- 1 | # Load the rails application 2 | require File.expand_path('../application', __FILE__) 3 | 4 | # Initialize the rails application 5 | Rails3Example::Application.initialize! 6 | -------------------------------------------------------------------------------- /examples/rails3-example/config/environments/development.rb: -------------------------------------------------------------------------------- 1 | Rails3Example::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 webserver when you make code changes. 7 | config.cache_classes = false 8 | 9 | # Log error messages when you accidentally call methods on nil. 10 | config.whiny_nils = true 11 | 12 | # Show full error reports and disable caching 13 | config.consider_all_requests_local = true 14 | config.action_view.debug_rjs = true 15 | config.action_controller.perform_caching = false 16 | 17 | # Don't care if the mailer can't send 18 | config.action_mailer.raise_delivery_errors = false 19 | 20 | # Print deprecation notices to the Rails logger 21 | config.active_support.deprecation = :log 22 | 23 | # Only use best-standards-support built into browsers 24 | config.action_dispatch.best_standards_support = :builtin 25 | end 26 | 27 | -------------------------------------------------------------------------------- /examples/rails3-example/config/environments/production.rb: -------------------------------------------------------------------------------- 1 | Rails3Example::Application.configure do 2 | # Settings specified here will take precedence over those in config/application.rb 3 | 4 | # The production environment is meant for finished, "live" apps. 5 | # Code is not reloaded between requests 6 | config.cache_classes = true 7 | 8 | # Full error reports are disabled and caching is turned on 9 | config.consider_all_requests_local = false 10 | config.action_controller.perform_caching = true 11 | 12 | # Specifies the header that your server uses for sending files 13 | config.action_dispatch.x_sendfile_header = "X-Sendfile" 14 | 15 | # For nginx: 16 | # config.action_dispatch.x_sendfile_header = 'X-Accel-Redirect' 17 | 18 | # If you have no front-end server that supports something like X-Sendfile, 19 | # just comment this out and Rails will serve the files 20 | 21 | # See everything in the log (default is :info) 22 | # config.log_level = :debug 23 | 24 | # Use a different logger for distributed setups 25 | # config.logger = SyslogLogger.new 26 | 27 | # Use a different cache store in production 28 | # config.cache_store = :mem_cache_store 29 | 30 | # Disable Rails's static asset server 31 | # In production, Apache or nginx will already do this 32 | config.serve_static_assets = false 33 | 34 | # Enable serving of images, stylesheets, and javascripts from an asset server 35 | # config.action_controller.asset_host = "http://assets.example.com" 36 | 37 | # Disable delivery errors, bad email addresses will be ignored 38 | # config.action_mailer.raise_delivery_errors = false 39 | 40 | # Enable threaded mode 41 | # config.threadsafe! 42 | 43 | # Enable locale fallbacks for I18n (makes lookups for any locale fall back to 44 | # the I18n.default_locale when a translation can not be found) 45 | config.i18n.fallbacks = true 46 | 47 | # Send deprecation notices to registered listeners 48 | config.active_support.deprecation = :notify 49 | end 50 | -------------------------------------------------------------------------------- /examples/rails3-example/config/environments/test.rb: -------------------------------------------------------------------------------- 1 | Rails3Example::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 | # Log error messages when you accidentally call methods on nil. 11 | config.whiny_nils = true 12 | 13 | # Show full error reports and disable caching 14 | config.consider_all_requests_local = true 15 | config.action_controller.perform_caching = false 16 | 17 | # Raise exceptions instead of rendering exception templates 18 | config.action_dispatch.show_exceptions = false 19 | 20 | # Disable request forgery protection in test environment 21 | config.action_controller.allow_forgery_protection = false 22 | 23 | # Tell Action Mailer not to deliver emails to the real world. 24 | # The :test delivery method accumulates sent emails in the 25 | # ActionMailer::Base.deliveries array. 26 | config.action_mailer.delivery_method = :test 27 | 28 | # Use SQL instead of Active Record's schema dumper when creating the test database. 29 | # This is necessary if your schema can't be completely dumped by the schema dumper, 30 | # like if you have constraints or database-specific column types 31 | # config.active_record.schema_format = :sql 32 | 33 | # Print deprecation notices to the stderr 34 | config.active_support.deprecation = :stderr 35 | end 36 | -------------------------------------------------------------------------------- /examples/rails3-example/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 | -------------------------------------------------------------------------------- /examples/rails3-example/config/initializers/inflections.rb: -------------------------------------------------------------------------------- 1 | # Be sure to restart your server when you modify this file. 2 | 3 | # Add new inflection rules using the following format 4 | # (all these examples are active by default): 5 | # ActiveSupport::Inflector.inflections do |inflect| 6 | # inflect.plural /^(ox)$/i, '\1en' 7 | # inflect.singular /^(ox)en/i, '\1' 8 | # inflect.irregular 'person', 'people' 9 | # inflect.uncountable %w( fish sheep ) 10 | # end 11 | -------------------------------------------------------------------------------- /examples/rails3-example/config/initializers/mime_types.rb: -------------------------------------------------------------------------------- 1 | # Be sure to restart your server when you modify this file. 2 | 3 | # Add new mime types for use in respond_to blocks: 4 | # Mime::Type.register "text/richtext", :rtf 5 | # Mime::Type.register_alias "text/html", :iphone 6 | -------------------------------------------------------------------------------- /examples/rails3-example/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 | Rails3Example::Application.config.secret_token = 'e5f8bf909ecfaf4a9a4b275acb58dc8126733a4d4a25bec1266a624296cd1845c3d6c19fcc44f2346a31aac4a25544348e9ebada6903bb35d5f6a235954cff26' 8 | -------------------------------------------------------------------------------- /examples/rails3-example/config/initializers/session_store.rb: -------------------------------------------------------------------------------- 1 | # Be sure to restart your server when you modify this file. 2 | 3 | Rails3Example::Application.config.session_store :cookie_store, :key => '_rails3-example_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 | # Rails3Example::Application.config.session_store :active_record_store 9 | -------------------------------------------------------------------------------- /examples/rails3-example/config/locales/en.yml: -------------------------------------------------------------------------------- 1 | # Sample localization file for English. Add more files in this directory for other locales. 2 | # See http://github.com/svenfuchs/rails-i18n/tree/master/rails%2Flocale for starting points. 3 | 4 | en: 5 | hello: "Hello world" 6 | -------------------------------------------------------------------------------- /examples/rails3-example/config/routes.rb: -------------------------------------------------------------------------------- 1 | Rails3Example::Application.routes.draw do 2 | resource :session, :controller => :session 3 | resource :account, :controller => :account 4 | 5 | match "/oauth/authorize", :via => :get, :to => "authorization#new" 6 | match "/oauth/authorize", :via => :post, :to => "authorization#create" 7 | 8 | root :to => 'home#show' 9 | end 10 | -------------------------------------------------------------------------------- /examples/rails3-example/db/migrate/20110508151935_add_account_table.rb: -------------------------------------------------------------------------------- 1 | class AddAccountTable < ActiveRecord::Migration 2 | def self.up 3 | create_table :accounts, :force => true do |table| 4 | table.string :login, :null => false 5 | table.string :password, :null => false 6 | end 7 | end 8 | 9 | def self.down 10 | drop_table :accounts 11 | end 12 | end 13 | -------------------------------------------------------------------------------- /examples/rails3-example/db/migrate/20110508151948_add_oauth2_tables.rb: -------------------------------------------------------------------------------- 1 | class AddOauth2Tables < ActiveRecord::Migration 2 | def self.up 3 | create_table 'oauth_clients', :force => true do |t| 4 | t.string 'name' 5 | t.string 'oauth_identifier', :null => false 6 | t.string 'oauth_secret', :null => false 7 | t.string 'oauth_redirect_uri' 8 | end 9 | 10 | create_table 'oauth_authorization_codes', :force => true do |t| 11 | t.integer 'authorization_id', :null => false 12 | t.string 'code', :null => false 13 | t.datetime 'expires_at' 14 | t.datetime 'created_at' 15 | t.datetime 'updated_at' 16 | t.string 'redirect_uri' 17 | end 18 | 19 | create_table 'oauth_authorizations', :force => true do |t| 20 | t.integer 'client_id', :null => false 21 | t.integer 'resource_owner_id' 22 | t.string 'resource_owner_type' 23 | t.string 'scope' 24 | t.datetime 'expires_at' 25 | end 26 | 27 | create_table 'oauth_access_tokens', :force => true do |t| 28 | t.integer 'authorization_id', :null => false 29 | t.string 'access_token', :null => false 30 | t.string 'refresh_token' 31 | t.datetime 'expires_at' 32 | t.datetime 'created_at' 33 | t.datetime 'updated_at' 34 | end 35 | end 36 | 37 | def self.down 38 | drop_table 'oauth_access_tokens' 39 | drop_table 'oauth_authorizations' 40 | drop_table 'oauth_authorization_codes' 41 | drop_table 'oauth_clients' 42 | end 43 | end 44 | -------------------------------------------------------------------------------- /examples/rails3-example/db/schema.rb: -------------------------------------------------------------------------------- 1 | # This file is auto-generated from the current state of the database. Instead 2 | # of editing this file, please use the migrations feature of Active Record to 3 | # incrementally modify your database, and then regenerate this schema definition. 4 | # 5 | # Note that this schema.rb definition is the authoritative source for your 6 | # database schema. If you need to create the application database on another 7 | # system, you should be using db:schema:load, not running all the migrations 8 | # from scratch. The latter is a flawed and unsustainable approach (the more migrations 9 | # you'll amass, the slower it'll run and the greater likelihood for issues). 10 | # 11 | # It's strongly recommended to check this file into your version control system. 12 | 13 | ActiveRecord::Schema.define(:version => 20110614224516) do 14 | 15 | create_table "accounts", :force => true do |t| 16 | t.string "login", :null => false 17 | t.string "password", :null => false 18 | end 19 | 20 | create_table "oauth_access_tokens", :force => true do |t| 21 | t.integer "authorization_id", :null => false 22 | t.string "access_token", :null => false 23 | t.string "refresh_token" 24 | t.datetime "expires_at" 25 | t.datetime "created_at" 26 | t.datetime "updated_at" 27 | end 28 | 29 | create_table "oauth_authorization_codes", :force => true do |t| 30 | t.integer "authorization_id", :null => false 31 | t.string "code", :null => false 32 | t.datetime "expires_at" 33 | t.datetime "created_at" 34 | t.datetime "updated_at" 35 | t.string "redirect_uri" 36 | end 37 | 38 | create_table "oauth_authorizations", :force => true do |t| 39 | t.integer "client_id", :null => false 40 | t.integer "resource_owner_id" 41 | t.string "resource_owner_type" 42 | t.string "scope" 43 | t.datetime "expires_at" 44 | end 45 | 46 | create_table "oauth_clients", :force => true do |t| 47 | t.string "name" 48 | t.string "oauth_identifier", :null => false 49 | t.string "oauth_secret", :null => false 50 | t.string "oauth_redirect_uri" 51 | end 52 | 53 | end 54 | -------------------------------------------------------------------------------- /examples/rails3-example/db/seeds.rb: -------------------------------------------------------------------------------- 1 | # This file should contain all the record creation needed to seed the database with its default values. 2 | # The data can then be loaded with the rake db:seed (or created alongside the db with db:setup). 3 | # 4 | # Examples: 5 | # 6 | # cities = City.create([{ :name => 'Chicago' }, { :name => 'Copenhagen' }]) 7 | # Mayor.create(:name => 'Daley', :city => cities.first) 8 | 9 | OAuth2::Provider.client_class.create! :name => 'Example Client', :oauth_identifier => 'abcdefgh12345678', :oauth_secret => 'secret' 10 | 11 | Account.create! :login => 'tomafro', :password => 'secret' -------------------------------------------------------------------------------- /examples/rails3-example/doc/README_FOR_APP: -------------------------------------------------------------------------------- 1 | Use this README file to introduce your application and point to useful places in the API for learning more. 2 | Run "rake doc:app" to generate API documentation for your models, controllers, helpers, and libraries. 3 | -------------------------------------------------------------------------------- /examples/rails3-example/lib/tasks/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/freerange/oauth2-provider/d8c390f523e921df48a0cb11939497811b93b49c/examples/rails3-example/lib/tasks/.gitkeep -------------------------------------------------------------------------------- /examples/rails3-example/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 | -------------------------------------------------------------------------------- /examples/rails3-example/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 | -------------------------------------------------------------------------------- /examples/rails3-example/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 | -------------------------------------------------------------------------------- /examples/rails3-example/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/freerange/oauth2-provider/d8c390f523e921df48a0cb11939497811b93b49c/examples/rails3-example/public/favicon.ico -------------------------------------------------------------------------------- /examples/rails3-example/public/images/rails.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/freerange/oauth2-provider/d8c390f523e921df48a0cb11939497811b93b49c/examples/rails3-example/public/images/rails.png -------------------------------------------------------------------------------- /examples/rails3-example/public/robots.txt: -------------------------------------------------------------------------------- 1 | # See http://www.robotstxt.org/wc/norobots.html for documentation on how to use the robots.txt file 2 | # 3 | # To ban all spiders from the entire site uncomment the next two lines: 4 | # User-Agent: * 5 | # Disallow: / 6 | -------------------------------------------------------------------------------- /examples/rails3-example/public/stylesheets/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/freerange/oauth2-provider/d8c390f523e921df48a0cb11939497811b93b49c/examples/rails3-example/public/stylesheets/.gitkeep -------------------------------------------------------------------------------- /examples/rails3-example/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 | -------------------------------------------------------------------------------- /lib/oauth2-provider.rb: -------------------------------------------------------------------------------- 1 | require 'oauth2/provider/rails' if defined?(Rails) 2 | require 'oauth2/provider' 3 | -------------------------------------------------------------------------------- /lib/oauth2/provider.rb: -------------------------------------------------------------------------------- 1 | require 'active_support/all' 2 | 3 | module OAuth2 4 | module Provider 5 | autoload :Rails, 'oauth2/provider/rails' 6 | autoload :Models, 'oauth2/provider/models' 7 | autoload :Random, 'oauth2/provider/random' 8 | autoload :Rack, 'oauth2/provider/rack' 9 | 10 | mattr_accessor :backend 11 | self.backend = :activerecord 12 | 13 | mattr_accessor :authorization_class_name 14 | mattr_accessor :access_token_class_name 15 | mattr_accessor :authorization_code_class_name 16 | mattr_accessor :client_class_name 17 | 18 | [:resource_owner, :client, :authorization, :access_token, :authorization_code].each do |model| 19 | instance_eval %{ 20 | def #{model}_class 21 | #{model}_class_name.constantize 22 | end 23 | } 24 | end 25 | 26 | mattr_accessor :resource_owner_class_name 27 | self.resource_owner_class_name = 'ResourceOwner' 28 | mattr_accessor :access_token_path 29 | self.access_token_path = '/oauth/access_token' 30 | 31 | def self.configure 32 | yield self 33 | activate 34 | end 35 | 36 | def self.activate 37 | case backend 38 | when :mongoid then OAuth2::Provider::Models::Mongoid.activate 39 | else OAuth2::Provider::Models::ActiveRecord.activate 40 | end 41 | end 42 | end 43 | end 44 | -------------------------------------------------------------------------------- /lib/oauth2/provider/models.rb: -------------------------------------------------------------------------------- 1 | module OAuth2::Provider::Models 2 | autoload :ActiveRecord, 'oauth2/provider/models/active_record' 3 | autoload :Mongoid, 'oauth2/provider/models/mongoid' 4 | 5 | autoload :Authorization, 'oauth2/provider/models/authorization' 6 | autoload :AccessToken, 'oauth2/provider/models/access_token' 7 | autoload :AuthorizationCode, 'oauth2/provider/models/authorization_code' 8 | autoload :Client, 'oauth2/provider/models/client' 9 | 10 | module TokenExpiry 11 | extend ActiveSupport::Concern 12 | 13 | included do 14 | mattr_accessor :default_token_lifespan 15 | end 16 | 17 | def initialize(attributes = {}, *args, &block) 18 | attributes ||= {} 19 | if default_token_lifespan 20 | attributes = attributes.reverse_merge(:expires_at => default_token_lifespan.from_now) 21 | end 22 | super 23 | end 24 | 25 | def fresh? 26 | !expired? 27 | end 28 | 29 | def expired? 30 | self.expires_at && self.expires_at < Time.now 31 | end 32 | 33 | def expires_in 34 | if expired? 35 | 0 36 | else 37 | self.expires_at && self.expires_at.to_i - Time.now.to_i 38 | end 39 | end 40 | end 41 | 42 | module RandomToken 43 | extend ActiveSupport::Concern 44 | 45 | module ClassMethods 46 | def random_token 47 | OAuth2::Provider::Random.base62(48) 48 | end 49 | 50 | def unique_random_token(attribute) 51 | key = random_token while (key.nil? || where(attribute => key).exists?) 52 | key 53 | end 54 | end 55 | end 56 | end -------------------------------------------------------------------------------- /lib/oauth2/provider/models/access_token.rb: -------------------------------------------------------------------------------- 1 | module OAuth2::Provider::Models::AccessToken 2 | extend ActiveSupport::Concern 3 | 4 | included do 5 | include OAuth2::Provider::Models::TokenExpiry, OAuth2::Provider::Models::RandomToken 6 | self.default_token_lifespan = 1.month 7 | 8 | validates_presence_of :authorization, :access_token 9 | validate :expires_at_isnt_greater_than_authorization 10 | 11 | delegate :scope, :has_scope?, :client, :resource_owner, :to => :authorization 12 | end 13 | 14 | def initialize(attributes = {}, *args, &block) 15 | attributes ||= {} # Mongoid passes in nil 16 | super attributes.reverse_merge( 17 | :access_token => self.class.unique_random_token(:access_token), 18 | :refresh_token => self.class.unique_random_token(:refresh_token) 19 | ) 20 | end 21 | 22 | def as_json(options = {}) 23 | {"access_token" => access_token}.tap do |result| 24 | result["expires_in"] = expires_in if expires_at.present? 25 | result["refresh_token"] = refresh_token if refresh_token.present? 26 | end 27 | end 28 | 29 | def refreshable? 30 | refresh_token.present? && authorization.fresh? 31 | end 32 | 33 | private 34 | 35 | def expires_at_isnt_greater_than_authorization 36 | if !authorization.nil? && authorization.expires_at 37 | unless expires_at.nil? || expires_at <= authorization.expires_at 38 | errors.add(:expires_at, :must_be_less_than_authorization) 39 | end 40 | end 41 | end 42 | 43 | module ClassMethods 44 | def refresh_with(refresh_token) 45 | if refresh_token && token = find_by_refresh_token(refresh_token) 46 | if token.refreshable? 47 | new(:authorization => token.authorization).tap do |result| 48 | if result.authorization.expires_at && result.authorization.expires_at < result.expires_at 49 | result.expires_at = result.authorization.expires_at 50 | end 51 | result.save! 52 | end 53 | end 54 | end 55 | end 56 | end 57 | end 58 | -------------------------------------------------------------------------------- /lib/oauth2/provider/models/active_record.rb: -------------------------------------------------------------------------------- 1 | module OAuth2::Provider::Models::ActiveRecord 2 | autoload :Authorization, 'oauth2/provider/models/active_record/authorization' 3 | autoload :AccessToken, 'oauth2/provider/models/active_record/access_token' 4 | autoload :AuthorizationCode, 'oauth2/provider/models/active_record/authorization_code' 5 | autoload :Client, 'oauth2/provider/models/active_record/client' 6 | 7 | mattr_accessor :client_table_name 8 | self.client_table_name = 'oauth_clients' 9 | 10 | mattr_accessor :access_token_table_name 11 | self.access_token_table_name = 'oauth_access_tokens' 12 | 13 | mattr_accessor :authorization_code_table_name 14 | self.authorization_code_table_name = 'oauth_authorization_codes' 15 | 16 | mattr_accessor :authorization_table_name 17 | self.authorization_table_name = 'oauth_authorizations' 18 | 19 | def self.activate(options = {}) 20 | OAuth2::Provider.client_class_name ||= "OAuth2::Provider::Models::ActiveRecord::Client" 21 | OAuth2::Provider.access_token_class_name ||= "OAuth2::Provider::Models::ActiveRecord::AccessToken" 22 | OAuth2::Provider.authorization_code_class_name ||= "OAuth2::Provider::Models::ActiveRecord::AuthorizationCode" 23 | OAuth2::Provider.authorization_class_name ||= "OAuth2::Provider::Models::ActiveRecord::Authorization" 24 | 25 | OAuth2::Provider.client_class.table_name = client_table_name 26 | OAuth2::Provider.access_token_class.table_name = access_token_table_name 27 | OAuth2::Provider.authorization_code_class.table_name = authorization_code_table_name 28 | OAuth2::Provider.authorization_class.table_name = authorization_table_name 29 | end 30 | end 31 | -------------------------------------------------------------------------------- /lib/oauth2/provider/models/active_record/access_token.rb: -------------------------------------------------------------------------------- 1 | class OAuth2::Provider::Models::ActiveRecord::AccessToken < ActiveRecord::Base 2 | module Behaviour 3 | extend ActiveSupport::Concern 4 | 5 | included do 6 | include OAuth2::Provider::Models::AccessToken 7 | 8 | belongs_to :authorization, :class_name => OAuth2::Provider.authorization_class_name, :foreign_key => 'authorization_id' 9 | end 10 | end 11 | 12 | include Behaviour 13 | end -------------------------------------------------------------------------------- /lib/oauth2/provider/models/active_record/authorization.rb: -------------------------------------------------------------------------------- 1 | class OAuth2::Provider::Models::ActiveRecord::Authorization < ActiveRecord::Base 2 | module Behaviour 3 | extend ActiveSupport::Concern 4 | 5 | included do 6 | include OAuth2::Provider::Models::Authorization 7 | 8 | belongs_to :client, :class_name => OAuth2::Provider.client_class_name, :foreign_key => 'client_id' 9 | 10 | has_many :access_tokens, :class_name => OAuth2::Provider.access_token_class_name, :foreign_key => 'authorization_id' 11 | has_many :authorization_codes, :class_name => OAuth2::Provider.authorization_code_class_name, :foreign_key => 'authorization_id' 12 | end 13 | end 14 | 15 | include Behaviour 16 | end -------------------------------------------------------------------------------- /lib/oauth2/provider/models/active_record/authorization_code.rb: -------------------------------------------------------------------------------- 1 | class OAuth2::Provider::Models::ActiveRecord::AuthorizationCode < ActiveRecord::Base 2 | module Behaviour 3 | extend ActiveSupport::Concern 4 | 5 | included do 6 | include OAuth2::Provider::Models::AuthorizationCode 7 | 8 | belongs_to :authorization, :class_name => OAuth2::Provider.authorization_class_name, :foreign_key => 'authorization_id' 9 | end 10 | end 11 | 12 | include Behaviour 13 | end -------------------------------------------------------------------------------- /lib/oauth2/provider/models/active_record/client.rb: -------------------------------------------------------------------------------- 1 | class OAuth2::Provider::Models::ActiveRecord::Client < ActiveRecord::Base 2 | module Behaviour 3 | extend ActiveSupport::Concern 4 | 5 | included do 6 | include OAuth2::Provider::Models::Client 7 | 8 | has_many :authorizations, :class_name => OAuth2::Provider.authorization_class_name, :foreign_key => 'client_id' 9 | has_many :authorization_codes, :through => :authorizations, :class_name => OAuth2::Provider.authorization_code_class_name 10 | has_many :access_tokens, :through => :authorizations, :class_name => OAuth2::Provider.access_token_class_name 11 | end 12 | end 13 | 14 | include Behaviour 15 | end -------------------------------------------------------------------------------- /lib/oauth2/provider/models/authorization.rb: -------------------------------------------------------------------------------- 1 | module OAuth2::Provider::Models::Authorization 2 | extend ActiveSupport::Concern 3 | 4 | included do 5 | include OAuth2::Provider::Models::TokenExpiry 6 | self.default_token_lifespan = nil 7 | 8 | validates_presence_of :client 9 | end 10 | 11 | def has_scope?(s) 12 | scope && scope.split(" ").include?(s) 13 | end 14 | 15 | def revoke 16 | authorization_codes.destroy_all 17 | access_tokens.destroy_all 18 | destroy 19 | end 20 | 21 | def resource_owner=(ro) 22 | self.resource_owner_id = ro && ro.id 23 | self.resource_owner_type = ro && ro.class.name 24 | end 25 | 26 | def resource_owner 27 | resource_owner_id && resource_owner_class.find(resource_owner_id) 28 | end 29 | 30 | def resource_owner_class 31 | resource_owner_type.constantize 32 | end 33 | 34 | module ClassMethods 35 | def allowing(client, owner, scope) 36 | where( 37 | :client_id => client.id, 38 | :resource_owner_id => owner && owner.id, 39 | :resource_owner_type => owner && owner.class.name, 40 | :scope => scope 41 | ).select(&:fresh?) 42 | end 43 | 44 | def all_for(ro) 45 | return [] unless ro 46 | self.where(:resource_owner_id => ro.id, :resource_owner_type => ro.class.name).all 47 | end 48 | end 49 | end -------------------------------------------------------------------------------- /lib/oauth2/provider/models/authorization_code.rb: -------------------------------------------------------------------------------- 1 | module OAuth2::Provider::Models::AuthorizationCode 2 | extend ActiveSupport::Concern 3 | 4 | included do 5 | include OAuth2::Provider::Models::TokenExpiry, OAuth2::Provider::Models::RandomToken 6 | self.default_token_lifespan = 1.minute 7 | 8 | delegate :client, :resource_owner, :to => :authorization 9 | validates_presence_of :authorization, :code, :expires_at, :redirect_uri 10 | end 11 | 12 | def initialize(*args) 13 | super 14 | self.code ||= self.class.unique_random_token(:code) 15 | end 16 | 17 | module ClassMethods 18 | def claim(code, redirect_uri) 19 | if authorization_code = find_by_code_and_redirect_uri(code, redirect_uri) 20 | if authorization_code.fresh? 21 | authorization_code.destroy 22 | authorization_code.authorization.access_tokens.create! 23 | end 24 | end 25 | end 26 | end 27 | end -------------------------------------------------------------------------------- /lib/oauth2/provider/models/client.rb: -------------------------------------------------------------------------------- 1 | require 'addressable/uri' 2 | 3 | module OAuth2::Provider::Models::Client 4 | extend ActiveSupport::Concern 5 | 6 | included do 7 | include OAuth2::Provider::Models::RandomToken 8 | validates_presence_of :oauth_identifier, :oauth_secret, :name 9 | validates_uniqueness_of :oauth_identifier 10 | end 11 | 12 | def initialize(*args, &block) 13 | super 14 | self.oauth_identifier ||= self.class.unique_random_token(:oauth_identifier) 15 | self.oauth_secret ||= self.class.unique_random_token(:oauth_secret) 16 | end 17 | 18 | def to_param 19 | new_record? ? nil : oauth_identifier 20 | end 21 | 22 | def allow_grant_type?(grant_type) 23 | true 24 | end 25 | 26 | def allow_redirection?(uri) 27 | uri_host = Addressable::URI.parse(uri).host 28 | unless oauth_redirect_uri.nil? or oauth_redirect_uri.empty? 29 | Addressable::URI.parse(oauth_redirect_uri).host == uri_host 30 | else 31 | !uri_host.nil? && true 32 | end 33 | rescue Addressable::URI::InvalidURIError 34 | false 35 | end 36 | 37 | module ClassMethods 38 | def from_param(identifier) 39 | self.find_by_oauth_identifier(identifier) 40 | end 41 | end 42 | end -------------------------------------------------------------------------------- /lib/oauth2/provider/models/mongoid.rb: -------------------------------------------------------------------------------- 1 | module OAuth2::Provider::Models::Mongoid 2 | autoload :Authorization, 'oauth2/provider/models/mongoid/authorization' 3 | autoload :AccessToken, 'oauth2/provider/models/mongoid/access_token' 4 | autoload :AuthorizationCode, 'oauth2/provider/models/mongoid/authorization_code' 5 | autoload :Client, 'oauth2/provider/models/mongoid/client' 6 | 7 | mattr_accessor :client_collection_name 8 | self.client_collection_name = 'oauth_clients' 9 | 10 | mattr_accessor :access_token_collection_name 11 | self.access_token_collection_name = 'oauth_access_tokens' 12 | 13 | mattr_accessor :authorization_code_collection_name 14 | self.authorization_code_collection_name = 'oauth_authorization_codes' 15 | 16 | mattr_accessor :authorization_collection_name 17 | self.authorization_collection_name = 'oauth_authorizations' 18 | 19 | def self.activate(options = {}) 20 | OAuth2::Provider.client_class_name ||= "OAuth2::Provider::Models::Mongoid::Client" 21 | OAuth2::Provider.access_token_class_name ||= "OAuth2::Provider::Models::Mongoid::AccessToken" 22 | OAuth2::Provider.authorization_code_class_name ||= "OAuth2::Provider::Models::Mongoid::AuthorizationCode" 23 | OAuth2::Provider.authorization_class_name ||= "OAuth2::Provider::Models::Mongoid::Authorization" 24 | 25 | OAuth2::Provider.client_class.collection_name = client_collection_name 26 | OAuth2::Provider.access_token_class.collection_name = access_token_collection_name 27 | OAuth2::Provider.authorization_code_class.collection_name = authorization_code_collection_name 28 | OAuth2::Provider.authorization_class.collection_name = authorization_collection_name 29 | end 30 | end -------------------------------------------------------------------------------- /lib/oauth2/provider/models/mongoid/access_token.rb: -------------------------------------------------------------------------------- 1 | class OAuth2::Provider::Models::Mongoid::AccessToken 2 | module Behaviour 3 | extend ActiveSupport::Concern 4 | 5 | included do 6 | include ::Mongoid::Document 7 | include OAuth2::Provider::Models::AccessToken 8 | 9 | field :access_token 10 | field :expires_at, :type => Time 11 | field :refresh_token 12 | 13 | belongs_to(:authorization, 14 | :class_name => OAuth2::Provider.authorization_class_name, 15 | :foreign_key => :oauth_authorization_id 16 | ) 17 | 18 | belongs_to(:client, 19 | :class_name => OAuth2::Provider.client_class_name, 20 | :foreign_key => :oauth_client_id 21 | ) 22 | 23 | before_save do 24 | self.client ||= authorization.client 25 | end 26 | end 27 | 28 | module ClassMethods 29 | def find_by_refresh_token(refresh_token) 30 | where(:refresh_token => refresh_token).first 31 | end 32 | 33 | def find_by_access_token(access_token) 34 | where(:access_token => access_token).first 35 | end 36 | end 37 | end 38 | 39 | include Behaviour 40 | end -------------------------------------------------------------------------------- /lib/oauth2/provider/models/mongoid/authorization.rb: -------------------------------------------------------------------------------- 1 | class OAuth2::Provider::Models::Mongoid::Authorization 2 | module Behaviour 3 | extend ActiveSupport::Concern 4 | 5 | included do 6 | include ::Mongoid::Document 7 | include OAuth2::Provider::Models::Authorization 8 | 9 | field :scope 10 | field :expires_at, :type => Time 11 | field :resource_owner_id 12 | field :resource_owner_type 13 | 14 | belongs_to(:client, 15 | :class_name => OAuth2::Provider.client_class_name, 16 | :foreign_key => :client_id 17 | ) 18 | 19 | has_many(:access_tokens, 20 | :class_name => OAuth2::Provider.access_token_class_name, 21 | :foreign_key => :oauth_authorization_id 22 | ) 23 | 24 | has_many(:authorization_codes, 25 | :class_name => OAuth2::Provider.authorization_code_class_name, 26 | :foreign_key => :oauth_authorization_id 27 | ) 28 | end 29 | end 30 | 31 | include Behaviour 32 | end -------------------------------------------------------------------------------- /lib/oauth2/provider/models/mongoid/authorization_code.rb: -------------------------------------------------------------------------------- 1 | class OAuth2::Provider::Models::Mongoid::AuthorizationCode 2 | module Behaviour 3 | extend ActiveSupport::Concern 4 | 5 | included do 6 | include ::Mongoid::Document 7 | include OAuth2::Provider::Models::AuthorizationCode 8 | 9 | field :code 10 | field :expires_at, :type => Time 11 | field :redirect_uri 12 | 13 | belongs_to(:authorization, 14 | :class_name => OAuth2::Provider.authorization_class_name, 15 | :foreign_key => :oauth_authorization_id 16 | ) 17 | 18 | belongs_to(:client, 19 | :class_name => OAuth2::Provider.client_class_name, 20 | :foreign_key => :oauth_client_id 21 | ) 22 | 23 | before_save do 24 | self.client ||= authorization.client 25 | end 26 | end 27 | 28 | module ClassMethods 29 | def find_by_code_and_redirect_uri(code, redirect_uri) 30 | where(:code => code, :redirect_uri => redirect_uri).first 31 | end 32 | 33 | def find_by_id(id) 34 | where(:id => id).first 35 | end 36 | 37 | def find_by_code(code) 38 | where(:code => code).first 39 | end 40 | end 41 | end 42 | 43 | include Behaviour 44 | end -------------------------------------------------------------------------------- /lib/oauth2/provider/models/mongoid/client.rb: -------------------------------------------------------------------------------- 1 | class OAuth2::Provider::Models::Mongoid::Client 2 | module Behaviour 3 | extend ActiveSupport::Concern 4 | 5 | included do 6 | include ::Mongoid::Document 7 | include OAuth2::Provider::Models::Client 8 | 9 | field :name 10 | field :oauth_redirect_uri 11 | field :oauth_secret 12 | field :oauth_identifier 13 | 14 | has_many(:authorizations, 15 | :class_name => OAuth2::Provider.authorization_class_name, 16 | :foreign_key => :oauth_client_id 17 | ) 18 | 19 | has_many(:access_tokens, 20 | :class_name => OAuth2::Provider.access_token_class_name, 21 | :foreign_key => :oauth_client_id 22 | ) 23 | 24 | has_many(:authorization_codes, 25 | :class_name => OAuth2::Provider.authorization_code_class_name, 26 | :foreign_key => :oauth_client_id 27 | ) 28 | end 29 | 30 | module ClassMethods 31 | def find_by_oauth_identifier(identifier) 32 | where(:oauth_identifier => identifier).first 33 | end 34 | 35 | def find_by_oauth_identifier_and_oauth_secret(identifier, secret) 36 | where(:oauth_identifier => identifier, :oauth_secret => secret).first 37 | end 38 | end 39 | end 40 | 41 | include Behaviour 42 | end -------------------------------------------------------------------------------- /lib/oauth2/provider/rack.rb: -------------------------------------------------------------------------------- 1 | module OAuth2::Provider::Rack 2 | autoload :AccessTokenHandler, 'oauth2/provider/rack/access_token_handler' 3 | autoload :AuthenticationHandler, 'oauth2/provider/rack/authentication_handler' 4 | autoload :AuthenticationMediator, 'oauth2/provider/rack/authentication_mediator' 5 | autoload :AuthorizationCodeRequest, 'oauth2/provider/rack/authorization_code_request' 6 | autoload :Middleware, 'oauth2/provider/rack/middleware' 7 | autoload :Request, 'oauth2/provider/rack/request' 8 | autoload :ResourceRequest, 'oauth2/provider/rack/resource_request' 9 | autoload :Responses, 'oauth2/provider/rack/responses' 10 | autoload :AuthorizationCodesSupport, 'oauth2/provider/rack/authorization_codes_support' 11 | 12 | class InvalidRequest < StandardError 13 | end 14 | end -------------------------------------------------------------------------------- /lib/oauth2/provider/rack/access_token_handler.rb: -------------------------------------------------------------------------------- 1 | require 'httpauth' 2 | 3 | module OAuth2::Provider::Rack 4 | class AccessTokenHandler 5 | attr_reader :app, :env, :request 6 | 7 | def initialize(app, env) 8 | @app = app 9 | @env = env 10 | @request = env['oauth2'] 11 | end 12 | 13 | def process 14 | if request.post? 15 | block_unsupported_grant_types || handle_basic_auth_header || block_invalid_clients || handle_grant_type 16 | else 17 | Responses.only_supported 'POST' 18 | end 19 | end 20 | 21 | def handle_basic_auth_header 22 | with_required_params 'grant_type' do |grant_type| 23 | if grant_type == 'client_credentials' && request.env['HTTP_AUTHORIZATION'] =~ /^Basic/ 24 | @env['oauth2'].params['client_id'], @env['oauth2'].params['client_secret'] = HTTPAuth::Basic.unpack_authorization(request.env['HTTP_AUTHORIZATION']) 25 | next 26 | end 27 | end 28 | end 29 | 30 | def handle_grant_type 31 | send grant_type_handler_method(request.params["grant_type"]) 32 | end 33 | 34 | def handle_password_grant_type 35 | with_required_params 'username', 'password' do |username, password| 36 | if resource_owner = OAuth2::Provider.resource_owner_class.authenticate_with_username_and_password(username, password) 37 | token_response OAuth2::Provider.access_token_class.create!( 38 | :authorization => OAuth2::Provider.authorization_class.create!(:resource_owner => resource_owner, :client => oauth_client) 39 | ) 40 | else 41 | Responses.json_error 'invalid_grant' 42 | end 43 | end 44 | end 45 | 46 | def handle_authorization_code_grant_type 47 | with_required_params 'code', 'redirect_uri' do |code, redirect_uri| 48 | if token = oauth_client.authorization_codes.claim(code, redirect_uri) 49 | token_response token 50 | else 51 | Responses.json_error 'invalid_grant' 52 | end 53 | end 54 | end 55 | 56 | def handle_refresh_token_grant_type 57 | with_required_params 'refresh_token' do |refresh_token| 58 | if token = oauth_client.access_tokens.refresh_with(refresh_token) 59 | token_response token 60 | else 61 | Responses.json_error 'invalid_grant' 62 | end 63 | end 64 | end 65 | 66 | def handle_client_credentials_grant_type 67 | token_response OAuth2::Provider.access_token_class.create!( 68 | :authorization => OAuth2::Provider.authorization_class.create!(:resource_owner => oauth_client, :client => oauth_client), 69 | :refresh_token => nil 70 | ) 71 | end 72 | 73 | def with_required_params(*names, &block) 74 | missing_params = names - request.params.keys 75 | if missing_params.empty? 76 | yield *request.params.values_at(*names) 77 | else 78 | if missing_params.size == 1 79 | Responses.json_error 'invalid_request', :description => "missing '#{missing_params.join}' parameter" 80 | else 81 | describe_parameters = missing_params.map{|x| "'#{x}'"}.join(", ") 82 | Responses.json_error 'invalid_request', :description => "missing #{describe_parameters} parameters" 83 | end 84 | end 85 | end 86 | 87 | def token_response(token) 88 | json = token.as_json.tap do |json| 89 | json[:state] = request.params['state'] if request.params['state'] 90 | end 91 | [200, {'Content-Type' => 'application/json', 'Cache-Control' => 'no-cache, no-store, max-age=0, must-revalidate'}, [ActiveSupport::JSON.encode(json)]] 92 | end 93 | 94 | def block_unsupported_grant_types 95 | with_required_params 'grant_type' do |grant_type| 96 | unless respond_to?(grant_type_handler_method(grant_type), true) 97 | Responses.json_error 'unsupported_grant_type' 98 | end 99 | end 100 | end 101 | 102 | def block_invalid_clients 103 | with_required_params 'grant_type', 'client_id', 'client_secret' do |grant_type, client_id, client_secret| 104 | @oauth_client = OAuth2::Provider.client_class.find_by_oauth_identifier_and_oauth_secret(client_id, client_secret) 105 | if @oauth_client.nil? 106 | Responses.json_error 'invalid_client' 107 | elsif !@oauth_client.allow_grant_type?(grant_type) 108 | Responses.json_error 'unauthorized_client' 109 | end 110 | end 111 | end 112 | 113 | def oauth_client 114 | @oauth_client 115 | end 116 | 117 | def grant_type_handler_method(grant_type) 118 | "handle_#{grant_type}_grant_type" 119 | end 120 | end 121 | end 122 | -------------------------------------------------------------------------------- /lib/oauth2/provider/rack/authorization_code_request.rb: -------------------------------------------------------------------------------- 1 | module OAuth2::Provider::Rack 2 | class AuthorizationCodeRequest 3 | def initialize(params) 4 | @params = params 5 | validate! 6 | end 7 | 8 | def grant!(resource_owner = nil, authorization_expires_at = nil) 9 | grant = client.authorizations.create!( 10 | :resource_owner => resource_owner, 11 | :client => client, 12 | :scope => scope, 13 | :expires_at => authorization_expires_at 14 | ) 15 | code = grant.authorization_codes.create! :redirect_uri => redirect_uri 16 | throw_response Responses.redirect_with_code(code.code, redirect_uri) 17 | end 18 | 19 | def grant_existing!(resource_owner = nil) 20 | if existing = OAuth2::Provider.authorization_class.allowing(client, resource_owner, scope).first 21 | code = existing.authorization_codes.create! :redirect_uri => redirect_uri 22 | throw_response Responses.redirect_with_code(code.code, redirect_uri) 23 | end 24 | end 25 | 26 | def deny! 27 | throw_response Responses.redirect_with_error('access_denied', redirect_uri) 28 | end 29 | 30 | def invalid_scope! 31 | throw_response Responses.redirect_with_error('invalid_scope', redirect_uri) 32 | end 33 | 34 | def client_id 35 | @params['client_id'] 36 | end 37 | 38 | def client 39 | @client ||= OAuth2::Provider.client_class.from_param(client_id) 40 | end 41 | 42 | def redirect_uri 43 | @params['redirect_uri'] 44 | end 45 | 46 | def redirect_uri_valid? 47 | client && client.allow_redirection?(redirect_uri) 48 | end 49 | 50 | def scope 51 | @params['scope'] 52 | end 53 | 54 | private 55 | 56 | def validate! 57 | unless client_id 58 | raise OAuth2::Provider::Rack::InvalidRequest, 'No client_id provided' 59 | end 60 | 61 | unless client 62 | raise OAuth2::Provider::Rack::InvalidRequest, 'client_id is invalid' 63 | end 64 | 65 | unless redirect_uri 66 | raise OAuth2::Provider::Rack::InvalidRequest, 'No redirect_uri provided' 67 | end 68 | 69 | unless redirect_uri_valid? 70 | raise OAuth2::Provider::Rack::InvalidRequest, 'Provided redirect_uri is invalid' 71 | end 72 | end 73 | 74 | def throw_response(response) 75 | throw :oauth2, response 76 | end 77 | end 78 | end -------------------------------------------------------------------------------- /lib/oauth2/provider/rack/authorization_codes_support.rb: -------------------------------------------------------------------------------- 1 | module OAuth2::Provider::Rack::AuthorizationCodesSupport 2 | protected 3 | 4 | def oauth2_authorization_request 5 | request.env['oauth2.authorization_request'] ||= OAuth2::Provider::Rack::AuthorizationCodeRequest.new(request.params) 6 | end 7 | 8 | def block_invalid_authorization_code_requests 9 | oauth2_authorization_request 10 | end 11 | 12 | def regrant_existing_authorizations 13 | oauth2_authorization_request.grant_existing! 14 | end 15 | 16 | def grant_authorization_code(resource_owner = nil, authorization_expires_at = nil) 17 | oauth2_authorization_request.grant! resource_owner, authorization_expires_at 18 | end 19 | 20 | def deny_authorization_code 21 | oauth2_authorization_request.deny! 22 | end 23 | 24 | def declare_oauth_scope_invalid 25 | oauth2_authorization_request.invalid_scope! 26 | end 27 | end -------------------------------------------------------------------------------- /lib/oauth2/provider/rack/middleware.rb: -------------------------------------------------------------------------------- 1 | module OAuth2::Provider::Rack 2 | class Middleware 3 | def initialize(app) 4 | @app = app 5 | end 6 | 7 | def call(env) 8 | request = env['oauth2'] = ResourceRequest.new(env) 9 | 10 | response = catch :oauth2 do 11 | if request.path == OAuth2::Provider.access_token_path 12 | handle_access_token_request(env) 13 | else 14 | @app.call(env) 15 | end 16 | end 17 | rescue InvalidRequest => e 18 | [400, {}, [e.message]] 19 | end 20 | 21 | def handle_access_token_request(env) 22 | AccessTokenHandler.new(@app, env).process 23 | end 24 | end 25 | end -------------------------------------------------------------------------------- /lib/oauth2/provider/rack/resource_request.rb: -------------------------------------------------------------------------------- 1 | require 'rack/auth/abstract/request' 2 | 3 | module OAuth2::Provider::Rack 4 | class ResourceRequest < Rack::Request 5 | include Responses 6 | 7 | delegate :has_scope?, :to => :authorization 8 | 9 | def token 10 | token_from_param || token_from_header 11 | end 12 | 13 | def has_token? 14 | !token.nil? 15 | end 16 | 17 | def token_from_param 18 | params["oauth_token"] 19 | end 20 | 21 | def token_from_header 22 | if authorization_header.provided? 23 | authorization_header.params 24 | end 25 | end 26 | 27 | def authorization_header 28 | @authorization_header ||= Rack::Auth::AbstractRequest.new(env) 29 | end 30 | 31 | def authenticate_request!(options, &block) 32 | if authenticated? 33 | if options[:scope].nil? || has_scope?(options[:scope]) 34 | yield 35 | else 36 | insufficient_scope! 37 | end 38 | else 39 | authentication_required! 40 | end 41 | end 42 | 43 | def authorization 44 | validate_token! 45 | @authorization 46 | end 47 | 48 | def authenticated? 49 | authorization.present? 50 | end 51 | 52 | def resource_owner 53 | authorization && authorization.resource_owner 54 | end 55 | 56 | def validate_token! 57 | if has_token? && @token_validated.nil? 58 | @token_validated = true 59 | block_invalid_request 60 | block_invalid_token 61 | end 62 | end 63 | 64 | def block_invalid_request 65 | if token_from_param && token_from_header && (token_from_param != token_from_header) 66 | invalid_request! 'both authorization header and oauth_token provided, with conflicting tokens' 67 | end 68 | end 69 | 70 | def block_invalid_token 71 | access_token = OAuth2::Provider.access_token_class.find_by_access_token(token) 72 | @authorization = access_token.authorization if access_token 73 | authentication_required! 'invalid_token' if access_token.nil? || access_token.expired? 74 | end 75 | end 76 | end -------------------------------------------------------------------------------- /lib/oauth2/provider/rack/responses.rb: -------------------------------------------------------------------------------- 1 | require 'addressable/uri' 2 | 3 | module OAuth2::Provider::Rack::Responses 4 | def self.unauthorized(error = nil) 5 | challenge = "OAuth2" 6 | challenge << %{ error="#{error}"} if error 7 | [401, {'Content-Type' => 'text/plain', 'Content-Length' => '0', 'WWW-Authenticate' => challenge}, []] 8 | end 9 | 10 | def self.only_supported(supported) 11 | [405, {'Allow' => supported}, ["Only #{supported} requests allowed"]] 12 | end 13 | 14 | def self.json_error(error, options = {}) 15 | description = %{, "error_description": "#{options[:description]}"} if options[:description] 16 | [options[:status] || 400, {'Content-Type' => 'application/json'}, [%{{"error": "#{error}"#{description}}}]] 17 | end 18 | 19 | def self.redirect_with_error(error, uri) 20 | [302, {'Location' => append_to_uri(uri, :error => error)}, []] 21 | end 22 | 23 | def self.redirect_with_code(code, uri) 24 | [302, {'Location' => append_to_uri(uri, :code => code)}, []] 25 | end 26 | 27 | def insufficient_scope! 28 | throw_response OAuth2::Provider::Rack::Responses.json_error('insufficient_scope', :status => 403) 29 | end 30 | 31 | def invalid_request!(description) 32 | throw_response OAuth2::Provider::Rack::Responses.json_error('invalid_request', :description => description, :status => 401) 33 | end 34 | 35 | def authentication_required!(reason = nil) 36 | env['warden'] && env['warden'].custom_failure! 37 | throw_response OAuth2::Provider::Rack::Responses.unauthorized(reason) 38 | end 39 | 40 | private 41 | 42 | def self.append_to_uri(uri, parameters = {}) 43 | u = Addressable::URI.parse(uri) 44 | u.query_values = (u.query_values || {}).merge(parameters) 45 | u.to_s 46 | end 47 | 48 | def throw_response(response) 49 | throw :oauth2, response 50 | end 51 | end -------------------------------------------------------------------------------- /lib/oauth2/provider/rails.rb: -------------------------------------------------------------------------------- 1 | require 'oauth2/provider' 2 | 3 | module OAuth2::Provider::Rails 4 | autoload :ControllerAuthentication, 'oauth2/provider/rails/controller_authentication' 5 | 6 | class Railtie < Rails::Railtie 7 | config.oauth2_provider = ActiveSupport::OrderedOptions.new 8 | config.oauth2_provider.activerecord = ActiveSupport::OrderedOptions.new 9 | config.oauth2_provider.mongoid = ActiveSupport::OrderedOptions.new 10 | 11 | initializer "oauth2_provider.config" do |app| 12 | app.config.oauth2_provider.except(:activerecord, :mongoid).each do |k,v| 13 | OAuth2::Provider.send "#{k}=", v 14 | end 15 | 16 | app.config.oauth2_provider.activerecord.each do |k, v| 17 | OAuth2::Provider::Models::ActiveRecord.send "#{k}=", v 18 | end 19 | 20 | app.config.oauth2_provider.mongoid.each do |k, v| 21 | OAuth2::Provider::Models::Mongoid.send "#{k}=", v 22 | end 23 | 24 | OAuth2::Provider.activate 25 | end 26 | 27 | initializer "oauth2_provider.initialize_controller" do |app| 28 | ActionController::Base.module_eval do 29 | include OAuth2::Provider::Rails::ControllerAuthentication 30 | end 31 | end 32 | 33 | initializer "oauth2_provider.initialize_middleware" do |app| 34 | app.middleware.use ::OAuth2::Provider::Rack::Middleware 35 | end 36 | end 37 | end 38 | -------------------------------------------------------------------------------- /lib/oauth2/provider/rails/controller_authentication.rb: -------------------------------------------------------------------------------- 1 | require 'oauth2/provider' 2 | 3 | module OAuth2::Provider::Rails::ControllerAuthentication 4 | extend ActiveSupport::Concern 5 | 6 | module ClassMethods 7 | def authenticate_with_oauth(options = {}) 8 | around_filter AuthenticationFilter.new(options.delete(:scope)), options 9 | end 10 | 11 | class AuthenticationFilter 12 | def initialize(scope = nil) 13 | @scope = scope 14 | end 15 | 16 | def filter(controller, &block) 17 | controller.request.env['oauth2'].authenticate_request! :scope => @scope, &block 18 | end 19 | end 20 | end 21 | end -------------------------------------------------------------------------------- /lib/oauth2/provider/random.rb: -------------------------------------------------------------------------------- 1 | require 'oauth2/provider' 2 | require 'securerandom' 3 | 4 | module OAuth2::Provider::Random 5 | module Base62 6 | CHARS = ('0'..'9').to_a + ('a'..'z').to_a + ('A'..'Z').to_a 7 | 8 | # Adapted from http://refactormycode.com/codes/125-base-62-encoding 9 | def self.encode(i) 10 | return '0' if i == 0 11 | s = '' 12 | while i > 0 13 | s << CHARS[i.modulo(62)] 14 | i /= 62 15 | end 16 | s.reverse! 17 | s 18 | end 19 | end 20 | 21 | def base62(length = 8) 22 | number = SecureRandom.random_number(62 ** length) 23 | Base62.encode(number).rjust(length, '0') 24 | end 25 | 26 | def base36(length = 8) 27 | SecureRandom.random_number(36 ** length).to_s(36).rjust(length, '0') 28 | end 29 | 30 | module_function :base62, :base36 31 | end -------------------------------------------------------------------------------- /lib/oauth2/provider/version.rb: -------------------------------------------------------------------------------- 1 | module OAuth2 2 | module Provider 3 | VERSION = "0.0.19" 4 | end 5 | end 6 | -------------------------------------------------------------------------------- /oauth2-provider.gemspec: -------------------------------------------------------------------------------- 1 | # -*- encoding: utf-8 -*- 2 | $:.push File.expand_path("../lib", __FILE__) 3 | require "oauth2/provider/version" 4 | 5 | Gem::Specification.new do |s| 6 | s.name = "oauth2-provider" 7 | s.version = OAuth2::Provider::VERSION 8 | s.platform = Gem::Platform::RUBY 9 | s.authors = ["Tom Ward"] 10 | s.email = ["tom@popdog.net"] 11 | s.homepage = "http://tomafro.net" 12 | s.summary = %q{OAuth2 Provider, extracted from api.hashblue.com} 13 | s.description = %q{OAuth2 Provider, extracted from api.hashblue.com} 14 | 15 | s.files = `git ls-files`.split("\n") 16 | s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n") 17 | s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) } 18 | s.require_paths = ["lib"] 19 | 20 | # Main dependencies 21 | s.add_dependency 'activesupport', '~>3.0' 22 | s.add_dependency 'addressable', '~>2.2' 23 | s.add_dependency 'httpauth', '~> 0.1' 24 | 25 | s.add_development_dependency 'rack-test', '~>0.5.7' 26 | s.add_development_dependency 'activerecord', '~>3.0' 27 | s.add_development_dependency 'rspec', '~>2.9.0' 28 | s.add_development_dependency 'mocha', '~>0.9.12' 29 | s.add_development_dependency 'rake', '~>0.9.2' 30 | s.add_development_dependency 'sqlite3', '~>1.3.5' 31 | s.add_development_dependency 'timecop', '~>0.3.4' 32 | s.add_development_dependency 'yajl-ruby', '~>0.7.5' 33 | s.add_development_dependency 'mongoid', '2.0.0.rc.6' 34 | s.add_development_dependency 'bson', '1.2.0' 35 | s.add_development_dependency 'bson_ext', '1.2.0' 36 | end 37 | -------------------------------------------------------------------------------- /spec/models/access_token_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | describe OAuth2::Provider.access_token_class do 4 | describe "any instance" do 5 | subject do 6 | OAuth2::Provider.access_token_class.new :authorization => build_authorization 7 | end 8 | 9 | it "is valid with an access grant, expiry time and access token" do 10 | subject.expires_at.should_not be_nil 11 | subject.access_token.should_not be_nil 12 | subject.authorization.should_not be_nil 13 | 14 | subject.should be_valid 15 | end 16 | 17 | it "is invalid without an access token" do 18 | subject.access_token = nil 19 | subject.should_not be_valid 20 | end 21 | 22 | it "is invalid without an access grant" do 23 | subject.authorization = nil 24 | subject.should_not be_valid 25 | end 26 | 27 | it "is invalid if expires_at is later than the authorization's value" do 28 | subject.authorization.expires_at = 1.minute.from_now 29 | subject.expires_at = 10.minutes.from_now 30 | subject.should_not be_valid 31 | end 32 | 33 | it "returns time in seconds until expiry when expires_in called" do 34 | subject.expires_at = 60.minutes.from_now 35 | subject.expires_in.should == (60 * 60) 36 | end 37 | 38 | it "returns 0 for expires_in when already expired" do 39 | subject.expires_at = 60.minutes.ago 40 | subject.expires_in.should == 0 41 | end 42 | 43 | it "returns nil for expires_in when no expiry time is set" do 44 | subject.expires_at = nil 45 | subject.expires_in.should be_nil 46 | end 47 | 48 | it "includes expires_in, refresh_token and access_token as JSON format" do 49 | subject.as_json.should == {"expires_in" => subject.expires_in, "access_token" => subject.access_token, "refresh_token" => subject.refresh_token} 50 | end 51 | 52 | it "excludes refresh_token from JSON format if no refresh token set" do 53 | subject.refresh_token = nil 54 | subject.as_json.should == {"expires_in" => subject.expires_in, "access_token" => subject.access_token} 55 | end 56 | 57 | it "excludes expires_in from JSON format if expiry time set" do 58 | subject.expires_at = nil 59 | subject.as_json.should == {"refresh_token" => subject.refresh_token, "access_token" => subject.access_token} 60 | end 61 | 62 | it "is refreshable, if it has a refresh token" do 63 | subject.refresh_token = 'abcd1234' 64 | subject.should be_refreshable 65 | end 66 | 67 | it "is not refreshable if it has no refresh token" do 68 | subject.refresh_token = nil 69 | subject.should_not be_refreshable 70 | end 71 | 72 | it "is not refreshable if it has a refresh token, but its authorization has expired" do 73 | subject.refresh_token = 'abcd1234' 74 | subject.authorization.expires_at = 60.minutes.ago 75 | subject.should_not be_refreshable 76 | end 77 | end 78 | 79 | describe "a new instance" do 80 | subject do 81 | OAuth2::Provider.access_token_class.new 82 | end 83 | 84 | it "uses .unique_random_token to assign random access and refresh tokens" do 85 | OAuth2::Provider.access_token_class.stubs(:unique_random_token).with(:access_token).returns('random-access-token') 86 | OAuth2::Provider.access_token_class.stubs(:unique_random_token).with(:refresh_token).returns('random-refresh-token') 87 | subject.access_token.should eql('random-access-token') 88 | subject.refresh_token.should eql('random-refresh-token') 89 | end 90 | 91 | it "expires in 1 month by default" do 92 | subject.expires_at.should == 1.month.from_now 93 | end 94 | 95 | it "allows default expiry time to be overidden" do 96 | overidden = OAuth2::Provider.access_token_class.new(:expires_at => nil) 97 | overidden.expires_at.should be_nil 98 | end 99 | end 100 | 101 | describe "refreshing an existing token" do 102 | subject do 103 | OAuth2::Provider.access_token_class.create! :authorization => create_authorization, :expires_at => 23.days.ago 104 | end 105 | 106 | it "returns a new access token with the same client, resource_owner and scope, but a new expiry time" do 107 | result = OAuth2::Provider.access_token_class.refresh_with(subject.refresh_token) 108 | result.should_not be_nil 109 | result.expires_at.should == 1.month.from_now 110 | result.authorization.should == subject.authorization 111 | end 112 | 113 | it "returns token with expires_at set to authorization.expires_at if validation would fail otherwise" do 114 | subject.authorization.update_attributes(:expires_at => 5.minutes.from_now) 115 | result = OAuth2::Provider.access_token_class.refresh_with(subject.refresh_token) 116 | result.expires_at.should == 5.minutes.from_now 117 | end 118 | 119 | it "returns nil if the provided token doesn't match" do 120 | OAuth2::Provider.access_token_class.refresh_with('wrong').should be_nil 121 | end 122 | 123 | it "returns nil if the existing refresh token is nil, whatever value is provided" do 124 | subject.update_attributes(:refresh_token => nil) 125 | OAuth2::Provider.access_token_class.refresh_with(nil).should be_nil 126 | end 127 | end 128 | end -------------------------------------------------------------------------------- /spec/models/authorization_code_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | describe OAuth2::Provider.authorization_code_class do 4 | describe "any instance" do 5 | subject do 6 | OAuth2::Provider.authorization_code_class.new( 7 | :authorization => create_authorization, 8 | :redirect_uri => "http://redirect.example.com/callback" 9 | ) 10 | end 11 | 12 | it "is valid with an access grant, expiry time, redirect uri and code" do 13 | subject.should be_valid 14 | end 15 | 16 | it "is invalid without a redirect_uri" do 17 | subject.redirect_uri = nil 18 | subject.should_not be_valid 19 | end 20 | 21 | it "is invalid without a code" do 22 | subject.code = nil 23 | subject.should_not be_valid 24 | end 25 | 26 | it "is invalid without an access grant" do 27 | subject.authorization = nil 28 | subject.should_not be_valid 29 | end 30 | 31 | it "is invalid when expires_at isn't set" do 32 | subject.expires_at = nil 33 | subject.should_not be_valid 34 | end 35 | 36 | it "has expired when expires_at is in the past" do 37 | subject.expires_at = 1.second.ago 38 | subject.should be_expired 39 | end 40 | 41 | it "has not expired when expires_at is now or in the future" do 42 | subject.expires_at = Time.now 43 | subject.should_not be_expired 44 | end 45 | end 46 | 47 | describe "a new instance" do 48 | subject do 49 | OAuth2::Provider.authorization_code_class.new 50 | end 51 | 52 | it "uses .unique_random_token to assign a code" do 53 | OAuth2::Provider.authorization_code_class.stubs(:unique_random_token).with(:code).returns('random-token') 54 | OAuth2::Provider.authorization_code_class.new.code.should eql('random-token') 55 | end 56 | 57 | it "expires in 1 minute by default" do 58 | subject.expires_at.should == 1.minute.from_now 59 | end 60 | end 61 | 62 | describe "a saved instance" do 63 | subject do 64 | OAuth2::Provider.authorization_code_class.create!( 65 | :authorization => create_authorization, 66 | :redirect_uri => "https://client.example.com/callback/here" 67 | ) 68 | end 69 | 70 | it "can be claimed with the correct code and redirect_uri" do 71 | OAuth2::Provider.authorization_code_class.claim(subject.code, subject.redirect_uri).should_not be_nil 72 | end 73 | 74 | it "returns an access token when claimed" do 75 | OAuth2::Provider.authorization_code_class.claim(subject.code, subject.redirect_uri).should be_instance_of(OAuth2::Provider.access_token_class) 76 | end 77 | 78 | it "can't be claimed twice" do 79 | OAuth2::Provider.authorization_code_class.claim(subject.code, subject.redirect_uri) 80 | OAuth2::Provider.authorization_code_class.claim(subject.code, subject.redirect_uri).should be_nil 81 | end 82 | 83 | it "can't be claimed without a matching code" do 84 | OAuth2::Provider.authorization_code_class.claim("incorrectCode", subject.redirect_uri).should be_nil 85 | end 86 | 87 | it "can't be claimed without a matching redirect_uri" do 88 | OAuth2::Provider.authorization_code_class.claim(subject.code, "https://wrong.example.com").should be_nil 89 | end 90 | 91 | it "can't be claimed once expired" do 92 | Timecop.travel subject.expires_at + 1.minute 93 | OAuth2::Provider.authorization_code_class.claim(subject.code, subject.redirect_uri).should be_nil 94 | end 95 | end 96 | 97 | describe "the access token returned when a code is claimed" do 98 | subject do 99 | @code = OAuth2::Provider.authorization_code_class.create!( 100 | :authorization => create_authorization, 101 | :redirect_uri => "https://client.example.com/callback/here" 102 | ) 103 | OAuth2::Provider.authorization_code_class.claim(@code.code, @code.redirect_uri) 104 | end 105 | 106 | it "is saved to the database" do 107 | subject.should_not be_new_record 108 | end 109 | 110 | it "has same access grant as claimed code" do 111 | subject.authorization.should == @code.authorization 112 | end 113 | end 114 | end -------------------------------------------------------------------------------- /spec/models/authorization_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | describe OAuth2::Provider.authorization_class do 4 | describe "any instance" do 5 | subject do 6 | result = OAuth2::Provider.authorization_class.new :client => create_client 7 | end 8 | 9 | it "is valid with a client" do 10 | subject.client.should_not be_nil 11 | subject.should be_valid 12 | end 13 | 14 | it "is invalid without a client" do 15 | subject.client = nil 16 | subject.should_not be_valid 17 | end 18 | 19 | it "has a given scope, if scope string includes scope" do 20 | subject.scope = "first second third" 21 | subject.should have_scope("first") 22 | subject.should have_scope("second") 23 | subject.should have_scope("third") 24 | end 25 | 26 | it "doesn't have a given scope, if scope string doesn't scope" do 27 | subject.scope = "first second third" 28 | subject.should_not have_scope("fourth") 29 | end 30 | end 31 | 32 | describe "a new instance" do 33 | subject do 34 | OAuth2::Provider.authorization_class.new 35 | end 36 | 37 | it "has no expiry time by default" do 38 | subject.expires_at.should be_nil 39 | end 40 | end 41 | 42 | describe "after being persisted and restored" do 43 | before :each do 44 | @client = create_client 45 | @owner = create_resource_owner 46 | @original = OAuth2::Provider.authorization_class.create!(:client => @client, :resource_owner => @owner, :expires_at => 1.year.from_now) 47 | end 48 | 49 | subject do 50 | OAuth2::Provider.authorization_class.find(@original.id) 51 | end 52 | 53 | it "remembers client" do 54 | subject.client.should eql(@client) 55 | end 56 | 57 | it "remembers resource owner" do 58 | subject.resource_owner.should eql(@owner) 59 | end 60 | end 61 | 62 | describe "obtain all authorizations for a resource owner" do 63 | before :each do 64 | @client = create_client 65 | @owner = create_resource_owner 66 | @authorization = OAuth2::Provider.authorization_class.create!(:client => @client, :resource_owner => @owner, :expires_at => 1.year.from_now) 67 | end 68 | 69 | subject do 70 | OAuth2::Provider.authorization_class.all_for(@owner) 71 | end 72 | 73 | it "returns correct number of authorizations" do 74 | subject.count.should eql(1) 75 | end 76 | 77 | it "should hold information on the authorized client" do 78 | subject[0].client.should eql(@client) 79 | end 80 | end 81 | 82 | describe ".allowing(client, owner, scope)" do 83 | before :each do 84 | @client = create_client 85 | @owner = create_resource_owner 86 | @scope = "any-scope" 87 | @authorization = OAuth2::Provider.authorization_class.create!( 88 | :client => @client, 89 | :resource_owner => @owner, 90 | :scope => @scope, 91 | :expires_at => 1.hour.from_now 92 | ) 93 | end 94 | 95 | it "returns existing authorizations for the given client, owner and scope" do 96 | results = OAuth2::Provider.authorization_class.allowing(@client, @owner, @scope).to_a 97 | results.should eql([@authorization]) 98 | end 99 | 100 | it "doesn't return authorizations where client is different" do 101 | results = OAuth2::Provider.authorization_class.allowing(create_client, @owner, @scope).to_a 102 | results.should eql([]) 103 | end 104 | 105 | it "doesn't return authorizations where owner is different" do 106 | results = OAuth2::Provider.authorization_class.allowing(@client, create_resource_owner, @scope).to_a 107 | results.should eql([]) 108 | end 109 | 110 | it "doesn't return authorizations where scope is different" do 111 | results = OAuth2::Provider.authorization_class.allowing(@client, @owner, 'another-scope').to_a 112 | results.should eql([]) 113 | end 114 | 115 | it "doesn't return expired authorizations" do 116 | Timecop.travel 4.hours.from_now 117 | results = OAuth2::Provider.authorization_class.allowing(@client, @owner, @scope).to_a 118 | results.should eql([]) 119 | end 120 | end 121 | 122 | describe "#revoke" do 123 | subject do 124 | OAuth2::Provider.authorization_class.create! :client => create_client 125 | end 126 | 127 | it "destroys itself" do 128 | subject.revoke 129 | subject.should be_destroyed 130 | end 131 | 132 | it "destroys any related authorization codes" do 133 | subject.authorization_codes.create! :redirect_uri => 'https://example.com' 134 | subject.revoke 135 | subject.authorization_codes.should be_empty 136 | end 137 | 138 | it "destroys any related access tokens" do 139 | subject.access_tokens.create! 140 | subject.revoke 141 | subject.access_tokens.should be_empty 142 | end 143 | end 144 | end -------------------------------------------------------------------------------- /spec/models/client_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | describe OAuth2::Provider.client_class do 4 | describe "any instance" do 5 | subject do 6 | OAuth2::Provider.client_class.new :name => 'client' 7 | end 8 | 9 | it "is valid with a name, oauth identifier and oauth secret" do 10 | subject.should be_valid 11 | end 12 | 13 | it "is invalid without a name" do 14 | subject.name = nil 15 | subject.should_not be_valid 16 | end 17 | 18 | it "is invalid without an oauth identifier" do 19 | subject.oauth_identifier = nil 20 | subject.should_not be_valid 21 | end 22 | 23 | it "is invalid without an oauth secret" do 24 | subject.oauth_secret = nil 25 | subject.should_not be_valid 26 | end 27 | 28 | it "is invalid if oauth_identifier not unique" do 29 | duplicate = OAuth2::Provider.client_class.create! :name => 'client2' 30 | subject.oauth_identifier = duplicate.oauth_identifier 31 | subject.should_not be_valid 32 | end 33 | 34 | it "allows any grant type (custom subclasses can override this)" do 35 | subject.allow_grant_type?('password').should be_true 36 | subject.allow_grant_type?('authorization_code').should be_true 37 | subject.allow_grant_type?('client_credentials').should be_true 38 | end 39 | end 40 | 41 | describe "a new instance" do 42 | subject do 43 | OAuth2::Provider.client_class.new :name => 'client' 44 | end 45 | 46 | it "uses .unique_random_token to assign random oauth identifier and secret" do 47 | OAuth2::Provider.client_class.stubs(:unique_random_token).with(:oauth_identifier).returns('random-identifier') 48 | OAuth2::Provider.client_class.stubs(:unique_random_token).with(:oauth_secret).returns('random-secret') 49 | subject.oauth_identifier.should eql('random-identifier') 50 | subject.oauth_secret.should eql('random-secret') 51 | end 52 | 53 | it "returns nil when to_param called" do 54 | subject.to_param.should be_nil 55 | end 56 | end 57 | 58 | describe "a saved instance" do 59 | subject do 60 | OAuth2::Provider.client_class.create! :name => 'client' 61 | end 62 | 63 | it "returns oauth_identifer when to_param called" do 64 | subject.to_param.should == subject.oauth_identifier 65 | end 66 | 67 | it "is findable by calling from_param with its oauth_identifier" do 68 | subject.should == OAuth2::Provider.client_class.from_param(subject.oauth_identifier) 69 | end 70 | end 71 | 72 | describe "#allow_redirection?(uri)" do 73 | describe "on a client with an oauth_redirect_uri" do 74 | subject do 75 | OAuth2::Provider.client_class.new :name => 'client', :oauth_redirect_uri => "http://valid.example.com/any/path" 76 | end 77 | 78 | it "returns true if hosts match" do 79 | subject.allow_redirection?("http://valid.example.com/another/path").should be_true 80 | end 81 | 82 | it "returns false if hosts are different match" do 83 | subject.allow_redirection?("http://invalid.example.com/another/path").should be_false 84 | end 85 | 86 | it "returns false if the provided uri isn't a valid uri" do 87 | subject.allow_redirection?("a-load-of-rubbish").should be_false 88 | end 89 | end 90 | 91 | describe "on a client with an empty oauth_redirect_uri" do 92 | subject do 93 | OAuth2::Provider.client_class.new :name => 'client', :oauth_redirect_uri => "" 94 | end 95 | 96 | it "always returns true" do 97 | subject.allow_redirection?("http://anything.example.com/any/path").should be_true 98 | end 99 | 100 | it "returns false if the provided uri isn't a valid uri" do 101 | subject.allow_redirection?("a-load-of-rubbish").should be_false 102 | end 103 | end 104 | 105 | describe "on a client without an oauth_redirect_uri" do 106 | subject do 107 | OAuth2::Provider.client_class.new :name => 'client' 108 | end 109 | 110 | it "always returns true" do 111 | subject.allow_redirection?("http://anything.example.com/any/path").should be_true 112 | end 113 | 114 | it "returns false if the provided uri isn't a valid uri" do 115 | subject.allow_redirection?("a-load-of-rubbish").should be_false 116 | end 117 | end 118 | end 119 | end 120 | -------------------------------------------------------------------------------- /spec/models/random_token_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | describe OAuth2::Provider::Models::RandomToken do 4 | describe ".unique_random_token(attribute)" do 5 | let :model do 6 | OAuth2::Provider.client_class 7 | end 8 | 9 | it "uses .random_token to generate a random token" do 10 | model.stubs(:random_token).returns('random-token') 11 | model.unique_random_token(:oauth_identifier).should eql('random-token') 12 | end 13 | 14 | it "calls .random_token repeatedly until unused token found" do 15 | m1 = model.create! :name => 'anything' 16 | m2 = model.create! :name => 'ignore' 17 | model.stubs(:random_token).returns(m1.oauth_identifier).then.returns(m2.oauth_identifier).then.returns('3rd-random-token') 18 | model.unique_random_token(:oauth_identifier).should eql('3rd-random-token') 19 | end 20 | 21 | it "only regards tokens used for same attribute as used" do 22 | m1 = model.create! :name => 'anything' 23 | model.stubs(:random_token).returns(m1.oauth_identifier).then.returns('2nd-random-token') 24 | model.unique_random_token(:oauth_secret).should eql(m1.oauth_identifier) 25 | end 26 | end 27 | end -------------------------------------------------------------------------------- /spec/requests/access_tokens_controller_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | class CustomClient < OAuth2::Provider.client_class 4 | end 5 | 6 | class NotAllowedGrantTypeClient < OAuth2::Provider.client_class 7 | def allow_grant_type?(grant_type) 8 | false 9 | end 10 | end 11 | 12 | describe "POSTs to /oauth/access_token" do 13 | before :each do 14 | @code = create_authorization_code 15 | @client = @code.authorization.client 16 | @valid_params = { 17 | :grant_type => 'authorization_code', 18 | :client_id => @code.authorization.client.oauth_identifier, 19 | :client_secret => @code.authorization.client.oauth_secret, 20 | :code => @code.code, 21 | :redirect_uri => @code.redirect_uri 22 | } 23 | end 24 | 25 | describe "Any request without a client_id parameter" do 26 | before :each do 27 | post "/oauth/access_token", @valid_params.except(:client_id) 28 | end 29 | 30 | responds_with_json_error 'invalid_request', :description => "missing 'client_id' parameter", :status => 400 31 | end 32 | 33 | describe "Any request without a client_secret parameter" do 34 | before :each do 35 | post "/oauth/access_token", @valid_params.except(:client_secret) 36 | end 37 | 38 | responds_with_json_error 'invalid_request', :description => "missing 'client_secret' parameter", :status => 400 39 | end 40 | 41 | describe "Any request without a grant_type parameter" do 42 | before :each do 43 | post "/oauth/access_token", @valid_params.except(:grant_type) 44 | end 45 | 46 | responds_with_json_error 'invalid_request', :description => "missing 'grant_type' parameter", :status => 400 47 | end 48 | 49 | describe "Any request without several required parameters" do 50 | before :each do 51 | post "/oauth/access_token", @valid_params.except(:client_id, :client_secret) 52 | end 53 | 54 | responds_with_json_error 'invalid_request', :description => "missing 'client_id', 'client_secret' parameters", :status => 400 55 | end 56 | 57 | describe "Any request with an unsupported grant_type" do 58 | before :each do 59 | post "/oauth/access_token", @valid_params.merge(:grant_type => 'unsupported') 60 | end 61 | 62 | responds_with_json_error 'unsupported_grant_type', :status => 400 63 | end 64 | 65 | describe "Any request where the client_id is unknown" do 66 | before :each do 67 | post "/oauth/access_token", @valid_params.merge(:client_id => 'unknown') 68 | end 69 | 70 | responds_with_json_error 'invalid_client', :status => 400 71 | end 72 | 73 | describe "Any request where the client_secret is wrong" do 74 | before :each do 75 | post "/oauth/access_token", @valid_params.merge(:client_secret => 'wrongvalue') 76 | end 77 | 78 | responds_with_json_error 'invalid_client', :status => 400 79 | end 80 | 81 | describe "Any request which doesn't use POST" do 82 | before :each do 83 | get "/oauth/access_token", @valid_params 84 | end 85 | 86 | it "responds with Method Not Allowed (405), and POST in the Allow header" do 87 | response.status.should == 405 88 | response.headers["Allow"].should == "POST" 89 | end 90 | end 91 | 92 | describe "Any request where the client isn't allowed to use the requested grant type" do 93 | before :each do 94 | @original_client_class_name = OAuth2::Provider.client_class_name 95 | OAuth2::Provider.client_class_name = NotAllowedGrantTypeClient.name 96 | @client = NotAllowedGrantTypeClient.create! :name => 'client' 97 | @code = create_authorization_code(:authorization => create_authorization(:client => @client)) 98 | @valid_params = { 99 | :grant_type => 'authorization_code', 100 | :client_id => @code.authorization.client.oauth_identifier, 101 | :client_secret => @code.authorization.client.oauth_secret, 102 | :code => @code.code, 103 | :redirect_uri => @code.redirect_uri 104 | } 105 | post "/oauth/access_token", @valid_params 106 | end 107 | 108 | after :each do 109 | OAuth2::Provider.client_class_name = @original_client_class_name 110 | end 111 | 112 | responds_with_json_error 'unauthorized_client', :status => 400 113 | end 114 | 115 | describe "A request using the authorization_code grant type" do 116 | describe "with valid client, code and redirect_uri" do 117 | before :each do 118 | post "/oauth/access_token", @valid_params 119 | end 120 | 121 | it "responds with claimed access token, refresh token and expiry time in JSON" do 122 | token = OAuth2::Provider.access_token_class.find_by_access_token(json_from_response["access_token"]) 123 | token.should_not be_nil 124 | json_from_response["expires_in"].should == token.expires_in 125 | json_from_response["refresh_token"].should == token.refresh_token 126 | end 127 | 128 | it "sets cache-control header to no-store, as response is sensitive" do 129 | response.headers["Cache-Control"].should =~ /no-store/ 130 | end 131 | 132 | it "destroys the claimed code, so it can't be used a second time" do 133 | OAuth2::Provider.authorization_code_class.find_by_id(@code.id).should be_nil 134 | end 135 | 136 | it "doesn't include a state in the JSON response" do 137 | json_from_response.keys.include?("state").should be_false 138 | end 139 | end 140 | 141 | describe "with valid client, code and redirect_uri and an additional state parameter" do 142 | before :each do 143 | post "/oauth/access_token", @valid_params.merge(:state => 'some-state-goes-here') 144 | end 145 | 146 | it "includes the state in the JSON response" do 147 | json_from_response["state"].should == 'some-state-goes-here' 148 | end 149 | end 150 | 151 | describe "with an unknown code" do 152 | before :each do 153 | post "/oauth/access_token", @valid_params.merge(:code => 'unknown') 154 | end 155 | 156 | responds_with_json_error 'invalid_grant', :status => 400 157 | end 158 | 159 | describe "with an incorrect redirect uri" do 160 | before :each do 161 | post "/oauth/access_token", @valid_params.merge(:redirect_uri => 'https://wrong.example.com') 162 | end 163 | 164 | responds_with_json_error 'invalid_grant', :status => 400 165 | end 166 | 167 | describe "without a code parameter" do 168 | before :each do 169 | post "/oauth/access_token", @valid_params.except(:code) 170 | end 171 | 172 | responds_with_json_error 'invalid_request', :status => 400 173 | end 174 | 175 | describe "without a redirect_uri parameter" do 176 | before :each do 177 | post "/oauth/access_token", @valid_params.except(:redirect_uri) 178 | end 179 | 180 | responds_with_json_error 'invalid_request', :status => 400 181 | end 182 | end 183 | 184 | describe "A request using the password grant type" do 185 | before :each do 186 | @resource_owner = ExampleResourceOwner.create!(:username => 'name', :password => 'password') 187 | @valid_params = { 188 | :grant_type => 'password', 189 | :client_id => @client.to_param, 190 | :client_secret => @client.oauth_secret, 191 | :username => @resource_owner.username, 192 | :password => @resource_owner.password 193 | } 194 | end 195 | 196 | describe "with valid username and password" do 197 | before :each do 198 | post "/oauth/access_token", @valid_params 199 | end 200 | 201 | it "responds with access token, refresh token and expiry time in JSON" do 202 | token = OAuth2::Provider.access_token_class.find_by_access_token(json_from_response["access_token"]) 203 | token.should_not be_nil 204 | json_from_response["expires_in"].should == token.expires_in 205 | json_from_response["refresh_token"].should == token.refresh_token 206 | end 207 | 208 | it "sets cache-control header to no-store, as response is sensitive" do 209 | response.headers["Cache-Control"].should =~ /no-store/ 210 | end 211 | 212 | it "doesn't include a state in the JSON response" do 213 | json_from_response.keys.include?("state").should be_false 214 | end 215 | end 216 | 217 | describe "with valid username and password and an additional state parameter" do 218 | before :each do 219 | post "/oauth/access_token", @valid_params.merge(:state => 'some-state-goes-here') 220 | end 221 | 222 | it "includes the state in the JSON response" do 223 | json_from_response["state"].should == 'some-state-goes-here' 224 | end 225 | end 226 | 227 | describe "with an incorrect username" do 228 | before :each do 229 | post "/oauth/access_token", @valid_params.merge(:username => 'wrong') 230 | end 231 | 232 | responds_with_json_error 'invalid_grant', :status => 400 233 | end 234 | 235 | describe "with an incorrect password" do 236 | before :each do 237 | post "/oauth/access_token", @valid_params.merge(:password => 'wrong') 238 | end 239 | 240 | responds_with_json_error 'invalid_grant', :status => 400 241 | end 242 | 243 | describe "without a username parameter" do 244 | before :each do 245 | post "/oauth/access_token", @valid_params.except(:username) 246 | end 247 | 248 | responds_with_json_error 'invalid_request', :status => 400 249 | end 250 | 251 | describe "without a password parameter" do 252 | before :each do 253 | post "/oauth/access_token", @valid_params.except(:password) 254 | end 255 | 256 | responds_with_json_error 'invalid_request', :status => 400 257 | end 258 | end 259 | 260 | describe "A request using the refresh token grant type" do 261 | before :each do 262 | @token = create_access_token 263 | 264 | @client = @token.authorization.client 265 | @valid_params = { 266 | :grant_type => 'refresh_token', 267 | :refresh_token => @token.refresh_token, 268 | :client_id => @client.oauth_identifier, 269 | :client_secret => @client.oauth_secret 270 | } 271 | end 272 | 273 | describe "with a valid refresh token" do 274 | before :each do 275 | post "/oauth/access_token", @valid_params 276 | end 277 | 278 | it "responds with refreshed access token, refresh token and expiry time in JSON" do 279 | token = OAuth2::Provider.access_token_class.find_by_access_token(json_from_response["access_token"]) 280 | token.should_not be_nil 281 | token.should_not == @token 282 | json_from_response["expires_in"].should == token.expires_in 283 | json_from_response["refresh_token"].should == token.refresh_token 284 | end 285 | end 286 | 287 | describe "when the token belongs to a different client" do 288 | before :each do 289 | @other_client = OAuth2::Provider.client_class.create! :name => 'client' 290 | post "/oauth/access_token", @valid_params.merge(:client_id => @other_client.oauth_identifier, :client_secret => @other_client.oauth_secret) 291 | end 292 | 293 | responds_with_json_error 'invalid_grant', :status => 400 294 | end 295 | 296 | describe "when the token is incorrect" do 297 | before :each do 298 | post "/oauth/access_token", @valid_params.merge(:refresh_token => 'incorrect') 299 | end 300 | 301 | responds_with_json_error 'invalid_grant', :status => 400 302 | end 303 | 304 | describe "without a refresh_token parameter" do 305 | before :each do 306 | post "/oauth/access_token", @valid_params.except(:refresh_token) 307 | end 308 | 309 | responds_with_json_error 'invalid_request', :status => 400 310 | end 311 | end 312 | 313 | shared_examples_for 'client_credentials grant type' do 314 | describe "with valid client_id and client_secret" do 315 | before :each do 316 | post "/oauth/access_token", @valid_params, @valid_headers 317 | end 318 | 319 | it "responds with access token, and expiry time in JSON" do 320 | token = OAuth2::Provider.access_token_class.find_by_access_token(json_from_response["access_token"]) 321 | token.should_not be_nil 322 | json_from_response["expires_in"].should == token.expires_in 323 | end 324 | 325 | it "sets cache-control header to no-store, as response is sensitive" do 326 | response.headers["Cache-Control"].should =~ /no-store/ 327 | end 328 | 329 | it "doesn't include a refresh_token in the JSON response" do 330 | json_from_response.keys.include?("refresh_token").should be_false 331 | end 332 | 333 | it "doesn't include a state in the JSON response" do 334 | json_from_response.keys.include?("state").should be_false 335 | end 336 | end 337 | end 338 | 339 | describe "A request using the client_credentials grant type with client_credentials encoded in 'Authorization' header" do 340 | before :each do 341 | @valid_params = { 342 | :grant_type => 'client_credentials' 343 | } 344 | @valid_headers = { 345 | 'HTTP_AUTHORIZATION' => HTTPAuth::Basic.pack_authorization(@client.to_param, @client.oauth_secret) 346 | } 347 | end 348 | it_behaves_like 'client_credentials grant type' 349 | end 350 | 351 | describe "A request using the client_credentials grant type with client_credentials encoded in response body" do 352 | before :each do 353 | @valid_params = { 354 | :grant_type => 'client_credentials', 355 | :client_id => @client.to_param, 356 | :client_secret => @client.oauth_secret 357 | } 358 | @valid_headers = {} 359 | end 360 | it_behaves_like 'client_credentials grant type' 361 | end 362 | 363 | describe "When using a custom client class" do 364 | before :each do 365 | @original_client_class_name = OAuth2::Provider.client_class_name 366 | OAuth2::Provider.client_class_name = "CustomClient" 367 | @client = CustomClient.create! :name => 'client' 368 | @client_params = { 369 | :client_id => @client.to_param, 370 | :client_secret => @client.oauth_secret, 371 | } 372 | end 373 | 374 | after :each do 375 | OAuth2::Provider.client_class_name = @original_client_class_name 376 | end 377 | 378 | describe "requests using authorization code grant type" do 379 | before :each do 380 | @code = create_authorization_code(:authorization => create_authorization(:client => @client)) 381 | @valid_params = @client_params.merge( 382 | :grant_type => 'authorization_code', 383 | :code => @code.code, 384 | :redirect_uri => @code.redirect_uri 385 | ) 386 | post "/oauth/access_token", @valid_params 387 | end 388 | 389 | it "are still successful" do 390 | response.should be_successful 391 | end 392 | end 393 | 394 | describe "requests using password grant type" do 395 | before :each do 396 | @resource_owner = ExampleResourceOwner.create!(:username => 'name', :password => 'password') 397 | @valid_params = @client_params.merge( 398 | :grant_type => 'password', 399 | :username => @resource_owner.username, 400 | :password => @resource_owner.password 401 | ) 402 | post "/oauth/access_token", @valid_params 403 | end 404 | 405 | it "are still successful" do 406 | response.should be_successful 407 | end 408 | end 409 | end 410 | end 411 | -------------------------------------------------------------------------------- /spec/requests/authentication_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | describe "A request for a protected resource" do 4 | action do |env| 5 | env['oauth2'].authenticate_request!(:scope => nil) do 6 | successful_response 7 | end 8 | end 9 | 10 | before :each do 11 | @token = create_access_token( 12 | :authorization => create_authorization( 13 | :scope => "protected write" 14 | ) 15 | ) 16 | end 17 | 18 | describe "with no token passed" do 19 | before :each do 20 | get "/protected" 21 | end 22 | 23 | responds_with_status 401 24 | responds_with_header 'WWW-Authenticate', 'OAuth2' 25 | end 26 | 27 | describe "with a token passed as an oauth_token parameter" do 28 | before :each do 29 | get "/protected", :oauth_token => @token.access_token 30 | end 31 | 32 | it "is successful" do 33 | response.should be_successful 34 | end 35 | 36 | it "makes the access token available to the requested action" do 37 | response.body.should == "Success" 38 | end 39 | end 40 | 41 | describe "with a token passed in an Authorization header" do 42 | before :each do 43 | get "/protected", {}, {"HTTP_AUTHORIZATION" => "OAuth #{@token.access_token}"} 44 | end 45 | 46 | it "is successful" do 47 | response.should be_successful 48 | end 49 | 50 | it "makes the access token available to the requested action" do 51 | response.body.should == "Success" 52 | end 53 | end 54 | 55 | describe "with same token passed in both the Authorization header and oauth_token parameter" do 56 | before :each do 57 | get "/protected", {:oauth_token => @token.access_token}, {"HTTP_AUTHORIZATION" => "OAuth #{@token.access_token}"} 58 | end 59 | 60 | it "is successful" do 61 | response.should be_successful 62 | end 63 | 64 | it "makes the access token available to the requested action" do 65 | response.body.should == "Success" 66 | end 67 | end 68 | 69 | describe "with different tokens passed in both the Authorization header and oauth_token parameter" do 70 | before :each do 71 | get "/protected", {:oauth_token => @token.access_token}, {"HTTP_AUTHORIZATION" => "OAuth DifferentToken"} 72 | end 73 | 74 | responds_with_json_error 'invalid_request', :description => 'both authorization header and oauth_token provided, with conflicting tokens', :status => 401 75 | end 76 | 77 | describe "with an invalid token" do 78 | before :each do 79 | get "/protected", :oauth_token => 'invalid-token' 80 | end 81 | 82 | responds_with_status 401 83 | responds_with_header 'WWW-Authenticate', 'OAuth2 error="invalid_token"' 84 | end 85 | 86 | describe "with an expired token that can be refreshed" do 87 | before :each do 88 | @token.update_attributes(:expires_at => 1.day.ago) 89 | get "/protected", :oauth_token => @token.access_token 90 | end 91 | 92 | responds_with_status 401 93 | responds_with_header 'WWW-Authenticate', 'OAuth2 error="invalid_token"' 94 | end 95 | 96 | describe "with an expired token that can't be refreshed" do 97 | before :each do 98 | @token.update_attributes(:expires_at => 1.day.ago, :refresh_token => nil) 99 | get "/protected", :oauth_token => @token.access_token 100 | end 101 | 102 | responds_with_status 401 103 | responds_with_header 'WWW-Authenticate', 'OAuth2 error="invalid_token"' 104 | end 105 | 106 | describe "when warden is part of the stack" do 107 | it "bypasses warden when no token is passed" do 108 | warden = "warden" 109 | warden.expects(:custom_failure!) 110 | get "/protected", {}, {'warden' => warden} 111 | end 112 | 113 | it "bypasses warden when token invalid" do 114 | warden = "warden" 115 | warden.expects(:custom_failure!) 116 | get "/protected", {:oauth_token => 'invalid_token'}, {'warden' => warden} 117 | end 118 | end 119 | end 120 | 121 | describe "A request for a protected resource requiring a specific scope" do 122 | action do |env| 123 | env['oauth2'].authenticate_request!(:scope => 'omnipotent') do 124 | successful_response 125 | end 126 | end 127 | 128 | before :each do 129 | @token = create_access_token(:authorization => create_authorization(:scope => "omnipotent admin")) 130 | @insufficient_token = create_access_token(:authorization => create_authorization(:scope => "impotent admin")) 131 | end 132 | 133 | describe "made with a token with sufficient scope" do 134 | before :each do 135 | get '/protected_by_scope', :oauth_token => @token.access_token 136 | end 137 | 138 | it "is successful" do 139 | response.should be_successful 140 | end 141 | end 142 | 143 | describe "made with a token with insufficient scope" do 144 | before :each do 145 | get '/protected_by_scope', :oauth_token => @insufficient_token.access_token 146 | end 147 | 148 | responds_with_json_error 'insufficient_scope', :status => 403 149 | end 150 | end 151 | -------------------------------------------------------------------------------- /spec/requests/authorization_code_request_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | describe OAuth2::Provider::Rack::AuthorizationCodeRequest do 4 | describe "#initialize" do 5 | before :each do 6 | @client = OAuth2::Provider.client_class.create! :name => 'client' 7 | @valid_params = { 8 | 'client_id' => @client.oauth_identifier, 9 | 'redirect_uri' => "https://redirect.example.com/callback" 10 | } 11 | end 12 | 13 | describe "with a valid client_id and redirect_uri" do 14 | it "doesn't raise any exception" do 15 | lambda { 16 | OAuth2::Provider::Rack::AuthorizationCodeRequest.new(@valid_params) 17 | }.should_not raise_error 18 | end 19 | end 20 | 21 | describe "without a client_id" do 22 | it "raises OAuth2::Provider::Rack::InvalidRequest" do 23 | lambda { 24 | OAuth2::Provider::Rack::AuthorizationCodeRequest.new(@valid_params.except('client_id')) 25 | }.should raise_error(OAuth2::Provider::Rack::InvalidRequest) 26 | end 27 | end 28 | 29 | describe "with an unknown client" do 30 | it "raises OAuth2::Provider::Rack::InvalidRequest" do 31 | lambda { 32 | OAuth2::Provider::Rack::AuthorizationCodeRequest.new(@valid_params.merge( 33 | 'client_id' => 'unknown' 34 | )) 35 | }.should raise_error(OAuth2::Provider::Rack::InvalidRequest) 36 | end 37 | end 38 | 39 | describe "without a redirect_uri" do 40 | it "raises OAuth2::Provider::Rack::InvalidRequest" do 41 | lambda { 42 | OAuth2::Provider::Rack::AuthorizationCodeRequest.new(@valid_params.except('redirect_uri')) 43 | }.should raise_error(OAuth2::Provider::Rack::InvalidRequest) 44 | end 45 | end 46 | 47 | describe "with a redirect_uri the client regards as invalid" do 48 | before :each do 49 | OAuth2::Provider.client_class.stubs(:from_param).returns(@client) 50 | @client.expects(:allow_redirection?).with(@valid_params['redirect_uri']).returns(false) 51 | end 52 | 53 | it "raises OAuth2::Provider::Rack::InvalidRequest" do 54 | lambda { 55 | OAuth2::Provider::Rack::AuthorizationCodeRequest.new(@valid_params) 56 | }.should raise_error(OAuth2::Provider::Rack::InvalidRequest) 57 | end 58 | end 59 | end 60 | 61 | describe "#grant_existing!(resource_owner)" do 62 | before :each do 63 | @client = OAuth2::Provider.client_class.create! :name => 'client' 64 | @owner = create_resource_owner 65 | @scope = 'a-scope' 66 | @request = OAuth2::Provider::Rack::AuthorizationCodeRequest.new( 67 | 'client_id' => @client.oauth_identifier, 68 | 'redirect_uri' => "https://redirect.example.com/callback", 69 | 'scope' => @scope 70 | ) 71 | end 72 | 73 | describe "when matching authorization exists" do 74 | before :each do 75 | @authorization = create_authorization(:client => @client, :resource_owner => @owner, :scope => @scope) 76 | end 77 | 78 | it "throws an oauth2 response" do 79 | lambda { 80 | @request.grant_existing!(@owner) 81 | }.should throw_symbol(:oauth2) 82 | end 83 | 84 | it "creates an authorization code for the matching authorization" do 85 | catch :oauth2 do 86 | @request.grant_existing!(@owner) 87 | end 88 | code = @authorization.reload.authorization_codes.first 89 | code.should_not be_nil 90 | code.redirect_uri.should eql("https://redirect.example.com/callback") 91 | end 92 | 93 | it "includes authorization code in the response" do 94 | response = catch :oauth2 do 95 | @request.grant_existing!(@owner) 96 | end 97 | code = @authorization.reload.authorization_codes.first 98 | uri = response[1]["Location"] 99 | Addressable::URI.parse(uri).query_values['code'].should == code.code 100 | end 101 | end 102 | 103 | describe "when no matching authorization exists" do 104 | it "returns normally" do 105 | lambda { 106 | @request.grant_existing!(@owner) 107 | }.should_not throw_symbol(:oauth2) 108 | end 109 | end 110 | end 111 | end 112 | 113 | describe OAuth2::Provider::Rack::AuthorizationCodeRequest do 114 | before :each do 115 | ExampleResourceOwner.destroy_all 116 | @client = OAuth2::Provider.client_class.create! :name => 'client' 117 | @valid_params = { 118 | :client_id => @client.oauth_identifier, 119 | :redirect_uri => "https://redirect.example.com/callback" 120 | } 121 | @owner = create_resource_owner 122 | end 123 | 124 | describe "Validating requests" do 125 | action do |env| 126 | request = Rack::Request.new(env) 127 | env['oauth2.authorization_request'] ||= OAuth2::Provider::Rack::AuthorizationCodeRequest.new(request.params) 128 | successful_response 129 | end 130 | 131 | describe "Any request with a client_id and redirect_uri" do 132 | before :each do 133 | get '/oauth/authorize', @valid_params 134 | end 135 | 136 | it "is successful" do 137 | response.status.should == 200 138 | end 139 | end 140 | 141 | describe "Any request without a client_id" do 142 | before :each do 143 | get '/oauth/authorize', @valid_params.except(:client_id) 144 | end 145 | 146 | it "returns 400" do 147 | response.status.should == 400 148 | end 149 | end 150 | 151 | describe "Any request without a redirect_uri" do 152 | before :each do 153 | get '/oauth/authorize', @valid_params.except(:redirect_uri) 154 | end 155 | 156 | it "returns 400" do 157 | response.status.should == 400 158 | end 159 | end 160 | 161 | describe "Any request with an invalid redirect_uri" do 162 | before :each do 163 | get '/oauth/authorize', @valid_params.merge(:redirect_uri => "http://") 164 | end 165 | 166 | it "returns 400" do 167 | response.status.should == 400 168 | end 169 | end 170 | 171 | describe "Any request with an unknown client id" do 172 | before :each do 173 | get '/oauth/authorize', @valid_params.merge(:client_id => 'unknown') 174 | end 175 | 176 | it "returns 400" do 177 | response.status.should == 400 178 | end 179 | end 180 | 181 | describe "A request where the scope is declared invalid" do 182 | action do |env| 183 | request = Rack::Request.new(env) 184 | env['oauth2.authorization_request'] ||= OAuth2::Provider::Rack::AuthorizationCodeRequest.new(request.params) 185 | env['oauth2.authorization_request'].invalid_scope! 186 | successful_response 187 | end 188 | 189 | before :each do 190 | get '/oauth/authorize', @valid_params 191 | end 192 | 193 | redirects_back_with_error 'invalid_scope' 194 | end 195 | end 196 | 197 | describe "Intercepting invalid requests" do 198 | action do |env| 199 | request = Rack::Request.new(env) 200 | begin 201 | env['oauth2.authorization_request'] ||= OAuth2::Provider::Rack::AuthorizationCodeRequest.new(request.params) 202 | successful_response 203 | rescue OAuth2::Provider::Rack::InvalidRequest => e 204 | [418, {'Content-Type' => 'text/plain'}, e.to_s] 205 | end 206 | end 207 | 208 | before :each do 209 | get '/oauth/authorize', @valid_params.except(:client_id) 210 | end 211 | 212 | it "should return the specific response" do 213 | response.status.should == 418 214 | end 215 | end 216 | 217 | describe "When the client has a redirect_uri attribute" do 218 | before :each do 219 | @client = OAuth2::Provider.client_class.create! :name => 'client', :oauth_redirect_uri => "https://redirect.example.com/callback" 220 | @valid_params = { 221 | :client_id => @client.oauth_identifier, 222 | :redirect_uri => "https://redirect.example.com/callback" 223 | } 224 | end 225 | 226 | action do |env| 227 | request = Rack::Request.new(env) 228 | env['oauth2.authorization_request'] ||= OAuth2::Provider::Rack::AuthorizationCodeRequest.new(request.params) 229 | successful_response 230 | end 231 | 232 | it "returns a 400 if the redirect_uri parameter doesn't match hostnames" do 233 | get '/oauth/authorize', @valid_params.merge(:redirect_uri => "https://evil.example.com/callback") 234 | response.status.should == 400 235 | end 236 | 237 | it "returns a 200 if the redirect_uri parameter matches hostname but the path is different" do 238 | get '/oauth/authorize', @valid_params.merge(:redirect_uri => "https://redirect.example.com/other_callback") 239 | response.status.should == 200 240 | end 241 | end 242 | 243 | describe "Granting a code" do 244 | action do |env| 245 | request = Rack::Request.new(env) 246 | env['oauth2.authorization_request'] ||= OAuth2::Provider::Rack::AuthorizationCodeRequest.new(request.params) 247 | env['oauth2.authorization_request'].grant! ExampleResourceOwner.first 248 | end 249 | 250 | before :each do 251 | post '/oauth/authorize', @valid_params.merge(:submit => 'Yes') 252 | end 253 | 254 | it "redirects back to the redirect_uri with a valid authorization code for the client" do 255 | response.status.should == 302 256 | code = Addressable::URI.parse(response.location).query_values["code"] 257 | code.should_not be_nil 258 | found = OAuth2::Provider.authorization_code_class.find_by_code(code) 259 | found.should_not be_nil 260 | found.authorization.client.should == @client 261 | found.authorization.resource_owner.should == @owner 262 | found.should_not be_expired 263 | end 264 | end 265 | 266 | describe "Granting a code with a scope" do 267 | action do |env| 268 | request = Rack::Request.new(env) 269 | env['oauth2.authorization_request'] ||= OAuth2::Provider::Rack::AuthorizationCodeRequest.new(request.params) 270 | env['oauth2.authorization_request'].grant! ExampleResourceOwner.first 271 | end 272 | 273 | before :each do 274 | post '/oauth/authorize', @valid_params.merge(:submit => 'Yes', :scope => 'periscope') 275 | end 276 | 277 | it "includes the scope in the granted authorization" do 278 | code = Addressable::URI.parse(response.location).query_values["code"] 279 | found = OAuth2::Provider.authorization_code_class.find_by_code(code) 280 | found.authorization.scope.should == 'periscope' 281 | end 282 | end 283 | 284 | describe "Granting a code with custom authorization length" do 285 | action do |env| 286 | request = Rack::Request.new(env) 287 | env['oauth2.authorization_request'] ||= OAuth2::Provider::Rack::AuthorizationCodeRequest.new(request.params) 288 | env['oauth2.authorization_request'].grant! ExampleResourceOwner.first, 5.years.from_now 289 | end 290 | 291 | before :each do 292 | post '/oauth/authorize', @valid_params.merge(:submit => 'Yes', :five_years => 'true') 293 | end 294 | 295 | it "redirects with an authorization code linked to the extended authorization" do 296 | code = Addressable::URI.parse(response.location).query_values["code"] 297 | found = OAuth2::Provider.authorization_code_class.find_by_code(code) 298 | found.authorization.expires_at.should eql(5.years.from_now) 299 | end 300 | end 301 | 302 | describe "Denying a code" do 303 | action do |env| 304 | request = Rack::Request.new(env) 305 | env['oauth2.authorization_request'] ||= OAuth2::Provider::Rack::AuthorizationCodeRequest.new(request.params) 306 | env['oauth2.authorization_request'].deny! 307 | end 308 | 309 | before :each do 310 | post '/oauth/authorize', @valid_params.merge(:submit => 'No') 311 | end 312 | 313 | redirects_back_with_error 'access_denied' 314 | end 315 | end -------------------------------------------------------------------------------- /spec/requests/middleware_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | describe OAuth2::Provider::Rack::Middleware do 4 | subject do 5 | ::OAuth2::Provider::Rack::Middleware.new(main_app) 6 | end 7 | 8 | def app 9 | subject 10 | end 11 | 12 | describe "in general" do 13 | let :main_app do 14 | Proc.new do 15 | [200, {'Content-Type' => 'text/plain'}, ['Apptastic']] 16 | end 17 | end 18 | 19 | it "passes requests to /oauth/access_token to #handle_access_token_request" do 20 | subject.expects(:handle_access_token_request).returns( 21 | [418, {'Content-Type' => 'text/plain'}, ['Short and stout']] 22 | ) 23 | get "/oauth/access_token" 24 | response.status.should eql(418) 25 | response.body.should eql('Short and stout') 26 | end 27 | 28 | it "passes other requests to the main app" do 29 | get "/any/other/path" 30 | response.status.should eql(200) 31 | response.body.should eql('Apptastic') 32 | end 33 | 34 | describe "with access_token_path configured to /api/oauth/access_token" do 35 | before(:each) do 36 | OAuth2::Provider.configure do |config| 37 | config.access_token_path = '/api/oauth/access_token' 38 | end 39 | end 40 | 41 | it "passes requests to /api/oauth/access_token to #handle_access_token_request" do 42 | subject.expects(:handle_access_token_request).returns( 43 | [418, {'Content-Type' => 'text/plain'}, ['Short and stout']] 44 | ) 45 | get "/api/oauth/access_token" 46 | response.status.should eql(418) 47 | response.body.should eql('Short and stout') 48 | end 49 | 50 | after(:each) do 51 | OAuth2::Provider.configure do |config| 52 | config.access_token_path = '/oauth/access_token' 53 | end 54 | end 55 | end 56 | end 57 | 58 | describe "when main app throws :oauth2 response" do 59 | let :main_app do 60 | Proc.new do 61 | throw :oauth2, [418, {'Content-Type' => 'text/plain'}, ['Teapot']] 62 | end 63 | end 64 | 65 | it "uses thrown response" do 66 | get "/any/path" 67 | response.status.should eql(418) 68 | response.body.should eql('Teapot') 69 | end 70 | end 71 | end 72 | -------------------------------------------------------------------------------- /spec/schema.rb: -------------------------------------------------------------------------------- 1 | ActiveRecord::Schema.define(:version => 20110323171649) do 2 | create_table 'example_resource_owners', :force => true do |t| 3 | t.string 'username' 4 | t.string 'password' 5 | end 6 | 7 | create_table 'oauth_clients', :force => true do |t| 8 | t.string 'name' 9 | t.string 'oauth_redirect_uri' 10 | t.string 'oauth_identifier', :null => false 11 | t.string 'oauth_secret', :null => false 12 | end 13 | 14 | create_table 'oauth_authorization_codes', :force => true do |t| 15 | t.integer 'authorization_id', :null => false 16 | t.string 'code', :null => false 17 | t.datetime 'expires_at' 18 | t.datetime 'created_at' 19 | t.datetime 'updated_at' 20 | t.string 'redirect_uri', :null => false 21 | end 22 | 23 | create_table 'oauth_authorizations', :force => true do |t| 24 | t.integer 'client_id', :null => false 25 | t.integer 'resource_owner_id' 26 | t.string 'resource_owner_type' 27 | t.string 'scope' 28 | t.datetime 'expires_at' 29 | end 30 | 31 | create_table 'oauth_access_tokens', :force => true do |t| 32 | t.integer 'authorization_id', :null => false 33 | t.string 'access_token', :null => false 34 | t.string 'refresh_token' 35 | t.datetime 'expires_at' 36 | t.datetime 'created_at' 37 | t.datetime 'updated_at' 38 | end 39 | end 40 | -------------------------------------------------------------------------------- /spec/set_backend_env_to_mongoid.rb: -------------------------------------------------------------------------------- 1 | ENV['BACKEND']='mongoid' -------------------------------------------------------------------------------- /spec/spec_helper.rb: -------------------------------------------------------------------------------- 1 | require 'bundler/setup' 2 | require 'rack' 3 | require 'rack/test' 4 | require 'oauth2-provider' 5 | 6 | require 'timecop' 7 | require 'yajl' 8 | 9 | require "support/#{ENV["BACKEND"] || 'activerecord'}_backend" 10 | 11 | require 'support/macros' 12 | require 'support/factories' 13 | require 'support/rack' 14 | 15 | RSpec.configure do |config| 16 | config.before :each do 17 | Timecop.freeze 2001, 1, 1, 12 18 | end 19 | 20 | config.after :each do 21 | Timecop.return 22 | end 23 | 24 | config.mock_with :mocha 25 | 26 | config.include OAuth2::Provider::RSpec::Macros 27 | config.include OAuth2::Provider::RSpec::Factories 28 | config.include OAuth2::Provider::RSpec::Rack 29 | end -------------------------------------------------------------------------------- /spec/support/activerecord_backend.rb: -------------------------------------------------------------------------------- 1 | require 'active_record' 2 | 3 | ActiveRecord::Base.establish_connection( 4 | :adapter => "sqlite3", 5 | :database => ":memory:" 6 | ) 7 | 8 | require "schema.rb" 9 | 10 | class ExampleResourceOwner < ActiveRecord::Base 11 | def self.authenticate_with_username_and_password(*args) 12 | find_by_username_and_password(*args) 13 | end 14 | end 15 | 16 | OAuth2::Provider.configure do |config| 17 | config.resource_owner_class_name = 'ExampleResourceOwner' 18 | end -------------------------------------------------------------------------------- /spec/support/factories.rb: -------------------------------------------------------------------------------- 1 | module OAuth2::Provider 2 | module RSpec 3 | module Factories 4 | def build_client(attributes = {}) 5 | OAuth2::Provider.client_class.new({:name => 'client'}.merge(attributes)) 6 | end 7 | 8 | def create_client(attributes = {}) 9 | build_client(attributes).tap do |c| 10 | c.save! 11 | end 12 | end 13 | 14 | def build_authorization(attributes = {}) 15 | OAuth2::Provider.authorization_class.new({ 16 | :client => build_client 17 | }.merge(attributes)) 18 | end 19 | 20 | def create_authorization(attributes = {}) 21 | build_authorization({:client => create_client}.merge(attributes)).tap do |ag| 22 | ag.save! 23 | end 24 | end 25 | 26 | def build_authorization_code(attributes = {}) 27 | OAuth2::Provider.authorization_code_class.new({ 28 | :redirect_uri => "https://client.example.com/callback", 29 | :authorization => build_authorization 30 | }.merge(attributes)) 31 | end 32 | 33 | def create_authorization_code(attributes = {}) 34 | build_authorization_code({:authorization => create_authorization}.merge(attributes)).tap do |ac| 35 | ac.save! 36 | end 37 | end 38 | 39 | def build_access_token(attributes = {}) 40 | OAuth2::Provider.access_token_class.new({ 41 | :authorization => build_authorization 42 | }.merge(attributes)) 43 | end 44 | 45 | def create_access_token(attributes = {}) 46 | build_access_token({:authorization => create_authorization}.merge(attributes)).tap do |ac| 47 | ac.save! 48 | end 49 | end 50 | 51 | def create_resource_owner(attributes = {}) 52 | ExampleResourceOwner.create! 53 | end 54 | end 55 | end 56 | end 57 | -------------------------------------------------------------------------------- /spec/support/macros.rb: -------------------------------------------------------------------------------- 1 | module OAuth2::Provider 2 | module RSpec 3 | module Macros 4 | extend ActiveSupport::Concern 5 | 6 | def json_from_response 7 | @json_from_response ||= begin 8 | response.content_type.should == "application/json" 9 | Yajl::Parser.new.parse(response.body) 10 | end 11 | end 12 | 13 | module ClassMethods 14 | def responds_with_header(name, value) 15 | it %{responds with header #{name}: #{value}} do 16 | response.headers[name].should == value 17 | end 18 | end 19 | 20 | def responds_with_status(status) 21 | it %{responds with status #{status}} do 22 | response.status.should == status 23 | end 24 | end 25 | 26 | def responds_with_json_error(name, options = {}) 27 | error_description = %{, "error_description": "#{options[:description]}"} if options[:description] 28 | it %{responds with json: {"error": "#{name}"#{error_description}}, status: #{options[:status]}} do 29 | response.status.should == options[:status] 30 | json = json_from_response 31 | json["error"].should == name 32 | json["error_description"].should == options[:description] if options[:description] 33 | end 34 | end 35 | 36 | def redirects_back_with_error(name) 37 | it %{redirects back with error '#{name}'} do 38 | response.status.should == 302 39 | error = Addressable::URI.parse(response.location).query_values["error"] 40 | error.should == name 41 | end 42 | end 43 | end 44 | end 45 | end 46 | end -------------------------------------------------------------------------------- /spec/support/mongoid_backend.rb: -------------------------------------------------------------------------------- 1 | require 'mongoid' 2 | 3 | class ExampleResourceOwner 4 | include Mongoid::Document 5 | 6 | field :username 7 | field :password 8 | 9 | references_many :authorizations, :class_name => "OAuth2::Provider::Models::Mongoid::Authorization" 10 | 11 | def self.authenticate_with_username_and_password(username, password) 12 | where(:username => username, :password => password).first 13 | end 14 | end 15 | 16 | OAuth2::Provider.configure do |config| 17 | config.backend = :mongoid 18 | config.resource_owner_class_name = 'ExampleResourceOwner' 19 | end 20 | 21 | Mongoid.logger.level = Logger::INFO 22 | Mongoid.configure do |config| 23 | config.from_hash( 24 | "host" => "127.0.0.1", 25 | "autocreate_indexes" => false, 26 | "allow_dynamic_fields" => true, 27 | "include_root_in_json" => false, 28 | "parameterize_keys" => true, 29 | "persist_in_safe_mode" => true, 30 | "raise_not_found_error" => true, 31 | "reconnect_time" => 3, 32 | "use_activesupport_time_zone" => true, 33 | "database" => "oauth2_test" 34 | ) 35 | end 36 | 37 | -------------------------------------------------------------------------------- /spec/support/rack.rb: -------------------------------------------------------------------------------- 1 | module OAuth2::Provider 2 | module RSpec 3 | module Rack 4 | extend ActiveSupport::Concern 5 | 6 | included do 7 | class_attribute :action_block 8 | include ::Rack::Test::Methods 9 | end 10 | 11 | def app 12 | ::OAuth2::Provider::Rack::Middleware.new( 13 | action_block 14 | ) 15 | end 16 | 17 | def response 18 | last_response 19 | end 20 | 21 | module ClassMethods 22 | def action(&block) 23 | self.action_block = block 24 | end 25 | 26 | def successful_response 27 | [200, {'Content-Type' => 'text/plain'}, 'Success'] 28 | end 29 | end 30 | end 31 | end 32 | end --------------------------------------------------------------------------------