Completed
Push — master ( bcff18...adb900 )
by Nicolas
01:15
created

glances.GlancesPassword.__init__()   A

Complexity

Conditions 1

Size

Total Lines 5

Duplication

Lines 0
Ratio 0 %
Metric Value
cc 1
dl 0
loc 5
rs 9.4285
1
# -*- coding: utf-8 -*-
2
#
3
# This file is part of Glances.
4
#
5
# Copyright (C) 2015 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.globals import appname, BSD, LINUX, OSX, WINDOWS
31
from glances.logger import logger
32
33
34
class GlancesPassword(object):
35
36
    """This class contains all the methods relating to password."""
37
38
    def __init__(self, username='glances'):
39
        self.username = username
40
        self.password_path = self.get_password_path()
41
        self.password_filename = self.username + '.pwd'
42
        self.password_filepath = os.path.join(self.password_path, self.password_filename)
43
44
    def get_password_path(self):
45
        r"""Get the path where the password file will be stored.
46
47
        * Linux and BSD: ~/.config/glances
48
        * OS X: ~/Library/glances
49
        * Windows: %APPDATA%\glances
50
        """
51
        if LINUX or BSD:
52
            app_path = os.environ.get('XDG_CONFIG_HOME') or os.path.expanduser('~/.config')
53
        elif OSX:
54
            app_path = os.path.join(os.environ.get('HOME'), 'Library')
55
        elif WINDOWS:
56
            app_path = os.environ.get('APPDATA')
57
        else:
58
            app_path = '.'
59
60
        # Append the Glances folder
61
        app_path = os.path.join(app_path, appname)
62
63
        return app_path
64
65
    def sha256_hash(self, plain_password):
66
        """Return the SHA-256 of the given password."""
67
        return hashlib.sha256(b(plain_password)).hexdigest()
68
69
    def get_hash(self, salt, plain_password):
70
        """Return the hashed password, salt + SHA-256."""
71
        return hashlib.sha256(salt.encode() + plain_password.encode()).hexdigest()
72
73
    def hash_password(self, plain_password):
74
        """Hash password with a salt based on UUID (universally unique identifier)."""
75
        salt = uuid.uuid4().hex
76
        encrypted_password = self.get_hash(salt, plain_password)
77
        return salt + '$' + encrypted_password
78
79
    def check_password(self, hashed_password, plain_password):
80
        """Encode the plain_password with the salt of the hashed_password.
81
82
        Return the comparison with the encrypted_password.
83
        """
84
        salt, encrypted_password = hashed_password.split('$')
85
        re_encrypted_password = self.get_hash(salt, plain_password)
86
        return encrypted_password == re_encrypted_password
87
88
    def get_password(self, description='', confirm=False, clear=False):
89
        """Get the password from a Glances client or server.
90
91
        For Glances server, get the password (confirm=True, clear=False):
92
            1) from the password file (if it exists)
93
            2) from the CLI
94
        Optionally: save the password to a file (hashed with salt + SHA-256)
95
96
        For Glances client, get the password (confirm=False, clear=True):
97
            1) from the CLI
98
            2) the password is hashed with SHA-256 (only SHA string transit
99
               through the network)
100
        """
101
        if os.path.exists(self.password_filepath) and not clear:
102
            # If the password file exist then use it
103
            logger.info("Read password from file {0}".format(self.password_filepath))
104
            password = self.load_password()
105
        else:
106
            # password_sha256 is the plain SHA-256 password
107
            # password_hashed is the salt + SHA-256 password
108
            password_sha256 = self.sha256_hash(getpass.getpass(description))
109
            password_hashed = self.hash_password(password_sha256)
110
            if confirm:
111
                # password_confirm is the clear password (only used to compare)
112
                password_confirm = self.sha256_hash(getpass.getpass('Password (confirm): '))
113
114
                if not self.check_password(password_hashed, password_confirm):
115
                    logger.critical("Sorry, passwords do not match. Exit.")
116
                    sys.exit(1)
117
118
            # Return the plain SHA-256 or the salted password
119
            if clear:
120
                password = password_sha256
121
            else:
122
                password = password_hashed
123
124
            # Save the hashed password to the password file
125
            if not clear:
126
                save_input = input('Do you want to save the password? [Yes/No]: ')
127
                if len(save_input) > 0 and save_input[0].upper() == 'Y':
128
                    self.save_password(password_hashed)
129
130
        return password
131
132
    def save_password(self, hashed_password):
133
        """Save the hashed password to the Glances folder."""
134
        # Check if the Glances folder already exists
135
        if not os.path.exists(self.password_path):
136
            # Create the Glances folder
137
            try:
138
                os.makedirs(self.password_path)
139
            except OSError as e:
140
                logger.error("Cannot create Glances directory: {0}".format(e))
141
                return
142
143
        # Create/overwrite the password file
144
        with open(self.password_filepath, 'wb') as file_pwd:
145
            file_pwd.write(b(hashed_password))
146
147
    def load_password(self):
148
        """Load the hashed password from the Glances folder."""
149
        # Read the password file, if it exists
150
        with open(self.password_filepath, 'r') as file_pwd:
151
            hashed_password = file_pwd.read()
152
153
        return hashed_password
154