mirror of
https://github.com/dkmstr/openuds.git
synced 2025-10-05 07:33:41 +03:00
Compare commits
289 Commits
nonMultiCl
...
v3.5
Author | SHA1 | Date | |
---|---|---|---|
|
e7089d9d86 | ||
|
7a367d9011 | ||
|
089b8c82dc | ||
|
df82c3854c | ||
|
f158235f16 | ||
|
fe704c0ba6 | ||
|
8d635b781b | ||
|
00c48dff69 | ||
|
01269e6d3e | ||
|
9478a86b02 | ||
|
86e8c759ec | ||
|
3d421ac38c | ||
|
adec02dc3f | ||
|
43458cbf99 | ||
|
ec2645b0a2 | ||
|
9d0df6cfae | ||
|
cfbce5aef5 | ||
|
cf6820aa2b | ||
|
1a85f60f4f | ||
|
83394f0d34 | ||
|
c34fc41f56 | ||
|
90aa455586 | ||
|
bc2328a239 | ||
|
d9d3bc452c | ||
|
08f14bff57 | ||
|
653bff420f | ||
|
73a3c89e04 | ||
|
adaabf9d83 | ||
|
3cfbdc86e0 | ||
|
ba759b3652 | ||
|
1e3478314b | ||
|
89864b11c2 | ||
|
c6a40ac182 | ||
|
7d9ffca559 | ||
|
f43b9c7bfd | ||
|
972c48ddee | ||
|
118e642700 | ||
|
dfa441871b | ||
|
18c5e3a242 | ||
|
3a4d571a6c | ||
|
3cc42e1e73 | ||
|
ffe9baa9a5 | ||
|
0b05009d3f | ||
|
b34b12ec9f | ||
|
fb70524cb3 | ||
|
4c66401e4f | ||
|
364ebd6f3a | ||
|
493cbbb4e7 | ||
|
5277a74c1c | ||
|
1e01339b93 | ||
|
9343f7c263 | ||
|
7775964d62 | ||
|
a207e8f65f | ||
|
0a0f2771ae | ||
|
ceb5fd9bde | ||
|
7bfa6a6c4f | ||
|
858b79614b | ||
|
45b47ce702 | ||
|
dd98ba5653 | ||
|
0fe5b32224 | ||
|
a0adc1ded3 | ||
|
4f5cc505d3 | ||
|
8bac68b55b | ||
|
b5412e70fd | ||
|
75cd3c4845 | ||
|
e8c45b568d | ||
|
540a2b83be | ||
|
aa4d157c30 | ||
|
69ca93586a | ||
|
cf283bba0f | ||
|
9abaada7cb | ||
|
b359892454 | ||
|
927a86c835 | ||
|
2b5aa9c9a4 | ||
|
b3047e366d | ||
|
d2ef6e3704 | ||
|
5fb4461934 | ||
|
2f5f87e122 | ||
|
d9be83863c | ||
|
5fed04d64d | ||
|
8a2e2deaf1 | ||
|
86990638dc | ||
|
40b9572233 | ||
|
5836b33299 | ||
|
282495ce0f | ||
|
2b33ffc656 | ||
|
e0149900a7 | ||
|
7bed6ac171 | ||
|
0d77e86af2 | ||
|
a179522f4c | ||
|
21c2976d82 | ||
|
ee30ab4604 | ||
|
1fba4d3f9f | ||
|
5084fec43f | ||
|
04e24d406f | ||
|
f58ef9b6d3 | ||
|
18d4147d59 | ||
|
ccd429454e | ||
|
5ce7ddc3a7 | ||
|
3dd73f4723 | ||
|
c3531f3e7e | ||
|
ba7b1c0198 | ||
|
ba90dae5d6 | ||
|
f7cd474264 | ||
|
a255b52628 | ||
|
8d93144e24 | ||
|
27d158f514 | ||
|
2b4e771709 | ||
|
3ebc0dd26f | ||
|
79739bf9b8 | ||
|
f702c144fc | ||
|
ce2d2b1c2e | ||
|
790c204b6a | ||
|
d80cf4052e | ||
|
6a86b0ff04 | ||
|
0d412c4a9a | ||
|
ac9e6dafdf | ||
|
efd0ca3f88 | ||
|
edb4a32496 | ||
|
b239ff6cab | ||
|
d55d1bc619 | ||
|
917a201483 | ||
|
4809252434 | ||
|
8be0d9702a | ||
|
36acb0b0c0 | ||
|
420b78d45d | ||
|
e1ccc62dab | ||
|
6b0d98d4eb | ||
|
7bec7bd7cc | ||
|
270957fab5 | ||
|
47c6ca42f1 | ||
|
c1f6ed376b | ||
|
250ade6aee | ||
|
bde63f7b4f | ||
|
eb4be53508 | ||
|
3003066a91 | ||
|
10805ded7e | ||
|
21c221a6db | ||
|
1857134f42 | ||
|
835dc05e63 | ||
|
4cc4af5bd1 | ||
|
986a82f225 | ||
|
90b64c1721 | ||
|
f403d4ff3e | ||
|
c5071cf348 | ||
|
679956702b | ||
|
98d7a24656 | ||
|
b67771d5f3 | ||
|
672c35c903 | ||
|
6df1bc0a50 | ||
|
01119d1914 | ||
|
a4d1ecb95f | ||
|
237f7e5b77 | ||
|
edb74ab9c6 | ||
|
86eb1a9421 | ||
|
c09ea0eb63 | ||
|
ea79ccbee1 | ||
|
da82a26dd8 | ||
|
c129c83ca0 | ||
|
d8e6de8c1e | ||
|
e0d79cb590 | ||
|
59bd6c1649 | ||
|
564f0e17de | ||
|
842212f186 | ||
|
e4b609c4ce | ||
|
741855030f | ||
|
293b7f02ad | ||
|
fddd54fa99 | ||
|
cd640af37f | ||
|
6f99b63731 | ||
|
6b3355f819 | ||
|
660cfdcd0e | ||
|
47df6c58fc | ||
|
91c90766a3 | ||
|
2a834460d1 | ||
|
5bd77676ca | ||
|
8ef97a7773 | ||
|
abafa7bfac | ||
|
dcb7b3e28e | ||
|
41aa22fadd | ||
|
d02974ad87 | ||
|
b2a067300c | ||
|
afbc75bff0 | ||
|
4c453d2b1f | ||
|
26f33626c2 | ||
|
cb8284d076 | ||
|
ef3dd893d9 | ||
|
d531a1612a | ||
|
de9c06bc2c | ||
|
2400cc99cd | ||
|
7f5c3c3bbd | ||
|
710f2fb0e4 | ||
|
ede23ad793 | ||
|
9a3913cc42 | ||
|
5bf98782ea | ||
|
3a69c9205e | ||
|
3615db877e | ||
|
2286ccaca1 | ||
|
f90bf3a421 | ||
|
df815776da | ||
|
54f7fd21dc | ||
|
8e3d90e7f3 | ||
|
afa9e0aab6 | ||
|
77b0c7c8e1 | ||
|
23afd01004 | ||
|
c30a67d363 | ||
|
aa2d268453 | ||
|
de40c72d9e | ||
|
d0b30b561c | ||
|
e485374836 | ||
|
3934f2b88d | ||
|
c72bcf4200 | ||
|
1b7076e645 | ||
|
e637f208bd | ||
|
75e54618bb | ||
|
04864e3846 | ||
|
a52be141ea | ||
|
afcbd058d1 | ||
|
8285e2daad | ||
|
03bfb3efbb | ||
|
8c4b84e7db | ||
|
4f8fe793cc | ||
|
286b320257 | ||
|
68411f0726 | ||
|
1be49a6e0e | ||
|
c21c0b44ce | ||
|
46aa9139a0 | ||
|
574b19a905 | ||
|
612646bd1c | ||
|
10d9279b89 | ||
|
a8a5063083 | ||
|
29b6613c95 | ||
|
8aa7dc3c6f | ||
|
e75d373d03 | ||
|
91d2398ade | ||
|
f4e953c9c9 | ||
|
f14f36b0d0 | ||
|
d1e51c0103 | ||
|
d38347c534 | ||
|
6fd307e86e | ||
|
51407b54ee | ||
|
91f90c8630 | ||
|
ca5b54c8e2 | ||
|
8d74055357 | ||
|
8e81d51a43 | ||
|
5ff6cdaf69 | ||
|
13cbfe26c7 | ||
|
d497235eeb | ||
|
7d8bcf2168 | ||
|
5706f9d681 | ||
|
cd06597918 | ||
|
49ce5622d6 | ||
|
de5031febf | ||
|
b29baf2a29 | ||
|
aaa909fff0 | ||
|
99ee0b00fc | ||
|
f2643df05f | ||
|
2520cce429 | ||
|
962015c355 | ||
|
582ba01014 | ||
|
eec8588628 | ||
|
37f59e952d | ||
|
46bab75a92 | ||
|
8f7421ef9d | ||
|
a7584f9e8e | ||
|
fad735bb87 | ||
|
5ba704ac8a | ||
|
3c5ef5817f | ||
|
de0db84a5d | ||
|
548b6e813d | ||
|
31b513a7ef | ||
|
fa7ce3de0b | ||
|
3a7e7b8dfc | ||
|
c9488329b9 | ||
|
55c4574021 | ||
|
59179584f2 | ||
|
92de3b01dd | ||
|
c62d62dd65 | ||
|
e02318e665 | ||
|
612ae63cf2 | ||
|
cb44662134 | ||
|
a359ff2263 | ||
|
9ca3a7cdeb | ||
|
1736cae1c1 | ||
|
727ffe0365 | ||
|
b031e0aa3c | ||
|
d7886a1281 | ||
|
09e88b60f5 | ||
|
6af0617c2a |
@@ -3,4 +3,4 @@
|
||||
FOLDER=/usr/share/UDSActor
|
||||
|
||||
cd $FOLDER
|
||||
exec python3 actor_client.py -platform xcb $@
|
||||
exec python3 -s actor_client.py -platform xcb $@
|
||||
|
@@ -69,7 +69,7 @@ if __name__ == "__main__":
|
||||
timer.start(1000)
|
||||
timer.timeout.connect(lambda *a: None) # type: ignore # timeout can be connected to a callable
|
||||
|
||||
qApp.exec_()
|
||||
qApp.exec()
|
||||
|
||||
# On windows, if no window is created, this point will never be reached.
|
||||
qApp.end()
|
||||
|
@@ -187,9 +187,9 @@ if __name__ == "__main__":
|
||||
app = QApplication(sys.argv)
|
||||
|
||||
if udsactor.platform.operations.checkPermissions() is False:
|
||||
QMessageBox.critical(None, 'UDS Actor', 'This Program must be executed as administrator', QMessageBox.Ok)
|
||||
QMessageBox.critical(None, 'UDS Actor', 'This Program must be executed as administrator', QMessageBox.Ok) # type: ignore
|
||||
sys.exit(1)
|
||||
|
||||
myapp = UDSConfigDialog()
|
||||
myapp.show()
|
||||
sys.exit(app.exec_())
|
||||
sys.exit(app.exec())
|
||||
|
@@ -40,6 +40,7 @@ import PyQt5 # pylint: disable=unused-import
|
||||
from PyQt5.QtWidgets import QApplication, QDialog, QMessageBox
|
||||
|
||||
import udsactor
|
||||
import udsactor.tools
|
||||
|
||||
from ui.setup_dialog_unmanaged_ui import Ui_UdsActorSetupDialog
|
||||
|
||||
@@ -49,6 +50,7 @@ if typing.TYPE_CHECKING:
|
||||
|
||||
logger = logging.getLogger('actor')
|
||||
|
||||
|
||||
class UDSConfigDialog(QDialog):
|
||||
_host: str = ''
|
||||
_config: udsactor.types.ActorConfigurationType
|
||||
@@ -60,65 +62,99 @@ class UDSConfigDialog(QDialog):
|
||||
self.ui = Ui_UdsActorSetupDialog()
|
||||
self.ui.setupUi(self)
|
||||
self.ui.host.setText(self._config.host)
|
||||
self.ui.validateCertificate.setCurrentIndex(1 if self._config.validateCertificate else 0)
|
||||
self.ui.validateCertificate.setCurrentIndex(
|
||||
1 if self._config.validateCertificate else 0
|
||||
)
|
||||
self.ui.logLevelComboBox.setCurrentIndex(self._config.log_level)
|
||||
self.ui.serviceToken.setText(self._config.master_token)
|
||||
self.ui.serviceToken.setText(self._config.master_token or '')
|
||||
self.ui.restrictNet.setText(self._config.restrict_net or '')
|
||||
|
||||
self.ui.testButton.setEnabled(bool(self._config.master_token and self._config.host))
|
||||
self.ui.testButton.setEnabled(
|
||||
bool(self._config.master_token and self._config.host)
|
||||
)
|
||||
|
||||
@property
|
||||
def api(self) -> udsactor.rest.UDSServerApi:
|
||||
return udsactor.rest.UDSServerApi(self.ui.host.text(), self.ui.validateCertificate.currentIndex() == 1)
|
||||
return udsactor.rest.UDSServerApi(
|
||||
self.ui.host.text(), self.ui.validateCertificate.currentIndex() == 1
|
||||
)
|
||||
|
||||
def finish(self) -> None:
|
||||
self.close()
|
||||
|
||||
def configChanged(self, text: str) -> None:
|
||||
self.ui.testButton.setEnabled(self.ui.host.text() == self._config.host and self.ui.serviceToken.text() == self._config.master_token)
|
||||
self.ui.testButton.setEnabled(
|
||||
self.ui.host.text() == self._config.host
|
||||
and self.ui.serviceToken.text() == self._config.master_token
|
||||
and self.ui.restrictNet.text() == self._config.restrict_net
|
||||
)
|
||||
|
||||
def testUDSServer(self) -> None:
|
||||
if not self._config.master_token or not self._config.host:
|
||||
self.ui.testButton.setEnabled(False)
|
||||
return
|
||||
try:
|
||||
api = udsactor.rest.UDSServerApi(self._config.host, self._config.validateCertificate)
|
||||
api = udsactor.rest.UDSServerApi(
|
||||
self._config.host, self._config.validateCertificate
|
||||
)
|
||||
if not api.test(self._config.master_token, udsactor.types.UNMANAGED):
|
||||
QMessageBox.information(
|
||||
self,
|
||||
'UDS Test',
|
||||
'Service token seems to be invalid . Please, check token validity.',
|
||||
QMessageBox.Ok
|
||||
QMessageBox.Ok,
|
||||
)
|
||||
else:
|
||||
QMessageBox.information(
|
||||
self,
|
||||
'UDS Test',
|
||||
'Configuration for {} seems to be correct.'.format(self._config.host),
|
||||
QMessageBox.Ok
|
||||
'Configuration for {} seems to be correct.'.format(
|
||||
self._config.host
|
||||
),
|
||||
QMessageBox.Ok,
|
||||
)
|
||||
except Exception:
|
||||
QMessageBox.information(
|
||||
self,
|
||||
'UDS Test',
|
||||
'Configured host {} seems to be inaccesible.'.format(self._config.host),
|
||||
QMessageBox.Ok
|
||||
QMessageBox.Ok,
|
||||
)
|
||||
|
||||
def saveConfig(self) -> None:
|
||||
# Ensure restrict_net is empty or a valid subnet
|
||||
restrictNet = self.ui.restrictNet.text().strip()
|
||||
if restrictNet:
|
||||
try:
|
||||
subnet = udsactor.tools.strToNoIPV4Network(restrictNet)
|
||||
if not subnet:
|
||||
raise Exception('Invalid subnet')
|
||||
except Exception:
|
||||
QMessageBox.information(
|
||||
self,
|
||||
'Invalid subnet',
|
||||
'Invalid subnet {}. Please, check it.'.format(restrictNet),
|
||||
QMessageBox.Ok,
|
||||
)
|
||||
return
|
||||
|
||||
# Store parameters on register for later use, notify user of registration
|
||||
self._config = udsactor.types.ActorConfigurationType(
|
||||
actorType=udsactor.types.UNMANAGED,
|
||||
host=self.ui.host.text(),
|
||||
validateCertificate=self.ui.validateCertificate.currentIndex() == 1,
|
||||
master_token=self.ui.serviceToken.text(),
|
||||
log_level=self.ui.logLevelComboBox.currentIndex()
|
||||
master_token=self.ui.serviceToken.text().strip(),
|
||||
restrict_net=restrictNet,
|
||||
log_level=self.ui.logLevelComboBox.currentIndex(),
|
||||
)
|
||||
|
||||
udsactor.platform.store.writeConfig(self._config)
|
||||
# Enables test button
|
||||
self.ui.testButton.setEnabled(True)
|
||||
# Informs the user
|
||||
QMessageBox.information(self, 'UDS Configuration', 'Configuration saved.', QMessageBox.Ok)
|
||||
QMessageBox.information(
|
||||
self, 'UDS Configuration', 'Configuration saved.', QMessageBox.Ok
|
||||
)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
@@ -127,9 +163,9 @@ if __name__ == "__main__":
|
||||
os.environ['QT_X11_NO_MITSHM'] = '1'
|
||||
|
||||
app = QApplication(sys.argv)
|
||||
|
||||
|
||||
if udsactor.platform.operations.checkPermissions() is False:
|
||||
QMessageBox.critical(None, 'UDS Actor', 'This Program must be executed as administrator', QMessageBox.Ok)
|
||||
QMessageBox.critical(None, 'UDS Actor', 'This Program must be executed as administrator', QMessageBox.Ok) # type: ignore
|
||||
sys.exit(1)
|
||||
|
||||
if len(sys.argv) > 2:
|
||||
@@ -153,4 +189,4 @@ if __name__ == "__main__":
|
||||
|
||||
myapp = UDSConfigDialog()
|
||||
myapp.show()
|
||||
sys.exit(app.exec_())
|
||||
sys.exit(app.exec())
|
||||
|
@@ -10,8 +10,8 @@
|
||||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>595</width>
|
||||
<height>220</height>
|
||||
<width>601</width>
|
||||
<height>243</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="sizePolicy">
|
||||
@@ -55,7 +55,7 @@
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>10</x>
|
||||
<y>180</y>
|
||||
<y>210</y>
|
||||
<width>181</width>
|
||||
<height>23</height>
|
||||
</rect>
|
||||
@@ -83,7 +83,7 @@
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>410</x>
|
||||
<y>180</y>
|
||||
<y>210</y>
|
||||
<width>171</width>
|
||||
<height>23</height>
|
||||
</rect>
|
||||
@@ -117,7 +117,7 @@
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>210</x>
|
||||
<y>180</y>
|
||||
<y>210</y>
|
||||
<width>181</width>
|
||||
<height>23</height>
|
||||
</rect>
|
||||
@@ -144,7 +144,7 @@
|
||||
<x>10</x>
|
||||
<y>10</y>
|
||||
<width>571</width>
|
||||
<height>161</height>
|
||||
<height>191</height>
|
||||
</rect>
|
||||
</property>
|
||||
<layout class="QFormLayout" name="formLayout">
|
||||
@@ -221,14 +221,14 @@
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="3" column="0">
|
||||
<item row="4" column="0">
|
||||
<widget class="QLabel" name="label_loglevel">
|
||||
<property name="text">
|
||||
<string>Log Level</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="3" column="1">
|
||||
<item row="4" column="1">
|
||||
<widget class="QComboBox" name="logLevelComboBox">
|
||||
<property name="currentIndex">
|
||||
<number>1</number>
|
||||
@@ -258,6 +258,23 @@
|
||||
</item>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="3" column="0">
|
||||
<widget class="QLabel" name="label_restrictNet">
|
||||
<property name="text">
|
||||
<string>Restrict Net</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="3" column="1">
|
||||
<widget class="QLineEdit" name="restrictNet">
|
||||
<property name="toolTip">
|
||||
<string>UDS user with administration rights (Will not be stored on template)</string>
|
||||
</property>
|
||||
<property name="whatsThis">
|
||||
<string><html><head/><body><p>Administrator user on UDS Server.</p><p>Note: This credential will not be stored on client. Will be used to obtain an unique token for this image.</p></body></html></string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
<zorder>label_host</zorder>
|
||||
<zorder>host</zorder>
|
||||
@@ -267,6 +284,8 @@
|
||||
<zorder>label_security</zorder>
|
||||
<zorder>label_loglevel</zorder>
|
||||
<zorder>logLevelComboBox</zorder>
|
||||
<zorder>label_restrictNet</zorder>
|
||||
<zorder>restrictNet</zorder>
|
||||
</widget>
|
||||
</widget>
|
||||
<resources>
|
||||
@@ -353,6 +372,22 @@
|
||||
</hint>
|
||||
</hints>
|
||||
</connection>
|
||||
<connection>
|
||||
<sender>restrictNet</sender>
|
||||
<signal>textChanged(QString)</signal>
|
||||
<receiver>UdsActorSetupDialog</receiver>
|
||||
<slot>configChanged()</slot>
|
||||
<hints>
|
||||
<hint type="sourcelabel">
|
||||
<x>341</x>
|
||||
<y>139</y>
|
||||
</hint>
|
||||
<hint type="destinationlabel">
|
||||
<x>295</x>
|
||||
<y>121</y>
|
||||
</hint>
|
||||
</hints>
|
||||
</connection>
|
||||
</connections>
|
||||
<slots>
|
||||
<slot>finish()</slot>
|
||||
|
@@ -185,7 +185,8 @@ class UDSActorClient(threading.Thread): # pylint: disable=too-many-instance-att
|
||||
|
||||
try:
|
||||
# Notify loging and mark it
|
||||
self._loginInfo = self.api.login(platform.operations.getCurrentUser(), platform.operations.getSessionType())
|
||||
user, sessionType = platform.operations.getCurrentUser(), platform.operations.getSessionType()
|
||||
self._loginInfo = self.api.login(user, sessionType)
|
||||
|
||||
if self._loginInfo.max_idle:
|
||||
platform.operations.initIdleDuration(self._loginInfo.max_idle)
|
||||
@@ -197,8 +198,11 @@ class UDSActorClient(threading.Thread): # pylint: disable=too-many-instance-att
|
||||
|
||||
time.sleep(1.3) # Sleeps between loop iterations
|
||||
|
||||
self.api.logout(user + self._extraLogoff, sessionType)
|
||||
logger.info('Notified logout for %s (%s)', user, sessionType) # Log logout
|
||||
|
||||
# Clean up login info
|
||||
self._loginInfo = None
|
||||
self.api.logout(platform.operations.getCurrentUser() + self._extraLogoff)
|
||||
except Exception as e:
|
||||
logger.error('Error on client loop: %s', e)
|
||||
|
||||
|
@@ -42,7 +42,7 @@ class LocalProvider(handler.Handler):
|
||||
return result._asdict()
|
||||
|
||||
def post_logout(self) -> typing.Any:
|
||||
self._service.logout(self._params['username'])
|
||||
self._service.logout(self._params['username'], self._params['session_type'])
|
||||
return 'ok'
|
||||
|
||||
def post_ping(self) -> typing.Any:
|
||||
|
@@ -38,6 +38,7 @@ from ..log import logger
|
||||
if typing.TYPE_CHECKING:
|
||||
from ..service import CommonService
|
||||
|
||||
|
||||
class PublicProvider(handler.Handler):
|
||||
def post_logout(self) -> typing.Any:
|
||||
logger.debug('Sending LOGOFF to clients')
|
||||
@@ -51,7 +52,9 @@ class PublicProvider(handler.Handler):
|
||||
logger.debug('Sending MESSAGE to clients')
|
||||
if 'message' not in self._params:
|
||||
raise Exception('Invalid message parameters')
|
||||
self._service._clientsPool.message(self._params['message']) # pylint: disable=protected-access
|
||||
self._service._clientsPool.message(
|
||||
self._params['message']
|
||||
) # pylint: disable=protected-access
|
||||
return 'ok'
|
||||
|
||||
def post_script(self) -> typing.Any:
|
||||
@@ -60,7 +63,9 @@ class PublicProvider(handler.Handler):
|
||||
raise Exception('Invalid script parameters')
|
||||
if self._params.get('user', False):
|
||||
logger.debug('Sending SCRIPT to client')
|
||||
self._service._clientsPool.executeScript(self._params['script']) # pylint: disable=protected-access
|
||||
self._service._clientsPool.executeScript(
|
||||
self._params['script']
|
||||
) # pylint: disable=protected-access
|
||||
else:
|
||||
# Execute script at server space, that is, here
|
||||
# as a parallel thread
|
||||
@@ -72,14 +77,22 @@ class PublicProvider(handler.Handler):
|
||||
logger.debug('Received Pre connection')
|
||||
if 'user' not in self._params or 'protocol' not in self._params:
|
||||
raise Exception('Invalid preConnect parameters')
|
||||
return self._service.preConnect(self._params['user'], self._params['protocol'], self._params.get('ip', 'unknown'), self._params.get('hostname', 'unknown'))
|
||||
return self._service.preConnect(
|
||||
self._params['user'],
|
||||
self._params['protocol'],
|
||||
self._params.get('ip', 'unknown'),
|
||||
self._params.get('hostname', 'unknown'),
|
||||
self._params.get('udsuser', 'unknown'),
|
||||
)
|
||||
|
||||
def get_information(self) -> typing.Any:
|
||||
# Return something useful? :)
|
||||
return 'UDS Actor Secure Server'
|
||||
|
||||
def get_screenshot(self) -> typing.Any:
|
||||
return self._service._clientsPool.screenshot() # pylint: disable=protected-access
|
||||
return (
|
||||
self._service._clientsPool.screenshot()
|
||||
) # pylint: disable=protected-access
|
||||
|
||||
def get_uuid(self) -> typing.Any:
|
||||
if self._service.isManaged():
|
||||
|
@@ -159,7 +159,7 @@ class HTTPServerThread(threading.Thread):
|
||||
# self._server.socket = ssl.wrap_socket(self._server.socket, certfile=self.certFile, server_side=True)
|
||||
|
||||
context = ssl.SSLContext(ssl.PROTOCOL_TLS_SERVER)
|
||||
context.options = ssl.CERT_NONE
|
||||
# context.options = ssl.CERT_NONE
|
||||
context.load_cert_chain(certfile=self._certFile, password=password)
|
||||
self._server.socket = context.wrap_socket(self._server.socket, server_side=True)
|
||||
|
||||
|
@@ -1 +0,0 @@
|
||||
VERSION = '3.0.0'
|
@@ -37,6 +37,7 @@ import typing
|
||||
class LocalLogger: # pylint: disable=too-few-public-methods
|
||||
linux = False
|
||||
windows = True
|
||||
serviceLogger = False
|
||||
|
||||
logger: typing.Optional[logging.Logger]
|
||||
|
||||
|
@@ -91,12 +91,12 @@ def _getInterfaces() -> typing.List[str]:
|
||||
|
||||
s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
|
||||
names = array.array(str('B'), b'\0' * space)
|
||||
outbytes = struct.unpack(str('iL'), fcntl.ioctl(
|
||||
outbytes = struct.unpack('iL', fcntl.ioctl(
|
||||
s.fileno(),
|
||||
0x8912, # SIOCGIFCONF
|
||||
struct.pack(str('iL'), space, names.buffer_info()[0])
|
||||
struct.pack('iL', space, names.buffer_info()[0])
|
||||
))[0]
|
||||
namestr = names.tostring()
|
||||
namestr = names.tobytes()
|
||||
# return namestr, outbytes
|
||||
return [namestr[i:i + offset].split(b'\0', 1)[0].decode('utf-8') for i in range(0, outbytes, length)]
|
||||
|
||||
@@ -155,7 +155,7 @@ def renameComputer(newName: str) -> bool:
|
||||
Returns True if reboot needed
|
||||
'''
|
||||
rename(newName)
|
||||
return True # Always reboot right now. Not much slower but much more better
|
||||
return True # Always reboot right now. Not much slower but much more convenient
|
||||
|
||||
|
||||
def joinDomain(domain: str, ou: str, account: str, password: str, executeInOneStep: bool = False):
|
||||
|
@@ -56,6 +56,7 @@ def readConfig() -> types.ActorConfigurationType:
|
||||
validateCertificate=uds.getboolean('validate', fallback=False),
|
||||
master_token=uds.get('master_token', None),
|
||||
own_token=uds.get('own_token', None),
|
||||
restrict_net=uds.get('restrict_net', None),
|
||||
pre_command=uds.get('pre_command', None),
|
||||
runonce_command=uds.get('runonce_command', None),
|
||||
post_command=uds.get('post_command', None),
|
||||
@@ -78,6 +79,7 @@ def writeConfig(config: types.ActorConfigurationType) -> None:
|
||||
writeIfValue(config.actorType, 'type')
|
||||
writeIfValue(config.master_token, 'master_token')
|
||||
writeIfValue(config.own_token, 'own_token')
|
||||
writeIfValue(config.restrict_net, 'restrict_net')
|
||||
writeIfValue(config.pre_command, 'pre_command')
|
||||
writeIfValue(config.post_command, 'post_command')
|
||||
writeIfValue(config.runonce_command, 'runonce_command')
|
||||
|
@@ -37,41 +37,51 @@ import typing
|
||||
import requests
|
||||
|
||||
from . import types
|
||||
from .info import VERSION
|
||||
from .version import VERSION
|
||||
|
||||
# Default public listen port
|
||||
LISTEN_PORT = 43910
|
||||
|
||||
# Default timeout
|
||||
TIMEOUT = 5 # 5 seconds is more than enought
|
||||
TIMEOUT = 5 # 5 seconds is more than enought
|
||||
|
||||
# Constants
|
||||
UNKNOWN = 'unknown'
|
||||
|
||||
|
||||
class RESTError(Exception):
|
||||
ERRCODE = 0
|
||||
|
||||
|
||||
class RESTConnectionError(RESTError):
|
||||
ERRCODE = -1
|
||||
|
||||
|
||||
# Errors ""raised"" from broker
|
||||
class RESTInvalidKeyError(RESTError):
|
||||
ERRCODE = 1
|
||||
|
||||
|
||||
class RESTUnmanagedHostError(RESTError):
|
||||
ERRCODE = 2
|
||||
|
||||
|
||||
class RESTUserServiceNotFoundError(RESTError):
|
||||
ERRCODE = 3
|
||||
|
||||
|
||||
class RESTOsManagerError(RESTError):
|
||||
ERRCODE = 4
|
||||
|
||||
|
||||
# For avoid proxy on localhost connections
|
||||
NO_PROXY = {
|
||||
'http': None,
|
||||
'https': None,
|
||||
}
|
||||
|
||||
UDS_BASE_URL = 'https://{}/uds/rest/'
|
||||
|
||||
#
|
||||
# Basic UDS Api
|
||||
#
|
||||
@@ -79,6 +89,7 @@ class UDSApi: # pylint: disable=too-few-public-methods
|
||||
"""
|
||||
Base for remote api accesses
|
||||
"""
|
||||
|
||||
_host: str
|
||||
_validateCert: bool
|
||||
_url: str
|
||||
@@ -86,12 +97,12 @@ class UDSApi: # pylint: disable=too-few-public-methods
|
||||
def __init__(self, host: str, validateCert: bool) -> None:
|
||||
self._host = host
|
||||
self._validateCert = validateCert
|
||||
self._url = "https://{}/uds/rest/".format(self._host)
|
||||
self._url = UDS_BASE_URL.format(self._host)
|
||||
# Disable logging requests messages except for errors, ...
|
||||
logging.getLogger("requests").setLevel(logging.CRITICAL)
|
||||
logging.getLogger("urllib3").setLevel(logging.ERROR)
|
||||
logging.getLogger('request').setLevel(logging.CRITICAL)
|
||||
logging.getLogger('urllib3').setLevel(logging.ERROR)
|
||||
try:
|
||||
warnings.simplefilter("ignore") # Disables all warnings
|
||||
warnings.simplefilter('ignore') # Disables all warnings
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
@@ -99,19 +110,19 @@ class UDSApi: # pylint: disable=too-few-public-methods
|
||||
def _headers(self) -> typing.MutableMapping[str, str]:
|
||||
return {
|
||||
'Content-Type': 'application/json',
|
||||
'User-Agent': 'UDS Actor v{}'.format(VERSION)
|
||||
'User-Agent': 'UDS Actor v{}'.format(VERSION),
|
||||
}
|
||||
|
||||
def _apiURL(self, method: str) -> str:
|
||||
raise NotImplementedError
|
||||
|
||||
def _doPost(
|
||||
self,
|
||||
method: str, # i.e. 'initialize', 'ready', ....
|
||||
payLoad: typing.MutableMapping[str, typing.Any],
|
||||
headers: typing.Optional[typing.MutableMapping[str, str]] = None,
|
||||
disableProxy: bool = False
|
||||
) -> typing.Any:
|
||||
self,
|
||||
method: str, # i.e. 'initialize', 'ready', ....
|
||||
payLoad: typing.MutableMapping[str, typing.Any],
|
||||
headers: typing.Optional[typing.MutableMapping[str, str]] = None,
|
||||
disableProxy: bool = False,
|
||||
) -> typing.Any:
|
||||
headers = headers or self._headers
|
||||
try:
|
||||
result = requests.post(
|
||||
@@ -120,7 +131,9 @@ class UDSApi: # pylint: disable=too-few-public-methods
|
||||
headers=headers,
|
||||
verify=self._validateCert,
|
||||
timeout=TIMEOUT,
|
||||
proxies=NO_PROXY if disableProxy else None # if not proxies wanted, enforce it
|
||||
proxies=NO_PROXY # type: ignore
|
||||
if disableProxy
|
||||
else None, # if not proxies wanted, enforce it
|
||||
)
|
||||
|
||||
if result.ok:
|
||||
@@ -139,6 +152,7 @@ class UDSApi: # pylint: disable=too-few-public-methods
|
||||
|
||||
raise RESTError(data)
|
||||
|
||||
|
||||
#
|
||||
# UDS Broker API access
|
||||
#
|
||||
@@ -148,7 +162,12 @@ class UDSServerApi(UDSApi):
|
||||
|
||||
def enumerateAuthenticators(self) -> typing.Iterable[types.AuthenticatorType]:
|
||||
try:
|
||||
result = requests.get(self._url + 'auth/auths', headers=self._headers, verify=self._validateCert, timeout=4)
|
||||
result = requests.get(
|
||||
self._url + 'auth/auths',
|
||||
headers=self._headers,
|
||||
verify=self._validateCert,
|
||||
timeout=4,
|
||||
)
|
||||
if result.ok:
|
||||
for v in sorted(result.json(), key=lambda x: x['priority']):
|
||||
yield types.AuthenticatorType(
|
||||
@@ -157,7 +176,7 @@ class UDSServerApi(UDSApi):
|
||||
auth=v['auth'],
|
||||
type=v['type'],
|
||||
priority=v['priority'],
|
||||
isCustom=v['isCustom']
|
||||
isCustom=v['isCustom'],
|
||||
)
|
||||
except Exception:
|
||||
pass
|
||||
@@ -173,7 +192,7 @@ class UDSServerApi(UDSApi):
|
||||
preCommand: str,
|
||||
runOnceCommand: str,
|
||||
postCommand: str,
|
||||
logLevel: int
|
||||
logLevel: int,
|
||||
) -> str:
|
||||
"""
|
||||
Raises an exception if could not register, or registers and returns the "authorization token"
|
||||
@@ -186,7 +205,7 @@ class UDSServerApi(UDSApi):
|
||||
'pre_command': preCommand,
|
||||
'run_once_command': runOnceCommand,
|
||||
'post_command': postCommand,
|
||||
'log_level': logLevel
|
||||
'log_level': logLevel,
|
||||
}
|
||||
|
||||
# First, try to login to REST api
|
||||
@@ -194,13 +213,23 @@ class UDSServerApi(UDSApi):
|
||||
# First, try to login
|
||||
authInfo = {'auth': auth, 'username': username, 'password': password}
|
||||
headers = self._headers
|
||||
result = requests.post(self._url + 'auth/login', data=json.dumps(authInfo), headers=headers, verify=self._validateCert)
|
||||
result = requests.post(
|
||||
self._url + 'auth/login',
|
||||
data=json.dumps(authInfo),
|
||||
headers=headers,
|
||||
verify=self._validateCert,
|
||||
)
|
||||
if not result.ok or result.json()['result'] == 'error':
|
||||
raise Exception() # Invalid credentials
|
||||
|
||||
headers['X-Auth-Token'] = result.json()['token']
|
||||
|
||||
result = requests.post(self._apiURL('register'), data=json.dumps(data), headers=headers, verify=self._validateCert)
|
||||
result = requests.post(
|
||||
self._apiURL('register'),
|
||||
data=json.dumps(data),
|
||||
headers=headers,
|
||||
verify=self._validateCert,
|
||||
)
|
||||
if result.ok:
|
||||
return result.json()['result']
|
||||
except requests.ConnectionError as e:
|
||||
@@ -212,13 +241,18 @@ class UDSServerApi(UDSApi):
|
||||
|
||||
raise RESTError(result.content.decode())
|
||||
|
||||
def initialize(self, token: str, interfaces: typing.Iterable[types.InterfaceInfoType], actor_type: typing.Optional[str]) -> types.InitializationResultType:
|
||||
def initialize(
|
||||
self,
|
||||
token: str,
|
||||
interfaces: typing.Iterable[types.InterfaceInfoType],
|
||||
actor_type: typing.Optional[str],
|
||||
) -> types.InitializationResultType:
|
||||
# Generate id list from netork cards
|
||||
payload = {
|
||||
'type': actor_type or types.MANAGED,
|
||||
'token': token,
|
||||
'version': VERSION,
|
||||
'id': [{'mac': i.mac, 'ip': i.ip} for i in interfaces]
|
||||
'id': [{'mac': i.mac, 'ip': i.ip} for i in interfaces],
|
||||
}
|
||||
r = self._doPost('initialize', payload)
|
||||
os = r['os']
|
||||
@@ -232,53 +266,55 @@ class UDSServerApi(UDSApi):
|
||||
password=os.get('password'),
|
||||
new_password=os.get('new_password'),
|
||||
ad=os.get('ad'),
|
||||
ou=os.get('ou')
|
||||
) if r['os'] else None
|
||||
ou=os.get('ou'),
|
||||
)
|
||||
if r['os']
|
||||
else None,
|
||||
)
|
||||
|
||||
def ready(self, own_token: str, secret: str, ip: str, port: int) -> types.CertificateInfoType:
|
||||
payload = {
|
||||
'token': own_token,
|
||||
'secret': secret,
|
||||
'ip': ip,
|
||||
'port': port
|
||||
}
|
||||
def ready(
|
||||
self, own_token: str, secret: str, ip: str, port: int
|
||||
) -> types.CertificateInfoType:
|
||||
payload = {'token': own_token, 'secret': secret, 'ip': ip, 'port': port}
|
||||
result = self._doPost('ready', payload)
|
||||
|
||||
return types.CertificateInfoType(
|
||||
private_key=result['private_key'],
|
||||
server_certificate=result['server_certificate'],
|
||||
password=result['password']
|
||||
password=result['password'],
|
||||
)
|
||||
|
||||
def notifyIpChange(self, own_token: str, secret: str, ip: str, port: int) -> types.CertificateInfoType:
|
||||
payload = {
|
||||
'token': own_token,
|
||||
'secret': secret,
|
||||
'ip': ip,
|
||||
'port': port
|
||||
}
|
||||
def notifyIpChange(
|
||||
self, own_token: str, secret: str, ip: str, port: int
|
||||
) -> types.CertificateInfoType:
|
||||
payload = {'token': own_token, 'secret': secret, 'ip': ip, 'port': port}
|
||||
result = self._doPost('ipchange', payload)
|
||||
|
||||
return types.CertificateInfoType(
|
||||
private_key=result['private_key'],
|
||||
server_certificate=result['server_certificate'],
|
||||
password=result['password']
|
||||
password=result['password'],
|
||||
)
|
||||
|
||||
def notifyUnmanagedCallback(self, master_token: str, secret: str, interfaces: typing.Iterable[types.InterfaceInfoType], port: int) -> types.CertificateInfoType:
|
||||
def notifyUnmanagedCallback(
|
||||
self,
|
||||
master_token: str,
|
||||
secret: str,
|
||||
interfaces: typing.Iterable[types.InterfaceInfoType],
|
||||
port: int,
|
||||
) -> types.CertificateInfoType:
|
||||
payload = {
|
||||
'id': [{'mac': i.mac, 'ip': i.ip} for i in interfaces],
|
||||
'token': master_token,
|
||||
'secret': secret,
|
||||
'port': port
|
||||
'port': port,
|
||||
}
|
||||
result = self._doPost('unmanaged', payload)
|
||||
|
||||
return types.CertificateInfoType(
|
||||
private_key=result['private_key'],
|
||||
server_certificate=result['server_certificate'],
|
||||
password=result['password']
|
||||
password=result['password'],
|
||||
)
|
||||
|
||||
def login(
|
||||
@@ -288,14 +324,11 @@ class UDSServerApi(UDSApi):
|
||||
username: str,
|
||||
sessionType: str,
|
||||
interfaces: typing.Iterable[types.InterfaceInfoType],
|
||||
secret: typing.Optional[str]
|
||||
secret: typing.Optional[str],
|
||||
) -> types.LoginResultInfoType:
|
||||
if not token:
|
||||
return types.LoginResultInfoType(
|
||||
ip='0.0.0.0',
|
||||
hostname=UNKNOWN,
|
||||
dead_line=None,
|
||||
max_idle=None
|
||||
ip='0.0.0.0', hostname=UNKNOWN, dead_line=None, max_idle=None
|
||||
)
|
||||
payload = {
|
||||
'type': actor_type or types.MANAGED,
|
||||
@@ -310,7 +343,7 @@ class UDSServerApi(UDSApi):
|
||||
ip=result['ip'],
|
||||
hostname=result['hostname'],
|
||||
dead_line=result['dead_line'],
|
||||
max_idle=result['max_idle']
|
||||
max_idle=result['max_idle'],
|
||||
)
|
||||
|
||||
def logout(
|
||||
@@ -318,29 +351,26 @@ class UDSServerApi(UDSApi):
|
||||
actor_type: typing.Optional[str],
|
||||
token: str,
|
||||
username: str,
|
||||
sessionType: str,
|
||||
interfaces: typing.Iterable[types.InterfaceInfoType],
|
||||
secret: typing.Optional[str]
|
||||
) -> None:
|
||||
secret: typing.Optional[str],
|
||||
) -> typing.Optional[str]:
|
||||
if not token:
|
||||
return
|
||||
return None
|
||||
payload = {
|
||||
'type': actor_type or types.MANAGED,
|
||||
'id': [{'mac': i.mac, 'ip': i.ip} for i in interfaces],
|
||||
'token': token,
|
||||
'username': username,
|
||||
'secret': secret or ''
|
||||
'session_type': sessionType,
|
||||
'secret': secret or '',
|
||||
}
|
||||
self._doPost('logout', payload)
|
||||
|
||||
return self._doPost('logout', payload) # Can be 'ok' or 'notified'
|
||||
|
||||
def log(self, own_token: str, level: int, message: str) -> None:
|
||||
if not own_token:
|
||||
return
|
||||
payLoad = {
|
||||
'token': own_token,
|
||||
'level': level,
|
||||
'message': message
|
||||
}
|
||||
payLoad = {'token': own_token, 'level': level, 'message': message}
|
||||
self._doPost('log', payLoad) # Ignores result...
|
||||
|
||||
def test(self, master_token: str, actorType: typing.Optional[str]) -> bool:
|
||||
@@ -359,26 +389,25 @@ class UDSClientApi(UDSApi):
|
||||
|
||||
def _apiURL(self, method: str) -> str:
|
||||
return self._url + method
|
||||
|
||||
def post(
|
||||
self,
|
||||
method: str, # i.e. 'initialize', 'ready', ....
|
||||
payLoad: typing.MutableMapping[str, typing.Any]
|
||||
) -> typing.Any:
|
||||
self,
|
||||
method: str, # i.e. 'initialize', 'ready', ....
|
||||
payLoad: typing.MutableMapping[str, typing.Any],
|
||||
) -> typing.Any:
|
||||
return self._doPost(method=method, payLoad=payLoad, disableProxy=True)
|
||||
|
||||
def register(self, callbackUrl: str) -> None:
|
||||
payLoad = {
|
||||
'callback_url': callbackUrl
|
||||
}
|
||||
payLoad = {'callback_url': callbackUrl}
|
||||
self.post('register', payLoad)
|
||||
|
||||
def unregister(self, callbackUrl: str) -> None:
|
||||
payLoad = {
|
||||
'callback_url': callbackUrl
|
||||
}
|
||||
payLoad = {'callback_url': callbackUrl}
|
||||
self.post('unregister', payLoad)
|
||||
|
||||
def login(self, username: str, sessionType: typing.Optional[str] = None) -> types.LoginResultInfoType:
|
||||
def login(
|
||||
self, username: str, sessionType: typing.Optional[str] = None
|
||||
) -> types.LoginResultInfoType:
|
||||
payLoad = {
|
||||
'username': username,
|
||||
'session_type': sessionType or UNKNOWN,
|
||||
@@ -388,12 +417,13 @@ class UDSClientApi(UDSApi):
|
||||
ip=result['ip'],
|
||||
hostname=result['hostname'],
|
||||
dead_line=result['dead_line'],
|
||||
max_idle=result['max_idle']
|
||||
max_idle=result['max_idle'],
|
||||
)
|
||||
|
||||
def logout(self, username: str) -> None:
|
||||
def logout(self, username: str, sessionType: typing.Optional[str]) -> None:
|
||||
payLoad = {
|
||||
'username': username
|
||||
'username': username,
|
||||
'session_type': sessionType or UNKNOWN
|
||||
}
|
||||
self.post('logout', payLoad)
|
||||
|
||||
|
@@ -39,6 +39,7 @@ import typing
|
||||
from . import platform
|
||||
from . import rest
|
||||
from . import types
|
||||
from . import tools
|
||||
|
||||
from .log import logger, DEBUG, INFO, ERROR, FATAL
|
||||
from .http import clients_pool, server, cert
|
||||
@@ -55,6 +56,7 @@ from .http import clients_pool, server, cert
|
||||
# else:
|
||||
# logger.setLevel(20000)
|
||||
|
||||
|
||||
class CommonService: # pylint: disable=too-many-instance-attributes
|
||||
_isAlive: bool = True
|
||||
_rebootRequested: bool = False
|
||||
@@ -75,7 +77,9 @@ class CommonService: # pylint: disable=too-many-instance-attributes
|
||||
logger.debug('Executing command on {}: {}'.format(section, cmdLine))
|
||||
res = subprocess.check_call(cmdLine, shell=True)
|
||||
except Exception as e:
|
||||
logger.error('Got exception executing: {} - {} - {}'.format(section, cmdLine, e))
|
||||
logger.error(
|
||||
'Got exception executing: {} - {} - {}'.format(section, cmdLine, e)
|
||||
)
|
||||
return False
|
||||
logger.debug('Result of executing cmd for {} was {}'.format(section, res))
|
||||
return True
|
||||
@@ -86,7 +90,9 @@ class CommonService: # pylint: disable=too-many-instance-attributes
|
||||
self._api = rest.UDSServerApi(self._cfg.host, self._cfg.validateCertificate)
|
||||
self._secret = secrets.token_urlsafe(33)
|
||||
self._clientsPool = clients_pool.UDSActorClientPool()
|
||||
self._certificate = cert.defaultCertificate # For being used on "unmanaged" hosts only
|
||||
self._certificate = (
|
||||
cert.defaultCertificate
|
||||
) # For being used on "unmanaged" hosts only
|
||||
self._http = None
|
||||
|
||||
# Initialzies loglevel and serviceLogger
|
||||
@@ -112,16 +118,24 @@ class CommonService: # pylint: disable=too-many-instance-attributes
|
||||
self._http.start()
|
||||
|
||||
def isManaged(self) -> bool:
|
||||
return self._cfg.actorType != types.UNMANAGED # Only "unmanaged" hosts are unmanaged, the rest are "managed"
|
||||
return (
|
||||
self._cfg.actorType != types.UNMANAGED
|
||||
) # Only "unmanaged" hosts are unmanaged, the rest are "managed"
|
||||
|
||||
def serviceInterfaceInfo(self, interfaces: typing.Optional[typing.List[types.InterfaceInfoType]] = None) -> typing.Optional[types.InterfaceInfoType]:
|
||||
def serviceInterfaceInfo(
|
||||
self, interfaces: typing.Optional[typing.List[types.InterfaceInfoType]] = None
|
||||
) -> typing.Optional[types.InterfaceInfoType]:
|
||||
"""
|
||||
returns the inteface with unique_id mac or first interface or None if no interfaces...
|
||||
"""
|
||||
interfaces = interfaces or self._interfaces # Emty interfaces is like "no ip change" because cannot be notified
|
||||
interfaces = (
|
||||
interfaces or self._interfaces
|
||||
) # Emty interfaces is like "no ip change" because cannot be notified
|
||||
if self._cfg.config and interfaces:
|
||||
try:
|
||||
return next(x for x in interfaces if x.mac.lower() == self._cfg.config.unique_id)
|
||||
return next(
|
||||
x for x in interfaces if x.mac.lower() == self._cfg.config.unique_id
|
||||
)
|
||||
except StopIteration:
|
||||
return interfaces[0]
|
||||
|
||||
@@ -152,7 +166,12 @@ class CommonService: # pylint: disable=too-many-instance-attributes
|
||||
while self._isAlive:
|
||||
counter -= 1
|
||||
try:
|
||||
self._certificate = self._api.ready(self._cfg.own_token, self._secret, srvInterface.ip, rest.LISTEN_PORT)
|
||||
self._certificate = self._api.ready(
|
||||
self._cfg.own_token,
|
||||
self._secret,
|
||||
srvInterface.ip,
|
||||
rest.LISTEN_PORT,
|
||||
)
|
||||
except rest.RESTConnectionError as e:
|
||||
if not logged: # Only log connection problems ONCE
|
||||
logged = True
|
||||
@@ -168,7 +187,9 @@ class CommonService: # pylint: disable=too-many-instance-attributes
|
||||
# Success or any error that is not recoverable (retunerd by UDS). if Error, service will be cleaned in a while.
|
||||
break
|
||||
else:
|
||||
logger.error('Could not locate IP address!!!. (Not registered with UDS)')
|
||||
logger.error(
|
||||
'Could not locate IP address!!!. (Not registered with UDS)'
|
||||
)
|
||||
|
||||
# Do not continue if not alive...
|
||||
if not self._isAlive:
|
||||
@@ -176,7 +197,9 @@ class CommonService: # pylint: disable=too-many-instance-attributes
|
||||
|
||||
# Cleans sensible data
|
||||
if self._cfg.config:
|
||||
self._cfg = self._cfg._replace(config=self._cfg.config._replace(os=None), data=None)
|
||||
self._cfg = self._cfg._replace(
|
||||
config=self._cfg.config._replace(os=None), data=None
|
||||
)
|
||||
platform.store.writeConfig(self._cfg)
|
||||
|
||||
logger.info('Service ready')
|
||||
@@ -195,10 +218,10 @@ class CommonService: # pylint: disable=too-many-instance-attributes
|
||||
self._cfg = self._cfg._replace(runonce_command=None)
|
||||
platform.store.writeConfig(self._cfg)
|
||||
if self.execute(runOnce, "runOnce"):
|
||||
# If runonce is present, will not do anythin more
|
||||
# So we have to ensure that, when runonce command is finished, reboots the machine.
|
||||
# That is, the COMMAND itself has to restart the machine!
|
||||
return False # If the command fails, continue with the rest of the operations...
|
||||
# If runonce is present, will not do anythin more
|
||||
# So we have to ensure that, when runonce command is finished, reboots the machine.
|
||||
# That is, the COMMAND itself has to restart the machine!
|
||||
return False # If the command fails, continue with the rest of the operations...
|
||||
|
||||
# Retry configuration while not stop service, config in case of error 10 times, reboot vm
|
||||
counter = 10
|
||||
@@ -208,9 +231,20 @@ class CommonService: # pylint: disable=too-many-instance-attributes
|
||||
if self._cfg.config and self._cfg.config.os:
|
||||
osData = self._cfg.config.os
|
||||
if osData.action == 'rename':
|
||||
self.rename(osData.name, osData.username, osData.password, osData.new_password)
|
||||
self.rename(
|
||||
osData.name,
|
||||
osData.username,
|
||||
osData.password,
|
||||
osData.new_password,
|
||||
)
|
||||
elif osData.action == 'rename_ad':
|
||||
self.joinDomain(osData.name, osData.ad or '', osData.ou or '', osData.username or '', osData.password or '')
|
||||
self.joinDomain(
|
||||
osData.name,
|
||||
osData.ad or '',
|
||||
osData.ou or '',
|
||||
osData.username or '',
|
||||
osData.password or '',
|
||||
)
|
||||
|
||||
if self._rebootRequested:
|
||||
try:
|
||||
@@ -234,7 +268,12 @@ class CommonService: # pylint: disable=too-many-instance-attributes
|
||||
self.getInterfaces() # Ensure we have interfaces
|
||||
if self._cfg.master_token:
|
||||
try:
|
||||
self._certificate = self._api.notifyUnmanagedCallback(self._cfg.master_token, self._secret, self._interfaces, rest.LISTEN_PORT)
|
||||
self._certificate = self._api.notifyUnmanagedCallback(
|
||||
self._cfg.master_token,
|
||||
self._secret,
|
||||
self._interfaces,
|
||||
rest.LISTEN_PORT,
|
||||
)
|
||||
except Exception as e:
|
||||
logger.error('Couuld not notify unmanaged callback: %s', e)
|
||||
|
||||
@@ -245,13 +284,17 @@ class CommonService: # pylint: disable=too-many-instance-attributes
|
||||
return
|
||||
|
||||
while self._isAlive:
|
||||
self._interfaces = list(platform.operations.getNetworkInfo())
|
||||
self._interfaces = tools.validNetworkCards(
|
||||
self._cfg.restrict_net, platform.operations.getNetworkInfo()
|
||||
)
|
||||
if self._interfaces:
|
||||
break
|
||||
self.doWait(5000)
|
||||
|
||||
def initialize(self) -> bool:
|
||||
if self._initialized or not self._cfg.host or not self._isAlive: # Not configured or not running
|
||||
if (
|
||||
self._initialized or not self._cfg.host or not self._isAlive
|
||||
): # Not configured or not running
|
||||
return False
|
||||
|
||||
self._initialized = True
|
||||
@@ -268,9 +311,15 @@ class CommonService: # pylint: disable=too-many-instance-attributes
|
||||
try:
|
||||
# If master token is present, initialize and get configuration data
|
||||
if self._cfg.master_token:
|
||||
initResult: types.InitializationResultType = self._api.initialize(self._cfg.master_token, self._interfaces, self._cfg.actorType)
|
||||
initResult: types.InitializationResultType = self._api.initialize(
|
||||
self._cfg.master_token, self._interfaces, self._cfg.actorType
|
||||
)
|
||||
if not initResult.own_token: # Not managed
|
||||
logger.debug('This host is not managed by UDS Broker (ids: {})'.format(self._interfaces))
|
||||
logger.debug(
|
||||
'This host is not managed by UDS Broker (ids: {})'.format(
|
||||
self._interfaces
|
||||
)
|
||||
)
|
||||
return False
|
||||
|
||||
# Only removes master token for managed machines (will need it on next client execution)
|
||||
@@ -279,9 +328,8 @@ class CommonService: # pylint: disable=too-many-instance-attributes
|
||||
master_token=master_token,
|
||||
own_token=initResult.own_token,
|
||||
config=types.ActorDataConfigurationType(
|
||||
unique_id=initResult.unique_id,
|
||||
os=initResult.os
|
||||
)
|
||||
unique_id=initResult.unique_id, os=initResult.os
|
||||
),
|
||||
)
|
||||
|
||||
# On first successfull initialization request, master token will dissapear for managed hosts so it will be no more available (not needed anyway)
|
||||
@@ -294,10 +342,16 @@ class CommonService: # pylint: disable=too-many-instance-attributes
|
||||
|
||||
break # Initial configuration done..
|
||||
except rest.RESTConnectionError as e:
|
||||
logger.info('Trying to inititialize connection with broker (last error: {})'.format(e))
|
||||
logger.info(
|
||||
'Trying to inititialize connection with broker (last error: {})'.format(
|
||||
e
|
||||
)
|
||||
)
|
||||
self.doWait(5000) # Wait a bit and retry
|
||||
except rest.RESTError as e: # Invalid key?
|
||||
logger.error('Error validating with broker. (Invalid token?): {}'.format(e))
|
||||
except rest.RESTError as e: # Invalid key?
|
||||
logger.error(
|
||||
'Error validating with broker. (Invalid token?): {}'.format(e)
|
||||
)
|
||||
return False
|
||||
except Exception:
|
||||
logger.exception()
|
||||
@@ -307,7 +361,9 @@ class CommonService: # pylint: disable=too-many-instance-attributes
|
||||
|
||||
def uninitialize(self):
|
||||
self._initialized = False
|
||||
self._cfg = self._cfg._replace(own_token=None) # Ensures assigned token is cleared
|
||||
self._cfg = self._cfg._replace(
|
||||
own_token=None
|
||||
) # Ensures assigned token is cleared
|
||||
|
||||
def finish(self) -> None:
|
||||
if self._http:
|
||||
@@ -321,8 +377,9 @@ class CommonService: # pylint: disable=too-many-instance-attributes
|
||||
self._cfg.actorType,
|
||||
self._cfg.own_token,
|
||||
'',
|
||||
'',
|
||||
self._interfaces,
|
||||
self._secret
|
||||
self._secret,
|
||||
)
|
||||
except Exception as e:
|
||||
logger.error('Error notifying final logout to UDS: %s', e)
|
||||
@@ -334,19 +391,33 @@ class CommonService: # pylint: disable=too-many-instance-attributes
|
||||
return # Unamanaged hosts does not changes ips. (The full initialize-login-logout process is done in a row, so at login the IP is correct)
|
||||
|
||||
try:
|
||||
if not self._cfg.own_token or not self._cfg.config or not self._cfg.config.unique_id:
|
||||
if (
|
||||
not self._cfg.own_token
|
||||
or not self._cfg.config
|
||||
or not self._cfg.config.unique_id
|
||||
):
|
||||
# Not enouth data do check
|
||||
return
|
||||
currentInterfaces = list(platform.operations.getNetworkInfo())
|
||||
currentInterfaces = tools.validNetworkCards(
|
||||
self._cfg.restrict_net, platform.operations.getNetworkInfo()
|
||||
)
|
||||
old = self.serviceInterfaceInfo()
|
||||
new = self.serviceInterfaceInfo(currentInterfaces)
|
||||
if not new or not old:
|
||||
raise Exception('No ip currently available for {}'.format(self._cfg.config.unique_id))
|
||||
raise Exception(
|
||||
'No ip currently available for {}'.format(
|
||||
self._cfg.config.unique_id
|
||||
)
|
||||
)
|
||||
if old.ip != new.ip:
|
||||
self._certificate = self._api.notifyIpChange(self._cfg.own_token, self._secret, new.ip, rest.LISTEN_PORT)
|
||||
self._certificate = self._api.notifyIpChange(
|
||||
self._cfg.own_token, self._secret, new.ip, rest.LISTEN_PORT
|
||||
)
|
||||
# Now store new addresses & interfaces...
|
||||
self._interfaces = currentInterfaces
|
||||
logger.info('Ip changed from {} to {}. Notified to UDS'.format(old.ip, new.ip))
|
||||
logger.info(
|
||||
'Ip changed from {} to {}. Notified to UDS'.format(old.ip, new.ip)
|
||||
)
|
||||
# Stop the running HTTP Thread and start a new one, with new generated cert
|
||||
self.startHttpServer()
|
||||
except Exception as e:
|
||||
@@ -354,29 +425,34 @@ class CommonService: # pylint: disable=too-many-instance-attributes
|
||||
logger.warn('Checking ips failed: {}'.format(e))
|
||||
|
||||
def rename(
|
||||
self,
|
||||
name: str,
|
||||
userName: typing.Optional[str] = None,
|
||||
oldPassword: typing.Optional[str] = None,
|
||||
newPassword: typing.Optional[str] = None
|
||||
) -> None:
|
||||
self,
|
||||
name: str,
|
||||
userName: typing.Optional[str] = None,
|
||||
oldPassword: typing.Optional[str] = None,
|
||||
newPassword: typing.Optional[str] = None,
|
||||
) -> None:
|
||||
'''
|
||||
Invoked when broker requests a rename action
|
||||
default does nothing
|
||||
'''
|
||||
hostName = platform.operations.getComputerName()
|
||||
|
||||
if hostName.lower() == name.lower():
|
||||
logger.info('Computer name is already {}'.format(hostName))
|
||||
return
|
||||
|
||||
# Check for password change request for an user
|
||||
if userName and newPassword:
|
||||
logger.info('Setting password for configured user')
|
||||
try:
|
||||
platform.operations.changeUserPassword(userName, oldPassword or '', newPassword)
|
||||
platform.operations.changeUserPassword(
|
||||
userName, oldPassword or '', newPassword
|
||||
)
|
||||
except Exception as e:
|
||||
raise Exception('Could not change password for user {} (maybe invalid current password is configured at broker): {} '.format(userName, str(e)))
|
||||
# Logs error, but continue renaming computer
|
||||
logger.error(
|
||||
'Could not change password for user {}: {}'.format(userName, e)
|
||||
)
|
||||
|
||||
if hostName.lower() == name.lower():
|
||||
logger.info('Computer name is already {}'.format(hostName))
|
||||
return
|
||||
|
||||
if platform.operations.renameComputer(name):
|
||||
self.reboot()
|
||||
@@ -389,7 +465,7 @@ class CommonService: # pylint: disable=too-many-instance-attributes
|
||||
|
||||
# Now check if every registered client is already there (if logged in OFC)
|
||||
if self._loggedIn and not self._clientsPool.ping():
|
||||
self.logout('client_unavailable')
|
||||
self.logout('client_unavailable', '')
|
||||
except Exception as e:
|
||||
logger.error('Exception on main service loop: %s', e)
|
||||
|
||||
@@ -397,13 +473,8 @@ class CommonService: # pylint: disable=too-many-instance-attributes
|
||||
# Methods that can be overriden by linux & windows Actor
|
||||
# ******************************************************
|
||||
def joinDomain( # pylint: disable=unused-argument, too-many-arguments
|
||||
self,
|
||||
name: str,
|
||||
domain: str,
|
||||
ou: str,
|
||||
account: str,
|
||||
password: str
|
||||
) -> None:
|
||||
self, name: str, domain: str, ou: str, account: str, password: str
|
||||
) -> None:
|
||||
'''
|
||||
Invoked when broker requests a "domain" action
|
||||
default does nothing
|
||||
@@ -411,19 +482,24 @@ class CommonService: # pylint: disable=too-many-instance-attributes
|
||||
logger.debug('Base join invoked: {} on {}, {}'.format(name, domain, ou))
|
||||
|
||||
# Client notifications
|
||||
def login(self, username: str, sessionType: typing.Optional[str] = None) -> types.LoginResultInfoType:
|
||||
result = types.LoginResultInfoType(ip='', hostname='', dead_line=None, max_idle=None)
|
||||
self._loggedIn = True
|
||||
|
||||
def login(
|
||||
self, username: str, sessionType: typing.Optional[str] = None
|
||||
) -> types.LoginResultInfoType:
|
||||
result = types.LoginResultInfoType(
|
||||
ip='', hostname='', dead_line=None, max_idle=None
|
||||
)
|
||||
master_token = None
|
||||
secret = None
|
||||
# If unmanaged, do initialization now, because we don't know before this
|
||||
# Also, even if not initialized, get a "login" notification token
|
||||
if not self.isManaged():
|
||||
self.initialize()
|
||||
self._initialized = (
|
||||
self.initialize()
|
||||
) # Maybe it's a local login by an unmanaged host.... On real login, will execute initilize again
|
||||
# Unamanaged, need the master token
|
||||
master_token = self._cfg.master_token
|
||||
secret = self._secret
|
||||
|
||||
|
||||
# Own token will not be set if UDS did not assigned the initialized VM to an user
|
||||
# In that case, take master token (if machine is Unamanaged version)
|
||||
token = self._cfg.own_token or master_token
|
||||
@@ -434,33 +510,43 @@ class CommonService: # pylint: disable=too-many-instance-attributes
|
||||
username,
|
||||
sessionType or '',
|
||||
self._interfaces,
|
||||
secret
|
||||
secret,
|
||||
)
|
||||
|
||||
script = platform.store.invokeScriptOnLogin()
|
||||
if script:
|
||||
script += f'{username} {sessionType or "unknown"} {self._cfg.actorType}'
|
||||
self.execute(script, 'Logon')
|
||||
if result.logged_in:
|
||||
logger.debug('Login successful')
|
||||
self._loggedIn = True
|
||||
script = platform.store.invokeScriptOnLogin()
|
||||
if script:
|
||||
logger.info('Executing script on login: {}'.format(script))
|
||||
script += f'{username} {sessionType or "unknown"} {self._cfg.actorType}'
|
||||
self.execute(script, 'Logon')
|
||||
|
||||
return result
|
||||
|
||||
def logout(self, username: str) -> None:
|
||||
self._loggedIn = False
|
||||
|
||||
master_token = self._cfg.master_token if self.isManaged() else None
|
||||
def logout(self, username: str, sessionType: typing.Optional[str]) -> None:
|
||||
master_token = self._cfg.master_token
|
||||
|
||||
# Own token will not be set if UDS did not assigned the initialized VM to an user
|
||||
# In that case, take master token (if machine is Unamanaged version)
|
||||
token = self._cfg.own_token or master_token
|
||||
if token:
|
||||
self._api.logout(
|
||||
self._cfg.actorType,
|
||||
token,
|
||||
username,
|
||||
self._interfaces,
|
||||
self._secret
|
||||
)
|
||||
# If logout is not processed (that is, not ok result), the logout has not been processed
|
||||
if (
|
||||
self._api.logout(
|
||||
self._cfg.actorType,
|
||||
token,
|
||||
username,
|
||||
sessionType or '',
|
||||
self._interfaces,
|
||||
self._secret,
|
||||
)
|
||||
!= 'ok'
|
||||
):
|
||||
logger.info('Logout from %s ignored as required by uds broker', username)
|
||||
return
|
||||
|
||||
self._loggedIn = False
|
||||
self.onLogout(username)
|
||||
|
||||
if not self.isManaged():
|
||||
@@ -487,13 +573,25 @@ class CommonService: # pylint: disable=too-many-instance-attributes
|
||||
'''
|
||||
logger.info('Service stopped')
|
||||
|
||||
def preConnect(self, userName: str, protocol: str, ip: str, hostname: str) -> str: # pylint: disable=unused-argument
|
||||
def preConnect(
|
||||
self, userName: str, protocol: str, ip: str, hostname: str, udsUserName: str
|
||||
) -> str:
|
||||
'''
|
||||
Invoked when received a PRE Connection request via REST
|
||||
Base preconnect executes the preconnect command
|
||||
'''
|
||||
if self._cfg.pre_command:
|
||||
self.execute(self._cfg.pre_command + ' {} {} {} {}'.format(userName.replace('"', '%22'), protocol, ip, hostname), 'preConnect')
|
||||
self.execute(
|
||||
self._cfg.pre_command
|
||||
+ ' {} {} {} {} {}'.format(
|
||||
userName.replace('"', '%22'),
|
||||
protocol,
|
||||
ip,
|
||||
hostname,
|
||||
udsUserName.replace('"', '%22'),
|
||||
),
|
||||
'preConnect',
|
||||
)
|
||||
|
||||
return 'ok'
|
||||
|
||||
|
@@ -28,20 +28,58 @@
|
||||
'''
|
||||
@author: Adolfo Gómez, dkmaster at dkmon dot com
|
||||
'''
|
||||
# pylint: disable=invalid-name
|
||||
import threading
|
||||
import ipaddress
|
||||
import typing
|
||||
|
||||
if typing.TYPE_CHECKING:
|
||||
from udsactor.types import InterfaceInfoType
|
||||
|
||||
from udsactor.log import logger
|
||||
|
||||
class ScriptExecutorThread(threading.Thread):
|
||||
|
||||
def __init__(self, script: str) -> None:
|
||||
super(ScriptExecutorThread, self).__init__()
|
||||
self.script = script
|
||||
|
||||
def run(self) -> None:
|
||||
from udsactor.log import logger
|
||||
|
||||
try:
|
||||
logger.debug('Executing script: {}'.format(self.script))
|
||||
exec(self.script, globals(), None) # pylint: disable=exec-used
|
||||
except Exception as e:
|
||||
logger.error('Error executing script: {}'.format(e))
|
||||
logger.exception()
|
||||
|
||||
|
||||
# Convert "X.X.X.X/X" to ipaddress.IPv4Network
|
||||
def strToNoIPV4Network(net: typing.Optional[str]) -> typing.Optional[ipaddress.IPv4Network]:
|
||||
if not net: # Empty or None
|
||||
return None
|
||||
try:
|
||||
return ipaddress.IPv4Interface(net).network
|
||||
except Exception:
|
||||
return None
|
||||
|
||||
|
||||
def validNetworkCards(
|
||||
net: typing.Optional[str], cards: typing.Iterable['InterfaceInfoType']
|
||||
) -> typing.List['InterfaceInfoType']:
|
||||
try:
|
||||
subnet = strToNoIPV4Network(net)
|
||||
except Exception as e:
|
||||
subnet = None
|
||||
|
||||
if subnet is None:
|
||||
return list(cards)
|
||||
|
||||
def isValid(ip: str, subnet: ipaddress.IPv4Network) -> bool:
|
||||
if not ip:
|
||||
return False
|
||||
try:
|
||||
return ipaddress.IPv4Address(ip) in subnet
|
||||
except Exception:
|
||||
return False
|
||||
|
||||
return [c for c in cards if isValid(c.ip, subnet)]
|
||||
|
@@ -35,6 +35,7 @@ class ActorConfigurationType(typing.NamedTuple):
|
||||
actorType: typing.Optional[str] = None
|
||||
master_token: typing.Optional[str] = None
|
||||
own_token: typing.Optional[str] = None
|
||||
restrict_net: typing.Optional[str] = None
|
||||
|
||||
pre_command: typing.Optional[str] = None
|
||||
runonce_command: typing.Optional[str] = None
|
||||
@@ -57,6 +58,10 @@ class LoginResultInfoType(typing.NamedTuple):
|
||||
dead_line: typing.Optional[int]
|
||||
max_idle: typing.Optional[int] # Not provided by broker
|
||||
|
||||
@property
|
||||
def logged_in(self) -> bool:
|
||||
return self.hostname != '' or self.ip != ''
|
||||
|
||||
class CertificateInfoType(typing.NamedTuple):
|
||||
private_key: str
|
||||
server_certificate: str
|
||||
|
1
actor/src/udsactor/version.py
Normal file
1
actor/src/udsactor/version.py
Normal file
@@ -0,0 +1 @@
|
||||
VERSION = '3.5.0'
|
@@ -34,7 +34,7 @@ import os
|
||||
import tempfile
|
||||
import typing
|
||||
|
||||
import servicemanager # pylint: disable=import-error
|
||||
import servicemanager
|
||||
|
||||
# Valid logging levels, from UDS Broker (uds.core.utils.log).
|
||||
from .. import loglevel
|
||||
@@ -42,6 +42,7 @@ from .. import loglevel
|
||||
class LocalLogger: # pylint: disable=too-few-public-methods
|
||||
linux = False
|
||||
windows = True
|
||||
serviceLogger = False
|
||||
|
||||
logger: typing.Optional[logging.Logger]
|
||||
|
||||
|
@@ -41,6 +41,8 @@ from .service import UDSActorSvc
|
||||
def setupRecoverService():
|
||||
svc_name = UDSActorSvc._svc_name_ # pylint: disable=protected-access
|
||||
|
||||
hs = None
|
||||
hscm = None
|
||||
try:
|
||||
hscm = win32service.OpenSCManager(None, None, win32service.SC_MANAGER_ALL_ACCESS)
|
||||
|
||||
@@ -57,9 +59,11 @@ def setupRecoverService():
|
||||
}
|
||||
win32service.ChangeServiceConfig2(hs, win32service.SERVICE_CONFIG_FAILURE_ACTIONS, service_failure_actions)
|
||||
finally:
|
||||
win32service.CloseServiceHandle(hs)
|
||||
if hs:
|
||||
win32service.CloseServiceHandle(hs)
|
||||
finally:
|
||||
win32service.CloseServiceHandle(hscm)
|
||||
if hscm:
|
||||
win32service.CloseServiceHandle(hscm)
|
||||
|
||||
|
||||
def run() -> None:
|
||||
|
@@ -139,7 +139,7 @@ class UDSActorSvc(win32serviceutil.ServiceFramework, CommonService):
|
||||
logger.info('Using multiple step join because configuration requests to do so')
|
||||
self.multiStepJoin(name, domain, ou, account, password)
|
||||
|
||||
def preConnect(self, userName: str, protocol: str, ip: str, hostname: str) -> str:
|
||||
def preConnect(self, userName: str, protocol: str, ip: str, hostname: str, udsUserName: str) -> str:
|
||||
logger.debug('Pre connect invoked')
|
||||
|
||||
if protocol == 'rdp': # If connection is not using rdp, skip adding user
|
||||
@@ -168,7 +168,7 @@ class UDSActorSvc(win32serviceutil.ServiceFramework, CommonService):
|
||||
self._user = None
|
||||
logger.debug('User {} already in group'.format(userName))
|
||||
|
||||
return super().preConnect(userName, protocol, ip, hostname)
|
||||
return super().preConnect(userName, protocol, ip, hostname, udsUserName)
|
||||
|
||||
def ovLogon(self, username: str, password: str) -> str:
|
||||
"""
|
||||
|
@@ -2,9 +2,10 @@
|
||||
|
||||
# Form implementation generated from reading ui file 'setup-dialog.ui'
|
||||
#
|
||||
# Created by: PyQt5 UI code generator 5.13.2
|
||||
# Created by: PyQt5 UI code generator 5.15.2
|
||||
#
|
||||
# WARNING! All changes made in this file will be lost!
|
||||
# WARNING: Any manual changes made to this file will be lost when pyuic5 is
|
||||
# run again. Do not edit this file unless you know what you are doing.
|
||||
|
||||
|
||||
from PyQt5 import QtCore, QtGui, QtWidgets
|
||||
|
@@ -2,9 +2,10 @@
|
||||
|
||||
# Form implementation generated from reading ui file 'setup-dialog-unmanaged.ui'
|
||||
#
|
||||
# Created by: PyQt5 UI code generator 5.13.2
|
||||
# Created by: PyQt5 UI code generator 5.15.2
|
||||
#
|
||||
# WARNING! All changes made in this file will be lost!
|
||||
# WARNING: Any manual changes made to this file will be lost when pyuic5 is
|
||||
# run again. Do not edit this file unless you know what you are doing.
|
||||
|
||||
|
||||
from PyQt5 import QtCore, QtGui, QtWidgets
|
||||
@@ -14,7 +15,7 @@ class Ui_UdsActorSetupDialog(object):
|
||||
def setupUi(self, UdsActorSetupDialog):
|
||||
UdsActorSetupDialog.setObjectName("UdsActorSetupDialog")
|
||||
UdsActorSetupDialog.setWindowModality(QtCore.Qt.WindowModal)
|
||||
UdsActorSetupDialog.resize(595, 220)
|
||||
UdsActorSetupDialog.resize(601, 243)
|
||||
sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Preferred, QtWidgets.QSizePolicy.Preferred)
|
||||
sizePolicy.setHorizontalStretch(0)
|
||||
sizePolicy.setVerticalStretch(0)
|
||||
@@ -34,12 +35,12 @@ class Ui_UdsActorSetupDialog(object):
|
||||
UdsActorSetupDialog.setModal(True)
|
||||
self.saveButton = QtWidgets.QPushButton(UdsActorSetupDialog)
|
||||
self.saveButton.setEnabled(True)
|
||||
self.saveButton.setGeometry(QtCore.QRect(10, 180, 181, 23))
|
||||
self.saveButton.setGeometry(QtCore.QRect(10, 210, 181, 23))
|
||||
self.saveButton.setMinimumSize(QtCore.QSize(181, 0))
|
||||
self.saveButton.setContextMenuPolicy(QtCore.Qt.DefaultContextMenu)
|
||||
self.saveButton.setObjectName("saveButton")
|
||||
self.closeButton = QtWidgets.QPushButton(UdsActorSetupDialog)
|
||||
self.closeButton.setGeometry(QtCore.QRect(410, 180, 171, 23))
|
||||
self.closeButton.setGeometry(QtCore.QRect(410, 210, 171, 23))
|
||||
sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Preferred, QtWidgets.QSizePolicy.Fixed)
|
||||
sizePolicy.setHorizontalStretch(0)
|
||||
sizePolicy.setVerticalStretch(0)
|
||||
@@ -49,11 +50,11 @@ class Ui_UdsActorSetupDialog(object):
|
||||
self.closeButton.setObjectName("closeButton")
|
||||
self.testButton = QtWidgets.QPushButton(UdsActorSetupDialog)
|
||||
self.testButton.setEnabled(False)
|
||||
self.testButton.setGeometry(QtCore.QRect(210, 180, 181, 23))
|
||||
self.testButton.setGeometry(QtCore.QRect(210, 210, 181, 23))
|
||||
self.testButton.setMinimumSize(QtCore.QSize(181, 0))
|
||||
self.testButton.setObjectName("testButton")
|
||||
self.layoutWidget = QtWidgets.QWidget(UdsActorSetupDialog)
|
||||
self.layoutWidget.setGeometry(QtCore.QRect(10, 10, 571, 161))
|
||||
self.layoutWidget.setGeometry(QtCore.QRect(10, 10, 571, 191))
|
||||
self.layoutWidget.setObjectName("layoutWidget")
|
||||
self.formLayout = QtWidgets.QFormLayout(self.layoutWidget)
|
||||
self.formLayout.setSizeConstraint(QtWidgets.QLayout.SetDefaultConstraint)
|
||||
@@ -84,7 +85,7 @@ class Ui_UdsActorSetupDialog(object):
|
||||
self.formLayout.setWidget(2, QtWidgets.QFormLayout.FieldRole, self.serviceToken)
|
||||
self.label_loglevel = QtWidgets.QLabel(self.layoutWidget)
|
||||
self.label_loglevel.setObjectName("label_loglevel")
|
||||
self.formLayout.setWidget(3, QtWidgets.QFormLayout.LabelRole, self.label_loglevel)
|
||||
self.formLayout.setWidget(4, QtWidgets.QFormLayout.LabelRole, self.label_loglevel)
|
||||
self.logLevelComboBox = QtWidgets.QComboBox(self.layoutWidget)
|
||||
self.logLevelComboBox.setFrame(True)
|
||||
self.logLevelComboBox.setObjectName("logLevelComboBox")
|
||||
@@ -96,7 +97,13 @@ class Ui_UdsActorSetupDialog(object):
|
||||
self.logLevelComboBox.setItemText(2, "ERROR")
|
||||
self.logLevelComboBox.addItem("")
|
||||
self.logLevelComboBox.setItemText(3, "FATAL")
|
||||
self.formLayout.setWidget(3, QtWidgets.QFormLayout.FieldRole, self.logLevelComboBox)
|
||||
self.formLayout.setWidget(4, QtWidgets.QFormLayout.FieldRole, self.logLevelComboBox)
|
||||
self.label_restrictNet = QtWidgets.QLabel(self.layoutWidget)
|
||||
self.label_restrictNet.setObjectName("label_restrictNet")
|
||||
self.formLayout.setWidget(3, QtWidgets.QFormLayout.LabelRole, self.label_restrictNet)
|
||||
self.restrictNet = QtWidgets.QLineEdit(self.layoutWidget)
|
||||
self.restrictNet.setObjectName("restrictNet")
|
||||
self.formLayout.setWidget(3, QtWidgets.QFormLayout.FieldRole, self.restrictNet)
|
||||
self.label_host.raise_()
|
||||
self.host.raise_()
|
||||
self.label_serviceToken.raise_()
|
||||
@@ -105,6 +112,8 @@ class Ui_UdsActorSetupDialog(object):
|
||||
self.label_security.raise_()
|
||||
self.label_loglevel.raise_()
|
||||
self.logLevelComboBox.raise_()
|
||||
self.label_restrictNet.raise_()
|
||||
self.restrictNet.raise_()
|
||||
|
||||
self.retranslateUi(UdsActorSetupDialog)
|
||||
self.logLevelComboBox.setCurrentIndex(1)
|
||||
@@ -113,6 +122,7 @@ class Ui_UdsActorSetupDialog(object):
|
||||
self.saveButton.clicked.connect(UdsActorSetupDialog.saveConfig)
|
||||
self.host.textChanged['QString'].connect(UdsActorSetupDialog.configChanged)
|
||||
self.serviceToken.textChanged['QString'].connect(UdsActorSetupDialog.configChanged)
|
||||
self.restrictNet.textChanged['QString'].connect(UdsActorSetupDialog.configChanged)
|
||||
QtCore.QMetaObject.connectSlotsByName(UdsActorSetupDialog)
|
||||
|
||||
def retranslateUi(self, UdsActorSetupDialog):
|
||||
@@ -139,4 +149,7 @@ class Ui_UdsActorSetupDialog(object):
|
||||
self.serviceToken.setToolTip(_translate("UdsActorSetupDialog", "UDS user with administration rights (Will not be stored on template)"))
|
||||
self.serviceToken.setWhatsThis(_translate("UdsActorSetupDialog", "<html><head/><body><p>Administrator user on UDS Server.</p><p>Note: This credential will not be stored on client. Will be used to obtain an unique token for this image.</p></body></html>"))
|
||||
self.label_loglevel.setText(_translate("UdsActorSetupDialog", "Log Level"))
|
||||
self.label_restrictNet.setText(_translate("UdsActorSetupDialog", "Restrict Net"))
|
||||
self.restrictNet.setToolTip(_translate("UdsActorSetupDialog", "UDS user with administration rights (Will not be stored on template)"))
|
||||
self.restrictNet.setWhatsThis(_translate("UdsActorSetupDialog", "<html><head/><body><p>Administrator user on UDS Server.</p><p>Note: This credential will not be stored on client. Will be used to obtain an unique token for this image.</p></body></html>"))
|
||||
from ui import uds_rc
|
||||
|
@@ -2,7 +2,7 @@
|
||||
|
||||
# Resource object code
|
||||
#
|
||||
# Created by: The Resource Compiler for PyQt5 (Qt v5.13.2)
|
||||
# Created by: The Resource Compiler for PyQt5 (Qt v5.15.2)
|
||||
#
|
||||
# WARNING! All changes made in this file will be lost!
|
||||
|
||||
|
@@ -14,6 +14,8 @@ APPSDIR := $(DESTDIR)/usr/share/applications
|
||||
PYC := $(shell find $(SOURCEDIR) -name '*.py[co]')
|
||||
CACHES := $(shell find $(SOURCEDIR) -name '__pycache__')
|
||||
|
||||
|
||||
|
||||
clean:
|
||||
rm -rf $(PYC) $(CACHES) $(DESTDIR)
|
||||
install:
|
||||
@@ -55,16 +57,16 @@ build-appimage:
|
||||
ifeq ($(DISTRO),x86_64)
|
||||
cat udsclient-appimage-x86_64.recipe | sed -e s/"version: 0.0.0"/"version: $(VERSION)"/g > appimage.recipe
|
||||
endif
|
||||
ifeq ($(DISTRO),armf)
|
||||
cat udsclient-appimage-x86_64.recipe | sed -e s/"version: 0.0.0"/"version: $(VERSION)"/g | sed -e s/amd64\\\|x86_64/armhf/g > appimage.recipe
|
||||
ifeq ($(DISTRO),armhf)
|
||||
cat udsclient-appimage-x86_64.recipe | sed -e s/"version: 0.0.0"/"version: $(VERSION)"/g | sed -e s/amd64/armhf/g | sed -e s/x86_64/armhf/g > appimage.recipe
|
||||
endif
|
||||
ifeq ($(DISTRO),i686)
|
||||
cat udsclient-appimage-x86_64.recipe | sed -e s/"version: 0.0.0"/"version: $(VERSION)"/g | sed -e s/amd64/i386/g | sed -e s/x86_64/i686/g > appimage.recipe
|
||||
endif
|
||||
# Ensure all working folders are "clean"
|
||||
-rm -rf appimage appimage-builder-cache /tmp/UDSClientDir
|
||||
-rm -rf appimage appimage-builder-cache /tmp/UDSClientDir appimage-build AppDir
|
||||
|
||||
appimage-builder --recipe appimage.recipe
|
||||
appimage-builder --recipe appimage.recipe --appdir /tmp/UDSClientDir
|
||||
# Now create dist and move appimage
|
||||
rm -rf $(DESTDIR)
|
||||
mkdir -p $(DESTDIR)
|
||||
@@ -77,4 +79,29 @@ endif
|
||||
tar czvf ../udsclient3-$(DISTRO)-$(VERSION).tar.gz -C $(DESTDIR) .
|
||||
|
||||
# cleanup
|
||||
-rm -rf appimage appimage-builder-cache /tmp/UDSClientDir
|
||||
-rm -rf appimage appimage-builder-cache /tmp/UDSClientDir appimage-build AppDir
|
||||
|
||||
build-igel:
|
||||
rm -rf $(DESTDIR)
|
||||
mkdir -p $(DESTDIR)
|
||||
# Calculate the size of the custom partition (15 megas more than the appimage size)
|
||||
@$(eval APPIMAGE_SIZE=$(shell du -sm UDSClient-$(VERSION)-x86_64.AppImage | cut -f1))
|
||||
@$(eval APPIMAGE_SIZE=$(shell expr $(APPIMAGE_SIZE) + 15))
|
||||
cat igel/UDSClient-Profile-template.xml | sed -e s/"_SIZE_"/"$(APPIMAGE_SIZE)M"/g > $(DESTDIR)/UDSClient-Profile.xml
|
||||
cat igel/UDSClient-template.inf | sed -e s/"_SIZE_"/"$(APPIMAGE_SIZE)M"/g > $(DESTDIR)/UDSClient.inf
|
||||
cp UDSClient-$(VERSION)-x86_64.AppImage $(DESTDIR)/UDSClient
|
||||
cp igel/UDSClient.desktop $(DESTDIR)/UDSClient.desktop
|
||||
cp igel/init.sh $(DESTDIR)/init.sh
|
||||
tar cjvf $(DESTDIR)/UDSClient.tar.bz2 -C $(DESTDIR) UDSClient UDSClient.desktop init.sh
|
||||
zip -j ../udsclient3-$(VERSION)-igel.zip $(DESTDIR)/UDSClient-Profile.xml $(DESTDIR)/UDSClient.inf $(DESTDIR)/UDSClient.tar.bz2
|
||||
cd ..
|
||||
rm -rf $(DESTDIR)
|
||||
|
||||
build-thinpro:
|
||||
rm -rf $(DESTDIR)
|
||||
mkdir -p $(DESTDIR)
|
||||
cp -r thinpro/* $(DESTDIR)
|
||||
cp UDSClient-$(VERSION)-x86_64.AppImage $(DESTDIR)/UDSClient
|
||||
tar czvf ../udsclient3-$(VERSION)-thinpro.tar.gz -C $(DESTDIR) .
|
||||
rm -rf $(DESTDIR)
|
||||
|
||||
|
@@ -43,5 +43,11 @@ make DESTDIR=appimage DISTRO=x86_64 VERSION=${VERSION} build-appimage
|
||||
make DESTDIR=appimage DISTRO=armhf VERSION=${VERSION} build-appimage
|
||||
make DESTDIR=appimage DISTRO=i686 VERSION=${VERSION} build-appimage
|
||||
|
||||
# Now create igel version
|
||||
# we need first to create the Appimage for x86_64
|
||||
make DESTDIR=igelimage DISTRO=x86_64 VERSION=${VERSION} build-igel
|
||||
|
||||
# Create the thinpro version
|
||||
make DESTDIR=thinproimage DISTRO=x86_64 VERSION=${VERSION} build-thinpro
|
||||
|
||||
rpm --addsign ../*rpm
|
||||
|
@@ -1,26 +1,38 @@
|
||||
Format-Specification: http://svn.debian.org/wsvn/dep/web/deps/dep5.mdwn?op=file&rev=135
|
||||
Name: udsclient3
|
||||
Maintainer: Adolfo Gómez García
|
||||
Source: http://www.udsenterprise.com/
|
||||
Source: http://github.com/dkmstr/openuds/client-py3
|
||||
|
||||
Copyright: 2014 Virtual Cable S.L.U.
|
||||
License: BSD-3-clause
|
||||
Files: *
|
||||
Copyright: (c) 2014-2022, Virtual Cable S.L.U.
|
||||
License: 3-BSD
|
||||
|
||||
License: GPL-2+
|
||||
This program is free software; you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation; either version 2 of the License, or
|
||||
(at your option) any later version.
|
||||
.
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
.
|
||||
You should have received a copy of the GNU General Public License along
|
||||
with this program; if not, write to the Free Software Foundation, Inc.,
|
||||
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
.
|
||||
On Debian systems, the full text of the GNU General Public
|
||||
License version 2 can be found in the file
|
||||
`/usr/share/common-licenses/GPL-2'.
|
||||
License: 3-BSD
|
||||
All rights reserved.
|
||||
.
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions are met:
|
||||
.
|
||||
* Redistributions of source code must retain the above copyright notice, this
|
||||
list of conditions and the following disclaimer.
|
||||
.
|
||||
* Redistributions in binary form must reproduce the above copyright notice,
|
||||
this list of conditions and the following disclaimer in the documentation
|
||||
and/or other materials provided with the distribution.
|
||||
.
|
||||
* Neither the name of pg_query nor the names of its contributors may be used
|
||||
to endorse or promote products derived from this software without specific
|
||||
prior written permission.
|
||||
.
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||
ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
|
||||
LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
||||
CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
||||
SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
||||
INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
||||
CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
||||
ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
||||
POSSIBILITY OF SUCH DAMAGE.
|
||||
|
69
client-py3/full/linux/igel/UDSClient-Profile-template.xml
Executable file
69
client-py3/full/linux/igel/UDSClient-Profile-template.xml
Executable file
@@ -0,0 +1,69 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
|
||||
<profile>
|
||||
<profile_id>1126</profile_id>
|
||||
<profilename>UDSClient</profilename>
|
||||
<firmware>
|
||||
<model>IGEL OS 11</model>
|
||||
<version>11.05.120.01</version>
|
||||
</firmware>
|
||||
<description></description>
|
||||
<overwritesessions>false</overwritesessions>
|
||||
<is_master_profile>false</is_master_profile>
|
||||
<is_igel_os>true</is_igel_os>
|
||||
<settings>
|
||||
<pclass name="custom_partition.enabled">
|
||||
<pvalue instancenr="-1" variableExpression="" variableSubstitutionActive="false">true</pvalue>
|
||||
<variableSubstitutionActive>false</variableSubstitutionActive>
|
||||
</pclass>
|
||||
<pclass name="system.security.apparmor">
|
||||
<pvalue instancenr="-1" variableExpression="" variableSubstitutionActive="false">false</pvalue>
|
||||
<variableSubstitutionActive>false</variableSubstitutionActive>
|
||||
</pclass>
|
||||
<pclass name="custom_partition.mountpoint">
|
||||
<pvalue instancenr="-1" variableExpression="" variableSubstitutionActive="false">/UDSClient</pvalue>
|
||||
<variableSubstitutionActive>false</variableSubstitutionActive>
|
||||
</pclass>
|
||||
<pclass name="custom_partition.size">
|
||||
<pvalue instancenr="-1" variableExpression="" variableSubstitutionActive="false">_SIZE_</pvalue>
|
||||
<variableSubstitutionActive>false</variableSubstitutionActive>
|
||||
</pclass>
|
||||
</settings>
|
||||
<instancesettings>
|
||||
<instance classprefix="custom_partition.source%" serialnumber="-719cadfe:17ca470644a:-7fff127.0.1.1">
|
||||
<ivalue classname="custom_partition.source%.autoupdate" variableExpression="" variableSubstitutionActive="false">false</ivalue>
|
||||
<ivalue classname="custom_partition.source%.crypt_password" variableExpression="" variableSubstitutionActive="false">000d4317311f2c0031133c4d3e4c3d</ivalue>
|
||||
<ivalue classname="custom_partition.source%.final_action" variableExpression="" variableSubstitutionActive="false"></ivalue>
|
||||
<ivalue classname="custom_partition.source%.init_action" variableExpression="" variableSubstitutionActive="false">/UDSClient/init.sh</ivalue>
|
||||
<ivalue classname="custom_partition.source%.password" variableExpression="" variableSubstitutionActive="false"></ivalue>
|
||||
<ivalue classname="custom_partition.source%.url" variableExpression="" variableSubstitutionActive="false">https://[UMS_SERVER]:8443/ums_filetransfer/UDSClient-igel.inf</ivalue>
|
||||
<ivalue classname="custom_partition.source%.username" variableExpression="" variableSubstitutionActive="false">[UMS_USERNAME]</ivalue>
|
||||
</instance>
|
||||
<instance classprefix="sessions.chromium%" serialnumber="-6b5264e9:17ca6f65505:-8000127.0.1.1">
|
||||
<ivalue classname="sessions.chromium%.app.browser_startup_page" variableExpression="" variableSubstitutionActive="false">1</ivalue>
|
||||
<ivalue classname="sessions.chromium%.app.homepage" variableExpression="" variableSubstitutionActive="false">https://demo.udsenterprise.com</ivalue>
|
||||
<ivalue classname="sessions.chromium%.applaunch" variableExpression="" variableSubstitutionActive="false">true</ivalue>
|
||||
<ivalue classname="sessions.chromium%.applaunch_path" variableExpression="" variableSubstitutionActive="false"></ivalue>
|
||||
<ivalue classname="sessions.chromium%.applaunch_system" variableExpression="" variableSubstitutionActive="false">false</ivalue>
|
||||
<ivalue classname="sessions.chromium%.autostart" variableExpression="" variableSubstitutionActive="false">false</ivalue>
|
||||
<ivalue classname="sessions.chromium%.autostartnotify" variableExpression="" variableSubstitutionActive="false">false</ivalue>
|
||||
<ivalue classname="sessions.chromium%.desktop" variableExpression="" variableSubstitutionActive="false">true</ivalue>
|
||||
<ivalue classname="sessions.chromium%.desktop_path" variableExpression="" variableSubstitutionActive="false"></ivalue>
|
||||
<ivalue classname="sessions.chromium%.hotkey" variableExpression="" variableSubstitutionActive="false"></ivalue>
|
||||
<ivalue classname="sessions.chromium%.hotkeymodifier" variableExpression="" variableSubstitutionActive="false">None</ivalue>
|
||||
<ivalue classname="sessions.chromium%.icon" variableExpression="" variableSubstitutionActive="false">chromium</ivalue>
|
||||
<ivalue classname="sessions.chromium%.menu_path" variableExpression="" variableSubstitutionActive="false"></ivalue>
|
||||
<ivalue classname="sessions.chromium%.name" variableExpression="UDS" variableSubstitutionActive="true">###LOC_DEFAULT###</ivalue>
|
||||
<ivalue classname="sessions.chromium%.position" variableExpression="" variableSubstitutionActive="false">0</ivalue>
|
||||
<ivalue classname="sessions.chromium%.pulldown" variableExpression="" variableSubstitutionActive="false">false</ivalue>
|
||||
<ivalue classname="sessions.chromium%.pwprotected" variableExpression="" variableSubstitutionActive="false">none</ivalue>
|
||||
<ivalue classname="sessions.chromium%.quick_start" variableExpression="" variableSubstitutionActive="false">false</ivalue>
|
||||
<ivalue classname="sessions.chromium%.scardautostart" variableExpression="" variableSubstitutionActive="false">false</ivalue>
|
||||
<ivalue classname="sessions.chromium%.snotify" variableExpression="" variableSubstitutionActive="false">true</ivalue>
|
||||
<ivalue classname="sessions.chromium%.startmenu" variableExpression="" variableSubstitutionActive="false">true</ivalue>
|
||||
<ivalue classname="sessions.chromium%.startmenu_system" variableExpression="" variableSubstitutionActive="false">false</ivalue>
|
||||
<ivalue classname="sessions.chromium%.usehotkey" variableExpression="" variableSubstitutionActive="false">false</ivalue>
|
||||
<ivalue classname="sessions.chromium%.waittime2autostart" variableExpression="" variableSubstitutionActive="false">0</ivalue>
|
||||
<ivalue classname="sessions.chromium%.waittime2restart" variableExpression="" variableSubstitutionActive="false">0</ivalue>
|
||||
</instance>
|
||||
</instancesettings>
|
||||
</profile>
|
7
client-py3/full/linux/igel/UDSClient-template.inf
Executable file
7
client-py3/full/linux/igel/UDSClient-template.inf
Executable file
@@ -0,0 +1,7 @@
|
||||
[INFO]
|
||||
[PART]
|
||||
file="UDSClient.tar.bz2"
|
||||
version="1.1_igel1"
|
||||
size="_SIZE_"
|
||||
name="UDSClient"
|
||||
minfw="11.05.120"
|
11
client-py3/full/linux/igel/UDSClient.desktop
Executable file
11
client-py3/full/linux/igel/UDSClient.desktop
Executable file
@@ -0,0 +1,11 @@
|
||||
[Desktop Entry]
|
||||
Name=UDSClient
|
||||
Comment=UDS Helper
|
||||
Keywords=uds;client;vdi;
|
||||
Exec=/UDSClient/UDSClient %u
|
||||
Icon=help-browser
|
||||
StartupNotify=true
|
||||
Terminal=false
|
||||
Type=Application
|
||||
Categories=Utility;
|
||||
MimeType=x-scheme-handler/uds;x-scheme-handler/udss;
|
2
client-py3/full/linux/igel/init.sh
Executable file
2
client-py3/full/linux/igel/init.sh
Executable file
@@ -0,0 +1,2 @@
|
||||
#!/bin/sh
|
||||
cp /UDSClient/UDSClient.desktop /usr/share/applications.mime
|
@@ -8,6 +8,8 @@ echo "Installation process done."
|
||||
echo "Remember that the following packages must be installed on system:"
|
||||
echo "* Python3 paramiko"
|
||||
echo "* Python3 PyQt5"
|
||||
echo "* Python3 six"
|
||||
echo "* Python3 requests"
|
||||
echo "* Python3 cryptography"
|
||||
echo "Theese packages (as their names), are dependent on your platform, so you must locate and install them"
|
||||
echo "Also, ensure that a /media folder exists on your machine, that will be redirected on RDP connections"
|
||||
|
4
client-py3/full/linux/thinpro/firefox/45-uds
Normal file
4
client-py3/full/linux/thinpro/firefox/45-uds
Normal file
@@ -0,0 +1,4 @@
|
||||
# UDS handlers.json
|
||||
cp "/lib/UDSClient/firefox/handlers.json" "$FIREFOX_PROFILE_HANDLERS"
|
||||
ffset "network.protocol-handler.external.uds" "true"
|
||||
ffset "network.protocol-handler.external.udss" "true"
|
98
client-py3/full/linux/thinpro/firefox/handlers.json
Normal file
98
client-py3/full/linux/thinpro/firefox/handlers.json
Normal file
@@ -0,0 +1,98 @@
|
||||
{
|
||||
"defaultHandlersVersion": {
|
||||
"en-US": 4
|
||||
},
|
||||
"mimeTypes": {
|
||||
"application/pdf": {
|
||||
"action": 3,
|
||||
"extensions": [
|
||||
"pdf"
|
||||
]
|
||||
},
|
||||
"application/x-ica": {
|
||||
"action": 2,
|
||||
"extensions": [
|
||||
"ica"
|
||||
],
|
||||
"handlers": [
|
||||
{
|
||||
"name": "wfica",
|
||||
"path": "/usr/share/hptc-firefox-mgr/handlers/citrix"
|
||||
}
|
||||
]
|
||||
},
|
||||
"application/x-rdp": {
|
||||
"action": 2,
|
||||
"extensions": [
|
||||
"rdp"
|
||||
],
|
||||
"handlers": [
|
||||
{
|
||||
"name": "HP xfreerdp",
|
||||
"path": "/usr/share/hptc-firefox-mgr/handlers/rdp"
|
||||
}
|
||||
]
|
||||
},
|
||||
"text/lic": {
|
||||
"action": 2,
|
||||
"extensions": [
|
||||
"lic"
|
||||
],
|
||||
"handlers": [
|
||||
{
|
||||
"name": "Copy license to ThinPro",
|
||||
"path": "/usr/share/hptc-firefox-mgr/handlers/copy_lic"
|
||||
}
|
||||
]
|
||||
},
|
||||
"text/xml": {
|
||||
"action": 3,
|
||||
"extensions": [
|
||||
"xml"
|
||||
]
|
||||
},
|
||||
"image/svg+xml": {
|
||||
"action": 3,
|
||||
"extensions": [
|
||||
"svg"
|
||||
]
|
||||
},
|
||||
"image/webp": {
|
||||
"action": 3,
|
||||
"extensions": [
|
||||
"webp"
|
||||
]
|
||||
}
|
||||
},
|
||||
"schemes": {
|
||||
"vmware-view": {
|
||||
"action": 2,
|
||||
"handlers": [
|
||||
{
|
||||
"name": "VMWare Horizon View",
|
||||
"path": "/usr/share/hptc-firefox-mgr/handlers/vmware"
|
||||
}
|
||||
]
|
||||
},
|
||||
"uds": {
|
||||
"action": 2,
|
||||
"handlers": [
|
||||
{
|
||||
"name": "UDS Client for ThinPro (SSL)",
|
||||
"path": "/usr/share/hptc-firefox-mgr/handlers/uds"
|
||||
}
|
||||
]
|
||||
},
|
||||
"udss": {
|
||||
"action": 2,
|
||||
"handlers": [
|
||||
{
|
||||
"name": "UDS Client for ThinPro",
|
||||
"path": "/usr/share/hptc-firefox-mgr/handlers/uds"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
}
|
5
client-py3/full/linux/thinpro/firefox/uds
Normal file
5
client-py3/full/linux/thinpro/firefox/uds
Normal file
@@ -0,0 +1,5 @@
|
||||
#!/bin/sh
|
||||
|
||||
export LD_PRELOAD=""
|
||||
/bin/udsclient $*
|
||||
exit 0
|
2
client-py3/full/linux/thinpro/firefox7.1/45-uds
Normal file
2
client-py3/full/linux/thinpro/firefox7.1/45-uds
Normal file
@@ -0,0 +1,2 @@
|
||||
# UDS handlers.json
|
||||
restore "/lib/UDSClient/firefox/handlers.json" "$FIREFOX_PROFILE_HANDLERS"
|
50
client-py3/full/linux/thinpro/firefox7.1/handlers.json
Normal file
50
client-py3/full/linux/thinpro/firefox7.1/handlers.json
Normal file
@@ -0,0 +1,50 @@
|
||||
{
|
||||
"defaultHandlersVersion":{
|
||||
"en-US":4
|
||||
},
|
||||
"mimeTypes":{
|
||||
"application/pdf":{
|
||||
"action":3,
|
||||
"extensions":["pdf"]
|
||||
},
|
||||
"application/x-ica":{
|
||||
"action":2,
|
||||
"handlers":[{
|
||||
"name":"wfica",
|
||||
"path":"/usr/bin/hptc-firefox-run-wfica.sh"
|
||||
}],
|
||||
"extensions":["ica"]
|
||||
},
|
||||
"application/x-rdp":{
|
||||
"action":2,
|
||||
"handlers":[{
|
||||
"name":"HP xfreerdp",
|
||||
"path":"/usr/bin/hptc-run-rdp-file-freerdp.sh"
|
||||
}],
|
||||
"extensions":["rdp"]
|
||||
}
|
||||
},
|
||||
"schemes":{
|
||||
"vmware-view":{
|
||||
"action":2,
|
||||
"handlers":[{
|
||||
"name":"VMWare Horizon View",
|
||||
"path":"/usr/bin/vmware-view"
|
||||
}]
|
||||
},
|
||||
"udss":{
|
||||
"action":2,
|
||||
"handlers":[{
|
||||
"name":"UDS Client",
|
||||
"path":"/bin/udsclient"
|
||||
}]
|
||||
},
|
||||
"uds":{
|
||||
"action":2,
|
||||
"handlers":[{
|
||||
"name":"UDS Client",
|
||||
"path":"/bin/udsclient"
|
||||
}]
|
||||
}
|
||||
}
|
||||
}
|
37
client-py3/full/linux/thinpro/firefox7.1/syspref.js
Normal file
37
client-py3/full/linux/thinpro/firefox7.1/syspref.js
Normal file
@@ -0,0 +1,37 @@
|
||||
// This file can be used to configure global preferences for Firefox
|
||||
// Example: Homepage
|
||||
//pref("browser.startup.homepage", "http://www.weebls-stuff.com/wab/");
|
||||
pref("plugin.default.state", 2);
|
||||
pref("xpinstall.signatures.required", false, locked);
|
||||
pref("extensions.autoDisableScopes", 0, locked);
|
||||
pref("extensions.pocket.enabled", false, locked);
|
||||
pref("extensions.screenshots.disabled", true, locked);
|
||||
pref("datareporting.policy.dataSubmissionEnabled", false, locked);
|
||||
pref("datareporting.policy.dataSubmissionEnabled.v2", false, locked);
|
||||
|
||||
pref("app.update.auto", false, locked);
|
||||
pref("app.update.enabled", false, locked);
|
||||
pref("browser.download.manager.closeWhenDone", true, locked);
|
||||
pref("browser.helperApps.neverAsk.openFile", "application/x-rdp, application/x-java-jnlp-file", locked);
|
||||
pref("browser.EULA.3.accepted", true, locked);
|
||||
pref("browser.rights.3.shown", true, locked);
|
||||
pref("browser.safebrowsing.enabled", false, locked);
|
||||
pref("browser.search.update", false, locked);
|
||||
pref("browser.sessionstore.enabled", false, locked);
|
||||
pref("browser.sessionhistory.cache_subframes", false, locked);
|
||||
pref("datareporting.healthreport.service.enabled", false, locked);
|
||||
pref("datareporting.healthreport.uploadEnabled", false, locked);
|
||||
pref("devtools.toolbox.host", "none", locked);
|
||||
pref("extensions.autoDisableScopes", 14, locked);
|
||||
pref("extensions.blocklist.enabled", false, locked);
|
||||
pref("extensions.update.enabled", false, locked);
|
||||
pref("intl.charsetmenu.browser.cache", "UTF-8", locked);
|
||||
|
||||
pref("network.protocol-handler.external.mailto", false, locked);
|
||||
pref("network.protocol-handler.external.news", false, locked);
|
||||
pref("network.protocol-handler.external.snews", false, locked);
|
||||
pref("network.protocol-handler.external.nntp", false, locked);
|
||||
pref("network.protocol-handler.external-default", false, locked);
|
||||
pref("network.protocol-handler.external.vmware-view", true, locked);
|
||||
pref("network.protocol-handler.external.uds", true, locked);
|
||||
pref("network.protocol-handler.external.udss", true, locked);
|
38
client-py3/full/linux/thinpro/install-uds-client.sh
Executable file
38
client-py3/full/linux/thinpro/install-uds-client.sh
Executable file
@@ -0,0 +1,38 @@
|
||||
#!/bin/sh
|
||||
|
||||
# Common part
|
||||
|
||||
# unlocks so we can write on TC
|
||||
fsunlock
|
||||
|
||||
cp UDSClient /bin/udsclient
|
||||
chmod 755 /bin/udsclient
|
||||
# RDP Script for UDSClient. Launchs udsclient using the "Template_UDS" profile
|
||||
cp udsrdp /usr/bin
|
||||
|
||||
INSTALLED=0
|
||||
# Installation for 7.1.x version
|
||||
grep -q "7.1" /etc/issue
|
||||
if [ $? -eq 0 ]; then
|
||||
echo "Installing for thinpro version 7.1"
|
||||
# Allow UDS apps without asking
|
||||
cp firefox7.1/syspref.js /etc/firefox
|
||||
# Copy handlers.json for firefox
|
||||
mkdir -p /lib/UDSClient/firefox/ > /dev/null 2>&1
|
||||
cp firefox7.1/handlers.json /lib/UDSClient/firefox/
|
||||
# and runner
|
||||
cp firefox7.1/45-uds /etc/hptc-firefox-mgr/prestart
|
||||
else
|
||||
echo "Installing for thinpro version 7.2 or later"
|
||||
# Copy handlers for firefox
|
||||
mkdir -p /lib/UDSClient/firefox/ > /dev/null 2>&1
|
||||
# Copy handlers.json for firefox
|
||||
cp firefox/handlers.json /lib/UDSClient/firefox/
|
||||
cp firefox/45-uds /etc/hptc-firefox-mgr/prestart
|
||||
# copy uds handler for firefox
|
||||
cp firefox/uds /usr/share/hptc-firefox-mgr/handlers/uds
|
||||
chmod 755 /usr/share/hptc-firefox-mgr/handlers/uds
|
||||
fi
|
||||
|
||||
# Common part
|
||||
fslock
|
390
client-py3/full/linux/thinpro/udsrdp
Executable file
390
client-py3/full/linux/thinpro/udsrdp
Executable file
@@ -0,0 +1,390 @@
|
||||
#!/bin/bash
|
||||
|
||||
function clearParams {
|
||||
mclient set $REGKEY/address ""
|
||||
mclient set $REGKEY/username ""
|
||||
mclient set $REGKEY/password ""
|
||||
mclient set $REGKEY/domain ""
|
||||
|
||||
mclient set $REGKEY/authorizations/user/execution 0
|
||||
|
||||
mclient commit
|
||||
}
|
||||
|
||||
function getRegKey {
|
||||
# Get Template_UDS
|
||||
for key in `mclient get root/ConnectionType/freerdp/connections | sed "s/dir //g"`; do
|
||||
val=`mclient get $key/label | sed "s/value //g"`
|
||||
if [ "$val" == "Template_UDS" ]; then
|
||||
REGKEY=$key
|
||||
fi
|
||||
done
|
||||
}
|
||||
|
||||
function createUDSConnectionTemplate {
|
||||
TMPFILE=$(mktemp /tmp/udsexport.XXXXXX)
|
||||
cat > $TMPFILE << EOF
|
||||
<Profile>
|
||||
<ProfileSettings>
|
||||
<Name>UDS Template Profile</Name>
|
||||
<RegistryRoot>root/ConnectionType/freerdp/connections/{ff064bd9-047a-45ec-b70f-04ab218186ff}</RegistryRoot>
|
||||
<Target>
|
||||
<Hardware>t420</Hardware>
|
||||
<ImageId>T7X62022</ImageId>
|
||||
<Version>6.2.0</Version>
|
||||
<Config>standard</Config>
|
||||
</Target>
|
||||
</ProfileSettings>
|
||||
<ProfileRegistry>
|
||||
<NodeDir name="{ff064bd9-047a-45ec-b70f-04ab218186ff}">
|
||||
<NodeDir name="rdWebFeed">
|
||||
<NodeKey name="keepResourcesWindowOpened">
|
||||
<NodeParam name="value">0</NodeParam>
|
||||
<NodeParam name="type">bool</NodeParam>
|
||||
</NodeKey>
|
||||
<NodeKey name="autoStartSingleResource">
|
||||
<NodeParam name="value">0</NodeParam>
|
||||
<NodeParam name="type">bool</NodeParam>
|
||||
</NodeKey>
|
||||
<NodeKey name="autoDisconnectTimeout">
|
||||
<NodeParam name="value">0</NodeParam>
|
||||
<NodeParam name="type">number</NodeParam>
|
||||
</NodeKey>
|
||||
</NodeDir>
|
||||
<NodeDir name="loginfields">
|
||||
<NodeKey name="username">
|
||||
<NodeParam name="value">3</NodeParam>
|
||||
<NodeParam name="type">number</NodeParam>
|
||||
</NodeKey>
|
||||
<NodeKey name="rememberme">
|
||||
<NodeParam name="value">2</NodeParam>
|
||||
<NodeParam name="type">number</NodeParam>
|
||||
</NodeKey>
|
||||
<NodeKey name="password">
|
||||
<NodeParam name="value">3</NodeParam>
|
||||
<NodeParam name="type">number</NodeParam>
|
||||
</NodeKey>
|
||||
<NodeKey name="domain">
|
||||
<NodeParam name="value">3</NodeParam>
|
||||
<NodeParam name="type">number</NodeParam>
|
||||
</NodeKey>
|
||||
</NodeDir>
|
||||
<NodeDir name="authorizations">
|
||||
<NodeDir name="user">
|
||||
<NodeKey name="execution">
|
||||
<NodeParam name="value">0</NodeParam>
|
||||
<NodeParam name="type">string</NodeParam>
|
||||
</NodeKey>
|
||||
<NodeKey name="edit">
|
||||
<NodeParam name="value">0</NodeParam>
|
||||
<NodeParam name="type">string</NodeParam>
|
||||
</NodeKey>
|
||||
</NodeDir>
|
||||
</NodeDir>
|
||||
<NodeKey name="address">
|
||||
<NodeParam name="value"/>
|
||||
<NodeParam name="type">string</NodeParam>
|
||||
</NodeKey>
|
||||
<NodeKey name="username">
|
||||
<NodeParam name="value"/>
|
||||
<NodeParam name="type">string</NodeParam>
|
||||
</NodeKey>
|
||||
<NodeKey name="password">
|
||||
<NodeParam name="value">NLCR.1</NodeParam>
|
||||
<NodeParam name="type">rc4</NodeParam>
|
||||
</NodeKey>
|
||||
<NodeKey name="domain">
|
||||
<NodeParam name="value"/>
|
||||
<NodeParam name="type">string</NodeParam>
|
||||
</NodeKey>
|
||||
<NodeKey name="label">
|
||||
<NodeParam name="value">Template_UDS</NodeParam>
|
||||
<NodeParam name="type">string</NodeParam>
|
||||
</NodeKey>
|
||||
<NodeKey name="credentialsType">
|
||||
<NodeParam name="value">password</NodeParam>
|
||||
<NodeParam name="type">string</NodeParam>
|
||||
</NodeKey>
|
||||
<NodeKey name="gatewayEnabled">
|
||||
<NodeParam name="value">0</NodeParam>
|
||||
<NodeParam name="type">bool</NodeParam>
|
||||
</NodeKey>
|
||||
<NodeKey name="gatewayPort">
|
||||
<NodeParam name="value">443</NodeParam>
|
||||
<NodeParam name="type">number</NodeParam>
|
||||
</NodeKey>
|
||||
<NodeKey name="gatewayUsesSameCredentials">
|
||||
<NodeParam name="value">1</NodeParam>
|
||||
<NodeParam name="type">bool</NodeParam>
|
||||
</NodeKey>
|
||||
<NodeKey name="gatewayCredentialsType">
|
||||
<NodeParam name="value">password</NodeParam>
|
||||
<NodeParam name="type">string</NodeParam>
|
||||
</NodeKey>
|
||||
<NodeKey name="remoteDesktopService">
|
||||
<NodeParam name="value">Remote Computer</NodeParam>
|
||||
<NodeParam name="type">string</NodeParam>
|
||||
</NodeKey>
|
||||
<NodeKey name="windowMode">
|
||||
<NodeParam name="value">Remote Application</NodeParam>
|
||||
<NodeParam name="type">string</NodeParam>
|
||||
</NodeKey>
|
||||
<NodeKey name="seamlessWindow">
|
||||
<NodeParam name="value">0</NodeParam>
|
||||
<NodeParam name="type">bool</NodeParam>
|
||||
</NodeKey>
|
||||
<NodeKey name="windowType">
|
||||
<NodeParam name="value">full</NodeParam>
|
||||
<NodeParam name="type">string</NodeParam>
|
||||
</NodeKey>
|
||||
<NodeKey name="windowSizePercentage">
|
||||
<NodeParam name="value">70</NodeParam>
|
||||
<NodeParam name="type">number</NodeParam>
|
||||
</NodeKey>
|
||||
<NodeKey name="windowSizeWidth">
|
||||
<NodeParam name="value">1024</NodeParam>
|
||||
<NodeParam name="type">number</NodeParam>
|
||||
</NodeKey>
|
||||
<NodeKey name="windowSizeHeight">
|
||||
<NodeParam name="value">768</NodeParam>
|
||||
<NodeParam name="type">number</NodeParam>
|
||||
</NodeKey>
|
||||
<NodeKey name="mouseMotionEvents">
|
||||
<NodeParam name="value">1</NodeParam>
|
||||
<NodeParam name="type">bool</NodeParam>
|
||||
</NodeKey>
|
||||
<NodeKey name="compression">
|
||||
<NodeParam name="value">1</NodeParam>
|
||||
<NodeParam name="type">bool</NodeParam>
|
||||
</NodeKey>
|
||||
<NodeKey name="rdpEncryption">
|
||||
<NodeParam name="value">1</NodeParam>
|
||||
<NodeParam name="type">bool</NodeParam>
|
||||
</NodeKey>
|
||||
<NodeKey name="offScreenBitmaps">
|
||||
<NodeParam name="value">1</NodeParam>
|
||||
<NodeParam name="type">bool</NodeParam>
|
||||
</NodeKey>
|
||||
<NodeKey name="attachToConsole">
|
||||
<NodeParam name="value">0</NodeParam>
|
||||
<NodeParam name="type">bool</NodeParam>
|
||||
</NodeKey>
|
||||
<NodeKey name="clipboardExtension">
|
||||
<NodeParam name="value">1</NodeParam>
|
||||
<NodeParam name="type">bool</NodeParam>
|
||||
</NodeKey>
|
||||
<NodeKey name="rdp6Buffering">
|
||||
<NodeParam name="value">1</NodeParam>
|
||||
<NodeParam name="type">bool</NodeParam>
|
||||
</NodeKey>
|
||||
<NodeKey name="rdpProgressiveCodec">
|
||||
<NodeParam name="value">1</NodeParam>
|
||||
<NodeParam name="type">bool</NodeParam>
|
||||
</NodeKey>
|
||||
<NodeKey name="securityLevel">
|
||||
<NodeParam name="value">0</NodeParam>
|
||||
<NodeParam name="type">number</NodeParam>
|
||||
</NodeKey>
|
||||
<NodeKey name="tlsVersion">
|
||||
<NodeParam name="value">auto</NodeParam>
|
||||
<NodeParam name="type">string</NodeParam>
|
||||
</NodeKey>
|
||||
<NodeKey name="sound">
|
||||
<NodeParam name="value">1</NodeParam>
|
||||
<NodeParam name="type">number</NodeParam>
|
||||
</NodeKey>
|
||||
<NodeKey name="printerMapping">
|
||||
<NodeParam name="value">1</NodeParam>
|
||||
<NodeParam name="type">number</NodeParam>
|
||||
</NodeKey>
|
||||
<NodeKey name="portMapping">
|
||||
<NodeParam name="value">1</NodeParam>
|
||||
<NodeParam name="type">number</NodeParam>
|
||||
</NodeKey>
|
||||
<NodeKey name="usbStorageRedirection">
|
||||
<NodeParam name="value">1</NodeParam>
|
||||
<NodeParam name="type">number</NodeParam>
|
||||
</NodeKey>
|
||||
<NodeKey name="localPartitionRedirection">
|
||||
<NodeParam name="value">1</NodeParam>
|
||||
<NodeParam name="type">number</NodeParam>
|
||||
</NodeKey>
|
||||
<NodeKey name="scRedirection">
|
||||
<NodeParam name="value">1</NodeParam>
|
||||
<NodeParam name="type">number</NodeParam>
|
||||
</NodeKey>
|
||||
<NodeKey name="usbMiscRedirection">
|
||||
<NodeParam name="value">2</NodeParam>
|
||||
<NodeParam name="type">number</NodeParam>
|
||||
</NodeKey>
|
||||
<NodeKey name="perfFlagNoWallpaper">
|
||||
<NodeParam name="value">0</NodeParam>
|
||||
<NodeParam name="type">number</NodeParam>
|
||||
</NodeKey>
|
||||
<NodeKey name="perfFlagFontSmoothing">
|
||||
<NodeParam name="value">1</NodeParam>
|
||||
<NodeParam name="type">number</NodeParam>
|
||||
</NodeKey>
|
||||
<NodeKey name="perfFlagDesktopComposition">
|
||||
<NodeParam name="value">1</NodeParam>
|
||||
<NodeParam name="type">number</NodeParam>
|
||||
</NodeKey>
|
||||
<NodeKey name="perfFlagNoWindowDrag">
|
||||
<NodeParam name="value">0</NodeParam>
|
||||
<NodeParam name="type">number</NodeParam>
|
||||
</NodeKey>
|
||||
<NodeKey name="perfFlagNoMenuAnimations">
|
||||
<NodeParam name="value">0</NodeParam>
|
||||
<NodeParam name="type">number</NodeParam>
|
||||
</NodeKey>
|
||||
<NodeKey name="perfFlagNoTheming">
|
||||
<NodeParam name="value">0</NodeParam>
|
||||
<NodeParam name="type">number</NodeParam>
|
||||
</NodeKey>
|
||||
<NodeKey name="timeoutsEnabled">
|
||||
<NodeParam name="value">1</NodeParam>
|
||||
<NodeParam name="type">bool</NodeParam>
|
||||
</NodeKey>
|
||||
<NodeKey name="timeoutWarning">
|
||||
<NodeParam name="value">6000</NodeParam>
|
||||
<NodeParam name="type">number</NodeParam>
|
||||
</NodeKey>
|
||||
<NodeKey name="timeoutWarningDialog">
|
||||
<NodeParam name="value">0</NodeParam>
|
||||
<NodeParam name="type">bool</NodeParam>
|
||||
</NodeKey>
|
||||
<NodeKey name="timeoutRecovery">
|
||||
<NodeParam name="value">30000</NodeParam>
|
||||
<NodeParam name="type">number</NodeParam>
|
||||
</NodeKey>
|
||||
<NodeKey name="timeoutError">
|
||||
<NodeParam name="value">0</NodeParam>
|
||||
<NodeParam name="type">number</NodeParam>
|
||||
</NodeKey>
|
||||
<NodeKey name="showRDPDashboard">
|
||||
<NodeParam name="value">0</NodeParam>
|
||||
<NodeParam name="type">bool</NodeParam>
|
||||
</NodeKey>
|
||||
<NodeKey name="showConnectionGraph">
|
||||
<NodeParam name="value">0</NodeParam>
|
||||
<NodeParam name="type">bool</NodeParam>
|
||||
</NodeKey>
|
||||
<NodeKey name="x11Synchronous">
|
||||
<NodeParam name="value">0</NodeParam>
|
||||
<NodeParam name="type">bool</NodeParam>
|
||||
</NodeKey>
|
||||
<NodeKey name="x11Logging">
|
||||
<NodeParam name="value">0</NodeParam>
|
||||
<NodeParam name="type">bool</NodeParam>
|
||||
</NodeKey>
|
||||
<NodeKey name="x11LogAutoflush">
|
||||
<NodeParam name="value">0</NodeParam>
|
||||
<NodeParam name="type">bool</NodeParam>
|
||||
</NodeKey>
|
||||
<NodeKey name="x11Capture">
|
||||
<NodeParam name="value">0</NodeParam>
|
||||
<NodeParam name="type">bool</NodeParam>
|
||||
</NodeKey>
|
||||
<NodeKey name="SingleSignOn">
|
||||
<NodeParam name="value">1</NodeParam>
|
||||
<NodeParam name="type">bool</NodeParam>
|
||||
</NodeKey>
|
||||
<NodeKey name="autostart">
|
||||
<NodeParam name="value">0</NodeParam>
|
||||
<NodeParam name="type">number</NodeParam>
|
||||
</NodeKey>
|
||||
<NodeKey name="waitForNetwork">
|
||||
<NodeParam name="value">1</NodeParam>
|
||||
<NodeParam name="type">bool</NodeParam>
|
||||
</NodeKey>
|
||||
<NodeKey name="hasDesktopIcon">
|
||||
<NodeParam name="value">0</NodeParam>
|
||||
<NodeParam name="type">bool</NodeParam>
|
||||
</NodeKey>
|
||||
<NodeKey name="autoReconnect">
|
||||
<NodeParam name="value">0</NodeParam>
|
||||
<NodeParam name="type">bool</NodeParam>
|
||||
</NodeKey>
|
||||
</NodeDir>
|
||||
</ProfileRegistry>
|
||||
<ProfileFiles/>
|
||||
</Profile>
|
||||
EOF
|
||||
mclient import $TMPFILE
|
||||
rm $TMPFILE
|
||||
}
|
||||
|
||||
ADDRESS=
|
||||
USERNAME=
|
||||
PASSWORD=
|
||||
DOMAIN=
|
||||
REGKEY=
|
||||
CLEAR=0
|
||||
|
||||
# Try to locate registry key for UDS Template
|
||||
getRegKey
|
||||
|
||||
if [ "$REGKEY" == "" ]; then
|
||||
# Not found, create on based on our template
|
||||
createUDSConnectionTemplate
|
||||
getRegKey
|
||||
fi
|
||||
|
||||
for param in $@; do
|
||||
if [ "/u:" == "${param:0:3}" ]; then
|
||||
USERNAME=${param:3}
|
||||
CLEAR=1
|
||||
fi
|
||||
|
||||
if [ "/p:" == "${param:0:3}" ]; then
|
||||
PASSWORD=${param:3}
|
||||
CLEAR=1
|
||||
fi
|
||||
|
||||
if [ "/d:" == "${param:0:3}" ]; then
|
||||
DOMAIN=${param:3}
|
||||
CLEAR=1
|
||||
fi
|
||||
|
||||
if [ "/v:" == "${param:0:3}" ]; then
|
||||
ADDRESS=${param:3}
|
||||
CLEAR=1
|
||||
fi
|
||||
done
|
||||
|
||||
if [ "$CLEAR" -eq 1 ]; then
|
||||
clearParams
|
||||
fi
|
||||
|
||||
ID=`basename $REGKEY`
|
||||
RESPAWN=0
|
||||
|
||||
if [ "" != "$ADDRESS" ]; then
|
||||
mclient set $REGKEY/address "${ADDRESS}"
|
||||
RESPAWN=1
|
||||
fi
|
||||
|
||||
if [ "" != "$USERNAME" ]; then
|
||||
mclient set $REGKEY/username "${USERNAME}"
|
||||
RESPAWN=1
|
||||
fi
|
||||
|
||||
if [ "" != "$PASSWORD" ]; then
|
||||
mclient set $REGKEY/password "${PASSWORD}"
|
||||
RESPAWN=1
|
||||
fi
|
||||
|
||||
if [ "" != "$DOMAIN" ]; then
|
||||
mclient set $REGKEY/domain "${DOMAIN}"
|
||||
RESPAWN=1
|
||||
fi
|
||||
|
||||
if [ "$RESPAWN" -eq 1 ]; then
|
||||
mclient set $REGKEY/authorizations/user/execution 1
|
||||
mclient commit
|
||||
exec $0 # Restart without command line
|
||||
fi
|
||||
|
||||
process-connection $ID
|
||||
|
||||
clearParams
|
@@ -1,18 +1,17 @@
|
||||
version: 1
|
||||
script:
|
||||
# Remove any previous build
|
||||
- rm -rf /tmp/UDSClientDir | true
|
||||
- rm -rf $TARGET_APPDIR | true
|
||||
# Make usr and icons dirs
|
||||
- mkdir -p /tmp/UDSClientDir/usr/src
|
||||
- mkdir -p $TARGET_APPDIR/usr/src
|
||||
# Copy the python application code into the UDSClientDir
|
||||
- cp ../src/UDS*.py /tmp/UDSClientDir/usr/src
|
||||
- cp -r ../src/uds /tmp/UDSClientDir/usr/src
|
||||
- cp ../src/UDS*.py $TARGET_APPDIR/usr/src
|
||||
- cp -r ../src/uds $TARGET_APPDIR/usr/src
|
||||
# Remove __pycache__ and .mypy if exists
|
||||
- rm /tmp/UDSClientDir/usr/src/.mypy_cache -rf 2>&1 > /dev/null
|
||||
- rm /tmp/UDSClientDir/usr/src/uds/.mypy_cache -rf 2>&1 > /dev/null
|
||||
- rm /tmp/UDSClientDir/usr/src/__pycache__ -rf 2>&1 > /dev/null
|
||||
- rm /tmp/UDSClientDir/usr/src/uds/__pycache__ -rf 2>&1 > /dev/null
|
||||
|
||||
- rm $TARGET_APPDIR/usr/src/.mypy_cache -rf 2>&1 > /dev/null
|
||||
- rm $TARGET_APPDIR/usr/src/uds/.mypy_cache -rf 2>&1 > /dev/null
|
||||
- rm $TARGET_APPDIR/usr/src/__pycache__ -rf 2>&1 > /dev/null
|
||||
- rm $TARGET_APPDIR/usr/src/uds/__pycache__ -rf 2>&1 > /dev/null
|
||||
AppDir:
|
||||
# On /tmp, that is an ext4 filesystem. On btrfs squashfs complains with "Unrecognised xattr prefix btrfs.compression"
|
||||
path: /tmp/UDSClientDir
|
||||
@@ -31,7 +30,7 @@ AppDir:
|
||||
arch: amd64
|
||||
sources:
|
||||
- sourceline: 'deb [arch=amd64] http://ftp.de.debian.org/debian/ bullseye main contrib non-free'
|
||||
key_url: 'http://keyserver.ubuntu.com/pks/lookup?op=get&search=0x04EE7237B7D453EC'
|
||||
key_url: 'http://keyserver.ubuntu.com/pks/lookup?op=get&search=0x648ACFD622F3D138'
|
||||
|
||||
include:
|
||||
- python3
|
||||
@@ -57,6 +56,6 @@ AppDir:
|
||||
PYTHONPATH: '${APPDIR}/usr/lib/python3.9/site-packages'
|
||||
|
||||
AppImage:
|
||||
update-information: None
|
||||
sign-key: None
|
||||
# update-information: None
|
||||
sign-key: 592AF43A64B8559137FA2458AA4ECFEE784E6BA7
|
||||
arch: x86_64
|
||||
|
@@ -1,4 +1,4 @@
|
||||
#!/usr/bin/env python3
|
||||
#!/usr/bin/env -S python3 -s
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
# Copyright (c) 2014-2021 Virtual Cable S.L.U.
|
||||
@@ -44,10 +44,10 @@ from PyQt5.QtCore import QSettings
|
||||
from uds.rest import RestApi, RetryException, InvalidVersion, UDSException
|
||||
|
||||
# Just to ensure there are available on runtime
|
||||
from uds.forward import forward # type: ignore
|
||||
from uds.tunnel import forward as f2 # type: ignore
|
||||
from uds.forward import forward as ssh_forward # type: ignore
|
||||
from uds.tunnel import forward as tunnel_forwards # type: ignore
|
||||
|
||||
from uds.log import logger, DEBUG
|
||||
from uds.log import logger
|
||||
from uds import tools
|
||||
from uds import VERSION
|
||||
|
||||
@@ -151,6 +151,8 @@ class UDSClient(QtWidgets.QMainWindow):
|
||||
return
|
||||
except Exception as e:
|
||||
self.showError(e)
|
||||
self.closeWindow()
|
||||
return
|
||||
|
||||
self.getTransportData()
|
||||
|
||||
@@ -176,8 +178,6 @@ class UDSClient(QtWidgets.QMainWindow):
|
||||
# Retry operation in ten seconds
|
||||
QtCore.QTimer.singleShot(10000, self.getTransportData)
|
||||
except Exception as e:
|
||||
if DEBUG:
|
||||
logger.exception('Got exception on getTransportData')
|
||||
self.showError(e)
|
||||
|
||||
def start(self):
|
||||
@@ -316,12 +316,11 @@ def minimal(api: RestApi, ticket: str, scrambler: str):
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
def main(args: typing.List[str]):
|
||||
app = QtWidgets.QApplication(sys.argv)
|
||||
logger.debug('Initializing connector for %s(%s)', sys.platform, platform.machine())
|
||||
|
||||
# Initialize app
|
||||
app = QtWidgets.QApplication(sys.argv)
|
||||
|
||||
logger.debug('Arguments: %s', args)
|
||||
# Set several info for settings
|
||||
QtCore.QCoreApplication.setOrganizationName('Virtual Cable S.L.U.')
|
||||
QtCore.QCoreApplication.setApplicationName('UDS Connector')
|
||||
@@ -343,11 +342,11 @@ if __name__ == "__main__":
|
||||
# First parameter must be url
|
||||
useMinimal = False
|
||||
try:
|
||||
uri = sys.argv[1]
|
||||
uri = args[1]
|
||||
|
||||
if uri == '--minimal':
|
||||
useMinimal = True
|
||||
uri = sys.argv[2] # And get URI
|
||||
uri = args[2] # And get URI
|
||||
|
||||
if uri == '--test':
|
||||
sys.exit(0)
|
||||
@@ -362,8 +361,8 @@ if __name__ == "__main__":
|
||||
'ssl:%s, host:%s, ticket:%s, scrambler:%s',
|
||||
ssl,
|
||||
host,
|
||||
UDSClient.ticket,
|
||||
UDSClient.scrambler,
|
||||
ticket,
|
||||
scrambler,
|
||||
)
|
||||
except Exception:
|
||||
logger.debug('Detected execution without valid URI, exiting')
|
||||
@@ -392,7 +391,7 @@ if __name__ == "__main__":
|
||||
|
||||
win.start()
|
||||
|
||||
exitVal = app.exec_()
|
||||
exitVal = app.exec()
|
||||
logger.debug('Execution finished correctly')
|
||||
|
||||
except Exception as e:
|
||||
@@ -404,3 +403,6 @@ if __name__ == "__main__":
|
||||
|
||||
logger.debug('Exiting')
|
||||
sys.exit(exitVal)
|
||||
|
||||
if __name__ == "__main__":
|
||||
main(sys.argv)
|
||||
|
75
client-py3/full/src/UDSClientLauncher.py
Normal file
75
client-py3/full/src/UDSClientLauncher.py
Normal file
@@ -0,0 +1,75 @@
|
||||
import sys
|
||||
import os.path
|
||||
import subprocess
|
||||
import typing
|
||||
|
||||
from uds.log import logger
|
||||
import UDSClient
|
||||
from UDSLauncherMac import Ui_MacLauncher
|
||||
|
||||
from PyQt5 import QtCore, QtWidgets, QtGui
|
||||
|
||||
SCRIPT_NAME = 'UDSClientLauncher'
|
||||
|
||||
class UdsApplication(QtWidgets.QApplication):
|
||||
path: str
|
||||
tunnels: typing.List[subprocess.Popen]
|
||||
|
||||
def __init__(self, argv: typing.List[str]) -> None:
|
||||
super().__init__(argv)
|
||||
self.path = os.path.join(os.path.dirname(sys.argv[0]).replace('Resources', 'MacOS'), SCRIPT_NAME)
|
||||
self.tunnels = []
|
||||
self.lastWindowClosed.connect(self.closeTunnels) # type: ignore
|
||||
|
||||
def cleanTunnels(self) -> None:
|
||||
def isRunning(p: subprocess.Popen):
|
||||
try:
|
||||
if p.poll() is None:
|
||||
return True
|
||||
except Exception as e:
|
||||
logger.debug('Got error polling subprocess: %s', e)
|
||||
return False
|
||||
|
||||
for k in [i for i, tunnel in enumerate(self.tunnels) if not isRunning(tunnel)]:
|
||||
try:
|
||||
del self.tunnels[k]
|
||||
except Exception as e:
|
||||
logger.debug('Error closing tunnel: %s', e)
|
||||
|
||||
def closeTunnels(self) -> None:
|
||||
logger.debug('Closing remaining tunnels')
|
||||
for tunnel in self.tunnels:
|
||||
logger.debug('Checking %s - "%s"', tunnel, tunnel.poll())
|
||||
if tunnel.poll() is None: # Running
|
||||
logger.info('Found running tunnel %s, closing it', tunnel.pid)
|
||||
tunnel.kill()
|
||||
|
||||
def event(self, evnt: QtCore.QEvent) -> bool:
|
||||
if evnt.type() == QtCore.QEvent.FileOpen:
|
||||
fe = typing.cast(QtGui.QFileOpenEvent, evnt)
|
||||
logger.debug('Got url: %s', fe.url().url())
|
||||
fe.accept()
|
||||
logger.debug('Spawning %s', self.path)
|
||||
# First, remove all finished tunnel processed from check queue
|
||||
self.cleanTunnels()
|
||||
# And now add a new one
|
||||
self.tunnels.append(subprocess.Popen([self.path, fe.url().url()]))
|
||||
|
||||
return super().event(evnt)
|
||||
|
||||
|
||||
def main(args: typing.List[str]):
|
||||
if len(args) > 1:
|
||||
UDSClient.main(args)
|
||||
else:
|
||||
app = UdsApplication(sys.argv)
|
||||
window = QtWidgets.QMainWindow()
|
||||
Ui_MacLauncher().setupUi(window)
|
||||
|
||||
window.showMinimized()
|
||||
|
||||
sys.exit(app.exec())
|
||||
|
||||
if __name__ == "__main__":
|
||||
main(args=sys.argv)
|
||||
|
75
client-py3/full/src/UDSLauncherMac.py
Normal file
75
client-py3/full/src/UDSLauncherMac.py
Normal file
@@ -0,0 +1,75 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
# Form implementation generated from reading ui file 'UDSLauncherMac.ui'
|
||||
#
|
||||
# Created by: PyQt5 UI code generator 5.15.2
|
||||
#
|
||||
# WARNING: Any manual changes made to this file will be lost when pyuic5 is
|
||||
# run again. Do not edit this file unless you know what you are doing.
|
||||
|
||||
|
||||
from PyQt5 import QtCore, QtGui, QtWidgets
|
||||
|
||||
|
||||
class Ui_MacLauncher(object):
|
||||
def setupUi(self, MacLauncher):
|
||||
MacLauncher.setObjectName("MacLauncher")
|
||||
MacLauncher.setWindowModality(QtCore.Qt.NonModal)
|
||||
MacLauncher.resize(235, 120)
|
||||
MacLauncher.setCursor(QtGui.QCursor(QtCore.Qt.ArrowCursor))
|
||||
icon = QtGui.QIcon()
|
||||
icon.addPixmap(QtGui.QPixmap(":/images/logo-uds-small"), QtGui.QIcon.Normal, QtGui.QIcon.Off)
|
||||
MacLauncher.setWindowIcon(icon)
|
||||
MacLauncher.setWindowOpacity(1.0)
|
||||
self.centralwidget = QtWidgets.QWidget(MacLauncher)
|
||||
self.centralwidget.setAutoFillBackground(True)
|
||||
self.centralwidget.setObjectName("centralwidget")
|
||||
self.verticalLayout_2 = QtWidgets.QVBoxLayout(self.centralwidget)
|
||||
self.verticalLayout_2.setContentsMargins(4, 4, 4, 4)
|
||||
self.verticalLayout_2.setSpacing(4)
|
||||
self.verticalLayout_2.setObjectName("verticalLayout_2")
|
||||
self.frame = QtWidgets.QFrame(self.centralwidget)
|
||||
self.frame.setFrameShape(QtWidgets.QFrame.StyledPanel)
|
||||
self.frame.setFrameShadow(QtWidgets.QFrame.Raised)
|
||||
self.frame.setObjectName("frame")
|
||||
self.verticalLayout = QtWidgets.QVBoxLayout(self.frame)
|
||||
self.verticalLayout.setObjectName("verticalLayout")
|
||||
self.topLabel = QtWidgets.QLabel(self.frame)
|
||||
self.topLabel.setTextFormat(QtCore.Qt.RichText)
|
||||
self.topLabel.setObjectName("topLabel")
|
||||
self.verticalLayout.addWidget(self.topLabel)
|
||||
self.image = QtWidgets.QLabel(self.frame)
|
||||
self.image.setMinimumSize(QtCore.QSize(0, 32))
|
||||
self.image.setAutoFillBackground(True)
|
||||
self.image.setText("")
|
||||
self.image.setPixmap(QtGui.QPixmap(":/images/logo-uds-small"))
|
||||
self.image.setScaledContents(False)
|
||||
self.image.setAlignment(QtCore.Qt.AlignCenter)
|
||||
self.image.setObjectName("image")
|
||||
self.verticalLayout.addWidget(self.image)
|
||||
self.label_2 = QtWidgets.QLabel(self.frame)
|
||||
self.label_2.setTextFormat(QtCore.Qt.RichText)
|
||||
self.label_2.setObjectName("label_2")
|
||||
self.verticalLayout.addWidget(self.label_2)
|
||||
self.verticalLayout_2.addWidget(self.frame)
|
||||
MacLauncher.setCentralWidget(self.centralwidget)
|
||||
|
||||
self.retranslateUi(MacLauncher)
|
||||
QtCore.QMetaObject.connectSlotsByName(MacLauncher)
|
||||
|
||||
def retranslateUi(self, MacLauncher):
|
||||
_translate = QtCore.QCoreApplication.translate
|
||||
MacLauncher.setWindowTitle(_translate("MacLauncher", "UDS Launcher"))
|
||||
self.topLabel.setText(_translate("MacLauncher", "<html><head/><body><p align=\"center\"><span style=\" font-size:12pt; font-weight:600;\">UDS Launcher</span></p></body></html>"))
|
||||
self.label_2.setText(_translate("MacLauncher", "<html><head/><body><p align=\"center\"><span style=\" font-size:6pt;\">Closing this window will end all UDS tunnels</span></p></body></html>"))
|
||||
import UDSResources_rc
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
import sys
|
||||
app = QtWidgets.QApplication(sys.argv)
|
||||
MacLauncher = QtWidgets.QMainWindow()
|
||||
ui = Ui_MacLauncher()
|
||||
ui.setupUi(MacLauncher)
|
||||
MacLauncher.show()
|
||||
sys.exit(app.exec_())
|
113
client-py3/full/src/UDSLauncherMac.ui
Normal file
113
client-py3/full/src/UDSLauncherMac.ui
Normal file
@@ -0,0 +1,113 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<ui version="4.0">
|
||||
<class>MacLauncher</class>
|
||||
<widget class="QMainWindow" name="MacLauncher">
|
||||
<property name="windowModality">
|
||||
<enum>Qt::NonModal</enum>
|
||||
</property>
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>235</width>
|
||||
<height>120</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="cursor">
|
||||
<cursorShape>ArrowCursor</cursorShape>
|
||||
</property>
|
||||
<property name="windowTitle">
|
||||
<string>UDS Launcher</string>
|
||||
</property>
|
||||
<property name="windowIcon">
|
||||
<iconset resource="UDSResources.qrc">
|
||||
<normaloff>:/images/logo-uds-small</normaloff>:/images/logo-uds-small</iconset>
|
||||
</property>
|
||||
<property name="windowOpacity">
|
||||
<double>1.000000000000000</double>
|
||||
</property>
|
||||
<widget class="QWidget" name="centralwidget">
|
||||
<property name="autoFillBackground">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
<layout class="QVBoxLayout" name="verticalLayout_2">
|
||||
<property name="spacing">
|
||||
<number>4</number>
|
||||
</property>
|
||||
<property name="leftMargin">
|
||||
<number>4</number>
|
||||
</property>
|
||||
<property name="topMargin">
|
||||
<number>4</number>
|
||||
</property>
|
||||
<property name="rightMargin">
|
||||
<number>4</number>
|
||||
</property>
|
||||
<property name="bottomMargin">
|
||||
<number>4</number>
|
||||
</property>
|
||||
<item>
|
||||
<widget class="QFrame" name="frame">
|
||||
<property name="frameShape">
|
||||
<enum>QFrame::StyledPanel</enum>
|
||||
</property>
|
||||
<property name="frameShadow">
|
||||
<enum>QFrame::Raised</enum>
|
||||
</property>
|
||||
<layout class="QVBoxLayout" name="verticalLayout">
|
||||
<item>
|
||||
<widget class="QLabel" name="topLabel">
|
||||
<property name="text">
|
||||
<string><html><head/><body><p align="center"><span style=" font-size:12pt; font-weight:600;">UDS Launcher</span></p></body></html></string>
|
||||
</property>
|
||||
<property name="textFormat">
|
||||
<enum>Qt::RichText</enum>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QLabel" name="image">
|
||||
<property name="minimumSize">
|
||||
<size>
|
||||
<width>0</width>
|
||||
<height>32</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="autoFillBackground">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string notr="true"/>
|
||||
</property>
|
||||
<property name="pixmap">
|
||||
<pixmap resource="UDSResources.qrc">:/images/logo-uds-small</pixmap>
|
||||
</property>
|
||||
<property name="scaledContents">
|
||||
<bool>false</bool>
|
||||
</property>
|
||||
<property name="alignment">
|
||||
<set>Qt::AlignCenter</set>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QLabel" name="label_2">
|
||||
<property name="text">
|
||||
<string><html><head/><body><p align="center"><span style=" font-size:6pt;">Closing this window will end all UDS tunnels</span></p></body></html></string>
|
||||
</property>
|
||||
<property name="textFormat">
|
||||
<enum>Qt::RichText</enum>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</widget>
|
||||
<resources>
|
||||
<include location="UDSResources.qrc"/>
|
||||
</resources>
|
||||
<connections/>
|
||||
</ui>
|
@@ -2,158 +2,13 @@
|
||||
|
||||
# Resource object code
|
||||
#
|
||||
# Created by: The Resource Compiler for PyQt5 (Qt v5.13.2)
|
||||
# Created by: The Resource Compiler for PyQt5 (Qt v5.15.2)
|
||||
#
|
||||
# WARNING! All changes made in this file will be lost!
|
||||
|
||||
from PyQt5 import QtCore
|
||||
|
||||
qt_resource_data = b"\
|
||||
\x00\x00\x08\xed\
|
||||
\x89\
|
||||
\x50\x4e\x47\x0d\x0a\x1a\x0a\x00\x00\x00\x0d\x49\x48\x44\x52\x00\
|
||||
\x00\x00\x25\x00\x00\x00\x30\x08\x06\x00\x00\x00\x96\x85\xb3\x2b\
|
||||
\x00\x00\x00\x06\x62\x4b\x47\x44\x00\xff\x00\xff\x00\xff\xa0\xbd\
|
||||
\xa7\x93\x00\x00\x00\x09\x70\x48\x59\x73\x00\x00\x0b\x13\x00\x00\
|
||||
\x0b\x13\x01\x00\x9a\x9c\x18\x00\x00\x00\x07\x74\x49\x4d\x45\x07\
|
||||
\xdf\x03\x1b\x0e\x10\x3b\x1e\x53\x78\x6b\x00\x00\x08\x7a\x49\x44\
|
||||
\x41\x54\x58\xc3\xed\x98\x7b\x70\x54\xf5\x15\xc7\x3f\xbf\xfb\xd8\
|
||||
\x7b\xb3\x79\x42\x70\x09\x08\xa8\xa5\x20\x54\x4a\xaa\xa5\x6b\xad\
|
||||
\x81\xfa\x18\xf1\x3a\xad\x53\x67\xaa\x16\xad\xd1\x5a\x75\xc4\x19\
|
||||
\xa7\x63\xeb\xab\x1a\x94\x91\x91\xaa\xb5\x76\x3a\xd3\x97\xa9\x75\
|
||||
\x98\x96\xd2\x86\x19\x5b\x47\x85\x61\xd5\x1a\x35\xc1\x16\x57\xa8\
|
||||
\xe1\xfd\x0e\x04\x03\x92\x9b\x40\x4c\x76\x37\xd9\xec\x7d\xfc\xfa\
|
||||
\xc7\xde\xd0\x4d\xd8\x4d\x42\xa1\xd3\x3f\xf4\xcc\xec\x24\xfb\xdb\
|
||||
\x73\xcf\xef\xfb\x3b\xe7\x7b\xce\x3d\xbf\x03\x9f\xc9\x67\xf2\x7f\
|
||||
\x94\x58\x3c\x72\x5a\xcf\x5f\xb1\xc8\xe4\x8a\x45\xe6\xa8\x7a\x62\
|
||||
\x2c\x40\xac\xa8\x9d\xfb\xdd\x00\xa6\x07\x9f\xb3\x81\xb3\x1c\x47\
|
||||
\x96\xef\xde\xe3\x86\xf7\xb7\xba\x66\x67\x97\x27\x92\x29\xc9\xc0\
|
||||
\x80\x74\x84\xa0\x17\xf8\x18\x68\x03\xb6\x34\x36\xa4\xf7\xe4\x02\
|
||||
\x6c\x6c\x48\x8f\x1d\xd4\x20\x90\xc0\x33\xa5\xc0\xc5\xc0\x0d\xc0\
|
||||
\x95\x01\x98\xec\xc3\x02\x76\xed\x76\x59\xf7\x16\x74\xa7\x2a\xf1\
|
||||
\xb4\x08\x52\x14\x81\x00\x55\xf1\xd0\x45\x0f\xaa\xdf\x83\xf0\xd3\
|
||||
\x08\xd9\x8f\xe2\xf7\xf5\x80\xf7\x5b\x50\x7f\xd7\xd8\x90\x3e\x50\
|
||||
\x08\xd8\x88\x9e\x7a\x73\xd3\xc4\x99\x9e\x27\x7f\x13\x80\x39\x49\
|
||||
\x14\x05\x7e\xf9\x82\x4a\x4b\xea\x01\x12\xe6\xe5\xf8\x98\x20\x3d\
|
||||
\x94\xa2\x32\xb4\x8a\x89\x08\xc3\x40\x75\x93\x18\x99\x7d\x14\xa5\
|
||||
\x37\x52\x92\x7a\x9b\xe2\x54\x13\x9a\xdb\x71\x50\x0a\xf5\xa6\xc6\
|
||||
\x86\xf4\x86\x7c\xc0\x46\x02\x25\x5e\x6e\x9a\xb0\x24\x5c\xa4\x2c\
|
||||
\x93\xb2\x80\x82\x80\xf8\xf6\x49\xfc\xb4\x79\x15\xaa\xec\x47\xe0\
|
||||
\xa2\x16\x8f\x43\x1b\x37\x29\x8b\x58\x4a\x40\x20\x85\x0a\x68\x80\
|
||||
\x47\x79\xe2\x35\xaa\x3a\x1e\x46\x73\x0f\xef\x01\x65\x4e\x63\x43\
|
||||
\xda\x19\x6e\x57\x2d\x84\x68\x42\x0d\xea\x79\x53\x94\xab\xcb\x4a\
|
||||
\xd4\x1a\xd3\x28\x8c\x7d\x62\xe5\x00\x42\xfa\x6c\x6d\xaf\x46\x33\
|
||||
\x34\xb4\xca\x29\x08\x45\x19\x76\x72\x1f\x81\x8b\x40\x92\x36\x2e\
|
||||
\x20\xe4\xb4\x53\x94\x6e\xa9\x04\xd9\x74\x60\x9b\xdb\x7a\x52\x04\
|
||||
\x0a\x6d\xd6\xb5\x1e\x6f\xdb\x9e\xf4\xe6\xdd\xad\x03\x8e\xeb\x15\
|
||||
\x76\xa7\xa6\x38\x7c\xb3\xfa\xaf\x5c\x32\xbd\x09\x69\x54\x9c\x04\
|
||||
\x68\xa8\x64\x3d\x97\x36\x2f\xc0\x57\x0c\x80\x0b\xf3\xd2\x62\x24\
|
||||
\x4e\xe9\x21\x5e\xde\xba\xab\x7f\xed\xc1\xf6\xcc\x88\x19\x5a\x56\
|
||||
\xd4\x43\xed\x25\x2f\x32\x6d\xdc\xfe\xec\xbe\xa3\xa5\xbc\xf4\x72\
|
||||
\x51\x8e\x1d\x54\x6d\x9d\xce\xaa\xe5\xee\x80\xa6\xb1\xe4\x9f\x9b\
|
||||
\x52\x6d\x76\x97\x4b\x21\x27\x78\xbe\xca\xb4\xca\x83\xdc\xf1\x95\
|
||||
\x67\xa8\x30\xbb\xf0\x65\xe1\xb3\x4a\xa1\xa0\xbb\xed\x08\x99\x01\
|
||||
\xd8\x7d\x4a\xa0\x56\x2e\x77\xb2\xc0\x9e\x72\xb7\xfb\x52\xd6\x35\
|
||||
\x6f\x4c\xd2\xdd\xe3\xa1\xaa\xf9\xf9\x35\xe0\x9a\x54\x4f\xfe\x07\
|
||||
\xb7\xcf\x5d\x42\x99\xd1\x8d\xe3\x87\x0a\x96\x46\xdd\x69\x47\x48\
|
||||
\x07\x60\xc3\x29\x87\x6f\x50\xfe\xfc\xb4\xbb\xaa\xab\x3b\xb3\x74\
|
||||
\xfd\xc6\x24\x7d\x7d\x3e\x4a\x01\xde\xf7\xbb\xc5\xcc\xab\x5a\xc7\
|
||||
\x3d\x17\xde\xc7\xb9\xe5\x3b\xba\xd3\x5e\x78\xf7\x70\x40\x8a\x9f\
|
||||
\x44\x75\x6d\x84\xf4\xed\xc6\x86\x74\x67\xbe\x0a\x5f\x10\xd4\x8f\
|
||||
\x9e\x1b\xcf\xca\xe5\xd9\x6c\x3d\xd2\x7d\x83\x59\x77\xcf\x42\x31\
|
||||
\xbe\xac\x94\x0f\xb6\x26\xf0\xfc\xc2\x07\x70\xfc\x10\x73\x26\x34\
|
||||
\xf1\xf0\x57\xbf\x3b\xf0\xc3\x79\x77\x3d\x74\xf4\x58\xe9\xd9\xc0\
|
||||
\x75\xc0\x8b\x20\xfa\x34\xaf\x0b\xd5\xeb\x06\x78\xaf\x90\x8d\x82\
|
||||
\xa0\x7e\x7e\xff\x71\x00\xe5\xef\x1b\x6b\x96\x1c\xef\x49\x1d\x0b\
|
||||
\x1b\xda\xd2\x19\xb3\x6e\x61\xdf\xe1\x89\xec\x6b\xeb\x1f\xd1\xb3\
|
||||
\x12\x41\x59\xe8\x58\xd5\xc5\x93\xd7\x2e\x8d\xdd\x3a\xd1\x3c\xfa\
|
||||
\x44\xd9\x2b\xc0\x5d\x8e\x56\x3c\xc3\xc8\xec\x7b\x5b\xf5\x93\x80\
|
||||
\x7c\x17\x38\xf5\x8a\x7e\xfd\x83\x28\xba\xc2\x9a\xd9\xb3\x6a\xae\
|
||||
\x39\xcc\x6a\x8e\xf4\x4c\x26\xec\xac\xa2\x38\x75\x27\x57\x5e\x12\
|
||||
\xa6\x72\x9c\x36\x96\xe8\xaf\x05\x6e\xb5\xa2\xf6\x71\x80\x4b\x6f\
|
||||
\x9b\x7b\xa3\xee\x7c\xbc\x5a\xf1\x7b\x2e\x6c\x6c\xc8\xb4\x9c\x72\
|
||||
\xf6\xbd\xf4\x2c\xbe\xa6\xd2\xba\xa3\xd5\xc7\x4e\x4e\x26\xa4\x81\
|
||||
\x6b\x5e\x4f\x52\xb9\x8d\x0d\x2d\x7d\xa4\x07\x24\x42\x8c\xe2\x34\
|
||||
\xf8\x06\xf0\xc4\xe0\x82\x99\xde\x76\x4c\xf1\x7b\x5e\x02\xf5\x50\
|
||||
\xa1\x8e\x61\x74\xa2\x0b\xde\xd3\xfd\x96\xa4\x22\x82\xda\xa2\x18\
|
||||
\xf8\xe5\x4b\xb1\x13\xb3\x89\x6f\x4e\x8d\xb5\x0b\xb9\x37\x16\x8f\
|
||||
\xdc\x92\xad\x7d\xc6\x3b\xa0\xdc\x02\xb2\xfb\x94\xba\x84\xa1\x1e\
|
||||
\x0b\x85\x91\x99\x5d\x99\xca\x75\x53\x5d\xc3\x3a\xf1\x94\x9a\xd9\
|
||||
\x48\xa8\x73\x3e\x73\x66\xea\x44\xab\xc3\x64\x9c\x51\xab\xe6\xc7\
|
||||
\x52\x32\xe5\x9a\x8b\x6d\x7f\x34\xc5\x51\x3d\xb5\x72\x79\xa6\x0f\
|
||||
\xf8\x83\x9e\xa8\xe3\x44\x4d\x94\xe0\x85\xe6\xe1\x54\xfc\x9a\xbd\
|
||||
\xad\x09\x5a\x3f\xca\x14\xac\x5f\xb9\xaf\x53\x21\xb8\x6a\x2c\x24\
|
||||
\x1c\x53\x9d\x42\x29\x5d\x2a\x9c\x2d\x5d\x7a\x6a\xc5\x7f\x7c\x2b\
|
||||
\xc1\x2d\xfe\x3e\xfd\x7a\x2d\x5b\x76\x7e\x42\x22\xe9\x8d\xc6\x2f\
|
||||
\x01\x4c\x3a\x23\xa0\x6a\xeb\x74\x56\x3e\x99\xf0\x41\x7c\x4f\xef\
|
||||
\x7d\x0c\xc5\xd9\x3d\x04\x58\xa6\xfc\x57\x74\x25\xe7\xb2\x63\x6f\
|
||||
\x02\xcf\x1b\xd1\x94\x0b\x6c\x1a\x0b\x28\x75\x34\x85\x2d\xcd\x7e\
|
||||
\x16\xd8\x72\x67\x6f\xf5\xfc\xbe\x22\xc5\xdd\x5e\xe3\x19\x57\x83\
|
||||
\x28\x09\xce\x6f\x20\xf5\xd9\x24\x3a\xd7\x30\xa1\x22\x4d\x69\x49\
|
||||
\xc1\x32\xf1\x82\x15\xb5\x57\x9c\xb9\xf0\x9d\x48\x70\xef\x49\x25\
|
||||
\xf3\xce\x9f\x8c\x9e\x7b\x10\xb2\xf7\xc4\xb2\x6f\x7c\x9d\xfe\xd0\
|
||||
\xdd\xfc\x6b\x87\x87\xeb\xe5\x25\xfc\x0b\xc0\x03\x63\xbd\x7c\xa8\
|
||||
\x63\xc1\xb2\xa5\xd9\x0f\xfe\x92\xa9\x9e\x2f\x9b\x84\xb7\xf7\x7c\
|
||||
\x35\xf3\xde\x2c\xd7\xfc\x36\x08\x23\xeb\x30\x7d\x3a\xe9\xee\x35\
|
||||
\x14\x85\xba\x99\x58\xa9\xe5\xf6\x24\xcf\x00\x8f\x59\x51\x3b\x39\
|
||||
\xfc\x12\x72\xda\x9e\xaa\xad\xd3\xb3\xd9\xf8\x13\x8e\x01\x37\x2a\
|
||||
\x99\xf5\x7f\x2b\xea\x9c\x85\xe2\xbc\x0f\x02\x3c\x7d\x1a\x8a\x3e\
|
||||
\x89\x9d\x7b\xd3\x88\xec\x1b\x3b\x03\x3c\x02\xd4\x59\x51\xbb\x77\
|
||||
\xac\x80\xc6\x54\xa7\xf2\x12\x3f\x78\x51\xd7\xd6\xa9\x8f\x81\xff\
|
||||
\xa0\x1b\xbe\xa3\xd4\xd7\x2f\x45\x4b\x2c\xc3\x50\xdb\xb9\xf9\xda\
|
||||
\xf1\x47\x5d\x4f\x3e\x60\x45\xed\x55\xf9\xae\x69\x67\x1c\xd4\x10\
|
||||
\xaf\x2d\x77\xa8\xad\x53\xbe\x84\xf4\x1f\x15\x92\x8b\x7c\xa1\x98\
|
||||
\xe7\x4e\x29\xda\xb6\xb0\xa6\xf4\xfe\x05\x73\x8f\x6e\x1f\xe4\xcf\
|
||||
\xa9\x00\x2a\x78\xfb\x1d\x8d\x8c\xb1\x78\xe4\x24\x9d\x9b\x1f\xd1\
|
||||
\xab\x6f\x7f\x3c\xb4\x60\x73\x6b\x44\xcb\x67\xa3\x90\xcd\x11\xf7\
|
||||
\x3a\x5d\x50\x23\xd9\x1c\x66\x7b\x7c\x2c\x1e\xf9\x62\x2c\x1e\x39\
|
||||
\xa7\xd0\xfe\x62\xd8\x0f\xe7\x02\x1b\xac\xa8\x5d\x55\x60\x83\x1f\
|
||||
\x00\x33\xad\xa8\x7d\x6f\xb0\x76\x14\xd8\x09\xf4\x07\x49\x53\x01\
|
||||
\x84\x81\x57\x81\xa7\xac\xa8\x9d\xca\xb9\x6d\xcf\xc8\x36\x7a\xf4\
|
||||
\x02\xbb\x80\x09\x40\x0d\x50\x6f\x45\xed\x67\x73\xb9\xa7\xe5\xc9\
|
||||
\xc6\xca\x11\x0e\x6f\x02\xc5\x39\xdf\xcf\x02\x2e\xb5\xa2\xf6\xfe\
|
||||
\x1c\xf0\x4a\x00\xfe\xfd\x58\x3c\x72\xbd\x15\xb5\x77\x05\x3f\x6d\
|
||||
\x08\xfa\xaa\xb5\x39\xba\x26\xf0\x7a\x2c\x1e\x29\xb5\xa2\xf6\xe3\
|
||||
\x83\xdc\x53\x38\x7d\xd1\x86\xcd\x20\x7c\x2b\x6a\xff\x02\x58\x01\
|
||||
\xd4\x05\xeb\x93\x80\x72\x60\x48\x53\x67\x45\xed\x34\xb0\x18\xe8\
|
||||
\x0d\x0e\x33\xd4\xe0\x99\x90\x61\x59\xd6\x0c\x58\xb1\x78\x64\x8a\
|
||||
\x15\xb5\xdb\x63\xf1\xc8\xcf\x80\xa7\x63\xf1\x48\x0b\x90\x00\x8e\
|
||||
\x00\x6d\x56\xd4\xde\x0a\xec\x1c\xe4\x69\xbe\xf0\x89\x42\x23\xa0\
|
||||
\xff\xa2\x8c\x78\xc1\x4b\x58\x0f\x00\xff\x38\x16\x8f\x7c\x39\x98\
|
||||
\xda\x8c\x03\x66\x03\x97\xc5\xe2\x91\x69\xc0\x8b\x56\xd4\x7e\xbd\
|
||||
\x90\xa7\xbc\x02\xa7\x3e\x71\x69\x06\xfc\x31\xce\xb3\xce\x0a\x0e\
|
||||
\xd0\x11\x8b\x47\xae\x02\x34\x2b\x6a\xaf\x1b\xec\x14\x62\xf1\x48\
|
||||
\x51\x30\x66\x8a\x02\x8f\xc6\xe2\x91\xb6\x41\xfe\x29\xc3\xdc\x7f\
|
||||
\x10\xb0\x63\xf1\xc8\x75\x05\x42\x73\x25\xf0\xfe\x68\x21\x8c\xc5\
|
||||
\x23\xa5\xc0\x43\xc0\xab\x56\xd4\xee\x03\xce\x07\x9e\x1b\xa6\xd7\
|
||||
\x6f\x45\x6d\x1b\xd8\x1c\x80\x2f\x39\x29\x14\xaf\x35\xcd\xe1\xda\
|
||||
\x05\xdb\x08\x5c\xbc\x1a\x78\x12\x78\x27\x48\xf7\x69\x41\x46\xe9\
|
||||
\x56\xd4\x5e\x94\xe3\x95\x04\x70\x6f\x30\xa9\x53\x03\xc3\x17\x00\
|
||||
\xdf\x01\xfe\x68\x45\xed\xe7\x72\x74\xd7\x01\x4e\x00\x6e\x7f\x70\
|
||||
\xa9\xa8\x06\xee\x03\x5a\xac\xa8\xfd\x50\x5e\x7e\xac\x58\xf9\xb8\
|
||||
\x72\x7b\xed\x32\x7f\xcd\xfa\x99\xc5\xba\x71\xec\x56\x29\xb5\xb9\
|
||||
\x42\x78\x2a\xc8\x0e\x21\xbc\x37\x16\xce\xfb\xa4\x39\x57\x7f\x4d\
|
||||
\xf3\xac\x07\x75\xb3\xcb\x43\xf8\x02\x29\x84\x94\x6a\x1a\xe4\x81\
|
||||
\xe2\xf1\x9d\x6f\xce\xff\x3c\x27\xa6\x22\xcf\x3f\xff\xfb\xb2\xc5\
|
||||
\x8b\xef\xec\x7d\x63\x53\xf9\x65\xd2\xd7\xae\x90\x52\x9d\x0c\xd2\
|
||||
\x45\xc8\x43\x8a\x9a\x5e\xb7\xf0\xa2\xe4\x87\x00\x0d\xaf\xdc\xc4\
|
||||
\xa2\x6f\xfd\x65\x28\xa8\xfa\xfa\xfa\xf3\x84\x92\x69\xf3\xdd\x70\
|
||||
\xcd\x40\xdf\x94\x23\xbd\x1d\x0b\xba\x8c\xe2\x43\x45\xba\x69\x9b\
|
||||
\xba\xd9\xa9\xeb\x66\xa7\x16\x64\xce\x78\x84\xff\x51\x26\x35\xf5\
|
||||
\x6c\x29\x55\x0f\x29\xfc\xfe\xc4\xf4\xce\x70\xc5\xf6\x73\x14\x35\
|
||||
\x83\xa2\xa6\x3b\x15\xb5\xcf\x00\xe1\x03\xdd\xd9\x22\x29\x76\x3a\
|
||||
\xe9\x09\x5a\xdf\x27\x5f\x38\xae\x86\x7a\x8b\x9d\xfe\xaa\x94\xef\
|
||||
\x99\x99\xf2\xaa\xc6\xa9\x5a\xa8\xc7\x01\x79\xe8\xee\xbb\x17\xf7\
|
||||
\xe5\x23\xba\x2f\xfd\xd0\xd7\x84\xe2\xce\x30\x4b\x0e\x9a\x66\xc9\
|
||||
\xc1\xcf\x01\x21\x20\x02\x1c\x00\xa6\x06\xc5\xb5\x03\xa9\x74\x84\
|
||||
\xc2\x87\xcf\x0f\x6c\x54\x19\x25\x6d\xfb\x80\x99\xc0\xd1\x6c\x2f\
|
||||
\x3e\xd8\x9a\xb2\x39\x1b\x2a\x79\xb9\x6e\x76\x66\xca\xab\xde\x3d\
|
||||
\x14\x84\xd9\x05\x7a\x82\xaa\x7e\x18\x84\x07\xec\xcd\x07\xea\xa3\
|
||||
\x60\x9a\xfb\x41\x90\x65\x22\xa7\xd2\xbb\x01\x6f\xd4\x20\x4b\x1d\
|
||||
\x60\x4d\xa0\x37\xb8\xf6\x56\x4e\x06\x1b\xc1\xe7\x78\xc0\x21\x2d\
|
||||
\xd0\xcd\xb5\x2b\x81\x0f\x83\x35\xf7\x7f\x3a\x6f\xaf\xaf\xaf\xcf\
|
||||
\xfb\xff\x67\xf2\xa9\x90\x7f\x03\xbb\x9c\x9c\x9c\x32\xd8\x63\xca\
|
||||
\x00\x00\x00\x00\x49\x45\x4e\x44\xae\x42\x60\x82\
|
||||
\x00\x00\x8e\x8c\
|
||||
\x89\
|
||||
\x50\x4e\x47\x0d\x0a\x1a\x0a\x00\x00\x00\x0d\x49\x48\x44\x52\x00\
|
||||
@@ -2437,6 +2292,151 @@ qt_resource_data = b"\
|
||||
\x00\x80\x10\x0c\x00\x00\x00\x10\x82\x01\x00\x00\x00\x42\x30\x00\
|
||||
\x00\x00\xe0\x96\xff\x7f\x00\x27\x97\xdb\xb5\x4d\x29\xcb\x9d\x00\
|
||||
\x00\x00\x00\x49\x45\x4e\x44\xae\x42\x60\x82\
|
||||
\x00\x00\x08\xed\
|
||||
\x89\
|
||||
\x50\x4e\x47\x0d\x0a\x1a\x0a\x00\x00\x00\x0d\x49\x48\x44\x52\x00\
|
||||
\x00\x00\x25\x00\x00\x00\x30\x08\x06\x00\x00\x00\x96\x85\xb3\x2b\
|
||||
\x00\x00\x00\x06\x62\x4b\x47\x44\x00\xff\x00\xff\x00\xff\xa0\xbd\
|
||||
\xa7\x93\x00\x00\x00\x09\x70\x48\x59\x73\x00\x00\x0b\x13\x00\x00\
|
||||
\x0b\x13\x01\x00\x9a\x9c\x18\x00\x00\x00\x07\x74\x49\x4d\x45\x07\
|
||||
\xdf\x03\x1b\x0e\x10\x3b\x1e\x53\x78\x6b\x00\x00\x08\x7a\x49\x44\
|
||||
\x41\x54\x58\xc3\xed\x98\x7b\x70\x54\xf5\x15\xc7\x3f\xbf\xfb\xd8\
|
||||
\x7b\xb3\x79\x42\x70\x09\x08\xa8\xa5\x20\x54\x4a\xaa\xa5\x6b\xad\
|
||||
\x81\xfa\x18\xf1\x3a\xad\x53\x67\xaa\x16\xad\xd1\x5a\x75\xc4\x19\
|
||||
\xa7\x63\xeb\xab\x1a\x94\x91\x91\xaa\xb5\x76\x3a\xd3\x97\xa9\x75\
|
||||
\x98\x96\xd2\x86\x19\x5b\x47\x85\x61\xd5\x1a\x35\xc1\x16\x57\xa8\
|
||||
\xe1\xfd\x0e\x04\x03\x92\x9b\x40\x4c\x76\x37\xd9\xec\x7d\xfc\xfa\
|
||||
\xc7\xde\xd0\x4d\xd8\x4d\x42\xa1\xd3\x3f\xf4\xcc\xec\x24\xfb\xdb\
|
||||
\x73\xcf\xef\xfb\x3b\xe7\x7b\xce\x3d\xbf\x03\x9f\xc9\x67\xf2\x7f\
|
||||
\x94\x58\x3c\x72\x5a\xcf\x5f\xb1\xc8\xe4\x8a\x45\xe6\xa8\x7a\x62\
|
||||
\x2c\x40\xac\xa8\x9d\xfb\xdd\x00\xa6\x07\x9f\xb3\x81\xb3\x1c\x47\
|
||||
\x96\xef\xde\xe3\x86\xf7\xb7\xba\x66\x67\x97\x27\x92\x29\xc9\xc0\
|
||||
\x80\x74\x84\xa0\x17\xf8\x18\x68\x03\xb6\x34\x36\xa4\xf7\xe4\x02\
|
||||
\x6c\x6c\x48\x8f\x1d\xd4\x20\x90\xc0\x33\xa5\xc0\xc5\xc0\x0d\xc0\
|
||||
\x95\x01\x98\xec\xc3\x02\x76\xed\x76\x59\xf7\x16\x74\xa7\x2a\xf1\
|
||||
\xb4\x08\x52\x14\x81\x00\x55\xf1\xd0\x45\x0f\xaa\xdf\x83\xf0\xd3\
|
||||
\x08\xd9\x8f\xe2\xf7\xf5\x80\xf7\x5b\x50\x7f\xd7\xd8\x90\x3e\x50\
|
||||
\x08\xd8\x88\x9e\x7a\x73\xd3\xc4\x99\x9e\x27\x7f\x13\x80\x39\x49\
|
||||
\x14\x05\x7e\xf9\x82\x4a\x4b\xea\x01\x12\xe6\xe5\xf8\x98\x20\x3d\
|
||||
\x94\xa2\x32\xb4\x8a\x89\x08\xc3\x40\x75\x93\x18\x99\x7d\x14\xa5\
|
||||
\x37\x52\x92\x7a\x9b\xe2\x54\x13\x9a\xdb\x71\x50\x0a\xf5\xa6\xc6\
|
||||
\x86\xf4\x86\x7c\xc0\x46\x02\x25\x5e\x6e\x9a\xb0\x24\x5c\xa4\x2c\
|
||||
\x93\xb2\x80\x82\x80\xf8\xf6\x49\xfc\xb4\x79\x15\xaa\xec\x47\xe0\
|
||||
\xa2\x16\x8f\x43\x1b\x37\x29\x8b\x58\x4a\x40\x20\x85\x0a\x68\x80\
|
||||
\x47\x79\xe2\x35\xaa\x3a\x1e\x46\x73\x0f\xef\x01\x65\x4e\x63\x43\
|
||||
\xda\x19\x6e\x57\x2d\x84\x68\x42\x0d\xea\x79\x53\x94\xab\xcb\x4a\
|
||||
\xd4\x1a\xd3\x28\x8c\x7d\x62\xe5\x00\x42\xfa\x6c\x6d\xaf\x46\x33\
|
||||
\x34\xb4\xca\x29\x08\x45\x19\x76\x72\x1f\x81\x8b\x40\x92\x36\x2e\
|
||||
\x20\xe4\xb4\x53\x94\x6e\xa9\x04\xd9\x74\x60\x9b\xdb\x7a\x52\x04\
|
||||
\x0a\x6d\xd6\xb5\x1e\x6f\xdb\x9e\xf4\xe6\xdd\xad\x03\x8e\xeb\x15\
|
||||
\x76\xa7\xa6\x38\x7c\xb3\xfa\xaf\x5c\x32\xbd\x09\x69\x54\x9c\x04\
|
||||
\x68\xa8\x64\x3d\x97\x36\x2f\xc0\x57\x0c\x80\x0b\xf3\xd2\x62\x24\
|
||||
\x4e\xe9\x21\x5e\xde\xba\xab\x7f\xed\xc1\xf6\xcc\x88\x19\x5a\x56\
|
||||
\xd4\x43\xed\x25\x2f\x32\x6d\xdc\xfe\xec\xbe\xa3\xa5\xbc\xf4\x72\
|
||||
\x51\x8e\x1d\x54\x6d\x9d\xce\xaa\xe5\xee\x80\xa6\xb1\xe4\x9f\x9b\
|
||||
\x52\x6d\x76\x97\x4b\x21\x27\x78\xbe\xca\xb4\xca\x83\xdc\xf1\x95\
|
||||
\x67\xa8\x30\xbb\xf0\x65\xe1\xb3\x4a\xa1\xa0\xbb\xed\x08\x99\x01\
|
||||
\xd8\x7d\x4a\xa0\x56\x2e\x77\xb2\xc0\x9e\x72\xb7\xfb\x52\xd6\x35\
|
||||
\x6f\x4c\xd2\xdd\xe3\xa1\xaa\xf9\xf9\x35\xe0\x9a\x54\x4f\xfe\x07\
|
||||
\xb7\xcf\x5d\x42\x99\xd1\x8d\xe3\x87\x0a\x96\x46\xdd\x69\x47\x48\
|
||||
\x07\x60\xc3\x29\x87\x6f\x50\xfe\xfc\xb4\xbb\xaa\xab\x3b\xb3\x74\
|
||||
\xfd\xc6\x24\x7d\x7d\x3e\x4a\x01\xde\xf7\xbb\xc5\xcc\xab\x5a\xc7\
|
||||
\x3d\x17\xde\xc7\xb9\xe5\x3b\xba\xd3\x5e\x78\xf7\x70\x40\x8a\x9f\
|
||||
\x44\x75\x6d\x84\xf4\xed\xc6\x86\x74\x67\xbe\x0a\x5f\x10\xd4\x8f\
|
||||
\x9e\x1b\xcf\xca\xe5\xd9\x6c\x3d\xd2\x7d\x83\x59\x77\xcf\x42\x31\
|
||||
\xbe\xac\x94\x0f\xb6\x26\xf0\xfc\xc2\x07\x70\xfc\x10\x73\x26\x34\
|
||||
\xf1\xf0\x57\xbf\x3b\xf0\xc3\x79\x77\x3d\x74\xf4\x58\xe9\xd9\xc0\
|
||||
\x75\xc0\x8b\x20\xfa\x34\xaf\x0b\xd5\xeb\x06\x78\xaf\x90\x8d\x82\
|
||||
\xa0\x7e\x7e\xff\x71\x00\xe5\xef\x1b\x6b\x96\x1c\xef\x49\x1d\x0b\
|
||||
\x1b\xda\xd2\x19\xb3\x6e\x61\xdf\xe1\x89\xec\x6b\xeb\x1f\xd1\xb3\
|
||||
\x12\x41\x59\xe8\x58\xd5\xc5\x93\xd7\x2e\x8d\xdd\x3a\xd1\x3c\xfa\
|
||||
\x44\xd9\x2b\xc0\x5d\x8e\x56\x3c\xc3\xc8\xec\x7b\x5b\xf5\x93\x80\
|
||||
\x7c\x17\x38\xf5\x8a\x7e\xfd\x83\x28\xba\xc2\x9a\xd9\xb3\x6a\xae\
|
||||
\x39\xcc\x6a\x8e\xf4\x4c\x26\xec\xac\xa2\x38\x75\x27\x57\x5e\x12\
|
||||
\xa6\x72\x9c\x36\x96\xe8\xaf\x05\x6e\xb5\xa2\xf6\x71\x80\x4b\x6f\
|
||||
\x9b\x7b\xa3\xee\x7c\xbc\x5a\xf1\x7b\x2e\x6c\x6c\xc8\xb4\x9c\x72\
|
||||
\xf6\xbd\xf4\x2c\xbe\xa6\xd2\xba\xa3\xd5\xc7\x4e\x4e\x26\xa4\x81\
|
||||
\x6b\x5e\x4f\x52\xb9\x8d\x0d\x2d\x7d\xa4\x07\x24\x42\x8c\xe2\x34\
|
||||
\xf8\x06\xf0\xc4\xe0\x82\x99\xde\x76\x4c\xf1\x7b\x5e\x02\xf5\x50\
|
||||
\xa1\x8e\x61\x74\xa2\x0b\xde\xd3\xfd\x96\xa4\x22\x82\xda\xa2\x18\
|
||||
\xf8\xe5\x4b\xb1\x13\xb3\x89\x6f\x4e\x8d\xb5\x0b\xb9\x37\x16\x8f\
|
||||
\xdc\x92\xad\x7d\xc6\x3b\xa0\xdc\x02\xb2\xfb\x94\xba\x84\xa1\x1e\
|
||||
\x0b\x85\x91\x99\x5d\x99\xca\x75\x53\x5d\xc3\x3a\xf1\x94\x9a\xd9\
|
||||
\x48\xa8\x73\x3e\x73\x66\xea\x44\xab\xc3\x64\x9c\x51\xab\xe6\xc7\
|
||||
\x52\x32\xe5\x9a\x8b\x6d\x7f\x34\xc5\x51\x3d\xb5\x72\x79\xa6\x0f\
|
||||
\xf8\x83\x9e\xa8\xe3\x44\x4d\x94\xe0\x85\xe6\xe1\x54\xfc\x9a\xbd\
|
||||
\xad\x09\x5a\x3f\xca\x14\xac\x5f\xb9\xaf\x53\x21\xb8\x6a\x2c\x24\
|
||||
\x1c\x53\x9d\x42\x29\x5d\x2a\x9c\x2d\x5d\x7a\x6a\xc5\x7f\x7c\x2b\
|
||||
\xc1\x2d\xfe\x3e\xfd\x7a\x2d\x5b\x76\x7e\x42\x22\xe9\x8d\xc6\x2f\
|
||||
\x01\x4c\x3a\x23\xa0\x6a\xeb\x74\x56\x3e\x99\xf0\x41\x7c\x4f\xef\
|
||||
\x7d\x0c\xc5\xd9\x3d\x04\x58\xa6\xfc\x57\x74\x25\xe7\xb2\x63\x6f\
|
||||
\x02\xcf\x1b\xd1\x94\x0b\x6c\x1a\x0b\x28\x75\x34\x85\x2d\xcd\x7e\
|
||||
\x16\xd8\x72\x67\x6f\xf5\xfc\xbe\x22\xc5\xdd\x5e\xe3\x19\x57\x83\
|
||||
\x28\x09\xce\x6f\x20\xf5\xd9\x24\x3a\xd7\x30\xa1\x22\x4d\x69\x49\
|
||||
\xc1\x32\xf1\x82\x15\xb5\x57\x9c\xb9\xf0\x9d\x48\x70\xef\x49\x25\
|
||||
\xf3\xce\x9f\x8c\x9e\x7b\x10\xb2\xf7\xc4\xb2\x6f\x7c\x9d\xfe\xd0\
|
||||
\xdd\xfc\x6b\x87\x87\xeb\xe5\x25\xfc\x0b\xc0\x03\x63\xbd\x7c\xa8\
|
||||
\x63\xc1\xb2\xa5\xd9\x0f\xfe\x92\xa9\x9e\x2f\x9b\x84\xb7\xf7\x7c\
|
||||
\x35\xf3\xde\x2c\xd7\xfc\x36\x08\x23\xeb\x30\x7d\x3a\xe9\xee\x35\
|
||||
\x14\x85\xba\x99\x58\xa9\xe5\xf6\x24\xcf\x00\x8f\x59\x51\x3b\x39\
|
||||
\xfc\x12\x72\xda\x9e\xaa\xad\xd3\xb3\xd9\xf8\x13\x8e\x01\x37\x2a\
|
||||
\x99\xf5\x7f\x2b\xea\x9c\x85\xe2\xbc\x0f\x02\x3c\x7d\x1a\x8a\x3e\
|
||||
\x89\x9d\x7b\xd3\x88\xec\x1b\x3b\x03\x3c\x02\xd4\x59\x51\xbb\x77\
|
||||
\xac\x80\xc6\x54\xa7\xf2\x12\x3f\x78\x51\xd7\xd6\xa9\x8f\x81\xff\
|
||||
\xa0\x1b\xbe\xa3\xd4\xd7\x2f\x45\x4b\x2c\xc3\x50\xdb\xb9\xf9\xda\
|
||||
\xf1\x47\x5d\x4f\x3e\x60\x45\xed\x55\xf9\xae\x69\x67\x1c\xd4\x10\
|
||||
\xaf\x2d\x77\xa8\xad\x53\xbe\x84\xf4\x1f\x15\x92\x8b\x7c\xa1\x98\
|
||||
\xe7\x4e\x29\xda\xb6\xb0\xa6\xf4\xfe\x05\x73\x8f\x6e\x1f\xe4\xcf\
|
||||
\xa9\x00\x2a\x78\xfb\x1d\x8d\x8c\xb1\x78\xe4\x24\x9d\x9b\x1f\xd1\
|
||||
\xab\x6f\x7f\x3c\xb4\x60\x73\x6b\x44\xcb\x67\xa3\x90\xcd\x11\xf7\
|
||||
\x3a\x5d\x50\x23\xd9\x1c\x66\x7b\x7c\x2c\x1e\xf9\x62\x2c\x1e\x39\
|
||||
\xa7\xd0\xfe\x62\xd8\x0f\xe7\x02\x1b\xac\xa8\x5d\x55\x60\x83\x1f\
|
||||
\x00\x33\xad\xa8\x7d\x6f\xb0\x76\x14\xd8\x09\xf4\x07\x49\x53\x01\
|
||||
\x84\x81\x57\x81\xa7\xac\xa8\x9d\xca\xb9\x6d\xcf\xc8\x36\x7a\xf4\
|
||||
\x02\xbb\x80\x09\x40\x0d\x50\x6f\x45\xed\x67\x73\xb9\xa7\xe5\xc9\
|
||||
\xc6\xca\x11\x0e\x6f\x02\xc5\x39\xdf\xcf\x02\x2e\xb5\xa2\xf6\xfe\
|
||||
\x1c\xf0\x4a\x00\xfe\xfd\x58\x3c\x72\xbd\x15\xb5\x77\x05\x3f\x6d\
|
||||
\x08\xfa\xaa\xb5\x39\xba\x26\xf0\x7a\x2c\x1e\x29\xb5\xa2\xf6\xe3\
|
||||
\x83\xdc\x53\x38\x7d\xd1\x86\xcd\x20\x7c\x2b\x6a\xff\x02\x58\x01\
|
||||
\xd4\x05\xeb\x93\x80\x72\x60\x48\x53\x67\x45\xed\x34\xb0\x18\xe8\
|
||||
\x0d\x0e\x33\xd4\xe0\x99\x90\x61\x59\xd6\x0c\x58\xb1\x78\x64\x8a\
|
||||
\x15\xb5\xdb\x63\xf1\xc8\xcf\x80\xa7\x63\xf1\x48\x0b\x90\x00\x8e\
|
||||
\x00\x6d\x56\xd4\xde\x0a\xec\x1c\xe4\x69\xbe\xf0\x89\x42\x23\xa0\
|
||||
\xff\xa2\x8c\x78\xc1\x4b\x58\x0f\x00\xff\x38\x16\x8f\x7c\x39\x98\
|
||||
\xda\x8c\x03\x66\x03\x97\xc5\xe2\x91\x69\xc0\x8b\x56\xd4\x7e\xbd\
|
||||
\x90\xa7\xbc\x02\xa7\x3e\x71\x69\x06\xfc\x31\xce\xb3\xce\x0a\x0e\
|
||||
\xd0\x11\x8b\x47\xae\x02\x34\x2b\x6a\xaf\x1b\xec\x14\x62\xf1\x48\
|
||||
\x51\x30\x66\x8a\x02\x8f\xc6\xe2\x91\xb6\x41\xfe\x29\xc3\xdc\x7f\
|
||||
\x10\xb0\x63\xf1\xc8\x75\x05\x42\x73\x25\xf0\xfe\x68\x21\x8c\xc5\
|
||||
\x23\xa5\xc0\x43\xc0\xab\x56\xd4\xee\x03\xce\x07\x9e\x1b\xa6\xd7\
|
||||
\x6f\x45\x6d\x1b\xd8\x1c\x80\x2f\x39\x29\x14\xaf\x35\xcd\xe1\xda\
|
||||
\x05\xdb\x08\x5c\xbc\x1a\x78\x12\x78\x27\x48\xf7\x69\x41\x46\xe9\
|
||||
\x56\xd4\x5e\x94\xe3\x95\x04\x70\x6f\x30\xa9\x53\x03\xc3\x17\x00\
|
||||
\xdf\x01\xfe\x68\x45\xed\xe7\x72\x74\xd7\x01\x4e\x00\x6e\x7f\x70\
|
||||
\xa9\xa8\x06\xee\x03\x5a\xac\xa8\xfd\x50\x5e\x7e\xac\x58\xf9\xb8\
|
||||
\x72\x7b\xed\x32\x7f\xcd\xfa\x99\xc5\xba\x71\xec\x56\x29\xb5\xb9\
|
||||
\x42\x78\x2a\xc8\x0e\x21\xbc\x37\x16\xce\xfb\xa4\x39\x57\x7f\x4d\
|
||||
\xf3\xac\x07\x75\xb3\xcb\x43\xf8\x02\x29\x84\x94\x6a\x1a\xe4\x81\
|
||||
\xe2\xf1\x9d\x6f\xce\xff\x3c\x27\xa6\x22\xcf\x3f\xff\xfb\xb2\xc5\
|
||||
\x8b\xef\xec\x7d\x63\x53\xf9\x65\xd2\xd7\xae\x90\x52\x9d\x0c\xd2\
|
||||
\x45\xc8\x43\x8a\x9a\x5e\xb7\xf0\xa2\xe4\x87\x00\x0d\xaf\xdc\xc4\
|
||||
\xa2\x6f\xfd\x65\x28\xa8\xfa\xfa\xfa\xf3\x84\x92\x69\xf3\xdd\x70\
|
||||
\xcd\x40\xdf\x94\x23\xbd\x1d\x0b\xba\x8c\xe2\x43\x45\xba\x69\x9b\
|
||||
\xba\xd9\xa9\xeb\x66\xa7\x16\x64\xce\x78\x84\xff\x51\x26\x35\xf5\
|
||||
\x6c\x29\x55\x0f\x29\xfc\xfe\xc4\xf4\xce\x70\xc5\xf6\x73\x14\x35\
|
||||
\x83\xa2\xa6\x3b\x15\xb5\xcf\x00\xe1\x03\xdd\xd9\x22\x29\x76\x3a\
|
||||
\xe9\x09\x5a\xdf\x27\x5f\x38\xae\x86\x7a\x8b\x9d\xfe\xaa\x94\xef\
|
||||
\x99\x99\xf2\xaa\xc6\xa9\x5a\xa8\xc7\x01\x79\xe8\xee\xbb\x17\xf7\
|
||||
\xe5\x23\xba\x2f\xfd\xd0\xd7\x84\xe2\xce\x30\x4b\x0e\x9a\x66\xc9\
|
||||
\xc1\xcf\x01\x21\x20\x02\x1c\x00\xa6\x06\xc5\xb5\x03\xa9\x74\x84\
|
||||
\xc2\x87\xcf\x0f\x6c\x54\x19\x25\x6d\xfb\x80\x99\xc0\xd1\x6c\x2f\
|
||||
\x3e\xd8\x9a\xb2\x39\x1b\x2a\x79\xb9\x6e\x76\x66\xca\xab\xde\x3d\
|
||||
\x14\x84\xd9\x05\x7a\x82\xaa\x7e\x18\x84\x07\xec\xcd\x07\xea\xa3\
|
||||
\x60\x9a\xfb\x41\x90\x65\x22\xa7\xd2\xbb\x01\x6f\xd4\x20\x4b\x1d\
|
||||
\x60\x4d\xa0\x37\xb8\xf6\x56\x4e\x06\x1b\xc1\xe7\x78\xc0\x21\x2d\
|
||||
\xd0\xcd\xb5\x2b\x81\x0f\x83\x35\xf7\x7f\x3a\x6f\xaf\xaf\xaf\xcf\
|
||||
\xfb\xff\x67\xf2\xa9\x90\x7f\x03\xbb\x9c\x9c\x9c\x32\xd8\x63\xca\
|
||||
\x00\x00\x00\x00\x49\x45\x4e\x44\xae\x42\x60\x82\
|
||||
"
|
||||
|
||||
qt_resource_name = b"\
|
||||
@@ -2444,21 +2444,21 @@ qt_resource_name = b"\
|
||||
\x07\x03\x7d\xc3\
|
||||
\x00\x69\
|
||||
\x00\x6d\x00\x61\x00\x67\x00\x65\x00\x73\
|
||||
\x00\x0e\
|
||||
\x01\x8e\xbf\xec\
|
||||
\x00\x6c\
|
||||
\x00\x6f\x00\x67\x00\x6f\x00\x2d\x00\x75\x00\x64\x00\x73\x00\x2d\x00\x73\x00\x6d\x00\x61\x00\x6c\x00\x6c\
|
||||
\x00\x0c\
|
||||
\x05\xe1\xfc\x77\
|
||||
\x00\x6c\
|
||||
\x00\x6f\x00\x67\x00\x6f\x00\x2d\x00\x75\x00\x64\x00\x73\x00\x2d\x00\x62\x00\x69\x00\x67\
|
||||
\x00\x0e\
|
||||
\x01\x8e\xbf\xec\
|
||||
\x00\x6c\
|
||||
\x00\x6f\x00\x67\x00\x6f\x00\x2d\x00\x75\x00\x64\x00\x73\x00\x2d\x00\x73\x00\x6d\x00\x61\x00\x6c\x00\x6c\
|
||||
"
|
||||
|
||||
qt_resource_struct_v1 = b"\
|
||||
\x00\x00\x00\x00\x00\x02\x00\x00\x00\x01\x00\x00\x00\x01\
|
||||
\x00\x00\x00\x00\x00\x02\x00\x00\x00\x02\x00\x00\x00\x02\
|
||||
\x00\x00\x00\x30\x00\x00\x00\x00\x00\x01\x00\x00\x8e\x90\
|
||||
\x00\x00\x00\x12\x00\x00\x00\x00\x00\x01\x00\x00\x00\x00\
|
||||
\x00\x00\x00\x34\x00\x00\x00\x00\x00\x01\x00\x00\x08\xf1\
|
||||
"
|
||||
|
||||
qt_resource_struct_v2 = b"\
|
||||
@@ -2466,9 +2466,9 @@ qt_resource_struct_v2 = b"\
|
||||
\x00\x00\x00\x00\x00\x00\x00\x00\
|
||||
\x00\x00\x00\x00\x00\x02\x00\x00\x00\x02\x00\x00\x00\x02\
|
||||
\x00\x00\x00\x00\x00\x00\x00\x00\
|
||||
\x00\x00\x00\x12\x00\x00\x00\x00\x00\x01\x00\x00\x00\x00\
|
||||
\x00\x00\x00\x30\x00\x00\x00\x00\x00\x01\x00\x00\x8e\x90\
|
||||
\x00\x00\x01\x70\xc4\x82\x24\xd0\
|
||||
\x00\x00\x00\x34\x00\x00\x00\x00\x00\x01\x00\x00\x08\xf1\
|
||||
\x00\x00\x00\x12\x00\x00\x00\x00\x00\x01\x00\x00\x00\x00\
|
||||
\x00\x00\x01\x70\xc4\x82\x24\xd0\
|
||||
"
|
||||
|
||||
|
@@ -2,9 +2,10 @@
|
||||
|
||||
# Form implementation generated from reading ui file 'UDSWindow.ui'
|
||||
#
|
||||
# Created by: PyQt5 UI code generator 5.13.2
|
||||
# Created by: PyQt5 UI code generator 5.15.2
|
||||
#
|
||||
# WARNING! All changes made in this file will be lost!
|
||||
# WARNING: Any manual changes made to this file will be lost when pyuic5 is
|
||||
# run again. Do not edit this file unless you know what you are doing.
|
||||
|
||||
|
||||
from PyQt5 import QtCore, QtGui, QtWidgets
|
||||
|
@@ -31,7 +31,7 @@
|
||||
'''
|
||||
from __future__ import unicode_literals
|
||||
|
||||
VERSION = '3.5.0'
|
||||
VERSION = '3.5.1'
|
||||
|
||||
__title__ = 'udclient'
|
||||
__version__ = VERSION
|
||||
|
@@ -25,7 +25,11 @@ class CheckfingerPrints(paramiko.MissingHostKeyPolicy):
|
||||
if self.fingerPrints:
|
||||
remotefingerPrints = hexlify(key.get_fingerprint()).decode().lower()
|
||||
if remotefingerPrints not in self.fingerPrints.split(','):
|
||||
logger.error("Server {!r} has invalid fingerPrints. ({} vs {})".format(hostname, remotefingerPrints, self.fingerPrints))
|
||||
logger.error(
|
||||
"Server {!r} has invalid fingerPrints. ({} vs {})".format(
|
||||
hostname, remotefingerPrints, self.fingerPrints
|
||||
)
|
||||
)
|
||||
raise paramiko.SSHException(
|
||||
"Server {!r} has invalid fingerPrints".format(hostname)
|
||||
)
|
||||
@@ -47,21 +51,39 @@ class Handler(socketserver.BaseRequestHandler):
|
||||
self.thread.currentConnections += 1
|
||||
|
||||
try:
|
||||
chan = self.ssh_transport.open_channel('direct-tcpip',
|
||||
(self.chain_host, self.chain_port),
|
||||
self.request.getpeername())
|
||||
chan = self.ssh_transport.open_channel(
|
||||
'direct-tcpip',
|
||||
(self.chain_host, self.chain_port),
|
||||
self.request.getpeername(),
|
||||
)
|
||||
except Exception as e:
|
||||
logger.exception('Incoming request to %s:%d failed: %s', self.chain_host, self.chain_port, repr(e))
|
||||
logger.exception(
|
||||
'Incoming request to %s:%d failed: %s',
|
||||
self.chain_host,
|
||||
self.chain_port,
|
||||
repr(e),
|
||||
)
|
||||
return
|
||||
if chan is None:
|
||||
logger.error('Incoming request to %s:%d was rejected by the SSH server.', self.chain_host, self.chain_port)
|
||||
logger.error(
|
||||
'Incoming request to %s:%d was rejected by the SSH server.',
|
||||
self.chain_host,
|
||||
self.chain_port,
|
||||
)
|
||||
return
|
||||
|
||||
logger.debug('Connected! Tunnel open %r -> %r -> %r', self.request.getpeername(), chan.getpeername(), (self.chain_host, self.chain_port))
|
||||
logger.debug(
|
||||
'Connected! Tunnel open %r -> %r -> %r',
|
||||
self.request.getpeername(),
|
||||
chan.getpeername(),
|
||||
(self.chain_host, self.chain_port),
|
||||
)
|
||||
# self.ssh_transport.set_keepalive(10) # Keep alive every 10 seconds...
|
||||
try:
|
||||
while self.event.is_set() is False:
|
||||
r, _w, _x = select.select([self.request, chan], [], [], 1) # pylint: disable=unused-variable
|
||||
r, _w, _x = select.select(
|
||||
[self.request, chan], [], [], 1
|
||||
) # pylint: disable=unused-variable
|
||||
|
||||
if self.request in r:
|
||||
data = self.request.recv(1024)
|
||||
@@ -80,7 +102,10 @@ class Handler(socketserver.BaseRequestHandler):
|
||||
peername = self.request.getpeername()
|
||||
chan.close()
|
||||
self.request.close()
|
||||
logger.debug('Tunnel closed from %r', peername,)
|
||||
logger.debug(
|
||||
'Tunnel closed from %r',
|
||||
peername,
|
||||
)
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
@@ -95,7 +120,18 @@ class ForwardThread(threading.Thread):
|
||||
client: typing.Optional[paramiko.SSHClient]
|
||||
fs: typing.Optional[ForwardServer]
|
||||
|
||||
def __init__(self, server, port, username, password, localPort, redirectHost, redirectPort, waitTime, fingerPrints):
|
||||
def __init__(
|
||||
self,
|
||||
server,
|
||||
port,
|
||||
username,
|
||||
password,
|
||||
localPort,
|
||||
redirectHost,
|
||||
redirectPort,
|
||||
waitTime,
|
||||
fingerPrints,
|
||||
):
|
||||
threading.Thread.__init__(self)
|
||||
self.client = None
|
||||
self.fs = None
|
||||
@@ -110,7 +146,7 @@ class ForwardThread(threading.Thread):
|
||||
self.redirectPort = redirectPort
|
||||
|
||||
self.waitTime = waitTime
|
||||
|
||||
|
||||
self.fingerPrints = fingerPrints
|
||||
|
||||
self.stopEvent = threading.Event()
|
||||
@@ -124,7 +160,17 @@ class ForwardThread(threading.Thread):
|
||||
if localPort is None:
|
||||
localPort = random.randrange(33000, 53000)
|
||||
|
||||
ft = ForwardThread(self.server, self.port, self.username, self.password, localPort, redirectHost, redirectPort, self.waitTime, self.fingerPrints)
|
||||
ft = ForwardThread(
|
||||
self.server,
|
||||
self.port,
|
||||
self.username,
|
||||
self.password,
|
||||
localPort,
|
||||
redirectHost,
|
||||
redirectPort,
|
||||
self.waitTime,
|
||||
self.fingerPrints,
|
||||
)
|
||||
ft.client = self.client
|
||||
self.client.useCount += 1 # type: ignore
|
||||
ft.start()
|
||||
@@ -134,7 +180,6 @@ class ForwardThread(threading.Thread):
|
||||
|
||||
return (ft, localPort)
|
||||
|
||||
|
||||
def _timerFnc(self):
|
||||
self.timer = None
|
||||
logger.debug('Timer fnc: %s', self.currentConnections)
|
||||
@@ -148,12 +193,21 @@ class ForwardThread(threading.Thread):
|
||||
self.client = paramiko.SSHClient()
|
||||
self.client.useCount = 1 # type: ignore
|
||||
self.client.load_system_host_keys()
|
||||
self.client.set_missing_host_key_policy(CheckfingerPrints(self.fingerPrints))
|
||||
self.client.set_missing_host_key_policy(
|
||||
CheckfingerPrints(self.fingerPrints)
|
||||
)
|
||||
|
||||
logger.debug('Connecting to ssh host %s:%d ...', self.server, self.port)
|
||||
|
||||
# To disable ssh-ageng asking for passwords: allow_agent=False
|
||||
self.client.connect(self.server, self.port, username=self.username, password=self.password, timeout=5, allow_agent=False)
|
||||
self.client.connect(
|
||||
self.server,
|
||||
self.port,
|
||||
username=self.username,
|
||||
password=self.password,
|
||||
timeout=5,
|
||||
allow_agent=False,
|
||||
)
|
||||
except Exception:
|
||||
logger.exception('Exception connecting: ')
|
||||
self.status = 2 # Error
|
||||
@@ -194,7 +248,17 @@ class ForwardThread(threading.Thread):
|
||||
logger.exception('Exception stopping')
|
||||
|
||||
|
||||
def forward(server, port, username, password, redirectHost, redirectPort, localPort=None, waitTime=10, fingerPrints=None):
|
||||
def forward(
|
||||
server,
|
||||
port,
|
||||
username,
|
||||
password,
|
||||
redirectHost,
|
||||
redirectPort,
|
||||
localPort=None,
|
||||
waitTime=10,
|
||||
fingerPrints=None,
|
||||
):
|
||||
'''
|
||||
Instantiates an ssh connection to server:port
|
||||
Returns the Thread created and the local redirected port as a list: (thread, port)
|
||||
@@ -204,10 +268,28 @@ def forward(server, port, username, password, redirectHost, redirectPort, localP
|
||||
if localPort is None:
|
||||
localPort = random.randrange(40000, 50000)
|
||||
|
||||
logger.debug('Connecting to %s:%s using %s/%s redirecting to %s:%s, listening on 127.0.0.1:%s',
|
||||
server, port, username, password, redirectHost, redirectPort, localPort)
|
||||
logger.debug(
|
||||
'Connecting to %s:%s using %s/%s redirecting to %s:%s, listening on 127.0.0.1:%s',
|
||||
server,
|
||||
port,
|
||||
username,
|
||||
password,
|
||||
redirectHost,
|
||||
redirectPort,
|
||||
localPort,
|
||||
)
|
||||
|
||||
ft = ForwardThread(server, port, username, password, localPort, redirectHost, redirectPort, waitTime, fingerPrints)
|
||||
ft = ForwardThread(
|
||||
server,
|
||||
port,
|
||||
username,
|
||||
password,
|
||||
localPort,
|
||||
redirectHost,
|
||||
redirectPort,
|
||||
waitTime,
|
||||
fingerPrints,
|
||||
)
|
||||
|
||||
ft.start()
|
||||
|
||||
|
@@ -29,8 +29,6 @@
|
||||
'''
|
||||
@author: Adolfo Gómez, dkmaster at dkmon dot com
|
||||
'''
|
||||
from __future__ import unicode_literals
|
||||
|
||||
import logging
|
||||
import os
|
||||
import os.path
|
||||
@@ -57,7 +55,7 @@ try:
|
||||
filename=logFile,
|
||||
filemode='a',
|
||||
format='%(levelname)s %(asctime)s %(message)s',
|
||||
level=LOGLEVEL
|
||||
level=LOGLEVEL,
|
||||
)
|
||||
except Exception:
|
||||
logging.basicConfig(format='%(levelname)s %(asctime)s %(message)s', level=LOGLEVEL)
|
||||
|
@@ -30,14 +30,13 @@
|
||||
'''
|
||||
@author: Adolfo Gómez, dkmaster at dkmon dot com
|
||||
'''
|
||||
from __future__ import unicode_literals
|
||||
|
||||
import sys
|
||||
|
||||
LINUX = 'Linux'
|
||||
WINDOWS = 'Windows'
|
||||
MAC_OS_X = 'Mac os x'
|
||||
|
||||
|
||||
def getOs():
|
||||
if sys.platform.startswith('linux'):
|
||||
return LINUX
|
||||
|
@@ -29,8 +29,6 @@
|
||||
'''
|
||||
@author: Adolfo Gómez, dkmaster at dkmon dot com
|
||||
'''
|
||||
# pylint: disable=c-extension-no-member,no-name-in-module
|
||||
|
||||
import json
|
||||
import bz2
|
||||
import base64
|
||||
@@ -42,7 +40,6 @@ import ssl
|
||||
import socket
|
||||
import typing
|
||||
|
||||
import certifi
|
||||
from cryptography import x509
|
||||
from cryptography.hazmat.backends import default_backend
|
||||
|
||||
@@ -63,9 +60,11 @@ CertCallbackType = typing.Callable[[str, str], bool]
|
||||
class UDSException(Exception):
|
||||
pass
|
||||
|
||||
|
||||
class RetryException(UDSException):
|
||||
pass
|
||||
|
||||
|
||||
class InvalidVersion(UDSException):
|
||||
downloadUrl: str
|
||||
|
||||
@@ -73,9 +72,10 @@ class InvalidVersion(UDSException):
|
||||
super().__init__(downloadUrl)
|
||||
self.downloadUrl = downloadUrl
|
||||
|
||||
|
||||
class RestApi:
|
||||
|
||||
_restApiUrl: str # base Rest API URL
|
||||
_restApiUrl: str # base Rest API URL
|
||||
_callbackInvalidCert: typing.Optional[CertCallbackType]
|
||||
_serverVersion: str
|
||||
|
||||
@@ -90,14 +90,18 @@ class RestApi:
|
||||
self._callbackInvalidCert = callbackInvalidCert
|
||||
self._serverVersion = ''
|
||||
|
||||
def get(self, url: str, params: typing.Optional[typing.Mapping[str, str]] = None) -> typing.Any:
|
||||
def get(
|
||||
self, url: str, params: typing.Optional[typing.Mapping[str, str]] = None
|
||||
) -> typing.Any:
|
||||
if params:
|
||||
url += '?' + '&'.join(
|
||||
'{}={}'.format(k, urllib.parse.quote(str(v).encode('utf8')))
|
||||
for k, v in params.items()
|
||||
)
|
||||
|
||||
return json.loads(RestApi.getUrl(self._restApiUrl + url, self._callbackInvalidCert))
|
||||
return json.loads(
|
||||
RestApi.getUrl(self._restApiUrl + url, self._callbackInvalidCert)
|
||||
)
|
||||
|
||||
def processError(self, data: typing.Any) -> None:
|
||||
if 'error' in data:
|
||||
@@ -106,7 +110,6 @@ class RestApi:
|
||||
|
||||
raise UDSException(data['error'])
|
||||
|
||||
|
||||
def getVersion(self) -> str:
|
||||
'''Gets and stores the serverVersion.
|
||||
Also checks that the version is valid for us. If not,
|
||||
@@ -122,12 +125,16 @@ class RestApi:
|
||||
try:
|
||||
if self._serverVersion > VERSION:
|
||||
raise InvalidVersion(downloadUrl)
|
||||
|
||||
|
||||
return self._serverVersion
|
||||
except InvalidVersion:
|
||||
raise
|
||||
except Exception as e:
|
||||
raise UDSException(e)
|
||||
|
||||
def getScriptAndParams(self, ticket: str, scrambler: str) -> typing.Tuple[str, typing.Any]:
|
||||
def getScriptAndParams(
|
||||
self, ticket: str, scrambler: str
|
||||
) -> typing.Tuple[str, typing.Any]:
|
||||
'''Gets the transport script, validates it if necesary
|
||||
and returns it'''
|
||||
try:
|
||||
@@ -145,10 +152,7 @@ class RestApi:
|
||||
params = None
|
||||
|
||||
if self._serverVersion <= OLD_METHOD_VERSION:
|
||||
script = bz2.decompress(base64.b64decode(data['result']))
|
||||
# This fixes uds 2.2 "write" string on binary streams on some transport
|
||||
script = script.replace(b'stdin.write("', b'stdin.write(b"')
|
||||
script = script.replace(b'version)', b'version.decode("utf-8"))')
|
||||
raise Exception('Server version is too old. Please, update it.')
|
||||
else:
|
||||
res = data['result']
|
||||
# We have three elements on result:
|
||||
@@ -173,7 +177,6 @@ class RestApi:
|
||||
|
||||
# exec(script.decode("utf-8"), globals(), {'parent': self, 'sp': params})
|
||||
|
||||
|
||||
@staticmethod
|
||||
def _open(
|
||||
url: str, certErrorCallback: typing.Optional[CertCallbackType] = None
|
||||
@@ -181,15 +184,23 @@ class RestApi:
|
||||
ctx = ssl.create_default_context()
|
||||
ctx.check_hostname = False
|
||||
ctx.verify_mode = ssl.CERT_NONE
|
||||
ctx.load_verify_locations(certifi.where())
|
||||
# If we have the certificates file, we use it
|
||||
if tools.getCaCertsFile() is not None:
|
||||
ctx.load_verify_locations(tools.getCaCertsFile())
|
||||
hostname = urllib.parse.urlparse(url)[1]
|
||||
serial = ''
|
||||
|
||||
port = ''
|
||||
if ':' in hostname:
|
||||
hostname, port = hostname.split(':')
|
||||
|
||||
if url.startswith('https'):
|
||||
port = port or '443'
|
||||
with ctx.wrap_socket(
|
||||
socket.socket(socket.AF_INET, socket.SOCK_STREAM), server_hostname=hostname
|
||||
socket.socket(socket.AF_INET, socket.SOCK_STREAM),
|
||||
server_hostname=hostname,
|
||||
) as s:
|
||||
s.connect((hostname, 443))
|
||||
s.connect((hostname, int(port)))
|
||||
# Get binary certificate
|
||||
binCert = s.getpeercert(True)
|
||||
if binCert:
|
||||
@@ -200,14 +211,17 @@ class RestApi:
|
||||
serial = hex(cert.serial_number)[2:]
|
||||
|
||||
response = None
|
||||
ctx.check_hostname = True
|
||||
ctx.verify_mode = ssl.CERT_REQUIRED
|
||||
ctx.check_hostname = True
|
||||
|
||||
def urlopen(url: str):
|
||||
# Generate the request with the headers
|
||||
req = urllib.request.Request(url, headers={
|
||||
'User-Agent': os_detector.getOs() + " - UDS Connector " + VERSION
|
||||
})
|
||||
req = urllib.request.Request(
|
||||
url,
|
||||
headers={
|
||||
'User-Agent': os_detector.getOs() + " - UDS Connector " + VERSION
|
||||
},
|
||||
)
|
||||
return urllib.request.urlopen(req, context=ctx)
|
||||
|
||||
try:
|
||||
|
@@ -33,6 +33,8 @@ import tempfile
|
||||
import string
|
||||
import random
|
||||
import os
|
||||
import os.path
|
||||
import sys
|
||||
import socket
|
||||
import stat
|
||||
import sys
|
||||
@@ -40,6 +42,8 @@ import time
|
||||
import base64
|
||||
import typing
|
||||
|
||||
import certifi
|
||||
|
||||
try:
|
||||
import psutil
|
||||
except ImportError:
|
||||
@@ -161,7 +165,9 @@ def unlinkFiles(early: bool = False) -> None:
|
||||
|
||||
def addTaskToWait(task: typing.Any, includeSubprocess: bool = False) -> None:
|
||||
logger.debug(
|
||||
'Added task %s to wait %s', task, 'with subprocesses' if includeSubprocess else ''
|
||||
'Added task %s to wait %s',
|
||||
task,
|
||||
'with subprocesses' if includeSubprocess else '',
|
||||
)
|
||||
_tasksToWait.append((task, includeSubprocess))
|
||||
|
||||
@@ -176,12 +182,22 @@ def waitForTasks() -> None:
|
||||
elif hasattr(task, 'wait'):
|
||||
task.wait()
|
||||
# If wait for spanwed process (look for process with task pid) and we can look for them...
|
||||
logger.debug('Psutil: %s, waitForSubp: %s, hasattr: %s', psutil, waitForSubp, hasattr(task, 'pid'))
|
||||
logger.debug(
|
||||
'Psutil: %s, waitForSubp: %s, hasattr: %s',
|
||||
psutil,
|
||||
waitForSubp,
|
||||
hasattr(task, 'pid'),
|
||||
)
|
||||
if psutil and waitForSubp and hasattr(task, 'pid'):
|
||||
subProcesses = list(filter(
|
||||
lambda x: x.ppid() == task.pid, psutil.process_iter(attrs=('ppid',))
|
||||
))
|
||||
logger.debug('Waiting for subprocesses... %s, %s', task.pid, subProcesses)
|
||||
subProcesses = list(
|
||||
filter(
|
||||
lambda x: x.ppid() == task.pid, # type: ignore
|
||||
psutil.process_iter(attrs=('ppid',)),
|
||||
)
|
||||
)
|
||||
logger.debug(
|
||||
'Waiting for subprocesses... %s, %s', task.pid, subProcesses
|
||||
)
|
||||
for i in subProcesses:
|
||||
logger.debug('Found %s', i)
|
||||
i.wait()
|
||||
@@ -218,11 +234,36 @@ def verifySignature(script: bytes, signature: bytes) -> bool:
|
||||
)
|
||||
|
||||
try:
|
||||
public_key.verify(
|
||||
base64.b64decode(signature), script, padding.PKCS1v15(), hashes.SHA256()
|
||||
public_key.verify( # type: ignore
|
||||
base64.b64decode(signature), script, padding.PKCS1v15(), hashes.SHA256() # type: ignore
|
||||
)
|
||||
except Exception: # InvalidSignature
|
||||
return False
|
||||
|
||||
# If no exception, the script was fine...
|
||||
return True
|
||||
|
||||
|
||||
def getCaCertsFile() -> typing.Optional[str]:
|
||||
# First, try certifi...
|
||||
|
||||
# If environment contains CERTIFICATE_BUNDLE_PATH, use it
|
||||
if 'CERTIFICATE_BUNDLE_PATH' in os.environ:
|
||||
return os.environ['CERTIFICATE_BUNDLE_PATH']
|
||||
|
||||
try:
|
||||
if os.path.exists(certifi.where()):
|
||||
return certifi.where()
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
logger.info('Certifi file does not exists: %s', certifi.where())
|
||||
|
||||
# Check if "standard" paths are valid for linux systems
|
||||
if 'linux' in sys.platform:
|
||||
for path in ('/etc/pki/tls/certs/ca-bundle.crt', '/etc/ssl/certs/ca-certificates.crt', '/etc/ssl/ca-bundle.pem'):
|
||||
if os.path.exists(path):
|
||||
logger.info('Found certifi path: %s', path)
|
||||
return path
|
||||
|
||||
return None
|
||||
|
@@ -39,7 +39,7 @@ import select
|
||||
import typing
|
||||
import logging
|
||||
|
||||
import certifi
|
||||
from . import tools
|
||||
|
||||
HANDSHAKE_V1 = b'\x5AMGB\xA5\x01\x00'
|
||||
BUFFER_SIZE = 1024 * 16 # Max buffer length
|
||||
@@ -114,11 +114,16 @@ class ForwardServer(socketserver.ThreadingTCPServer):
|
||||
|
||||
rsocket.connect(self.remote)
|
||||
|
||||
rsocket.sendall(HANDSHAKE_V1) # No response expected, just the handshake
|
||||
|
||||
context = ssl.create_default_context()
|
||||
|
||||
|
||||
# Do not "recompress" data, use only "base protocol" compression
|
||||
context.options |= ssl.OP_NO_COMPRESSION
|
||||
context.load_verify_locations(certifi.where()) # Load certifi certificates
|
||||
if tools.getCaCertsFile() is not None:
|
||||
context.load_verify_locations(
|
||||
tools.getCaCertsFile()
|
||||
) # Load certifi certificates
|
||||
|
||||
# If ignore remote certificate
|
||||
if self.check_certificate is False:
|
||||
@@ -136,7 +141,7 @@ class ForwardServer(socketserver.ThreadingTCPServer):
|
||||
|
||||
try:
|
||||
with self.connect() as ssl_socket:
|
||||
ssl_socket.sendall(HANDSHAKE_V1 + b'TEST')
|
||||
ssl_socket.sendall(b'TEST')
|
||||
resp = ssl_socket.recv(2)
|
||||
if resp != b'OK':
|
||||
raise Exception({'Invalid tunnelresponse: {resp}'})
|
||||
@@ -184,7 +189,7 @@ class Handler(socketserver.BaseRequestHandler):
|
||||
logger.debug('Ticket %s', self.server.ticket)
|
||||
with self.server.connect() as ssl_socket:
|
||||
# Send handhshake + command + ticket
|
||||
ssl_socket.sendall(HANDSHAKE_V1 + b'OPEN' + self.server.ticket.encode())
|
||||
ssl_socket.sendall(b'OPEN' + self.server.ticket.encode())
|
||||
# Check response is OK
|
||||
data = ssl_socket.recv(2)
|
||||
if data != b'OK':
|
||||
|
159
server/samples/REST4.py
Normal file
159
server/samples/REST4.py
Normal file
@@ -0,0 +1,159 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
#
|
||||
# Copyright (c) 2021 Virtual Cable S.L.U.
|
||||
# All rights reserved.
|
||||
#
|
||||
# Redistribution and use in source and binary forms, with or without modification,
|
||||
# are permitted provided that the following conditions are met:
|
||||
#
|
||||
# * Redistributions of source code must retain the above copyright notice,
|
||||
# this list of conditions and the following disclaimer.
|
||||
# * Redistributions in binary form must reproduce the above copyright notice,
|
||||
# this list of conditions and the following disclaimer in the documentation
|
||||
# and/or other materials provided with the distribution.
|
||||
# * Neither the name of Virtual Cable S.L. nor the names of its contributors
|
||||
# may be used to endorse or promote products derived from this software
|
||||
# without specific prior written permission.
|
||||
#
|
||||
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
||||
# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
'''
|
||||
@author: Adolfo Gómez, dkmaster at dkmon dot com
|
||||
'''
|
||||
|
||||
import json
|
||||
import sys
|
||||
import typing
|
||||
|
||||
import requests
|
||||
|
||||
rest_url = 'http://172.27.0.1:8000/uds/rest/'
|
||||
|
||||
session = requests.Session()
|
||||
session.headers.update({'Content-Type': 'application/json'})
|
||||
|
||||
|
||||
class RESTException(Exception):
|
||||
pass
|
||||
|
||||
|
||||
class AuthException(RESTException):
|
||||
pass
|
||||
|
||||
|
||||
class LogoutException(RESTException):
|
||||
pass
|
||||
|
||||
|
||||
# Hace login con el root, puede usarse cualquier autenticador y cualquier usuario, pero en la 1.5 solo está implementado poder hacer
|
||||
# este tipo de login con el usuario "root"
|
||||
def login():
|
||||
# parameters = '{ "auth": "admin", "username": "root", "password": "temporal" }'
|
||||
# parameters = '{ "auth": "interna", "username": "admin", "password": "temporal" }'
|
||||
parameters = {'auth': 'interna', 'username': 'admin', 'password': 'temporal'}
|
||||
|
||||
response = session.post(rest_url + 'auth/login', json=parameters)
|
||||
|
||||
if not response.ok:
|
||||
raise AuthException('Error logging in')
|
||||
|
||||
# resp contiene las cabeceras, content el contenido de la respuesta (que es json), pero aún está en formato texto
|
||||
res = response.json()
|
||||
print(res)
|
||||
|
||||
if res['result'] != 'ok': # Authentication error
|
||||
raise AuthException('Authentication error')
|
||||
|
||||
session.headers.update({'X-Auth-Token': res['token']})
|
||||
|
||||
|
||||
def logout():
|
||||
response = session.get(rest_url + 'auth/logout')
|
||||
|
||||
if not response.ok:
|
||||
raise LogoutException('Error logging out')
|
||||
|
||||
|
||||
# Sample response from request_pools
|
||||
# [
|
||||
# {
|
||||
# u'initial_srvs': 0,
|
||||
# u'name': u'WinAdolfo',
|
||||
# u'max_srvs': 0,
|
||||
# u'comments': u'',
|
||||
# u'id': 6,
|
||||
# u'state': u'A',
|
||||
# u'user_services_count': 3,
|
||||
# u'cache_l2_srvs': 0,
|
||||
# u'service_id': 9,
|
||||
# u'provider_id': 2,
|
||||
# u'cache_l1_srvs': 0,
|
||||
# u'restrained': False}
|
||||
# ]
|
||||
|
||||
|
||||
def request_pools() -> typing.List[typing.MutableMapping[str, typing.Any]]:
|
||||
response = session.get(rest_url + 'servicespools/overview')
|
||||
if not response.ok:
|
||||
raise RESTException('Error requesting pools')
|
||||
|
||||
return response.json()
|
||||
|
||||
|
||||
def request_ticket(
|
||||
username: str,
|
||||
authSmallName: str,
|
||||
groups: typing.Union[typing.List[str], str],
|
||||
servicePool: str,
|
||||
realName: typing.Optional[str] = None,
|
||||
transport: typing.Optional[str] = None,
|
||||
force: bool = False
|
||||
) -> typing.MutableMapping[str, typing.Any]:
|
||||
data = {
|
||||
'username': username,
|
||||
'authSmallName': authSmallName,
|
||||
'groups': groups,
|
||||
'servicePool': servicePool,
|
||||
'force': 'true' if force else 'false'
|
||||
}
|
||||
if realName:
|
||||
data['realname'] = realName
|
||||
if transport:
|
||||
data['transport'] = transport
|
||||
response = session.put(
|
||||
rest_url + 'tickets/create',
|
||||
json=data
|
||||
)
|
||||
if not response.ok:
|
||||
raise RESTException('Error requesting ticket')
|
||||
|
||||
return response.json()
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
# request_pools() # Not logged in, this will generate an error
|
||||
login() # Will raise an exception if error
|
||||
#pools = request_pools()
|
||||
#for i in pools:
|
||||
# print(i['id'], i['name'])
|
||||
ticket = request_ticket(
|
||||
username='adolfo',
|
||||
authSmallName='172.27.0.1:8000',
|
||||
groups=['adolfo', 'dkmaster'],
|
||||
servicePool='5d045a19-54b5-541b-ba56-447b0622191c',
|
||||
realName='Adolfo Gómez',
|
||||
force=True
|
||||
)
|
||||
print(ticket)
|
||||
|
||||
logout()
|
199
server/src/requirements.txt
Normal file
199
server/src/requirements.txt
Normal file
@@ -0,0 +1,199 @@
|
||||
altgraph==0.17.2
|
||||
appdirs==1.4.4
|
||||
asgiref==3.5.0
|
||||
asttokens==2.0.5
|
||||
Babel==2.9.1
|
||||
backcall==0.2.0
|
||||
backports.entry-points-selectable==1.1.1
|
||||
bcrypt==3.2.0
|
||||
beautifulsoup4==4.10.0
|
||||
bitarray==2.6.0
|
||||
black==22.1.0
|
||||
boto3==1.21.27
|
||||
botocore==1.24.27
|
||||
Brotli==1.0.9
|
||||
CacheControl==0.12.11
|
||||
cachetools==5.0.0
|
||||
cairocffi==1.3.0
|
||||
CairoSVG==2.5.2
|
||||
certifi==2021.10.8
|
||||
cffi==1.15.0
|
||||
chardet==4.0.0
|
||||
charset-normalizer==2.0.12
|
||||
click==8.0.4
|
||||
commonmark==0.9.1
|
||||
contextlib2==21.6.0
|
||||
coverage==6.4.4
|
||||
cryptography==36.0.2
|
||||
cssselect2==0.5.0
|
||||
curio==1.5
|
||||
cycler==0.11.0
|
||||
cyclonedx-python-lib==3.1.0
|
||||
Cython==0.29.28
|
||||
decorator==5.1.1
|
||||
defusedxml==0.7.1
|
||||
Delorean==1.0.0
|
||||
distlib==0.3.4
|
||||
Django==3.2.16
|
||||
django-extensions==3.1.5
|
||||
dnspython==2.2.1
|
||||
docker==5.0.3
|
||||
emrichen==0.2.3
|
||||
et-xmlfile==1.1.0
|
||||
executing==0.8.3
|
||||
filelock==3.6.0
|
||||
fonttools==4.31.2
|
||||
gdown==4.4.0
|
||||
gitdb==4.0.9
|
||||
GitPython==3.1.27
|
||||
google-api-core==2.7.1
|
||||
google-auth==2.6.2
|
||||
google-cloud-core==2.2.3
|
||||
google-cloud-translate==3.7.2
|
||||
googleapis-common-protos==1.56.0
|
||||
grpcio==1.44.0
|
||||
grpcio-status==1.44.0
|
||||
gunicorn==20.1.0
|
||||
h11==0.13.0
|
||||
html5lib==1.1
|
||||
huggingface-hub==0.4.0
|
||||
humanize==4.0.0
|
||||
idna==3.3
|
||||
ipython==8.2.0
|
||||
ipython-genutils==0.2.0
|
||||
isodate==0.6.1
|
||||
jedi==0.18.1
|
||||
jmespath==1.0.0
|
||||
joblib==1.2.0
|
||||
jsonpath-rw==1.4.0
|
||||
kiwisolver==1.4.1
|
||||
ldap3==2.9.1
|
||||
libvirt-python==8.1.0
|
||||
lockfile==0.12.2
|
||||
lxml==4.6.5
|
||||
matplotlib==3.5.1
|
||||
matplotlib-inline==0.1.3
|
||||
mpmath==1.2.1
|
||||
msgpack==1.0.4
|
||||
mypy==0.942
|
||||
mypy-extensions==0.4.3
|
||||
mysqlclient==2.1.0
|
||||
netaddr==0.8.0
|
||||
ntlm-auth==1.5.0
|
||||
numpy==1.22.3
|
||||
openpyxl==3.0.9
|
||||
ovirt-engine-sdk-python==4.5.0
|
||||
packageurl-python==0.10.4
|
||||
packaging==21.3
|
||||
paramiko==2.10.3
|
||||
parso==0.8.3
|
||||
pathspec==0.9.0
|
||||
pexpect==4.8.0
|
||||
pickleshare==0.7.5
|
||||
Pillow==9.0.1
|
||||
pip-api==0.0.30
|
||||
pip-requirements-parser==31.2.0
|
||||
pip_audit==2.4.5
|
||||
pkg_resources==0.0.0
|
||||
platformdirs==2.5.1
|
||||
ply==3.11
|
||||
prompt-toolkit==3.0.28
|
||||
proto-plus==1.20.3
|
||||
protobuf==3.19.4
|
||||
psutil==5.9.0
|
||||
ptyprocess==0.7.0
|
||||
pure-eval==0.2.2
|
||||
pyaml==21.10.1
|
||||
pyasn1==0.4.8
|
||||
pyasn1-modules==0.2.8
|
||||
pycparser==2.21
|
||||
pycryptodome==3.14.1
|
||||
pycurl==7.45.1
|
||||
pydyf==0.1.2
|
||||
Pygments==2.11.2
|
||||
pyinstaller==4.10
|
||||
pyinstaller-hooks-contrib==2022.3
|
||||
PyJWT==2.4.0
|
||||
PyNaCl==1.5.0
|
||||
pyOpenSSL==22.0.0
|
||||
pyparsing==3.0.7
|
||||
pyphen==0.12.0
|
||||
PyQt5==5.15.6
|
||||
PyQt5-Qt5==5.15.2
|
||||
PyQt5-sip==12.9.1
|
||||
pyrad==2.4
|
||||
PySide2==5.15.2.1
|
||||
PySocks==1.7.1
|
||||
pyspnego==0.5.1
|
||||
python-dateutil==2.8.2
|
||||
python-geoip-geolite2-coex==2018.1113
|
||||
python-geoip-python3==1.3
|
||||
python-ldap==3.4.0
|
||||
python-memcached==1.59
|
||||
python3-saml==1.14.0
|
||||
pytz==2022.1
|
||||
pytz-deprecation-shim==0.1.0.post0
|
||||
pyvmomi==7.0.3
|
||||
pywinrm==0.4.2
|
||||
PyYAML==6.0
|
||||
questionary==1.10.0
|
||||
regex==2022.3.15
|
||||
requests==2.27.1
|
||||
requests-credssp==2.0.0
|
||||
requests-ntlm==1.1.0
|
||||
resolvelib==0.8.1
|
||||
rich==12.6.0
|
||||
roam==0.3.1
|
||||
rsa==4.8
|
||||
ruamel.yaml==0.17.21
|
||||
ruamel.yaml.clib==0.2.6
|
||||
s3transfer==0.5.2
|
||||
sacremoses==0.0.49
|
||||
schema==0.7.5
|
||||
Send2Trash==1.8.0
|
||||
sentencepiece==0.1.96
|
||||
setproctitle==1.2.2
|
||||
shiboken2==5.15.2.1
|
||||
six==1.16.0
|
||||
smmap==5.0.0
|
||||
sortedcontainers==2.4.0
|
||||
soupsieve==2.3.1
|
||||
sqlparse==0.4.2
|
||||
stack-data==0.2.0
|
||||
sympy==1.10.1
|
||||
tinycss2==1.1.1
|
||||
tokenizers==0.11.6
|
||||
toml==0.10.2
|
||||
tomli==2.0.1
|
||||
tqdm==4.63.1
|
||||
traitlets==5.1.1
|
||||
transformers==4.17.0
|
||||
translate-toolkit==3.6.0
|
||||
typed-ast==1.5.2
|
||||
types-cryptography==3.3.18
|
||||
types-enum34==1.1.8
|
||||
types-ipaddress==1.0.8
|
||||
types-paramiko==2.8.17
|
||||
types-pycurl==7.44.7
|
||||
types-python-dateutil==2.8.10
|
||||
types-pytz==2021.3.7
|
||||
types-pyvmomi==7.0.5
|
||||
types-PyYAML==6.0.7
|
||||
types-requests==2.27.15
|
||||
types-urllib3==1.26.11
|
||||
typing_extensions==4.1.1
|
||||
tzdata==2022.1
|
||||
tzlocal==4.1
|
||||
urllib3==1.26.9
|
||||
uvicorn==0.17.6
|
||||
virtualenv==20.14.0
|
||||
wcwidth==0.2.5
|
||||
weasyprint==54.2
|
||||
webencodings==0.5.1
|
||||
websocket-client==1.3.1
|
||||
whitenoise==6.0.0
|
||||
xml-marshaller==1.0.2
|
||||
xmlsec==1.3.12
|
||||
xmltodict==0.12.0
|
||||
xxhash==3.0.0
|
||||
zopfli==0.2.1
|
16
server/src/server/asgi.py
Normal file
16
server/src/server/asgi.py
Normal file
@@ -0,0 +1,16 @@
|
||||
"""
|
||||
ASGI config for server project.
|
||||
|
||||
It exposes the ASGI callable as a module-level variable named ``application``.
|
||||
|
||||
For more information on this file, see
|
||||
https://docs.djangoproject.com/en/3.2/howto/deployment/asgi/
|
||||
"""
|
||||
|
||||
import os
|
||||
|
||||
from django.core.asgi import get_asgi_application
|
||||
|
||||
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'server.settings')
|
||||
|
||||
application = get_asgi_application()
|
@@ -8,12 +8,17 @@ import django
|
||||
# calculated paths for django and the site
|
||||
# used as starting points for various other paths
|
||||
DJANGO_ROOT = os.path.dirname(os.path.realpath(django.__file__))
|
||||
BASE_DIR = '/'.join(os.path.dirname(os.path.abspath(__file__)).split('/')[:-1]) # If used 'relpath' instead of abspath, returns path of "enterprise" instead of "openuds"
|
||||
BASE_DIR = '/'.join(
|
||||
os.path.dirname(os.path.abspath(__file__)).split('/')[:-1]
|
||||
) # If used 'relpath' instead of abspath, returns path of "enterprise" instead of "openuds"
|
||||
|
||||
DEBUG = True
|
||||
|
||||
# USE_X_FORWARDED_HOST = True
|
||||
SECURE_PROXY_SSL_HEADER = ('HTTP_X_FORWARDED_PROTO', 'https') # For testing behind a reverse proxy
|
||||
SECURE_PROXY_SSL_HEADER = (
|
||||
'HTTP_X_FORWARDED_PROTO',
|
||||
'https',
|
||||
) # For testing behind a reverse proxy
|
||||
|
||||
DATABASES = {
|
||||
'default': {
|
||||
@@ -29,12 +34,12 @@ DATABASES = {
|
||||
'PASSWORD': 'PASSWOR', # Not used with sqlite3.
|
||||
'HOST': 'localhost', # Set to empty string for localhost. Not used with sqlite3.
|
||||
'PORT': '3306', # Set to empty string for default. Not used with sqlite3.
|
||||
# 'CONN_MAX_AGE': 600, # Enable DB Pooling, 10 minutes max connection duration
|
||||
# 'CONN_MAX_AGE': 600, # Enable DB Pooling, 10 minutes max connection duration
|
||||
}
|
||||
}
|
||||
ALLOWED_HOSTS = '*'
|
||||
ALLOWED_HOSTS = ['*']
|
||||
|
||||
DEFAULT_AUTO_FIELD='django.db.models.AutoField'
|
||||
DEFAULT_AUTO_FIELD = 'django.db.models.AutoField'
|
||||
# Local time zone for this installation. Choices can be found here:
|
||||
# http://en.wikipedia.org/wiki/List_of_tz_zones_by_name
|
||||
# although not all choices may be available on all operating systems.
|
||||
@@ -54,17 +59,17 @@ LANGUAGE_CODE = 'en'
|
||||
ugettext = lambda s: s
|
||||
|
||||
LANGUAGES = (
|
||||
('es', ugettext('Spanish')),
|
||||
('en', ugettext('English')),
|
||||
('fr', ugettext('French')),
|
||||
('de', ugettext('German')),
|
||||
('pt', ugettext('Portuguese')),
|
||||
('it', ugettext('Italian')),
|
||||
('ar', ugettext('Arabic')),
|
||||
('eu', ugettext('Basque')),
|
||||
('ar', ugettext('Arabian')),
|
||||
('ca', ugettext('Catalan')),
|
||||
('zh-hans', ugettext('Chinese')),
|
||||
('es', ugettext('Spanish')),
|
||||
('en', ugettext('English')),
|
||||
('fr', ugettext('French')),
|
||||
('de', ugettext('German')),
|
||||
('pt', ugettext('Portuguese')),
|
||||
('it', ugettext('Italian')),
|
||||
('ar', ugettext('Arabic')),
|
||||
('eu', ugettext('Basque')),
|
||||
('ar', ugettext('Arabian')),
|
||||
('ca', ugettext('Catalan')),
|
||||
('zh-hans', ugettext('Chinese')),
|
||||
)
|
||||
|
||||
LANGUAGE_COOKIE_NAME = 'uds_lang'
|
||||
@@ -123,15 +128,15 @@ CACHES = {
|
||||
'OPTIONS': {
|
||||
'MAX_ENTRIES': 5000,
|
||||
'CULL_FREQUENCY': 3, # 0 = Entire cache will be erased once MAX_ENTRIES is reached, this is faster on DB. if other value, will remove 1/this number items fromm cache
|
||||
}
|
||||
},
|
||||
},
|
||||
# 'memory': {
|
||||
# 'BACKEND': 'django.core.cache.backends.locmem.LocMemCache',
|
||||
# }
|
||||
# 'memory': {
|
||||
# 'BACKEND': 'django.core.cache.backends.locmem.LocMemCache',
|
||||
# }
|
||||
'memory': {
|
||||
'BACKEND': 'django.core.cache.backends.memcached.MemcachedCache',
|
||||
'LOCATION': '127.0.0.1:11211',
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
# Related to file uploading
|
||||
@@ -175,6 +180,7 @@ MIDDLEWARE = [
|
||||
'django.contrib.messages.middleware.MessageMiddleware',
|
||||
'django.middleware.clickjacking.XFrameOptionsMiddleware',
|
||||
'django.middleware.security.SecurityMiddleware',
|
||||
'uds.core.util.middleware.security.UDSSecurityMiddleware',
|
||||
'uds.core.util.middleware.request.GlobalRequestMiddleware',
|
||||
'uds.core.util.middleware.xua.XUACompatibleMiddleware',
|
||||
'uds.core.util.middleware.redirect.RedirectMiddleware',
|
||||
@@ -229,25 +235,16 @@ LOGGING = {
|
||||
'simple': {
|
||||
'format': '%(levelname)s %(asctime)s %(module)s %(funcName)s %(lineno)d %(message)s'
|
||||
},
|
||||
'database': {
|
||||
'format': '%(levelname)s %(asctime)s Database %(message)s'
|
||||
},
|
||||
'auth': {
|
||||
'format': '%(asctime)s %(message)s'
|
||||
},
|
||||
'use': {
|
||||
'format': '%(asctime)s %(message)s'
|
||||
},
|
||||
'trace': {
|
||||
'format': '%(levelname)s %(asctime)s %(message)s'
|
||||
}
|
||||
'database': {'format': '%(levelname)s %(asctime)s Database %(message)s'},
|
||||
'auth': {'format': '%(asctime)s %(message)s'},
|
||||
'use': {'format': '%(asctime)s %(message)s'},
|
||||
'trace': {'format': '%(levelname)s %(asctime)s %(message)s'},
|
||||
},
|
||||
'handlers': {
|
||||
'null': {
|
||||
'level': 'DEBUG',
|
||||
'class': 'logging.NullHandler',
|
||||
},
|
||||
|
||||
'file': {
|
||||
'level': 'DEBUG',
|
||||
'class': 'logging.handlers.RotatingFileHandler',
|
||||
@@ -256,9 +253,8 @@ LOGGING = {
|
||||
'mode': 'a',
|
||||
'maxBytes': ROTATINGSIZE,
|
||||
'backupCount': 3,
|
||||
'encoding': 'utf-8'
|
||||
'encoding': 'utf-8',
|
||||
},
|
||||
|
||||
'database': {
|
||||
'level': 'DEBUG',
|
||||
'class': 'logging.handlers.RotatingFileHandler',
|
||||
@@ -267,9 +263,8 @@ LOGGING = {
|
||||
'mode': 'a',
|
||||
'maxBytes': ROTATINGSIZE,
|
||||
'backupCount': 3,
|
||||
'encoding': 'utf-8'
|
||||
'encoding': 'utf-8',
|
||||
},
|
||||
|
||||
'servicesFile': {
|
||||
'level': 'DEBUG',
|
||||
'class': 'logging.handlers.RotatingFileHandler',
|
||||
@@ -278,9 +273,8 @@ LOGGING = {
|
||||
'mode': 'a',
|
||||
'maxBytes': ROTATINGSIZE,
|
||||
'backupCount': 3,
|
||||
'encoding': 'utf-8'
|
||||
'encoding': 'utf-8',
|
||||
},
|
||||
|
||||
'workersFile': {
|
||||
'level': 'DEBUG',
|
||||
'class': 'logging.handlers.RotatingFileHandler',
|
||||
@@ -289,9 +283,8 @@ LOGGING = {
|
||||
'mode': 'a',
|
||||
'maxBytes': ROTATINGSIZE,
|
||||
'backupCount': 3,
|
||||
'encoding': 'utf-8'
|
||||
'encoding': 'utf-8',
|
||||
},
|
||||
|
||||
'authFile': {
|
||||
'level': 'DEBUG',
|
||||
'class': 'logging.handlers.RotatingFileHandler',
|
||||
@@ -300,9 +293,8 @@ LOGGING = {
|
||||
'mode': 'a',
|
||||
'maxBytes': ROTATINGSIZE,
|
||||
'backupCount': 3,
|
||||
'encoding': 'utf-8'
|
||||
'encoding': 'utf-8',
|
||||
},
|
||||
|
||||
'useFile': {
|
||||
'level': 'DEBUG',
|
||||
'class': 'logging.handlers.RotatingFileHandler',
|
||||
@@ -311,9 +303,8 @@ LOGGING = {
|
||||
'mode': 'a',
|
||||
'maxBytes': ROTATINGSIZE,
|
||||
'backupCount': 3,
|
||||
'encoding': 'utf-8'
|
||||
'encoding': 'utf-8',
|
||||
},
|
||||
|
||||
'traceFile': {
|
||||
'level': 'DEBUG',
|
||||
'class': 'logging.handlers.RotatingFileHandler',
|
||||
@@ -322,19 +313,18 @@ LOGGING = {
|
||||
'mode': 'a',
|
||||
'maxBytes': ROTATINGSIZE,
|
||||
'backupCount': 3,
|
||||
'encoding': 'utf-8'
|
||||
'encoding': 'utf-8',
|
||||
},
|
||||
|
||||
'console': {
|
||||
'level': 'DEBUG',
|
||||
'class': 'logging.StreamHandler',
|
||||
'formatter': 'simple'
|
||||
'formatter': 'simple',
|
||||
},
|
||||
'mail_admins': {
|
||||
'level': 'ERROR',
|
||||
'class': 'django.utils.log.AdminEmailHandler',
|
||||
'filters': ['require_debug_false']
|
||||
}
|
||||
'filters': ['require_debug_false'],
|
||||
},
|
||||
},
|
||||
'loggers': {
|
||||
'': {
|
||||
@@ -356,12 +346,16 @@ LOGGING = {
|
||||
'level': 'DEBUG',
|
||||
'propagate': False,
|
||||
},
|
||||
|
||||
# Disable fonttools (used by reports) logging (too verbose)
|
||||
'fontTools': {
|
||||
'handlers': ['null'],
|
||||
'propagate': True,
|
||||
'level': 'ERROR',
|
||||
},
|
||||
'uds': {
|
||||
'handlers': ['file'],
|
||||
'level': LOGLEVEL,
|
||||
},
|
||||
|
||||
'uds.core.workers': {
|
||||
'handlers': ['workersFile'],
|
||||
'level': LOGLEVEL,
|
||||
@@ -372,7 +366,6 @@ LOGGING = {
|
||||
'level': LOGLEVEL,
|
||||
'propagate': False,
|
||||
},
|
||||
|
||||
'uds.services': {
|
||||
'handlers': ['servicesFile'],
|
||||
'level': LOGLEVEL,
|
||||
@@ -395,7 +388,6 @@ LOGGING = {
|
||||
'handlers': ['traceFile'],
|
||||
'level': 'INFO',
|
||||
'propagate': False,
|
||||
}
|
||||
|
||||
}
|
||||
},
|
||||
},
|
||||
}
|
||||
|
@@ -1,34 +1,16 @@
|
||||
"""
|
||||
WSGI config for server project.
|
||||
|
||||
This module contains the WSGI application used by Django's development server
|
||||
and any production WSGI deployments. It should expose a module-level variable
|
||||
named ``application``. Django's ``runserver`` and ``runfcgi`` commands discover
|
||||
this application via the ``WSGI_APPLICATION`` setting.
|
||||
|
||||
Usually you will have the standard Django WSGI application here, but it also
|
||||
might make sense to replace the whole Django WSGI application with a custom one
|
||||
that later delegates to the Django one. For example, you could introduce WSGI
|
||||
middleware here, or combine a Django application with an application of another
|
||||
framework.
|
||||
It exposes the WSGI callable as a module-level variable named ``application``.
|
||||
|
||||
For more information on this file, see
|
||||
https://docs.djangoproject.com/en/3.2/howto/deployment/wsgi/
|
||||
"""
|
||||
from django.core.wsgi import get_wsgi_application
|
||||
|
||||
import six
|
||||
import os
|
||||
|
||||
if six.PY2:
|
||||
import sys
|
||||
from django.core.wsgi import get_wsgi_application
|
||||
|
||||
# noinspection PyCompatibility
|
||||
reload(sys)
|
||||
sys.setdefaultencoding('UTF-8') # @UndefinedVariable
|
||||
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'server.settings')
|
||||
|
||||
|
||||
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "server.settings")
|
||||
|
||||
# This application object is used by any WSGI server configured to use this
|
||||
# file. This includes Django's development server, if the WSGI_APPLICATION
|
||||
# setting points here.
|
||||
application = get_wsgi_application()
|
||||
|
@@ -50,14 +50,14 @@ from .handlers import (
|
||||
NotFound,
|
||||
RequestError,
|
||||
ResponseError,
|
||||
NotSupportedError
|
||||
NotSupportedError,
|
||||
)
|
||||
|
||||
from . import processors
|
||||
|
||||
# Not imported at runtime, just for type checking
|
||||
if typing.TYPE_CHECKING:
|
||||
from uds.core.util.request import ExtendedHttpRequest
|
||||
from uds.core.util.request import ExtendedHttpRequestWithUser
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
@@ -70,12 +70,15 @@ class Dispatcher(View):
|
||||
"""
|
||||
This class is responsible of dispatching REST requests
|
||||
"""
|
||||
|
||||
# This attribute will contain all paths--> handler relations, filled at Initialized method
|
||||
services: typing.ClassVar[typing.Dict[str, typing.Any]] = {'': None} # Will include a default /rest handler, but rigth now this will be fine
|
||||
services: typing.ClassVar[typing.Dict[str, typing.Any]] = {
|
||||
'': None
|
||||
} # Will include a default /rest handler, but rigth now this will be fine
|
||||
|
||||
# pylint: disable=too-many-locals, too-many-return-statements, too-many-branches, too-many-statements
|
||||
@method_decorator(csrf_exempt)
|
||||
def dispatch(self, request: 'ExtendedHttpRequest', *args, **kwargs):
|
||||
def dispatch(self, request: 'ExtendedHttpRequestWithUser', *args, **kwargs):
|
||||
"""
|
||||
Processes the REST request and routes it wherever it needs to be routed
|
||||
"""
|
||||
@@ -98,7 +101,9 @@ class Dispatcher(View):
|
||||
content_type = path[0].split('.')[1]
|
||||
|
||||
clean_path = path[0].split('.')[0]
|
||||
if not clean_path: # Skip empty path elements, so /x/y == /x////y for example (due to some bugs detected on some clients)
|
||||
if (
|
||||
not clean_path
|
||||
): # Skip empty path elements, so /x/y == /x////y for example (due to some bugs detected on some clients)
|
||||
path = path[1:]
|
||||
continue
|
||||
|
||||
@@ -115,9 +120,13 @@ class Dispatcher(View):
|
||||
# Here, service points to the path
|
||||
cls: typing.Optional[typing.Type[Handler]] = service['']
|
||||
if cls is None:
|
||||
return http.HttpResponseNotFound('Method not found', content_type="text/plain")
|
||||
return http.HttpResponseNotFound(
|
||||
'Method not found', content_type="text/plain"
|
||||
)
|
||||
|
||||
processor = processors.available_processors_ext_dict.get(content_type, processors.default_processor)(request)
|
||||
processor = processors.available_processors_ext_dict.get(
|
||||
content_type, processors.default_processor
|
||||
)(request)
|
||||
|
||||
# Obtain method to be invoked
|
||||
http_method: str = request.method.lower() if request.method else ''
|
||||
@@ -128,24 +137,40 @@ class Dispatcher(View):
|
||||
handler = None
|
||||
|
||||
try:
|
||||
handler = cls(request, full_path, http_method, processor.processParameters(), *args, **kwargs)
|
||||
handler = cls(
|
||||
request,
|
||||
full_path,
|
||||
http_method,
|
||||
processor.processParameters(),
|
||||
*args,
|
||||
**kwargs,
|
||||
)
|
||||
operation: typing.Callable[[], typing.Any] = getattr(handler, http_method)
|
||||
except processors.ParametersException as e:
|
||||
logger.debug('Path: %s', full_path)
|
||||
logger.debug('Error: %s', e)
|
||||
return http.HttpResponseServerError('Invalid parameters invoking {0}: {1}'.format(full_path, e), content_type="text/plain")
|
||||
return http.HttpResponseServerError(
|
||||
'Invalid parameters invoking {0}: {1}'.format(full_path, e),
|
||||
content_type="text/plain",
|
||||
)
|
||||
except AttributeError:
|
||||
allowedMethods = []
|
||||
for n in ['get', 'post', 'put', 'delete']:
|
||||
if hasattr(handler, n):
|
||||
allowedMethods.append(n)
|
||||
return http.HttpResponseNotAllowed(allowedMethods, content_type="text/plain")
|
||||
return http.HttpResponseNotAllowed(
|
||||
allowedMethods, content_type="text/plain"
|
||||
)
|
||||
except AccessDenied:
|
||||
return http.HttpResponseForbidden('access denied', content_type="text/plain")
|
||||
return http.HttpResponseForbidden(
|
||||
'access denied', content_type="text/plain"
|
||||
)
|
||||
except Exception:
|
||||
logger.exception('error accessing attribute')
|
||||
logger.debug('Getting attribute %s for %s', http_method, full_path)
|
||||
return http.HttpResponseServerError('Unexcepected error', content_type="text/plain")
|
||||
return http.HttpResponseServerError(
|
||||
'Unexcepected error', content_type="text/plain"
|
||||
)
|
||||
|
||||
# Invokes the handler's operation, add headers to response and returns
|
||||
try:
|
||||
@@ -180,12 +205,16 @@ class Dispatcher(View):
|
||||
Try to register Handler subclasses that have not been inherited
|
||||
"""
|
||||
for cls in classes:
|
||||
if not cls.__subclasses__(): # Only classes that has not been inherited will be registered as Handlers
|
||||
if (
|
||||
not cls.__subclasses__()
|
||||
): # Only classes that has not been inherited will be registered as Handlers
|
||||
if not cls.name:
|
||||
name = cls.__name__.lower()
|
||||
else:
|
||||
name = cls.name
|
||||
logger.debug('Adding handler %s for method %s in path %s', cls, name, cls.path)
|
||||
logger.debug(
|
||||
'Adding handler %s for method %s in path %s', cls, name, cls.path
|
||||
)
|
||||
service_node = Dispatcher.services # Root path
|
||||
if cls.path:
|
||||
for k in cls.path.split('/'):
|
||||
@@ -214,7 +243,9 @@ class Dispatcher(View):
|
||||
pkgpath = os.path.join(os.path.dirname(sys.modules[__name__].__file__), package)
|
||||
for _, name, _ in pkgutil.iter_modules([pkgpath]):
|
||||
# __import__(__name__ + '.' + package + '.' + name, globals(), locals(), [], 0)
|
||||
importlib.import_module( __name__ + '.' + package + '.' + name) # import module
|
||||
importlib.import_module(
|
||||
__name__ + '.' + package + '.' + name
|
||||
) # import module
|
||||
|
||||
importlib.invalidate_caches()
|
||||
|
||||
|
@@ -1,7 +1,7 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
#
|
||||
# Copyright (c) 2012-2019 Virtual Cable S.L.
|
||||
# Copyright (c) 2012-2021 Virtual Cable S.L.U.
|
||||
# All rights reserved.
|
||||
#
|
||||
# Redistribution and use in source and binary forms, with or without modification,
|
||||
@@ -12,7 +12,7 @@
|
||||
# * Redistributions in binary form must reproduce the above copyright notice,
|
||||
# this list of conditions and the following disclaimer in the documentation
|
||||
# and/or other materials provided with the distribution.
|
||||
# * Neither the name of Virtual Cable S.L. nor the names of its contributors
|
||||
# * Neither the name of Virtual Cable S.L.U. nor the names of its contributors
|
||||
# may be used to endorse or promote products derived from this software
|
||||
# without specific prior written permission.
|
||||
#
|
||||
@@ -30,11 +30,9 @@
|
||||
"""
|
||||
@author: Adolfo Gómez, dkmaster at dkmon dot com
|
||||
"""
|
||||
import datetime
|
||||
import typing
|
||||
import logging
|
||||
|
||||
from django.utils import timezone
|
||||
from django.contrib.sessions.backends.base import SessionBase
|
||||
from django.contrib.sessions.backends.db import SessionStore
|
||||
|
||||
@@ -46,8 +44,8 @@ from uds.core.managers import cryptoManager
|
||||
|
||||
# Not imported at runtime, just for type checking
|
||||
if typing.TYPE_CHECKING:
|
||||
from uds.core.util.request import ExtendedHttpRequest
|
||||
|
||||
from uds.core.util.request import ExtendedHttpRequestWithUser
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
AUTH_TOKEN_HEADER = 'HTTP_X_AUTH_TOKEN'
|
||||
@@ -93,31 +91,59 @@ class Handler:
|
||||
"""
|
||||
REST requests handler base class
|
||||
"""
|
||||
raw: typing.ClassVar[bool] = False # If true, Handler will return directly an HttpResponse Object
|
||||
name: typing.ClassVar[typing.Optional[str]] = None # If name is not used, name will be the class name in lower case
|
||||
path: typing.ClassVar[typing.Optional[str]] = None # Path for this method, so we can do /auth/login, /auth/logout, /auth/auths in a simple way
|
||||
authenticated: typing.ClassVar[bool] = True # By default, all handlers needs authentication. Will be overwriten if needs_admin or needs_staff,
|
||||
needs_admin: typing.ClassVar[bool] = False # By default, the methods will be accessible by anyone if nothing else indicated
|
||||
|
||||
raw: typing.ClassVar[
|
||||
bool
|
||||
] = False # If true, Handler will return directly an HttpResponse Object
|
||||
name: typing.ClassVar[
|
||||
typing.Optional[str]
|
||||
] = None # If name is not used, name will be the class name in lower case
|
||||
path: typing.ClassVar[
|
||||
typing.Optional[str]
|
||||
] = None # Path for this method, so we can do /auth/login, /auth/logout, /auth/auths in a simple way
|
||||
authenticated: typing.ClassVar[
|
||||
bool
|
||||
] = True # By default, all handlers needs authentication. Will be overwriten if needs_admin or needs_staff,
|
||||
needs_admin: typing.ClassVar[
|
||||
bool
|
||||
] = False # By default, the methods will be accessible by anyone if nothing else indicated
|
||||
needs_staff: typing.ClassVar[bool] = False # By default, staff
|
||||
|
||||
_request: 'ExtendedHttpRequest' # It's a modified HttpRequest
|
||||
_request: 'ExtendedHttpRequestWithUser' # It's a modified HttpRequest
|
||||
_path: str
|
||||
_operation: str
|
||||
_params: typing.Any # This is a deserliazied object from request. Can be anything as 'a' or {'a': 1} or ....
|
||||
_args: typing.Tuple[str, ...] # This are the "path" split by /, that is, the REST invocation arguments
|
||||
_args: typing.Tuple[
|
||||
str, ...
|
||||
] # This are the "path" split by /, that is, the REST invocation arguments
|
||||
_kwargs: typing.Dict
|
||||
_headers: typing.Dict[str, str]
|
||||
_session: typing.Optional[SessionStore]
|
||||
_authToken: typing.Optional[str]
|
||||
_user: 'User'
|
||||
|
||||
|
||||
# method names: 'get', 'post', 'put', 'patch', 'delete', 'head', 'options', 'trace'
|
||||
def __init__(self, request: 'ExtendedHttpRequest', path: str, operation: str, params: typing.Any, *args: str, **kwargs):
|
||||
def __init__(
|
||||
self,
|
||||
request: 'ExtendedHttpRequestWithUser',
|
||||
path: str,
|
||||
operation: str,
|
||||
params: typing.Any,
|
||||
*args: str,
|
||||
**kwargs
|
||||
):
|
||||
|
||||
logger.debug('Data: %s %s %s', self.__class__, self.needs_admin, self.authenticated)
|
||||
if (self.needs_admin or self.needs_staff) and not self.authenticated: # If needs_admin, must also be authenticated
|
||||
raise Exception('class {} is not authenticated but has needs_admin or needs_staff set!!'.format(self.__class__))
|
||||
logger.debug(
|
||||
'Data: %s %s %s', self.__class__, self.needs_admin, self.authenticated
|
||||
)
|
||||
if (
|
||||
self.needs_admin or self.needs_staff
|
||||
) and not self.authenticated: # If needs_admin, must also be authenticated
|
||||
raise Exception(
|
||||
'class {} is not authenticated but has needs_admin or needs_staff set!!'.format(
|
||||
self.__class__
|
||||
)
|
||||
)
|
||||
|
||||
self._request = request
|
||||
self._path = path
|
||||
@@ -127,7 +153,9 @@ class Handler:
|
||||
self._kwargs = kwargs
|
||||
self._headers = {}
|
||||
self._authToken = None
|
||||
if self.authenticated: # Only retrieve auth related data on authenticated handlers
|
||||
if (
|
||||
self.authenticated
|
||||
): # Only retrieve auth related data on authenticated handlers
|
||||
try:
|
||||
self._authToken = self._request.META.get(AUTH_TOKEN_HEADER, '')
|
||||
self._session = SessionStore(session_key=self._authToken)
|
||||
@@ -150,7 +178,6 @@ class Handler:
|
||||
else:
|
||||
self._user = User() # Empty user for non authenticated handlers
|
||||
|
||||
|
||||
def headers(self) -> typing.Dict[str, str]:
|
||||
"""
|
||||
Returns the headers of the REST request (all)
|
||||
@@ -191,16 +218,16 @@ class Handler:
|
||||
|
||||
@staticmethod
|
||||
def storeSessionAuthdata(
|
||||
session: SessionBase,
|
||||
id_auth: int,
|
||||
username: str,
|
||||
password: str,
|
||||
locale: str,
|
||||
platform: str,
|
||||
is_admin: bool,
|
||||
staff_member: bool,
|
||||
scrambler: str
|
||||
):
|
||||
session: SessionBase,
|
||||
id_auth: int,
|
||||
username: str,
|
||||
password: str,
|
||||
locale: str,
|
||||
platform: str,
|
||||
is_admin: bool,
|
||||
staff_member: bool,
|
||||
scrambler: str,
|
||||
):
|
||||
"""
|
||||
Stores the authentication data inside current session
|
||||
:param session: session handler (Djano user session object)
|
||||
@@ -220,20 +247,20 @@ class Handler:
|
||||
'locale': locale,
|
||||
'platform': platform,
|
||||
'is_admin': is_admin,
|
||||
'staff_member': staff_member
|
||||
'staff_member': staff_member,
|
||||
}
|
||||
|
||||
def genAuthToken(
|
||||
self,
|
||||
id_auth: int,
|
||||
username: str,
|
||||
password: str,
|
||||
locale: str,
|
||||
platform: str,
|
||||
is_admin: bool,
|
||||
staf_member: bool,
|
||||
scrambler: str
|
||||
):
|
||||
self,
|
||||
id_auth: int,
|
||||
username: str,
|
||||
password: str,
|
||||
locale: str,
|
||||
platform: str,
|
||||
is_admin: bool,
|
||||
staf_member: bool,
|
||||
scrambler: str,
|
||||
):
|
||||
"""
|
||||
Generates the authentication token from a session, that is basically
|
||||
the session key itself
|
||||
@@ -244,11 +271,21 @@ class Handler:
|
||||
:param staf_member: If user is considered staff member or not
|
||||
"""
|
||||
session = SessionStore()
|
||||
Handler.storeSessionAuthdata(session, id_auth, username, password, locale, platform, is_admin, staf_member, scrambler)
|
||||
Handler.storeSessionAuthdata(
|
||||
session,
|
||||
id_auth,
|
||||
username,
|
||||
password,
|
||||
locale,
|
||||
platform,
|
||||
is_admin,
|
||||
staf_member,
|
||||
scrambler,
|
||||
)
|
||||
session.save()
|
||||
self._authToken = session.session_key
|
||||
self._session = session
|
||||
|
||||
|
||||
return self._authToken
|
||||
|
||||
def cleanAuthToken(self) -> None:
|
||||
@@ -282,13 +319,20 @@ class Handler:
|
||||
self._session.accessed = True
|
||||
self._session.save()
|
||||
except Exception:
|
||||
logger.exception('Got an exception setting session value %s to %s', key, value)
|
||||
logger.exception(
|
||||
'Got an exception setting session value %s to %s', key, value
|
||||
)
|
||||
|
||||
def validSource(self) -> bool:
|
||||
try:
|
||||
return net.ipInNetwork(self._request.ip, GlobalConfig.ADMIN_TRUSTED_SOURCES.get(True))
|
||||
return net.ipInNetwork(
|
||||
self._request.ip, GlobalConfig.ADMIN_TRUSTED_SOURCES.get(True)
|
||||
)
|
||||
except Exception as e:
|
||||
logger.warning('Error checking truted ADMIN source: "%s" does not seems to be a valid network string. Using Unrestricted access.', GlobalConfig.ADMIN_TRUSTED_SOURCES.get())
|
||||
logger.warning(
|
||||
'Error checking truted ADMIN source: "%s" does not seems to be a valid network string. Using Unrestricted access.',
|
||||
GlobalConfig.ADMIN_TRUSTED_SOURCES.get(),
|
||||
)
|
||||
|
||||
return True
|
||||
|
||||
@@ -312,8 +356,10 @@ class Handler:
|
||||
authId = self.getValue('auth')
|
||||
username = self.getValue('username')
|
||||
# Maybe it's root user??
|
||||
if (GlobalConfig.SUPER_USER_ALLOW_WEBACCESS.getBool(True) and
|
||||
username == GlobalConfig.SUPER_USER_LOGIN.get(True) and
|
||||
authId == -1):
|
||||
if (
|
||||
GlobalConfig.SUPER_USER_ALLOW_WEBACCESS.getBool(True)
|
||||
and username == GlobalConfig.SUPER_USER_LOGIN.get(True)
|
||||
and authId == -1
|
||||
):
|
||||
return getRootUser()
|
||||
return Authenticator.objects.get(pk=authId).users.get(name=username)
|
||||
|
@@ -50,6 +50,7 @@ class Accounts(ModelHandler):
|
||||
"""
|
||||
Processes REST requests about accounts
|
||||
"""
|
||||
|
||||
model = Account
|
||||
detail = {'usage': AccountsUsage}
|
||||
|
||||
@@ -72,7 +73,7 @@ class Accounts(ModelHandler):
|
||||
'tags': [tag.tag for tag in item.tags.all()],
|
||||
'comments': item.comments,
|
||||
'time_mark': item.time_mark,
|
||||
'permission': permissions.getEffectivePermission(self._user, item)
|
||||
'permission': permissions.getEffectivePermission(self._user, item),
|
||||
}
|
||||
|
||||
def getGui(self, type_: str) -> typing.List[typing.Any]:
|
||||
|
@@ -70,7 +70,7 @@ class AccountsUsage(DetailHandler): # pylint: disable=too-many-public-methods
|
||||
'running': item.user_service is not None,
|
||||
'elapsed': item.elapsed,
|
||||
'elapsed_timemark': item.elapsed_timemark,
|
||||
'permission': perm
|
||||
'permission': perm,
|
||||
}
|
||||
|
||||
return retVal
|
||||
|
@@ -1,7 +1,7 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
#
|
||||
# Copyright (c) 2020 Virtual Cable S.L.U.
|
||||
# Copyright (c) 2021 Virtual Cable S.L.U.
|
||||
# All rights reserved.
|
||||
#
|
||||
# Redistribution and use in source and binary forms, with or without modification,
|
||||
@@ -64,7 +64,9 @@ class ActorTokens(ModelHandler):
|
||||
def item_as_dict(self, item: ActorToken) -> typing.Dict[str, typing.Any]:
|
||||
return {
|
||||
'id': item.token,
|
||||
'name': _('Token isued by {} from {}').format(item.username, item.hostname or item.ip),
|
||||
'name': _('Token isued by {} from {}').format(
|
||||
item.username, item.hostname or item.ip
|
||||
),
|
||||
'stamp': item.stamp,
|
||||
'username': item.username,
|
||||
'ip': item.ip,
|
||||
@@ -73,7 +75,7 @@ class ActorTokens(ModelHandler):
|
||||
'pre_command': item.pre_command,
|
||||
'post_command': item.post_command,
|
||||
'runonce_command': item.runonce_command,
|
||||
'log_level': ['DEBUG', 'INFO', 'ERROR', 'FATAL'][item.log_level%4]
|
||||
'log_level': ['DEBUG', 'INFO', 'ERROR', 'FATAL'][item.log_level % 4],
|
||||
}
|
||||
|
||||
def delete(self) -> str:
|
||||
@@ -83,7 +85,9 @@ class ActorTokens(ModelHandler):
|
||||
if len(self._args) != 1:
|
||||
raise RequestError('Delete need one and only one argument')
|
||||
|
||||
self.ensureAccess(self.model(), permissions.PERMISSION_ALL, root=True) # Must have write permissions to delete
|
||||
self.ensureAccess(
|
||||
self.model(), permissions.PERMISSION_ALL, root=True
|
||||
) # Must have write permissions to delete
|
||||
|
||||
try:
|
||||
self.model.objects.get(token=self._args[0]).delete()
|
||||
|
@@ -41,7 +41,7 @@ from uds.models import (
|
||||
TicketStore,
|
||||
)
|
||||
|
||||
#from uds.core import VERSION
|
||||
# from uds.core import VERSION
|
||||
from uds.core.managers import userServiceManager
|
||||
from uds.core import osmanagers
|
||||
from uds.core.util import log, certs
|
||||
@@ -64,8 +64,10 @@ UNMANAGED = 'unmanaged' # matches the definition of UDS Actors OFC
|
||||
class BlockAccess(Exception):
|
||||
pass
|
||||
|
||||
# Helpers
|
||||
|
||||
# Helpers
|
||||
def fixIdsList(idsList: typing.List[str]) -> typing.List[str]:
|
||||
return [i.upper() for i in idsList] + [i.lower() for i in idsList]
|
||||
|
||||
def checkBlockedIp(ip: str) -> None:
|
||||
if GlobalConfig.BLOCK_ACTOR_FAILURES.getBool() is False:
|
||||
@@ -73,7 +75,11 @@ def checkBlockedIp(ip: str) -> None:
|
||||
cache = Cache('actorv3')
|
||||
fails = cache.get(ip) or 0
|
||||
if fails > ALLOWED_FAILS:
|
||||
logger.info('Access to actor from %s is blocked for %s seconds since last fail', ip, GlobalConfig.LOGIN_BLOCK.getInt())
|
||||
logger.info(
|
||||
'Access to actor from %s is blocked for %s seconds since last fail',
|
||||
ip,
|
||||
GlobalConfig.LOGIN_BLOCK.getInt(),
|
||||
)
|
||||
raise BlockAccess()
|
||||
|
||||
|
||||
@@ -88,7 +94,9 @@ class ActorV3Action(Handler):
|
||||
path = 'actor/v3'
|
||||
|
||||
@staticmethod
|
||||
def actorResult(result: typing.Any = None, error: typing.Optional[str] = None) -> typing.MutableMapping[str, typing.Any]:
|
||||
def actorResult(
|
||||
result: typing.Any = None, error: typing.Optional[str] = None
|
||||
) -> typing.MutableMapping[str, typing.Any]:
|
||||
result = result or ''
|
||||
res = {'result': result, 'stamp': getSqlDatetimeAsUnix()}
|
||||
if error:
|
||||
@@ -130,6 +138,7 @@ class Test(ActorV3Action):
|
||||
"""
|
||||
Tests UDS Broker actor connectivity & key
|
||||
"""
|
||||
|
||||
name = 'test'
|
||||
|
||||
def post(self) -> typing.MutableMapping[str, typing.Any]:
|
||||
@@ -138,7 +147,9 @@ class Test(ActorV3Action):
|
||||
if self._params.get('type') == UNMANAGED:
|
||||
Service.objects.get(token=self._params['token'])
|
||||
else:
|
||||
ActorToken.objects.get(token=self._params['token']) # Not assigned, because only needs check
|
||||
ActorToken.objects.get(
|
||||
token=self._params['token']
|
||||
) # Not assigned, because only needs check
|
||||
except Exception:
|
||||
return ActorV3Action.actorResult('invalid token')
|
||||
|
||||
@@ -149,6 +160,7 @@ class Register(ActorV3Action):
|
||||
"""
|
||||
Registers an actor
|
||||
"""
|
||||
|
||||
authenticated = True
|
||||
needs_staff = True
|
||||
|
||||
@@ -182,16 +194,17 @@ class Register(ActorV3Action):
|
||||
runonce_command=self._params['run_once_command'],
|
||||
log_level=self._params['log_level'],
|
||||
token=secrets.token_urlsafe(36),
|
||||
stamp=getSqlDatetime()
|
||||
stamp=getSqlDatetime(),
|
||||
)
|
||||
return ActorV3Action.actorResult(actorToken.token)
|
||||
|
||||
|
||||
class Initiialize(ActorV3Action):
|
||||
class Initialize(ActorV3Action):
|
||||
"""
|
||||
Information about machine action.
|
||||
Also returns the id used for the rest of the actions. (Only this one will use actor key)
|
||||
"""
|
||||
|
||||
name = 'initialize'
|
||||
|
||||
def action(self) -> typing.MutableMapping[str, typing.Any]:
|
||||
@@ -228,38 +241,43 @@ class Initiialize(ActorV3Action):
|
||||
"""
|
||||
# First, validate token...
|
||||
logger.debug('Args: %s, Params: %s', self._args, self._params)
|
||||
service: typing.Optional[Service] = None
|
||||
try:
|
||||
# First, try to locate an user service providing this token.
|
||||
if self._params['type'] == UNMANAGED:
|
||||
# If unmanaged, use Service locator
|
||||
service: Service = Service.objects.get(token=self._params['token'])
|
||||
service = Service.objects.get(token=self._params['token'])
|
||||
# Locate an userService that belongs to this service and which
|
||||
# Build the possible ids and make initial filter to match service
|
||||
idsList = [x['ip'] for x in self._params['id']] + [x['mac'] for x in self._params['id']][:10]
|
||||
idsList = [x['ip'] for x in self._params['id']] + [
|
||||
x['mac'] for x in self._params['id']
|
||||
][:10]
|
||||
dbFilter = UserService.objects.filter(deployed_service__service=service)
|
||||
else:
|
||||
# If not service provided token, use actor tokens
|
||||
ActorToken.objects.get(token=self._params['token']) # Not assigned, because only needs check
|
||||
ActorToken.objects.get(
|
||||
token=self._params['token']
|
||||
) # Not assigned, because only needs check
|
||||
# Build the possible ids and make initial filter to match ANY userservice with provided MAC
|
||||
idsList = [i['mac'] for i in self._params['id'][:5]]
|
||||
dbFilter = UserService.objects.all()
|
||||
|
||||
# Valid actor token, now validate access allowed. That is, look for a valid mac from the ones provided.
|
||||
try:
|
||||
userService: UserService = next(
|
||||
iter(dbFilter.filter(
|
||||
unique_id__in=idsList,
|
||||
state__in=[State.USABLE, State.PREPARING]
|
||||
))
|
||||
# ensure idsLists has upper and lower versions for case sensitive databases
|
||||
idsList = fixIdsList(idsList)
|
||||
# Set full filter
|
||||
dbFilter = dbFilter.filter(
|
||||
unique_id__in=idsList,
|
||||
state__in=[State.USABLE, State.PREPARING],
|
||||
)
|
||||
|
||||
userService: UserService = next(iter(dbFilter))
|
||||
except Exception as e:
|
||||
logger.info('Unmanaged host request: %s, %s', self._params, e)
|
||||
return ActorV3Action.actorResult({
|
||||
'own_token': None,
|
||||
'max_idle': None,
|
||||
'unique_id': None,
|
||||
'os': None
|
||||
})
|
||||
return ActorV3Action.actorResult(
|
||||
{'own_token': None, 'max_idle': None, 'unique_id': None, 'os': None}
|
||||
)
|
||||
|
||||
# Managed by UDS, get initialization data from osmanager and return it
|
||||
# Set last seen actor version
|
||||
@@ -269,11 +287,13 @@ class Initiialize(ActorV3Action):
|
||||
if osManager:
|
||||
osData = osManager.actorData(userService)
|
||||
|
||||
return ActorV3Action.actorResult({
|
||||
'own_token': userService.uuid,
|
||||
'unique_id': userService.unique_id,
|
||||
'os': osData
|
||||
})
|
||||
return ActorV3Action.actorResult(
|
||||
{
|
||||
'own_token': userService.uuid,
|
||||
'unique_id': userService.unique_id,
|
||||
'os': osData,
|
||||
}
|
||||
)
|
||||
except (ActorToken.DoesNotExist, Service.DoesNotExist):
|
||||
raise BlockAccess()
|
||||
|
||||
@@ -282,6 +302,7 @@ class BaseReadyChange(ActorV3Action):
|
||||
"""
|
||||
Records the IP change of actor
|
||||
"""
|
||||
|
||||
name = 'notused' # Not really important, this is not a "leaf" class and will not be directly available
|
||||
|
||||
def action(self) -> typing.MutableMapping[str, typing.Any]:
|
||||
@@ -309,7 +330,12 @@ class BaseReadyChange(ActorV3Action):
|
||||
userService.updateData(userServiceInstance)
|
||||
|
||||
# Store communications url also
|
||||
ActorV3Action.setCommsUrl(userService, self._params['ip'], int(self._params['port']), self._params['secret'])
|
||||
ActorV3Action.setCommsUrl(
|
||||
userService,
|
||||
self._params['ip'],
|
||||
int(self._params['port']),
|
||||
self._params['secret'],
|
||||
)
|
||||
|
||||
if userService.os_state != State.USABLE:
|
||||
userService.setOsState(State.USABLE)
|
||||
@@ -327,13 +353,20 @@ class BaseReadyChange(ActorV3Action):
|
||||
userService.setProperty('priv', privateKey)
|
||||
userService.setProperty('priv_passwd', password)
|
||||
|
||||
return ActorV3Action.actorResult({'private_key': privateKey, 'server_certificate': cert, 'password': password})
|
||||
return ActorV3Action.actorResult(
|
||||
{
|
||||
'private_key': privateKey,
|
||||
'server_certificate': cert,
|
||||
'password': password,
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
class IpChange(BaseReadyChange):
|
||||
"""
|
||||
Processses IP Change.
|
||||
"""
|
||||
|
||||
name = 'ipchange'
|
||||
|
||||
|
||||
@@ -341,6 +374,7 @@ class Ready(BaseReadyChange):
|
||||
"""
|
||||
Notifies the user service is ready
|
||||
"""
|
||||
|
||||
name = 'ready'
|
||||
|
||||
def action(self) -> typing.MutableMapping[str, typing.Any]:
|
||||
@@ -371,6 +405,7 @@ class Version(ActorV3Action):
|
||||
Notifies the version.
|
||||
Used on possible "customized" actors.
|
||||
"""
|
||||
|
||||
name = 'version'
|
||||
|
||||
def action(self) -> typing.MutableMapping[str, typing.Any]:
|
||||
@@ -381,16 +416,26 @@ class Version(ActorV3Action):
|
||||
|
||||
return ActorV3Action.actorResult()
|
||||
|
||||
|
||||
class LoginLogout(ActorV3Action):
|
||||
name = 'notused' # Not really important, this is not a "leaf" class and will not be directly available
|
||||
|
||||
def notifyService(self, login: bool):
|
||||
def notifyService(self, isLogin: bool) -> None:
|
||||
try:
|
||||
# If unmanaged, use Service locator
|
||||
service : 'services.Service' = Service.objects.get(token=self._params['token']).getInstance()
|
||||
# Locate an userService that belongs to this service and which
|
||||
service: 'services.Service' = Service.objects.get(
|
||||
token=self._params['token']
|
||||
).getInstance()
|
||||
|
||||
# We have a valid service, now we can make notifications
|
||||
|
||||
# Build the possible ids and make initial filter to match service
|
||||
idsList = [x['ip'] for x in self._params['id']] + [x['mac'] for x in self._params['id']][:10]
|
||||
idsList = [x['ip'] for x in self._params['id']] + [
|
||||
x['mac'] for x in self._params['id']
|
||||
][:10]
|
||||
|
||||
# ensure idsLists has upper and lower versions for case sensitive databases
|
||||
idsList = fixIdsList(idsList)
|
||||
|
||||
validId: typing.Optional[str] = service.getValidId(idsList)
|
||||
|
||||
@@ -398,19 +443,16 @@ class LoginLogout(ActorV3Action):
|
||||
if not validId:
|
||||
raise Exception()
|
||||
|
||||
# Check secret if is stored
|
||||
storedInfo : typing.Optional[typing.MutableMapping[str, typing.Any]] = service.recoverIdInfo(validId)
|
||||
# If no secret valid
|
||||
if not storedInfo or self._params['secret'] != storedInfo['secret']:
|
||||
raise Exception()
|
||||
# Recover Id Info from service and validId
|
||||
# idInfo = service.recoverIdInfo(validId)
|
||||
|
||||
# Notify Service that someone logged in/out
|
||||
if login:
|
||||
is_remote = self._params.get('session_type', '')[:4] in ('xrdp', 'RDP-')
|
||||
if isLogin:
|
||||
# Try to guess if this is a remote session
|
||||
is_remote = self._params.get('session_type', '')[:3] in ('xrdp', 'RDP-')
|
||||
service.processLogin(validId, remote_login=is_remote)
|
||||
else:
|
||||
service.processLogout(validId)
|
||||
service.processLogout(validId, remote_login=is_remote)
|
||||
|
||||
# All right, service notified...
|
||||
except Exception:
|
||||
@@ -421,12 +463,29 @@ class Login(LoginLogout):
|
||||
"""
|
||||
Notifies user logged id
|
||||
"""
|
||||
|
||||
name = 'login'
|
||||
|
||||
# payload received
|
||||
# {
|
||||
# 'type': actor_type or types.MANAGED,
|
||||
# 'id': [{'mac': i.mac, 'ip': i.ip} for i in interfaces],
|
||||
# 'token': token,
|
||||
# 'username': username,
|
||||
# 'session_type': sessionType,
|
||||
# 'secret': secret or '',
|
||||
# }
|
||||
|
||||
@staticmethod
|
||||
def process_login(userService: UserService, username: str) -> typing.Optional[osmanagers.OSManager]:
|
||||
osManager: typing.Optional[osmanagers.OSManager] = userService.getOsManagerInstance()
|
||||
if not userService.in_use: # If already logged in, do not add a second login (windows does this i.e.)
|
||||
def process_login(
|
||||
userService: UserService, username: str
|
||||
) -> typing.Optional[osmanagers.OSManager]:
|
||||
osManager: typing.Optional[
|
||||
osmanagers.OSManager
|
||||
] = userService.getOsManagerInstance()
|
||||
if (
|
||||
not userService.in_use
|
||||
): # If already logged in, do not add a second login (windows does this i.e.)
|
||||
osmanagers.OSManager.loggedIn(userService, username)
|
||||
return osManager
|
||||
|
||||
@@ -439,7 +498,9 @@ class Login(LoginLogout):
|
||||
|
||||
try:
|
||||
userService: UserService = self.getUserService()
|
||||
osManager = Login.process_login(userService, self._params.get('username') or '')
|
||||
osManager = Login.process_login(
|
||||
userService, self._params.get('username') or ''
|
||||
)
|
||||
|
||||
maxIdle = osManager.maxIdle() if osManager else None
|
||||
|
||||
@@ -458,30 +519,31 @@ class Login(LoginLogout):
|
||||
except Exception: # If unamanaged host, lest do a bit more work looking for a service with the provided parameters...
|
||||
if isManaged:
|
||||
raise
|
||||
self.notifyService(login=True)
|
||||
self.notifyService(isLogin=True)
|
||||
|
||||
|
||||
return ActorV3Action.actorResult({
|
||||
'ip': ip,
|
||||
'hostname': hostname,
|
||||
'dead_line': deadLine,
|
||||
'max_idle': maxIdle
|
||||
})
|
||||
return ActorV3Action.actorResult(
|
||||
{'ip': ip, 'hostname': hostname, 'dead_line': deadLine, 'max_idle': maxIdle}
|
||||
)
|
||||
|
||||
|
||||
class Logout(LoginLogout):
|
||||
"""
|
||||
Notifies user logged out
|
||||
"""
|
||||
|
||||
name = 'logout'
|
||||
|
||||
@staticmethod
|
||||
def process_logout(userService: UserService, username: str) -> None:
|
||||
def process_logout(userService: UserService, username: str) -> None:
|
||||
"""
|
||||
This method is static so can be invoked from elsewhere
|
||||
"""
|
||||
osManager: typing.Optional[osmanagers.OSManager] = userService.getOsManagerInstance()
|
||||
if userService.in_use: # If already logged out, do not add a second logout (windows does this i.e.)
|
||||
osManager: typing.Optional[
|
||||
osmanagers.OSManager
|
||||
] = userService.getOsManagerInstance()
|
||||
if (
|
||||
userService.in_use
|
||||
): # If already logged out, do not add a second logout (windows does this i.e.)
|
||||
osmanagers.OSManager.loggedOut(userService, username)
|
||||
if osManager:
|
||||
if osManager.isRemovableOnLogout(userService):
|
||||
@@ -490,7 +552,6 @@ class Logout(LoginLogout):
|
||||
else:
|
||||
userService.remove()
|
||||
|
||||
|
||||
def action(self) -> typing.MutableMapping[str, typing.Any]:
|
||||
isManaged = self._params.get('type') != UNMANAGED
|
||||
|
||||
@@ -501,7 +562,8 @@ class Logout(LoginLogout):
|
||||
except Exception: # If unamanaged host, lest do a bit more work looking for a service with the provided parameters...
|
||||
if isManaged:
|
||||
raise
|
||||
self.notifyService(login=False) # Logout notification
|
||||
self.notifyService(isLogin=False) # Logout notification
|
||||
return ActorV3Action.actorResult('notified') # Result is that we have not processed the logout in fact, but notified the service
|
||||
|
||||
return ActorV3Action.actorResult('ok')
|
||||
|
||||
@@ -510,13 +572,19 @@ class Log(ActorV3Action):
|
||||
"""
|
||||
Sends a log from the service
|
||||
"""
|
||||
|
||||
name = 'log'
|
||||
|
||||
def action(self) -> typing.MutableMapping[str, typing.Any]:
|
||||
logger.debug('Args: %s, Params: %s', self._args, self._params)
|
||||
userService = self.getUserService()
|
||||
# Adjust loglevel to own, we start on 10000 for OTHER, and received is 0 for OTHER
|
||||
log.doLog(userService, int(self._params['level']) + 10000, self._params['message'], log.ACTOR)
|
||||
log.doLog(
|
||||
userService,
|
||||
int(self._params['level']) + 10000,
|
||||
self._params['message'],
|
||||
log.ACTOR,
|
||||
)
|
||||
|
||||
return ActorV3Action.actorResult('ok')
|
||||
|
||||
@@ -525,6 +593,7 @@ class Ticket(ActorV3Action):
|
||||
"""
|
||||
Gets an stored ticket
|
||||
"""
|
||||
|
||||
name = 'ticket'
|
||||
|
||||
def action(self) -> typing.MutableMapping[str, typing.Any]:
|
||||
@@ -532,12 +601,16 @@ class Ticket(ActorV3Action):
|
||||
|
||||
try:
|
||||
# Simple check that token exists
|
||||
ActorToken.objects.get(token=self._params['token']) # Not assigned, because only needs check
|
||||
ActorToken.objects.get(
|
||||
token=self._params['token']
|
||||
) # Not assigned, because only needs check
|
||||
except ActorToken.DoesNotExist:
|
||||
raise BlockAccess() # If too many blocks...
|
||||
|
||||
try:
|
||||
return ActorV3Action.actorResult(TicketStore.get(self._params['ticket'], invalidate=True))
|
||||
return ActorV3Action.actorResult(
|
||||
TicketStore.get(self._params['ticket'], invalidate=True)
|
||||
)
|
||||
except TicketStore.DoesNotExist:
|
||||
return ActorV3Action.actorResult(error='Invalid ticket')
|
||||
|
||||
@@ -550,7 +623,7 @@ class Unmanaged(ActorV3Action):
|
||||
unmanaged method expect a json POST with this fields:
|
||||
* id: List[dict] -> List of dictionary containing ip and mac:
|
||||
* token: str -> Valid Actor "master_token" (if invalid, will return an error).
|
||||
* secret: Secret for commsUrl for actor
|
||||
* secret: Secret for commsUrl for actor (Cu
|
||||
* port: port of the listener (normally 43910)
|
||||
|
||||
This method will also regenerater the public-private key pair for client, that will be needed for the new ip
|
||||
@@ -570,11 +643,42 @@ class Unmanaged(ActorV3Action):
|
||||
|
||||
# Build the possible ids and ask service if it recognizes any of it
|
||||
# If not recognized, will generate anyway the certificate, but will not be saved
|
||||
idsList = [x['ip'] for x in self._params['id']] + [x['mac'] for x in self._params['id']][:10]
|
||||
idsList = [x['ip'] for x in self._params['id']] + [
|
||||
x['mac'] for x in self._params['id']
|
||||
][:10]
|
||||
validId: typing.Optional[str] = service.getValidId(idsList)
|
||||
|
||||
# ensure idsLists has upper and lower versions for case sensitive databases
|
||||
idsList = fixIdsList(idsList)
|
||||
|
||||
# Check if there is already an assigned user service
|
||||
# To notify it logout
|
||||
userService: typing.Optional[UserService]
|
||||
try:
|
||||
dbFilter = UserService.objects.filter(
|
||||
unique_id__in=idsList,
|
||||
state__in=[State.USABLE, State.PREPARING],
|
||||
)
|
||||
|
||||
userService = next(
|
||||
iter(
|
||||
dbFilter.filter(
|
||||
unique_id__in=idsList,
|
||||
state__in=[State.USABLE, State.PREPARING],
|
||||
)
|
||||
)
|
||||
)
|
||||
except StopIteration:
|
||||
userService = None
|
||||
|
||||
# Try to infer the ip from the valid id (that could be an IP or a MAC)
|
||||
ip: str
|
||||
try:
|
||||
ip = next(x['ip'] for x in self._params['id'] if x['ip'] == validId or x['mac'] == validId)
|
||||
ip = next(
|
||||
x['ip']
|
||||
for x in self._params['id']
|
||||
if x['ip'] == validId or x['mac'] == validId
|
||||
)
|
||||
except StopIteration:
|
||||
ip = self._params['id'][0]['ip'] # Get first IP if no valid ip found
|
||||
|
||||
@@ -583,18 +687,25 @@ class Unmanaged(ActorV3Action):
|
||||
cert: typing.Dict[str, str] = {
|
||||
'private_key': privateKey,
|
||||
'server_certificate': certificate,
|
||||
'password': password
|
||||
'password': password,
|
||||
}
|
||||
if validId:
|
||||
# Notify service of it "just start" action
|
||||
service.notifyInitialization(validId)
|
||||
# If id is assigned to an user service, notify "logout" to it
|
||||
if userService:
|
||||
Logout.process_logout(userService, 'init')
|
||||
else:
|
||||
# If it is not assgined to an user service, notify service
|
||||
service.notifyInitialization(validId)
|
||||
|
||||
# Store certificate, secret & port with service if validId
|
||||
service.storeIdInfo(validId, {
|
||||
'cert': certificate,
|
||||
'secret': self._params['secret'],
|
||||
'port': int(self._params['port'])
|
||||
})
|
||||
service.storeIdInfo(
|
||||
validId,
|
||||
{
|
||||
'cert': certificate,
|
||||
'secret': self._params['secret'],
|
||||
'port': int(self._params['port']),
|
||||
},
|
||||
)
|
||||
|
||||
return ActorV3Action.actorResult(cert)
|
||||
|
||||
@@ -608,7 +719,11 @@ class Notify(ActorV3Action):
|
||||
|
||||
def get(self) -> typing.MutableMapping[str, typing.Any]:
|
||||
logger.debug('Args: %s, Params: %s', self._args, self._params)
|
||||
if 'action' not in self._params or 'token' not in self._params or self._params['action'] not in ('login', 'logout'):
|
||||
if (
|
||||
'action' not in self._params
|
||||
or 'token' not in self._params
|
||||
or self._params['action'] not in ('login', 'logout')
|
||||
):
|
||||
# Requested login or logout
|
||||
raise RequestError('Invalid parameters')
|
||||
|
||||
|
@@ -32,7 +32,7 @@
|
||||
"""
|
||||
import logging
|
||||
|
||||
from django.core.cache import cache as djCache
|
||||
from django.core.cache import caches
|
||||
from uds.core.util.cache import Cache as uCache
|
||||
from uds.REST import Handler, RequestError
|
||||
|
||||
@@ -57,5 +57,9 @@ class Cache(Handler):
|
||||
raise RequestError('Invalid Request')
|
||||
|
||||
uCache.purge()
|
||||
djCache.clear()
|
||||
for i in ('default', 'memory'):
|
||||
try:
|
||||
caches[i].clear()
|
||||
except Exception:
|
||||
pass # Ignore non existing cache
|
||||
return 'done'
|
||||
|
@@ -75,7 +75,7 @@ class CalendarRules(DetailHandler): # pylint: disable=too-many-public-methods
|
||||
'interval': item.interval,
|
||||
'duration': item.duration,
|
||||
'duration_unit': item.duration_unit,
|
||||
'permission': perm
|
||||
'permission': perm,
|
||||
}
|
||||
|
||||
return retVal
|
||||
@@ -98,7 +98,13 @@ class CalendarRules(DetailHandler): # pylint: disable=too-many-public-methods
|
||||
{'name': {'title': _('Rule name')}},
|
||||
{'start': {'title': _('Starts'), 'type': 'datetime'}},
|
||||
{'end': {'title': _('Ends'), 'type': 'date'}},
|
||||
{'frequency': {'title': _('Repeats'), 'type': 'dict', 'dict': dict((v[0], str(v[1])) for v in freqs)}},
|
||||
{
|
||||
'frequency': {
|
||||
'title': _('Repeats'),
|
||||
'type': 'dict',
|
||||
'dict': dict((v[0], str(v[1])) for v in freqs),
|
||||
}
|
||||
},
|
||||
{'interval': {'title': _('Every'), 'type': 'callback'}},
|
||||
{'duration': {'title': _('Duration'), 'type': 'callback'}},
|
||||
{'comments': {'title': _('Comments')}},
|
||||
@@ -108,7 +114,18 @@ class CalendarRules(DetailHandler): # pylint: disable=too-many-public-methods
|
||||
# Extract item db fields
|
||||
# We need this fields for all
|
||||
logger.debug('Saving rule %s / %s', parent, item)
|
||||
fields = self.readFieldsFromParams(['name', 'comments', 'frequency', 'start', 'end', 'interval', 'duration', 'duration_unit'])
|
||||
fields = self.readFieldsFromParams(
|
||||
[
|
||||
'name',
|
||||
'comments',
|
||||
'frequency',
|
||||
'start',
|
||||
'end',
|
||||
'interval',
|
||||
'duration',
|
||||
'duration_unit',
|
||||
]
|
||||
)
|
||||
|
||||
if int(fields['interval']) < 1:
|
||||
raise self.invalidItemException('Repeat must be greater than zero')
|
||||
|
@@ -50,6 +50,7 @@ class Calendars(ModelHandler):
|
||||
"""
|
||||
Processes REST requests about calendars
|
||||
"""
|
||||
|
||||
model = Calendar
|
||||
detail = {'rules': CalendarRules}
|
||||
|
||||
@@ -57,7 +58,14 @@ class Calendars(ModelHandler):
|
||||
|
||||
table_title = _('Calendars')
|
||||
table_fields = [
|
||||
{'name': {'title': _('Name'), 'visible': True, 'type': 'icon', 'icon': 'fa fa-calendar text-success'}},
|
||||
{
|
||||
'name': {
|
||||
'title': _('Name'),
|
||||
'visible': True,
|
||||
'type': 'icon',
|
||||
'icon': 'fa fa-calendar text-success',
|
||||
}
|
||||
},
|
||||
{'comments': {'title': _('Comments')}},
|
||||
{'modified': {'title': _('Modified'), 'type': 'datetime'}},
|
||||
{'tags': {'title': _('tags'), 'visible': False}},
|
||||
@@ -70,7 +78,7 @@ class Calendars(ModelHandler):
|
||||
'tags': [tag.tag for tag in item.tags.all()],
|
||||
'comments': item.comments,
|
||||
'modified': item.modified,
|
||||
'permission': permissions.getEffectivePermission(self._user, item)
|
||||
'permission': permissions.getEffectivePermission(self._user, item),
|
||||
}
|
||||
|
||||
def getGui(self, type_: str) -> typing.List[typing.Any]:
|
||||
|
@@ -38,7 +38,7 @@ from django.utils.translation import ugettext as _
|
||||
from django.urls import reverse
|
||||
from uds.REST import Handler
|
||||
from uds.REST import RequestError
|
||||
from uds.models import TicketStore, user
|
||||
from uds.models import TicketStore
|
||||
from uds.models import User
|
||||
from uds.web.util import errors
|
||||
from uds.core.managers import cryptoManager, userServiceManager
|
||||
@@ -46,11 +46,14 @@ from uds.core.util.config import GlobalConfig
|
||||
from uds.core.services.exceptions import ServiceNotReadyError
|
||||
from uds.core import VERSION as UDS_VERSION
|
||||
|
||||
if typing.TYPE_CHECKING:
|
||||
from uds.models import UserService
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
CLIENT_VERSION = UDS_VERSION
|
||||
REQUIRED_CLIENT_VERSION = '3.5.0'
|
||||
#CLIENT_VERSION = UDS_VERSION
|
||||
REQUIRED_CLIENT_VERSION = '3.6.0'
|
||||
CLIENT_VERSION = REQUIRED_CLIENT_VERSION
|
||||
|
||||
|
||||
# Enclosed methods under /client path
|
||||
@@ -58,15 +61,16 @@ class Client(Handler):
|
||||
"""
|
||||
Processes Client requests
|
||||
"""
|
||||
|
||||
authenticated = False # Client requests are not authenticated
|
||||
|
||||
@staticmethod
|
||||
def result(
|
||||
result: typing.Any = None,
|
||||
error: typing.Optional[typing.Union[str, int]] = None,
|
||||
errorCode: int = 0,
|
||||
retryable: bool = False
|
||||
) -> typing.Dict[str, typing.Any]:
|
||||
result: typing.Any = None,
|
||||
error: typing.Optional[typing.Union[str, int]] = None,
|
||||
errorCode: int = 0,
|
||||
retryable: bool = False,
|
||||
) -> typing.Dict[str, typing.Any]:
|
||||
"""
|
||||
Helper method to create a "result" set for actor response
|
||||
:param result: Result value to return (can be None, in which case it is converted to empty string '')
|
||||
@@ -84,7 +88,9 @@ class Client(Handler):
|
||||
if errorCode != 0:
|
||||
# Reformat error so it is better understood by users
|
||||
# error += ' (code {0:04X})'.format(errorCode)
|
||||
error = _('Your service is being created. Please, wait while we complete it') + ' ({}%)'.format(int(errorCode * 25))
|
||||
error = _(
|
||||
'Your service is being created. Please, wait while we complete it'
|
||||
) + ' ({}%)'.format(int(errorCode * 25))
|
||||
|
||||
res['error'] = error
|
||||
res['retryable'] = '1' if retryable else '0'
|
||||
@@ -106,18 +112,31 @@ class Client(Handler):
|
||||
logger.debug('Client args for GET: %s', self._args)
|
||||
|
||||
if not self._args: # Gets version
|
||||
return Client.result({
|
||||
'availableVersion': CLIENT_VERSION,
|
||||
'requiredVersion': REQUIRED_CLIENT_VERSION,
|
||||
'downloadUrl': self._request.build_absolute_uri(reverse('page.client-download'))
|
||||
})
|
||||
return Client.result(
|
||||
{
|
||||
'availableVersion': CLIENT_VERSION,
|
||||
'requiredVersion': REQUIRED_CLIENT_VERSION,
|
||||
'downloadUrl': 'A new version of UDS Client is required.\nPlease, download it from Client Download section.',
|
||||
}
|
||||
)
|
||||
|
||||
if len(self._args) == 1: # Simple test
|
||||
return Client.result(_('Correct'))
|
||||
|
||||
userService: typing.Optional['UserService'] = None
|
||||
try:
|
||||
ticket, scrambler = self._args # If more than 2 args, got an error. pylint: disable=unbalanced-tuple-unpacking
|
||||
(
|
||||
ticket,
|
||||
scrambler,
|
||||
) = (
|
||||
self._args
|
||||
) # If more than 2 args, got an error. pylint: disable=unbalanced-tuple-unpacking
|
||||
hostname = self._params['hostname'] # Or if hostname is not included...
|
||||
|
||||
version = self._params.get('version', '0.0.0')
|
||||
if version < '3.6.0':
|
||||
return Client.result(error='Client version not supported.\n Please, upgrade it.')
|
||||
|
||||
srcIp = self._request.ip
|
||||
|
||||
# Ip is optional,
|
||||
@@ -127,7 +146,13 @@ class Client(Handler):
|
||||
except Exception:
|
||||
raise RequestError('Invalid request')
|
||||
|
||||
logger.debug('Got Ticket: %s, scrambled: %s, Hostname: %s, Ip: %s', ticket, scrambler, hostname, srcIp)
|
||||
logger.debug(
|
||||
'Got Ticket: %s, scrambled: %s, Hostname: %s, Ip: %s',
|
||||
ticket,
|
||||
scrambler,
|
||||
hostname,
|
||||
srcIp,
|
||||
)
|
||||
|
||||
try:
|
||||
data = TicketStore.get(ticket)
|
||||
@@ -138,33 +163,76 @@ class Client(Handler):
|
||||
|
||||
try:
|
||||
logger.debug(data)
|
||||
ip, userService, userServiceInstance, transport, transportInstance = userServiceManager().getService(
|
||||
self._request.user, self._request.os, self._request.ip, data['service'], data['transport'], clientHostname=hostname
|
||||
(
|
||||
ip,
|
||||
userService,
|
||||
userServiceInstance,
|
||||
transport,
|
||||
transportInstance,
|
||||
) = userServiceManager().getService(
|
||||
self._request.user,
|
||||
self._request.os,
|
||||
self._request.ip,
|
||||
data['service'],
|
||||
data['transport'],
|
||||
clientHostname=hostname,
|
||||
)
|
||||
logger.debug(
|
||||
'Res: %s %s %s %s %s',
|
||||
ip,
|
||||
userService,
|
||||
userServiceInstance,
|
||||
transport,
|
||||
transportInstance,
|
||||
)
|
||||
logger.debug('Res: %s %s %s %s %s', ip, userService, userServiceInstance, transport, transportInstance)
|
||||
password = cryptoManager().symDecrpyt(data['password'], scrambler)
|
||||
|
||||
# userService.setConnectionSource(srcIp, hostname) # Store where we are accessing from so we can notify Service
|
||||
if not ip:
|
||||
raise ServiceNotReadyError
|
||||
raise ServiceNotReadyError()
|
||||
|
||||
# Set "accesedByClient"
|
||||
userService.setProperty('accessedByClient', '1')
|
||||
# This should never happen, but it's here just in case
|
||||
if not transportInstance:
|
||||
raise Exception('No transport instance!!!')
|
||||
|
||||
transportScript, signature, params = transportInstance.getEncodedTransportScript(userService, transport, ip, self._request.os, self._request.user, password, self._request)
|
||||
(
|
||||
transportScript,
|
||||
signature,
|
||||
params,
|
||||
) = transportInstance.getEncodedTransportScript(
|
||||
userService,
|
||||
transport,
|
||||
ip,
|
||||
self._request.os,
|
||||
self._request.user,
|
||||
password,
|
||||
self._request,
|
||||
)
|
||||
|
||||
logger.debug('Signature: %s', signature)
|
||||
logger.debug('Data:#######\n%s\n###########', params)
|
||||
|
||||
return Client.result(result={
|
||||
'script': transportScript,
|
||||
'signature': signature, # It is already on base64
|
||||
'params': codecs.encode(codecs.encode(json.dumps(params).encode(), 'bz2'), 'base64').decode(),
|
||||
})
|
||||
return Client.result(
|
||||
result={
|
||||
'script': transportScript,
|
||||
'signature': signature, # It is already on base64
|
||||
'params': codecs.encode(
|
||||
codecs.encode(json.dumps(params).encode(), 'bz2'), 'base64'
|
||||
).decode(),
|
||||
}
|
||||
)
|
||||
except ServiceNotReadyError as e:
|
||||
# Refresh ticket and make this retrayable
|
||||
TicketStore.revalidate(ticket, 20) # Retry will be in at most 5 seconds, so 20 is fine :)
|
||||
return Client.result(error=errors.SERVICE_IN_PREPARATION, errorCode=e.code, retryable=True)
|
||||
TicketStore.revalidate(
|
||||
ticket, 20
|
||||
) # Retry will be in at most 5 seconds, so 20 is fine :)
|
||||
return Client.result(
|
||||
error=errors.SERVICE_IN_PREPARATION, errorCode=e.code, retryable=True
|
||||
)
|
||||
except Exception as e:
|
||||
logger.exception("Exception")
|
||||
return Client.result(error=str(e))
|
||||
|
||||
finally:
|
||||
if userService:
|
||||
userService.setProperty('accessedByClient', '1')
|
||||
|
@@ -42,9 +42,15 @@ logger = logging.getLogger(__name__)
|
||||
# Pair of section/value removed from current UDS version
|
||||
REMOVED = {
|
||||
'UDS': (
|
||||
'allowPreferencesAccess', 'customHtmlLogin', 'UDS Theme',
|
||||
'UDS Theme Enhaced', 'css', 'allowPreferencesAccess',
|
||||
'loginUrl', 'maxLoginTries', 'loginBlockTime'
|
||||
'allowPreferencesAccess',
|
||||
'customHtmlLogin',
|
||||
'UDS Theme',
|
||||
'UDS Theme Enhaced',
|
||||
'css',
|
||||
'allowPreferencesAccess',
|
||||
'loginUrl',
|
||||
'maxLoginTries',
|
||||
'loginBlockTime',
|
||||
),
|
||||
'Cluster': ('Destination CPU Load', 'Migration CPU Load', 'Migration Free Memory'),
|
||||
'IPAUTH': ('autoLogin',),
|
||||
@@ -81,7 +87,7 @@ class Config(Handler):
|
||||
'crypt': cfg.isCrypted(),
|
||||
'longText': cfg.isLongText(),
|
||||
'type': cfg.getType(),
|
||||
'params': cfg.getParams()
|
||||
'params': cfg.getParams(),
|
||||
}
|
||||
logger.debug('Configuration: %s', res)
|
||||
return res
|
||||
|
@@ -88,7 +88,11 @@ class Connection(Handler):
|
||||
# Ensure user is present on request, used by web views methods
|
||||
self._request.user = self._user
|
||||
|
||||
return Connection.result(result=getServicesData(typing.cast(ExtendedHttpRequestWithUser, self._request)))
|
||||
return Connection.result(
|
||||
result=services.getServicesData(
|
||||
typing.cast(ExtendedHttpRequestWithUser, self._request)
|
||||
)
|
||||
)
|
||||
|
||||
def connection(self, doNotCheck: bool = False):
|
||||
idService = self._args[0]
|
||||
@@ -152,7 +156,7 @@ class Connection(Handler):
|
||||
self._request.ip, hostname
|
||||
) # Store where we are accessing from so we can notify Service
|
||||
|
||||
if not ip:
|
||||
if not ip or not transportInstance:
|
||||
raise ServiceNotReadyError()
|
||||
|
||||
transportScript = transportInstance.getEncodedTransportScript(
|
||||
@@ -183,7 +187,9 @@ class Connection(Handler):
|
||||
self._request.user = self._user # type: ignore
|
||||
self._request._cryptedpass = self._session['REST']['password'] # type: ignore
|
||||
self._request._scrambler = self._request.META['HTTP_SCRAMBLER'] # type: ignore
|
||||
linkInfo = services.enableService(self._request, idService=self._args[0], idTransport=self._args[1])
|
||||
linkInfo = services.enableService(
|
||||
self._request, idService=self._args[0], idTransport=self._args[1]
|
||||
)
|
||||
if linkInfo['error']:
|
||||
return Connection.result(error=linkInfo['error'])
|
||||
return Connection.result(result=linkInfo['url'])
|
||||
|
@@ -49,19 +49,27 @@ class Images(ModelHandler):
|
||||
"""
|
||||
Handles the gallery REST interface
|
||||
"""
|
||||
|
||||
path = 'gallery'
|
||||
model = Image
|
||||
save_fields = ['name', 'data']
|
||||
|
||||
table_title = _('Image Gallery')
|
||||
table_fields = [
|
||||
{'thumb': {'title': _('Image'), 'visible': True, 'type': 'image', 'width': '96px'}},
|
||||
{
|
||||
'thumb': {
|
||||
'title': _('Image'),
|
||||
'visible': True,
|
||||
'type': 'image',
|
||||
'width': '96px',
|
||||
}
|
||||
},
|
||||
{'name': {'title': _('Name')}},
|
||||
{'size': {'title': _('Size')}},
|
||||
]
|
||||
|
||||
def beforeSave(self, fields: typing.Dict[str, typing.Any]) -> None:
|
||||
fields['data'] = Image.prepareForDb(Image.decode64(fields['data'].encode('utf8')))
|
||||
fields['data'] = Image.prepareForDb(Image.decode64(fields['data']))
|
||||
|
||||
def afterSave(self, item: Image) -> None:
|
||||
# Updates the thumbnail and re-saves it
|
||||
@@ -69,17 +77,17 @@ class Images(ModelHandler):
|
||||
item.updateThumbnail()
|
||||
item.save()
|
||||
|
||||
|
||||
def getGui(self, type_: str) -> typing.List[typing.Any]:
|
||||
return self.addField(
|
||||
self.addDefaultFields([], ['name']), {
|
||||
self.addDefaultFields([], ['name']),
|
||||
{
|
||||
'name': 'data',
|
||||
'value': '',
|
||||
'label': ugettext('Image'),
|
||||
'tooltip': ugettext('Image object'),
|
||||
'type': gui.InputField.IMAGECHOICE_TYPE,
|
||||
'order': 100, # At end
|
||||
}
|
||||
},
|
||||
)
|
||||
|
||||
def item_as_dict(self, item: Image) -> typing.Dict[str, typing.Any]:
|
||||
@@ -92,7 +100,9 @@ class Images(ModelHandler):
|
||||
def item_as_dict_overview(self, item: Image) -> typing.Dict[str, typing.Any]:
|
||||
return {
|
||||
'id': item.uuid,
|
||||
'size': '{}x{}, {} bytes (thumb {} bytes)'.format(item.width, item.height, len(item.data), len(item.thumb)),
|
||||
'size': '{}x{}, {} bytes (thumb {} bytes)'.format(
|
||||
item.width, item.height, len(item.data), len(item.thumb)
|
||||
),
|
||||
'name': item.name,
|
||||
'thumb': item.thumb64,
|
||||
}
|
||||
|
@@ -53,15 +53,22 @@ logger = logging.getLogger(__name__)
|
||||
|
||||
# Enclosed methods under /auth path
|
||||
|
||||
|
||||
class Login(Handler):
|
||||
"""
|
||||
Responsible of user authentication
|
||||
"""
|
||||
|
||||
path = 'auth'
|
||||
authenticated = False # Public method
|
||||
|
||||
@staticmethod
|
||||
def result(result: str = 'error', token: str = None, scrambler: str = None, error: str = None) -> typing.MutableMapping[str, typing.Any]:
|
||||
def result(
|
||||
result: str = 'error',
|
||||
token: str = None,
|
||||
scrambler: str = None,
|
||||
error: str = None,
|
||||
) -> typing.MutableMapping[str, typing.Any]:
|
||||
res = {
|
||||
'result': result,
|
||||
'token': token,
|
||||
@@ -109,15 +116,31 @@ class Login(Handler):
|
||||
cache = Cache('RESTapi')
|
||||
fails = cache.get(self._request.ip) or 0
|
||||
if fails > ALLOWED_FAILS:
|
||||
logger.info('Access to REST API %s is blocked for %s seconds since last fail', self._request.ip, GlobalConfig.LOGIN_BLOCK.getInt())
|
||||
|
||||
logger.info(
|
||||
'Access to REST API %s is blocked for %s seconds since last fail',
|
||||
self._request.ip,
|
||||
GlobalConfig.LOGIN_BLOCK.getInt(),
|
||||
)
|
||||
|
||||
try:
|
||||
if 'auth_id' not in self._params and 'authId' not in self._params and 'authSmallName' not in self._params and 'auth' not in self._params:
|
||||
if (
|
||||
'auth_id' not in self._params
|
||||
and 'authId' not in self._params
|
||||
and 'authSmallName' not in self._params
|
||||
and 'auth' not in self._params
|
||||
):
|
||||
raise RequestError('Invalid parameters (no auth)')
|
||||
|
||||
scrambler: str = ''.join(random.SystemRandom().choice(string.ascii_letters + string.digits) for _ in range(32)) # @UndefinedVariable
|
||||
authId: typing.Optional[str] = self._params.get('authId', self._params.get('auth_id', None))
|
||||
authSmallName: typing.Optional[str] = self._params.get('authSmallName', None)
|
||||
scrambler: str = ''.join(
|
||||
random.SystemRandom().choice(string.ascii_letters + string.digits)
|
||||
for _ in range(32)
|
||||
) # @UndefinedVariable
|
||||
authId: typing.Optional[str] = self._params.get(
|
||||
'authId', self._params.get('auth_id', None)
|
||||
)
|
||||
authSmallName: typing.Optional[str] = self._params.get(
|
||||
'authSmallName', None
|
||||
)
|
||||
authName: typing.Optional[str] = self._params.get('auth', None)
|
||||
platform: str = self._params.get('platform', self._request.os)
|
||||
|
||||
@@ -126,9 +149,18 @@ class Login(Handler):
|
||||
|
||||
username, password = self._params['username'], self._params['password']
|
||||
locale: str = self._params.get('locale', 'en')
|
||||
if authName == 'admin' or authSmallName == 'admin' or authId == '00000000-0000-0000-0000-000000000000':
|
||||
if GlobalConfig.SUPER_USER_LOGIN.get(True) == username and GlobalConfig.SUPER_USER_PASS.get(True) == password:
|
||||
self.genAuthToken(-1, username, password, locale, platform, True, True, scrambler)
|
||||
if (
|
||||
authName == 'admin'
|
||||
or authSmallName == 'admin'
|
||||
or authId == '00000000-0000-0000-0000-000000000000'
|
||||
):
|
||||
if (
|
||||
GlobalConfig.SUPER_USER_LOGIN.get(True) == username
|
||||
and GlobalConfig.SUPER_USER_PASS.get(True) == password
|
||||
):
|
||||
self.genAuthToken(
|
||||
-1, username, password, locale, platform, True, True, scrambler
|
||||
)
|
||||
return Login.result(result='ok', token=self.getAuthToken())
|
||||
return Login.result(error='Invalid credentials')
|
||||
|
||||
@@ -149,13 +181,24 @@ class Login(Handler):
|
||||
# Sleep a while here to "prottect"
|
||||
time.sleep(3) # Wait 3 seconds if credentials fails for "protection"
|
||||
# And store in cache for blocking for a while if fails
|
||||
cache.put(self._request.ip, fails+1, GlobalConfig.LOGIN_BLOCK.getInt())
|
||||
|
||||
cache.put(
|
||||
self._request.ip, fails + 1, GlobalConfig.LOGIN_BLOCK.getInt()
|
||||
)
|
||||
|
||||
return Login.result(error='Invalid credentials')
|
||||
return Login.result(
|
||||
result='ok',
|
||||
token=self.genAuthToken(auth.id, user.name, password, locale, platform, user.is_admin, user.staff_member, scrambler),
|
||||
scrambler=scrambler
|
||||
token=self.genAuthToken(
|
||||
auth.id,
|
||||
user.name,
|
||||
password,
|
||||
locale,
|
||||
platform,
|
||||
user.is_admin,
|
||||
user.staff_member,
|
||||
scrambler,
|
||||
),
|
||||
scrambler=scrambler,
|
||||
)
|
||||
|
||||
except Exception:
|
||||
@@ -169,6 +212,7 @@ class Logout(Handler):
|
||||
"""
|
||||
Responsible of user de-authentication
|
||||
"""
|
||||
|
||||
path = 'auth'
|
||||
authenticated = True # By default, all handlers needs authentication
|
||||
|
||||
@@ -190,14 +234,16 @@ class Auths(Handler):
|
||||
auth: Authenticator
|
||||
for auth in Authenticator.objects.all():
|
||||
theType = auth.getType()
|
||||
if paramAll or (theType.isCustom() is False and theType.typeType not in ('IP',)):
|
||||
if paramAll or (
|
||||
theType.isCustom() is False and theType.typeType not in ('IP',)
|
||||
):
|
||||
yield {
|
||||
'authId': auth.uuid,
|
||||
'authSmallName': str(auth.small_name),
|
||||
'auth': auth.name,
|
||||
'type': theType.typeType,
|
||||
'priority': auth.priority,
|
||||
'isCustom': theType.isCustom()
|
||||
'isCustom': theType.isCustom(),
|
||||
}
|
||||
|
||||
def get(self):
|
||||
|
@@ -54,6 +54,7 @@ class MetaPools(ModelHandler):
|
||||
"""
|
||||
Handles Services Pools REST requests
|
||||
"""
|
||||
|
||||
model = MetaPool
|
||||
detail = {
|
||||
'pools': MetaServicesPool,
|
||||
@@ -62,8 +63,18 @@ class MetaPools(ModelHandler):
|
||||
'access': AccessCalendars,
|
||||
}
|
||||
|
||||
save_fields = ['name', 'short_name', 'comments', 'tags',
|
||||
'image_id', 'servicesPoolGroup_id', 'visible', 'policy', 'calendar_message', 'transport_grouping']
|
||||
save_fields = [
|
||||
'name',
|
||||
'short_name',
|
||||
'comments',
|
||||
'tags',
|
||||
'image_id',
|
||||
'servicesPoolGroup_id',
|
||||
'visible',
|
||||
'policy',
|
||||
'calendar_message',
|
||||
'transport_grouping',
|
||||
]
|
||||
|
||||
table_title = _('Meta Pools')
|
||||
table_fields = [
|
||||
@@ -93,8 +104,16 @@ class MetaPools(ModelHandler):
|
||||
poolGroupThumb = item.servicesPoolGroup.image.thumb64
|
||||
|
||||
allPools = item.members.all()
|
||||
userServicesCount = sum((i.pool.userServices.exclude(state__in=State.INFO_STATES).count() for i in allPools))
|
||||
userServicesInPreparation = sum((i.pool.userServices.filter(state=State.PREPARING).count()) for i in allPools)
|
||||
userServicesCount = sum(
|
||||
(
|
||||
i.pool.userServices.exclude(state__in=State.INFO_STATES).count()
|
||||
for i in allPools
|
||||
)
|
||||
)
|
||||
userServicesInPreparation = sum(
|
||||
(i.pool.userServices.filter(state=State.PREPARING).count())
|
||||
for i in allPools
|
||||
)
|
||||
|
||||
val = {
|
||||
'id': item.uuid,
|
||||
@@ -102,7 +121,9 @@ class MetaPools(ModelHandler):
|
||||
'short_name': item.short_name,
|
||||
'tags': [tag.tag for tag in item.tags.all()],
|
||||
'comments': item.comments,
|
||||
'thumb': item.image.thumb64 if item.image is not None else DEFAULT_THUMB_BASE64,
|
||||
'thumb': item.image.thumb64
|
||||
if item.image is not None
|
||||
else DEFAULT_THUMB_BASE64,
|
||||
'image_id': item.image.uuid if item.image is not None else None,
|
||||
'servicesPoolGroup_id': poolGroupId,
|
||||
'pool_group_name': poolGroupName,
|
||||
@@ -114,7 +135,7 @@ class MetaPools(ModelHandler):
|
||||
'fallbackAccess': item.fallbackAccess,
|
||||
'permission': permissions.getEffectivePermission(self._user, item),
|
||||
'calendar_message': item.calendar_message,
|
||||
'transport_grouping': item.transport_grouping
|
||||
'transport_grouping': item.transport_grouping,
|
||||
}
|
||||
|
||||
return val
|
||||
@@ -123,30 +144,50 @@ class MetaPools(ModelHandler):
|
||||
def getGui(self, type_: str) -> typing.List[typing.Any]:
|
||||
localGUI = self.addDefaultFields([], ['name', 'short_name', 'comments', 'tags'])
|
||||
|
||||
for field in [{
|
||||
for field in [
|
||||
{
|
||||
'name': 'policy',
|
||||
'values': [gui.choiceItem(k, str(v)) for k, v in MetaPool.TYPES.items()],
|
||||
'values': [
|
||||
gui.choiceItem(k, str(v)) for k, v in MetaPool.TYPES.items()
|
||||
],
|
||||
'label': ugettext('Policy'),
|
||||
'tooltip': ugettext('Service pool policy'),
|
||||
'type': gui.InputField.CHOICE_TYPE,
|
||||
'order': 100,
|
||||
}, {
|
||||
},
|
||||
{
|
||||
'name': 'image_id',
|
||||
'values': [gui.choiceImage(-1, '--------', DEFAULT_THUMB_BASE64)] + gui.sortedChoices([gui.choiceImage(v.uuid, v.name, v.thumb64) for v in Image.objects.all()]),
|
||||
'values': [gui.choiceImage(-1, '--------', DEFAULT_THUMB_BASE64)]
|
||||
+ gui.sortedChoices(
|
||||
[
|
||||
gui.choiceImage(v.uuid, v.name, v.thumb64)
|
||||
for v in Image.objects.all()
|
||||
]
|
||||
),
|
||||
'label': ugettext('Associated Image'),
|
||||
'tooltip': ugettext('Image assocciated with this service'),
|
||||
'type': gui.InputField.IMAGECHOICE_TYPE,
|
||||
'order': 120,
|
||||
'tab': gui.DISPLAY_TAB,
|
||||
}, {
|
||||
},
|
||||
{
|
||||
'name': 'servicesPoolGroup_id',
|
||||
'values': [gui.choiceImage(-1, _('Default'), DEFAULT_THUMB_BASE64)] + gui.sortedChoices([gui.choiceImage(v.uuid, v.name, v.thumb64) for v in ServicePoolGroup.objects.all()]),
|
||||
'values': [gui.choiceImage(-1, _('Default'), DEFAULT_THUMB_BASE64)]
|
||||
+ gui.sortedChoices(
|
||||
[
|
||||
gui.choiceImage(v.uuid, v.name, v.thumb64)
|
||||
for v in ServicePoolGroup.objects.all()
|
||||
]
|
||||
),
|
||||
'label': ugettext('Pool group'),
|
||||
'tooltip': ugettext('Pool group for this pool (for pool classify on display)'),
|
||||
'tooltip': ugettext(
|
||||
'Pool group for this pool (for pool classify on display)'
|
||||
),
|
||||
'type': gui.InputField.IMAGECHOICE_TYPE,
|
||||
'order': 121,
|
||||
'tab': gui.DISPLAY_TAB,
|
||||
}, {
|
||||
},
|
||||
{
|
||||
'name': 'visible',
|
||||
'value': True,
|
||||
'label': ugettext('Visible'),
|
||||
@@ -154,23 +195,31 @@ class MetaPools(ModelHandler):
|
||||
'type': gui.InputField.CHECKBOX_TYPE,
|
||||
'order': 123,
|
||||
'tab': gui.DISPLAY_TAB,
|
||||
}, {
|
||||
},
|
||||
{
|
||||
'name': 'calendar_message',
|
||||
'value': '',
|
||||
'label': ugettext('Calendar access denied text'),
|
||||
'tooltip': ugettext('Custom message to be shown to users if access is limited by calendar rules.'),
|
||||
'tooltip': ugettext(
|
||||
'Custom message to be shown to users if access is limited by calendar rules.'
|
||||
),
|
||||
'type': gui.InputField.TEXT_TYPE,
|
||||
'order': 124,
|
||||
'tab': gui.DISPLAY_TAB,
|
||||
}, {
|
||||
},
|
||||
{
|
||||
'name': 'transport_grouping',
|
||||
'values': [gui.choiceItem(k, str(v)) for k, v in MetaPool.TRANSPORT_SELECT.items()],
|
||||
'values': [
|
||||
gui.choiceItem(k, str(v))
|
||||
for k, v in MetaPool.TRANSPORT_SELECT.items()
|
||||
],
|
||||
'label': ugettext('Transport Selection'),
|
||||
'tooltip': ugettext('Transport selection policy'),
|
||||
'type': gui.InputField.CHOICE_TYPE,
|
||||
'order': 125,
|
||||
'tab': gui.DISPLAY_TAB
|
||||
}]:
|
||||
'tab': gui.DISPLAY_TAB,
|
||||
},
|
||||
]:
|
||||
self.addField(localGUI, field)
|
||||
|
||||
return localGUI
|
||||
|
@@ -154,7 +154,7 @@ class MetaAssignedService(DetailHandler):
|
||||
return UserService.objects.filter(
|
||||
uuid=processUuid(userServiceId),
|
||||
cache_level=0,
|
||||
deployed_service__meta=metaPool,
|
||||
deployed_service__in=[i.pool for i in metaPool.members.all()],
|
||||
)[0]
|
||||
except Exception:
|
||||
pass
|
||||
|
@@ -52,12 +52,20 @@ class Networks(ModelHandler):
|
||||
Processes REST requests about networks
|
||||
Implements specific handling for network related requests using GUI
|
||||
"""
|
||||
|
||||
model = Network
|
||||
save_fields = ['name', 'net_string', 'tags']
|
||||
|
||||
table_title = _('Networks')
|
||||
table_fields = [
|
||||
{'name': {'title': _('Name'), 'visible': True, 'type': 'icon', 'icon': 'fa fa-globe text-success'}},
|
||||
{
|
||||
'name': {
|
||||
'title': _('Name'),
|
||||
'visible': True,
|
||||
'type': 'icon',
|
||||
'icon': 'fa fa-globe text-success',
|
||||
}
|
||||
},
|
||||
{'net_string': {'title': _('Range')}},
|
||||
{'networks_count': {'title': _('Used by'), 'type': 'numeric', 'width': '8em'}},
|
||||
{'tags': {'title': _('tags'), 'visible': False}},
|
||||
@@ -75,14 +83,17 @@ class Networks(ModelHandler):
|
||||
|
||||
def getGui(self, type_: str) -> typing.List[typing.Any]:
|
||||
return self.addField(
|
||||
self.addDefaultFields([], ['name', 'tags']), {
|
||||
self.addDefaultFields([], ['name', 'tags']),
|
||||
{
|
||||
'name': 'net_string',
|
||||
'value': '',
|
||||
'label': ugettext('Network range'),
|
||||
'tooltip': ugettext('Network range. Accepts most network definitions formats (range, subnet, host, etc...'),
|
||||
'tooltip': ugettext(
|
||||
'Network range. Accepts most network definitions formats (range, subnet, host, etc...'
|
||||
),
|
||||
'type': gui.InputField.TEXT_TYPE,
|
||||
'order': 100, # At end
|
||||
}
|
||||
},
|
||||
)
|
||||
|
||||
def item_as_dict(self, item: Network) -> typing.Dict[str, typing.Any]:
|
||||
@@ -92,5 +103,5 @@ class Networks(ModelHandler):
|
||||
'tags': [tag.tag for tag in item.tags.all()],
|
||||
'net_string': item.net_string,
|
||||
'networks_count': item.transports.count(),
|
||||
'permission': permissions.getEffectivePermission(self._user, item)
|
||||
'permission': permissions.getEffectivePermission(self._user, item),
|
||||
}
|
||||
|
@@ -52,6 +52,7 @@ logger = logging.getLogger(__name__)
|
||||
ALLOW = 'ALLOW'
|
||||
DENY = 'DENY'
|
||||
|
||||
|
||||
class AccessCalendars(DetailHandler):
|
||||
@staticmethod
|
||||
def as_dict(item: 'CalendarAccess'):
|
||||
@@ -67,7 +68,9 @@ class AccessCalendars(DetailHandler):
|
||||
try:
|
||||
if not item:
|
||||
return [AccessCalendars.as_dict(i) for i in parent.calendarAccess.all()]
|
||||
return AccessCalendars.as_dict(parent.calendarAccess.get(uuid=processUuid(item)))
|
||||
return AccessCalendars.as_dict(
|
||||
parent.calendarAccess.get(uuid=processUuid(item))
|
||||
)
|
||||
except Exception:
|
||||
logger.exception('err: %s', item)
|
||||
raise self.invalidItemException()
|
||||
@@ -87,7 +90,9 @@ class AccessCalendars(DetailHandler):
|
||||
uuid = processUuid(item) if item is not None else None
|
||||
|
||||
try:
|
||||
calendar: Calendar = Calendar.objects.get(uuid=processUuid(self._params['calendarId']))
|
||||
calendar: Calendar = Calendar.objects.get(
|
||||
uuid=processUuid(self._params['calendarId'])
|
||||
)
|
||||
access: str = self._params['access'].upper()
|
||||
if access not in (ALLOW, DENY):
|
||||
raise Exception()
|
||||
@@ -103,13 +108,24 @@ class AccessCalendars(DetailHandler):
|
||||
calAccess.priority = priority
|
||||
calAccess.save()
|
||||
else:
|
||||
parent.calendarAccess.create(calendar=calendar, access=access, priority=priority)
|
||||
parent.calendarAccess.create(
|
||||
calendar=calendar, access=access, priority=priority
|
||||
)
|
||||
|
||||
log.doLog(parent, log.INFO, "Added access calendar {}/{} by {}".format(calendar.name, access, self._user.pretty_name), log.ADMIN)
|
||||
log.doLog(
|
||||
parent,
|
||||
log.INFO,
|
||||
"Added access calendar {}/{} by {}".format(
|
||||
calendar.name, access, self._user.pretty_name
|
||||
),
|
||||
log.ADMIN,
|
||||
)
|
||||
|
||||
def deleteItem(self, parent: 'ServicePool', item: str) -> None:
|
||||
calendarAccess = parent.calendarAccess.get(uuid=processUuid(self._args[0]))
|
||||
logStr = "Removed access calendar {} by {}".format(calendarAccess.calendar.name, self._user.pretty_name)
|
||||
logStr = "Removed access calendar {} by {}".format(
|
||||
calendarAccess.calendar.name, self._user.pretty_name
|
||||
)
|
||||
|
||||
calendarAccess.delete()
|
||||
|
||||
@@ -120,7 +136,10 @@ class ActionsCalendars(DetailHandler):
|
||||
"""
|
||||
Processes the transports detail requests of a Service Pool
|
||||
"""
|
||||
custom_methods = ['execute',]
|
||||
|
||||
custom_methods = [
|
||||
'execute',
|
||||
]
|
||||
|
||||
@staticmethod
|
||||
def as_dict(item: 'CalendarAction') -> typing.Dict[str, typing.Any]:
|
||||
@@ -131,19 +150,21 @@ class ActionsCalendars(DetailHandler):
|
||||
'calendarId': item.calendar.uuid,
|
||||
'calendar': item.calendar.name,
|
||||
'action': item.action,
|
||||
'actionDescription': action.get('description'),
|
||||
'actionDescription': action.get('description'),
|
||||
'atStart': item.at_start,
|
||||
'eventsOffset': item.events_offset,
|
||||
'params': params,
|
||||
'pretty_params': item.prettyParams,
|
||||
'nextExecution': item.next_execution,
|
||||
'lastExecution': item.last_execution
|
||||
'lastExecution': item.last_execution,
|
||||
}
|
||||
|
||||
def getItems(self, parent: 'ServicePool', item: typing.Optional[str]):
|
||||
try:
|
||||
if item is None:
|
||||
return [ActionsCalendars.as_dict(i) for i in parent.calendaraction_set.all()]
|
||||
return [
|
||||
ActionsCalendars.as_dict(i) for i in parent.calendaraction_set.all()
|
||||
]
|
||||
i = parent.calendaraction_set.get(uuid=processUuid(item))
|
||||
return ActionsCalendars.as_dict(i)
|
||||
except Exception:
|
||||
@@ -177,8 +198,12 @@ class ActionsCalendars(DetailHandler):
|
||||
|
||||
# logger.debug('Got parameters: {} {} {} {} ----> {}'.format(calendar, action, eventsOffset, atStart, params))
|
||||
logStr = "Added scheduled action \"{},{},{},{},{}\" by {}".format(
|
||||
calendar.name, action, eventsOffset,
|
||||
atStart and 'Start' or 'End', params, self._user.pretty_name
|
||||
calendar.name,
|
||||
action,
|
||||
eventsOffset,
|
||||
atStart and 'Start' or 'End',
|
||||
params,
|
||||
self._user.pretty_name,
|
||||
)
|
||||
|
||||
if uuid is not None:
|
||||
@@ -191,16 +216,26 @@ class ActionsCalendars(DetailHandler):
|
||||
calAction.params = params
|
||||
calAction.save()
|
||||
else:
|
||||
CalendarAction.objects.create(calendar=calendar, service_pool=parent, action=action, at_start=atStart, events_offset=eventsOffset, params=params)
|
||||
CalendarAction.objects.create(
|
||||
calendar=calendar,
|
||||
service_pool=parent,
|
||||
action=action,
|
||||
at_start=atStart,
|
||||
events_offset=eventsOffset,
|
||||
params=params,
|
||||
)
|
||||
|
||||
log.doLog(parent, log.INFO, logStr, log.ADMIN)
|
||||
|
||||
def deleteItem(self, parent: 'ServicePool', item: str) -> None:
|
||||
calendarAction = CalendarAction.objects.get(uuid=processUuid(self._args[0]))
|
||||
logStr = "Removed scheduled action \"{},{},{},{},{}\" by {}".format(
|
||||
calendarAction.calendar.name, calendarAction.action,
|
||||
calendarAction.events_offset, calendarAction.at_start and 'Start' or 'End', calendarAction.params,
|
||||
self._user.pretty_name
|
||||
calendarAction.calendar.name,
|
||||
calendarAction.action,
|
||||
calendarAction.events_offset,
|
||||
calendarAction.at_start and 'Start' or 'End',
|
||||
calendarAction.params,
|
||||
self._user.pretty_name,
|
||||
)
|
||||
|
||||
calendarAction.delete()
|
||||
@@ -213,11 +248,14 @@ class ActionsCalendars(DetailHandler):
|
||||
calendarAction: CalendarAction = CalendarAction.objects.get(uuid=uuid)
|
||||
self.ensureAccess(calendarAction, permissions.PERMISSION_MANAGEMENT)
|
||||
logStr = "Launched scheduled action \"{},{},{},{},{}\" by {}".format(
|
||||
calendarAction.calendar.name, calendarAction.action,
|
||||
calendarAction.events_offset, calendarAction.at_start and 'Start' or 'End', calendarAction.params,
|
||||
self._user.pretty_name
|
||||
calendarAction.calendar.name,
|
||||
calendarAction.action,
|
||||
calendarAction.events_offset,
|
||||
calendarAction.at_start and 'Start' or 'End',
|
||||
calendarAction.params,
|
||||
self._user.pretty_name,
|
||||
)
|
||||
|
||||
|
||||
calendarAction.execute()
|
||||
|
||||
log.doLog(parent, log.INFO, logStr, log.ADMIN)
|
||||
|
@@ -70,7 +70,7 @@ class OsManagers(ModelHandler):
|
||||
'type_name': type_.name(),
|
||||
'servicesTypes': type_.servicesType,
|
||||
'comments': osm.comments,
|
||||
'permission': permissions.getEffectivePermission(self._user, osm)
|
||||
'permission': permissions.getEffectivePermission(self._user, osm),
|
||||
}
|
||||
|
||||
def item_as_dict(self, item: OSManager) -> typing.Dict[str, typing.Any]:
|
||||
@@ -79,7 +79,9 @@ class OsManagers(ModelHandler):
|
||||
def checkDelete(self, item: OSManager) -> None:
|
||||
# Only can delete if no ServicePools attached
|
||||
if item.deployedServices.count() > 0:
|
||||
raise RequestError(ugettext('Can\'t delete an OS Manager with services pools associated'))
|
||||
raise RequestError(
|
||||
ugettext('Can\'t delete an OS Manager with services pools associated')
|
||||
)
|
||||
|
||||
# Types related
|
||||
def enum_types(self) -> typing.Iterable[typing.Type[osmanagers.OSManager]]:
|
||||
@@ -88,6 +90,9 @@ class OsManagers(ModelHandler):
|
||||
# Gui related
|
||||
def getGui(self, type_: str) -> typing.List[typing.Any]:
|
||||
try:
|
||||
return self.addDefaultFields(osmanagers.factory().lookup(type_).guiDescription(), ['name', 'comments', 'tags'])
|
||||
return self.addDefaultFields(
|
||||
osmanagers.factory().lookup(type_).guiDescription(), # type: ignore # may raise an exception if lookup fails
|
||||
['name', 'comments', 'tags'],
|
||||
)
|
||||
except:
|
||||
raise NotFound('type not found')
|
||||
|
@@ -50,6 +50,7 @@ class Proxies(ModelHandler):
|
||||
"""
|
||||
Processes REST requests about proxys
|
||||
"""
|
||||
|
||||
model = Proxy
|
||||
|
||||
save_fields = ['name', 'host', 'port', 'ssl', 'check_cert', 'comments', 'tags']
|
||||
@@ -74,42 +75,49 @@ class Proxies(ModelHandler):
|
||||
'port': item.port,
|
||||
'ssl': item.ssl,
|
||||
'check_cert': item.check_cert,
|
||||
'permission': permissions.getEffectivePermission(self._user, item)
|
||||
'permission': permissions.getEffectivePermission(self._user, item),
|
||||
}
|
||||
|
||||
def getGui(self, type_: str) -> typing.List[typing.Any]:
|
||||
g = self.addDefaultFields([], ['name', 'comments', 'tags'])
|
||||
|
||||
for f in [
|
||||
{
|
||||
'name': 'host',
|
||||
'value': '',
|
||||
'label': ugettext('Host'),
|
||||
'tooltip': ugettext('Server (IP or FQDN) that will serve as proxy.'),
|
||||
'type': gui.InputField.TEXT_TYPE,
|
||||
'order': 110,
|
||||
}, {
|
||||
'name': 'port',
|
||||
'value': '9090',
|
||||
'minValue': '0',
|
||||
'label': ugettext('Port'),
|
||||
'tooltip': ugettext('Port of proxy server'),
|
||||
'type': gui.InputField.NUMERIC_TYPE,
|
||||
'order': 111,
|
||||
}, {
|
||||
'name': 'ssl',
|
||||
'value': True,
|
||||
'label': ugettext('Use SSL'),
|
||||
'tooltip': ugettext('If active, the proxied connections will be done using HTTPS'),
|
||||
'type': gui.InputField.CHECKBOX_TYPE,
|
||||
}, {
|
||||
'name': 'check_cert',
|
||||
'value': True,
|
||||
'label': ugettext('Check Certificate'),
|
||||
'tooltip': ugettext('If active, any SSL certificate will be checked (will not allow self signed certificates on proxy)'),
|
||||
'type': gui.InputField.CHECKBOX_TYPE,
|
||||
},
|
||||
]:
|
||||
{
|
||||
'name': 'host',
|
||||
'value': '',
|
||||
'label': ugettext('Host'),
|
||||
'tooltip': ugettext('Server (IP or FQDN) that will serve as proxy.'),
|
||||
'type': gui.InputField.TEXT_TYPE,
|
||||
'order': 110,
|
||||
},
|
||||
{
|
||||
'name': 'port',
|
||||
'value': '9090',
|
||||
'minValue': '0',
|
||||
'label': ugettext('Port'),
|
||||
'tooltip': ugettext('Port of proxy server'),
|
||||
'type': gui.InputField.NUMERIC_TYPE,
|
||||
'order': 111,
|
||||
},
|
||||
{
|
||||
'name': 'ssl',
|
||||
'value': True,
|
||||
'label': ugettext('Use SSL'),
|
||||
'tooltip': ugettext(
|
||||
'If active, the proxied connections will be done using HTTPS'
|
||||
),
|
||||
'type': gui.InputField.CHECKBOX_TYPE,
|
||||
},
|
||||
{
|
||||
'name': 'check_cert',
|
||||
'value': True,
|
||||
'label': ugettext('Check Certificate'),
|
||||
'tooltip': ugettext(
|
||||
'If active, any SSL certificate will be checked (will not allow self signed certificates on proxy)'
|
||||
),
|
||||
'type': gui.InputField.CHECKBOX_TYPE,
|
||||
},
|
||||
]:
|
||||
self.addField(g, f)
|
||||
|
||||
return g
|
||||
|
@@ -41,7 +41,17 @@ from uds import reports
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
VALID_PARAMS = ('authId', 'authSmallName', 'auth', 'username', 'realname', 'password', 'groups', 'servicePool', 'transport')
|
||||
VALID_PARAMS = (
|
||||
'authId',
|
||||
'authSmallName',
|
||||
'auth',
|
||||
'username',
|
||||
'realname',
|
||||
'password',
|
||||
'groups',
|
||||
'servicePool',
|
||||
'transport',
|
||||
)
|
||||
|
||||
|
||||
# Enclosed methods under /actor path
|
||||
@@ -49,14 +59,21 @@ class Reports(model.BaseModelHandler):
|
||||
"""
|
||||
Processes actor requests
|
||||
"""
|
||||
|
||||
needs_admin = True # By default, staff is lower level needed
|
||||
|
||||
table_title = _('Available reports')
|
||||
table_fields = [
|
||||
{'group': {'title': _('Group')}},
|
||||
{'name': {'title': _('Name')}}, # Will process this field on client in fact, not sent by server
|
||||
{'description': {'title': _('Description')}}, # Will process this field on client in fact, not sent by server
|
||||
{'mime_type': {'title': _('Generates')}}, # Will process this field on client in fact, not sent by server
|
||||
{
|
||||
'name': {'title': _('Name')}
|
||||
}, # Will process this field on client in fact, not sent by server
|
||||
{
|
||||
'description': {'title': _('Description')}
|
||||
}, # Will process this field on client in fact, not sent by server
|
||||
{
|
||||
'mime_type': {'title': _('Generates')}
|
||||
}, # Will process this field on client in fact, not sent by server
|
||||
]
|
||||
# Field from where to get "class" and prefix for that class, so this will generate "row-state-A, row-state-X, ....
|
||||
table_row_style = {'field': 'state', 'prefix': 'row-state-'}
|
||||
@@ -85,7 +102,9 @@ class Reports(model.BaseModelHandler):
|
||||
if self._args[0] == model.OVERVIEW:
|
||||
return list(self.getItems())
|
||||
elif self._args[0] == model.TABLEINFO:
|
||||
return self.processTableFields(self.table_title, self.table_fields, self.table_row_style)
|
||||
return self.processTableFields(
|
||||
self.table_title, self.table_fields, self.table_row_style
|
||||
)
|
||||
|
||||
if nArgs == 2:
|
||||
if self._args[0] == model.GUI:
|
||||
@@ -97,7 +116,12 @@ class Reports(model.BaseModelHandler):
|
||||
"""
|
||||
Processes a PUT request
|
||||
"""
|
||||
logger.debug('method PUT for %s, %s, %s', self.__class__.__name__, self._args, self._params)
|
||||
logger.debug(
|
||||
'method PUT for %s, %s, %s',
|
||||
self.__class__.__name__,
|
||||
self._args,
|
||||
self._params,
|
||||
)
|
||||
|
||||
if len(self._args) != 1:
|
||||
raise self.invalidRequestException()
|
||||
@@ -112,7 +136,7 @@ class Reports(model.BaseModelHandler):
|
||||
'mime_type': report.mime_type,
|
||||
'encoded': report.encoded,
|
||||
'filename': report.filename,
|
||||
'data': result
|
||||
'data': result,
|
||||
}
|
||||
|
||||
return data
|
||||
@@ -126,7 +150,9 @@ class Reports(model.BaseModelHandler):
|
||||
return sorted(report.guiDescription(report), key=lambda f: f['gui']['order'])
|
||||
|
||||
# Returns the list of
|
||||
def getItems(self, *args, **kwargs) -> typing.Generator[typing.Dict[str, typing.Any], None, None]:
|
||||
def getItems(
|
||||
self, *args, **kwargs
|
||||
) -> typing.Generator[typing.Dict[str, typing.Any], None, None]:
|
||||
for i in reports.availableReports:
|
||||
yield {
|
||||
'id': i.getUuid(),
|
||||
@@ -134,5 +160,5 @@ class Reports(model.BaseModelHandler):
|
||||
'encoded': i.encoded,
|
||||
'group': i.translated_group(),
|
||||
'name': i.translated_name(),
|
||||
'description': i.translated_description()
|
||||
'description': i.translated_description(),
|
||||
}
|
||||
|
@@ -50,6 +50,7 @@ class ServicesPoolGroups(ModelHandler):
|
||||
"""
|
||||
Handles the gallery REST interface
|
||||
"""
|
||||
|
||||
# needs_admin = True
|
||||
|
||||
path = 'gallery'
|
||||
@@ -59,7 +60,14 @@ class ServicesPoolGroups(ModelHandler):
|
||||
table_title = _('Services Pool Groups')
|
||||
table_fields = [
|
||||
{'priority': {'title': _('Priority'), 'type': 'numeric', 'width': '6em'}},
|
||||
{'thumb': {'title': _('Image'), 'visible': True, 'type': 'image', 'width': '96px'}},
|
||||
{
|
||||
'thumb': {
|
||||
'title': _('Image'),
|
||||
'visible': True,
|
||||
'type': 'image',
|
||||
'width': '96px',
|
||||
}
|
||||
},
|
||||
{'name': {'title': _('Name')}},
|
||||
{'comments': {'title': _('Comments')}},
|
||||
]
|
||||
@@ -79,14 +87,22 @@ class ServicesPoolGroups(ModelHandler):
|
||||
def getGui(self, type_: str) -> typing.List[typing.Any]:
|
||||
localGui = self.addDefaultFields([], ['name', 'comments', 'priority'])
|
||||
|
||||
for field in [{
|
||||
for field in [
|
||||
{
|
||||
'name': 'image_id',
|
||||
'values': [gui.choiceImage(-1, '--------', DEFAULT_THUMB_BASE64)] + gui.sortedChoices([gui.choiceImage(v.uuid, v.name, v.thumb64) for v in Image.objects.all()]),
|
||||
'values': [gui.choiceImage(-1, '--------', DEFAULT_THUMB_BASE64)]
|
||||
+ gui.sortedChoices(
|
||||
[
|
||||
gui.choiceImage(v.uuid, v.name, v.thumb64)
|
||||
for v in Image.objects.all()
|
||||
]
|
||||
),
|
||||
'label': ugettext('Associated Image'),
|
||||
'tooltip': ugettext('Image assocciated with this service'),
|
||||
'type': gui.InputField.IMAGECHOICE_TYPE,
|
||||
'order': 102,
|
||||
}]:
|
||||
}
|
||||
]:
|
||||
self.addField(localGui, field)
|
||||
|
||||
return localGui
|
||||
@@ -100,7 +116,9 @@ class ServicesPoolGroups(ModelHandler):
|
||||
'image_id': item.image.uuid if item.image else None,
|
||||
}
|
||||
|
||||
def item_as_dict_overview(self, item: ServicePoolGroup) -> typing.Dict[str, typing.Any]:
|
||||
def item_as_dict_overview(
|
||||
self, item: ServicePoolGroup
|
||||
) -> typing.Dict[str, typing.Any]:
|
||||
return {
|
||||
'id': item.uuid,
|
||||
'priority': item.priority,
|
||||
|
@@ -54,10 +54,13 @@ from uds.models.calendar_action import (
|
||||
CALENDAR_ACTION_PUBLISH,
|
||||
CALENDAR_ACTION_ADD_TRANSPORT,
|
||||
CALENDAR_ACTION_DEL_TRANSPORT,
|
||||
CALENDAR_ACTION_DEL_ALL_TRANSPORTS,
|
||||
CALENDAR_ACTION_ADD_GROUP,
|
||||
CALENDAR_ACTION_DEL_GROUP,
|
||||
CALENDAR_ACTION_DEL_ALL_GROUPS,
|
||||
CALENDAR_ACTION_IGNORE_UNUSED,
|
||||
CALENDAR_ACTION_REMOVE_USERSERVICES,
|
||||
CALENDAR_ACTION_REMOVE_STUCK_USERSERVICES,
|
||||
)
|
||||
|
||||
from uds.core.managers import userServiceManager
|
||||
@@ -466,7 +469,7 @@ class ServicesPools(ModelHandler):
|
||||
{
|
||||
'name': 'max_srvs',
|
||||
'value': '0',
|
||||
'minValue': '1',
|
||||
'minValue': '0',
|
||||
'label': ugettext('Maximum number of services to provide'),
|
||||
'tooltip': ugettext(
|
||||
'Maximum number of service (assigned and L1 cache) that can be created for this service'
|
||||
@@ -536,16 +539,8 @@ class ServicesPools(ModelHandler):
|
||||
for k, v in serviceType.cacheConstrains.items():
|
||||
fields[k] = v
|
||||
|
||||
if serviceType.maxDeployed != -1:
|
||||
fields['max_srvs'] = min(
|
||||
(int(fields['max_srvs']), serviceType.maxDeployed)
|
||||
)
|
||||
fields['initial_srvs'] = min(
|
||||
int(fields['initial_srvs']), serviceType.maxDeployed
|
||||
)
|
||||
fields['cache_l1_srvs'] = min(
|
||||
int(fields['cache_l1_srvs']), serviceType.maxDeployed
|
||||
)
|
||||
if serviceType.usesCache_L2 is False:
|
||||
fields['cache_l2_srvs'] = 0
|
||||
|
||||
if serviceType.usesCache is False:
|
||||
for k in (
|
||||
@@ -555,9 +550,22 @@ class ServicesPools(ModelHandler):
|
||||
'max_srvs',
|
||||
):
|
||||
fields[k] = 0
|
||||
else: # uses cache, adjust values
|
||||
fields['max_srvs'] = int(fields['max_srvs']) or 1 # ensure max_srvs is at least 1
|
||||
fields['initial_srvs'] = int(fields['initial_srvs'])
|
||||
fields['cache_l1_srvs'] = int(fields['cache_l1_srvs'])
|
||||
|
||||
if serviceType.maxDeployed != -1:
|
||||
fields['max_srvs'] = min(
|
||||
(fields['max_srvs'], serviceType.maxDeployed)
|
||||
)
|
||||
fields['initial_srvs'] = min(
|
||||
fields['initial_srvs'], serviceType.maxDeployed
|
||||
)
|
||||
fields['cache_l1_srvs'] = min(
|
||||
fields['cache_l1_srvs'], serviceType.maxDeployed
|
||||
)
|
||||
|
||||
if serviceType.usesCache_L2 is False:
|
||||
fields['cache_l2_srvs'] = 0
|
||||
|
||||
except Exception:
|
||||
raise RequestError(ugettext('This service requires an OS Manager'))
|
||||
@@ -668,14 +676,17 @@ class ServicesPools(ModelHandler):
|
||||
validActions += (
|
||||
CALENDAR_ACTION_ADD_TRANSPORT,
|
||||
CALENDAR_ACTION_DEL_TRANSPORT,
|
||||
CALENDAR_ACTION_DEL_ALL_TRANSPORTS,
|
||||
CALENDAR_ACTION_ADD_GROUP,
|
||||
CALENDAR_ACTION_DEL_GROUP,
|
||||
CALENDAR_ACTION_DEL_ALL_GROUPS
|
||||
)
|
||||
|
||||
# Advanced actions
|
||||
validActions += (
|
||||
CALENDAR_ACTION_IGNORE_UNUSED,
|
||||
CALENDAR_ACTION_REMOVE_USERSERVICES,
|
||||
CALENDAR_ACTION_REMOVE_STUCK_USERSERVICES,
|
||||
)
|
||||
return validActions
|
||||
|
||||
|
@@ -64,16 +64,10 @@ class ServicesUsage(DetailHandler):
|
||||
|
||||
if item.user is None:
|
||||
owner = ''
|
||||
owner_info = {
|
||||
'auth_id': '',
|
||||
'user_id': ''
|
||||
}
|
||||
owner_info = {'auth_id': '', 'user_id': ''}
|
||||
else:
|
||||
owner = item.user.pretty_name
|
||||
owner_info = {
|
||||
'auth_id': item.user.manager.uuid,
|
||||
'user_id': item.user.uuid
|
||||
}
|
||||
owner_info = {'auth_id': item.user.manager.uuid, 'user_id': item.user.uuid}
|
||||
|
||||
return {
|
||||
'id': item.uuid,
|
||||
@@ -90,19 +84,30 @@ class ServicesUsage(DetailHandler):
|
||||
'ip': props.get('ip', _('unknown')),
|
||||
'source_host': item.src_hostname,
|
||||
'source_ip': item.src_ip,
|
||||
'in_use': item.in_use
|
||||
'in_use': item.in_use,
|
||||
}
|
||||
|
||||
def getItems(self, parent: 'Provider', item: typing.Optional[str]):
|
||||
try:
|
||||
if item is None:
|
||||
userServicesQuery = UserService.objects.filter(deployed_service__service__provider=parent)
|
||||
userServicesQuery = UserService.objects.filter(
|
||||
deployed_service__service__provider=parent
|
||||
)
|
||||
else:
|
||||
userServicesQuery = UserService.objects.filter(deployed_service__service_uuid=processUuid(item))
|
||||
userServicesQuery = UserService.objects.filter(
|
||||
deployed_service__service_uuid=processUuid(item)
|
||||
)
|
||||
|
||||
return [ServicesUsage.itemToDict(k) for k in userServicesQuery.filter(state=State.USABLE).order_by('creation_date').
|
||||
prefetch_related('deployed_service').prefetch_related('deployed_service__service').prefetch_related('properties').
|
||||
prefetch_related('user').prefetch_related('user__manager')]
|
||||
return [
|
||||
ServicesUsage.itemToDict(k)
|
||||
for k in userServicesQuery.filter(state=State.USABLE)
|
||||
.order_by('creation_date')
|
||||
.prefetch_related('deployed_service')
|
||||
.prefetch_related('deployed_service__service')
|
||||
.prefetch_related('properties')
|
||||
.prefetch_related('user')
|
||||
.prefetch_related('user__manager')
|
||||
]
|
||||
|
||||
except Exception:
|
||||
logger.exception('getItems')
|
||||
@@ -131,7 +136,9 @@ class ServicesUsage(DetailHandler):
|
||||
def deleteItem(self, parent: 'Provider', item: str) -> None:
|
||||
userService: UserService
|
||||
try:
|
||||
userService = UserService.objects.get(uuid=processUuid(item), deployed_service__service__provider=parent)
|
||||
userService = UserService.objects.get(
|
||||
uuid=processUuid(item), deployed_service__service__provider=parent
|
||||
)
|
||||
except Exception:
|
||||
raise self.invalidItemException()
|
||||
|
||||
|
@@ -53,13 +53,16 @@ if typing.TYPE_CHECKING:
|
||||
cache = Cache('StatsDispatcher')
|
||||
|
||||
# Enclosed methods under /stats path
|
||||
POINTS = 300
|
||||
SINCE = 30 # Days, if higer values used, ensure mysql/mariadb has a bigger sort buffer
|
||||
POINTS = 150
|
||||
SINCE = 7 # Days, if higer values used, ensure mysql/mariadb has a bigger sort buffer
|
||||
USE_MAX = True
|
||||
CACHE_TIME = SINCE * 24 * 3600 // POINTS
|
||||
|
||||
|
||||
def getServicesPoolsCounters(
|
||||
servicePool: typing.Optional[models.ServicePool], counter_type: int, since_days: int = SINCE
|
||||
servicePool: typing.Optional[models.ServicePool],
|
||||
counter_type: int,
|
||||
since_days: int = SINCE,
|
||||
) -> typing.List[typing.Mapping[str, typing.Any]]:
|
||||
try:
|
||||
cacheKey = (
|
||||
@@ -91,7 +94,7 @@ def getServicesPoolsCounters(
|
||||
val.append({'stamp': x[0], 'value': int(x[1])})
|
||||
logger.debug('val: %s', val)
|
||||
if len(val) >= 2:
|
||||
cache.put(cacheKey, codecs.encode(pickle.dumps(val), 'zip'), 600)
|
||||
cache.put(cacheKey, codecs.encode(pickle.dumps(val), 'zip'), CACHE_TIME*2)
|
||||
else:
|
||||
val = [{'stamp': since, 'value': 0}, {'stamp': to, 'value': 0}]
|
||||
else:
|
||||
@@ -142,14 +145,18 @@ class System(Handler):
|
||||
pool: typing.Optional[models.ServicePool] = None
|
||||
if len(self._args) == 3:
|
||||
try:
|
||||
pool = models.ServicePool.objects.get(uuid=processUuid(self._args[2]))
|
||||
pool = models.ServicePool.objects.get(
|
||||
uuid=processUuid(self._args[2])
|
||||
)
|
||||
except Exception:
|
||||
pool = None
|
||||
# If pool is None, needs admin also
|
||||
if not pool and not self._user.is_admin:
|
||||
raise AccessDenied()
|
||||
# Check permission for pool..
|
||||
if not permissions.checkPermissions(self._user, typing.cast('Model', pool), permissions.PERMISSION_READ):
|
||||
if not permissions.checkPermissions(
|
||||
self._user, typing.cast('Model', pool), permissions.PERMISSION_READ
|
||||
):
|
||||
raise AccessDenied()
|
||||
if self._args[0] == 'stats':
|
||||
if self._args[1] == 'assigned':
|
||||
@@ -160,9 +167,15 @@ class System(Handler):
|
||||
return getServicesPoolsCounters(pool, counters.CT_CACHED)
|
||||
elif self._args[1] == 'complete':
|
||||
return {
|
||||
'assigned': getServicesPoolsCounters(pool, counters.CT_ASSIGNED, since_days=7),
|
||||
'inuse': getServicesPoolsCounters(pool, counters.CT_INUSE, since_days=7),
|
||||
'cached': getServicesPoolsCounters(pool, counters.CT_CACHED, since_days=7),
|
||||
'assigned': getServicesPoolsCounters(
|
||||
pool, counters.CT_ASSIGNED, since_days=7
|
||||
),
|
||||
'inuse': getServicesPoolsCounters(
|
||||
pool, counters.CT_INUSE, since_days=7
|
||||
),
|
||||
'cached': getServicesPoolsCounters(
|
||||
pool, counters.CT_CACHED, since_days=7
|
||||
),
|
||||
}
|
||||
|
||||
raise RequestError('invalid request')
|
||||
|
@@ -1,7 +1,7 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
#
|
||||
# Copyright (c) 2014-2019 Virtual Cable S.L.
|
||||
# Copyright (c) 2014-2021 Virtual Cable S.L.U.
|
||||
# All rights reserved.
|
||||
#
|
||||
# Redistribution and use in source and binary forms, with or without modification,
|
||||
@@ -44,10 +44,20 @@ from uds.core.util import tools
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
# Valid parameters accepted by ticket creation method
|
||||
VALID_PARAMS = (
|
||||
'authId', 'authTag', 'authSmallName', 'auth', 'username',
|
||||
'realname', 'password', 'groups', 'servicePool', 'transport',
|
||||
'force', 'userIp'
|
||||
'authId',
|
||||
'authTag',
|
||||
'authSmallName',
|
||||
'auth',
|
||||
'username',
|
||||
'realname',
|
||||
'password',
|
||||
'groups',
|
||||
'servicePool',
|
||||
'transport', # Admited to be backwards compatible, but not used. Will be removed on a future release.
|
||||
'force',
|
||||
'userIp',
|
||||
)
|
||||
|
||||
|
||||
@@ -67,15 +77,18 @@ class Tickets(Handler):
|
||||
password:
|
||||
groups:
|
||||
servicePool:
|
||||
transport:
|
||||
transport: Ignored. Transport must be auto-detected on ticket auth
|
||||
force: If "1" or "true" will ensure that:
|
||||
- Groups exists on authenticator
|
||||
- servicePool has these groups in it's allowed list
|
||||
"""
|
||||
|
||||
needs_admin = True # By default, staff is lower level needed
|
||||
|
||||
@staticmethod
|
||||
def result(result: str = '', error: typing.Optional[str] = None) -> typing.Dict[str, typing.Any]:
|
||||
def result(
|
||||
result: str = '', error: typing.Optional[str] = None
|
||||
) -> typing.Dict[str, typing.Any]:
|
||||
"""
|
||||
Returns a result for a Ticket request
|
||||
"""
|
||||
@@ -112,7 +125,9 @@ class Tickets(Handler):
|
||||
raise RequestError('Invalid parameters (no auth)')
|
||||
|
||||
# Must be invoked as '/rest/ticket/create, with "username", ("authId" or ("authSmallName" or "authTag"), "groups" (array) and optionally "time" (in seconds) as paramteres
|
||||
def put(self): # pylint: disable=too-many-locals,too-many-branches,too-many-statements
|
||||
def put(
|
||||
self,
|
||||
): # pylint: disable=too-many-locals,too-many-branches,too-many-statements
|
||||
"""
|
||||
Processes put requests, currently only under "create"
|
||||
"""
|
||||
@@ -124,40 +139,57 @@ class Tickets(Handler):
|
||||
if 'username' not in self._params or 'groups' not in self._params:
|
||||
raise RequestError('Invalid parameters')
|
||||
|
||||
force: bool = self._params.get('force', '0') in ('1', 'true', 'True')
|
||||
force: bool = self._params.get('force', '0') in ('1', 'true', 'True', True)
|
||||
|
||||
userIp: typing.Optional[str] = self._params.get('userIp', None)
|
||||
|
||||
try:
|
||||
servicePoolId = None
|
||||
transportId = None
|
||||
|
||||
authId = self._params.get('authId', None)
|
||||
authName = self._params.get('auth', None)
|
||||
authTag = self._params.get('authTag', self._params.get('authSmallName', None))
|
||||
authTag = self._params.get(
|
||||
'authTag', self._params.get('authSmallName', None)
|
||||
)
|
||||
|
||||
# Will raise an exception if no auth found
|
||||
if authId:
|
||||
auth = models.Authenticator.objects.get(uuid=processUuid(authId.lower()))
|
||||
auth = models.Authenticator.objects.get(
|
||||
uuid=processUuid(authId.lower())
|
||||
)
|
||||
elif authName:
|
||||
auth = models.Authenticator.objects.get(name=authName)
|
||||
else:
|
||||
auth = models.Authenticator.objects.get(small_name=authTag)
|
||||
|
||||
username: str = self._params['username']
|
||||
password: str = self._params.get('password', '') # Some machines needs password, depending on configuration
|
||||
password: str = self._params.get(
|
||||
'password', ''
|
||||
) # Some machines needs password, depending on configuration
|
||||
|
||||
groupIds: typing.List[str] = []
|
||||
for groupName in tools.asList(self._params['groups']):
|
||||
try:
|
||||
groupIds.append(auth.groups.get(name=groupName).uuid)
|
||||
except Exception:
|
||||
logger.info('Group %s from ticket does not exists on auth %s, forced creation: %s', groupName, auth, force)
|
||||
logger.info(
|
||||
'Group %s from ticket does not exists on auth %s, forced creation: %s',
|
||||
groupName,
|
||||
auth,
|
||||
force,
|
||||
)
|
||||
if force: # Force creation by call
|
||||
groupIds.append(auth.groups.create(name=groupName, comments='Autocreated form ticket by using force paratemeter').uuid)
|
||||
groupIds.append(
|
||||
auth.groups.create(
|
||||
name=groupName,
|
||||
comments='Autocreated form ticket by using force paratemeter',
|
||||
).uuid
|
||||
)
|
||||
|
||||
if not groupIds: # No valid group in groups names
|
||||
raise RequestError('Authenticator does not contain ANY of the requested groups and force is not used')
|
||||
raise RequestError(
|
||||
'Authenticator does not contain ANY of the requested groups and force is not used'
|
||||
)
|
||||
|
||||
time = int(self._params.get('time', 60))
|
||||
time = 60 if time < 1 else time
|
||||
@@ -166,55 +198,50 @@ class Tickets(Handler):
|
||||
if 'servicePool' in self._params:
|
||||
# Check if is pool or metapool
|
||||
poolUuid = processUuid(self._params['servicePool'])
|
||||
pool : typing.Union[models.ServicePool, models.MetaPool]
|
||||
pool: typing.Union[models.ServicePool, models.MetaPool]
|
||||
|
||||
try:
|
||||
pool = typing.cast(models.MetaPool, models.MetaPool.objects.get(uuid=poolUuid)) # If not an metapool uuid, will process it as a servicePool
|
||||
pool = typing.cast(
|
||||
models.MetaPool, models.MetaPool.objects.get(uuid=poolUuid)
|
||||
) # If not an metapool uuid, will process it as a servicePool
|
||||
if force:
|
||||
# First, add groups to metapool
|
||||
for addGrp in set(groupIds) - set(pool.assignedGroups.values_list('uuid', flat=True)):
|
||||
for addGrp in set(groupIds) - set(
|
||||
pool.assignedGroups.values_list('uuid', flat=True)
|
||||
):
|
||||
pool.assignedGroups.add(auth.groups.get(uuid=addGrp))
|
||||
# And now, to ALL metapool members
|
||||
for metaMember in pool.members.all():
|
||||
# First, add groups to metapool
|
||||
for addGrp in set(groupIds) - set(metaMember.pool.assignedGroups.values_list('uuid', flat=True)):
|
||||
metaMember.assignedGroups.add(auth.groups.get(uuid=addGrp))
|
||||
|
||||
# Now add groups to pools
|
||||
for addGrp in set(groupIds) - set(
|
||||
metaMember.pool.assignedGroups.values_list(
|
||||
'uuid', flat=True
|
||||
)
|
||||
):
|
||||
metaMember.pool.assignedGroups.add(
|
||||
auth.groups.get(uuid=addGrp)
|
||||
)
|
||||
|
||||
# For metapool, transport is ignored..
|
||||
|
||||
servicePoolId = 'M' + pool.uuid
|
||||
transportId = 'meta'
|
||||
|
||||
|
||||
except models.MetaPool.DoesNotExist:
|
||||
pool = typing.cast(models.ServicePool, models.ServicePool.objects.get(uuid=poolUuid))
|
||||
pool = typing.cast(
|
||||
models.ServicePool,
|
||||
models.ServicePool.objects.get(uuid=poolUuid),
|
||||
)
|
||||
|
||||
# If forced that servicePool must honor groups
|
||||
if force:
|
||||
for addGrp in set(groupIds) - set(pool.assignedGroups.values_list('uuid', flat=True)):
|
||||
for addGrp in set(groupIds) - set(
|
||||
pool.assignedGroups.values_list('uuid', flat=True)
|
||||
):
|
||||
pool.assignedGroups.add(auth.groups.get(uuid=addGrp))
|
||||
|
||||
if 'transport' in self._params:
|
||||
transport: models.Transport = models.Transport.objects.get(uuid=processUuid(self._params['transport']))
|
||||
try:
|
||||
pool.validateTransport(transport)
|
||||
except Exception:
|
||||
logger.error('Transport %s is not valid for Service Pool %s', transport.name, pool.name)
|
||||
raise Exception('Invalid transport for Service Pool')
|
||||
else:
|
||||
transport = models.Transport(uuid=None)
|
||||
if userIp:
|
||||
for v in pool.transports.order_by('priority'):
|
||||
if v.validForIp(userIp):
|
||||
transport = v
|
||||
break
|
||||
|
||||
if transport.uuid is None:
|
||||
logger.error('Service pool %s does not has valid transports for ip %s', pool.name, userIp)
|
||||
raise Exception('Service pool does not has any valid transports for ip {}'.format(userIp))
|
||||
|
||||
servicePoolId = 'F' + pool.uuid
|
||||
transportId = transport.uuid
|
||||
|
||||
|
||||
except models.Authenticator.DoesNotExist:
|
||||
return Tickets.result(error='Authenticator does not exists')
|
||||
except models.ServicePool.DoesNotExist:
|
||||
@@ -231,7 +258,6 @@ class Tickets(Handler):
|
||||
'groups': groupIds,
|
||||
'auth': auth.uuid,
|
||||
'servicePool': servicePoolId,
|
||||
'transport': transportId,
|
||||
}
|
||||
|
||||
ticket = models.TicketStore.create(data)
|
||||
|
@@ -1,7 +1,7 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
#
|
||||
# Copyright (c) 2014-2019 Virtual Cable S.L.
|
||||
# Copyright (c) 2014-2021 Virtual Cable S.L.U.
|
||||
# All rights reserved.
|
||||
#
|
||||
# Redistribution and use in source and binary forms, with or without modification,
|
||||
@@ -12,7 +12,7 @@
|
||||
# * Redistributions in binary form must reproduce the above copyright notice,
|
||||
# this list of conditions and the following disclaimer in the documentation
|
||||
# and/or other materials provided with the distribution.
|
||||
# * Neither the name of Virtual Cable S.L. nor the names of its contributors
|
||||
# * Neither the name of Virtual Cable S.L.U. nor the names of its contributors
|
||||
# may be used to endorse or promote products derived from this software
|
||||
# without specific prior written permission.
|
||||
#
|
||||
@@ -50,7 +50,15 @@ logger = logging.getLogger(__name__)
|
||||
|
||||
class Transports(ModelHandler):
|
||||
model = Transport
|
||||
save_fields = ['name', 'comments', 'tags', 'priority', 'nets_positive', 'allowed_oss', 'label']
|
||||
save_fields = [
|
||||
'name',
|
||||
'comments',
|
||||
'tags',
|
||||
'priority',
|
||||
'nets_positive',
|
||||
'allowed_oss',
|
||||
'label',
|
||||
]
|
||||
|
||||
table_title = _('Transports')
|
||||
table_fields = [
|
||||
@@ -58,7 +66,13 @@ class Transports(ModelHandler):
|
||||
{'name': {'title': _('Name'), 'visible': True, 'type': 'iconType'}},
|
||||
{'type_name': {'title': _('Type')}},
|
||||
{'comments': {'title': _('Comments')}},
|
||||
{'pools_count': {'title': _('Service Pools'), 'type': 'numeric', 'width': '6em'}},
|
||||
{
|
||||
'pools_count': {
|
||||
'title': _('Service Pools'),
|
||||
'type': 'numeric',
|
||||
'width': '6em',
|
||||
}
|
||||
},
|
||||
{'allowed_oss': {'title': _('Devices'), 'width': '8em'}},
|
||||
{'tags': {'title': _('tags'), 'visible': False}},
|
||||
]
|
||||
@@ -72,52 +86,90 @@ class Transports(ModelHandler):
|
||||
if not transport:
|
||||
raise self.invalidItemException()
|
||||
|
||||
field = self.addDefaultFields(transport.guiDescription(), ['name', 'comments', 'tags', 'priority'])
|
||||
field = self.addField(field, {
|
||||
'name': 'nets_positive',
|
||||
'value': True,
|
||||
'label': ugettext('Network access'),
|
||||
'tooltip': ugettext('If checked, the transport will be enabled for the selected networks. If unchecked, transport will be disabled for selected networks'),
|
||||
'type': 'checkbox',
|
||||
'order': 100, # At end
|
||||
})
|
||||
field = self.addField(field, {
|
||||
'name': 'networks',
|
||||
'value': [],
|
||||
'values': sorted([{'id': x.uuid, 'text': x.name} for x in Network.objects.all()], key=lambda x: x['text'].lower()),
|
||||
'label': ugettext('Networks'),
|
||||
'tooltip': ugettext('Networks associated with this transport. If No network selected, will mean "all networks"'),
|
||||
'type': 'multichoice',
|
||||
'order': 101
|
||||
})
|
||||
field = self.addField(field, {
|
||||
'name': 'allowed_oss',
|
||||
'value': [],
|
||||
'values': sorted([{'id': x, 'text': x.replace('CrOS', 'Chrome OS')} for x in OsDetector.knownOss], key=lambda x: x['text'].lower()),
|
||||
'label': ugettext('Allowed Devices'),
|
||||
'tooltip': ugettext('If empty, any kind of device compatible with this transport will be allowed. Else, only devices compatible with selected values will be allowed'),
|
||||
'type': 'multichoice',
|
||||
'order': 102
|
||||
})
|
||||
field = self.addField(field, {
|
||||
'name': 'pools',
|
||||
'value': [],
|
||||
'values': [{'id': x.uuid, 'text': x.name} for x in ServicePool.objects.all().order_by('name') if transport.protocol in x.service.getType().allowedProtocols],
|
||||
'label': ugettext('Service Pools'),
|
||||
'tooltip': ugettext('Currently assigned services pools'),
|
||||
'type': 'multichoice',
|
||||
'order': 103
|
||||
})
|
||||
field = self.addField(field, {
|
||||
'name': 'label',
|
||||
'length': 32,
|
||||
'value': '',
|
||||
'label': ugettext('Label'),
|
||||
'tooltip': ugettext('Metapool transport label (only used on metapool transports grouping)'),
|
||||
'type': 'text',
|
||||
'order': 201,
|
||||
'tab': gui.ADVANCED_TAB
|
||||
})
|
||||
field = self.addDefaultFields(
|
||||
transport.guiDescription(), ['name', 'comments', 'tags', 'priority']
|
||||
)
|
||||
field = self.addField(
|
||||
field,
|
||||
{
|
||||
'name': 'nets_positive',
|
||||
'value': True,
|
||||
'label': ugettext('Network access'),
|
||||
'tooltip': ugettext(
|
||||
'If checked, the transport will be enabled for the selected networks. If unchecked, transport will be disabled for selected networks'
|
||||
),
|
||||
'type': 'checkbox',
|
||||
'order': 100, # At end
|
||||
},
|
||||
)
|
||||
field = self.addField(
|
||||
field,
|
||||
{
|
||||
'name': 'networks',
|
||||
'value': [],
|
||||
'values': sorted(
|
||||
[{'id': x.uuid, 'text': x.name} for x in Network.objects.all()],
|
||||
key=lambda x: x['text'].lower(),
|
||||
),
|
||||
'label': ugettext('Networks'),
|
||||
'tooltip': ugettext(
|
||||
'Networks associated with this transport. If No network selected, will mean "all networks"'
|
||||
),
|
||||
'type': 'multichoice',
|
||||
'order': 101,
|
||||
},
|
||||
)
|
||||
field = self.addField(
|
||||
field,
|
||||
{
|
||||
'name': 'allowed_oss',
|
||||
'value': [],
|
||||
'values': sorted(
|
||||
[
|
||||
{'id': x.name, 'text': x.name}
|
||||
for x in OsDetector.knownOss
|
||||
],
|
||||
key=lambda x: x['text'].lower(),
|
||||
),
|
||||
'label': ugettext('Allowed Devices'),
|
||||
'tooltip': ugettext(
|
||||
'If empty, any kind of device compatible with this transport will be allowed. Else, only devices compatible with selected values will be allowed'
|
||||
),
|
||||
'type': 'multichoice',
|
||||
'order': 102,
|
||||
},
|
||||
)
|
||||
field = self.addField(
|
||||
field,
|
||||
{
|
||||
'name': 'pools',
|
||||
'value': [],
|
||||
'values': [
|
||||
{'id': x.uuid, 'text': x.name}
|
||||
for x in ServicePool.objects.all().order_by('name')
|
||||
if transport.protocol in x.service.getType().allowedProtocols
|
||||
],
|
||||
'label': ugettext('Service Pools'),
|
||||
'tooltip': ugettext('Currently assigned services pools'),
|
||||
'type': 'multichoice',
|
||||
'order': 103,
|
||||
},
|
||||
)
|
||||
field = self.addField(
|
||||
field,
|
||||
{
|
||||
'name': 'label',
|
||||
'length': 32,
|
||||
'value': '',
|
||||
'label': ugettext('Label'),
|
||||
'tooltip': ugettext(
|
||||
'Metapool transport label (only used on metapool transports grouping)'
|
||||
),
|
||||
'type': 'text',
|
||||
'order': 201,
|
||||
'tab': ugettext(gui.ADVANCED_TAB),
|
||||
},
|
||||
)
|
||||
|
||||
return field
|
||||
|
||||
@@ -133,14 +185,16 @@ class Transports(ModelHandler):
|
||||
'nets_positive': item.nets_positive,
|
||||
'label': item.label,
|
||||
'networks': [{'id': n.uuid} for n in item.networks.all()],
|
||||
'allowed_oss': [{'id': x} for x in item.allowed_oss.split(',')] if item.allowed_oss != '' else [],
|
||||
'allowed_oss': [{'id': x} for x in item.allowed_oss.split(',')]
|
||||
if item.allowed_oss != ''
|
||||
else [],
|
||||
'pools': pools,
|
||||
'pools_count': len(pools),
|
||||
'deployed_count': item.deployedServices.count(),
|
||||
'type': type_.type(),
|
||||
'type_name': type_.name(),
|
||||
'protocol': type_.protocol,
|
||||
'permission': permissions.getEffectivePermission(self._user, item)
|
||||
'permission': permissions.getEffectivePermission(self._user, item),
|
||||
}
|
||||
|
||||
def beforeSave(self, fields: typing.Dict[str, typing.Any]) -> None:
|
||||
|
@@ -38,18 +38,21 @@ from uds.core import managers
|
||||
from uds.REST import Handler
|
||||
from uds.REST import AccessDenied
|
||||
from uds.core.auths.auth import isTrustedSource
|
||||
from uds.core.util import log, net, request
|
||||
from uds.core.util import log, net
|
||||
from uds.core.util.stats import events
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
MAX_SESSION_LENGTH = 60*60*24*7
|
||||
MAX_SESSION_LENGTH = (
|
||||
60 * 60 * 24 * 7 * 2
|
||||
) # Two weeks is max session length for a tunneled connection
|
||||
|
||||
# Enclosed methods under /tunnel path
|
||||
class TunnelTicket(Handler):
|
||||
"""
|
||||
Processes tunnel requests
|
||||
"""
|
||||
|
||||
authenticated = False # Client requests are not authenticated
|
||||
path = 'tunnel'
|
||||
name = 'ticket'
|
||||
@@ -59,7 +62,10 @@ class TunnelTicket(Handler):
|
||||
Processes get requests, currently none
|
||||
"""
|
||||
logger.debug(
|
||||
'Tunnel parameters for GET: %s (%s) from %s', self._args, self._params, self._request.ip
|
||||
'Tunnel parameters for GET: %s (%s) from %s',
|
||||
self._args,
|
||||
self._params,
|
||||
self._request.ip,
|
||||
)
|
||||
|
||||
if (
|
||||
@@ -73,52 +79,69 @@ class TunnelTicket(Handler):
|
||||
# Take token from url
|
||||
token = self._args[2][:48]
|
||||
if not models.TunnelToken.validateToken(token):
|
||||
if self._args[1][:4] == 'stop':
|
||||
# "Eat" invalid stop requests, because Applications does not like them
|
||||
return {}
|
||||
logger.error('Invalid token %s from %s', token, self._request.ip)
|
||||
raise AccessDenied()
|
||||
|
||||
|
||||
# Try to get ticket from DB
|
||||
try:
|
||||
user, userService, host, port, extra = models.TicketStore.get_for_tunnel(
|
||||
self._args[0]
|
||||
)
|
||||
host = host or ''
|
||||
data = {}
|
||||
if self._args[1][:4] == 'stop':
|
||||
sent, recv = self._params['sent'], self._params['recv']
|
||||
# Ensures extra exists...
|
||||
extra = extra or {}
|
||||
now = models.getSqlDatetimeAsUnix()
|
||||
totalTime = now - extra.get('b', now-1)
|
||||
totalTime = now - extra.get('b', now - 1)
|
||||
msg = f'User {user.name} stopped tunnel {extra.get("t", "")[:8]}... to {host}:{port}: u:{sent}/d:{recv}/t:{totalTime}.'
|
||||
log.doLog(user.manager, log.INFO, msg)
|
||||
log.doLog(userService, log.INFO, msg)
|
||||
|
||||
# Try to log Close event
|
||||
try:
|
||||
# If pool does not exists, do not log anything
|
||||
events.addEvent(
|
||||
userService.deployed_service,
|
||||
events.ET_TUNNEL_CLOSE,
|
||||
duration=totalTime,
|
||||
sent=sent,
|
||||
received=recv,
|
||||
tunnel=extra.get('t', 'unknown'),
|
||||
)
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
else:
|
||||
if net.ipToLong(self._args[1][:32]) == 0:
|
||||
raise Exception('Invalid from IP')
|
||||
events.addEvent(
|
||||
userService.deployed_service,
|
||||
events.ET_TUNNEL_ACCESS,
|
||||
events.ET_TUNNEL_OPEN,
|
||||
username=user.pretty_name,
|
||||
srcip=self._args[1],
|
||||
dstip=host,
|
||||
uniqueid=userService.unique_id,
|
||||
tunnel=self._args[0],
|
||||
)
|
||||
msg = f'User {user.name} started tunnel {self._args[0][:8]}... to {host}:{port} from {self._args[1]}.'
|
||||
log.doLog(user.manager, log.INFO, msg)
|
||||
log.doLog(userService, log.INFO, msg)
|
||||
# Generate new, notify only, ticket
|
||||
rstr = managers.cryptoManager().randomString(length=8)
|
||||
notifyTicket = models.TicketStore.create_for_tunnel(
|
||||
userService=userService,
|
||||
port=port,
|
||||
host=host,
|
||||
extra={'t': self._args[0], 'b': models.getSqlDatetimeAsUnix()},
|
||||
validity=MAX_SESSION_LENGTH)
|
||||
data = {
|
||||
'host': host,
|
||||
'port': port,
|
||||
'notify': notifyTicket
|
||||
}
|
||||
extra={
|
||||
't': self._args[0], # ticket
|
||||
'b': models.getSqlDatetimeAsUnix(), # Begin time stamp
|
||||
},
|
||||
validity=MAX_SESSION_LENGTH,
|
||||
)
|
||||
data = {'host': host, 'port': port, 'notify': notifyTicket}
|
||||
|
||||
return data
|
||||
except Exception as e:
|
||||
@@ -136,7 +159,9 @@ class TunnelRegister(Handler):
|
||||
now = models.getSqlDatetimeAsUnix()
|
||||
try:
|
||||
# If already exists a token for this MAC, return it instead of creating a new one, and update the information...
|
||||
tunnelToken = models.TunnelToken.objects.get(ip=self._params['ip'], hostname= self._params['hostname'])
|
||||
tunnelToken = models.TunnelToken.objects.get(
|
||||
ip=self._params['ip'], hostname=self._params['hostname']
|
||||
)
|
||||
# Update parameters
|
||||
tunnelToken.username = self._user.pretty_name
|
||||
tunnelToken.ip_from = self._request.ip
|
||||
@@ -150,15 +175,8 @@ class TunnelRegister(Handler):
|
||||
ip=self._params['ip'],
|
||||
hostname=self._params['hostname'],
|
||||
token=secrets.token_urlsafe(36),
|
||||
stamp=models.getSqlDatetime()
|
||||
stamp=models.getSqlDatetime(),
|
||||
)
|
||||
except Exception as e:
|
||||
return {
|
||||
'result': '',
|
||||
'stamp': now,
|
||||
'error': str(e)
|
||||
}
|
||||
return {
|
||||
'result': tunnelToken.token,
|
||||
'stamp': now
|
||||
}
|
||||
return {'result': '', 'stamp': now, 'error': str(e)}
|
||||
return {'result': tunnelToken.token, 'stamp': now}
|
||||
|
@@ -42,13 +42,11 @@ from uds.core.util import permissions
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
# Enclosed methods under /osm path
|
||||
|
||||
|
||||
class TunnelTokens(ModelHandler):
|
||||
model = TunnelToken
|
||||
|
||||
table_title = _('Actor tokens')
|
||||
table_title = _('Tunnel tokens')
|
||||
table_fields = [
|
||||
{'token': {'title': _('Token')}},
|
||||
{'stamp': {'title': _('Date'), 'type': 'datetime'}},
|
||||
@@ -65,7 +63,7 @@ class TunnelTokens(ModelHandler):
|
||||
'username': item.username,
|
||||
'ip': item.ip,
|
||||
'hostname': item.hostname,
|
||||
'token': item.token
|
||||
'token': item.token,
|
||||
}
|
||||
|
||||
def delete(self) -> str:
|
||||
@@ -75,7 +73,9 @@ class TunnelTokens(ModelHandler):
|
||||
if len(self._args) != 1:
|
||||
raise RequestError('Delete need one and only one argument')
|
||||
|
||||
self.ensureAccess(self.model(), permissions.PERMISSION_ALL, root=True) # Must have write permissions to delete
|
||||
self.ensureAccess(
|
||||
self.model(), permissions.PERMISSION_ALL, root=True
|
||||
) # Must have write permissions to delete
|
||||
|
||||
try:
|
||||
self.model.objects.get(token=self._args[0]).delete()
|
||||
|
@@ -51,6 +51,7 @@ class AssignedService(DetailHandler):
|
||||
"""
|
||||
Rest handler for Assigned Services, wich parent is Service
|
||||
"""
|
||||
|
||||
custom_methods = [
|
||||
'reset',
|
||||
]
|
||||
@@ -239,7 +240,10 @@ class CachedService(AssignedService):
|
||||
"""
|
||||
Rest handler for Cached Services, wich parent is Service
|
||||
"""
|
||||
custom_methods: typing.ClassVar[typing.List[str]] = [] # Remove custom methods from assigned services
|
||||
|
||||
custom_methods: typing.ClassVar[
|
||||
typing.List[str]
|
||||
] = [] # Remove custom methods from assigned services
|
||||
|
||||
def getItems(self, parent: models.ServicePool, item: typing.Optional[str]):
|
||||
# Extract provider
|
||||
|
@@ -87,20 +87,54 @@ class Users(DetailHandler):
|
||||
del v['uuid']
|
||||
yield v
|
||||
|
||||
def getItems(self, parent, item):
|
||||
def getItems(self, parent: Authenticator, item: typing.Optional[str]):
|
||||
logger.debug(item)
|
||||
# Extract authenticator
|
||||
try:
|
||||
if item is None:
|
||||
values = list(Users.uuid_to_id(parent.users.all().values('uuid', 'name', 'real_name', 'comments', 'state', 'staff_member', 'is_admin', 'last_access', 'parent')))
|
||||
values = list(
|
||||
Users.uuid_to_id(
|
||||
parent.users.all().values(
|
||||
'uuid',
|
||||
'name',
|
||||
'real_name',
|
||||
'comments',
|
||||
'state',
|
||||
'staff_member',
|
||||
'is_admin',
|
||||
'last_access',
|
||||
'parent',
|
||||
)
|
||||
)
|
||||
)
|
||||
for res in values:
|
||||
res['role'] = res['staff_member'] and (res['is_admin'] and _('Admin') or _('Staff member')) or _('User')
|
||||
res['role'] = (
|
||||
res['staff_member']
|
||||
and (res['is_admin'] and _('Admin') or _('Staff member'))
|
||||
or _('User')
|
||||
)
|
||||
return values
|
||||
else:
|
||||
u = parent.users.get(uuid=processUuid(item))
|
||||
res = model_to_dict(u, fields=('name', 'real_name', 'comments', 'state', 'staff_member', 'is_admin', 'last_access', 'parent'))
|
||||
res = model_to_dict(
|
||||
u,
|
||||
fields=(
|
||||
'name',
|
||||
'real_name',
|
||||
'comments',
|
||||
'state',
|
||||
'staff_member',
|
||||
'is_admin',
|
||||
'last_access',
|
||||
'parent',
|
||||
),
|
||||
)
|
||||
res['id'] = u.uuid
|
||||
res['role'] = res['staff_member'] and (res['is_admin'] and _('Admin') or _('Staff member')) or _('User')
|
||||
res['role'] = (
|
||||
res['staff_member']
|
||||
and (res['is_admin'] and _('Admin') or _('Staff member'))
|
||||
or _('User')
|
||||
)
|
||||
usr = aUser(u)
|
||||
res['groups'] = [g.dbGroup().uuid for g in usr.groups()]
|
||||
logger.debug('Item: %s', res)
|
||||
@@ -111,24 +145,41 @@ class Users(DetailHandler):
|
||||
|
||||
def getTitle(self, parent):
|
||||
try:
|
||||
return _('Users of {0}').format(Authenticator.objects.get(uuid=processUuid(self._kwargs['parent_id'])).name)
|
||||
return _('Users of {0}').format(
|
||||
Authenticator.objects.get(
|
||||
uuid=processUuid(self._kwargs['parent_id'])
|
||||
).name
|
||||
)
|
||||
except Exception:
|
||||
return _('Current users')
|
||||
|
||||
def getFields(self, parent):
|
||||
return [
|
||||
{'name': {'title': _('Username'), 'visible': True, 'type': 'icon', 'icon': 'fa fa-user text-success'}},
|
||||
{
|
||||
'name': {
|
||||
'title': _('Username'),
|
||||
'visible': True,
|
||||
'type': 'icon',
|
||||
'icon': 'fa fa-user text-success',
|
||||
}
|
||||
},
|
||||
{'role': {'title': _('Role')}},
|
||||
{'real_name': {'title': _('Name')}},
|
||||
{'comments': {'title': _('Comments')}},
|
||||
{'state': {'title': _('state'), 'type': 'dict', 'dict': State.dictionary()}},
|
||||
{
|
||||
'state': {
|
||||
'title': _('state'),
|
||||
'type': 'dict',
|
||||
'dict': State.dictionary(),
|
||||
}
|
||||
},
|
||||
{'last_access': {'title': _('Last access'), 'type': 'datetime'}},
|
||||
]
|
||||
|
||||
def getRowStyle(self, parent):
|
||||
return {'field': 'state', 'prefix': 'row-state-'}
|
||||
|
||||
def getLogs(self, parent, item):
|
||||
def getLogs(self, parent: Authenticator, item):
|
||||
user = None
|
||||
try:
|
||||
user = parent.users.get(uuid=processUuid(item))
|
||||
@@ -137,9 +188,19 @@ class Users(DetailHandler):
|
||||
|
||||
return log.getLogs(user)
|
||||
|
||||
def saveItem(self, parent, item):
|
||||
def saveItem(self, parent: Authenticator, item: typing.Optional[str]) -> None:
|
||||
logger.debug('Saving user %s / %s', parent, item)
|
||||
valid_fields = ['name', 'real_name', 'comments', 'state', 'staff_member', 'is_admin']
|
||||
valid_fields = [
|
||||
'name',
|
||||
'real_name',
|
||||
'comments',
|
||||
'state',
|
||||
'staff_member',
|
||||
'is_admin',
|
||||
]
|
||||
if self._params.get('name', '') == '':
|
||||
raise RequestError(_('Username cannot be empty'))
|
||||
|
||||
if 'password' in self._params:
|
||||
valid_fields.append('password')
|
||||
self._params['password'] = cryptoManager().hash(self._params['password'])
|
||||
@@ -153,7 +214,9 @@ class Users(DetailHandler):
|
||||
try:
|
||||
auth = parent.getInstance()
|
||||
if item is None: # Create new
|
||||
auth.createUser(fields) # this throws an exception if there is an error (for example, this auth can't create users)
|
||||
auth.createUser(
|
||||
fields
|
||||
) # this throws an exception if there is an error (for example, this auth can't create users)
|
||||
user = parent.users.create(**fields)
|
||||
else:
|
||||
auth.modifyUser(fields) # Notifies authenticator
|
||||
@@ -161,7 +224,9 @@ class Users(DetailHandler):
|
||||
user.__dict__.update(fields)
|
||||
|
||||
logger.debug('User parent: %s', user.parent)
|
||||
if auth.isExternalSource is False and (user.parent is None or user.parent == ''):
|
||||
if auth.isExternalSource is False and (
|
||||
user.parent is None or user.parent == ''
|
||||
):
|
||||
groups = self.readFieldsFromParams(['groups'])['groups']
|
||||
logger.debug('Groups: %s', groups)
|
||||
logger.debug('Got Groups %s', parent.groups.filter(uuid__in=groups))
|
||||
@@ -177,18 +242,20 @@ class Users(DetailHandler):
|
||||
raise RequestError(str(e))
|
||||
except ValidationError as e:
|
||||
raise RequestError(str(e.message))
|
||||
except RequestError:
|
||||
raise
|
||||
except Exception:
|
||||
logger.exception('Saving user')
|
||||
raise self.invalidRequestException()
|
||||
|
||||
return self.getItems(parent, user.uuid)
|
||||
|
||||
def deleteItem(self, parent, item):
|
||||
def deleteItem(self, parent: Authenticator, item):
|
||||
try:
|
||||
user = parent.users.get(uuid=processUuid(item))
|
||||
if not self._user.is_admin and (user.is_admin or user.staff_member):
|
||||
logger.warn('Removal of user {} denied due to insufficients rights')
|
||||
raise self.invalidItemException('Removal of user {} denied due to insufficients rights')
|
||||
raise self.invalidItemException(
|
||||
'Removal of user {} denied due to insufficients rights'
|
||||
)
|
||||
|
||||
assignedUserService: 'UserService'
|
||||
for assignedUserService in user.userServices.all():
|
||||
@@ -210,23 +277,29 @@ class Users(DetailHandler):
|
||||
|
||||
return 'deleted'
|
||||
|
||||
def servicesPools(self, parent, item):
|
||||
def servicesPools(self, parent: Authenticator, item):
|
||||
uuid = processUuid(item)
|
||||
user = parent.users.get(uuid=processUuid(uuid))
|
||||
res = []
|
||||
groups = list(user.getGroups())
|
||||
for i in getPoolsForGroups(groups):
|
||||
res.append({
|
||||
'id': i.uuid,
|
||||
'name': i.name,
|
||||
'thumb': i.image.thumb64 if i.image is not None else DEFAULT_THUMB_BASE64,
|
||||
'user_services_count': i.userServices.exclude(state__in=(State.REMOVED, State.ERROR)).count(),
|
||||
'state': _('With errors') if i.isRestrained() else _('Ok'),
|
||||
})
|
||||
res.append(
|
||||
{
|
||||
'id': i.uuid,
|
||||
'name': i.name,
|
||||
'thumb': i.image.thumb64
|
||||
if i.image is not None
|
||||
else DEFAULT_THUMB_BASE64,
|
||||
'user_services_count': i.userServices.exclude(
|
||||
state__in=(State.REMOVED, State.ERROR)
|
||||
).count(),
|
||||
'state': _('With errors') if i.isRestrained() else _('Ok'),
|
||||
}
|
||||
)
|
||||
|
||||
return res
|
||||
|
||||
def userServices(self, parent, item):
|
||||
def userServices(self, parent: Authenticator, item):
|
||||
uuid = processUuid(item)
|
||||
user = parent.users.get(uuid=processUuid(uuid))
|
||||
res = []
|
||||
@@ -244,7 +317,7 @@ class Groups(DetailHandler):
|
||||
|
||||
custom_methods = ['servicesPools', 'users']
|
||||
|
||||
def getItems(self, parent, item):
|
||||
def getItems(self, parent: Authenticator, item):
|
||||
try:
|
||||
multi = False
|
||||
if item is None:
|
||||
@@ -261,58 +334,87 @@ class Groups(DetailHandler):
|
||||
'comments': i.comments,
|
||||
'state': i.state,
|
||||
'type': i.is_meta and 'meta' or 'group',
|
||||
'meta_if_any': i.meta_if_any
|
||||
'meta_if_any': i.meta_if_any,
|
||||
}
|
||||
if i.is_meta:
|
||||
val['groups'] = list(x.uuid for x in i.groups.all().order_by('name'))
|
||||
val['groups'] = list(
|
||||
x.uuid for x in i.groups.all().order_by('name')
|
||||
)
|
||||
res.append(val)
|
||||
if multi or not i:
|
||||
return res
|
||||
# Add pools field if 1 item only
|
||||
res = res[0]
|
||||
result = res[0]
|
||||
if i.is_meta:
|
||||
res['pools'] = [] # Meta groups do not have "assigned "pools, they get it from groups interaction
|
||||
result[
|
||||
'pools'
|
||||
] = (
|
||||
[]
|
||||
) # Meta groups do not have "assigned "pools, they get it from groups interaction
|
||||
else:
|
||||
res['pools'] = [v.uuid for v in i.deployedServices.all()]
|
||||
return res
|
||||
result['pools'] = [v.uuid for v in i.deployedServices.all()]
|
||||
return result
|
||||
except Exception:
|
||||
logger.exception('REST groups')
|
||||
raise self.invalidItemException()
|
||||
|
||||
def getTitle(self, parent):
|
||||
try:
|
||||
return _('Groups of {0}').format(Authenticator.objects.get(uuid=processUuid(self._kwargs['parent_id'])).name)
|
||||
return _('Groups of {0}').format(
|
||||
Authenticator.objects.get(
|
||||
uuid=processUuid(self._kwargs['parent_id'])
|
||||
).name
|
||||
)
|
||||
except Exception:
|
||||
return _('Current groups')
|
||||
|
||||
def getFields(self, parent):
|
||||
return [
|
||||
{'name': {'title': _('Group'), 'visible': True, 'type': 'icon_dict', 'icon_dict': {'group': 'fa fa-group text-success', 'meta': 'fa fa-gears text-info'}}},
|
||||
{
|
||||
'name': {
|
||||
'title': _('Group'),
|
||||
'visible': True,
|
||||
'type': 'icon_dict',
|
||||
'icon_dict': {
|
||||
'group': 'fa fa-group text-success',
|
||||
'meta': 'fa fa-gears text-info',
|
||||
},
|
||||
}
|
||||
},
|
||||
{'comments': {'title': _('Comments')}},
|
||||
{'state': {'title': _('state'), 'type': 'dict', 'dict': State.dictionary()}},
|
||||
{
|
||||
'state': {
|
||||
'title': _('state'),
|
||||
'type': 'dict',
|
||||
'dict': State.dictionary(),
|
||||
}
|
||||
},
|
||||
]
|
||||
|
||||
def getTypes(self, parent, forType):
|
||||
def getTypes(self, parent: Authenticator, forType):
|
||||
tDct = {
|
||||
'group': {'name': _('Group'), 'description': _('UDS Group')},
|
||||
'meta': {'name': _('Meta group'), 'description': _('UDS Meta Group')},
|
||||
}
|
||||
types = [{
|
||||
'name': tDct[t]['name'],
|
||||
'type': t,
|
||||
'description': tDct[t]['description'],
|
||||
'icon': ''
|
||||
} for t in tDct]
|
||||
types = [
|
||||
{
|
||||
'name': tDct[t]['name'],
|
||||
'type': t,
|
||||
'description': tDct[t]['description'],
|
||||
'icon': '',
|
||||
}
|
||||
for t in tDct
|
||||
]
|
||||
|
||||
if forType is None:
|
||||
return types
|
||||
|
||||
try:
|
||||
return types[forType]
|
||||
return next(filter(lambda x: x['type'] == forType, types))
|
||||
except Exception:
|
||||
raise self.invalidRequestException()
|
||||
|
||||
def saveItem(self, parent, item):
|
||||
def saveItem(self, parent: Authenticator, item) -> None:
|
||||
group = None # Avoid warning on reference before assignment
|
||||
try:
|
||||
is_meta = self._params['type'] == 'meta'
|
||||
@@ -322,12 +424,16 @@ class Groups(DetailHandler):
|
||||
logger.debug('Meta any %s', meta_if_any)
|
||||
logger.debug('Pools: %s', pools)
|
||||
valid_fields = ['name', 'comments', 'state']
|
||||
if self._params.get('name', '') == '':
|
||||
raise RequestError(_('Group name is required'))
|
||||
fields = self.readFieldsFromParams(valid_fields)
|
||||
is_pattern = fields.get('name', '').find('pat:') == 0
|
||||
auth = parent.getInstance()
|
||||
if item is None: # Create new
|
||||
if not is_meta and not is_pattern:
|
||||
auth.createGroup(fields) # this throws an exception if there is an error (for example, this auth can't create groups)
|
||||
auth.createGroup(
|
||||
fields
|
||||
) # this throws an exception if there is an error (for example, this auth can't create groups)
|
||||
toSave = {}
|
||||
for k in valid_fields:
|
||||
toSave[k] = fields[k]
|
||||
@@ -362,13 +468,13 @@ class Groups(DetailHandler):
|
||||
raise RequestError(_('User already exists (duplicate key error)'))
|
||||
except AuthenticatorException as e:
|
||||
raise RequestError(str(e))
|
||||
except RequestError:
|
||||
raise
|
||||
except Exception:
|
||||
logger.exception('Saving group')
|
||||
raise self.invalidRequestException()
|
||||
|
||||
return self.getItems(parent, group.uuid)
|
||||
|
||||
def deleteItem(self, parent, item):
|
||||
def deleteItem(self, parent: Authenticator, item: str) -> None:
|
||||
try:
|
||||
group = parent.groups.get(uuid=item)
|
||||
|
||||
@@ -376,24 +482,28 @@ class Groups(DetailHandler):
|
||||
except Exception:
|
||||
raise self.invalidItemException()
|
||||
|
||||
return 'deleted'
|
||||
|
||||
def servicesPools(self, parent, item):
|
||||
def servicesPools(self, parent: Authenticator, item: str) -> typing.List[typing.Mapping[str, typing.Any]]:
|
||||
uuid = processUuid(item)
|
||||
group = parent.groups.get(uuid=processUuid(uuid))
|
||||
res = []
|
||||
res: typing.List[typing.Mapping[str, typing.Any]] = []
|
||||
for i in getPoolsForGroups((group,)):
|
||||
res.append({
|
||||
'id': i.uuid,
|
||||
'name': i.name,
|
||||
'thumb': i.image.thumb64 if i.image is not None else DEFAULT_THUMB_BASE64,
|
||||
'user_services_count': i.userServices.exclude(state__in=(State.REMOVED, State.ERROR)).count(),
|
||||
'state': _('With errors') if i.isRestrained() else _('Ok'),
|
||||
})
|
||||
res.append(
|
||||
{
|
||||
'id': i.uuid,
|
||||
'name': i.name,
|
||||
'thumb': i.image.thumb64
|
||||
if i.image is not None
|
||||
else DEFAULT_THUMB_BASE64,
|
||||
'user_services_count': i.userServices.exclude(
|
||||
state__in=(State.REMOVED, State.ERROR)
|
||||
).count(),
|
||||
'state': _('With errors') if i.isRestrained() else _('Ok'),
|
||||
}
|
||||
)
|
||||
|
||||
return res
|
||||
|
||||
def users(self, parent, item):
|
||||
def users(self, parent: Authenticator, item: str) -> typing.List[typing.Mapping[str, typing.Any]]:
|
||||
uuid = processUuid(item)
|
||||
group = parent.groups.get(uuid=processUuid(uuid))
|
||||
|
||||
@@ -403,10 +513,10 @@ class Groups(DetailHandler):
|
||||
'name': user.name,
|
||||
'real_name': user.real_name,
|
||||
'state': user.state,
|
||||
'last_access': user.last_access
|
||||
'last_access': user.last_access,
|
||||
}
|
||||
|
||||
res = []
|
||||
res: typing.List[typing.Mapping[str, typing.Any]] = []
|
||||
if group.is_meta:
|
||||
# Get all users for everygroup and
|
||||
groups = getGroupsFromMeta((group,))
|
||||
|
@@ -37,12 +37,10 @@ from ..handlers import Handler
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class UDSVersion(Handler):
|
||||
authenticated = False # Version requests are public
|
||||
name = 'version'
|
||||
|
||||
def get(self) -> typing.MutableMapping[str, typing.Any]:
|
||||
return {
|
||||
'version': VERSION,
|
||||
'build': VERSION_STAMP
|
||||
}
|
||||
return {'version': VERSION, 'build': VERSION_STAMP}
|
||||
|
@@ -1066,7 +1066,11 @@ class ModelHandler(BaseModelHandler):
|
||||
if tags:
|
||||
logger.debug('Updating tags: %s', tags)
|
||||
item.tags.set(
|
||||
[Tag.objects.get_or_create(tag=val)[0] for val in tags if val != '']
|
||||
[
|
||||
Tag.objects.get_or_create(tag=val)[0]
|
||||
for val in tags
|
||||
if val != ''
|
||||
]
|
||||
)
|
||||
elif isinstance(
|
||||
tags, list
|
||||
|
@@ -1,7 +1,7 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
#
|
||||
# Copyright (c) 2012-2019 Virtual Cable S.L.
|
||||
# Copyright (c) 2012-2021 Virtual Cable S.L.U.
|
||||
# All rights reserved.
|
||||
#
|
||||
# Redistribution and use in source and binary forms, with or without modification,
|
||||
@@ -12,7 +12,7 @@
|
||||
# * Redistributions in binary form must reproduce the above copyright notice,
|
||||
# this list of conditions and the following disclaimer in the documentation
|
||||
# and/or other materials provided with the distribution.
|
||||
# * Neither the name of Virtual Cable S.L. nor the names of its contributors
|
||||
# * Neither the name of Virtual Cable S.L.U. nor the names of its contributors
|
||||
# may be used to endorse or promote products derived from this software
|
||||
# without specific prior written permission.
|
||||
#
|
||||
@@ -52,6 +52,7 @@ class ContentProcessor:
|
||||
"""
|
||||
Process contents (request/response) so Handlers can manage them
|
||||
"""
|
||||
|
||||
mime_type: typing.ClassVar[str] = ''
|
||||
extensions: typing.ClassVar[typing.Iterable[str]] = []
|
||||
|
||||
@@ -81,7 +82,9 @@ class ContentProcessor:
|
||||
Converts an obj to a response of specific type (json, XML, ...)
|
||||
This is done using "render" method of specific type
|
||||
"""
|
||||
return http.HttpResponse(content=self.render(obj), content_type=self.mime_type + "; charset=utf-8")
|
||||
return http.HttpResponse(
|
||||
content=self.render(obj), content_type=self.mime_type + "; charset=utf-8"
|
||||
)
|
||||
|
||||
def render(self, obj: typing.Any):
|
||||
"""
|
||||
@@ -98,7 +101,7 @@ class ContentProcessor:
|
||||
return obj
|
||||
|
||||
if isinstance(obj, dict):
|
||||
return {k:ContentProcessor.procesForRender(v) for k, v in obj.items()}
|
||||
return {k: ContentProcessor.procesForRender(v) for k, v in obj.items()}
|
||||
|
||||
if isinstance(obj, (list, tuple, types.GeneratorType)):
|
||||
return [ContentProcessor.procesForRender(v) for v in obj]
|
||||
@@ -117,11 +120,15 @@ class MarshallerProcessor(ContentProcessor):
|
||||
If we have a simple marshaller for processing contents
|
||||
this class will allow us to set up a new one simply setting "marshaller"
|
||||
"""
|
||||
|
||||
marshaller: typing.ClassVar[typing.Any] = None
|
||||
|
||||
def processParameters(self) -> typing.MutableMapping[str, typing.Any]:
|
||||
try:
|
||||
if self._request.META.get('CONTENT_LENGTH', '0') == '0' or not self._request.body:
|
||||
if (
|
||||
self._request.META.get('CONTENT_LENGTH', '0') == '0'
|
||||
or not self._request.body
|
||||
):
|
||||
return self.processGetParameters()
|
||||
# logger.debug('Body: >>{}<< {}'.format(self._request.body, len(self._request.body)))
|
||||
res = self.marshaller.loads(self._request.body.decode('utf8'))
|
||||
@@ -143,14 +150,16 @@ class JsonProcessor(MarshallerProcessor):
|
||||
"""
|
||||
Provides JSON content processor
|
||||
"""
|
||||
|
||||
mime_type = 'application/json'
|
||||
extensions = ['json']
|
||||
marshaller = json # type: ignore
|
||||
|
||||
|
||||
# ---------------
|
||||
# XML Processor
|
||||
# ---------------
|
||||
#===============================================================================
|
||||
# ===============================================================================
|
||||
# class XMLProcessor(MarshallerProcessor):
|
||||
# """
|
||||
# Provides XML content processor
|
||||
@@ -158,12 +167,14 @@ class JsonProcessor(MarshallerProcessor):
|
||||
# mime_type = 'application/xml'
|
||||
# extensions = ['xml']
|
||||
# marshaller = xml_marshaller
|
||||
#===============================================================================
|
||||
# ===============================================================================
|
||||
|
||||
|
||||
processors_list = (JsonProcessor,)
|
||||
default_processor: typing.Type[ContentProcessor] = JsonProcessor
|
||||
available_processors_mime_dict: typing.Dict[str, typing.Type[ContentProcessor]] = {cls.mime_type: cls for cls in processors_list}
|
||||
available_processors_mime_dict: typing.Dict[str, typing.Type[ContentProcessor]] = {
|
||||
cls.mime_type: cls for cls in processors_list
|
||||
}
|
||||
available_processors_ext_dict: typing.Dict[str, typing.Type[ContentProcessor]] = {}
|
||||
for cls in processors_list:
|
||||
for ext in cls.extensions:
|
||||
|
@@ -85,7 +85,7 @@ default_app_config = 'uds.UDSAppConfig'
|
||||
|
||||
@receiver(connection_created)
|
||||
def extend_sqlite(connection=None, **kwargs):
|
||||
if connection.vendor == "sqlite":
|
||||
if connection and connection.vendor == "sqlite":
|
||||
logger.debug('Connection vendor is sqlite, extending methods')
|
||||
cursor = connection.cursor()
|
||||
cursor.execute('PRAGMA synchronous=OFF')
|
||||
@@ -95,3 +95,4 @@ def extend_sqlite(connection=None, **kwargs):
|
||||
connection.connection.create_function("MIN", 2, min)
|
||||
connection.connection.create_function("MAX", 2, max)
|
||||
connection.connection.create_function("CEIL", 1, math.ceil)
|
||||
|
||||
|
@@ -31,6 +31,7 @@
|
||||
import logging
|
||||
|
||||
from django.http import HttpResponse
|
||||
from django.middleware import csrf
|
||||
from django.shortcuts import render
|
||||
from django.template import RequestContext, loader
|
||||
from django.utils.translation import ugettext as _
|
||||
@@ -41,10 +42,22 @@ from uds.core.util.decorators import denyBrowsers
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
CSRF_FIELD = 'csrfmiddlewaretoken'
|
||||
|
||||
|
||||
@denyBrowsers(browsers=['ie<10'])
|
||||
@webLoginRequired(admin=True)
|
||||
def index(request):
|
||||
return render(request, 'uds/admin/index.html')
|
||||
# Gets csrf token
|
||||
csrf_token = csrf.get_token(request)
|
||||
if csrf_token is not None:
|
||||
csrf_token = str(csrf_token)
|
||||
|
||||
return render(
|
||||
request,
|
||||
'uds/admin/index.html',
|
||||
{'csrf_field': CSRF_FIELD, 'csrf_token': csrf_token},
|
||||
)
|
||||
|
||||
|
||||
@denyBrowsers(browsers=['ie<10'])
|
||||
|
@@ -38,11 +38,7 @@ from django.utils.translation import ugettext_noop as _
|
||||
from uds.core import auths
|
||||
from uds.core.util import net
|
||||
from uds.core.ui import gui
|
||||
from uds.core.util.request import getRequest
|
||||
|
||||
# Not imported at runtime, just for type checking
|
||||
if typing.TYPE_CHECKING:
|
||||
from django.http import HttpRequest # pylint: disable=ungrouped-imports
|
||||
from uds.core.util.request import getRequest, ExtendedHttpRequest
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
@@ -51,7 +47,7 @@ class IPAuth(auths.Authenticator):
|
||||
acceptProxy = gui.CheckBoxField(
|
||||
label=_('Accept proxy'),
|
||||
defvalue=gui.FALSE,
|
||||
order=3,
|
||||
order=50,
|
||||
tooltip=_(
|
||||
'If checked, requests via proxy will get FORWARDED ip address'
|
||||
' (take care with this bein checked, can take internal IP addresses from internet)'
|
||||
@@ -59,6 +55,14 @@ class IPAuth(auths.Authenticator):
|
||||
tab=gui.ADVANCED_TAB
|
||||
)
|
||||
|
||||
visibleFromNets = gui.TextField(
|
||||
order=50,
|
||||
label=_('Visible only from this networks'),
|
||||
defvalue='',
|
||||
tooltip=_('This authenticator will be visible only from these networks. Leave empty to allow all networks'),
|
||||
tab=gui.ADVANCED_TAB
|
||||
)
|
||||
|
||||
typeName = _('IP Authenticator')
|
||||
typeType = 'IPAuth'
|
||||
typeDescription = _('IP Authenticator')
|
||||
@@ -93,6 +97,18 @@ class IPAuth(auths.Authenticator):
|
||||
return True
|
||||
return False
|
||||
|
||||
def isVisibleFrom(self, request: 'ExtendedHttpRequest'):
|
||||
"""
|
||||
Used by the login interface to determine if the authenticator is visible on the login page.
|
||||
"""
|
||||
validNets = self.visibleFromNets.value.strip()
|
||||
try:
|
||||
if not validNets or net.ipInNetwork(request.ip, validNets):
|
||||
return True
|
||||
except Exception as e:
|
||||
logger.error('Invalid network for IP auth: %s', e)
|
||||
return False
|
||||
|
||||
def internalAuthenticate(self, username: str, credentials: str, groupsManager: 'auths.GroupsManager') -> bool:
|
||||
# In fact, username does not matter, will get IP from request
|
||||
username = self.getIp() # Override provided username and use source IP
|
||||
@@ -108,7 +124,7 @@ class IPAuth(auths.Authenticator):
|
||||
def check(self):
|
||||
return _("All seems to be fine.")
|
||||
|
||||
def getJavascript(self, request: 'HttpRequest') -> typing.Optional[str]:
|
||||
def getJavascript(self, request: 'ExtendedHttpRequest') -> typing.Optional[str]:
|
||||
# We will authenticate ip here, from request.ip
|
||||
# If valid, it will simply submit form with ip submited and a cached generated random password
|
||||
ip = self.getIp()
|
||||
|
@@ -158,6 +158,14 @@ class InternalDBAuth(auths.Authenticator):
|
||||
|
||||
groupsManager.validate([g.name for g in user.groups.all()])
|
||||
|
||||
def getRealName(self, username: str) -> str:
|
||||
# Return the real name of the user, if it is set
|
||||
try:
|
||||
user = self.dbAuthenticator().users.get(name=username, state=State.ACTIVE)
|
||||
return user.real_name or username
|
||||
except Exception:
|
||||
return super().getRealName(username)
|
||||
|
||||
def createUser(self, usrData):
|
||||
pass
|
||||
|
||||
|
@@ -123,6 +123,7 @@ class RadiusAuth(auths.Authenticator):
|
||||
self.secret.value.encode(),
|
||||
authPort=self.port.num(),
|
||||
nasIdentifier=self.nasIdentifier.value,
|
||||
appClassPrefix=self.appClassPrefix.value,
|
||||
)
|
||||
|
||||
def authenticate(
|
||||
|
@@ -501,7 +501,9 @@ class RegexLdap(auths.Authenticator):
|
||||
usr = self.__getUser(username)
|
||||
|
||||
if usr is None:
|
||||
authLogLogin(getRequest(), self.dbAuthenticator(), username, 'Invalid user')
|
||||
authLogLogin(
|
||||
getRequest(), self.dbAuthenticator(), username, 'Invalid user'
|
||||
)
|
||||
return False
|
||||
|
||||
try:
|
||||
@@ -510,7 +512,9 @@ class RegexLdap(auths.Authenticator):
|
||||
usr['dn'], credentials
|
||||
) # Will raise an exception if it can't connect
|
||||
except:
|
||||
authLogLogin(getRequest(), self.dbAuthenticator(), username, 'Invalid password')
|
||||
authLogLogin(
|
||||
getRequest(), self.dbAuthenticator(), username, 'Invalid password'
|
||||
)
|
||||
return False
|
||||
|
||||
groupsManager.validate(self.__getGroups(usr))
|
||||
|
@@ -1,7 +1,7 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
#
|
||||
# Copyright (c) 2012-2019 Virtual Cable S.L.
|
||||
# Copyright (c) 2012-2021 Virtual Cable S.L.U.
|
||||
# All rights reserved.
|
||||
#
|
||||
# Redistribution and use in source and binary forms, with or without modification,
|
||||
@@ -12,7 +12,7 @@
|
||||
# * Redistributions in binary form must reproduce the above copyright notice,
|
||||
# this list of conditions and the following disclaimer in the documentation
|
||||
# and/or other materials provided with the distribution.
|
||||
# * Neither the name of Virtual Cable S.L. nor the names of its contributors
|
||||
# * Neither the name of Virtual Cable S.L.U. nor the names of its contributors
|
||||
# may be used to endorse or promote products derived from this software
|
||||
# without specific prior written permission.
|
||||
#
|
||||
@@ -38,7 +38,10 @@ from uds.core.ui import gui
|
||||
from uds.core import auths
|
||||
|
||||
if typing.TYPE_CHECKING:
|
||||
from django.http import HttpRequest, HttpResponse # pylint: disable=ungrouped-imports
|
||||
from django.http import (
|
||||
HttpRequest,
|
||||
HttpResponse,
|
||||
) # pylint: disable=ungrouped-imports
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
@@ -125,7 +128,9 @@ class SampleAuth(auths.Authenticator):
|
||||
# unserialization, and at this point all will be default values
|
||||
# so self.groups.value will be []
|
||||
if values and len(self.groups.value) < 2:
|
||||
raise auths.Authenticator.ValidationException(_('We need more than two groups!'))
|
||||
raise auths.Authenticator.ValidationException(
|
||||
_('We need more than two groups!')
|
||||
)
|
||||
|
||||
def searchUsers(self, pattern: str) -> typing.Iterable[typing.Dict[str, str]]:
|
||||
"""
|
||||
@@ -137,7 +142,13 @@ class SampleAuth(auths.Authenticator):
|
||||
facility for users. In our case, we will simply return a list of users
|
||||
(array of dictionaries with ids and names) with the pattern plus 1..10
|
||||
"""
|
||||
return [{'id': '{0}-{1}'.format(pattern, a), 'name': '{0} number {1}'.format(pattern, a)} for a in range(1, 10)]
|
||||
return [
|
||||
{
|
||||
'id': '{0}-{1}'.format(pattern, a),
|
||||
'name': '{0} number {1}'.format(pattern, a),
|
||||
}
|
||||
for a in range(1, 10)
|
||||
]
|
||||
|
||||
def searchGroups(self, pattern: str) -> typing.Iterable[typing.Dict[str, str]]:
|
||||
"""
|
||||
@@ -154,7 +165,9 @@ class SampleAuth(auths.Authenticator):
|
||||
res.append({'id': g, 'name': ''})
|
||||
return res
|
||||
|
||||
def authenticate(self, username: str, credentials: str, groupsManager: 'auths.GroupsManager') -> bool:
|
||||
def authenticate(
|
||||
self, username: str, credentials: str, groupsManager: 'auths.GroupsManager'
|
||||
) -> bool:
|
||||
"""
|
||||
This method is invoked by UDS whenever it needs an user to be authenticated.
|
||||
It is used from web interface, but also from administration interface to
|
||||
@@ -196,7 +209,9 @@ class SampleAuth(auths.Authenticator):
|
||||
|
||||
:note: groupsManager is an in/out parameter
|
||||
"""
|
||||
if username != credentials: # All users with same username and password are allowed
|
||||
if (
|
||||
username != credentials
|
||||
): # All users with same username and password are allowed
|
||||
return False
|
||||
|
||||
# Now the tricky part. We will make this user belong to groups that contains at leat
|
||||
@@ -247,11 +262,17 @@ class SampleAuth(auths.Authenticator):
|
||||
# I know, this is a bit ugly, but this is just a sample :-)
|
||||
|
||||
res = '<p>Login name: <input id="logname" type="text"/></p>'
|
||||
res += '<p><a href="" onclick="window.location.replace(\'' + self.callbackUrl() + '?user='
|
||||
res += (
|
||||
'<p><a href="" onclick="window.location.replace(\''
|
||||
+ self.callbackUrl()
|
||||
+ '?user='
|
||||
)
|
||||
res += '\' + $(\'#logname\').val()); return false;">Login</a></p>'
|
||||
return res
|
||||
|
||||
def authCallback(self, parameters: typing.Dict[str, typing.Any], gm: 'auths.GroupsManager') -> typing.Optional[str]:
|
||||
def authCallback(
|
||||
self, parameters: typing.Dict[str, typing.Any], gm: 'auths.GroupsManager'
|
||||
) -> typing.Optional[str]:
|
||||
"""
|
||||
We provide this as a sample of callback for an user.
|
||||
We will accept all petitions that has "user" parameter
|
||||
@@ -286,6 +307,7 @@ class SampleAuth(auths.Authenticator):
|
||||
Here, we will set the state to "Inactive" and realName to the same as username, but twice :-)
|
||||
"""
|
||||
from uds.core.util.state import State
|
||||
|
||||
usrData['real_name'] = usrData['name'] + ' ' + usrData['name']
|
||||
usrData['state'] = State.INACTIVE
|
||||
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user