Permalink
Browse files

Basic middleware to catch and authorize access_token

  • Loading branch information...
0 parents commit 3116c2684796e2f2fa4627ccdd0ccf073bfb3641 @ismasan committed Jul 7, 2011
@@ -0,0 +1,5 @@
+*.gem
+.bundle
+Gemfile.lock
+pkg/*
+.rvmrc
28 Gemfile
@@ -0,0 +1,28 @@
+source "http://rubygems.org"
+
+# Specify your gem's dependencies in rack-oauth2_utils.gemspec
+#gemspec
+
+gem 'rack'
+
+group :test do
+ gem 'minitest'
+ gem "rack-test"
+end
+
+# use Rack::OAuth2::Access::Middleware, :store => Rack::OAuth2::Access::MemoryStore
+#
+#
+# run App
+#
+# before do
+#
+# end
+#
+# Rack::OAuth2::Access::AppHelpers
+#
+# before do
+# account = Account.find(oauth.identity)
+# end
+#
+# get '/' do
@@ -0,0 +1,2 @@
+require 'bundler'
+Bundler::GemHelper.install_tasks
@@ -0,0 +1,9 @@
+require 'rack'
+require 'rack-oauth2_utils/middleware'
+require 'rack-oauth2_utils/oauth_request'
+
+module Rack
+ module OAuth2Utils
+ # Your code goes here...
+ end
+end
@@ -0,0 +1,45 @@
+module Rack
+ module OAuth2Utils
+
+ class Middleware
+
+ attr_reader :store
+
+ def initialize(app, options = {})
+ @app = app
+ @store = options[:store] || {}
+ @realm = options[:realm]
+ @logger = options[:logger]
+ end
+
+ def call(env)
+ request = OAuthRequest.new(env)
+ logger = @logger || env["rack.logger"]
+
+ # If not oauth header / param, leave it up to the app.
+ return @app.call(env) unless request.oauth?
+
+ # Fetch identity
+ if identity = store[request.access_token] # identity found, forward to backend
+ env["oauth.identity"] = identity
+ logger.info "RO2U: Authorized #{identity}" if logger
+ else # invalid token
+ logger.info "RO2U: Invalid token" if logger
+ return unauthorized(request)
+ end
+ @app.call(env)
+ end
+
+ protected
+
+ # Returns WWW-Authenticate header.
+ def unauthorized(request)
+ challenge = 'OAuth realm="%s"' % (@realm || request.host)
+ challenge << ', error="invalid_token", error_description="The access token is invalid."'
+ return [401, { "WWW-Authenticate" => challenge, 'Content-Type' => 'text/plain' }, ['The access token is invalid.']]
+ end
+
+ end
+
+ end
+end
@@ -0,0 +1,41 @@
+# Wraps Rack::Request to expose Basic and OAuth authentication
+# credentials.
+# Borrowed from https://github.com/flowtown/rack-oauth2-server
+#
+module Rack
+ module OAuth2Utils
+
+ class OAuthRequest < Rack::Request
+
+ AUTHORIZATION_KEYS = %w{HTTP_AUTHORIZATION X-HTTP_AUTHORIZATION X_HTTP_AUTHORIZATION}
+
+ # Returns authorization header.
+ def authorization_header
+ @authorization_header ||= (
+ h = AUTHORIZATION_KEYS.inject(nil) { |auth, key| auth || @env[key] }
+ if h && h[/^oauth/i]
+ h.gsub(/\n/, "").split[1]
+ else
+ nil
+ end
+ )
+ end
+
+ def authorization_param
+ @authorization_param ||= self.GET['oauth_token']
+ end
+
+ # True if authentication scheme is OAuth.
+ def oauth?
+ authorization_header || authorization_param
+ end
+
+ # If OAuth, returns access token.
+ #
+ def access_token
+ @access_token ||= oauth?
+ end
+ end
+
+ end
+end
@@ -0,0 +1,5 @@
+module Rack
+ module OAuth2Utils
+ VERSION = "0.0.1"
+ end
+end
@@ -0,0 +1,25 @@
+# -*- encoding: utf-8 -*-
+$:.push File.expand_path("../lib", __FILE__)
+require "rack-oauth2_utils/version"
+
+Gem::Specification.new do |s|
+ s.name = "rack-oauth2_utils"
+ s.version = Rack::OAuth2Utils::VERSION
+ s.platform = Gem::Platform::RUBY
+ s.authors = ["TODO: Write your name"]
+ s.email = ["TODO: Write your email address"]
+ s.homepage = ""
+ s.summary = %q{TODO: Write a gem summary}
+ s.description = %q{TODO: Write a gem description}
+
+ s.rubyforge_project = "rack-oauth2_utils"
+
+ s.add_dependency 'rack', ">= 1.2.2"
+ s.add_development_dependency "bundler", ">= 1.0.0"
+ s.add_development_dependency "minitest"
+
+ s.files = `git ls-files`.split("\n")
+ s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
+ s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
+ s.require_paths = ["lib"]
+end
@@ -0,0 +1,136 @@
+require File.expand_path(File.dirname(__FILE__) + '/test_helper')
+
+describe Rack::OAuth2Utils::Middleware do
+
+ include Rack::Test::Methods
+
+ OK_RESPONSE = [200, {'Content-Type' => 'text/plain'}, ['Hello world']]
+ FORBIDDEN_RESPONSE = [403, {'Content-Type' => 'text/plain'}, ['Nono']]
+
+ def app
+ @app ||= Rack::Builder.new do
+ # Simple token / identity store
+ use Rack::OAuth2Utils::Middleware, :store => {
+ # token # identity
+ 'aaaaa' => 'ismasan',
+ 'bbbbb' => 'sachi'
+ }
+ # Public endpoint
+ map('/public'){
+ run lambda {|env| OK_RESPONSE }
+ }
+ # Private, o auth protected
+ map('/private'){
+ run lambda {|env|
+ if env['oauth.identity']
+ OK_RESPONSE
+ else
+ FORBIDDEN_RESPONSE
+ end
+ }
+ }
+ end
+ end
+
+ describe "no token" do
+
+ describe 'public resource' do
+ before {get '/public'}
+
+ it 'should return 200 Ok' do
+ last_response.status.must_equal 200
+ end
+
+ it 'should return body' do
+ last_response.body.must_equal 'Hello world'
+ end
+ end
+
+ describe 'private resource' do
+ before {get '/private'}
+
+ it 'should return 200 Ok' do
+ last_response.status.must_equal 403
+ end
+
+ it 'should return body' do
+ last_response.body.must_equal 'Nono'
+ end
+ end
+ end
+
+ describe 'with invalid token' do
+
+ before {
+ header "Authorization", "OAuth invalidtoken"
+ }
+
+ describe 'public resource' do
+ before {get '/public'}
+
+ it 'should return 401 Unauthorized' do
+ last_response.status.must_equal 401
+ end
+
+ it 'should return WWW-Authenticate header with realm and error info' do
+ last_response.headers['WWW-Authenticate'].must_equal "OAuth realm=\"example.org\", error=\"invalid_token\", error_description=\"The access token is invalid.\""
+ end
+ end
+
+ describe 'private resource' do
+ before {get '/private'}
+
+ it 'should return 401 Unauthorized' do
+ last_response.status.must_equal 401
+ end
+
+ it 'should return WWW-Authenticate header with realm and error info' do
+ last_response.headers['WWW-Authenticate'].must_equal "OAuth realm=\"example.org\", error=\"invalid_token\", error_description=\"The access token is invalid.\""
+ end
+ end
+ end
+
+ describe 'with valid token' do
+
+ before {
+ header "Authorization", "OAuth aaaaa"
+ }
+
+ describe 'public resource' do
+ before {get '/public'}
+
+ it 'should return 200 Ok' do
+ last_response.status.must_equal 200
+ end
+
+ it 'should return body' do
+ last_response.body.must_equal 'Hello world'
+ end
+ end
+
+ describe 'private resource' do
+ before {get '/private'}
+
+ it 'should return 200 Ok' do
+ last_response.status.must_equal 200
+ end
+
+ it 'should return body' do
+ last_response.body.must_equal 'Hello world'
+ end
+ end
+ end
+
+ describe 'with valid token as query param' do
+ before {get '/private', 'oauth_token' => 'aaaaa'}
+
+ it 'should return 200 Ok' do
+ last_response.status.must_equal 200
+ end
+
+ it 'should return body' do
+ last_response.body.must_equal 'Hello world'
+ end
+ end
+
+end
@@ -0,0 +1,14 @@
+require 'rubygems'
+require 'bundler'
+Bundler.setup :default, :test
+
+ENV["RACK_ENV"] = "test"
+
+$LOAD_PATH.unshift(File.dirname(__FILE__))
+$LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
+
+require 'rack-oauth2_utils'
+require "rack/test"
+
+
+require 'minitest/autorun'

0 comments on commit 3116c26

Please sign in to comment.