Mercurial > p > roundup > code
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) |
