p3lib 1.1.62__py3-none-any.whl → 1.1.64__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- p3lib/bokeh_auth.py +249 -0
- p3lib/bokeh_gui.py +23 -6
- p3lib/boot_manager.py +0 -2
- p3lib/netif.py +1 -3
- {p3lib-1.1.62.dist-info → p3lib-1.1.64.dist-info}/METADATA +1 -1
- {p3lib-1.1.62.dist-info → p3lib-1.1.64.dist-info}/RECORD +9 -8
- {p3lib-1.1.62.dist-info → p3lib-1.1.64.dist-info}/LICENSE +0 -0
- {p3lib-1.1.62.dist-info → p3lib-1.1.64.dist-info}/WHEEL +0 -0
- {p3lib-1.1.62.dist-info → p3lib-1.1.64.dist-info}/top_level.txt +0 -0
p3lib/bokeh_auth.py
ADDED
@@ -0,0 +1,249 @@
|
|
1
|
+
'''
|
2
|
+
|
3
|
+
This file is a modified version of the Bokeh authorisation example code.
|
4
|
+
Many thanks to bokeh for this.
|
5
|
+
|
6
|
+
'''
|
7
|
+
import os
|
8
|
+
import json
|
9
|
+
import tornado
|
10
|
+
from tornado.web import RequestHandler
|
11
|
+
from argon2 import PasswordHasher
|
12
|
+
from argon2.exceptions import VerificationError
|
13
|
+
|
14
|
+
# could define get_user_async instead
|
15
|
+
def get_user(request_handler):
|
16
|
+
user = request_handler.get_cookie("user")
|
17
|
+
return user
|
18
|
+
|
19
|
+
# could also define get_login_url function (but must give up LoginHandler)
|
20
|
+
login_url = "/login"
|
21
|
+
|
22
|
+
# optional login page for login_url
|
23
|
+
class LoginHandler(RequestHandler):
|
24
|
+
|
25
|
+
def get(self):
|
26
|
+
try:
|
27
|
+
errormessage = self.get_argument("error")
|
28
|
+
except Exception:
|
29
|
+
errormessage = ""
|
30
|
+
self.render("login.html", errormessage=errormessage)
|
31
|
+
|
32
|
+
def set_credentials(self, credHasher):
|
33
|
+
"""@brief Set the object that has details of the valid usernames and passwords."""
|
34
|
+
self._credHasher = credHasher
|
35
|
+
|
36
|
+
def check_permission(self, username, password):
|
37
|
+
"""@brief Check if we the username and password are valid
|
38
|
+
@return True if the username and password are valid."""
|
39
|
+
valid = False
|
40
|
+
# If we have details of the valid usernames and passwords.
|
41
|
+
if hasattr(self, '_credHasher'):
|
42
|
+
if self._credHasher.valid(username, password):
|
43
|
+
valid = True
|
44
|
+
return valid
|
45
|
+
|
46
|
+
def post(self):
|
47
|
+
username = self.get_argument("username", "")
|
48
|
+
password = self.get_argument("password", "")
|
49
|
+
auth = self.check_permission(username, password)
|
50
|
+
if auth:
|
51
|
+
self.set_current_user(username)
|
52
|
+
self.redirect("/")
|
53
|
+
else:
|
54
|
+
error_msg = "?error=" + tornado.escape.url_escape("Login incorrect")
|
55
|
+
self.redirect(login_url + error_msg)
|
56
|
+
|
57
|
+
def set_current_user(self, user):
|
58
|
+
if user:
|
59
|
+
self.set_cookie("user", tornado.escape.json_encode(user))
|
60
|
+
else:
|
61
|
+
self.clear_cookie("user")
|
62
|
+
|
63
|
+
# optional logout_url, available as curdoc().session_context.logout_url
|
64
|
+
logout_url = "/logout"
|
65
|
+
|
66
|
+
# optional logout handler for logout_url
|
67
|
+
class LogoutHandler(RequestHandler):
|
68
|
+
|
69
|
+
def get(self):
|
70
|
+
self.clear_cookie("user")
|
71
|
+
self.redirect("/")
|
72
|
+
|
73
|
+
class CredentialsHasherExeption(Exception):
|
74
|
+
pass
|
75
|
+
|
76
|
+
|
77
|
+
class CredentialsHasher(object):
|
78
|
+
"""@brief Responsible for storing hashed credentials to a local file.
|
79
|
+
There are issues storing hashed credentials and so this is not
|
80
|
+
recommended for high security systems but is aimed at providing
|
81
|
+
a simple credentials storage solution for Bokeh servers."""
|
82
|
+
|
83
|
+
def __init__(self, credentialsJsonFile):
|
84
|
+
"""@brief Construct an object that can be used to generate a credentials has file and check
|
85
|
+
credentials entered by a user.
|
86
|
+
@param credentialsJsonFile A file that contains the hashed (via argon2) login credentials."""
|
87
|
+
self._credentialsJsonFile = credentialsJsonFile
|
88
|
+
self._passwordHasher = PasswordHasher()
|
89
|
+
self._credDict = self._getCredDict()
|
90
|
+
|
91
|
+
def _getCredDict(self):
|
92
|
+
"""@brief Get a dictionary containing the current credentials.
|
93
|
+
@return A dict containing the credentials.
|
94
|
+
value = username
|
95
|
+
key = hashed password."""
|
96
|
+
credDict = {}
|
97
|
+
# If the hash file exists
|
98
|
+
if os.path.isfile(self._credentialsJsonFile):
|
99
|
+
# Add the hash a a line in the file
|
100
|
+
with open(self._credentialsJsonFile, 'r') as fd:
|
101
|
+
contents = fd.read()
|
102
|
+
credDict = json.loads(contents)
|
103
|
+
return credDict
|
104
|
+
|
105
|
+
def isUsernameAvailable(self, username):
|
106
|
+
"""@brief Determine if the username is not already used.
|
107
|
+
@param username The login username.
|
108
|
+
@return True if the username is not already used."""
|
109
|
+
usernameAvailable = True
|
110
|
+
if username in self._credDict:
|
111
|
+
usernameAvailable = False
|
112
|
+
return usernameAvailable
|
113
|
+
|
114
|
+
def _saveCredentials(self):
|
115
|
+
"""@brief Save the cr3edentials to the file."""
|
116
|
+
with open(self._credentialsJsonFile, 'w', encoding='utf-8') as f:
|
117
|
+
json.dump(self._credDict, f, ensure_ascii=False, indent=4)
|
118
|
+
|
119
|
+
def add(self, username, password):
|
120
|
+
"""@brief Add credential to the stored hashes.
|
121
|
+
@param username The login username.
|
122
|
+
@param password The login password."""
|
123
|
+
if self.isUsernameAvailable(username):
|
124
|
+
hash = self._passwordHasher.hash(password)
|
125
|
+
self._credDict[username] = hash
|
126
|
+
self._saveCredentials()
|
127
|
+
|
128
|
+
else:
|
129
|
+
raise CredentialsHasherExeption(f"{username} username is already in use.")
|
130
|
+
|
131
|
+
def remove(self, username):
|
132
|
+
"""@brief Remove a user from the stored hashes.
|
133
|
+
If the username is not present then this method will return without an error.
|
134
|
+
@param username The login username.
|
135
|
+
@return True if the username/password was removed"""
|
136
|
+
removed = False
|
137
|
+
if username in self._credDict:
|
138
|
+
del self._credDict[username]
|
139
|
+
self._saveCredentials()
|
140
|
+
removed = True
|
141
|
+
return removed
|
142
|
+
|
143
|
+
def verify(self, username, password):
|
144
|
+
"""@brief Check the credentials are valid and stored in the hash file.
|
145
|
+
@param username The login username.
|
146
|
+
@param password The login password.
|
147
|
+
@return True if the username and password are authorised."""
|
148
|
+
validCredential = False
|
149
|
+
if username in self._credDict:
|
150
|
+
storedHash = self._credDict[username]
|
151
|
+
try:
|
152
|
+
self._passwordHasher.verify(storedHash, password)
|
153
|
+
validCredential = True
|
154
|
+
|
155
|
+
except VerificationError:
|
156
|
+
pass
|
157
|
+
|
158
|
+
return validCredential
|
159
|
+
|
160
|
+
def getCredentialCount(self):
|
161
|
+
"""@brief Get the number of credentials that are stored.
|
162
|
+
@return The number of credentials stored."""
|
163
|
+
return len(self._credDict.keys())
|
164
|
+
|
165
|
+
def getUsernameList(self):
|
166
|
+
"""@brief Get a list of usernames.
|
167
|
+
@return A list of usernames."""
|
168
|
+
return list(self._credDict.keys())
|
169
|
+
|
170
|
+
class CredentialsManager(object):
|
171
|
+
"""@brief Responsible for allowing the user to add and remove credentials to a a local file."""
|
172
|
+
|
173
|
+
def __init__(self, uio, credentialsJsonFile):
|
174
|
+
"""@brief Constructor.
|
175
|
+
@param uio A UIO instance that allows user input output.
|
176
|
+
@param credentialsJsonFile A file that contains the hashed (via argon2) login credentials."""
|
177
|
+
self._uio = uio
|
178
|
+
self._credentialsJsonFile = credentialsJsonFile
|
179
|
+
self.credentialsHasher = CredentialsHasher(self._credentialsJsonFile)
|
180
|
+
|
181
|
+
def _add(self):
|
182
|
+
"""@brief Add a username/password to the list of credentials."""
|
183
|
+
self._uio.info('Add a username/password')
|
184
|
+
username = self._uio.getInput('Enter the username: ')
|
185
|
+
if self.credentialsHasher.isUsernameAvailable(username):
|
186
|
+
password = self._uio.getInput('Enter the password: ')
|
187
|
+
self.credentialsHasher.add(username, password)
|
188
|
+
else:
|
189
|
+
self._uio.error(f"{username} is already in use.")
|
190
|
+
|
191
|
+
def _delete(self):
|
192
|
+
"""@brief Delete a username/password from the list of credentials."""
|
193
|
+
self._uio.info('Delete a username/password')
|
194
|
+
username = self._uio.getInput('Enter the username: ')
|
195
|
+
if not self.credentialsHasher.isUsernameAvailable(username):
|
196
|
+
if self.credentialsHasher.remove(username):
|
197
|
+
self._uio.info(f"Removed {username}")
|
198
|
+
else:
|
199
|
+
self._uio.error(f"Failed to remove {username}.")
|
200
|
+
|
201
|
+
else:
|
202
|
+
self._uio.error(f"{username} not found.")
|
203
|
+
|
204
|
+
def _check(self):
|
205
|
+
"""@brief Check a username/password from the list of credentials."""
|
206
|
+
self._uio.info('Check a username/password')
|
207
|
+
username = self._uio.getInput('Enter the username: ')
|
208
|
+
password = self._uio.getInput('Enter the password: ')
|
209
|
+
if self.credentialsHasher.verify(username, password):
|
210
|
+
self._uio.info(f"The username and password match.")
|
211
|
+
else:
|
212
|
+
self._uio.error(f"The username and password do not match.")
|
213
|
+
|
214
|
+
def _showUsernames(self):
|
215
|
+
"""@brief Show the user a list of the usernames stored."""
|
216
|
+
table = [["USERNAME"]]
|
217
|
+
for username in self.credentialsHasher.getUsernameList():
|
218
|
+
table.append([username])
|
219
|
+
self._uio.showTable(table)
|
220
|
+
|
221
|
+
def manage(self):
|
222
|
+
"""@brief Allow the user to add and remove user credentials from a local file."""
|
223
|
+
while True:
|
224
|
+
self._uio.info("")
|
225
|
+
self._showUsernames()
|
226
|
+
self._uio.info(f"{self.credentialsHasher.getCredentialCount()} credentials stored in {self._credentialsJsonFile}")
|
227
|
+
self._uio.info("")
|
228
|
+
self._uio.info("A - Add a username/password.")
|
229
|
+
self._uio.info("D - Delete a username/password.")
|
230
|
+
self._uio.info("C - Check a username/password is stored.")
|
231
|
+
self._uio.info("Q - Quit.")
|
232
|
+
response = self._uio.getInput('Enter one of the above options: ')
|
233
|
+
response = response.upper()
|
234
|
+
if response == 'A':
|
235
|
+
self._add()
|
236
|
+
elif response == 'D':
|
237
|
+
self._delete()
|
238
|
+
elif response == 'C':
|
239
|
+
self._check()
|
240
|
+
elif response == 'Q':
|
241
|
+
return
|
242
|
+
else:
|
243
|
+
self._uio.error(f"{response} is an invalid response.")
|
244
|
+
|
245
|
+
|
246
|
+
|
247
|
+
|
248
|
+
|
249
|
+
|
p3lib/bokeh_gui.py
CHANGED
@@ -22,6 +22,7 @@ from bokeh.models import Range
|
|
22
22
|
from bokeh.palettes import Category20_20 as palette
|
23
23
|
from bokeh.resources import Resources
|
24
24
|
from bokeh.embed import file_html
|
25
|
+
from bokeh.server.auth_provider import AuthModule
|
25
26
|
|
26
27
|
from bokeh.plotting import save, output_file
|
27
28
|
from bokeh.layouts import gridplot, column, row
|
@@ -96,14 +97,14 @@ class TabbedGUI(object):
|
|
96
97
|
x_axis_type="datetime",
|
97
98
|
x_axis_location="below",
|
98
99
|
y_range=yrange,
|
99
|
-
|
100
|
-
|
100
|
+
width=width,
|
101
|
+
height=height)
|
101
102
|
else:
|
102
103
|
fig = figure(title=title,
|
103
104
|
x_axis_type="datetime",
|
104
105
|
x_axis_location="below",
|
105
|
-
|
106
|
-
|
106
|
+
width=width,
|
107
|
+
height=height)
|
107
108
|
|
108
109
|
fig.yaxis.axis_label = yAxisName
|
109
110
|
return fig
|
@@ -758,17 +759,19 @@ class MultiAppServer(object):
|
|
758
759
|
@return The TCP port or -1 if no port is available."""
|
759
760
|
return SingleAppServer.GetNextUnusedPort(basePort=basePort, maxPort=maxPort, bindAddress=bindAddress)
|
760
761
|
|
761
|
-
def __init__(self, address="0.0.0.0", bokehPort=0, wsOrigin="*:*"):
|
762
|
+
def __init__(self, address="0.0.0.0", bokehPort=0, wsOrigin="*:*", credentialsJsonFile=None):
|
762
763
|
"""@Constructor
|
763
764
|
@param address The address of the bokeh server.
|
764
765
|
@param bokehPort The TCP port to run the server on. If left at the default
|
765
766
|
of 0 then a spare TCP port will be used.
|
767
|
+
@param credentialsJsonFile A file that contains the json formatted hashed (via argon2) login credentials.
|
766
768
|
"""
|
767
769
|
if bokehPort == 0:
|
768
770
|
bokehPort = MultiAppServer.GetNextUnusedPort()
|
769
771
|
self._bokehPort=bokehPort
|
770
772
|
self._address=address
|
771
773
|
os.environ[MultiAppServer.BOKEH_ALLOW_WS_ORIGIN]=wsOrigin
|
774
|
+
self._credentialsJsonFile = credentialsJsonFile
|
772
775
|
|
773
776
|
def getServerPort(self):
|
774
777
|
"""@return The bokeh server port."""
|
@@ -797,7 +800,21 @@ class MultiAppServer(object):
|
|
797
800
|
#As this gets run in a thread we need to start an event loop
|
798
801
|
evtLoop = asyncio.new_event_loop()
|
799
802
|
asyncio.set_event_loop(evtLoop)
|
800
|
-
self.
|
803
|
+
if self._credentialsJsonFile is None:
|
804
|
+
self._server = Server(appDict,
|
805
|
+
address=self._address,
|
806
|
+
port=self._bokehPort)
|
807
|
+
|
808
|
+
else:
|
809
|
+
# We don't check the credentials hash file exists as this should have been
|
810
|
+
# done at a higher level. We assume that web server authoristion is required.
|
811
|
+
selfPath = os.path.dirname(os.path.abspath(__file__))
|
812
|
+
authFile = os.path.join(selfPath, "bokeh_auth.py")
|
813
|
+
authModule = AuthModule(authFile)
|
814
|
+
self._server = Server(appDict,
|
815
|
+
address=self._address,
|
816
|
+
port=self._bokehPort,
|
817
|
+
auth_provider=authModule)
|
801
818
|
self._server.start()
|
802
819
|
if openBrowser:
|
803
820
|
#Show the server in a web browser window
|
p3lib/boot_manager.py
CHANGED
@@ -286,9 +286,7 @@ class LinuxBootManager(object):
|
|
286
286
|
def getStatusLines(self):
|
287
287
|
"""@brief Get a status report.
|
288
288
|
@return Lines of text indicating the status of a previously started process."""
|
289
|
-
statusLines = []
|
290
289
|
appName, _ = self._getApp()
|
291
|
-
cmd = "{} status {}".format(LinuxBootManager.SYSTEM_CTL, appName)
|
292
290
|
p = Popen([LinuxBootManager.SYSTEM_CTL, 'status', appName], stdin=PIPE, stdout=PIPE, stderr=PIPE)
|
293
291
|
output, err = p.communicate(b"input data that is passed to subprocess' stdin")
|
294
292
|
response = output.decode() + "\n" + err.decode()
|
p3lib/netif.py
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
#!/usr/bin/env python3.8
|
2
2
|
|
3
3
|
import platform
|
4
|
-
from subprocess import
|
4
|
+
from subprocess import check_output
|
5
5
|
import socket
|
6
6
|
import struct
|
7
7
|
|
@@ -112,7 +112,6 @@ class NetIF(object):
|
|
112
112
|
key = name of interface
|
113
113
|
value = A list of <IP ADDRESS>/<NET MASK BIT COUNT>"""
|
114
114
|
netIFDict = {}
|
115
|
-
ifName = None
|
116
115
|
|
117
116
|
cmdOutput = check_output(['/sbin/ip','a'] ).decode()
|
118
117
|
netIFDict = self._getLinuxIFDict(cmdOutput, includeNoIPIF=includeNoIPIF)
|
@@ -127,7 +126,6 @@ class NetIF(object):
|
|
127
126
|
value = A list of <IP ADDRESS>/<NET MASK BIT COUNT>"""
|
128
127
|
netIFDict = {}
|
129
128
|
lines = cmdOutput.lower().split('\n')
|
130
|
-
interfaceID = 1
|
131
129
|
ifName = None
|
132
130
|
ifAddressList = []
|
133
131
|
for line in lines:
|
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.1
|
2
2
|
Name: p3lib
|
3
|
-
Version: 1.1.
|
3
|
+
Version: 1.1.64
|
4
4
|
Summary: A group of python modules for networking, plotting data, config storage, automating boot scripts, ssh access and user input output.
|
5
5
|
Home-page: https://github.com/pjaos/p3lib
|
6
6
|
Author: Paul Austen
|
@@ -1,19 +1,20 @@
|
|
1
1
|
p3lib/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
2
2
|
p3lib/ate.py,sha256=_BiqMUYNAlp4O8MkP_PAUe62Bzd8dzV4Ipv62OFS6Ok,4759
|
3
|
-
p3lib/
|
4
|
-
p3lib/
|
3
|
+
p3lib/bokeh_auth.py,sha256=u8lrdRRtnkBntwXU47ISM85myC_dhQXvv5SGYgeprdM,9816
|
4
|
+
p3lib/bokeh_gui.py,sha256=Kx8mKcKrxsaf7md6Jk252nBC8yilUFRx3nMCfVc8S4Q,39835
|
5
|
+
p3lib/boot_manager.py,sha256=-kbfYbFpO-ktKv_heUgYdvvIQrntfCQ7pBcPWqS3C0s,12551
|
5
6
|
p3lib/conduit.py,sha256=jPkjdtyCx2I6SFqcEo8y2g7rgnZ-jNY7oCuYIETzT5Q,6046
|
6
7
|
p3lib/database_if.py,sha256=mgTdSeCTQs1V10E7KuyK3xRkdaU3RXrqYpMQvosrA_M,11897
|
7
8
|
p3lib/helper.py,sha256=-B63_ncfchMwXrvyVcnj9sxwGnMJ3ASmLmpoYa4tBmM,11862
|
8
9
|
p3lib/json_networking.py,sha256=6u4s1SmypjTYPnSxHP712OgQ3ZJaxOqIkgHQ1J7Qews,9738
|
9
10
|
p3lib/mqtt_rpc.py,sha256=6LmFA1kR4HSJs9eWbOJORRHNY01L_lHWjvtE2fmY8P8,10511
|
10
|
-
p3lib/netif.py,sha256=
|
11
|
+
p3lib/netif.py,sha256=3QV5OGdHhELIf4MBj6mx5MNCtVeZ7JXoNEkeu4KzCaE,9796
|
11
12
|
p3lib/netplotly.py,sha256=PMDx-w1jtRVW6Od5u_kuKbBxNpTS_Y88mMF60puMxLM,9363
|
12
13
|
p3lib/pconfig.py,sha256=_ri9w3aauHXZp8u2YLYHBVroFR_iCqaTCwj_MRa3rHo,30153
|
13
14
|
p3lib/ssh.py,sha256=YE1ddgiEt9EeBM8evuxCV8Gnj4jz1Sv52vEKidvjrJA,38369
|
14
15
|
p3lib/uio.py,sha256=hMarPnYXnqVF23HUIeDfzREo7TMdBjrupXMY_ffuCbI,23133
|
15
|
-
p3lib-1.1.
|
16
|
-
p3lib-1.1.
|
17
|
-
p3lib-1.1.
|
18
|
-
p3lib-1.1.
|
19
|
-
p3lib-1.1.
|
16
|
+
p3lib-1.1.64.dist-info/LICENSE,sha256=igqTy5u0kVWM1n-NUZMvAlinY6lVjAXKoag0okkS8V8,1067
|
17
|
+
p3lib-1.1.64.dist-info/METADATA,sha256=C6TYAMUOgbrxycGFILpXgCLDcyRbxNgJKjGQBG8ytpE,920
|
18
|
+
p3lib-1.1.64.dist-info/WHEEL,sha256=oiQVh_5PnQM0E3gPdiz09WCNmwiHDMaGer_elqB3coM,92
|
19
|
+
p3lib-1.1.64.dist-info/top_level.txt,sha256=SDCpXYh-19yCFp4Z8ZK4B-3J4NvTCJElZ42NPgcR6-U,6
|
20
|
+
p3lib-1.1.64.dist-info/RECORD,,
|
File without changes
|
File without changes
|
File without changes
|