Changeset 10394 for main/waeup.cas/trunk/waeup/cas/server.py
- Timestamp:
- 4 Jul 2013, 09:04:41 (11 years ago)
- File:
-
- 1 edited
Legend:
- Unmodified
- Added
- Removed
-
main/waeup.cas/trunk/waeup/cas/server.py
r10351 r10394 2 2 """ 3 3 import os 4 import tempfile 4 import random 5 import time 5 6 from webob import exc, Response 6 7 from webob.dec import wsgify 8 from waeup.cas.authenticators import get_authenticator 9 from waeup.cas.db import ( 10 DB, DBSessionContext, LoginTicket, ServiceTicket, TicketGrantingCookie) 7 11 8 12 template_dir = os.path.join(os.path.dirname(__file__), 'templates') 13 14 RANDOM = random.SystemRandom(os.urandom(1024)) 15 16 #: The chars allowed by protocol specification for tickets and cookie 17 #: values. 18 ALPHABET = ('abcdefghijklmnopqrstuvwxyz' 19 'ABCDEFGHIJKLMNOPQRSTUVWXYZ' 20 '01234567789-') 21 22 23 def get_random_string(length): 24 """Get a random string of length `length`. 25 26 The returned string should be hard to guess but is not 27 neccessarily unique. 28 """ 29 return ''.join([RANDOM.choice(ALPHABET) for x in range(length)]) 30 31 32 def get_unique_string(): 33 """Get a unique string based on current time. 34 35 The returned string contains only chars from `ALPHABET`. 36 37 We try to be unique by using a timestamp in high resolution, so 38 that even tickets created shortly after another should differ. On 39 very fast machines, however, this might be not enough (currently 40 we use 16 decimal places). 41 42 This is fast because we don't have to fetch foreign data sources 43 nor have to do database lookups. 44 45 The returned string will be unique but it won't be hard to guess 46 for people able to read a clock. 47 """ 48 return ('%.16f' % time.time()).replace('.', '-') 49 50 51 def create_service_ticket(user, service=None): 52 """Get a service ticket. 53 54 Ticket length will be 32 chars, randomly picked from `ALPHABET`. 55 """ 56 t_id = 'ST-' + get_random_string(29) 57 return ServiceTicket(t_id, user, service) 58 59 60 def create_login_ticket(): 61 """Create a unique login ticket. 62 63 Login tickets are required to be unique (but not neccessarily hard 64 to guess), according to protocol specification. 65 """ 66 t_id = 'LT-%s' % get_unique_string() 67 return LoginTicket(t_id) 68 69 70 def check_login_ticket(db, lt_string): 71 """Check whether `lt_string` represents a valid login ticket in `db`. 72 """ 73 if lt_string is None: 74 return False 75 q = db.query(LoginTicket).filter(LoginTicket.ticket == lt_string) 76 result = [x for x in q] 77 if result: 78 db.delete(result[0]) 79 return len(result) > 0 80 81 82 def create_tgc_value(): 83 """Get a ticket granting cookie value. 84 """ 85 value = 'TGC-' + get_random_string(128) 86 return TicketGrantingCookie(value) 87 88 89 def set_session_cookie(response, db): 90 """Create a session cookie (ticket granting cookie) on `response`. 91 92 The `db` database is used to make the created cookie value 93 persistent. 94 """ 95 tgc = create_tgc_value() 96 db.add(tgc) 97 response.set_cookie( 98 'cas-tgc', tgc.value, path='/', secure=True, httponly=True) 99 return response 9 100 10 101 … … 26 117 If the database file does not exist, it will be created. 27 118 """ 28 def __init__(self, db _path=None):29 if db_path is None:30 db_path = os.path.join(tempfile.mkdtemp(), 'cas.db')31 self. db_path = db_path119 def __init__(self, db='sqlite:///:memory:', auth=None): 120 self.db_connection_string = db 121 self.db = DB(self.db_connection_string) 122 self.auth = auth 32 123 33 124 @wsgify 34 125 def __call__(self, req): 35 if req.path in ['/login', '/validate', '/logout']: 36 return getattr(self, req.path[1:])(req) 126 with DBSessionContext(): 127 if req.path in ['/login', '/validate', '/logout']: 128 return getattr(self, req.path[1:])(req) 37 129 return exc.HTTPNotFound() 38 130 131 def _get_template(self, name): 132 path = os.path.join(template_dir, name) 133 if os.path.isfile(path): 134 return open(path, 'r').read() 135 return None 136 39 137 def login(self, req): 40 return Response( 41 open(os.path.join(template_dir, 'login.html'), 'r').read()) 138 service = req.POST.get('service', req.GET.get('service', None)) 139 service_field = '' 140 username = req.POST.get('username', None) 141 password = req.POST.get('password', None) 142 valid_lt = check_login_ticket(self.db, req.POST.get('lt')) 143 if username and password and valid_lt: 144 # act as credentials acceptor 145 cred_ok = self.auth.check_credentials(username, password) 146 if cred_ok: 147 if service is None: 148 # show logged-in screen 149 html = self._get_template('login_successful.html') 150 resp = Response(html) 151 resp = set_session_cookie(resp, self.db) 152 return resp 153 else: 154 # safely redirect to service given 155 st = create_service_ticket(service) 156 self.db.add(st) 157 service = '%s?ticket=%s' % (service, st.ticket) 158 html = self._get_template('login_service_redirect.html') 159 html = html.replace('SERVICE_URL', service) 160 resp = exc.HTTPSeeOther(location=service) 161 resp.cache_control = 'no-store' 162 resp.pragma = 'no-cache' 163 # some arbitrary date in the past 164 resp.expires = 'Thu, 01 Dec 1994 16:00:00 GMT' 165 resp.text = html 166 return resp 167 if service is not None: 168 service_field = ( 169 '<input type="hidden" name="service" value="%s" />' % ( 170 service) 171 ) 172 lt = create_login_ticket() 173 self.db.add(lt) 174 html = self._get_template('login.html') 175 html = html.replace('LT_VALUE', lt.ticket) 176 html = html.replace('SERVICE_FIELD_VALUE', service_field) 177 return Response(html) 42 178 43 179 def validate(self, req): … … 51 187 52 188 def make_cas_server(global_conf, **local_conf): 189 local_conf = get_authenticator(local_conf) 53 190 return CASServer(**local_conf)
Note: See TracChangeset for help on using the changeset viewer.