Changeset 1123
- Timestamp:
- 12/07/07 13:26:22 (3 years ago)
- Location:
- trunk/rp
- Files:
-
- 1 added
- 10 modified
-
common/python/infocard/event.py (modified) (3 diffs)
-
common/python/infocard/infocardlib.py (modified) (20 diffs)
-
common/python/infocard/xmlseclibs.py (modified) (1 diff)
-
trac/infocard_acct/0.11/infocard_acct/db.py (modified) (3 diffs)
-
trac/infocard_acct/0.11/infocard_acct/groups.py (modified) (6 diffs)
-
trac/infocard_acct/0.11/infocard_acct/session.py (modified) (3 diffs)
-
trac/infocard_acct/0.11/infocard_acct/templates/.infocard-detail.html.marks (added)
-
trac/infocard_acct/0.11/infocard_acct/templates/authenticate.html (modified) (1 diff)
-
trac/infocard_acct/0.11/infocard_acct/templates/infocard-detail.html (modified) (2 diffs)
-
trac/infocard_acct/0.11/infocard_acct/templates/infocard-session-detail.html (modified) (1 diff)
-
trac/infocard_acct/0.11/infocard_acct/web_ui.py (modified) (15 diffs)
Legend:
- Unmodified
- Added
- Removed
-
trunk/rp/common/python/infocard/event.py
r1104 r1123 18 18 19 19 import logging 20 import re 20 21 21 22 CRITICAL = logging.CRITICAL … … 25 26 INFO = logging.INFO 26 27 NOTSET = logging.NOTSET 28 29 config_fields = ['CRITICAL', 'DEBUG', 'ERROR', 'FATAL', 'INFO'] 27 30 28 31 class IEventLog: … … 37 40 38 41 class 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 """ 42 49 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""" 43 69 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) 46 71 self.events.append(event) 47 72 48 73 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 """ 49 79 count = 0 50 80 for event in self.events: 51 81 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)))) 53 84 return count 54 85 55 86 class Event: 87 """Structure to trace an indivdual event""" 56 88 text = None 57 89 tag = None -
trunk/rp/common/python/infocard/infocardlib.py
r1104 r1123 104 104 OPTION_multivalued_claims = 'multivalued_claims' 105 105 106 """ options which may reduce security by ignoring or downgrading some checks.107 some options may need to be set for interoperablity108 """109 OPTION_check_required_claims = 'check_required_claims'110 111 106 112 107 class InfoCardProcessor: 113 108 114 109 def __init__(self): 115 110 self.options = {} 116 111 117 112 def set_decode(self, privateKey, passPhrase = None, isFile=False, isCert = True): 118 113 if isFile: … … 125 120 self.options[OPTION_CryptoKeyIsFile] = isFile 126 121 self.options[OPTION_CryptoKeyIsCert] = isCert 127 128 122 123 129 124 def set_claims(self, required, optional = None, multivalued = None): 130 125 self.options[OPTION_required_claims] = required 131 126 self.options[OPTION_optional_claims] = optional 132 127 self.options[OPTION_multivalued_claims] = multivalued 133 128 134 129 def set_options(self, options): 135 130 for name in options.keys(): 136 131 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 143 133 def _option(self, name): 144 134 try: … … 146 136 except Exception: 147 137 return None 148 138 149 139 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) 154 143 if secToken: 155 144 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 164 146 return secToken 165 166 147 148 167 149 class 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): 171 153 self.crypted = None #Raw security token as delivered from the client 172 154 self.cryptedDom = None #Raw security token as delivered from the client … … 178 160 self.metadata = {} #array of misc data about the token, such as 179 161 #token type 180 self.eventLog = BasicEventLog() 181 162 self.eventLog = BasicEventLog(options) 163 self.namespace = SAML_1_0_ASSERT_NS 164 182 165 def _option(self, options, option): 183 166 try: … … 187 170 188 171 def process_token(self, xmlToken, options): 172 """ process the token, until processed no data is present 173 """ 174 189 175 self.crypted = xmlToken 190 176 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') 191 180 objenc = xmlseclibs.XMLSecEnc(self.eventLog) 192 181 self.cryptedDom = minidom.parseString(self.crypted) … … 198 187 objenc.setNode(encData) 199 188 objenc.type = encData.getAttribute("Type") 200 189 201 190 key = None 202 191 objKey = objenc.locateKey() … … 212 201 self._option(options, OPTION_CryptoKeyIsCert)) 213 202 key = objencKey.decryptKey(self.objKeyInfo) 214 203 215 204 if (not objKey) or (not key): 216 205 self.eventLog.add_event("Error loading key to handle"\ … … 218 207 else: 219 208 objKey.loadKey(key) 220 209 221 210 decrypt = objenc.decryptNode(objKey, False) 222 211 if decrypt: … … 227 216 event.FATAL, 'parse') 228 217 else: 229 #do validity checking230 self.isValid = self._isValid(options)231 218 #grab assertions/claims 232 219 self._setupAssertions(options) 233 220 #set up metadata about the token for later use 234 221 self._setupMiscData(options) 222 #do validity checking 223 self.isValid = self._isValid(options) 235 224 else: 236 225 self.eventLog.add_event('Unable to decrypt token', … … 239 228 self.eventLog.add_event("SecToken did not pass all valididation requirements", 240 229 event.ERROR, 'info') 230 241 231 except Exception, why: 242 232 trace = traceback.format_exception(*sys.exc_info()) … … 246 236 event.FATAL, 'exception') 247 237 248 """Setup all of the data about the token we can249 """250 238 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 """ 251 242 rootNode = self.decrypted.documentElement 252 243 if rootNode: … … 257 248 self.metadata[META_MinorVersion] = rootNode.getAttribute('MinorVersion') 258 249 self.metadata[META_CardKeyHash] = self._getCardKeyHash() 250 259 251 260 252 def _isValid(self, options): 261 253 """ Validate the SAML token 262 254 """ 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 264 262 objXMLSecDSig = xmlseclibs.XMLSecurityDSig(self.eventLog) 265 263 objDSig = objXMLSecDSig.locateSignature(self.decrypted) … … 275 273 self.eventLog.add_event("SAML Reference Validation Failed", 276 274 event.ERROR, 'validate-saml-reference') 277 return False278 275 279 276 key = None 280 277 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: 282 282 self.objKeyInfo = xmlseclibs.XMLSecEnc.staticLocateKeyInfo(objKey, 283 283 objDSig, self.eventLog) … … 288 288 # event.INFO) 289 289 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 302 295 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 310 300 query = '/mysaml:Assertion/mysaml:Conditions' 311 301 nodelist = xpath.Evaluate(query, context=xPath) … … 315 305 self.metadata[META_NotOnOrAfter] = node.getAttribute('NotOnOrAfter') 316 306 317 if ((not self.metadata[META_NotBefore]) or318 (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 False323 307 if (not checkDateConditions(self.metadata[META_NotBefore], self.metadata[META_NotOnOrAfter])): 324 308 currentTime = datetime.datetime.utcnow() … … 328 312 currentTime.isoformat()), 329 313 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 331 365 if self.eventLog.has_severity(event.ERROR): 332 366 return False … … 343 377 mvClaims = temp.rsplit(' ') 344 378 xPath = xpath.CreateContext(self.decrypted) 345 xPath.setNamespaces({'mysaml' : SAML_1_0_ASSERT_NS})379 xPath.setNamespaces({'mysaml' : self.namespace}) 346 380 query = '/mysaml:Assertion/mysaml:AttributeStatement/mysaml:Attribute' 347 381 nodelist = xpath.Evaluate(query, context=xPath) … … 359 393 try: 360 394 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') 364 398 #TODO multivalued check 365 399 pass 366 400 except Exception: 367 401 # 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') 370 404 nsDict[name] = value 371 405 except Exception: 372 406 # 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') 375 409 self.assertions[ns] = {name:value} 376 410 … … 392 426 claim = identifier 393 427 428 # self.eventLog.add_event("Looking for ns: \"%s\" claim: \"%s\" " \ 429 # % (ns, claim), 430 # event.INFO, 'get-assertions') 431 394 432 claims = dict() 395 433 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') 396 437 if not ns or (ns and ns == nsElem): 397 438 nsDict = self.assertions[nsElem] … … 400 441 return nsDict[key] 401 442 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] 404 445 else: 405 446 claims[key] = nsDict[key] 406 447 if claims.items(): 407 448 return claims 408 else:409 return None410 449 411 450 -
trunk/rp/common/python/infocard/xmlseclibs.py
r1104 r1123 588 588 " by not including this element your chosen IdP puts you"\ 589 589 " at risk of a brown bag attack!", 590 event. INFO, 'signature-missing-InclusiveNameSpaces')590 event.ERROR, 'signature-missing-InclusiveNameSpaces') 591 591 592 592 if (isinstance(signedDataNode, minidom.Node)): -
trunk/rp/trac/infocard_acct/0.11/infocard_acct/db.py
r1102 r1123 53 53 db = self.env.get_db_cnx() 54 54 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) 56 56 cursor.execute("INSERT INTO cardkey " 57 57 "(username,cardkeyhash) " … … 67 67 db = self.env.get_db_cnx() 68 68 cursor = db.cursor() 69 self.log.debug('DBAssociationStore:check_association for \"%s\"', cardkeyhash)69 # self.log.debug('DBAssociationStore:check_association for \"%s\"', cardkeyhash) 70 70 cursor.execute("SELECT username FROM cardkey " 71 71 "WHERE cardkeyhash=%s", (cardkeyhash,)) 72 72 user = None 73 73 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]) 75 75 return row[0] 76 76 return None … … 193 193 self.found_db_version = int(value[0]) 194 194 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)) 197 197 198 198 if self.found_db_version == db_default.db_version: -
trunk/rp/trac/infocard_acct/0.11/infocard_acct/groups.py
r1103 r1123 25 25 from session import SecTokenChangeListener 26 26 27 from infocard import infocardlib 28 27 29 session_var_name = 'sec_groups' 28 30 29 31 class SecTokenGroups(Component): 30 32 """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 """ 32 36 33 implements(SecTokenChangeListener )37 implements(SecTokenChangeListener, IPermissionGroupProvider) 34 38 35 39 def __init__(self): … … 39 43 self.env.log.debug('SecTokenGroups disabled') 40 44 return 41 gDefSets = self.config.getlist('infocard_acct', 'group_definitions') 42 for gDef in gDefSets: 43 gDef = gDef.strip() #remove any unsightly spaces 45 44 46 # self.env.log.debug('SecTokenGroups processing group definition : ' + gDef) 45 47 groupRules = {} 46 48 groupRules['name'] = gDef 47 49 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')): 51 57 self.gDefs[gDef] = groupRules 52 58 … … 62 68 rule = self.gDefs[ruleName] 63 69 try: 64 if self._does_rule_match(rule, se ssion):65 values = self._evalute_rule(rule, se ssion)70 if self._does_rule_match(rule, secToken): 71 values = self._evalute_rule(rule, secToken) 66 72 if values: 67 73 self.env.log.debug('SecTokenGroups rule adds : '\ … … 74 80 self.env.log.debug('SecTokenGroups error evaluating rule: %s' % (ruleName)) 75 81 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)) 78 84 79 85 return groups … … 84 90 del req.session[session_var_name] 85 91 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 101 111 # else: 102 112 # data = eval('tok_claim_privatepersonalidentifier', None, session) 103 113 # return (data,) 104 except Exception:105 pass114 # except Exception: 115 # pass 106 116 return None 107 108 109 class SessionGroupProvider(Component):110 """111 Provides permission groups by evaluating what is stored on the session112 Note, this doesn't change from invocation to invocation so often a cache113 may be used.114 """115 implements(IPermissionGroupProvider)116 117 117 118 # IPermissionGroupProvider interface … … 127 128 "WHERE sid=%s and authenticated=%s and name=%s", (username, int(True), session_var_name)) 128 129 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(' ') 131 133 except Exception: 132 134 pass 133 135 134 136 return [] 135 137 138 -
trunk/rp/trac/infocard_acct/0.11/infocard_acct/session.py
r1103 r1123 63 63 def login(self, req, secToken): 64 64 """ on login set the email address and name on the session""" 65 66 # self.log.debug('SecTokenPreferences setup %s %s' % (req, req.session)) 65 67 if secToken and req.session: 66 68 email = secToken.getAssertion('http://schemas.xmlsoap.org/ws/2005/05/identity/claims/emailaddress') … … 73 75 fullname += ' ' + last 74 76 75 self.log.debug('SecTokenPreferences login%s, %s' % (email, fullname))76 77 # self.log.debug('SecTokenPreferences login: %s, %s' % (email, fullname)) 78 77 79 #visit should we override or update the email or just set if not set? 78 80 if email and (self.overrideSession or not req.session.has_key('email')): 79 81 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')): 81 83 req.session['name'] = fullname 82 84 … … 113 115 """On logout we should cleanup all of the garbage so that subsequent 114 116 sessions don't end up with incorrect data""" 115 #meta = req.session.get('tok_meta')117 meta = req.session.get('tok_meta') 116 118 # if meta: 117 119 # for key in meta.rsplit(' '): -
trunk/rp/trac/infocard_acct/0.11/infocard_acct/templates/authenticate.html
r1102 r1123 55 55 </form> 56 56 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> 100 97 </body> 101 98 </html> -
trunk/rp/trac/infocard_acct/0.11/infocard_acct/templates/infocard-detail.html
r1098 r1123 8 8 <head> 9 9 <title>${title}</title> 10 <script type="text/javascript">11 $(document).ready(function() {12 $('#user')[0].focus();13 });14 </script>15 10 </head> 16 11 … … 27 22 <thead> 28 23 <tr> 29 <th> label</th><th>description</th><th>severity</th>24 <th>severity</th><th>label</th><th>description</th> 30 25 </tr> 31 26 </thead> 32 27 <tbody> 28 <?python 29 import logging 30 def severityName(level): 31 return logging.getLevelName(level) 32 ?> 33 33 <div py:for="element in events"> 34 34 <tr> 35 <td><pre>${ element.severity}</pre></td>35 <td><pre>${severityName(element.severity)}</pre></td> 36 36 <td><pre>${element.tag}</pre></td> 37 37 <td><pre>${element.text}</pre></td> -
trunk/rp/trac/infocard_acct/0.11/infocard_acct/templates/infocard-session-detail.html
r1103 r1123 8 8 <head> 9 9 <title>${title}</title> 10 <script type="text/javascript">11 $(document).ready(function() {12 $('#user')[0].focus();13 });14 </script>15 10 </head> 16 11 -
trunk/rp/trac/infocard_acct/0.11/infocard_acct/web_ui.py
r1103 r1123 35 35 import re 36 36 import time 37 import urlparse 37 38 38 39 from trac import perm, util … … 55 56 56 57 import infocard.xmlseclibs, infocard.infocardlib 57 from infocard import infocardlib 58 from infocard import infocardlib, event 58 59 from infocard.infocardlib import SecToken, InfoCardProcessor 59 60 … … 71 72 privateKey = '/' 72 73 privateKeyPassPhrase = '' 73 processors = {}74 74 mandatoryClaims = ('http://schemas.xmlsoap.org/ws/2005/05/identity/claims/privatepersonalidentifier',) 75 displayDebug = False76 75 77 76 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') 93 124 94 125 #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))) 102 165 103 166 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. """ 104 170 #self.log.debug('web_ui:LoginModule:authenticate' ) 105 171 authnanme = None … … 117 183 118 184 def match_request(self, req): 185 """See if we handle the request, we watch for login/logout""" 119 186 if if_enabled(auth.LoginModule.match_request) \ 120 187 and ( (re.match(r'/login/?$', req.path_info) is not None) \ … … 125 192 126 193 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)) 128 199 if req.path_info.startswith('/logout'): 129 200 self._cleanup_session(req) 130 201 if req.path_info.startswith('/login'): 131 202 if req.authname == 'anonymous': 203 204 if not self._was_sent_secure(req): 205 self._redirect_secure(req) 206 132 207 data = { 133 208 'title': 'Login', … … 136 211 'referer': self._referer(req), 137 212 '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 144 216 } 145 217 146 218 if not req.args.get('xmlToken') and not req.args.get('cardkeyhash'): 147 data['display_infocard'] = 'True'219 data['display_infocard'] = True 148 220 if req.method == 'POST': 149 221 data['login_error'] = 'Invalid username or password' … … 160 232 #invalid security token, handle error 161 233 data['infocard'] = secToken 234 data['title'] = 'Invalid Infocard Detail' 162 235 if secToken.eventLog: 163 236 data['events'] = secToken.eventLog.events … … 171 244 data['login_error'] = 'Credentials have not been associated with your account' 172 245 data['submit_text'] = 'Associate' 173 data['blockID'] = ' default'246 data['blockID'] = 'infocard_acct' 174 247 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'): 178 249 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' 183 258 if secToken.eventLog: 184 259 data['events'] = secToken.eventLog.events 185 return 'infocard-detail.html', data, None186 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 188 263 self._setup_session(req) 189 264 return auth.LoginModule.process_request(self, req) 190 265 191 266 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 193 269 """ 194 270 SecTokenSessionModule(self.env).logout(req) … … 204 280 It should be only used for the debug login. 205 281 """ 282 206 283 ignore_case = BoolOption('trac', 'ignore_auth_case', 'false', 207 284 """Whether case should be ignored for login names (''since 0.9'').""") … … 221 298 req.outcookie['trac_auth'] = cookie 222 299 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' 231 312 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) 233 330 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 241 332 242 333 … … 249 340 secToken = req.environ['Security_Token'] 250 341 except (NameError, KeyError): 251 processor = self._get_ processor(req)342 processor = self._get_option(req, 'processorTag') 252 343 secToken = processor.process_token(req.args.get('xmlToken')) 253 344 req.environ['Security_Token'] = secToken 254 345 return secToken 255 346 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 261 347 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 262 352 # self.log.debug('web_ui:LoginModule:_remote_user' ) 263 353 user = req.args.get('user') … … 287 377 return user 288 378 else: 379 associateduser = self._get_option(req, 'associated_user') 380 if associateduser: 381 return associateduser 289 382 self.log.debug('web_user:LoginModule:_remote_user:check_association failed') 290 383 #self.log.debug('web_user:LoginModule:_remote_user: -> None') … … 292 385 293 386 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.""" 295 389 referer = self._referer(req) 296 390 if referer and not referer.startswith(req.base_url): … … 315 409 """ 316 410 from pkg_resources import resource_filename 317 # self.log.debug('LoginModule:get_htdocs_dirs: %s', resource_filename(__name__, 'htdocs'))318 411 return [('infocard_acct', resource_filename(__name__, 'htdocs')), 319 412 ('site', self.env.get_htdocs_dir())] … … 324 417 """ 325 418 from pkg_resources import resource_filename 326 # self.log.debug('LoginModule:get_templates_dirs: %s', resource_filename(__name__, 'templates'))327 419 return [resource_filename(__name__, 'templates')] 328 420