-
Notifications
You must be signed in to change notification settings - Fork 729
/
Copy pathmaster_password.py
206 lines (170 loc) · 6.77 KB
/
master_password.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
import secrets
import keyring
from keyring.errors import KeyringLocked, NoKeyringError
import config
from flask import current_app, session
from flask_login import current_user
from pgadmin.model import db, User, Server
from pgadmin.utils.constants import KEY_RING_SERVICE_NAME, KEY_RING_USER_NAME
from pgadmin.utils.crypto import encrypt, decrypt
MASTERPASS_CHECK_TEXT = 'ideas are bulletproof'
def set_crypt_key(_key, _new_login=True):
"""
Set the crypt key
:param _key: The key
:param _new_login: Is fresh login or password change
"""
current_app.keyManager.set(_key, _new_login)
def get_crypt_key():
"""
Returns the crypt key
:return: the key
"""
enc_key = current_app.keyManager.get()
if enc_key is None:
if config.SERVER_MODE:
if config.MASTER_PASSWORD_REQUIRED:
return False, None
# Use the session key if available
if 'pass_enc_key' in session:
return True, session['pass_enc_key']
else:
# if desktop mode and master pass and
# local os secret is disabled then use the password hash
if not config.MASTER_PASSWORD_REQUIRED and\
not config.USE_OS_SECRET_STORAGE:
return True, current_user.password
# If master pass or local os secret enabled but enc_key is still None
# or pass_enc_key not in session
return False, None
# If enc_key is available, return True with the enc_key
return True, enc_key
def get_master_password_key_from_os_secret():
# Try to get master key is from local os storage
master_key = keyring.get_password(KEY_RING_SERVICE_NAME,
KEY_RING_USER_NAME)
if not master_key:
# If master password does not exist, keychain does not ask for
# permission. This will forces to ask for permission
keyring.set_password(KEY_RING_SERVICE_NAME,
'entry_to_check_keychain_access',
'dummy_password')
return master_key
def generate_master_password_key_for_os_secret():
return secrets.token_urlsafe(12)
def validate_master_password(password):
"""
Validate the password/key against the stored encrypted text
:param password: password/key
:return: Valid or not
"""
# master pass is incorrect if decryption fails
try:
decrypted_text = decrypt(current_user.masterpass_check, password)
if isinstance(decrypted_text, bytes):
decrypted_text = decrypted_text.decode()
if MASTERPASS_CHECK_TEXT != decrypted_text:
return False
else:
return True
except Exception:
return False
def set_masterpass_check_text(password, clear=False):
"""
Set the encrypted text which will be used later to validate entered key
:param password: password/key
:param clear: remove the encrypted text
"""
try:
masterpass_check = None
if not clear:
masterpass_check = encrypt(MASTERPASS_CHECK_TEXT, password)
# set the encrypted sample text with the new
# master pass
db.session.query(User) \
.filter(User.id == current_user.id) \
.update({User.masterpass_check: masterpass_check})
db.session.commit()
except Exception:
db.session.rollback()
raise
def cleanup_master_password():
"""
Remove the master password and saved passwords from DB which are
encrypted using master password. Also remove the encrypted text
"""
# also remove the master password check string as it will help if master
# password entered/enabled again
set_masterpass_check_text('', clear=True)
from pgadmin.browser.server_groups.servers.utils \
import remove_saved_passwords
remove_saved_passwords(current_user.id)
current_app.keyManager.hard_reset()
from pgadmin.utils.driver import get_driver
driver = get_driver(config.PG_DEFAULT_DRIVER)
for server in Server.query.filter_by(user_id=current_user.id).all():
manager = driver.connection_manager(server.id)
manager.update(server)
def delete_local_storage_master_key():
"""
Deletes the auto generated master key stored in keyring
"""
if not config.SERVER_MODE and not config.USE_OS_SECRET_STORAGE:
# Retrieve from os secret storage
try:
# try to get key
master_key = keyring.get_password(KEY_RING_SERVICE_NAME,
KEY_RING_USER_NAME)
if master_key:
keyring.delete_password(KEY_RING_SERVICE_NAME,
KEY_RING_USER_NAME)
current_app.logger.warning(
'Deleted master key stored in OS password manager.')
except (NoKeyringError, KeyringLocked) as e:
error = 'Failed to delete master key stored in OS password ' \
'manager because Keyring backend not found or ' \
'access denied. Error: {0}'.format(e)
current_app.logger.warning(error)
except Exception as e:
error = 'Failed to delete master key stored in OS password ' \
'manager. Error: {0}'.format(e)
current_app.logger.warning(error)
def process_masterpass_disabled():
"""
On master password disable, remove the connection data from session as it
may have saved password which will cause trouble
:param session: Flask session
:param conn_data: connection manager copy from session if any
"""
if not config.SERVER_MODE and not config.MASTER_PASSWORD_REQUIRED \
and current_user.masterpass_check is not None:
cleanup_master_password()
return True
return False
def get_master_password_from_master_hook():
"""
This method executes specified command & returns output.
:param command: Shell command with absolute path
:return: Output of command.
"""
import subprocess
cmd = config.MASTER_PASSWORD_HOOK
command = cmd.replace('%u', current_user.username) \
if '%u' in cmd else cmd
try:
p = subprocess.Popen(command, stdout=subprocess.PIPE, shell=True)
out, err = p.communicate()
if p.returncode == 0:
output = out.decode() if hasattr(out, 'decode') else out
output = output.strip()
return output
else:
error = "Command '{0}' failed, exit-code={1} error = {2}".format(
command, p.returncode, str(err))
current_app.logger.error(error)
except Exception as e:
current_app.logger.exception(
'Failed to retrieve master password from the master password hook'
' utility.Error: {0}'.format(e)
)
return None