#!/usr/bin/env ruby # # Welcome to the OpenID protect CGI script. This is designed to make # directories on your webserver availble only to OpenID users. It only # requires that you have Ruby, the Ruby OpenID library and the ability # to run CGI scripts on your server. # # This code is freely available under the BSD license. USE AT YOUR OWN RISK. # Copyright 2006 JanRain Inc. by Brian Ellin - brianellin at gmail.com # # `-: # `-/oo+ # ./ooooo+ # -oooooo+ # -oooooo+ # -oooooo+ # :oooooo+` # `````````:oooooo+.```````` # `````````````:oooooo+`````````````` ```` # ``````````` -oooooo+ `````````````` # `````````` -oooooo+ ``````````` # ```````` -oooooo+ ````````````` # ```````` -oooooo+ # ```````` -oooooo+ # ```````` -oooooo+ # ```````` -oooooo+ # ```````` -oooooo+ # ````````` -oooooo+ # `````````` -oooooo+ # ````````````` -oooooo+ # `````````````:oooo/-` # ````````:+:.` # ` # Installation. # # Put this cgi script somehwere visible, and chmod +x it. Make sure you # have ruby >= 1.8.4 installed and the latest version of the ruby-openid # library installed. The library is available at: # # http://www.openidenabled.com/openid/libraries/ruby # # Cofiguration options. # # 1) RESOURCES. The RESOURCES variable below contains a an Array of Arrays # describing the directories you'd like to make visible to your users. # The first element is a human-readable name of the dir, second is a brief # description, and third is the full path to the directory. You may add # as many resources as you like. RESOURCES = [ ['Stuff', 'Some stuff to share', '/path/to/stuff'] ] # # 2) USERS. The USERS variable below is an Array of URLS of openid users # who may access the RESOURCES. Verified users who are not in the array # will not have access to your resources. You may allow anyone with an # openid to access your resources by setting this variable to nil. USERS = ['http://brianellin.com/'] # 3) The HEADER is what users see at the top of the page. HEADER = "Protected by OpenID. " # 4) OpenID Store Location. OpenID needs to store some infomation between # requests, and this data needs to live in a secure directory. It should # exist in a place that is not readable by other users of your system. STORE_DIR = '/tmp/openid-store' # 5) LOG_FILE. This variable contains the file which events will be logged to. LOG_FILE = '/dev/null' # 6) WEBMASTER. Email address or string indicating how to contact the person # running the server. WEBMASTER = 'brianellin at gmail.com' # 7) APP_URL. The full url of this cgi script. APP_URL = consumer_url = 'http://brianellin.com/cgi-bin/safe.cgi' ############################################################### # Don't modify code below unless you know what you are doing! # ############################################################### require 'cgi' require 'cgi/session' require 'pathname' begin require 'rubygems' require_gem 'ruby-openid' rescue LoadError require 'openid' end # some common mime type defs mime = < '302', 'location' => url}) exit(0) end end # extend FileStore to use Marshal so it can store interesting objects other # than strings. We need to store a real object in the session state which # represents the yadis discovery. class MarshallingFileStore < CGI::Session::FileStore def restore unless @hash begin f = File.open(@path, 'r') f.flock(File::LOCK_SH) @hash = Marshal.load(f) ensure f.close unless f.nil? end end @hash end def update return unless @hash begin f = File.open(@path, File::CREAT|File::TRUNC|File::RDWR, 0600) f.flock File::LOCK_EX Marshal.dump(@hash, f) ensure f.close unless f.nil? end end end # the CGI object provides an incompatabile interface w/ the # the OpenID library query object interface. This method maps # the cgi object to a regular hash object. def query_from_cgi(cgi) query = {} cgi.keys.each {|k| query[k] = cgi[k]} return query end # setup cgi = CGI.new('html4') session = CGI::Session.new(cgi, 'session_key' => 'openid_safe', 'session_prefix' => 'openid_session', 'database_manager' => MarshallingFileStore) store = OpenID::FilesystemStore.new(STORE_DIR) consumer = OpenID::Consumer.new(session, store) logout = '' def burl(args) args['action'] = 'browse' OpenID::Util.append_args(APP_URL, args) end def barf(msg) print msg exit(0) end def logged_in_content content = 'You have access to:
' RESOURCES.each do |name, desc, path| link = OpenID::Util.append_args(APP_URL, {'action'=>'browse', 'name' => name}) content += "
#{name}
#{desc}
" end content += '
' end def log(msg) $log.puts(Time.now.to_s + ' ' + msg) end # dispatch case cgi['action'] when 'login' request = consumer.begin(cgi['openid_url']) return_to = OpenID::Util.append_args(consumer_url, {'action' => 'openid-response'}) redirect_url = request.redirect_url(consumer_url, return_to) cgi.redirect(redirect_url) when 'openid-response' query = query_from_cgi(cgi) response = consumer.complete(query) case response.status when OpenID::SUCCESS openid_url = response.identity_url allowed = USERS.find {|u| OpenID::Util.urls_equal?(u, openid_url)} if USERS.nil? or allowed session['openid'] = openid_url log("VERIFIED - #{openid_url}") cgi.redirect(consumer_url) else log("VERIFIED BUT NOT ALLOWED- #{openid_url}") page = "Sorry, #{openid_url}. Your identity has been verified, but I don't trust you with my data.

You may contact me: #{WEBMASTER}

"+cgi.a(APP_URL) {'Home'} end when OpenID::FAILURE page = "Unable to verify your idenity." when OpenID::CANCEL page = "Verification cancelled." else page = "Got a bogus response from the OpenID server." end when 'logout' session.delete cgi.redirect(consumer_url) when 'browse' unless session['openid'] cgi.redirect(APP_URL) else logout = cgi.a(OpenID::Util.append_args(consumer_url, {'action'=>'logout'})) {'Logout'} end suffix = cgi['suffix'] # someone is trying to do bad things if suffix.include?('..') or suffix.index('/') == 0 barf('play nice!') end name, desc, path = hashed_resources[cgi['name']] full_path = Pathname.new(path) full_path += Pathname.new(suffix) if suffix if File.directory?(full_path) page = cgi.a(APP_URL) {'Home'} +' - '+ cgi.a(burl('name'=>name)) {name} stack = [] suffix.split('/').each do |s| stack << s page += ' / ' + cgi.a(burl('name'=>name,'suffix'=>stack.join('/'))) {s} end page += cgi.br*2 begin Dir.foreach(full_path) do |f| unless ['.','..'].member?(f) if suffix and suffix != '' slink = suffix+'/'+f else slink = f end url = burl('name'=>name,'suffix'=>slink) is_dir = File.directory?(full_path + f) ? '/' : '' page += "\n#{f}#{is_dir}
\n" end end rescue Errno::EACCES page += 'Sorry, you cannot see inside here.' end else headers = { 'status' => 'OK', 'content-disposition' => 'attachment; filename='+suffix, 'content-length' => File.stat(full_path).size.to_s } ext = suffix.split('.')[-1] headers['content-type'] = mimes[ext] if mimes[ext] log("DOWNLOAD - #{session['openid']} - #{name} - #{suffix}") # set the headers print cgi.header(headers) # stream the file... this should work for files of very large sized # without bringing down the server File.open(full_path, 'r') do |f| data = f.read(4096) while data print data # print has no \n at the end, puts does data = f.read(4096) end end exit(0) end else if session['openid'] logout = cgi.a(OpenID::Util.append_args(consumer_url, {'action'=>'logout'})) {'Logout'} page = logged_in_content else page = "
OpenID:
" end end session.close # render page cgi.out { cgi.html { cgi.head{ cgi.title {'Protected by OpenID'} } + \ cgi.body { HEADER + logout + cgi.br*2 + page } } }