# Because this helper was trully inspired by this post : # http://www.gittr.com/index.php/archive/sinatra-basic-authentication-selectively-applied/ # and because the code in this post was extracted out of Wink, this file follow the # Wink license : # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in # all copies or substantial portions of the Software. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN # THE SOFTWARE. module Capcode module Helpers module Authorization def auth #:nodoc: if @auth_type == :basic @auth ||= Rack::Auth::Basic::Request.new(env) else @auth ||= Rack::Auth::Digest::Request.new(env) end end def basic_unauthorized!(realm) #:nodoc: response['WWW-Authenticate'] = %(Basic realm="#{realm}") throw :halt, [ 401, {}, 'Authorization Required' ] end def digest_unauthorized!(realm, opaque) #:nodoc: response['WWW-Authenticate'] = %(Digest realm="#{realm}", qop="auth", nonce="#{Rack::Auth::Digest::Nonce.new.to_s}", opaque="#{H(opaque)}") throw :halt, [ 401, {}, 'Authorization Required' ] end def H(data) #:nodoc: ::Digest::MD5.hexdigest(data) end def KD(secret, data) #:nodoc: H([secret, data] * ':') end def A1(auth, password) #:nodoc: [ auth.username, auth.realm, password ] * ':' end def A2(auth) #:nodoc: [ auth.method, auth.uri ] * ':' end def digest(auth, password) #:nodoc: password_hash = H(A1(auth, password)) KD(password_hash, [ auth.nonce, auth.nc, auth.cnonce, "auth", H(A2(auth)) ] * ':') end def digest_authorize( ) #:nodoc: h = @authorizations.call( ) user = auth.username pass = h[user]||false (pass && (digest(auth, pass) == auth.response)) end def basic_authorize(username, password) #:nodoc: h = @authorizations.call( ) user = username pass = h[user]||false (pass == password) end def bad_request! #:nodoc: throw :halt, [ 400, {}, 'Bad Request' ] end def authorized? #:nodoc: request.env['REMOTE_USER'] end # Allow you to add and HTTP Authentication (Basic or Digest) to a controller # # Options : # * :type : Authentication type (:basic or :digest) - default : :basic # * :realm : realm ;) - default : "Capcode.app" # * :opaque : Your secret passphrase. You MUST set it if you use Digest Auth - default : "opaque" # # The block must return a Hash of username => password like that : # { # "user1" => "pass1", # "user2" => "pass2", # # ... # } def http_authentication( opts = {}, &b ) @auth = nil @auth_type = opts[:type]||:basic realm = opts[:realm]||"Capcode.app" opaque = opts[:opaque]||"opaque" @authorizations = b return if authorized? if @auth_type == :basic basic_unauthorized!(realm) unless auth.provided? bad_request! unless auth.basic? basic_unauthorized!(realm) unless basic_authorize(*auth.credentials) else digest_unauthorized!(realm, opaque) unless auth.provided? bad_request! unless auth.digest? digest_unauthorized!(realm, opaque) unless digest_authorize end request.env['REMOTE_USER'] = auth.username end end end end