1 | # -*- coding: utf-8 -*- |
||
2 | # |
||
3 | # This file is part of Glances. |
||
4 | # |
||
5 | # Copyright (C) 2019 Nicolargo <[email protected]> |
||
6 | # |
||
7 | # Glances is free software; you can redistribute it and/or modify |
||
8 | # it under the terms of the GNU Lesser General Public License as published by |
||
9 | # the Free Software Foundation, either version 3 of the License, or |
||
10 | # (at your option) any later version. |
||
11 | # |
||
12 | # Glances is distributed in the hope that it will be useful, |
||
13 | # but WITHOUT ANY WARRANTY; without even the implied warranty of |
||
14 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
||
15 | # GNU Lesser General Public License for more details. |
||
16 | # |
||
17 | # You should have received a copy of the GNU Lesser General Public License |
||
18 | # along with this program. If not, see <http://www.gnu.org/licenses/>. |
||
19 | |||
20 | """Manage password.""" |
||
21 | |||
22 | import getpass |
||
23 | import hashlib |
||
24 | import os |
||
25 | import sys |
||
26 | import uuid |
||
27 | from io import open |
||
28 | |||
29 | from glances.compat import b, input |
||
30 | from glances.config import user_config_dir |
||
31 | from glances.globals import safe_makedirs |
||
32 | from glances.logger import logger |
||
33 | |||
34 | |||
35 | class GlancesPassword(object): |
||
36 | |||
37 | """This class contains all the methods relating to password.""" |
||
38 | |||
39 | def __init__(self, username='glances'): |
||
40 | self.username = username |
||
41 | self.password_dir = user_config_dir() |
||
42 | self.password_filename = self.username + '.pwd' |
||
43 | self.password_file = os.path.join(self.password_dir, self.password_filename) |
||
44 | |||
45 | def sha256_hash(self, plain_password): |
||
46 | """Return the SHA-256 of the given password.""" |
||
47 | return hashlib.sha256(b(plain_password)).hexdigest() |
||
48 | |||
49 | def get_hash(self, salt, plain_password): |
||
50 | """Return the hashed password, salt + SHA-256.""" |
||
51 | return hashlib.sha256(salt.encode() + plain_password.encode()).hexdigest() |
||
52 | |||
53 | def hash_password(self, plain_password): |
||
54 | """Hash password with a salt based on UUID (universally unique identifier).""" |
||
55 | salt = uuid.uuid4().hex |
||
56 | encrypted_password = self.get_hash(salt, plain_password) |
||
57 | return salt + '$' + encrypted_password |
||
58 | |||
59 | def check_password(self, hashed_password, plain_password): |
||
60 | """Encode the plain_password with the salt of the hashed_password. |
||
61 | |||
62 | Return the comparison with the encrypted_password. |
||
63 | """ |
||
64 | salt, encrypted_password = hashed_password.split('$') |
||
65 | re_encrypted_password = self.get_hash(salt, plain_password) |
||
66 | return encrypted_password == re_encrypted_password |
||
67 | |||
68 | def get_password(self, description='', confirm=False, clear=False): |
||
69 | """Get the password from a Glances client or server. |
||
70 | |||
71 | For Glances server, get the password (confirm=True, clear=False): |
||
72 | 1) from the password file (if it exists) |
||
73 | 2) from the CLI |
||
74 | Optionally: save the password to a file (hashed with salt + SHA-256) |
||
75 | |||
76 | For Glances client, get the password (confirm=False, clear=True): |
||
77 | 1) from the CLI |
||
78 | 2) the password is hashed with SHA-256 (only SHA string transit |
||
79 | through the network) |
||
80 | """ |
||
81 | if os.path.exists(self.password_file) and not clear: |
||
82 | # If the password file exist then use it |
||
83 | logger.info("Read password from file {}".format(self.password_file)) |
||
84 | password = self.load_password() |
||
85 | else: |
||
86 | # password_sha256 is the plain SHA-256 password |
||
87 | # password_hashed is the salt + SHA-256 password |
||
88 | password_sha256 = self.sha256_hash(getpass.getpass(description)) |
||
89 | password_hashed = self.hash_password(password_sha256) |
||
90 | if confirm: |
||
91 | # password_confirm is the clear password (only used to compare) |
||
92 | password_confirm = self.sha256_hash(getpass.getpass('Password (confirm): ')) |
||
93 | |||
94 | if not self.check_password(password_hashed, password_confirm): |
||
95 | logger.critical("Sorry, passwords do not match. Exit.") |
||
96 | sys.exit(1) |
||
97 | |||
98 | # Return the plain SHA-256 or the salted password |
||
99 | if clear: |
||
100 | password = password_sha256 |
||
101 | else: |
||
102 | password = password_hashed |
||
103 | |||
104 | # Save the hashed password to the password file |
||
105 | if not clear: |
||
106 | save_input = input('Do you want to save the password? [Yes/No]: ') |
||
107 | if len(save_input) > 0 and save_input[0].upper() == 'Y': |
||
0 ignored issues
–
show
Unused Code
introduced
by
Loading history...
|
|||
108 | self.save_password(password_hashed) |
||
109 | |||
110 | return password |
||
111 | |||
112 | def save_password(self, hashed_password): |
||
113 | """Save the hashed password to the Glances folder.""" |
||
114 | # Create the glances directory |
||
115 | safe_makedirs(self.password_dir) |
||
116 | |||
117 | # Create/overwrite the password file |
||
118 | with open(self.password_file, 'wb') as file_pwd: |
||
119 | file_pwd.write(b(hashed_password)) |
||
120 | |||
121 | def load_password(self): |
||
122 | """Load the hashed password from the Glances folder.""" |
||
123 | # Read the password file, if it exists |
||
124 | with open(self.password_file, 'r') as file_pwd: |
||
125 | hashed_password = file_pwd.read() |
||
126 | |||
127 | return hashed_password |
||
128 |