Commit 1764c526 authored by Jérome Perrin's avatar Jérome Perrin

CSPRNG in restricted python

Allow python's cryptographically secure pseudorandom number generator for usage in restricted python and use it where it makes sense.

This also change the API of `Person_generatePassword` which no longer allow to control the number of letters and numbers.

/reviewed-on nexedi/erp5!847
parents 487dbafb f25ef810
Pipeline #7655 passed with stage
in 0 seconds
alpha = '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ' import os
random_id = '' import base64
for _ in range(0, 128):
random_id += random.choice(alpha) # This is python 3.6 secret.token_urlsafe
random_id = base64.urlsafe_b64encode(os.urandom(48)).rstrip(b'=').decode('ascii')
# Define Reference from ID provided by portal_ids # Define Reference from ID provided by portal_ids
portal = context.getPortalObject() portal = context.getPortalObject()
......
""" return ''.join(random.SystemRandom().sample(string.letters + string.digits, length))
This script generates a human readable random
password in the form 'word'+digits+'word'.
from http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/410076
parameters: number of 'characters' , number of 'digits'
Pradeep Kishore Gowda <pradeep at btbytes.com >
License : GPL
Date : 2005.April.15
Revision 1.2
ChangeLog:
1.1 - fixed typos
1.2 - renamed functions _apart & _npart to a_part & n_part as zope does not allow functions to
start with _
"""
import string, random
vowels = ['a','e','i','o','u']
consonants = [a for a in string.ascii_lowercase if a not in vowels]
digits = string.digits
def a_part(slen):
ret = ''
for i in range(slen):
if i%2 ==0:
randid = random.randint(0,20) #number of consonants
ret += consonants[randid]
else:
randid = random.randint(0,4) #number of vowels
ret += vowels[randid]
return ret
def n_part(slen):
ret = ''
for i in range(slen):
randid = random.randint(0,9) #number of digits
ret += digits[randid]
return ret
fpl = alpha/2
if alpha % 2 :
fpl = int(alpha/2) + 1
lpl = alpha - fpl
start = a_part(fpl)
mid = n_part(numeric)
end = a_part(lpl)
newpass = "%s%s%s" % (start,mid,end)
return newpass
...@@ -50,7 +50,7 @@ ...@@ -50,7 +50,7 @@
</item> </item>
<item> <item>
<key> <string>_params</string> </key> <key> <string>_params</string> </key>
<value> <string>alpha=6, numeric=2</string> </value> <value> <string>length=20</string> </value>
</item> </item>
<item> <item>
<key> <string>id</string> </key> <key> <string>id</string> </key>
...@@ -58,7 +58,7 @@ ...@@ -58,7 +58,7 @@
</item> </item>
<item> <item>
<key> <string>title</string> </key> <key> <string>title</string> </key>
<value> <string>Generate human readable random password</string> </value> <value> <string></string> </value>
</item> </item>
</dictionary> </dictionary>
</pickle> </pickle>
......
...@@ -52,7 +52,7 @@ if not login.hasPassword(): ...@@ -52,7 +52,7 @@ if not login.hasPassword():
credential_recovery.submit() credential_recovery.submit()
else: else:
# system should generate a password # system should generate a password
password = context.Person_generatePassword(alpha=5, numeric=3) password = context.Person_generatePassword()
login.setPassword(password) login.setPassword(password)
# create a global account # create a global account
......
...@@ -344,3 +344,7 @@ for member_id in dir(decimal): ...@@ -344,3 +344,7 @@ for member_id in dir(decimal):
if isinstance(member, type) and issubclass(member, decimal.DecimalException): if isinstance(member, type) and issubclass(member, decimal.DecimalException):
ContainerAssertions[member] = 1 ContainerAssertions[member] = 1
del member_id, member del member_id, member
from random import SystemRandom
allow_type(SystemRandom)
ModuleSecurityInfo('os').declarePublic('urandom')
...@@ -25,8 +25,6 @@ ...@@ -25,8 +25,6 @@
# #
############################################################################## ##############################################################################
import unittest
from Products.ERP5Type.tests.ERP5TypeTestCase import ERP5TypeTestCase from Products.ERP5Type.tests.ERP5TypeTestCase import ERP5TypeTestCase
from Products.ERP5Type.tests.utils import createZODBPythonScript from Products.ERP5Type.tests.utils import createZODBPythonScript
from Products.ERP5Type.tests.utils import removeZODBPythonScript from Products.ERP5Type.tests.utils import removeZODBPythonScript
...@@ -123,3 +121,19 @@ class TestRestrictedPythonSecurity(ERP5TypeTestCase): ...@@ -123,3 +121,19 @@ class TestRestrictedPythonSecurity(ERP5TypeTestCase):
'return urlparse.parse_qsl("q=s")', 'return urlparse.parse_qsl("q=s")',
expected=[('q', 's')] expected=[('q', 's')]
) )
def testSystemRandom(self):
self.createAndRunScript('import random',
'return random.SystemRandom().getrandbits(10)')
def test_os_urandom(self):
self.createAndRunScript('import os',
'return os.urandom(10)')
# other "unsafe" os members are not exposed
self.assertRaises(Unauthorized,
self.createAndRunScript, 'import os',
'return os.path.exists("/")')
self.assertRaises(Unauthorized,
self.createAndRunScript, 'import os',
'return os.system')
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment