PageRenderTime 49ms CodeModel.GetById 22ms RepoModel.GetById 0ms app.codeStats 0ms

/wxgeometrie/pylib/securite.py

https://github.com/wxgeo/geophar
Python | 184 lines | 110 code | 25 blank | 49 comment | 8 complexity | 6463db16c46bef56cafe5383e9ac8cf4 MD5 | raw file
  1. # -*- coding: utf-8 -*-
  2. ##########################################################################
  3. #
  4. # Gestion de la securite
  5. #
  6. ##########################################################################
  7. # WxGeometrie
  8. # Dynamic geometry, graph plotter, and more for french mathematic teachers.
  9. # Copyright (C) 2005-2013 Nicolas Pourcelot
  10. #
  11. # This program is free software; you can redistribute it and/or modify
  12. # it under the terms of the GNU General Public License as published by
  13. # the Free Software Foundation; either version 2 of the License, or
  14. # (at your option) any later version.
  15. #
  16. # This program is distributed in the hope that it will be useful,
  17. # but WITHOUT ANY WARRANTY; without even the implied warranty of
  18. # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  19. # GNU General Public License for more details.
  20. #
  21. # You should have received a copy of the GNU General Public License
  22. # along with this program; if not, write to the Free Software
  23. # Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
  24. import keyword, re, types
  25. import math # pour avoir droit aux fonctions mathematiques dans eval_restricted par defaut.
  26. import random as module_random# idem
  27. from ..pylib import advanced_split
  28. from .. import param
  29. def _avertissement(*arg, **kw):
  30. print("Instruction interdite en mode securisé.")
  31. #fonctions_interdites = ["eval", "compile", "execfile", "file", "open", "write", "getattr", "setattr"]
  32. liste_blanche = set(('False', 'None', 'True', 'abs', 'all', 'any', 'bool', 'callable', 'chr', 'close', \
  33. 'coerce', 'complex', 'dict', 'divmod', 'enumerate', 'filter', 'float', 'frozenset', 'globals', 'hash', 'hex', \
  34. 'id', 'int', 'isinstance', 'issubclass', 'iter', 'len', 'list', 'locals', 'long', 'map', 'max', 'min', 'object', 'oct', \
  35. 'ord', 'pow', 'range', 'repr', 'reversed', 'round', 'set', 'slice', 'sorted', 'str', 'sum', \
  36. 'tuple', 'type', 'zip'\
  37. 'IndexError', 'SyntaxError', 'NameError', 'StandardError', 'UnicodeDecodeError', 'RuntimeWarning', \
  38. 'Warning', 'FloatingPointError', 'FutureWarning', 'ImportWarning', 'TypeError', 'KeyboardInterrupt', \
  39. 'UserWarning', 'SystemError', 'BaseException', 'RuntimeError', 'GeneratorExit', 'StopIteration', \
  40. 'LookupError', 'UnicodeError', 'IndentationError', 'Exception', 'UnicodeTranslateError', 'UnicodeEncodeError', \
  41. 'PendingDeprecationWarning', 'ArithmeticError', 'MemoryError', 'ImportError', 'KeyError', 'SyntaxWarning', \
  42. 'EnvironmentError', 'OSError', 'DeprecationWarning', 'UnicodeWarning', 'ValueError', 'NotImplemented', \
  43. 'TabError', 'ZeroDivisionError', 'ReferenceError', 'AssertionError', 'UnboundLocalError', 'NotImplementedError', \
  44. 'AttributeError', 'OverflowError', 'WindowsError'))
  45. liste_noire = set(__builtins__.keys())
  46. liste_noire.difference_update(liste_blanche)
  47. dictionnaire_builtins = {}.fromkeys(list(liste_noire), _avertissement)
  48. dictionnaire_modules = {}.fromkeys([key for key, objet in globals().items() if type(objet) == types.ModuleType])
  49. ajouts_math = {"pi": math.pi, "e": math.e, "i": 1j}
  50. for key, obj in math.__dict__.items():
  51. if type(obj) == types.BuiltinFunctionType:
  52. ajouts_math[key] = obj
  53. for key, obj in module_random.__dict__.items():
  54. if type(obj) == types.BuiltinFunctionType:
  55. ajouts_math[key] = obj
  56. ##keywords = ('and', 'as', 'assert', 'break', 'class', 'continue', 'def', 'del', 'elif', 'else', 'except', 'exec', 'finally', 'for', 'from', 'global', 'if', 'import', 'in', 'is', 'lambda', 'not', 'or', 'pass', 'print', 'raise', 'return', 'try', 'while', 'with', 'yield')
  57. ### mots clés qui peuvent être utilisés
  58. ##keywords_autorises = set('and', 'assert', 'break', 'class', 'continue', 'def', 'del', 'elif', 'else', 'except', 'finally', 'for', 'if', 'in', 'is', 'lambda', 'not', 'or', 'pass', 'print', 'raise', 'return', 'try', 'while', 'with', 'yield')
  59. keywords = set(keyword.kwlist)
  60. # mots clés dont il faut s'assurer qu'ils ne puissent PAS être utilisés
  61. keywords_interdits = set(('exec', 'global', 'import')) # attention à global !
  62. # mots clés qui peuvent être utilisés
  63. keywords_autorises = keywords.difference(keywords_interdits)
  64. ### ici, il s'agit de mots clés qui doivent rester en début de ligne
  65. ### (plus précisément, il est impossible de rajouter devant une affectation, style 'variable = ').
  66. ##keywords_debut_ligne = ('break', 'class', 'continue', 'def', 'del', 'elif', 'else', 'except', 'exec', 'finally', 'pass', 'for', 'if', 'print', 'raise', 'return', 'try', 'while', 'with', 'yield')
  67. # Mots clés devant lesquels il est possible de rajouter une affectation, style 'variable = ...'.
  68. keywords_affectables = set(('lambda', 'not'))
  69. # Mots clés qui ne sont jamais en début de ligne.
  70. keywords_milieu = set(('or', 'and', 'as', 'in'))
  71. # Les autres, qui doivent rester en tête de ligne.
  72. keywords_non_affectables = keywords.difference(keywords_affectables).difference(keywords_milieu)
  73. def keywords_interdits_presents(chaine):
  74. return bool(re.search(r'(?<!\w)(' + '|'.join(keywords_interdits) + ')(?!\w)', chaine))
  75. def expression_affectable(chaine):
  76. return not bool(re.match('[ ]*(' + '|'.join(keywords_non_affectables) + ')(?!\w)', chaine))
  77. # Exemple d'usage :
  78. # dico = {"pi":3.1415926535897931, "e":2.7182818284590451} # dictionnaire local
  79. # dico.update(securite.dictionnaire_builtins)
  80. # exec(string, dico)
  81. def eval_restricted(s, dico_perso = None):
  82. """eval_restricted(s) évalue s dans un contexte vierge et légèrement sécurisé.
  83. Toutes les fonctions disponibles par défaut sont filtrées.
  84. Note: eval_restricted est le plus securisée possible, mais contrairement
  85. à eval_safe, il utilise la fonction eval ; il existe donc forcément des
  86. failles de sécurité.
  87. """
  88. dico = {"rand": module_random.random}
  89. dico.update(dictionnaire_builtins) # supprime certaines fonctions par defaut (en les redefinissant)
  90. dico.update(ajouts_math) # ajoute les fonctions mathematiques usuelles
  91. if dico_perso is not None:
  92. dico.update(dico_perso)
  93. for module in ("os", "sys", "securite"):
  94. dico.pop(module, None)
  95. return eval(s, dico)
  96. def eval_safe(s):
  97. """eval_safe(repr(x)) retourne x pour les types les plus usuels
  98. (int, str, float, bool, None, list, tuple, dict.)
  99. Mais aucune évaluation n'est faite, ce qui évite d'éxécuter un code dangereux.
  100. Le type de s est detecté, et la transformation appropriée appliquée.
  101. NB1: eval_safe n'est pas destiné à remplacer eval :
  102. - il est relativement lent,
  103. - le nombre de types supportés est volontairement réduit,
  104. - il n'est pas souple (eval_safe("2-3") renvoie une erreur).
  105. La fonction eval_safe est orientée uniquement securité.
  106. NB2: eval_safe est récursif (il peut lire des listes de tuples de ...).
  107. NB3: eval_safe est parfaitement securisé, car il ne fait (presque) jamais appel à une instruction eval.
  108. Contrairement à la fonction eval_restricted, qui est 'légèrement' securisée."""
  109. s = s.strip()
  110. def filtre(chaine, caracteres):
  111. return re.match("^[%s]*$" %caracteres, chaine) is not None
  112. if filtre(s, "-0-9") or s[-1] == "L": # entier
  113. return int(s)
  114. if filtre(s, "-+.e0-9"): # flottant
  115. return float(s)
  116. if len(s) > 1 and s[0] == s[-1] and s[0] in ("'", '"'): # chaine
  117. # s est évalué dans un contexte parfaitement vierge.
  118. return eval(s, {"__builtins__": {}}, {"__builtins__": {}})
  119. if len(s) > 2 and s[0] == "u" and s[1] == s[-1] and s[1] in ("'", '"'): #unicode
  120. return eval(s, {"__builtins__": {}}, {"__builtins__": {}}) # idem
  121. if s == "None":
  122. return None
  123. if s == "True":
  124. # Eviter return eval(s), car True peut etre redefini. Exemple : True = False (!)
  125. return True
  126. if s == "False":
  127. return False
  128. if s[0] in ("(", "["): # tuple ou liste
  129. liste = [eval_safe(t) for t in advanced_split(s[1:-1], ",") if t]
  130. if s[0] == "(":
  131. liste = tuple(liste)
  132. return liste
  133. if s[0] == "{": # dictionnaire
  134. dict = {}
  135. liste = [advanced_split(t, ":") for t in advanced_split(s[1:-1], ",") if t]
  136. for key, val in liste:
  137. dict[eval_safe(key)] = eval_safe(val)
  138. return dict
  139. raise TypeError("%s: types int, str, float, bool, ou None requis (ou liste ou tuple de ces types)" % repr(s))