Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
59 changes: 59 additions & 0 deletions seleniumbase/common/ReadMe.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
## Using methods from the "common" folder.

### Part 1: Decorators - (from [decorators.py](https://github.com/seleniumbase/SeleniumBase/blob/master/seleniumbase/common/decorators.py))

#### Use these Python decorators with your test methods as needed:

* @retry_on_exception(tries=6, delay=1, backoff=2, max_delay=32)

* @rate_limited(max_per_second)

Example demonstrating a rate-limited printing functionality:
```python
import unittest
from seleniumbase.common import decorators


class MyTestClass(unittest.TestCase):

@decorators.rate_limited(3.5) # The arg is max calls per second
def print_item(self, item):
print(item)

def test_rate_limited_printing(self):
print("\nRunning rate-limited print test:")
for item in range(1, 11):
self.print_item(item)
```

### Part 2: String/Password Obfuscation, Encryption, and Decryption

#### Intro:

Often in your tests, you may need to login to a website to perform testing. This generally means storing passwords in plaintext formats. For security reasons, that may not be an optimal solution. For this reason, encryption/obfuscation tools have been built here to help you mask your passwords in your tests. It's not a bulletproof solution, but it can keep anyone looking over your shoulder during test creation from getting your login passwords if they don't have your encryption key, which is stored in a separate file.

#### Usage:

* First, set your custom encryption/decryption key in your local clone of [settings.py](https://github.com/seleniumbase/SeleniumBase/blob/master/seleniumbase/config/settings.py). (If you modify they key later, you'll need to encrypt all your passwords again.)

* Next, use [obfuscate.py](https://github.com/seleniumbase/SeleniumBase/blob/master/seleniumbase/common/obfuscate.py) to obfuscate/encrypt passwords into coded strings:
```bash
python obfuscate.py

Enter password to obfuscate: (CTRL-C to exit)
Password: *********
Verify password:
Password: *********

Here is the obfuscated password:
$^*ENCRYPT=RXlYMSJWTz8HSwM=?&#$
```
(You can also use [unobfuscate.py](https://github.com/seleniumbase/SeleniumBase/blob/master/seleniumbase/common/unobfuscate.py) to encrypt passwords without having them masked while typing them. Or you can use it to decrypt an obfuscated pasword.)

* Finally, in your tests you can now decrypt obfuscated passwords for use in login methods like this:
```python
from seleniumbase.common import encryption
...
password = encryption.decrypt('$^*ENCRYPT=RXlYMSJWTz8HSwM=?&#$')
```
(You'll notice that encrypted strings have a common start token and end token. This is to help tell them apart from non-encrypted strings. You can customize these tokens in [settings.py](https://github.com/seleniumbase/SeleniumBase/blob/master/seleniumbase/config/settings.py). The current default setting is `$^*ENCRYPT=` for the start token and `?&#$` for the end token.)
150 changes: 150 additions & 0 deletions seleniumbase/common/encryption.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,150 @@
# -\*- coding: utf-8 -\*-

''' This is mainly for string obfuscation. '''

import base64
import codecs
import hashlib
from seleniumbase.config import settings


def str_xor(string, key):
if len(key) < 1:
raise Exception("2nd arg of str_xor() must be a string of length > 0!")
if len(string) > len(key):
difference = len(string) - len(key)
key = key + (
((difference / len(key)) * key) + key)
result = None
try:
result = "".join(
[chr(ord(c1) ^ ord(c2)) for (c1, c2) in zip(string, key)])
except:
string = string.decode('utf-8')
result = "".join(
[chr(ord(c1) ^ ord(c2)) for (c1, c2) in zip(string, key)])
return result


def is_obfuscated(string):
# Based on settings, determines if a string has already been obfuscated.
# Obfuscated strings have a common predefined start token and end token.
start_token = settings.OBFUSCATION_START_TOKEN
end_token = settings.OBFUSCATION_END_TOKEN
return (string.startswith(start_token) and string.endswith(end_token))


def shuffle_string(string):
if len(string) < 2:
return string
return (string[1::2] + string[::2])


def reverse_shuffle_string(string):
if len(string) < 2:
return string
new_string = ""
odd = (len(string) % 2 == 1)
part1 = string[:int(len(string)/2):1]
part2 = string[int(len(string)/2)::1]
for c in range(len(part1)):
new_string += part2[c]
new_string += part1[c]
if odd:
new_string += part2[-1]
return new_string


def blend_strings(string1, string2):
smallest_length = min(len(string1), len(string2))
new_string = ""
for c in range(smallest_length):
new_string += string1[c]
new_string += string2[c]
if len(string1) > len(string2):
new_string += string1[smallest_length:]
elif len(string2) > len(string1):
new_string += string2[smallest_length:]
else:
# Equal length strings
pass
return new_string


def rotate(l, n):
return l[n:] + l[:n]


def ord_string_sum(string):
count = 0
try:
for c in string:
count += ord(c)
except:
string = string.decode('utf-8')
for c in string:
count += ord(c)
return count


def decrypt(string):
# Password/String obfuscation/de-obfuscation
# Used for both encryption and decryption
# If you update the algorithm, you must re-encrypt all encrypted passwords!
encryption_key = settings.ENCRYPTION_KEY
start_token = settings.OBFUSCATION_START_TOKEN
end_token = settings.OBFUSCATION_END_TOKEN
already_encrypted = False
if is_obfuscated(string):
already_encrypted = True
string = string[len(start_token):-len(end_token)]
string = base64.b64decode(codecs.encode(string))
# Obfuscate the key used for string obfuscation
hd1 = hashlib.sha256(str(encryption_key).encode('utf-8')).hexdigest()
hd2 = hashlib.sha256(str(encryption_key[::-1]).encode('utf-8')).hexdigest()
b64_key = base64.b64encode(codecs.encode(encryption_key * 8))
xor_key = "".join([chr(ord(str(c3)) - int(c1, 16) - int(c2, 16)) for (
c1, c2, c3) in zip(hd1, hd2, b64_key.decode("utf-8"))])
xor_key = blend_strings(xor_key, encryption_key)
if len(xor_key) % 7 == 0:
xor_key = xor_key + encryption_key[-1]
xor_key = shuffle_string((xor_key * 8)[::7])
# Use the str_xor method for the main string obfuscation / de-obfuscation
if not already_encrypted:
if len(string) > 0:
rem1 = (ord_string_sum(string)) % 3
rem2 = (ord_string_sum(string)) % 4
rem3 = (ord_string_sum(string)) % 2
rem4 = (len(string) + ord_string_sum(string)) % 2
if len(string) % 2 != 0:
if rem3 == 1:
string = (chr(ord(string[-1])-5-rem1) + string +
chr(ord(string[-1])-13-rem1))
else:
string = (chr(ord(string[-1])-11-rem1) + string +
chr(ord(string[-1])-23-rem1))
elif len(string) > 1:
if rem4 == 1:
string = (chr(ord(string[0])-19+rem2) + string +
chr(ord(string[0])-7-rem2))
else:
string = (chr(ord(string[0])-26+rem2) + string +
chr(ord(string[0])-12-rem2))
rem5 = (len(string) + ord_string_sum(string)) % 23
string = rotate(string, rem5)
result = str_xor(shuffle_string(string)[::-1], xor_key)
rem6 = (len(result) + ord_string_sum(result)) % 17
result = rotate(result, rem6)
else:
rem6 = (len(string) + ord_string_sum(string)) % 17
string = rotate(string, -rem6)
result = reverse_shuffle_string(str_xor(string, xor_key)[::-1])
if len(result) > 2:
rem5 = (len(result) + ord_string_sum(result)) % 23
result = rotate(result, -rem5)
result = result[1:-1]
# Finalize encryption of non-encrypted string
if not already_encrypted:
result = base64.b64encode(codecs.encode(result))
result = start_token + result.decode("utf-8") + end_token
return result
34 changes: 34 additions & 0 deletions seleniumbase/common/obfuscate.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
"""
Obfuscates a string/password into a string that can be decrypted later on.

Usage:
python obfuscate.py
Then enter the password.
The result is an encrypted password.
"""

from seleniumbase.common import encryption
import getpass
import time


def main():
try:
while(1):
print("\nEnter password to obfuscate: (CTRL-C to exit)")
password = getpass.getpass()
print("Verify password:")
verify_password = getpass.getpass()
if password != verify_password:
print("*** ERROR: Passwords don't match! .. Please try again!")
continue
print("\nHere is the obfuscated password:")
time.sleep(0.07)
print(encryption.decrypt(password))
time.sleep(0.21)
except:
print("\nExiting...\n")


if __name__ == "__main__":
main()
34 changes: 34 additions & 0 deletions seleniumbase/common/unobfuscate.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
"""
Unobfuscates an encrypted string/password into a plaintext string/password.

Usage:
python unobfuscate.py
Then enter the encrypted string/password.
The result is a plaintext string/password.
Works the same as obfuscate.py, but doesn't mask the input.
"""

from seleniumbase.common import encryption
import time


def main():
try:
# Python 2 has the raw_input() method. Python 3 does not.
input_method = raw_input # noqa: ignore=F821
except:
input_method = input # Using Python 3
try:
while(1):
code = input_method(
'\nEnter obfuscated/encrypted string: (CTRL-C to exit):\n')
print("\nHere is the unobfuscated string/password:")
time.sleep(0.07)
print(encryption.decrypt(code))
time.sleep(0.21)
except:
print("\nExiting...\n")


if __name__ == "__main__":
main()
12 changes: 11 additions & 1 deletion seleniumbase/config/settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -94,7 +94,7 @@


# #####>>>>>----- RECOMMENDED SETTINGS -----<<<<<#####
# ##### (For database reporting and saving test logs)
# ##### (For database reporting, saving test logs, and password encryption)

# MySQL DB Credentials
# (For saving data from tests)
Expand All @@ -113,6 +113,16 @@
S3_SELENIUM_SECRET_KEY = "[S3 SECRET KEY]"


# ENCRYPTION SETTINGS
# (Used for string/password obfuscation)
# (You should reset the Encryption Key for every clone of SeleniumBase)
ENCRYPTION_KEY = "Pg^.l!8UdJ+Y7dMIe&fl*%!p9@ej]/#tL~3E4%6?"
# These tokens are added to the beginning and end of obfuscated passwords.
# Helps identify which strings/passwords have been obfuscated.
OBFUSCATION_START_TOKEN = "$^*ENCRYPT="
OBFUSCATION_END_TOKEN = "?&#$"


# #####>>>>>----- OPTIONAL SETTINGS -----<<<<<#####
# ##### (For reading emails, notifying people via chat apps, etc.)

Expand Down
4 changes: 2 additions & 2 deletions server_setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,9 @@

setup(
name='seleniumbase',
version='1.4.9',
version='1.4.10',
description='Test Automation Framework - http://seleniumbase.com',
long_description='Automation Framework for Simple & Reliable Web Testing',
long_description='Simple & Reliable Web Automation for Pytest & Nosetests',
platforms='Mac * Windows * Linux * Docker',
url='http://seleniumbase.com',
author='Michael Mintz',
Expand Down
4 changes: 2 additions & 2 deletions setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,9 @@

setup(
name='seleniumbase',
version='1.4.9',
version='1.4.10',
description='Test Automation Framework - http://seleniumbase.com',
long_description='Automation Framework for Simple & Reliable Web Testing',
long_description='Simple & Reliable Web Automation for Pytest & Nosetests',
platforms='Mac * Windows * Linux * Docker',
url='http://seleniumbase.com',
author='Michael Mintz',
Expand Down