Test Failed
Push — develop ( d7cf39...faa4bd )
by Nicolas
04:34 queued 10s
created

glances/password.py (1 issue)

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
Do not use len(SEQUENCE) as condition value
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