comparison roundup/cgi/client.py @ 7067:da58c2b28802

refactor: consolidate sets of identical log messages, flake8 fixes I had multiple places where identical messages were being logged in two places. Now I have one definition in each place and the same message is logged on two different code paths. Also wrapped some long strings making sure that they were included in extraction for translation. Fixed long comments.
author John Rouillard <rouilj@ieee.org>
date Wed, 23 Nov 2022 22:23:50 -0500
parents 8094cbf5f6f7
children bc06bad26872
comparison
equal deleted inserted replaced
7066:27c2d7295ba2 7067:da58c2b28802
1163 from roundup.hyperdb import iter_roles 1163 from roundup.hyperdb import iter_roles
1164 1164
1165 # if we got here token is valid, use the role 1165 # if we got here token is valid, use the role
1166 # and sub claims. 1166 # and sub claims.
1167 try: 1167 try:
1168 # make sure to str(token['sub']) the subject. As decoded 1168 # make sure to str(token['sub']) the
1169 # by json, it is unicode which thows an error when used 1169 # subject. As decoded by json, it is unicode
1170 # with 'nodeid in db' down the call chain. 1170 # which thows an error when used with 'nodeid
1171 # in db' down the call chain.
1171 user = self.db.user.get(str(token['sub']), 'username') 1172 user = self.db.user.get(str(token['sub']), 'username')
1172 except IndexError: 1173 except IndexError:
1173 raise LoginError("Token subject is invalid.") 1174 raise LoginError("Token subject is invalid.")
1174 1175
1175 # validate roles 1176 # validate roles
1231 elif '@action' in self.form: 1232 elif '@action' in self.form:
1232 action = self.form['@action'] 1233 action = self.form['@action']
1233 except TypeError: 1234 except TypeError:
1234 pass 1235 pass
1235 if isinstance(action, list): 1236 if isinstance(action, list):
1236 raise SeriousError('broken form: multiple @action values submitted') 1237 raise SeriousError(
1238 self._('broken form: multiple @action values submitted'))
1237 elif action != '': 1239 elif action != '':
1238 action = action.value.lower() 1240 action = action.value.lower()
1239 if action in ('login', 'register'): 1241 if action in ('login', 'register'):
1240 return 1242 return
1241 1243
1402 # If required headers are missing, raise an error 1404 # If required headers are missing, raise an error
1403 for header in header_names: 1405 for header in header_names:
1404 if (config["WEB_CSRF_ENFORCE_HEADER_%s" % header] == 'required' 1406 if (config["WEB_CSRF_ENFORCE_HEADER_%s" % header] == 'required'
1405 and "HTTP_%s" % header.replace('-', '_') not in self.env): 1407 and "HTTP_%s" % header.replace('-', '_') not in self.env):
1406 logger.error(self._( 1408 logger.error(self._(
1407 "csrf header %(header)s required but missing for user%(userid)s.") % { 1409 ''"csrf header %(header)s required but missing "
1410 ''"for user%(userid)s.") % {
1408 'header': header, 1411 'header': header,
1409 'userid': current_user}) 1412 'userid': current_user})
1410 raise Unauthorised(self._("Missing header: %s") % header) 1413 raise Unauthorised(self._("Missing header: %s") % header)
1411 1414
1412 # self.base always matches: ^https?://hostname 1415 # self.base always matches: ^https?://hostname
1413 enforce = config['WEB_CSRF_ENFORCE_HEADER_REFERER'] 1416 enforce = config['WEB_CSRF_ENFORCE_HEADER_REFERER']
1414 if 'HTTP_REFERER' in self.env and enforce != "no": 1417 if 'HTTP_REFERER' in self.env and enforce != "no":
1415 if not self.is_referer_header_ok(api=api): 1418 if not self.is_referer_header_ok(api=api):
1416 referer = self.env['HTTP_REFERER'] 1419 referer = self.env['HTTP_REFERER']
1420 logmsg = self._(
1421 ''"csrf Referer header check failed for user%(userid)s. "
1422 ''"Value=%(referer)s") % {'userid': current_user,
1423 'referer': referer}
1417 if enforce in ('required', 'yes'): 1424 if enforce in ('required', 'yes'):
1418 logger.error(self._( 1425 logger.error(logmsg)
1419 "csrf Referer header check failed for user%(userid)s. Value=%(referer)s") % {'userid': current_user, 'referer': referer})
1420 raise Unauthorised(self._("Invalid Referer: %s") % ( 1426 raise Unauthorised(self._("Invalid Referer: %s") % (
1421 referer)) 1427 referer))
1422 elif enforce == 'logfailure': 1428 elif enforce == 'logfailure':
1423 logger.warning(self._( 1429 logger.warning(logmsg)
1424 "csrf Referer header check failed for user%(userid)s. Value=%(referer)s") % {
1425 'userid': current_user, 'referer': referer})
1426 else: 1430 else:
1427 header_pass += 1 1431 header_pass += 1
1428 1432
1429 # if you change these make sure to consider what 1433 # if you change these make sure to consider what
1430 # happens if header variable exists but is empty. 1434 # happens if header variable exists but is empty.
1431 # self.base.find("") returns 0 for example not -1 1435 # self.base.find("") returns 0 for example not -1
1432 enforce = config['WEB_CSRF_ENFORCE_HEADER_ORIGIN'] 1436 enforce = config['WEB_CSRF_ENFORCE_HEADER_ORIGIN']
1433 if 'HTTP_ORIGIN' in self.env and enforce != "no": 1437 if 'HTTP_ORIGIN' in self.env and enforce != "no":
1434 if not self.is_origin_header_ok(api=api): 1438 if not self.is_origin_header_ok(api=api):
1435 origin = self.env['HTTP_ORIGIN'] 1439 origin = self.env['HTTP_ORIGIN']
1440 logmsg = self._(
1441 ''"csrf Origin header check failed for user%(userid)s. "
1442 ''"Value=%(origin)s") % {
1443 'userid': current_user, 'origin': origin}
1436 if enforce in ('required', 'yes'): 1444 if enforce in ('required', 'yes'):
1437 logger.error(self._( 1445 logger.error(logmsg)
1438 "csrf Origin header check failed for user%(userid)s. Value=%(origin)s") % {
1439 'userid': current_user, 'origin': origin})
1440 raise Unauthorised(self._("Invalid Origin %s" % origin)) 1446 raise Unauthorised(self._("Invalid Origin %s" % origin))
1441 elif enforce == 'logfailure': 1447 elif enforce == 'logfailure':
1442 logger.warning(self._( 1448 logger.warning(logmsg)
1443 "csrf Origin header check failed for user%(userid)s. Value=%(origin)s") % {'userid': current_user, 'origin': origin})
1444 else: 1449 else:
1445 header_pass += 1 1450 header_pass += 1
1446 1451
1447 enforce = config['WEB_CSRF_ENFORCE_HEADER_X-FORWARDED-HOST'] 1452 enforce = config['WEB_CSRF_ENFORCE_HEADER_X-FORWARDED-HOST']
1448 if 'HTTP_X_FORWARDED_HOST' in self.env: 1453 if 'HTTP_X_FORWARDED_HOST' in self.env:
1449 if enforce != "no": 1454 if enforce != "no":
1450 host = self.env['HTTP_X_FORWARDED_HOST'] 1455 host = self.env['HTTP_X_FORWARDED_HOST']
1451 foundat = self.base.find('://' + host + '/') 1456 foundat = self.base.find('://' + host + '/')
1452 # 4 means self.base has http:/ prefix, 5 means https:/ prefix 1457 # 4 means self.base has http:/ prefix, 5 means https:/ prefix
1453 if foundat not in [4, 5]: 1458 if foundat not in [4, 5]:
1459 logmsg = self._(
1460 ''"csrf X-FORWARDED-HOST header check failed "
1461 ''"for user%(userid)s. Value=%(host)s") % {
1462 'userid': current_user, 'host': host}
1454 if enforce in ('required', 'yes'): 1463 if enforce in ('required', 'yes'):
1455 logger.error(self._( 1464 logger.error(logmsg)
1456 "csrf X-FORWARDED-HOST header check failed for user%(userid)s. Value=%(host)s") % {
1457 'userid': current_user, 'host': host})
1458 raise Unauthorised(self._( 1465 raise Unauthorised(self._(
1459 "Invalid X-FORWARDED-HOST %s") % host) 1466 "Invalid X-FORWARDED-HOST %s") % host)
1460 elif enforce == 'logfailure': 1467 elif enforce == 'logfailure':
1461 logger.warning(self._( 1468 logger.warning(logmsg)
1462 "csrf X-FORWARDED-HOST header check failed for user%(userid)s. Value=%(host)s") % {
1463 'userid': current_user, 'host': host})
1464 else: 1469 else:
1465 header_pass += 1 1470 header_pass += 1
1466 else: 1471 else:
1467 # https://seclab.stanford.edu/websec/csrf/csrf.pdf 1472 # https://seclab.stanford.edu/websec/csrf/csrf.pdf
1468 # recommends checking HTTP HOST header as well. 1473 # recommends checking HTTP HOST header as well.
1474 if 'HTTP_HOST' in self.env and enforce != "no": 1479 if 'HTTP_HOST' in self.env and enforce != "no":
1475 host = self.env['HTTP_HOST'] 1480 host = self.env['HTTP_HOST']
1476 foundat = self.base.find('://' + host + '/') 1481 foundat = self.base.find('://' + host + '/')
1477 # 4 means http:// prefix, 5 means https:// prefix 1482 # 4 means http:// prefix, 5 means https:// prefix
1478 if foundat not in [4, 5]: 1483 if foundat not in [4, 5]:
1484 logmsg = self._(
1485 ''"csrf HOST header check failed for "
1486 ''"user%(userid)s. Value=%(host)s") % {
1487 'userid': current_user, 'host': host}
1479 if enforce in ('required', 'yes'): 1488 if enforce in ('required', 'yes'):
1480 logger.error(self._("csrf HOST header check failed for user%(userid)s. Value=%(host)s") % {'userid': current_user, 'host': host}) 1489 logger.error(logmsg)
1481 raise Unauthorised(self._("Invalid HOST %s") % host) 1490 raise Unauthorised(self._("Invalid HOST %s") % host)
1482 elif enforce == 'logfailure': 1491 elif enforce == 'logfailure':
1483 logger.warning(self._("csrf HOST header check failed for user%(userid)s. Value=%(host)s") % {'userid': current_user, 'host': host}) 1492 logger.warning(logmsg)
1484 else: 1493 else:
1485 header_pass += 1 1494 header_pass += 1
1486 1495
1487 enforce = config['WEB_CSRF_HEADER_MIN_COUNT'] 1496 enforce = config['WEB_CSRF_HEADER_MIN_COUNT']
1488 if header_pass < enforce: 1497 if header_pass < enforce:
1499 # Note we do not use CSRF nonces for xmlrpc/rest requests. 1508 # Note we do not use CSRF nonces for xmlrpc/rest requests.
1500 # 1509 #
1501 # see: https://www.owasp.org/index.php/Cross-Site_Request_Forgery_(CSRF)_Prevention_Cheat_Sheet#Protecting_REST_Services:_Use_of_Custom_Request_Headers 1510 # see: https://www.owasp.org/index.php/Cross-Site_Request_Forgery_(CSRF)_Prevention_Cheat_Sheet#Protecting_REST_Services:_Use_of_Custom_Request_Headers
1502 if 'HTTP_X_REQUESTED_WITH' not in self.env: 1511 if 'HTTP_X_REQUESTED_WITH' not in self.env:
1503 logger.error(self._( 1512 logger.error(self._(
1504 "csrf X-REQUESTED-WITH xmlrpc required header check failed for user%s."), 1513 ''"csrf X-REQUESTED-WITH xmlrpc required header "
1514 ''"check failed for user%s."),
1505 current_user) 1515 current_user)
1506 raise UsageError(self._("Required Header Missing")) 1516 raise UsageError(self._("Required Header Missing"))
1507 1517
1508 # Expire old csrf tokens now so we don't use them. These will 1518 # Expire old csrf tokens now so we don't use them. These will
1509 # be committed after the otks.destroy below. Note that the 1519 # be committed after the otks.destroy below. Note that the
1538 otks.commit() 1548 otks.commit()
1539 1549
1540 enforce = config['WEB_CSRF_ENFORCE_TOKEN'] 1550 enforce = config['WEB_CSRF_ENFORCE_TOKEN']
1541 if key is None: # we do not have an @csrf token 1551 if key is None: # we do not have an @csrf token
1542 if enforce == 'required': 1552 if enforce == 'required':
1543 logger.error(self._("Required csrf field missing for user%s"), current_user) 1553 logger.error(self._(
1544 raise UsageError(self._("We can't validate your session (csrf failure). Re-enter any unsaved data and try again.")) 1554 "Required csrf field missing for user%s"), current_user)
1555 raise UsageError(self._(
1556 ''"We can't validate your session (csrf failure). "
1557 ''"Re-enter any unsaved data and try again."))
1545 elif enforce == 'logfailure': 1558 elif enforce == 'logfailure':
1546 # FIXME include url 1559 # FIXME include url
1547 logger.warning(self._("csrf field not supplied by user%s"), 1560 logger.warning(self._("csrf field not supplied by user%s"),
1548 current_user) 1561 current_user)
1549 else: 1562 else:
1588 else: 1601 else:
1589 self.add_error_message("Reload window before logging in.") 1602 self.add_error_message("Reload window before logging in.")
1590 ''' 1603 '''
1591 # validate against user and session 1604 # validate against user and session
1592 if current_user != nonce_user: 1605 if current_user != nonce_user:
1593 if enforce in ('required', "yes"): 1606 logmsg = self._(
1594 logger.error( 1607 ''"Csrf mismatch user: current user %(user)s != stored "
1595 self._("Csrf mismatch user: current user %(user)s != stored user %(stored)s, current session, stored session: %(cur_sess)s,%(stor_sess)s for key %(key)s.") % { 1608 ''"user %(stored)s, current session, stored session: "
1596 'user': current_user, 1609 ''"%(cur_sess)s,%(stor_sess)s for key %(key)s.") % {
1597 'stored': nonce_user, 1610 'user': current_user,
1598 'cur_sess': current_session, 1611 'stored': nonce_user,
1599 'stor_sess': nonce_session, 1612 'cur_sess': current_session,
1600 'key': key}) 1613 'stor_sess': nonce_session,
1601 raise UsageError(self._("We can't validate your session (csrf failure). Re-enter any unsaved data and try again.")) 1614 'key': key}
1615 if enforce in ('required', 'yes'):
1616 logger.error(logmsg)
1617 raise UsageError(self._(
1618 ''"We can't validate your session (csrf failure). "
1619 ''"Re-enter any unsaved data and try again."))
1602 elif enforce == 'logfailure': 1620 elif enforce == 'logfailure':
1603 logger.warning( 1621 logger.warning(logmsg)
1604 self._("Csrf mismatch user: current user %(user)s != stored user %(stored)s, current session, stored session: %(cur_sess)s,%(stor_sess)s for key %(key)s.") % { 1622
1605 'user': current_user,
1606 'stored': nonce_user,
1607 'cur_sess': current_session,
1608 'stor_sess': nonce_session,
1609 'key': key})
1610 if current_session != nonce_session: 1623 if current_session != nonce_session:
1611 if enforce in ('required', "yes"): 1624 logmsg = self._(
1612 logger.error( 1625 ''"Csrf mismatch user: current session %(curr_sess)s "
1613 self._("Csrf mismatch user: current session %(curr_sess)s != stored session %(stor_sess)s, current user/stored user is: %(user)s for key %(key)s.") % { 1626 ''"!= stored session %(stor_sess)s, current user/stored "
1614 'curr_sess': current_session, 1627 ''"user is: %(user)s for key %(key)s.") % {
1615 'stor_sess': nonce_session, 1628 'curr_sess': current_session,
1616 'user': current_user, 1629 'stor_sess': nonce_session,
1617 'key': key}) 1630 'user': current_user,
1618 raise UsageError(self._("We can't validate your session (csrf failure). Re-enter any unsaved data and try again.")) 1631 'key': key}
1632 if enforce in ('required', 'yes'):
1633 logger.error(logmsg)
1634 raise UsageError(self._(
1635 ''"We can't validate your session (csrf failure). "
1636 ''"Re-enter any unsaved data and try again."))
1619 elif enforce == 'logfailure': 1637 elif enforce == 'logfailure':
1620 logger.warning( 1638 logger.warning(logmsg)
1621 self._("logged only: Csrf mismatch user: current session %(curr_sess)s != stored session %(stor_sess)s, current user/stored user is: %(user)s for key %(key)s.") % {
1622 'curr_sess': current_session,
1623 'stor_sess': nonce_session,
1624 'user': current_user,
1625 'key': key})
1626 1639
1627 # we are done and the change can occur. 1640 # we are done and the change can occur.
1628 return True 1641 return True
1629 1642
1630 def opendb(self, username): 1643 def opendb(self, username):
2212 pass 2225 pass
2213 if action is None: 2226 if action is None:
2214 return None 2227 return None
2215 2228
2216 if isinstance(action, list): 2229 if isinstance(action, list):
2217 raise SeriousError('broken form: multiple @action values submitted') 2230 raise SeriousError(
2231 self._('broken form: multiple @action values submitted'))
2218 else: 2232 else:
2219 action = action.value.lower() 2233 action = action.value.lower()
2220 2234
2221 try: 2235 try:
2222 action_klass = self.get_action_class(action) 2236 action_klass = self.get_action_class(action)

Roundup Issue Tracker: http://roundup-tracker.org/