Changeset 1123

Show
Ignore:
Timestamp:
12/07/07 13:26:22 (3 years ago)
Author:
dbuss
Message:

added checks for audience, missing required claims, extra claims, and other IIW RP compliance checks. Added ability to upgrade/downgrade any message severity generated while processing an infocard. Added functionality to allow more than one object tag to pre present on the login page with each a specifice set of configuration options. Added default user. Spiffied up some documentation and comments, while I was there removed some cruft.

Location:
trunk/rp
Files:
1 added
10 modified

Legend:

Unmodified
Added
Removed
  • trunk/rp/common/python/infocard/event.py

    r1104 r1123  
    1818 
    1919import logging 
     20import re 
    2021 
    2122CRITICAL = logging.CRITICAL                                                                     
     
    2526INFO = logging.INFO                                                        
    2627NOTSET = logging.NOTSET 
     28 
     29config_fields = ['CRITICAL', 'DEBUG', 'ERROR', 'FATAL', 'INFO'] 
    2730 
    2831class IEventLog: 
     
    3740 
    3841class BasicEventLog: 
    39 #       implements(IEventLog) 
    40          
    41         def __init__(self): 
     42        """ Implementation of an event log which allows specific tags to be upgraded 
     43        or downgraded in severity based on configuration 
     44        """ 
     45        config_fields = ['CRITICAL', 'DEBUG', 'ERROR', 'FATAL', 'INFO'] 
     46        def __init__(self, options=None): 
     47                """ 
     48                """ 
    4249                self.events = [] #array of errors, warnings, and other significant events 
     50                self.options = {} 
     51                if options: 
     52                        for field in config_fields: 
     53                                if options.has_key(field): 
     54                                        self.options[field] = options[field] 
     55#                                       self.events.append(Event("setting %s to %s" % (field, options[field]))) 
     56 
     57        def _get_severity(self, severity, tag): 
     58                if tag: 
     59                        for field in config_fields: 
     60                                if self.options.has_key(field) and (re.match(tag, self.options[field]) is not None): 
     61                                        mapped_severity = logging.getLevelName(field) 
     62#                                       self.events.append(Event("tag %s has severity %s instead of %s" % (tag, field, logging.getLevelName(severity)))) 
     63                                        return mapped_severity 
     64                return severity 
     65 
     66        def add_event(self, text, severity=NOTSET, tag=None): 
     67                """ add an event to the event queue, may upgrade or downgrade the  
     68                severity""" 
    4369                 
    44         def add_event(self, text, severity=NOTSET, tag=None): 
    45                 event = Event(text, severity, tag) 
     70                event = Event(text, self._get_severity(severity, tag), tag) 
    4671                self.events.append(event) 
    4772                 
    4873        def has_severity(self, severity): 
     74                """ used to get a count of the number of items of equal or greater  
     75                severity with CRITICAL being the largest, and NOTSET being the lowest. 
     76                Useful to check and see if any actual errors occured, in contrast to  
     77                informational events. 
     78                """ 
    4979                count = 0 
    5080                for event in self.events: 
    5181                        if event.severity >= severity: 
    52                                 count += count 
     82                                count += 1 
     83#               self.events.append(Event("examined %d events and found %d events of %s or greater" % (len(self.events), count, logging.getLevelName(severity)))) 
    5384                return count 
    5485                         
    5586class Event: 
     87        """Structure to trace an indivdual event""" 
    5688        text = None 
    5789        tag = None 
  • trunk/rp/common/python/infocard/infocardlib.py

    r1104 r1123  
    104104OPTION_multivalued_claims = 'multivalued_claims' 
    105105 
    106 """ options which may reduce security by ignoring or downgrading some checks. 
    107     some options may need to be set for interoperablity 
    108 """ 
    109 OPTION_check_required_claims = 'check_required_claims' 
    110  
    111106 
    112107class InfoCardProcessor: 
    113          
     108 
    114109        def __init__(self): 
    115110                self.options = {} 
    116                  
     111 
    117112        def set_decode(self, privateKey, passPhrase = None, isFile=False, isCert = True): 
    118113                if isFile: 
     
    125120                self.options[OPTION_CryptoKeyIsFile] = isFile 
    126121                self.options[OPTION_CryptoKeyIsCert] = isCert 
    127                  
    128                          
     122 
     123 
    129124        def set_claims(self, required, optional = None, multivalued = None): 
    130125                self.options[OPTION_required_claims] = required 
    131126                self.options[OPTION_optional_claims] = optional 
    132127                self.options[OPTION_multivalued_claims] = multivalued 
    133                  
     128 
    134129        def set_options(self, options): 
    135130                for name in options.keys(): 
    136131                        self.options[name] = options[name] 
    137          
    138         def _fix_options(self): 
    139                 if not self._option(OPTION_check_required_claims): 
    140                         self.options[OPTION_check_required_claims] = True 
    141                  
    142                  
     132 
    143133        def _option(self, name): 
    144134                try: 
     
    146136                except Exception: 
    147137                        return None 
    148                          
     138 
    149139        def process_token(self, xmlToken = None): 
    150                 self._fix_options() 
    151                  
    152                 #parse the token 
    153                 secToken = SecToken() 
     140                """Parse the token""" 
     141 
     142                secToken = SecToken(self.options) 
    154143                if secToken: 
    155144                        secToken.process_token(xmlToken, self.options) 
    156                                          
    157                 if secToken and secToken.isValid: 
    158                         #todo check for required claims, or extra claims as feature is enabled 
    159                         if self._option(OPTION_check_required_claims) and self._option(OPTION_required_claims): 
    160                                 rClaims = self._option(OPTION_required_claims).rsplit(' ') 
    161                                 for claim in rClaims: 
    162                                         pass 
    163                                          
     145 
    164146                return secToken 
    165                  
    166                  
     147 
     148 
    167149class SecToken: 
    168  
    169  
    170         def __init__(self): 
     150        """ Class for the parsing and holding of security token data""" 
     151 
     152        def __init__(self, options=None): 
    171153                self.crypted = None                     #Raw security token as delivered from the client 
    172154                self.cryptedDom = None          #Raw security token as delivered from the client 
     
    178160                self.metadata = {}                      #array of misc data about the token, such as  
    179161                                                                        #token type 
    180                 self.eventLog = BasicEventLog() 
    181  
     162                self.eventLog = BasicEventLog(options) 
     163                self.namespace = SAML_1_0_ASSERT_NS  
     164                 
    182165        def _option(self, options, option): 
    183166                try: 
     
    187170                         
    188171        def process_token(self, xmlToken, options): 
     172                """ process the token, until processed no data is present 
     173                """ 
     174                 
    189175                self.crypted = xmlToken 
    190176                try: 
     177                        if not self.crypted or not len(self.crypted): 
     178                                self.eventLog.add_event("Token not supplied or empty",  
     179                                        event.FATAL, 'empty-token') 
    191180                        objenc = xmlseclibs.XMLSecEnc(self.eventLog) 
    192181                        self.cryptedDom = minidom.parseString(self.crypted) 
     
    198187                                objenc.setNode(encData) 
    199188                                objenc.type = encData.getAttribute("Type") 
    200                  
     189 
    201190                                key = None 
    202191                                objKey = objenc.locateKey() 
     
    212201                                                                self._option(options, OPTION_CryptoKeyIsCert)) 
    213202                                                        key = objencKey.decryptKey(self.objKeyInfo) 
    214                  
     203 
    215204                                if (not objKey) or (not key): 
    216205                                        self.eventLog.add_event("Error loading key to handle"\ 
     
    218207                                else: 
    219208                                        objKey.loadKey(key) 
    220                                          
     209 
    221210                                        decrypt = objenc.decryptNode(objKey, False) 
    222211                                        if decrypt: 
     
    227216                                                                event.FATAL, 'parse') 
    228217                                                else: 
    229                                                         #do validity checking 
    230                                                         self.isValid = self._isValid(options) 
    231218                                                        #grab assertions/claims 
    232219                                                        self._setupAssertions(options) 
    233220                                                        #set up metadata about the token for later use 
    234221                                                        self._setupMiscData(options) 
     222                                                        #do validity checking 
     223                                                        self.isValid = self._isValid(options) 
    235224                                        else: 
    236225                                                self.eventLog.add_event('Unable to decrypt token', 
     
    239228                                self.eventLog.add_event("SecToken did not pass all valididation requirements",  
    240229                                        event.ERROR, 'info') 
     230 
    241231                except Exception, why: 
    242232                        trace = traceback.format_exception(*sys.exc_info()) 
     
    246236                                        event.FATAL, 'exception') 
    247237 
    248         """Setup all of the data about the token we can  
    249         """ 
    250238        def _setupMiscData(self, options): 
     239                """Setup all of the data about the token we can, not some data like the 
     240                audience, valid before/after are set in _isValid 
     241                """ 
    251242                rootNode = self.decrypted.documentElement 
    252243                if rootNode: 
     
    257248                        self.metadata[META_MinorVersion] = rootNode.getAttribute('MinorVersion') 
    258249                self.metadata[META_CardKeyHash] = self._getCardKeyHash() 
     250                 
    259251 
    260252        def _isValid(self, options): 
    261253                """ Validate the SAML token 
    262254                """ 
    263                  
     255                data = self.decrypted.documentElement.namespaceURI 
     256                if data == SAML_1_0_ASSERT_NS or data == SAML_1_1_ASSERT_NS: 
     257                        self.namespace = data 
     258                else: 
     259                        self.eventLog.add_event("Unknown SAML namespace encountered: %s " % (data),  
     260                                event.FATAL, 'unknown-namespace') 
     261 
    264262                objXMLSecDSig = xmlseclibs.XMLSecurityDSig(self.eventLog) 
    265263                objDSig = objXMLSecDSig.locateSignature(self.decrypted) 
     
    275273                        self.eventLog.add_event("SAML Reference Validation Failed", 
    276274                                event.ERROR, 'validate-saml-reference') 
    277                         return False 
    278275 
    279276                key = None 
    280277                objKey = objXMLSecDSig.locateKey() 
    281                 if objKey: 
     278                if not objKey: 
     279                        self.eventLog.add_event("Error locating/loading key to handle"\ 
     280                                "Signature", event.ERROR, 'decrypt-loadkey') 
     281                else: 
    282282                        self.objKeyInfo = xmlseclibs.XMLSecEnc.staticLocateKeyInfo(objKey,  
    283283                                                                        objDSig, self.eventLog) 
     
    288288#                                       event.INFO) 
    289289                                pass 
    290                 else: 
    291                         self.eventLog.add_event("Error locating/loading key to handle"\ 
    292                                 "Signature", event.ERROR, 'decrypt-loadkey') 
    293                         return False 
    294  
    295                 retVal = objXMLSecDSig.verify(objKey) 
    296                 if not retVal: 
    297                         self.eventLog.add_event("Unable to verify Signature", 
    298                                 event.ERROR, 'verify-signature') 
    299                         return False 
    300  
    301                 #validate Conditions, both Audience and Time 
     290                        retVal = objXMLSecDSig.verify(objKey) 
     291                        if not retVal: 
     292                                self.eventLog.add_event("Unable to verify Signature", 
     293                                        event.ERROR, 'verify-signature') 
     294 
    302295                xPath = xpath.CreateContext(self.decrypted) 
    303                 xPath.setNamespaces({'mysaml' : SAML_1_0_ASSERT_NS}) 
    304  
    305                 query = '/mysaml:Assertion/mysaml:Conditions/mysaml:AudienceRestrictionCondition/mysaml:Audience' 
    306                 nodelist = xpath.Evaluate(query, context=xPath) 
    307                 for node in nodelist: 
    308                         self.metadata[META_Audience] = node.firstChild.data 
    309  
     296                xPath.setNamespaces({'mysaml' : self.namespace}) 
     297 
     298                #validate Conditions 
     299                #validate time conditions 
    310300                query = '/mysaml:Assertion/mysaml:Conditions' 
    311301                nodelist = xpath.Evaluate(query, context=xPath) 
     
    315305                        self.metadata[META_NotOnOrAfter] = node.getAttribute('NotOnOrAfter') 
    316306 
    317                         if ((not self.metadata[META_NotBefore]) or  
    318                                 (not self.metadata[META_NotOnOrAfter])): 
    319                                 self.eventLog.add_event("Security Token does not contain "\ 
    320                                         "both NotBefore and NotOnOrAfter conditions", event.ERROR,  
    321                                         'validate-time-conditions-present') 
    322                                 return False 
    323307                        if (not checkDateConditions(self.metadata[META_NotBefore], self.metadata[META_NotOnOrAfter])): 
    324308                                currentTime = datetime.datetime.utcnow() 
     
    328312                                        currentTime.isoformat()), 
    329313                                event.ERROR, 'validate-time-conditions-inrange') 
    330                                 return False 
     314 
     315                if ((not self.metadata.has_key(META_NotBefore))  
     316                        or (not self.metadata.has_key(META_NotOnOrAfter))): 
     317                        self.eventLog.add_event("Security Token does not contain "\ 
     318                                "both NotBefore and NotOnOrAfter conditions", event.ERROR,  
     319                                'validate-time-conditions-present') 
     320 
     321                query = '/mysaml:Assertion/mysaml:Conditions/mysaml:AudienceRestrictionCondition/mysaml:Audience' 
     322                nodelist = xpath.Evaluate(query, context=xPath) 
     323                for node in nodelist: 
     324                        self.metadata[META_Audience] = node.firstChild.data 
     325                        if (self._option(options, META_Audience)  
     326                                and self.metadata[META_Audience] != self._option(options, META_Audience)): 
     327                                self.eventLog.add_event("Passed Audience \"%s\" doesn't match "\ 
     328                                "expected Audience \"%s\"" % (self.metadata[META_Audience], 
     329                                        self._option(options, META_Audience)), 
     330                                 event.ERROR, 'mismatched-audience') 
     331                        break 
     332 
     333                if not self.metadata.has_key(META_Audience):  
     334                        self.eventLog.add_event("Security Token does not contain "\ 
     335                                "an Audience restriction.", event.ERROR, 'validate-audience-present') 
     336 
     337                #Check for all required claims 
     338                rClaims = None 
     339                oClaims = None 
     340                if self._option(options, OPTION_optional_claims): 
     341                        oClaims = self._option(options, OPTION_optional_claims).rsplit(' ') 
     342                         
     343                if self._option(options, OPTION_required_claims): 
     344                        rClaims = self._option(options, OPTION_required_claims).rsplit(' ') 
     345                        for claim in rClaims: 
     346                                if claim and len(claim) and not self.getAssertion(claim): 
     347                                        self.eventLog.add_event("Security Token does not contain "\ 
     348                                        "required claim: \"%s\"" % (claim), 
     349                                        event.ERROR, 'validate-required-claims-present') 
     350 
     351                #check for extra claims 
     352                claims = self.getAssertion() 
     353                if claims: 
     354                        for claimName in claims.keys(): 
     355                                if ((not rClaims and not oClaims) 
     356                                        or ((not rClaims or (rClaims and claimName not in rClaims))  
     357                                                and (not oClaims or (oClaims and claimName not in oClaims)))): 
     358                                        self.eventLog.add_event("Security Token contains "\ 
     359                                                "unrequested claim: \"%s\"" % (claimName), 
     360                                                event.ERROR, 'validate-minimal-claims-present') 
     361 
     362                #This check acully enforces the isvalid by ensuring there are no errors 
     363                #Note what is shown in the code as error, info, fatal may not match 
     364                #the current configuration 
    331365                if self.eventLog.has_severity(event.ERROR): 
    332366                        return False 
     
    343377                                mvClaims = temp.rsplit(' ') 
    344378                        xPath = xpath.CreateContext(self.decrypted) 
    345                         xPath.setNamespaces({'mysaml' : SAML_1_0_ASSERT_NS}) 
     379                        xPath.setNamespaces({'mysaml' : self.namespace}) 
    346380                        query = '/mysaml:Assertion/mysaml:AttributeStatement/mysaml:Attribute' 
    347381                        nodelist = xpath.Evaluate(query, context=xPath) 
     
    359393                                                                try: 
    360394                                                                        if nsDict[name]: 
    361 #                                                                               self.eventLog.add_event("Ignoring multi-valued %s/%s : %s" \ 
    362 #                                                                               % (ns, name, value),  
    363 #                                                                               event.INFO, 'parse-assertions') 
     395                                                                                self.eventLog.add_event("Ignoring multi-valued %s/%s : %s" \ 
     396                                                                                        % (ns, name, value),  
     397                                                                                        event.INFO, 'parse-assertions') 
    364398                                                                                #TODO multivalued check 
    365399                                                                                pass 
    366400                                                                except Exception: 
    367401#                                                                       self.eventLog.add_event("Adding %s/%s : %s" \ 
    368 #                                                                       % (ns, name, value),  
    369 #                                                                       event.INFO, 'parse-assertions') 
     402#                                                                               % (ns, name, value),  
     403#                                                                               event.INFO, 'parse-assertions') 
    370404                                                                        nsDict[name] = value 
    371405                                                        except Exception: 
    372406#                                                               self.eventLog.add_event("First NS, adding %s/%s : %s" \ 
    373 #                                                               % (ns, name, value),  
    374 #                                                               event.INFO, 'parse-assertions') 
     407#                                                                       % (ns, name, value),  
     408#                                                                       event.INFO, 'parse-assertions') 
    375409                                                                self.assertions[ns] = {name:value} 
    376410 
     
    392426                        claim = identifier 
    393427 
     428#               self.eventLog.add_event("Looking for ns: \"%s\"   claim: \"%s\" " \ 
     429#                       % (ns, claim),  
     430#                       event.INFO, 'get-assertions') 
     431 
    394432                claims = dict() 
    395433                for nsElem in self.assertions.keys(): 
     434#                       self.eventLog.add_event("Comparing for requested ns: \"%s\"  to : \"%s\" " \ 
     435#                                       % (ns, nsElem),  
     436#                                       event.INFO, 'get-assertions') 
    396437                        if not ns or (ns and ns == nsElem): 
    397438                                nsDict = self.assertions[nsElem] 
     
    400441                                                return nsDict[key] 
    401442                                        elif not claim: 
    402                                                 if ns: 
    403                                                         claims[ns+'//'+key] = nsDict[key] 
     443                                                if ns or (not ns and not claim): 
     444                                                        claims[nsElem+'/'+key] = nsDict[key] 
    404445                                                else: 
    405446                                                        claims[key] = nsDict[key] 
    406447                if claims.items(): 
    407448                        return claims 
    408                 else: 
    409                         return None 
    410449 
    411450 
  • trunk/rp/common/python/infocard/xmlseclibs.py

    r1104 r1123  
    588588                                        " by not including this element your chosen IdP puts you"\ 
    589589                                        " at risk of a brown bag attack!", 
    590                                 event.INFO, 'signature-missing-InclusiveNameSpaces') 
     590                                event.ERROR, 'signature-missing-InclusiveNameSpaces') 
    591591                                 
    592592                if (isinstance(signedDataNode, minidom.Node)): 
  • trunk/rp/trac/infocard_acct/0.11/infocard_acct/db.py

    r1102 r1123  
    5353                db = self.env.get_db_cnx() 
    5454                cursor = db.cursor() 
    55                 self.log.debug('DBAssociationStore:set_association for \"%s\" to \"%s\"', user, cardkeyhash) 
     55#               self.log.debug('DBAssociationStore:set_association for \"%s\" to \"%s\"', user, cardkeyhash) 
    5656                cursor.execute("INSERT INTO cardkey " 
    5757                        "(username,cardkeyhash) " 
     
    6767                db = self.env.get_db_cnx() 
    6868                cursor = db.cursor() 
    69                 self.log.debug('DBAssociationStore:check_association for \"%s\"', cardkeyhash) 
     69#               self.log.debug('DBAssociationStore:check_association for \"%s\"', cardkeyhash) 
    7070                cursor.execute("SELECT username FROM cardkey " 
    7171                                           "WHERE cardkeyhash=%s", (cardkeyhash,)) 
    7272                user = None 
    7373                for row in cursor: 
    74                         self.log.debug('DBAssociationStore:check_association returning \"%s\"', row[0]) 
     74#                       self.log.debug('DBAssociationStore:check_association returning \"%s\"', row[0]) 
    7575                        return row[0] 
    7676                return None 
     
    193193                        self.found_db_version = int(value[0]) 
    194194 
    195                 self.log.debug('InfoCardAccountEnv: Current db version %s, required is %s' \ 
    196                                 % (self.found_db_version, db_default.db_version)) 
     195#               self.log.debug('InfoCardAccountEnv: Current db version %s, required is %s' \ 
     196#                               % (self.found_db_version, db_default.db_version)) 
    197197 
    198198                if self.found_db_version == db_default.db_version: 
  • trunk/rp/trac/infocard_acct/0.11/infocard_acct/groups.py

    r1103 r1123  
    2525from session import SecTokenChangeListener 
    2626 
     27from infocard import infocardlib 
     28 
    2729session_var_name = 'sec_groups' 
    2830 
    2931class SecTokenGroups(Component): 
    3032        """Using Policy to move items from the security token to the session as groups 
    31         evaluated once, then cleared from the cache on logout""" 
     33        evaluated once, then cleared from the cache on logout, 
     34        Also provides permission groups by evaluating what is stored on the session 
     35        """ 
    3236         
    33         implements(SecTokenChangeListener) 
     37        implements(SecTokenChangeListener, IPermissionGroupProvider) 
    3438 
    3539        def __init__(self): 
     
    3943                        self.env.log.debug('SecTokenGroups disabled') 
    4044                        return 
    41                 gDefSets =  self.config.getlist('infocard_acct', 'group_definitions') 
    42                 for gDef in gDefSets: 
    43                         gDef = gDef.strip()  #remove any unsightly spaces 
     45 
    4446#                       self.env.log.debug('SecTokenGroups processing group definition : ' + gDef) 
    4547                        groupRules = {}                  
    4648                        groupRules['name'] = gDef 
    4749                        groupRules['exclusive'] = self.config.getbool(gDef, 'exclusive', False) 
    48                         groupRules['matchEval'] = self.config.get(gDef, 'match_eval', '1') 
    49                         groupRules['ruleEval'] = self.config.get(gDef, 'rule_eval') 
    50                         if gDef and groupRules.has_key('ruleEval'): 
     50                        groupRules['matchExpression'] = self.config.get(gDef, 'match_expresion') 
     51                        groupRules['matchStatement'] = self.config.get(gDef, 'match_statement') 
     52                        groupRules['ruleExpresion'] = self.config.get(gDef, 'rule_expresion') 
     53                        groupRules['ruleStatement'] = self.config.get(gDef, 'rule_statement') 
     54                         
     55                        if gDef and (groupRules.has_key('ruleExpresion')  
     56                                or groupRules.has_key('ruleStatment')): 
    5157                                self.gDefs[gDef] = groupRules 
    5258 
     
    6268                        rule = self.gDefs[ruleName] 
    6369                        try: 
    64                                 if self._does_rule_match(rule, session): 
    65                                         values = self._evalute_rule(rule, session) 
     70                                if self._does_rule_match(rule, secToken): 
     71                                        values = self._evalute_rule(rule, secToken) 
    6672                                        if values: 
    6773                                                self.env.log.debug('SecTokenGroups rule adds : '\ 
     
    7480                                self.env.log.debug('SecTokenGroups error evaluating rule: %s' % (ruleName))  
    7581                         
    76                 if groups: 
    77                         self.env.log.debug('SecTokenGroups groups: ' + ','.join(groups)) 
     82                if groups and groups.count(): 
     83                        self.env.log.debug('SecTokenGroups groups (%d): ' %(groups.count() ) + ','.join(groups)) 
    7884 
    7985                return groups 
     
    8490                        del req.session[session_var_name] 
    8591 
    86         def _does_rule_match(self, rule, session): 
    87                 try: 
    88                         if rule.has_key('matchEval'): 
    89                                 eval(rule['matchEval'], None, session) 
    90                         else: 
    91                                 return True 
    92                 except Exception: 
    93                         return False 
    94          
    95         def _evalute_rule(self, rule, session): 
    96                 if rule and session: 
    97                         try: 
    98                                 if rule.has_key('ruleEval'): 
    99                                         data = eval(rule['ruleEval'], None, session) 
    100                                         return (data,) 
     92        def _does_rule_match(self, rule, secToken): 
     93#               try: 
     94                locals = {'secToken': sectoken} 
     95                if rule.has_key('matchExpresion'): 
     96                        eval(rule['matchExpresion'], None, locals) 
     97                else: 
     98                        return True 
     99#               except Exception: 
     100#                       return False 
     101 
     102        def _evalute_rule(self, rule, secToken): 
     103                if rule and secToken: 
     104#                       try: 
     105                        locals = {'secToken': sectoken} 
     106                        if rule.has_key('ruleExpresion'): 
     107                                data = eval(rule['ruleExpresion'], None, locals) 
     108                                return (data,) 
     109                        elif rule.has_key('ruleStatement'): 
     110                                pass 
    101111#                               else: 
    102112#                                       data = eval('tok_claim_privatepersonalidentifier', None, session) 
    103113#                                       return (data,) 
    104                         except Exception: 
    105                                 pass 
     114#                       except Exception: 
     115#                               pass 
    106116                return None 
    107  
    108  
    109 class SessionGroupProvider(Component): 
    110         """ 
    111         Provides permission groups by evaluating what is stored on the session 
    112         Note, this doesn't change from invocation to invocation so often a cache 
    113         may be used. 
    114         """ 
    115         implements(IPermissionGroupProvider) 
    116117 
    117118        # IPermissionGroupProvider interface 
     
    127128                                "WHERE sid=%s and authenticated=%s and name=%s", (username, int(True), session_var_name)) 
    128129                        for name, value in cursor: 
    129                                 self.env.log.debug('SessionGroupProvider groups: ' + ','.join(value)) 
    130                                 return value.split(' ') 
     130                                if value and len(value): 
     131                                        self.env.log.debug('SecTokenGroups groups: ' + ','.join(value)) 
     132                                        return value.split(' ') 
    131133                except Exception: 
    132134                        pass 
    133                          
     135 
    134136                return [] 
    135137 
     138 
  • trunk/rp/trac/infocard_acct/0.11/infocard_acct/session.py

    r1103 r1123  
    6363        def login(self, req, secToken): 
    6464                """ on login set the email address and name on the session""" 
     65                 
     66#               self.log.debug('SecTokenPreferences setup %s %s' % (req, req.session)) 
    6567                if secToken and req.session: 
    6668                        email = secToken.getAssertion('http://schemas.xmlsoap.org/ws/2005/05/identity/claims/emailaddress') 
     
    7375                                fullname += ' ' + last 
    7476 
    75                         self.log.debug('SecTokenPreferences login  %s, %s' % (email, fullname)) 
    76                          
     77#                       self.log.debug('SecTokenPreferences login:  %s, %s' % (email, fullname)) 
     78 
    7779                        #visit should we override or update the email or just set if not set? 
    7880                        if email and (self.overrideSession or not req.session.has_key('email')): 
    7981                                req.session['email'] = email 
    80                         if fullname and (self.overrideSession or not req.session.has_ye('name')): 
     82                        if fullname and (self.overrideSession or not req.session.has_key('name')): 
    8183                                req.session['name'] = fullname 
    8284 
     
    113115                """On logout we should cleanup all of the garbage so that subsequent  
    114116                sessions don't end up with incorrect data""" 
    115 #               meta = req.session.get('tok_meta') 
     117                meta = req.session.get('tok_meta') 
    116118#               if meta: 
    117119#                       for key in meta.rsplit(' '): 
  • trunk/rp/trac/infocard_acct/0.11/infocard_acct/templates/authenticate.html

    r1102 r1123  
    5555      </form> 
    5656 
    57  
    58           <div py:if="display_infocard" > 
    59                   <h1>Login using InfoCard</h1> 
    60          
    61                   <div class="system-message" py:if="infocard_error"> 
    62                         <h2>Error</h2> 
    63                         <p>${infocard_error}</p> 
    64                   </div> 
    65          
    66                   <form name="infocardLogin" id="infocardLogin" method="post" action=""> 
    67                         <img style="display: block" 
    68                         width="92" height="64" 
    69                         src="${href.chrome('/infocard_acct/images/infocard_92x64.png')}" 
    70                                 alt="InfoCard Login"   
    71                         onClick='image_disable(infocardLogin)'/> 
    72                         <input type="hidden" name="referer" value="${referer}" /> 
    73                         <input py:if="blockID" type="hidden" name="blockID" value="${blockID}" /> 
    74                         <OBJECT type="application/x-informationCard" name="xmlToken"> 
    75                            <PARAM Name="tokenType" Value="urn:oasis:names:tc:SAML:1.0:assertion"/> 
    76                            <!--PARAM Name="tokenType" Value="http://docs.oasis-open.org/wss/oasis-wss-saml-token-profile-1.1#SAMLV1.1"/--> 
    77                            <PARAM py:if="required_claims" Name="requiredClaims"  
    78                                         Value="${required_claims}"> 
    79                            </PARAM> 
    80                            <PARAM py:if="optional_claims" Name="optionalClaims"  
    81                                         Value="${optional_claims}"> 
    82                            </PARAM> 
    83                            <PARAM py:if="issuer" Name="issuer"  
    84                                         Value="${issuer}"> 
    85                            </PARAM> 
    86                            <PARAM py:if="issuerPolicy" Name="issuerPolicy"  
    87                                         Value="${issuerPolicy}"> 
    88                            </PARAM> 
    89                            <PARAM py:if="privacy_policy" Name="privacyUrl"  
    90                                         Value="${privacy_policy}"> 
    91                            </PARAM> 
    92                            <PARAM py:if="privacy_version" Name="privacyVersion"  
    93                                         Value="${privacy_version}"> 
    94                            </PARAM> 
    95                         </OBJECT> 
    96                   </form> 
    97                 </div> 
    98     </div> 
    99          
     57      <div py:for="name in infocards.keys()"> 
     58                <div py:if="display_infocard"  py:with="element = infocards[name]" > 
     59                  <fieldset id="${element.blockID}"> 
     60                          <h1 py:if="element.header_text">${element.header_text}</h1> 
     61                          <p class="help" py:if="element.help_text"> 
     62                                ${element.help_text} 
     63                          </p> 
     64                          <div class="system-message" py:if="infocard_error"> 
     65                                <h2>Error</h2> 
     66                                <p>${infocard_error}</p> 
     67                          </div> 
     68                 
     69                          <form name="${element.blockID}" id="${element.blockID}" method="post" action=""> 
     70                                <img style="display: block" 
     71                                   width="71" height="50" 
     72                                   src="${href.chrome('/infocard_acct/images/infocard_71x50.png')}" 
     73                                   alt="InfoCard Login"   
     74                                   onClick='${element.blockID}.submit()'/> 
     75                                <input type="hidden" name="referer" value="${referer}" /> 
     76                                <input type="hidden" name="blockID" value="${element.blockID}" /> 
     77                                <OBJECT type="application/x-informationCard" name="xmlToken"> 
     78                                   <PARAM Name="tokenType" Value="${element.token_type}"/> 
     79                                   <PARAM py:if="element.required_claims" Name="requiredClaims"  
     80                                                Value="${element.required_claims}"/> 
     81                                   <PARAM py:if="element.optional_claims" Name="optionalClaims"  
     82                                                Value="${element.optional_claims}" /> 
     83                                   <PARAM py:if="element.issuer" Name="issuer"  
     84                                                Value="${element.issuer}" /> 
     85                                   <PARAM py:if="element.issuerPolicy" Name="issuerPolicy"  
     86                                                Value="${element.issuerPolicy}" /> 
     87                                   <PARAM py:if="element.privacy_policy" Name="privacyUrl"  
     88                                                Value="${element.privacy_policy}" /> 
     89                                   <PARAM py:if="element.privacy_version" Name="privacyVersion"  
     90                                                Value="${element.privacy_version}" /> 
     91                                </OBJECT> 
     92                          </form> 
     93                        </fieldset> 
     94                 </div> 
     95           </div> 
     96     </div> 
    10097  </body> 
    10198</html> 
  • trunk/rp/trac/infocard_acct/0.11/infocard_acct/templates/infocard-detail.html

    r1098 r1123  
    88  <head> 
    99    <title>${title}</title> 
    10     <script type="text/javascript"> 
    11     $(document).ready(function() { 
    12         $('#user')[0].focus(); 
    13     }); 
    14     </script> 
    1510  </head> 
    1611 
     
    2722                <thead> 
    2823          <tr> 
    29             <th>label</th><th>description</th><th>severity</th> 
     24            <th>severity</th><th>label</th><th>description</th> 
    3025          </tr> 
    3126        </thead> 
    3227        <tbody> 
     28                <?python 
     29                   import logging 
     30                   def severityName(level): 
     31                      return logging.getLevelName(level) 
     32                ?> 
    3333                 <div py:for="element in events"> 
    3434                        <tr> 
    35                                 <td><pre>${element.severity}</pre></td> 
     35                                <td><pre>${severityName(element.severity)}</pre></td> 
    3636                                <td><pre>${element.tag}</pre></td> 
    3737                                <td><pre>${element.text}</pre></td> 
  • trunk/rp/trac/infocard_acct/0.11/infocard_acct/templates/infocard-session-detail.html

    r1103 r1123  
    88  <head> 
    99    <title>${title}</title> 
    10     <script type="text/javascript"> 
    11     $(document).ready(function() { 
    12         $('#user')[0].focus(); 
    13     }); 
    14     </script> 
    1510  </head> 
    1611 
  • trunk/rp/trac/infocard_acct/0.11/infocard_acct/web_ui.py

    r1103 r1123  
    3535import re 
    3636import time 
     37import urlparse 
    3738 
    3839from trac import perm, util 
     
    5556 
    5657import infocard.xmlseclibs, infocard.infocardlib 
    57 from infocard import infocardlib 
     58from infocard import infocardlib, event 
    5859from infocard.infocardlib import SecToken, InfoCardProcessor 
    5960 
     
    7172        privateKey = '/' 
    7273        privateKeyPassPhrase = '' 
    73         processors = {} 
    7474        mandatoryClaims = ('http://schemas.xmlsoap.org/ws/2005/05/identity/claims/privatepersonalidentifier',) 
    75         displayDebug = False 
    7675 
    7776        def __init__(self): 
    78                 privateKey = PathOption('infocard_acct', 'private_key_path', '/') 
    79                 privateKeyPassPhrase = PathOption('infocard_acct', 'private_key_pass_phrase', '') 
    80                 requiredClaims =  Option('infocard_acct', 'required_claims', '') 
    81                 optionalClaims = Option('infocard_acct', 'optional_claims', '') 
    82                 privacyPolicy = Option('infocard_acct', 'privacy_url', '') 
    83                 privacyPolicyVer = Option('infocard_acct', 'privacy_version', '') 
    84                 issuer = Option('infocard_acct', 'privacy_issuer', '') 
    85  
    86                 self.privateKey = self.env.config['infocard_acct'].get('private_key_path') 
    87                 self.requiredClaims = self.env.config['infocard_acct'].get('required_claims') 
    88                 self.optionalClaims = self.env.config['infocard_acct'].get('optional_claims') 
    89                 self.privacyPolicy = self.env.config['infocard_acct'].get('privacy_url') 
    90                 self.privacyPolicyVer = self.env.config['infocard_acct'].get('privacy_version') 
    91                 self.issuer = self.env.config['infocard_acct'].get('issuer') 
    92                 self.displayDebug = self.config.getbool('infocard_acct', 'debug', False) 
     77                self.processors = {} 
     78 
     79                self.privateKey = self.config.get('infocard_acct', 'private_key_path') 
     80                self.privateKeyPassPhrase = self.config.get('infocard_acct', 'private_key_pass_phrase') 
     81                self.processors['infocard_acct'] = self._process_options('infocard_acct') 
     82                 
     83                #read any sets of infocard options 
     84                Sets =  self.config.getlist('infocard_acct', 'infocard_definitions') 
     85                if Sets: 
     86                        for element in Sets: 
     87                                name = element.strip()  #remove any unsightly spaces 
     88                                self.processors[name] = self._process_options(name) 
     89                 
     90                scheme, host = urlparse.urlparse(self.env.abs_href())[:2] 
     91                if not scheme or not host: 
     92                        self.log.error('Trac configuration for \'base_url\' option '\ 
     93                        'doesn\'t contain host and scheme information.') 
     94                         
     95        def _process_options(self, tag): 
     96                """read a block of options from the configuration and store them in a  
     97                local structure for later use. 
     98                """ 
     99                options = {} 
     100                #uniquely name this block of options using it's tag from trac.ini 
     101                options['blockID'] = tag 
     102 
     103                #process basic options with no defaults 
     104                config_fields = ['required_claims', 'optional_claims',  
     105                        'privacy_url', 'privacy_version', 'issuer', 'associated_user',  
     106                        'Audience'] 
     107                for field in config_fields: 
     108                        data =  self.config.get(tag, field) 
     109                        if data: 
     110                                options[field] = data 
     111 
     112                #process warning levels and debug options, these may result in upgrades 
     113                #or downgrades in error reporting 
     114                for field in event.config_fields: 
     115                        data = self.config.get(tag, field) 
     116                        if data: 
     117                                options[field] = data 
     118 
     119                #process options which have defaults and need to be  
     120                options['debug_page'] = self.config.getbool(tag, 'debug_page', False) 
     121                options['header_text'] = self.config.get(tag, 'header_text', 'InfoCard Login') 
     122                options['help_text'] = self.config.get(tag, 'help_text', 'Use any InfoCard to login to your existing account') 
     123                options['token_type'] = self.config.get(tag, 'token_type', 'urn:oasis:names:tc:SAML:1.0:assertion') 
    93124 
    94125                #append all hard coded mandatory claims to the required claims  
    95                 rClaims = None 
    96                 if self.requiredClaims: 
    97                         rClaims = self.requiredClaims.rsplit(' ') 
    98                 for claim in self.mandatoryClaims: 
    99                         if not rClaims or not (claim in rClaims):   
    100                                 self.requiredClaims = self.requiredClaims + ' ' + claim  
    101  
     126                if options.has_key('required_claims'): 
     127                        options['required_claims'] = options['required_claims'] + " " + " ".join(self.mandatoryClaims) 
     128                else: 
     129                        options['required_claims'] = " " + ' '.join(self.mandatoryClaims) 
     130 
     131                #setup basic infocard processor for duty 
     132                processor = InfoCardProcessor() 
     133                if processor: 
     134                        processor.set_decode(self.privateKey, self.privateKeyPassPhrase, True, False) 
     135                        processor.set_claims( 
     136                                self._get_option_from_options(options, 'required_claims'),  
     137                                self._get_option_from_options(options, 'optional_claims')) 
     138                        processor.set_options(options) 
     139                        options['processorTag'] = processor 
     140#               self.log.debug("For %s required: %s, optional: %s" %  
     141#                       (tag, self._get_option_from_options(options, 'required_claims'),  
     142#                               self._get_option_from_options(options, 'optional_claims'))) 
     143                return options 
     144 
     145        def _was_sent_secure(self, req): 
     146#               self.log.error('was_sent_secure: '+req.abs_href.base) 
     147#               host = req.get_header('Host') 
     148#               if not host: 
     149#                       self.log.error('Request missing Host header, assuming secure') 
     150#                       return True 
     151#               scheme, host = urlparse.urlparse(host)[:2] 
     152#               if scheme == 'https': 
     153                        return True 
     154#               return False 
     155 
     156        def _redirect_secure(self, req): 
     157                """ redirect to absolute url over https for /login """  
     158                url = urlparse.urlparse(self.env.abs_href()) 
     159                if not url.hostname: 
     160                        host = req.get_header('Host') 
     161                else: 
     162                        host = url.hostname 
     163                req.redirect(urlparse.urlunparse(('https', host,  
     164                        url.path, req.path_info, None, None))) 
    102165 
    103166        def authenticate(self, req): 
     167                """called when req.authname is accessed, since this is actually called  
     168                by many processes in trac, it will often be the time when the security 
     169                token is read and processed.  """ 
    104170                #self.log.debug('web_ui:LoginModule:authenticate' ) 
    105171                authnanme = None 
     
    117183 
    118184        def match_request(self, req): 
     185                """See if we handle the request, we watch for login/logout""" 
    119186                if if_enabled(auth.LoginModule.match_request) \ 
    120187                        and ( (re.match(r'/login/?$', req.path_info) is not None) \ 
     
    125192 
    126193        def process_request(self, req): 
    127 #               self.log.debug('web_ui:LoginModule:process_request : '+req.path_info) 
     194                """ handle display of the login page, display of login results,  
     195                and logout requests""" 
     196 
     197#               self.log.debug('web_ui:LoginModule:process_request : %s' %  
     198#                       (req.path_info)) 
    128199                if req.path_info.startswith('/logout'): 
    129200                        self._cleanup_session(req) 
    130201                if req.path_info.startswith('/login'): 
    131202                        if req.authname == 'anonymous': 
     203                                 
     204                                if not self._was_sent_secure(req): 
     205                                        self._redirect_secure(req) 
     206                                 
    132207                                data = { 
    133208                                        'title': 'Login', 
     
    136211                                        'referer': self._referer(req), 
    137212                                        'reset_password_enabled': AccountModule(self.env).reset_password_enabled, 
    138                                         'required_claims': self.requiredClaims, 
    139                                         'optional_claims': self.optionalClaims, 
    140                                         'privacy_policy': self.privacyPolicy, 
    141                                         'privacy_version': self.privacyPolicyVer, 
    142                                         'issuer': self.issuer, 
    143                                         'submit_text': 'Login' 
     213                                        'submit_text': 'Login', 
     214                                        'display_infocard': True, 
     215                                        'infocards': self.processors 
    144216                                } 
    145  
     217                                 
    146218                                if not req.args.get('xmlToken') and not req.args.get('cardkeyhash'): 
    147                                         data['display_infocard'] = 'True' 
     219                                        data['display_infocard'] = True 
    148220                                        if req.method == 'POST': 
    149221                                                data['login_error'] = 'Invalid username or password' 
     
    160232                                                        #invalid security token, handle error 
    161233                                                        data['infocard'] = secToken 
     234                                                        data['title'] = 'Invalid Infocard Detail' 
    162235                                                        if secToken.eventLog: 
    163236                                                                data['events'] = secToken.eventLog.events 
     
    171244                                        data['login_error'] = 'Credentials have not been associated with your account' 
    172245                                        data['submit_text'] = 'Associate' 
    173                                         data['blockID'] = 'default' 
     246                                        data['blockID'] = 'infocard_acct' 
    174247                                return 'authenticate.html', data, None 
    175                         elif req.args.get('xmlToken') and self.displayDebug: 
    176                                 self._do_debug_login(req) 
    177                                 self._setup_session(req) 
     248                        elif req.args.get('xmlToken'): 
    178249                                secToken = self._get_token(req) 
    179                                 data = { 
    180                                         'title': 'Infocard Debug', 
    181                                         'infocard': secToken 
    182                                 } 
     250                                data = {'infocard': secToken} 
     251                                 
     252                                if self._get_option(req, 'debug_page'): 
     253                                        self._do_debug_login(req) 
     254                                        self._setup_session(req) 
     255                                        data['title'] = 'Infocard Debug' 
     256                                if not secToken.isValid: 
     257                                        data['title'] = 'Invalid Infocard Detail' 
    183258                                if secToken.eventLog: 
    184259                                        data['events'] = secToken.eventLog.events 
    185                                 return 'infocard-detail.html', data, None 
    186  
    187                         self.log.debug("%s",  self.displayDebug) 
     260                                if (not secToken.isValid) or self._get_option(req, 'debug_page'): 
     261                                        return 'infocard-detail.html', data, None 
     262 
    188263                        self._setup_session(req) 
    189264                return auth.LoginModule.process_request(self, req) 
    190265 
    191266        def _cleanup_session(self, req): 
    192                 """We need to delete session attributes here 
     267                """We need to delete session attributes here, allow all people who  
     268                potentially cached information from the login to act on the logout 
    193269                """ 
    194270                SecTokenSessionModule(self.env).logout(req) 
     
    204280                It should be only used for the debug login. 
    205281                """ 
     282 
    206283                ignore_case = BoolOption('trac', 'ignore_auth_case', 'false', 
    207284                """Whether case should be ignored for login names (''since 0.9'').""") 
     
    221298                req.outcookie['trac_auth'] = cookie 
    222299                req.outcookie['trac_auth']['path'] = req.href() 
    223                  
    224         def _get_processor(self, req): 
    225                 """Manage the singletons for proecessors   
    226                 """ 
    227                 processor = None 
    228                 processorTag = req.args.get('blockID') 
    229                 if not processorTag: 
    230                         processorTag = 'default' 
     300 
     301        def _get_option_from_options(self, options, tag): 
     302                if options and options.has_key(tag): 
     303                        return options[tag] 
     304                return None 
     305 
     306        def _get_option(self, req, tag): 
     307                """Manage the options lists for all infocard login configs   
     308                """ 
     309                block = req.args.get('blockID') 
     310                if not block: 
     311                        block = 'infocard_acct' 
    231312                try: 
    232                         processor = self.processors[processorTag] 
     313                        options = self.processors[block] 
     314                        if options and tag == 'processorTag' and not options.has_key('Audience'): 
     315                                #I really hate doing this here, but it's the only place we  
     316                                #have all of the required information 
     317                                url = urlparse.urlparse(self.env.abs_href()) 
     318                                if not url.hostname: 
     319                                        host = req.get_header('Host') 
     320                                else: 
     321                                        host = url.hostname 
     322                                Audience = (urlparse.urlunparse(('https', host,  
     323                                                                req.base_path, None, None, None)) 
     324                                                        +req.path_info) 
     325                                options['Audience'] = Audience 
     326                                processor = self._get_option_from_options(options, tag) 
     327                                if processor: 
     328                                        processor.set_options({'Audience': Audience}) 
     329                        return self._get_option_from_options(options, tag) 
    233330                except (NameError, KeyError): 
    234                         processor = InfoCardProcessor() 
    235                         if processor: 
    236                                 processor.set_decode(self.privateKey, self.privateKeyPassPhrase, True, False) 
    237                                 processor.set_claims(self.requiredClaims, self.optionalClaims) 
    238                                 self.processors[processorTag] = processor 
    239                                  
    240                 return processor 
     331                        pass 
    241332 
    242333 
     
    249340                        secToken = req.environ['Security_Token'] 
    250341                except (NameError, KeyError): 
    251                         processor = self._get_processor(req) 
     342                        processor = self._get_option(req, 'processorTag') 
    252343                        secToken = processor.process_token(req.args.get('xmlToken')) 
    253344                        req.environ['Security_Token'] = secToken 
    254345                return secToken 
    255346 
    256         def _do_login(self, req): 
    257                 if not req.remote_user:                                       
    258                         req.redirect(self.env.abs_href()) 
    259                 return auth.LoginModule._do_login(self, req) 
    260  
    261347        def _remote_user(self, req): 
     348                """ Called to determine the name of the user, it's poorly named.  It has 
     349                the side effect of actually verifying username/password or credentials 
     350                """ 
     351                 
    262352#               self.log.debug('web_ui:LoginModule:_remote_user' ) 
    263353                user = req.args.get('user') 
     
    287377                                                return user 
    288378                                        else: 
     379                                                associateduser = self._get_option(req, 'associated_user') 
     380                                                if associateduser: 
     381                                                        return associateduser 
    289382                                                self.log.debug('web_user:LoginModule:_remote_user:check_association failed') 
    290383                #self.log.debug('web_user:LoginModule:_remote_user: -> None') 
     
    292385 
    293386        def _redirect_back(self, req):                                                                                                                   
    294                 """Redirect the user back to the URL she came from.""" 
     387                """Redirect the user back to the URL she came from, only used during  
     388                debug logins, otherwise we allow trac to handle the request redirect.""" 
    295389                referer = self._referer(req) 
    296390                if referer and not referer.startswith(req.base_url): 
     
    315409                """ 
    316410                from pkg_resources import resource_filename 
    317 #               self.log.debug('LoginModule:get_htdocs_dirs: %s', resource_filename(__name__, 'htdocs')) 
    318411                return [('infocard_acct', resource_filename(__name__, 'htdocs')), 
    319412                   ('site', self.env.get_htdocs_dir())] 
     
    324417                """ 
    325418                from pkg_resources import resource_filename 
    326 #               self.log.debug('LoginModule:get_templates_dirs: %s', resource_filename(__name__, 'templates')) 
    327419                return [resource_filename(__name__, 'templates')] 
    328420