Commits

Michael Granger  committed 516a648

Started work

  • Participants
  • Parent commits dbe7b7b

Comments (0)

Files changed (10)

+# .rvm.gems generated gem export file. Note that any env variable settings will be missing. Append these after using a ';' field separator
+hoe-deveiate -v0.4.0
+hoe-bundler -v1.2.0
+strelka -v0.8.0
+simplecov -v0.8.2
+
+#!/usr/bin/env bash
+
+# This is an RVM Project .rvmrc file, used to automatically load the ruby
+# development environment upon cd'ing into the directory
+
+environment_id="ruby-2.0.0@strelka-cors"
+
+if [[ -d "${rvm_path:-$HOME/.rvm}/environments" \
+	&& -s "${rvm_path:-$HOME/.rvm}/environments/$environment_id" ]]; then
+	echo "Using ${environment_id}"
+	. "${rvm_path:-$HOME/.rvm}/environments/$environment_id"
+
+	if [[ -s "${rvm_path:-$HOME/.rvm}/hooks/after_use" ]]; then
+		. "${rvm_path:-$HOME/.rvm}/hooks/after_use"
+	fi
+else
+	# If the environment file has not yet been created, use the RVM CLI to select.
+	if ! rvm --create use  "$environment_id"
+		then
+		echo "Failed to create RVM environment '${environment_id}'."
+		exit 1
+	fi
+fi
+
+filename=".rvm.gems"
+if [[ -s "$filename" ]]; then
+	rvm gemset import "$filename"
+fi
+
+
 = strelka-cors
 
-* FIX (url)
+home :: http://deveiate.org/projects/Strelka-CORS
+code :: http://bitbucket.org/ged/strelka-cors
+github :: https://github.com/ged/strelka-cors
+docs :: http://deveiate.org/code/strelka-cors
+
 
 == Description
 
-FIX (describe your package)
+This is a Strelka application plugin for describing rules for Cross-Origin
+Resource Sharing ({CORS}[http://www.w3.org/TR/cors/]).
+
+By default, the plugin has paranoid defaults, and doesn't do anything. You'll need
+to grant access to the resources you want to share.
+
+There are a number of directives to help you tailor access, but the primary one
+is the {allow_origins}[rdoc-ref:Strelka::App::CORS::ClassMethods#allow_origins]
+declaration.
+
+
+=== Allow All Simple Requests
+
+If you just want to allow simple (GET, HEAD, POST) requests to your application
+from any origin, you can do it like so:
+
+    require 'strelka/app'
+    
+    class MyApp < Strelka::App
+        plugin :cors
+        allow_origins '*'
+
+        # The rest of your app
+
+    end
+
+This will add the appropriate header to outgoing responses.
+
+
 
 == Installation
 
     gem install strelka-cors
 
+
 == License
 
 Copyright (c) 2013, FIX

File cors_server_flowchart.png

Added
New image

File lib/strelka/app/cors.rb

+# -*- ruby -*-
+# vim: set nosta noet ts=4 sw=4:
+# encoding: utf-8
+
+require 'strelka' unless defined?( Strelka )
+require 'strelka/app' unless defined?( Strelka::App )
+require 'strelka/httprequest/cors'
+
+
+# Strelka::App plugin module for Cross-Origin Resource Sharing (CORS)
+#
+#     class MyService < Strelka::App
+#         plugins :cors
+#
+#         allow_origins '*'
+#
+#     end # MyService
+#
+# Resources:
+#
+# * http://www.w3.org/TR/cors/
+# * http://enable-cors.org/server.html
+# * http://www.html5rocks.com/en/tutorials/cors/
+#
+module Strelka::App::CORS
+	extend Strelka::Plugin
+
+	run_outside :routing, :restresources
+	run_inside  :errors, :sessions, :auth
+
+
+	# Class methods to add to including applications
+	module ClassMethods
+
+		### Extension hook. Add instance variables to extended classes.
+		def self::extended( mod )
+			mod.instance_variable_set( :@allowed_origins, [] )
+		end
+
+
+		### Get/declare the origins which are allowed to fetch resources
+		def allow_origins( *origins )
+			unless origins.empty?
+				@allowed_origins.concat( origins )
+			end
+			return @allowed_origins
+		end
+
+	end # module ClassMethods
+
+
+	### Extension callback -- extend the HTTPRequest class with Auth
+	### support when this plugin is loaded.
+	def self::included( object )
+		self.log.debug "Extending Request with CORS mixin"
+		Strelka::HTTPRequest.class_eval { include Strelka::HTTPRequest::CORS }
+		super
+	end
+
+
+	### Extend handled requests with CORS stuff.
+	def handle_request( request )
+		super { request.response }
+	end
+
+
+end # module Strelka::App::CORS
+
+

File lib/strelka/cors.rb

-# -*- ruby -*-
-# vim: set nosta noet ts=4 sw=4:
-# encoding: utf-8
-
-require 'strelka' unless defined?( Strelka )
-require 'strelka/app' unless defined?( Strelka::App )
-
-
-# Strelka::App plugin module for Cross-Origin Resource Sharing (CORS)
-#
-#     class MyService < Strelka::App
-#         plugins :cors
-#
-#         allow_origin 'localhost', '127.0.0.1', 'example.com'
-#
-#     end # MyService
-#
-# References:
-#
-# * http://www.w3.org/TR/cors/
-#
-module Strelka::App::CORS
-	extend Strelka::App::Plugin
-
-	run_before :routing, :restresources
-	run_after  :templating, :errors, :sessions, :auth
-
-
-	### Mark and time the app.
-	def handle_request( request )
-		Metriks.meter( "#@metriks_key.requests" ).mark
-
-		response = nil
-		Metriks.timer( "#@metriks_key.duration.app" ).time do
-			response = super
-		end
-
-		self.log.debug "Returning response: %p" % [ response ]
-		return response
-	end
-
-end # module Strelka::App::Metriks
-
-

File lib/strelka/httprequest/cors.rb

+# -*- ruby -*-
+#encoding: utf-8
+
+require 'strelka/httprequest' unless defined?( Strelka::HTTPRequest )
+
+
+# The mixin that adds methods to Strelka::HTTPRequest for
+# Cross-Origin Resource Sharing (CORS).
+module Strelka::HTTPRequest::CORS
+
+
+	### Add some instance variables to the request object.
+	def initialize( * ) # :notnew:
+		super
+		@origin = nil
+	end
+
+
+	######
+	public
+	######
+
+	### Return the URI in the Origin header (if the request has one) as a
+	### URI object. If the request doesn't have an Origin: header, returns
+	### nil.
+	def origin
+		unless @origin
+			origin_uri = self.headers.origin or return nil
+			@origin = URI( origin_uri )
+		end
+
+		return @origin
+	end
+
+
+	### Returns +true+ if the request contains an Origin header whose
+	### URI has a host that's different than its Host: header.
+	def cross_origin?
+		return self.origin && self.headers.host != self.origin.host
+	end
+
+end # module Strelka::HTTPRequest::CORS
+
+

File spec/helpers.rb

+#!/usr/bin/ruby
+# coding: utf-8
+
+BEGIN {
+	require 'pathname'
+	basedir = Pathname.new( __FILE__ ).dirname.parent.parent
+
+	srcdir = basedir.parent
+	strelkadir = srcdir + 'Strelka'
+	strelkalibdir = strelkadir + 'lib'
+
+	$LOAD_PATH.unshift( strelkalibdir.to_s ) unless $LOAD_PATH.include?( strelkalibdir.to_s )
+}
+
+# SimpleCov test coverage reporting; enable this using the :coverage rake task
+if ENV['COVERAGE']
+	$stderr.puts "\n\n>>> Enabling coverage report.\n\n"
+	require 'simplecov'
+	SimpleCov.start do
+		add_filter 'spec'
+		add_group "Needing tests" do |file|
+			file.covered_percent < 90
+		end
+	end
+end
+
+require 'loggability'
+require 'loggability/spechelpers'
+require 'configurability'
+
+require 'rspec'
+require 'mongrel2'
+require 'mongrel2/testing'
+
+require 'strelka'
+require 'strelka/testing'
+
+
+Loggability.format_with( :color ) if $stdout.tty?
+
+
+### RSpec helper functions.
+module Strelka::CORSSpecHelpers
+
+	# Send and receive specs for the test app
+	TEST_SEND_SPEC = 'tcp://127.0.0.1:9997'
+	TEST_RECV_SPEC = 'tcp://127.0.0.1:9996'
+
+end # Strelka::MetriksSpecHelpers
+
+
+abort "You need a version of RSpec >= 2.6.0" unless defined?( RSpec )
+
+### Mock with RSpec
+RSpec.configure do |config|
+	include Strelka::Constants
+	include Strelka::CORSSpecHelpers
+
+	config.run_all_when_everything_filtered = true
+	config.filter_run :focus
+	config.order = 'random'
+	config.mock_with( :rspec ) do |mock|
+		mock.syntax = :expect
+	end
+
+	config.include( Loggability::SpecHelpers )
+	config.include( Mongrel2::SpecHelpers )
+	config.include( Strelka::Constants )
+	config.include( Strelka::Testing )
+	config.include( Strelka::CORSSpecHelpers )
+end
+
+# vim: set nosta noet ts=4 sw=4:
+

File spec/strelka/app/cors_spec.rb

+#!/usr/bin/env rspec -cfd -b
+
+require_relative '../../helpers'
+
+require 'rspec'
+
+require 'strelka'
+require 'mongrel2/testing'
+require 'strelka/testing'
+require 'strelka/behavior/plugin'
+
+require 'strelka/app/cors'
+
+
+describe Strelka::App::CORS do
+
+	before( :all ) do
+		@request_factory = Mongrel2::RequestFactory.new( route: '/api/v1' )
+	end
+
+
+	it_should_behave_like( "A Strelka Plugin" )
+
+
+	it "adds a method to applications to declare origins that are allowed to access it" do
+		app = Class.new( Strelka::App ) do
+			plugins :cors
+		end
+
+		expect( app ).to respond_to( :allow_origins )
+	end
+
+
+	it "adds the CORS mixin to the request class" do
+		app = Class.new( Strelka::App ) do
+			plugins :cors
+		end
+		app.install_plugins
+
+		response = @request_factory.get( '/api/v1/verify' )
+
+		expect( response ).to respond_to( :cross_origin? )
+	end
+
+
+	describe "an app that " do
+
+		let( :appclass ) do
+			Class.new( Strelka::App ) do
+				plugins :cors
+
+				def initialize( appid='cors-test', sspec=TEST_SEND_SPEC, rspec=TEST_RECV_SPEC )
+					super
+				end
+
+				def handle_request( req )
+					super do
+						res = req.response
+						res.status = HTTP::OK
+						res.content_type = 'text/plain'
+						res.puts "Ran successfully."
+
+						res
+					end
+				end
+			end
+		end
+
+
+		it "doesn't do anything by default" do
+			request = @request_factory.get( '/api/v1/verify' )
+
+			res = appclass.new.handle( request )
+
+			expect( res.headers ).to_not include( :access_control_allow_origin )
+		end
+
+
+		it "allows declaration of a simple public resource" do
+			request = @request_factory.get( '/api/v1/verify' )
+
+			appclass.allow_origins( '*' )
+			res = appclass.new.handle( request )
+
+			expect( res.headers ).to include( :access_control_allow_origin )
+			expect( res.headers.access_control_allow_origin ).to eq( '*' )
+		end
+
+
+		it "allows declaration of a resource that is only accessable from the same origin" do
+			request = @request_factory.get( '/api/v1/verify' )
+
+			appclass.allow_origins( nil )
+			res = appclass.new.handle( request )
+
+			expect( res.headers ).to include( :access_control_allow_origin )
+			expect( res.headers.access_control_allow_origin ).to eq( 'null' )
+		end
+
+
+	end
+
+end
+

File spec/strelka/cors_spec.rb

-#!/usr/bin/env rspec -cfd -b
-
-require_relative '../helpers'
-
-require 'rspec'
-
-require 'strelka'
-require 'mongrel2/testing'
-require 'strelka/testing'
-require 'strelka/behavior/plugin'
-
-require 'strelka/cors'
-
-
-describe Strelka::App::CORS do
-
-	before( :all ) do
-		setup_logging( :fatal )
-		@request_factory = Mongrel2::RequestFactory.new( route: '/api/v1' )
-	end
-
-	after( :all ) do
-		reset_logging()
-	end
-
-
-	it_should_behave_like( "A Strelka::App Plugin" )
-
-
-	it "adds a method to applications to declare origins that are allowed to access it" do
-		app = Class.new( Strelka::App ) do
-			plugins :cors
-		end
-
-		expect( app.allowed_origins ).to eq([])
-
-		app.allowed_origins '*.example.com'
-	end
-
-
-	# The resource sharing policy described by this specification is bound to a particular resource. For the purposes of this section each resource is bound to the following:
-	# 
-	# A list of origins consisting of zero or more origins that are allowed access to the resource.
-	# 
-	# This can include the origin of the resource itself though be aware that requests to cross-origin resources can be redirected back to the resource.
-	# 
-	# A list of methods consisting of zero or more methods that are supported by the resource.
-	# 
-	# A list of headers consisting of zero or more header field names that are supported by the resource.
-	# 
-	# A list of exposed headers consisting of zero or more header field names of headers other than the simple response headers that the resource might use and can be exposed.
-	# 
-	# A supports credentials flag that indicates whether the resource supports user credentials in the request. It is true when the resource does and false otherwise.
-
-	it "adds the Auth mixin to the request class" do
-		app = Class.new( Strelka::App ) do
-			plugins :auth
-		end
-		app.install_plugins
-
-		@request_factory.get( '/api/v1/verify' ).should respond_to( :authenticated? )
-	end
-
-
-	describe "an including App" do
-
-		before( :each ) do
-			@app = Class.new( Strelka::App ) do
-				plugins :auth
-
-				# Stand in for a real AuthProvider
-				@auth_provider = RSpec::Mocks::Mock
-
-				def initialize( appid='auth-test', sspec=TEST_SEND_SPEC, rspec=TEST_RECV_SPEC )
-					super
-				end
-
-				def handle_request( req )
-					super do
-						res = req.response
-						res.status = HTTP::OK
-						res.content_type = 'text/plain'
-						res.puts "Ran successfully."
-
-						res
-					end
-				end
-			end
-		end
-
-		after( :each ) do
-			@app = nil
-		end
-
-
-		it "applies authentication and authorization to every request by default" do
-			app = @app.new
-			req = @request_factory.get( '/api/v1' )
-
-			app.auth_provider.should_receive( :authenticate ).and_return( 'anonymous' )
-			app.auth_provider.should_receive( :authorize )
-
-			res = app.handle( req )
-
-			res.status.should == HTTP::OK
-		end
-
-		it "doesn't have any auth criteria by default" do
-			@app.should_not have_auth_criteria()
-		end
-
-		it "sets the authenticated_user attribute of the request to the credentials of the authenticating user" do
-			app = @app.new
-			req = @request_factory.get( '/api/v1' )
-
-			app.auth_provider.should_receive( :authenticate ).and_return( 'anonymous' )
-			app.auth_provider.should_receive( :authorize ).and_return( true )
-
-			app.handle( req )
-			req.authenticated_user.should == 'anonymous'
-		end
-
-		it "has its configured auth provider inherited by subclasses" do
-			Strelka::App::Auth.configure( :provider => 'basic' )
-			subclass = Class.new( @app )
-			subclass.auth_provider.should == Strelka::AuthProvider::Basic
-		end
-
-		it "has its auth config inherited by subclasses" do
-			subclass = Class.new( @app )
-
-			subclass.positive_auth_criteria.should == @app.positive_auth_criteria
-			subclass.positive_auth_criteria.should_not equal( @app.positive_auth_criteria )
-			subclass.negative_auth_criteria.should == @app.negative_auth_criteria
-			subclass.negative_auth_criteria.should_not equal( @app.negative_auth_criteria )
-			subclass.positive_perms_criteria.should == @app.positive_perms_criteria
-			subclass.positive_perms_criteria.should_not equal( @app.positive_perms_criteria )
-			subclass.negative_perms_criteria.should == @app.negative_perms_criteria
-			subclass.negative_perms_criteria.should_not equal( @app.negative_perms_criteria )
-		end
-
-
-		it "allows auth criteria to be declared with a string" do
-			@app.require_auth_for( '/string' )
-			app = @app.new
-
-			req = @request_factory.get( '/api/v1/string' )
-			app.request_should_auth?( req ).should be_true()
-			req = @request_factory.get( '/api/v1/strong' )
-			app.request_should_auth?( req ).should be_false()
-			req = @request_factory.get( '/api/v1/stri' )
-			app.request_should_auth?( req ).should be_false()
-			req = @request_factory.get( '/api/v1/string/long' )
-			app.request_should_auth?( req ).should be_false()
-		end
-
-		it "allows auth criteria to be declared with a regexp" do
-			@app.require_auth_for( %r{/str[io]} )
-			app = @app.new
-
-			req = @request_factory.get( '/api/v1/stri' )
-			app.request_should_auth?( req ).should be_true()
-			req = @request_factory.get( '/api/v1/stro' )
-			app.request_should_auth?( req ).should be_true()
-			req = @request_factory.get( '/api/v1/string' ) # not right-bound
-			app.request_should_auth?( req ).should be_true()
-			req = @request_factory.get( '/api/v1/string/long' )
-			app.request_should_auth?( req ).should be_true()
-			req = @request_factory.get( '/api/v1/other/string/long' ) # Not left-bound
-			app.request_should_auth?( req ).should be_true()
-			req = @request_factory.get( '/api/v1/chatlog' ) # Not left-bound
-			app.request_should_auth?( req ).should be_false()
-		end
-
-		it "allows auth criteria to be declared with a string and a block" do
-			@app.require_auth_for( 'string' ) do |req|
-				req.verb != :GET
-			end
-
-			app = @app.new
-
-			req = @request_factory.get( '/api/v1/string' )
-			app.request_should_auth?( req ).should be_false()
-			req = @request_factory.post( '/api/v1/string' )
-			app.request_should_auth?( req ).should be_true()
-			req = @request_factory.put( '/api/v1/string' )
-			app.request_should_auth?( req ).should be_true()
-			req = @request_factory.delete( '/api/v1/string' )
-			app.request_should_auth?( req ).should be_true()
-			req = @request_factory.options( '/api/v1/string' )
-			app.request_should_auth?( req ).should be_true()
-		end
-
-		it "allows auth criteria to be declared with a regexp and a block" do
-			@app.require_auth_for( %r{/regexp(?:/(?<username>\w+))?} ) do |req, match|
-				match[:username] ? true : false
-			end
-
-			app = @app.new
-
-			req = @request_factory.get( '/api/v1/regexp' )
-			app.request_should_auth?( req ).should be_false()
-			req = @request_factory.get( '/api/v1/regexp/a_username' )
-			app.request_should_auth?( req ).should be_true()
-			req = @request_factory.get( '/api/v1/regexp/%20not+a+username' )
-			app.request_should_auth?( req ).should be_false()
-		end
-
-		it "allows auth criteria to be declared with just a block" do
-			@app.require_auth_for do |req|
-				path = req.app_path.gsub( %r{^/+|/+$}, '' )
-
-				(
-					path == 'strong' or
-					path =~ %r{^marlon_brando$}i or
-					req.verb == :POST or
-					req.content_type == 'application/x-www-form-urlencoded'
-				)
-			end
-
-			app = @app.new
-
-			req = @request_factory.get( '/api/v1/strong' )
-			app.request_should_auth?( req ).should be_true()
-			req = @request_factory.get( '/api/v1/marlon_brando' )
-			app.request_should_auth?( req ).should be_true()
-			req = @request_factory.post( '/api/v1/somewhere' )
-			app.request_should_auth?( req ).should be_true()
-			req = @request_factory.put( '/api/v1/somewhere' )
-			req.content_type = 'application/x-www-form-urlencoded'
-			app.request_should_auth?( req ).should be_true()
-
-			req = @request_factory.get( '/api/v1/string' )
-			app.request_should_auth?( req ).should be_false()
-			req = @request_factory.get( '/api/v1/marlon_brando/2' )
-			app.request_should_auth?( req ).should be_false()
-			req = @request_factory.put( '/api/v1/somewhere' )
-			app.request_should_auth?( req ).should be_false()
-
-		end
-
-		it "allows negative auth criteria to be declared with a string" do
-			@app.no_auth_for( '/string' )
-			app = @app.new
-
-			req = @request_factory.get( '/api/v1/string' )
-			app.request_should_auth?( req ).should be_false()
-			req = @request_factory.get( '/api/v1/strong' )
-			app.request_should_auth?( req ).should be_true()
-			req = @request_factory.get( '/api/v1/stri' )
-			app.request_should_auth?( req ).should be_true()
-			req = @request_factory.get( '/api/v1/string/long' )
-			app.request_should_auth?( req ).should be_true()
-		end
-
-		it "allows negative auth criteria to be declared with a regexp" do
-			@app.no_auth_for( %r{/str[io]} )
-			app = @app.new
-
-			req = @request_factory.get( '/api/v1/stri' )
-			app.request_should_auth?( req ).should be_false()
-			req = @request_factory.get( '/api/v1/stro' )
-			app.request_should_auth?( req ).should be_false()
-			req = @request_factory.get( '/api/v1/string' ) # not right-bound
-			app.request_should_auth?( req ).should be_false()
-			req = @request_factory.get( '/api/v1/string/long' )
-			app.request_should_auth?( req ).should be_false()
-			req = @request_factory.get( '/api/v1/other/string/long' ) # Not left-bound
-			app.request_should_auth?( req ).should be_false()
-			req = @request_factory.get( '/api/v1/chat' )
-			app.request_should_auth?( req ).should be_true()
-		end
-
-		it "allows negative auth criteria to be declared with a string and a block" do
-			@app.no_auth_for( 'string' ) {|req| req.verb == :GET }
-
-			app = @app.new
-
-			req = @request_factory.get( '/api/v1/string' )
-			app.request_should_auth?( req ).should be_false()
-			req = @request_factory.get( '/api/v1/strong' )
-			app.request_should_auth?( req ).should be_true()
-			req = @request_factory.post( '/api/v1/string' )
-			app.request_should_auth?( req ).should be_true()
-			req = @request_factory.put( '/api/v1/string' )
-			app.request_should_auth?( req ).should be_true()
-			req = @request_factory.delete( '/api/v1/string' )
-			app.request_should_auth?( req ).should be_true()
-			req = @request_factory.options( '/api/v1/string' )
-			app.request_should_auth?( req ).should be_true()
-		end
-
-		it "allows negative auth criteria to be declared with a regexp and a block" do
-			@app.no_auth_for( %r{/regexp(?:/(?<username>\w+))?} ) do |req, match|
-				match[:username] == 'guest'
-			end
-
-			app = @app.new
-
-			req = @request_factory.get( '/api/v1/regexp' )
-			app.request_should_auth?( req ).should be_true()
-			req = @request_factory.get( '/api/v1/regexp/a_username' )
-			app.request_should_auth?( req ).should be_true()
-			req = @request_factory.get( '/api/v1/regexp/%20not+a+username' )
-			app.request_should_auth?( req ).should be_true()
-			req = @request_factory.get( '/api/v1/regexp/guest' )
-			app.request_should_auth?( req ).should be_false()
-		end
-
-		it "allows negative auth criteria to be declared with just a block" do
-			@app.no_auth_for do |req|
-				req.app_path == '/foom' &&
-					req.verb == :GET &&
-					req.headers.accept.include?( 'text/plain' )
-			end
-
-			app = @app.new
-
-			req = @request_factory.get( '/api/v1/foom' )
-			app.request_should_auth?( req ).should be_true()
-			req = @request_factory.post( '/api/v1/foom', :accept => 'text/plain, text/html; q=0.5' )
-			app.request_should_auth?( req ).should be_true()
-			req = @request_factory.get( '/api/v1/foom', :accept => 'text/plain, text/html; q=0.5' )
-			app.request_should_auth?( req ).should be_false()
-
-		end
-
-
-		it "allows perms criteria to be declared with a string" do
-			@app.require_perms_for( '/string', :stringperm )
-			app = @app.new
-
-			req = @request_factory.get( '/api/v1/string' )
-			app.required_perms_for( req ).should == [ :stringperm ]
-			req = @request_factory.get( '/api/v1/strong' )
-			app.required_perms_for( req ).should == []
-		end
-
-		it "allows perms criteria to be declared with a regexp" do
-			@app.require_perms_for( %r{^/admin}, :admin )
-			@app.require_perms_for( %r{/grant}, :grant )
-			app = @app.new
-
-			req = @request_factory.get( '/api/v1/admin' )
-			app.required_perms_for( req ).should == [ :admin ]
-			req = @request_factory.get( '/api/v1/admin/grant' )
-			app.required_perms_for( req ).should == [ :admin, :grant ]
-			req = @request_factory.get( '/api/v1/users' )
-			app.required_perms_for( req ).should == []
-			req = @request_factory.get( '/api/v1/users/grant' )
-			app.required_perms_for( req ).should == [ :grant ]
-		end
-
-		it "allows perms criteria to be declared with a string and a block" do
-			@app.require_perms_for( '/string', :stringperm, :otherperm )
-			@app.require_perms_for( '/string', :rawdata ) do |req|
-				req.headers.accept && req.headers.accept =~ /json/i
-			end
-			app = @app.new
-
-			req = @request_factory.get( '/api/v1/string' )
-			app.required_perms_for( req ).should == [ :stringperm, :otherperm ]
-			req = @request_factory.get( '/api/v1/strong' )
-			app.required_perms_for( req ).should == []
-		end
-
-		it "allows multiple perms criteria for the same path" do
-			@app.no_auth_for( '' ) {|req| req.verb == :GET }
-			@app.require_perms_for %r{.*}, :it_assets_webapp
-			@app.require_perms_for( %r{.*}, :@sysadmin ) {|req, m| req.verb != :GET }
-
-			app = @app.new
-
-			req = @request_factory.get( '/api/v1' )
-			app.required_perms_for( req ).should == [ :it_assets_webapp ]
-			req = @request_factory.post( '/api/v1' )
-			app.required_perms_for( req ).should == [ :it_assets_webapp, :@sysadmin ]
-			req = @request_factory.get( '/api/v1/users' )
-			app.required_perms_for( req ).should == [ :it_assets_webapp ]
-			req = @request_factory.post( '/api/v1/users' )
-			app.required_perms_for( req ).should == [ :it_assets_webapp, :@sysadmin ]
-		end
-
-		it "allows perms criteria to be declared with a regexp and a block" do
-			userclass = Class.new do
-				def self::[]( username )
-					self.new(username)
-				end
-				def initialize( username ); @username = username; end
-				def is_admin?
-					@username == 'madeline'
-				end
-			end
-			@app.require_perms_for( %r{^/user}, :admin )
-			@app.require_perms_for( %r{^/user(/(?<username>\w+))?}, :superuser ) do |req, match|
-				user = userclass[ match[:username] ]
-				user.is_admin?
-			end
-			app = @app.new
-
-			req = @request_factory.get( '/api/v1/user' )
-			app.required_perms_for( req ).should == [ :admin ]
-			req = @request_factory.get( '/api/v1/user/jzero' )
-			app.required_perms_for( req ).should == [ :admin ]
-			req = @request_factory.get( '/api/v1/user/madeline' )
-			app.required_perms_for( req ).should == [ :admin, :superuser ]
-		end
-
-		it "allows perms the same as the appid to be declared with just a block" do
-			@app.require_perms_for do |req|
-				req.verb != :GET
-			end
-			app = @app.new
-
-			req = @request_factory.get( '/api/v1/accounts' )
-			app.required_perms_for( req ).should == []
-			req = @request_factory.post( '/api/v1/accounts', '' )
-			app.required_perms_for( req ).should == [ :auth_test ]
-			req = @request_factory.put( '/api/v1/accounts/1', '' )
-			app.required_perms_for( req ).should == [ :auth_test ]
-		end
-
-		it "allows negative perms criteria to be declared with a string" do
-			@app.no_perms_for( '/string' )
-			app = @app.new
-
-			req = @request_factory.get( '/api/v1/string' )
-			app.required_perms_for( req ).should be_empty()
-			req = @request_factory.get( '/api/v1/strong' )
-			app.required_perms_for( req ).should == [ :auth_test ] # default == appid
-		end
-
-		it "allows negative perms criteria to be declared with a regexp" do
-			@app.no_perms_for( %r{^/signup} )
-			app = @app.new
-
-			req = @request_factory.get( '/api/v1/signup' )
-			app.required_perms_for( req ).should be_empty()
-			req = @request_factory.get( '/api/v1/signup/reapply' )
-			app.required_perms_for( req ).should be_empty()
-			req = @request_factory.get( '/api/v1/index' )
-			app.required_perms_for( req ).should == [ :auth_test ]
-		end
-
-		it "allows negative perms criteria to be declared with a string and a block" do
-			@app.no_perms_for( '/' ) do |req|
-				req.verb == :GET
-			end
-			app = @app.new
-
-			req = @request_factory.get( '/api/v1' )
-			app.required_perms_for( req ).should be_empty()
-			req = @request_factory.post( '/api/v1' )
-			app.required_perms_for( req ).should == [ :auth_test ] # default == appid
-			req = @request_factory.get( '/api/v1/users' )
-			app.required_perms_for( req ).should == [ :auth_test ]
-		end
-
-		it "allows negative perms criteria to be declared with a regexp and a block" do
-			@app.no_perms_for( %r{^/collection/(?<collname>[^/]+)} ) do |req, match|
-				public_collections = %w[degasse ione champhion]
-				collname = match[:collname]
-				public_collections.include?( collname )
-			end
-			app = @app.new
-
-			req = @request_factory.get( '/api/v1/collection' )
-			app.required_perms_for( req ).should == [ :auth_test ]
-			req = @request_factory.get( '/api/v1/collection/degasse' )
-			app.required_perms_for( req ).should be_empty()
-			req = @request_factory.get( '/api/v1/collection/ione' )
-			app.required_perms_for( req ).should be_empty()
-			req = @request_factory.get( '/api/v1/collection/champhion' )
-			app.required_perms_for( req ).should be_empty()
-			req = @request_factory.get( '/api/v1/collection/calindra' )
-			app.required_perms_for( req ).should == [ :auth_test ]
-		end
-
-		it "allows negative perms criteria to be declared with just a block" do
-			@app.no_perms_for do |req|
-				intranet = IPAddr.new( '10.0.0.0/8' )
-				intranet.include?( req.remote_ip )
-			end
-			app = @app.new
-
-			req = @request_factory.get( '/api/v1/collection', x_forwarded_for: '10.0.1.68' )
-			app.required_perms_for( req ).should be_empty()
-			req = @request_factory.get( '/api/v1/collection', x_forwarded_for: '192.0.43.10' )
-			app.required_perms_for( req ).should == [ :auth_test ]
-		end
-
-
-		context "that has positive auth criteria" do
-
-			before( :each ) do
-				@app.require_auth_for( '/onlyauth' )
-				@app.require_auth_for( '/both' )
-			end
-
-			context "and positive perms criteria" do
-
-				before( :each ) do
-					@app.require_perms_for( '/both' )
-					@app.require_perms_for( '/onlyperms' )
-				end
-
-				it "authorizes a request that only matches the perms criteria" do
-					req = @request_factory.get( '/api/v1/onlyperms' )
-
-					app = @app.new
-					app.auth_provider.should_not_receive( :authenticate )
-					app.auth_provider.should_receive( :authorize )
-
-					app.handle( req )
-				end
-
-				it "authenticates a request that only matches the auth criteria" do
-					req = @request_factory.get( '/api/v1/onlyauth' )
-
-					app = @app.new
-					app.auth_provider.should_receive( :authenticate )
-					app.auth_provider.should_not_receive( :authorize )
-
-					app.handle( req )
-				end
-
-				it "authenticates and authorizes a request that matches both" do
-					req = @request_factory.get( '/api/v1/both' )
-
-					app = @app.new
-					app.auth_provider.should_receive( :authenticate )
-					app.auth_provider.should_receive( :authorize )
-
-					app.handle( req )
-				end
-
-				it "doesn't do either for a request that doesn't match either" do
-					req = @request_factory.get( '/api/v1/neither' )
-
-					app = @app.new
-					app.auth_provider.should_not_receive( :authenticate )
-					app.auth_provider.should_not_receive( :authorize )
-
-					app.handle( req )
-				end
-
-			end
-
-			context "and negative perms criteria" do
-
-				before( :each ) do
-					@app.no_perms_for( '/both' )
-					@app.no_perms_for( '/onlyperms' )
-				end
-
-				it "doesn't do either for a request that only matches the perms criteria" do
-					req = @request_factory.get( '/api/v1/onlyperms' )
-
-					app = @app.new
-					app.auth_provider.should_not_receive( :authenticate )
-					app.auth_provider.should_not_receive( :authorize )
-
-					app.handle( req )
-				end
-
-				it "authenticates and authorizes a request that only matches the auth criteria"do
-					req = @request_factory.get( '/api/v1/onlyauth' )
-
-					app = @app.new
-					app.auth_provider.should_receive( :authenticate )
-					app.auth_provider.should_receive( :authorize )
-
-					app.handle( req )
-				end
-
-				it "only authenticates a request that matches both" do
-					req = @request_factory.get( '/api/v1/both' )
-
-					app = @app.new
-					app.auth_provider.should_receive( :authenticate )
-					app.auth_provider.should_not_receive( :authorize )
-
-					app.handle( req )
-				end
-
-				it "only authorizes a request that doesn't match either" do
-					req = @request_factory.get( '/api/v1/neither' )
-
-					app = @app.new
-					app.auth_provider.should_not_receive( :authenticate )
-					app.auth_provider.should_receive( :authorize )
-
-					app.handle( req )
-				end
-
-			end
-
-		end
-
-
-		context "that has negative auth criteria" do
-
-			before( :each ) do
-				@app.no_auth_for( '/onlyauth' )
-				@app.no_auth_for( '/both' )
-			end
-
-			context "and positive perms criteria" do
-
-				before( :each ) do
-					@app.require_perms_for( '/both' )
-					@app.require_perms_for( '/onlyperms' )
-				end
-
-				it "authenticates and authorizes a request that only matches the perms criteria" do
-					req = @request_factory.get( '/api/v1/onlyperms' )
-
-					app = @app.new
-					app.auth_provider.should_receive( :authenticate )
-					app.auth_provider.should_receive( :authorize )
-
-					app.handle( req )
-				end
-
-				it "doesn't do either for a request that only matches the auth criteria" do
-					req = @request_factory.get( '/api/v1/onlyauth' )
-
-					app = @app.new
-					app.auth_provider.should_not_receive( :authenticate )
-					app.auth_provider.should_not_receive( :authorize )
-
-					app.handle( req )
-				end
-
-				it "authorizes a request that matches both" do
-					req = @request_factory.get( '/api/v1/both' )
-
-					app = @app.new
-					app.auth_provider.should_not_receive( :authenticate )
-					app.auth_provider.should_receive( :authorize )
-
-					app.handle( req )
-				end
-
-				it "authenticates a request that doesn't match either" do
-					req = @request_factory.get( '/api/v1/neither' )
-
-					app = @app.new
-					app.auth_provider.should_receive( :authenticate )
-					app.auth_provider.should_not_receive( :authorize )
-
-					app.handle( req )
-				end
-
-			end
-
-			context "and negative perms criteria" do
-
-				before( :each ) do
-					@app.no_perms_for( '/both' )
-					@app.no_perms_for( '/onlyperms' )
-				end
-
-				it "authenticates for a request that only matches the perms criteria" do
-					req = @request_factory.get( '/api/v1/onlyperms' )
-
-					app = @app.new
-					app.auth_provider.should_receive( :authenticate )
-					app.auth_provider.should_not_receive( :authorize )
-
-					app.handle( req )
-				end
-
-				it "authorizes a request that only matches the auth criteria" do
-					req = @request_factory.get( '/api/v1/onlyauth' )
-
-					app = @app.new
-					app.auth_provider.should_not_receive( :authenticate )
-					app.auth_provider.should_receive( :authorize )
-
-					app.handle( req )
-				end
-
-				it "doesn't do either for a request that matches both" do
-					req = @request_factory.get( '/api/v1/both' )
-
-					app = @app.new
-					app.auth_provider.should_not_receive( :authenticate )
-					app.auth_provider.should_not_receive( :authorize )
-
-					app.handle( req )
-				end
-
-				it "authenticates and authorizes a request that doesn't match either" do
-					req = @request_factory.get( '/api/v1/neither' )
-
-					app = @app.new
-					app.auth_provider.should_receive( :authenticate )
-					app.auth_provider.should_receive( :authorize )
-
-					app.handle( req )
-				end
-
-			end
-
-		end
-
-
-		context "that has overlapping perms criteria" do
-
-			before( :each ) do
-				@app.require_perms_for( %r{^/admin.*}, :admin )
-				@app.require_perms_for( %r{^/admin/upload.*}, :upload )
-			end
-
-			it "authorizes with a union of the permissions of both of the criteria" do
-				req = @request_factory.get( '/api/v1/admin/upload' )
-
-				app = @app.new
-				app.auth_provider.stub!( :authenticate ).and_return( :credentials )
-				app.auth_provider.should_receive( :authorize ).with( :credentials, req, [:admin, :upload] )
-
-				app.handle( req )
-			end
-
-		end
-
-	end
-
-end
-