Hi, 
 
this is my solution for SSO for Zope by accepting SAP-SSO-Ticket. 
 
SAP-SSO-Tickets are Cookies named MYSAPSSO2. They contain 
SAP-PortalUserName, SAP-Username, Validate-Time of the ticket and a signed 
signature by the issueing SAP-System. 
 
Since we currently use CookieCrumbler and LDAPUserFolder it was my goal to  
let the CookieCrumbler take the MYSAPSSO2 Cookie from the Request, let it 
be validated by an external ticket verification service, store the 
validated TicketInfo in the SESSION variable and let LDAPUserFolder load 
the trusted PortalUser with roles from the LDAP-Directory. 
 
Any comments or security discussion is welcome. 
 
Zope 2.7.6, CookieCrumbler 1.2, LDAPUserFolder 2.5 
 
Regards, 
Dirk 

-- 
Geschenkt: 3 Monate GMX ProMail gratis + 3 Ausgaben stern gratis
++ Jetzt anmelden & testen ++ http://www.gmx.net/de/go/promail ++
diff -uNr CookieCrumbler/CookieCrumbler.py CookieCrumbler-dirk/CookieCrumbler.py
--- CookieCrumbler/CookieCrumbler.py	Mon Jun 14 18:34:36 2004
+++ CookieCrumbler-dirk/CookieCrumbler.py	Mon Jun 27 22:53:17 2005
@@ -18,6 +18,7 @@
 from base64 import encodestring, decodestring
 from urllib import quote, unquote
 import sys
+import md5
 
 from Acquisition import aq_inner, aq_parent
 from DateTime import DateTime
@@ -29,6 +30,8 @@
 from ZPublisher.HTTPRequest import HTTPRequest
 from OFS.Folder import Folder
 
+from urllib2 import urlopen, Request
+
 try:
     from zExceptions import Redirect
 except ImportError:
@@ -40,6 +43,7 @@
 ATTEMPT_NONE = 0       # No attempt at authentication
 ATTEMPT_LOGIN = 1      # Attempt to log in
 ATTEMPT_RESUME = 2     # Attempt to resume session
+ATTEMPT_TICKET = 3     # Attempt to authenticate via sso-ticket
 
 ModifyCookieCrumblers = 'Modify Cookie Crumblers'
 ViewManagementScreens = Permissions.view_management_screens
@@ -83,6 +87,10 @@
                     'label':'Use cookie paths to limit scope'},
                    {'id':'cache_header_value', 'type': 'string', 'mode':'w',
                     'label':'Cache-Control header value'},
+                   {'id':'mysap_cookie', 'type': 'string', 'mode':'w',
+                    'label':'Authentication ticket cookie name'},
+                   {'id':'ticket_verifier_url', 'type': 'string', 'mode':'w',
+                    'label':'Ticket Verifier URL'},
                    )
 
     auth_cookie = '__ac'
@@ -94,6 +102,8 @@
     logout_page = 'logged_out'
     local_cookie_path = 0
     cache_header_value = 'no-cache'
+    mysap_cookie = 'MYSAPSSO2'
+    ticket_verifier_url = 'http://localhost:4080/verify'
 
     security.declarePrivate('delRequestVar')
     def delRequestVar(self, req, name):
@@ -137,6 +147,24 @@
     def defaultExpireAuthCookie(self, resp, cookie_name):
         resp.expireCookie(cookie_name, path=self.getCookiePath())
 
+
+    security.declarePrivate('verifyTicket')
+    def verifyTicket( self, ticket ):
+        """ Verify Ticket with external verification service """
+
+        req = Request( self.ticket_verifier_url, data=ticket )
+        req.add_header( 'Cookie', '%s=%s;' % (self.mysap_cookie, ticket) )
+
+        try:
+            resp = urlopen(req)
+            result = resp.read()
+            print result
+            return TicketInfo(result)
+        except Exception, ex:
+            print ex
+            return None
+
+
     security.declarePrivate('modifyRequest')
     def modifyRequest(self, req, resp):
         """Copies cookie-supplied credentials to the basic auth fields.
@@ -162,11 +190,41 @@
                 # created it.  The user must be using basic auth.
                 raise CookieCrumblerDisabled
 
-            if req.has_key(self.pw_cookie) and req.has_key(self.name_cookie):
+	    ticket_info = None
+
+	    if req.has_key(self.mysap_cookie) and self.ticket_verifier_url:
+		cookie_val = req.get(self.mysap_cookie)
+		m = md5.new()
+		m.update(cookie_val)
+		cookie_hash = m.digest()
+
+		#print cookie_val
+
+		if req.SESSION.has_key(cookie_hash):
+		    #print "get ticket_info %s" % cookie_hash
+		    ticket_info = req.SESSION.get(cookie_hash)
+		else:
+		    ticket_info = self.verifyTicket(cookie_val)
+
+		    if ticket_info:
+			#print "set ticket_info %s" % cookie_hash
+		     	req.SESSION.set(cookie_hash, ticket_info)
+
+	    if ticket_info:
+		    name = ticket_info.getPortalUser()
+                    ac = encodestring('%s:__ssoticket__=%s' % (name,cookie_hash)).rstrip()
+                    req._auth = 'Basic %s' % ac
+                    resp._auth = 1
+                    #print req._auth
+                    #print name
+		    attempt = ATTEMPT_TICKET
+
+            elif req.has_key(self.pw_cookie) and req.has_key(self.name_cookie):
                 # Attempt to log in and set cookies.
                 attempt = ATTEMPT_LOGIN
                 name = req[self.name_cookie]
                 pw = req[self.pw_cookie]
+		if pw[:13] == '__ssoticket__': pw = None
                 ac = encodestring('%s:%s' % (name, pw)).rstrip()
                 req._auth = 'Basic %s' % ac
                 resp._auth = 1
@@ -221,12 +279,13 @@
             return
 
         if (self.unauth_page or
-            attempt == ATTEMPT_LOGIN or attempt == ATTEMPT_NONE):
+            attempt in [ATTEMPT_LOGIN, ATTEMPT_NONE, ATTEMPT_TICKET]):
             # Modify the "unauthorized" response.
             req._hold(ResponseCleanup(resp))
             resp.unauthorized = self.unauthorized
             resp._unauthorized = self._unauthorized
-        if attempt != ATTEMPT_NONE:
+
+        if attempt != ATTEMPT_NONE and attempt != ATTEMPT_TICKET:
             # Trying to log in or resume a session
             if self.cache_header_value:
                 # we don't want caches to cache the resulting page
@@ -289,6 +348,7 @@
         # Fall through to the standard _unauthorized() call.
         resp._unauthorized()
 
+
     security.declarePublic('getUnauthorizedURL')
     def getUnauthorizedURL(self):
         '''
@@ -372,6 +432,33 @@
         return id
 
 Globals.InitializeClass(CookieCrumbler)
+
+class TicketInfo:
+
+    def __init__(self, string):
+
+        items = string.split(';')
+
+        self.portalUser = items[0]
+        self.user = items[1]
+        self.system = items[2]
+        self.client = items[3]
+        self.valid = items[4]
+
+    def getPortalUser(self):
+        return self.portalUser
+
+    def getUser(self):
+        return self.user
+
+    def getSystem(self):
+        return self.system
+
+    def getClient(self):
+        return self.client
+
+    def isValid(self):
+        return isValid
 
 
 class ResponseCleanup:
diff -uNr LDAPUserFolder/LDAPDelegate.py LDAPUserFolder-dirk/LDAPDelegate.py
--- LDAPUserFolder/LDAPDelegate.py	Thu Apr 14 22:42:23 2005
+++ LDAPUserFolder-dirk/LDAPDelegate.py	Mon Jun 27 22:28:24 2005
@@ -175,7 +175,7 @@
 
     def connect(self, bind_dn='', bind_pwd=''):
         """ initialize an ldap server connection """
-        if bind_dn != '':
+        if bind_dn != '' and bind_pwd != '__ssoticket__':
             user_dn = bind_dn
             user_pwd = bind_pwd or '~'
         elif self.binduid_usage == 1:
diff -uNr LDAPUserFolder/LDAPUserFolder.py LDAPUserFolder-dirk/LDAPUserFolder.py
--- LDAPUserFolder/LDAPUserFolder.py	Sat Apr 16 16:28:16 2005
+++ LDAPUserFolder-dirk/LDAPUserFolder.py	Mon Jun 27 22:54:18 2005
@@ -810,6 +810,14 @@
         if super and name == super.getUserName():
             user = super
         else:
+            if password == '__ssoticket__':
+                #print "password __ssoticket__ not allowed"
+		return None
+            if password[:14] == '__ssoticket__=':
+		password, hash = password.split('=')
+		if not request.SESSION.has_key(hash):
+                    #print "validated TicketInfo is missing"
+		    return None
             user = self.getUser(name, password)
 
         if user is not None:
_______________________________________________
Zope-Dev maillist  -  Zope-Dev@zope.org
http://mail.zope.org/mailman/listinfo/zope-dev
**  No cross posts or HTML encoding!  **
(Related lists - 
 http://mail.zope.org/mailman/listinfo/zope-announce
 http://mail.zope.org/mailman/listinfo/zope )

Reply via email to