├── .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
--------------------------------------------------------------------------------