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 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
- plot_width=width,
100
- plot_height=height)
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
- plot_width=width,
106
- plot_height=height)
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._server = Server(appDict, address=self._address, port=self._bokehPort)
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 call, check_output
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.62
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/bokeh_gui.py,sha256=8KhIGv6GrbbzOLm97hXSSKymDaR0ambg0-1ZSFgD0G4,38903
4
- p3lib/boot_manager.py,sha256=Gx7A8gdRHnpvk1sh5k4lixJQSCxA4TKCe-qGmJYWtyI,12650
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=XwtvokORmlNaeA1JPCChrgGAxKkcqU-MitJAXeHpUvI,9848
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.62.dist-info/LICENSE,sha256=igqTy5u0kVWM1n-NUZMvAlinY6lVjAXKoag0okkS8V8,1067
16
- p3lib-1.1.62.dist-info/METADATA,sha256=iYYCOwh4W8N_YMlW2UTRN7T7s9SVfHl4a6ZfazhpgHU,920
17
- p3lib-1.1.62.dist-info/WHEEL,sha256=oiQVh_5PnQM0E3gPdiz09WCNmwiHDMaGer_elqB3coM,92
18
- p3lib-1.1.62.dist-info/top_level.txt,sha256=SDCpXYh-19yCFp4Z8ZK4B-3J4NvTCJElZ42NPgcR6-U,6
19
- p3lib-1.1.62.dist-info/RECORD,,
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