Permalink
Browse files

Introduces Authorization Request for 'code' flow

The flow is described in: http://tools.ietf.org/html/draft-ietf-oauth-v2-22#section-4.1

The main implementation is in AuthorizationRequest class which is
responsible to validate all params, create the appropriate access
grant and redirect the client back to its redirect uri.
  • Loading branch information...
1 parent ed732ce commit ef4f5f22536ad36579a358c7bf22aacbdbbdead0 @felipeelias felipeelias committed Nov 17, 2011
@@ -1,9 +1,27 @@
module Doorkeeper
class AuthorizationsController < ApplicationController
+ rescue_from OAuth::MismatchRedirectURI do
+ Rails.logger.error "OAuth: Invalid application redirect_uri"
+ render :error
+ end
+
def new
+ @authorization = OAuth::AuthorizationRequest.new(current_resource, params)
+ render :error unless @authorization.valid?
end
def create
+ authorization = OAuth::AuthorizationRequest.new(current_resource, params)
+ if authorization.authorize
+ redirect_to authorization.redirect_uri
+ else
+ redirect_to authorization.invalid_redirect_uri
+ end
+ end
+
+ private
+
+ def current_resource
end
end
end
View
@@ -1,3 +1,29 @@
class AccessGrant < ActiveRecord::Base
+ include OAuth::RandomString
+ self.table_name = "oauth_access_grants"
+
+ belongs_to :application
+
+ validates :resource_owner_id, :application_id, :token, :expires_in, :presence => true
+
+ before_validation :generate_token, :on => :create
+
+ def expired?
+ expires_in.present? && Time.now > expired_time
+ end
+
+ def accessible?
+ !expired?
+ end
+
+ private
+
+ def expired_time
+ self.created_at + expires_in.seconds
+ end
+
+ def generate_token
+ self.token = unique_random_string_for(:token)
+ end
end
@@ -0,0 +1 @@
+<p>An error has occurred</p>
@@ -1,2 +1,22 @@
-<h1>Authorizations#new</h1>
-<p>Find me in app/views/doorkeeper/authorizations/new.html.erb</p>
+<div class="span16">
+ <h2>Authorize <%= @authorization.client_name %> to use your account?</h2>
+</div>
+
+<div class="span16">
+ <p>This application <b>will able to</b>:</p>
+
+ <ul>
+ <li>Access your public data</li>
+ </ul>
+
+ <%= form_tag authorization_path do %>
+ <%= hidden_field_tag :client_id, @authorization.client_id %>
+ <%= hidden_field_tag :response_type, @authorization.response_type %>
+ <%= submit_tag "Authorize", class: "btn primary" %> or
+ <% end %>
+ <%= form_tag authorization_path, :method => :delete do %>
+ <%= hidden_field_tag :client_id, @authorization.client_id %>
+ <%= hidden_field_tag :response_type, @authorization.response_type %>
+ <%= button_tag "Deny", class: "btn" %>
+ <% end %>
+</div>
View
@@ -1,6 +1,6 @@
Doorkeeper::Engine.routes.draw do
- get 'authorize', :to => "authorizations#new"
- post 'authorize', :to => "authorizations#create"
- delete 'authorize', :to => "authorizations#destroy"
- post 'token', :to => "tokens#create"
+ get 'authorize', :to => "authorizations#new", :as => :authorization
+ post 'authorize', :to => "authorizations#create", :as => :authorization
+ delete 'authorize', :to => "authorizations#destroy", :as => :authorization
+ post 'token', :to => "tokens#create", :as => :token
end
View
@@ -23,4 +23,5 @@ Gem::Specification.new do |s|
s.add_development_dependency "capybara"
s.add_development_dependency "generator_spec"
s.add_development_dependency "factory_girl_rails"
+ s.add_development_dependency "timecop"
end
View
@@ -1,6 +1,6 @@
require "doorkeeper/engine"
require "doorkeeper/oauth/random_string"
-require "doorkeeper/oauth/server"
+require "doorkeeper/oauth"
module Doorkeeper
end
View
@@ -0,0 +1,7 @@
+module Doorkeeper
+ module OAuth
+ class MismatchRedirectURI < StandardError; end
+
+ autoload :AuthorizationRequest, "doorkeeper/oauth/authorization_request"
+ end
+end
@@ -0,0 +1,86 @@
+module Doorkeeper::OAuth
+ class AuthorizationRequest
+ DEFAULT_EXPIRATION_TIME = 600
+
+ attr_reader :resource_owner, :options
+
+ delegate :name, :uid, :to => :client, :prefix => true
+ alias :client_id :client_uid
+
+ def initialize(resource_owner, options)
+ @resource_owner = resource_owner
+ @options = options
+ @grant = nil
+ end
+
+ def authorize
+ if valid?
+ @grant = AccessGrant.create!(
+ :application_id => client_id,
+ :resource_owner_id => resource_owner.id,
+ :expires_in => DEFAULT_EXPIRATION_TIME
+ )
+ end
+ end
+
+ def response_type
+ options[:response_type]
+ end
+
+ def valid?
+ has_response_type? &&
+ has_client? &&
+ redirect_uri_matches?
+ end
+
+ def token
+ @grant.token
+ end
+
+ def redirect_uri
+ build_uri { |uri| uri.query = "code=#{token}" }
+ end
+
+ def invalid_redirect_uri
+ build_uri { |uri| uri.query = "error=#{error_name}" }
+ end
+
+ def error_name
+ case
+ when !has_response_type? then "invalid_request"
+ end
+ end
+
+ private
+
+ def has_response_type?
+ response_type.present?
+ end
+
+ def has_client?
+ client.present?
+ end
+
+ def has_redirect_uri?
+ options[:redirect_uri].present?
+ end
+
+ def redirect_uri_mismatches?
+ has_redirect_uri? and client.redirect_uri != options[:redirect_uri]
+ end
+
+ def redirect_uri_matches?
+ redirect_uri_mismatches? ? raise(MismatchRedirectURI) : true
+ end
+
+ def client
+ @client ||= Application.find_by_uid(options[:client_id])
+ end
+
+ def build_uri
+ uri = URI.parse(client.redirect_uri)
+ yield uri
+ uri.to_s
+ end
+ end
+end
@@ -1 +0,0 @@
-require "doorkeeper/oauth/server/authorization"
@@ -1,47 +0,0 @@
-module OAuth
- module Server
- class Authorization
-
- class InvalidRedirectionURI < StandardError; end
-
- attr_reader :client, :resource, :grant
-
- def initialize(client, resource, options = {})
- @client, @resource, @options = client, resource, options
- end
-
- def grant!
- validate_params!
- @grant ||= AccessGrant.create(
- client: client,
- resource: resource
- )
- end
-
- def redirect_uri
- uri = URI.parse(client.redirect_uri)
- uri.query = "code=#{token}"
- uri.to_s
- end
-
- def token
- grant.code
- end
-
- private
-
- def options
- @options
- end
-
- def validate_params!
- raise InvalidRedirectionURI unless redirect_uri_valid?
- end
-
- def redirect_uri_valid?
- return true unless options[:redirect_uri].present?
- options[:redirect_uri] == client.redirect_uri
- end
- end
- end
-end
@@ -0,0 +1,63 @@
+require "spec_helper"
+
+module Doorkeeper
+ describe AuthorizationsController, "#new" do
+ describe "with valid params" do
+ before { OAuth::AuthorizationRequest.any_instance.stub(:valid?) { true } }
+
+ it "renders the :new template" do
+ get :new, :use_route => :doorkeeper
+ should render_template(:new)
+ end
+ end
+
+ describe "with invalid params" do
+ it "renders :error when params are invalid" do
+ OAuth::AuthorizationRequest.any_instance.stub(:valid?) { false }
+ get :new, :use_route => :doorkeeper
+ should render_template(:error)
+ end
+
+ it "renders :error when params are invalid" do
+ OAuth::AuthorizationRequest.any_instance.stub(:valid?) { raise OAuth::MismatchRedirectURI }
+ get :new, :use_route => :doorkeeper
+ should render_template(:error)
+ end
+ end
+ end
+
+ describe AuthorizationsController, "#create" do
+ let(:client) do
+ double(:client, :redirect_uri => "http://something.com/cb")
+ end
+
+ before do
+ OAuth::AuthorizationRequest.any_instance.stub(:client) { client }
+ OAuth::AuthorizationRequest.any_instance.stub(:token) { "token" }
+ end
+
+ describe "with valid params" do
+ it "redirects to client's uri" do
+ OAuth::AuthorizationRequest.any_instance.stub(:valid?) { true }
+ post :create, :use_route => :doorkeeper
+ should redirect_to("http://something.com/cb?code=token")
+ end
+ end
+
+ describe "with invalid params" do
+ it "renders :error when params are invalid" do
+ OAuth::AuthorizationRequest.any_instance.stub(:valid?) { false }
+ post :create, :use_route => :doorkeeper
+ should redirect_to("http://something.com/cb?error=invalid_request")
+ end
+
+ it "renders :error when params are invalid" do
+ OAuth::AuthorizationRequest.any_instance.stub(:valid?) { raise OAuth::MismatchRedirectURI }
+ post :create, :use_route => :doorkeeper
+ should render_template(:error)
+ end
+ end
+ end
+
+
+end
@@ -0,0 +1,7 @@
+FactoryGirl.define do
+ factory :access_grant do
+ sequence(:resource_owner_id) { |n| n }
+ application
+ expires_in 100
+ end
+end
@@ -1,5 +1,6 @@
FactoryGirl.define do
factory :application do
sequence(:name){ |n| "Application #{n}" }
+ redirect_uri "https://app.com/callback"
end
end
@@ -0,0 +1,60 @@
+require 'spec_helper'
+
+describe AccessGrant do
+ subject { Factory.build(:access_grant) }
+
+ it { should be_valid }
+
+ describe "validations" do
+ it "is invalid without resource_owner_id" do
+ subject.resource_owner_id = nil
+ should_not be_valid
+ end
+
+ it "is invalid without application_id" do
+ subject.application_id = nil
+ should_not be_valid
+ end
+
+ it "is invalid without token" do
+ subject.save
+ subject.token = nil
+ should_not be_valid
+ end
+
+ it "is invalid without expires_in" do
+ subject.expires_in = nil
+ should_not be_valid
+ end
+ end
+
+ describe "token" do
+ it "is unique" do
+ tokens = []
+ 3.times do
+ token = Factory(:access_grant).token
+ tokens.should_not include(token)
+ end
+ end
+
+ it "is generated before validation" do
+ expect { subject.valid? }.to change { subject.token }.from(nil)
+ end
+ end
+
+ describe "expired?" do
+ it "is not expired when" do
+ grant = Factory(:access_grant, :expires_in => 1000)
+ grant.should_not be_expired
+ grant.should be_accessible
+ end
+
+ it "is true if expired" do
+ grant = Factory(:access_grant, :expires_in => 400)
+ Timecop.freeze(Time.now + 600.seconds) do
+ grant.should be_expired
+ grant.should_not be_accessible
+ end
+ end
+ end
+end
Oops, something went wrong.

0 comments on commit ef4f5f2

Please sign in to comment.