Test Failed
Push — develop ( 5947e9...0302cd )
by Nicolas
02:20
created

glances.password.GlancesPassword.load_password()   A

Complexity

Conditions 2

Size

Total Lines 7
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 2
eloc 4
nop 1
dl 0
loc 7
rs 10
c 0
b 0
f 0
1
# -*- coding: utf-8 -*-
2
#
3
# This file is part of Glances.
4
#
5
# SPDX-FileCopyrightText: 2022 Nicolas Hennion <[email protected]>
6
#
7
# SPDX-License-Identifier: LGPL-3.0-only
8
#
9
10
"""Manage password."""
11
12
import getpass
13
import hashlib
14
import os
15
import sys
16
import uuid
17
from io import open
18
19
from glances.compat import b, input
20
from glances.config import user_config_dir
21
from glances.globals import safe_makedirs
22
from glances.logger import logger
23
24
25
class GlancesPassword(object):
26
27
    """This class contains all the methods relating to password."""
28
29
    def __init__(self, username='glances', config=None):
30
        self.username = username
31
32
        self.config = config
33
        self.password_dir = self.local_password_path()
34
        self.password_filename = self.username + '.pwd'
35
        self.password_file = os.path.join(self.password_dir, self.password_filename)
36
37
    def local_password_path(self):
38
        """Return the local password path.
39
        Related toissue: Password files in same configuration dir in effect #2143
40
        """
41
        return self.config.get_value('passwords',
42
                                     'local_password_path',
43
                                     default=user_config_dir())
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':
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