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

glances.GlancesServer.add_user()   A

Complexity

Conditions 1

Size

Total Lines 4

Duplication

Lines 0
Ratio 0 %
Metric Value
cc 1
dl 0
loc 4
rs 10
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 the Glances server."""
21
22
import json
23
import socket
24
import sys
25
from base64 import b64decode
26
27
from glances.compat import SimpleXMLRPCRequestHandler, SimpleXMLRPCServer
28
from glances.autodiscover import GlancesAutoDiscoverClient
29
from glances.globals import version
30
from glances.logger import logger
31
from glances.stats import GlancesStatsServer
32
from glances.timer import Timer
33
34
35
class GlancesXMLRPCHandler(SimpleXMLRPCRequestHandler, object):
36
37
    """Main XML-RPC handler."""
38
39
    rpc_paths = ('/RPC2', )
40
41
    def end_headers(self):
42
        # Hack to add a specific header
43
        # Thk to: https://gist.github.com/rca/4063325
44
        self.send_my_headers()
45
        super(GlancesXMLRPCHandler, self).end_headers()
46
47
    def send_my_headers(self):
48
        # Specific header is here (solved the issue #227)
49
        self.send_header("Access-Control-Allow-Origin", "*")
50
51
    def authenticate(self, headers):
52
        # auth = headers.get('Authorization')
53
        try:
54
            (basic, _, encoded) = headers.get('Authorization').partition(' ')
55
        except Exception:
56
            # Client did not ask for authentidaction
57
            # If server need it then exit
58
            return not self.server.isAuth
59
        else:
60
            # Client authentication
61
            (basic, _, encoded) = headers.get('Authorization').partition(' ')
62
            assert basic == 'Basic', 'Only basic authentication supported'
63
            # Encoded portion of the header is a string
64
            # Need to convert to bytestring
65
            encoded_byte_string = encoded.encode()
66
            # Decode base64 byte string to a decoded byte string
67
            decoded_bytes = b64decode(encoded_byte_string)
68
            # Convert from byte string to a regular string
69
            decoded_string = decoded_bytes.decode()
70
            # Get the username and password from the string
71
            (username, _, password) = decoded_string.partition(':')
72
            # Check that username and password match internal global dictionary
73
            return self.check_user(username, password)
74
75
    def check_user(self, username, password):
76
        # Check username and password in the dictionary
77
        if username in self.server.user_dict:
78
            from glances.password import GlancesPassword
79
            pwd = GlancesPassword()
80
            return pwd.check_password(self.server.user_dict[username], password)
81
        else:
82
            return False
83
84
    def parse_request(self):
85
        if SimpleXMLRPCRequestHandler.parse_request(self):
86
            # Next we authenticate
87
            if self.authenticate(self.headers):
88
                return True
89
            else:
90
                # if authentication fails, tell the client
91
                self.send_error(401, 'Authentication failed')
92
        return False
93
94
    def log_message(self, log_format, *args):
95
        # No message displayed on the server side
96
        pass
97
98
99
class GlancesXMLRPCServer(SimpleXMLRPCServer, object):
100
101
    """Init a SimpleXMLRPCServer instance (IPv6-ready)."""
102
103
    def __init__(self, bind_address, bind_port=61209,
104
                 requestHandler=GlancesXMLRPCHandler):
105
106
        try:
107
            self.address_family = socket.getaddrinfo(bind_address, bind_port)[0][0]
108
        except socket.error as e:
109
            logger.error("Couldn't open socket: {0}".format(e))
110
            sys.exit(1)
111
112
        super(GlancesXMLRPCServer, self).__init__((bind_address, bind_port), requestHandler)
113
114
115
class GlancesInstance(object):
116
117
    """All the methods of this class are published as XML-RPC methods."""
118
119
    def __init__(self, cached_time=1, config=None):
120
        # Init stats
121
        self.stats = GlancesStatsServer(config)
122
123
        # Initial update
124
        self.stats.update()
125
126
        # cached_time is the minimum time interval between stats updates
127
        # i.e. XML/RPC calls will not retrieve updated info until the time
128
        # since last update is passed (will retrieve old cached info instead)
129
        self.timer = Timer(0)
130
        self.cached_time = cached_time
131
132
    def __update__(self):
133
        # Never update more than 1 time per cached_time
134
        if self.timer.finished():
135
            self.stats.update()
136
            self.timer = Timer(self.cached_time)
137
138
    def init(self):
139
        # Return the Glances version
140
        return version
141
142
    def getAll(self):
143
        # Update and return all the stats
144
        self.__update__()
145
        return json.dumps(self.stats.getAll())
146
147
    def getAllPlugins(self):
148
        # Return the plugins list
149
        return json.dumps(self.stats.getAllPlugins())
150
151
    def getAllLimits(self):
152
        # Return all the plugins limits
153
        return json.dumps(self.stats.getAllLimitsAsDict())
154
155
    def getAllViews(self):
156
        # Return all the plugins views
157
        return json.dumps(self.stats.getAllViewsAsDict())
158
159
    def getAllMonitored(self):
160
        # Return the processes monitored list
161
        # return json.dumps(self.monitors.getAll())
162
        return json.dumps(self.stats.getAll()['monitor'])
163
164
    def __getattr__(self, item):
165
        """Overwrite the getattr method in case of attribute is not found.
166
167
        The goal is to dynamically generate the API get'Stats'() methods.
168
        """
169
        header = 'get'
170
        # Check if the attribute starts with 'get'
171
        if item.startswith(header):
172
            try:
173
                # Update the stat
174
                self.__update__()
175
                # Return the attribute
176
                return getattr(self.stats, item)
177
            except Exception:
178
                # The method is not found for the plugin
179
                raise AttributeError(item)
180
        else:
181
            # Default behavior
182
            raise AttributeError(item)
183
184
185
class GlancesServer(object):
186
187
    """This class creates and manages the TCP server."""
188
189
    def __init__(self, requestHandler=GlancesXMLRPCHandler,
190
                 cached_time=1,
191
                 config=None,
192
                 args=None):
193
        # Args
194
        self.args = args
195
196
        # Init the XML RPC server
197
        try:
198
            self.server = GlancesXMLRPCServer(args.bind_address, args.port, requestHandler)
199
        except Exception as e:
200
            logger.critical("Cannot start Glances server: {0}".format(e))
201
            sys.exit(2)
202
203
        # The users dict
204
        # username / password couple
205
        # By default, no auth is needed
206
        self.server.user_dict = {}
207
        self.server.isAuth = False
208
209
        # Register functions
210
        self.server.register_introspection_functions()
211
        self.server.register_instance(GlancesInstance(cached_time, config))
212
213
        if not self.args.disable_autodiscover:
214
            # Note: The Zeroconf service name will be based on the hostname
215
            self.autodiscover_client = GlancesAutoDiscoverClient(socket.gethostname(), args)
216
        else:
217
            logger.info("Glances autodiscover announce is disabled")
218
219
    def add_user(self, username, password):
220
        """Add an user to the dictionary."""
221
        self.server.user_dict[username] = password
222
        self.server.isAuth = True
223
224
    def serve_forever(self):
225
        """Call the main loop."""
226
        self.server.serve_forever()
227
228
    def server_close(self):
229
        """Close the Glances server session."""
230
        self.server.server_close()
231
232
    def end(self):
233
        """End of the Glances server session."""
234
        if not self.args.disable_autodiscover:
235
            self.autodiscover_client.close()
236
        self.server_close()
237