#!/usr/bin/env python ##print "Content-type: text/plain" ##print "" ##print "TEMPORARY MAINTENANCE" import sqlite3 import cgi import cgitb cgitb.enable() import pycasv2 import urlparse, urllib import os, sys import random TICKETS_DB = "dat/tickets.db" PARENT_CAS_SERVER = "https://fed.princeton.edu" SERVICE_URL = None # These are the endpoints allowed by CAS v2 POSSIBLE_ENDPOINTS = ["cas_login", "cas_logout", "serviceValidate", "logout"] #http://stackoverflow.com/questions/2506379/add-params-to-given-url-in-python def add_url_params(url, params): """ Add GET params to provided URL being aware of existing. :param url: string of target URL :param params: dict containing requested params to be added :return: string with updated URL >> url = 'http://stackoverflow.com/test?answers=true' >> new_params = {'answers': False, 'data': ['some','values']} >> add_url_params(url, new_params) 'http://stackoverflow.com/test?data=some&data=values&answers=false' """ from json import dumps try: from urllib import urlencode, unquote from urlparse import urlparse, parse_qsl, ParseResult except ImportError: # Python 3 fallback from urllib.parse import ( urlencode, unquote, urlparse, parse_qsl, ParseResult ) # Unquoting URL first so we don't loose existing args url = unquote(url) # Extracting url info parsed_url = urlparse(url) # Extracting URL arguments from parsed URL get_args = parsed_url.query # Converting URL arguments to dict parsed_get_args = dict(parse_qsl(get_args)) # Merging URL arguments dict with new params parsed_get_args.update(params) # Bool and Dict values should be converted to json-friendly values # you may throw this part away if you don't like it :) parsed_get_args.update( {k: dumps(v) for k, v in parsed_get_args.items() if isinstance(v, (bool, dict))} ) # Converting URL argument to proper query string encoded_get_args = urlencode(parsed_get_args, doseq=True) # Creating new parsed result object based on provided with new # URL arguments. Same thing happens inside of urlparse. new_url = ParseResult( parsed_url.scheme, parsed_url.netloc, parsed_url.path, parsed_url.params, encoded_get_args, parsed_url.fragment ).geturl() return new_url class PersistentSet(object): def __init__(self, filename, mutable = False): self.__db_filename = filename self.__conn = None self.__open_db() self.__mutable = mutable def __open_db(self): if type(self.__conn) is type(None): self.__conn = sqlite3.connect(self.__db_filename) self.__conn.row_factory = sqlite3.Row cur = self.__conn.cursor() cur.execute('''CREATE TABLE IF NOT EXISTS pset (id INTEGER PRIMARY KEY, item TEXT NOT NULL, annotation TEXT, ts DATETIME DEFAULT CURRENT_TIMESTAMP, UNIQUE(item))''') cur.execute("CREATE INDEX IF NOT EXISTS idx_item ON pset(item)") cur.execute("CREATE INDEX IF NOT EXISTS idx_ts ON pset(ts)") def __close_db(self): if not type(self.__conn) is type(None): self.__conn.commit() self.__conn.close() self.__conn = None def close(self): self.__close_db() def __simple_value_query(self, query, fields = None, default = None): cur = self.__conn.cursor() if fields: cur.execute(query, fields) else: cur.execute(query) row = cur.fetchone() if row == None or len(row) == 0: return default return row[0] def add(self, item, annotation = ""): if not self.contains(item): cur = self.__conn.cursor() if annotation != "" and annotation != None: query = "INSERT INTO pset(item, annotation) VALUES (?, ?)" cur.execute(query, (item, annotation)) else: query = "INSERT INTO pset(item) VALUES (?)" cur.execute(query, (item,)) self.__conn.commit() def contains(self, item): query = "SELECT annotation FROM pset WHERE item = ?" #query = "SELECT id FROM pset WHERE item = ?" val = self.__simple_value_query(query, fields = (item,)) #return not (val == None) if val != None: if val == "": return True else: return val else: return False def timestamp(self, item): import re from datetime import datetime, timedelta query = "SELECT ts FROM pset WHERE item = ?" val = self.__simple_value_query(query, fields = (item,)) if val != None: # FIXME, hard coding UTC # FIXME: ugly d = datetime(*map(int, re.split('[^\d]', val))) return d + timedelta(hours = 1) def get_calling_uri(query_string = None, append = True): port = os.environ.get("SERVER_PORT", "80") host = os.environ.get("SERVER_NAME", "localhost") query = os.environ.get("QUERY_STRING", "") path = os.environ.get("SCRIPT_NAME", "") scheme = "http" if os.environ.get("HTTPS", "") == "on": scheme = "https" netloc = "%s:%s" % (host, port) if ((scheme == "http" and port == "80") or \ (scheme == "https" and port == "443")): netloc = host if query_string != None: if append: query += "&" + query_string else: query = query_string data = (scheme, netloc, path, query, "") return urlparse.urlunsplit(data) def build_xml_response(username): XML_SUCCESS = """ %(username)s """ XML_INVALID_TICKET = """ Ticket '%(ticket)s' not recognized """ XML_INVALID_SERVICE = """ Ticket &#%(ticket)s' does not match supplied service. The original service was '%(original_service)s' and the supplied service was '%(provided_service)s'. """ return XML_SUCCESS % { 'username' : username } def script_proxycall(form): if "endpoint" in form: endpoint = form["endpoint"].value if endpoint in POSSIBLE_ENDPOINTS: pset = PersistentSet(TICKETS_DB) if endpoint == "cas_login" and "service" in form: target = form["service"].value enc_target = urllib.quote(target, safe='') SERVICE_URL = urllib.quote( get_calling_uri("callback=%s" % enc_target, append = False), safe = '') ticket = "" if "ticket" in form: ticket = form["ticket"].value else: nonce = ''.join([str(random.randint(0, 9)) for i in range(15)]) ticket = "PCT-" + nonce (status, user, cookie) = pycasv2.login( PARENT_CAS_SERVER, SERVICE_URL, secure=0) if user != "": pset.add(ticket, user) cas_url = add_url_params(target, { "ticket" : ticket }) print "Location: %s" % cas_url print "Content-type: text/html" print "Cache-Control: no-cache, no-store, must-revalidate" # print "Pragma: no-cache" # print "Expires: 0" # print """ If your browser does not redirect you, then please follow this link. """ % (cas_url) raise SystemExit elif endpoint == "serviceValidate" and "ticket" in form: ticket = form["ticket"].value ret = pset.contains(ticket) if ret: xml = build_xml_response(ret) print "Content-Type: text/html;charset=UTF-8" print "Cache-Control: no-cache, no-store, must-revalidate" # print "Pragma: no-cache" # print "Expires: 0" # print "Content-Language: en-US" print "Content-Length:", len(xml) print "Connection: close" print "" print xml raise SystemExit else: print "Content-type: text/plain\n\nError 1" elif endpoint == "cas_logout" or endpoint == "logout": cas_url = "https://fed.princeton.edu/cas/logout" print "Location: %s" % cas_url print "Content-type: text/html" print "Cache-Control: no-cache, no-store, must-revalidate" # print "Pragma: no-cache" # print "Expires: 0" # print """ If your browser does not redirect you, then please follow this link. """ % (cas_url) raise SystemExit else: print "Content-type: text/plain\n\nError 2" else: print "Content-type: text/plain\n\nError 3" if __name__ == "__main__" and len(sys.argv) > 0 and sys.argv[0] != "": # program main SCRIPT_NAME = ""#os.environ['SCRIPT_URI'] if not SERVICE_URL: SERVICE_URL = SCRIPT_NAME form = cgi.FieldStorage() user = "-1" if "callback" in form: callback = form["callback"].value enc_callback = urllib.quote(callback, safe='') SERVICE_URL = urllib.quote( get_calling_uri("callback=%s" % enc_callback, append = False), safe = '') ticket = form["ticket"].value (status, user, cookie) = pycasv2.login( PARENT_CAS_SERVER, SERVICE_URL, secure=0) if user != "": pset = PersistentSet(TICKETS_DB) pset.add(ticket, user) cas_url = add_url_params(callback, { "ticket" : ticket }) print "Location: %s" % cas_url print "Content-type: text/html" print """ If your browser does not redirect you, then please follow this link. """ % (cas_url) raise SystemExit else: ret = script_proxycall(form) print "Content-type: text/plain" print "" print "Error 4"