diff --git a/server/src/tests/web/util/test_services.py b/server/src/tests/web/util/test_services.py index 806509e4f..038434250 100644 --- a/server/src/tests/web/util/test_services.py +++ b/server/src/tests/web/util/test_services.py @@ -67,7 +67,7 @@ class TestGetServicesData(UDSTransactionTestCase): self.request.ip_version = 4 self.request.ip_proxy = '127.0.0.1' self.request.os = osd.DetectedOsInfo( - osd.KnownOS.Linux, osd.KnownBrowser.Firefox, 'Windows 10' + osd.KnownOS.LINUX, osd.KnownBrowser.FIREFOX, 'Windows 10' ) return super().setUp() diff --git a/server/src/uds/REST/methods/actor_v3.py b/server/src/uds/REST/methods/actor_v3.py index 1fd2ca806..48cb4c415 100644 --- a/server/src/uds/REST/methods/actor_v3.py +++ b/server/src/uds/REST/methods/actor_v3.py @@ -53,6 +53,7 @@ from uds.core.util import log, security from uds.core.util.state import State from uds.core.util.cache import Cache from uds.core.util.config import GlobalConfig +from uds.core.util.os_detector import KnownOS from uds.core import exceptions from uds.models.service import ServiceTokenAlias @@ -318,6 +319,7 @@ class Register(ActorV3Action): }, 'token': secrets.token_urlsafe(36), 'kind': RegisteredServers.ServerType.ACTOR, + 'os_type': self._params.get('os', KnownOS.UNKNOWN.os_name()), 'stamp': getSqlDatetime(), } diff --git a/server/src/uds/REST/methods/servers.py b/server/src/uds/REST/methods/servers.py index 5ed70ce11..bd8f01416 100644 --- a/server/src/uds/REST/methods/servers.py +++ b/server/src/uds/REST/methods/servers.py @@ -37,6 +37,7 @@ from django.utils.translation import gettext_lazy as _ from uds import models from uds.core.util.model import getSqlDatetimeAsUnix, getSqlDatetime +from uds.core.util.os_detector import KnownOS from uds.REST import Handler from uds.REST.exceptions import RequestError, NotFound from uds.REST.model import ModelHandler, OK @@ -74,6 +75,7 @@ class ServerRegister(Handler): token=secrets.token_urlsafe(36), stamp=getSqlDatetime(), kind=self._params['type'], + os_type=typing.cast(str, self._params.get('os', KnownOS.UNKNOWN.os_name())).lower(), data=self._params.get('data', None), ) except Exception as e: @@ -94,6 +96,7 @@ class ServersTokens(ModelHandler): {'username': {'title': _('Issued by')}}, {'hostname': {'title': _('Origin')}}, {'type': {'title': _('Type')}}, + {'os': {'title': _('OS')}}, {'ip': {'title': _('IP')}}, ] @@ -107,6 +110,7 @@ class ServersTokens(ModelHandler): 'hostname': item.hostname, 'token': item.token, 'type': models.RegisteredServers.ServerType(item.kind).as_str(), # type is a reserved word, so we use "kind" instead on model + 'os': item.os_type, } def delete(self) -> str: diff --git a/server/src/uds/REST/methods/tunnel.py b/server/src/uds/REST/methods/tunnel.py index 4ac9cdc08..219cc434a 100644 --- a/server/src/uds/REST/methods/tunnel.py +++ b/server/src/uds/REST/methods/tunnel.py @@ -34,6 +34,7 @@ import typing from uds import models from uds.core.util.model import getSqlDatetimeAsUnix, getSqlDatetime +from uds.core.util.os_detector import KnownOS from uds.REST import Handler from uds.REST import AccessDenied from uds.core.auths.auth import isTrustedSource @@ -161,4 +162,5 @@ class TunnelRegister(ServerRegister): # Just a compatibility method for old tunnel servers def post(self) -> typing.MutableMapping[str, typing.Any]: self._params['type'] = models.RegisteredServers.ServerType.TUNNEL - return super().post() \ No newline at end of file + self._params['os'] = KnownOS.LINUX.os_name() # Legacy tunnels are always linux + return super().post() diff --git a/server/src/uds/core/util/os_detector.py b/server/src/uds/core/util/os_detector.py index 46acd14cf..c179adb17 100644 --- a/server/src/uds/core/util/os_detector.py +++ b/server/src/uds/core/util/os_detector.py @@ -45,66 +45,69 @@ class DetectedOsInfo(typing.NamedTuple): class KnownOS(enum.Enum): - Linux = ('Linux', 'armv7l') - ChromeOS = ('CrOS',) - WindowsPhone = ('Windows Phone',) - Windows = ('Windows',) - MacOS = ('MacOsX',) - Android = ('Android',) - iPad = ('iPad',) # - iPhone = ('iPhone',) # In fact, these are IOS both, but we can diferentiate them + LINUX = ('Linux', 'armv7l') + CHROME_OS = ('CrOS',) + WINDOWS_PHONE = ('Windows Phone',) + WINDOWS = ('Windows',) + MAC_OS = ('MacOsX',) + ANDROID = ('Android',) + IPAD = ('iPad',) # + IPHONE = ('iPhone',) # In fact, these are IOS both, but we can diferentiate them WYSE = ('WYSE',) - Unknown = ('Unknown',) + UNKNOWN = ('Unknown',) def os_name(self): return self.value[0].lower() -knownOss = tuple(os for os in KnownOS if os != KnownOS.Unknown) +knownOss = tuple(os for os in KnownOS if os != KnownOS.UNKNOWN) -allOss = knownOss + (KnownOS.Unknown,) -desktopOss = (KnownOS.Linux, KnownOS.Windows, KnownOS.MacOS) +allOss = knownOss + (KnownOS.UNKNOWN,) +desktopOss = (KnownOS.LINUX, KnownOS.WINDOWS, KnownOS.MAC_OS) mobilesODD = list(set(allOss) - set(desktopOss)) -DEFAULT_OS = KnownOS.Windows +DEFAULT_OS = KnownOS.WINDOWS class KnownBrowser(enum.Enum): # Known browsers - Firefox = 'Firefox' - Seamonkey = 'Seamonkey' - Chrome = 'Chrome' - Chromium = 'Chromium' - Safari = 'Safari' - Opera = 'Opera' - IExplorer = 'Explorer' - Other = 'Other' + FIREFOX = 'Firefox' + SEAMONKEY = 'Seamonkey' + CHROME = 'Chrome' + CHROMIUM = 'Chromium' + SAFARI = 'Safari' + OPERA = 'Opera' + IEXPLORER = 'Explorer' + EDGE = 'Edge' + OTHER = 'Other' knownBrowsers = tuple(KnownBrowser) browsersREs: typing.Dict[KnownBrowser, typing.Tuple] = { - KnownBrowser.Firefox: (re.compile(r'Firefox/([0-9.]+)'),), - KnownBrowser.Seamonkey: (re.compile(r'Seamonkey/([0-9.]+)'),), - KnownBrowser.Chrome: (re.compile(r'Chrome/([0-9.]+)'),), - KnownBrowser.Chromium: (re.compile(r'Chromium/([0-9.]+)'),), - KnownBrowser.Safari: (re.compile(r'Safari/([0-9.]+)'),), - KnownBrowser.Opera: ( + KnownBrowser.FIREFOX: (re.compile(r'Firefox/([0-9.]+)'),), + KnownBrowser.SEAMONKEY: (re.compile(r'Seamonkey/([0-9.]+)'),), + KnownBrowser.CHROME: (re.compile(r'Chrome/([0-9.]+)'),), + KnownBrowser.CHROMIUM: (re.compile(r'Chromium/([0-9.]+)'),), + KnownBrowser.SAFARI: (re.compile(r'Safari/([0-9.]+)'),), + KnownBrowser.OPERA: ( re.compile(r'OPR/([0-9.]+)'), re.compile(r'Opera/([0-9.]+)'), ), - KnownBrowser.IExplorer: ( + KnownBrowser.IEXPLORER: ( re.compile(r';MSIE ([0-9.]+);'), re.compile(r'Trident/.*rv:([0-9.]+)'), ), + KnownBrowser.EDGE: (re.compile(r'Edg/([0-9.]+)'),), } browserRules: typing.Dict[KnownBrowser, typing.Tuple] = { - KnownBrowser.Chrome: (KnownBrowser.Chrome, (KnownBrowser.Chromium, KnownBrowser.Opera)), - KnownBrowser.Firefox: (KnownBrowser.Firefox, (KnownBrowser.Seamonkey,)), - KnownBrowser.IExplorer: (KnownBrowser.IExplorer, ()), - KnownBrowser.Chromium: (KnownBrowser.Chromium, (KnownBrowser.Chrome,)), - KnownBrowser.Safari: (KnownBrowser.Safari, (KnownBrowser.Chrome, KnownBrowser.Chromium, KnownBrowser.Opera)), - KnownBrowser.Seamonkey: (KnownBrowser.Seamonkey, (KnownBrowser.Firefox,)), - KnownBrowser.Opera: (KnownBrowser.Opera, ()), + KnownBrowser.EDGE: (KnownBrowser.EDGE, ()), + KnownBrowser.CHROME: (KnownBrowser.CHROME, (KnownBrowser.CHROMIUM, KnownBrowser.OPERA)), + KnownBrowser.FIREFOX: (KnownBrowser.FIREFOX, (KnownBrowser.SEAMONKEY,)), + KnownBrowser.IEXPLORER: (KnownBrowser.IEXPLORER, ()), + KnownBrowser.CHROMIUM: (KnownBrowser.CHROMIUM, (KnownBrowser.CHROME,)), + KnownBrowser.SAFARI: (KnownBrowser.SAFARI, (KnownBrowser.CHROME, KnownBrowser.CHROMIUM, KnownBrowser.OPERA)), + KnownBrowser.SEAMONKEY: (KnownBrowser.SEAMONKEY, (KnownBrowser.FIREFOX,)), + KnownBrowser.OPERA: (KnownBrowser.OPERA, ()), } @@ -114,9 +117,9 @@ def getOsFromUA( """ Basic OS Client detector (very basic indeed :-)) """ - ua = ua or KnownOS.Unknown.value[0] + ua = ua or KnownOS.UNKNOWN.value[0] - res = DetectedOsInfo(os=KnownOS.Unknown, browser=KnownBrowser.Other, version='0.0') + res = DetectedOsInfo(os=KnownOS.UNKNOWN, browser=KnownBrowser.OTHER, version='0.0') found: bool = False for os in knownOss: if found: diff --git a/server/src/uds/migrations/0046_auto_20230712_0404.py b/server/src/uds/migrations/0046_auto_20230712_0404.py index e0911db9c..515431bd2 100644 --- a/server/src/uds/migrations/0046_auto_20230712_0404.py +++ b/server/src/uds/migrations/0046_auto_20230712_0404.py @@ -1,13 +1,19 @@ import typing from django.db import migrations, models +from uds.core.util.os_detector import KnownOS ACTOR_TYPE = 2 # Hardcoded value from uds/models/registered_servers.py -def migrate_old_actor_tokens(apps, schema_editor): +def migrate_old_data(apps, schema_editor): try: RegisteredServers = apps.get_model('uds', 'RegisteredServers') ActorToken = apps.get_model('uds', 'ActorToken') + + # Current Registered servers are tunnel servers, and all tunnel servers are linux os, so update ip + RegisteredServers.objects.all().update(os_type=KnownOS.LINUX.os_name()) + + # Now append actors to registered servers, with "unknown" os type (legacy) for token in ActorToken.objects.all(): RegisteredServers.objects.create( username=token.username, @@ -18,6 +24,7 @@ def migrate_old_actor_tokens(apps, schema_editor): token=token.token, stamp=token.stamp, kind=ACTOR_TYPE, + os_type=KnownOS.UNKNOWN.os_name(), data={ 'mac': token.mac, 'pre_command': token.pre_command, @@ -32,7 +39,7 @@ def migrate_old_actor_tokens(apps, schema_editor): # Pytest is running this method twice?? raise e -def revert_migration_to_old_actor_tokens(apps, schema_editor): +def revert_old_data(apps, schema_editor): RegisteredServers = apps.get_model('uds', 'RegisteredServers') ActorToken = apps.get_model('uds', 'ActorToken') for server in RegisteredServers.objects.filter(kind=ACTOR_TYPE): @@ -83,9 +90,14 @@ class Migration(migrations.Migration): name="data", field=models.JSONField(blank=True, default=None, null=True), ), + migrations.AddField( + model_name="registeredservers", + name="os_type", + field=models.CharField(default="unknown", max_length=32), + ), migrations.RunPython( - migrate_old_actor_tokens, - revert_migration_to_old_actor_tokens, + migrate_old_data, + revert_old_data, atomic=True, ), migrations.DeleteModel( diff --git a/server/src/uds/models/registered_servers.py b/server/src/uds/models/registered_servers.py index 23895ad7a..1525d302f 100644 --- a/server/src/uds/models/registered_servers.py +++ b/server/src/uds/models/registered_servers.py @@ -30,6 +30,7 @@ Author: Adolfo Gómez, dkmaster at dkmon dot com ''' import typing import enum +from uds.core.util.os_detector import KnownOS from django.db import models from uds.core.util.request import ExtendedHttpRequest @@ -72,6 +73,7 @@ class RegisteredServers(models.Model): stamp = models.DateTimeField() # Date creation or validation of this entry kind = models.IntegerField(default=ServerType.TUNNEL.value) # Defaults to tunnel server, so we can migrate from previous versions + os_type = models.CharField(max_length=32, default=KnownOS.UNKNOWN.os_name()) # os type of server (linux, windows, etc..) data = models.JSONField(null=True, blank=True, default=None) diff --git a/server/src/uds/transports/RDP/rdp.py b/server/src/uds/transports/RDP/rdp.py index cb45e3b69..25c23b973 100644 --- a/server/src/uds/transports/RDP/rdp.py +++ b/server/src/uds/transports/RDP/rdp.py @@ -154,7 +154,7 @@ class RDPTransport(BaseRDPTransport): 'address': r.address, } - if os == os_detector.KnownOS.Windows: + if os == os_detector.KnownOS.WINDOWS: r.customParameters = self.customParametersWindows.value if password: r.password = '{password}' # nosec: password is not hardcoded @@ -163,7 +163,7 @@ class RDPTransport(BaseRDPTransport): 'as_file': r.as_file, } ) - elif os == os_detector.KnownOS.Linux: + elif os == os_detector.KnownOS.LINUX: r.customParameters = self.customParameters.value sp.update( { @@ -171,7 +171,7 @@ class RDPTransport(BaseRDPTransport): 'address': r.address, } ) - elif os == os_detector.KnownOS.MacOS: + elif os == os_detector.KnownOS.MAC_OS: r.customParameters = self.customParametersMAC.value sp.update( { diff --git a/server/src/uds/transports/RDP/rdp_file.py b/server/src/uds/transports/RDP/rdp_file.py index ae7f6a6d3..63effebf9 100644 --- a/server/src/uds/transports/RDP/rdp_file.py +++ b/server/src/uds/transports/RDP/rdp_file.py @@ -78,7 +78,7 @@ class RDPFile: width: typing.Union[str, int], height: typing.Union[str, int], bpp: str, - target: OsDetector.KnownOS = OsDetector.KnownOS.Windows, + target: OsDetector.KnownOS = OsDetector.KnownOS.WINDOWS, ): self.width = str(width) self.height = str(height) @@ -88,9 +88,9 @@ class RDPFile: def get(self): if self.target in ( - OsDetector.KnownOS.Windows, - OsDetector.KnownOS.Linux, - OsDetector.KnownOS.MacOS, + OsDetector.KnownOS.WINDOWS, + OsDetector.KnownOS.LINUX, + OsDetector.KnownOS.MAC_OS, ): return self.getGeneric() # Unknown target @@ -120,7 +120,7 @@ class RDPFile: params.append('/smartcard') if self.redirectAudio: - if self.alsa and self.target != OsDetector.KnownOS.MacOS: + if self.alsa and self.target != OsDetector.KnownOS.MAC_OS: params.append('/sound:sys:alsa,format:1,quality:high') params.append('/microphone:sys:alsa') else: @@ -132,7 +132,7 @@ class RDPFile: params.append('/video') if self.redirectDrives != 'false': - if self.target in (OsDetector.KnownOS.Linux, OsDetector.KnownOS.MacOS): + if self.target in (OsDetector.KnownOS.LINUX, OsDetector.KnownOS.MAC_OS): params.append('/drive:home,$HOME') else: params.append('/drive:Users,/Users') @@ -158,7 +158,7 @@ class RDPFile: params.append('/multimon') if self.fullScreen: - if self.target != OsDetector.KnownOS.MacOS: + if self.target != OsDetector.KnownOS.MAC_OS: params.append('/f') else: # On mac, will fix this later... params.append('/w:#WIDTH#') @@ -233,7 +233,7 @@ class RDPFile: if self.username: res += 'username:s:' + self.username + '\n' res += 'domain:s:' + self.domain + '\n' - if self.target == OsDetector.KnownOS.Windows: + if self.target == OsDetector.KnownOS.WINDOWS: res += 'password 51:b:' + password + '\n' res += 'alternate shell:s:' + '\n' @@ -299,7 +299,7 @@ class RDPFile: # res += 'camerastoredirect:s:*\n' # If target is windows, add customParameters - if self.target == OsDetector.KnownOS.Windows: + if self.target == OsDetector.KnownOS.WINDOWS: if self.customParameters and self.customParameters.strip() != '': res += self.customParameters.strip() + '\n' diff --git a/server/src/uds/transports/RDP/rdptunnel.py b/server/src/uds/transports/RDP/rdptunnel.py index c2a4c7fa4..031073ae2 100644 --- a/server/src/uds/transports/RDP/rdptunnel.py +++ b/server/src/uds/transports/RDP/rdptunnel.py @@ -204,7 +204,7 @@ class TRDPTransport(BaseRDPTransport): 'this_server': request.build_absolute_uri('/'), } - if os.os == os_detector.KnownOS.Windows: + if os.os == os_detector.KnownOS.WINDOWS: r.customParameters = self.customParametersWindows.value if password: r.password = '{password}' # nosec: password is not hardcoded @@ -213,14 +213,14 @@ class TRDPTransport(BaseRDPTransport): 'as_file': r.as_file, } ) - elif os.os == os_detector.KnownOS.Linux: + elif os.os == os_detector.KnownOS.LINUX: r.customParameters = self.customParameters.value sp.update( { 'as_new_xfreerdp_params': r.as_new_xfreerdp_params, } ) - elif os.os == os_detector.KnownOS.MacOS: + elif os.os == os_detector.KnownOS.MAC_OS: r.customParameters = self.customParametersMAC.value sp.update( { diff --git a/server/src/uds/transports/X2GO/x2go_base.py b/server/src/uds/transports/X2GO/x2go_base.py index 47043e96f..7cb6fe206 100644 --- a/server/src/uds/transports/X2GO/x2go_base.py +++ b/server/src/uds/transports/X2GO/x2go_base.py @@ -65,7 +65,7 @@ class BaseX2GOTransport(transports.Transport): iconFile = 'x2go.png' protocol = transports.protocols.X2GO - supportedOss = (OsDetector.KnownOS.Linux, OsDetector.KnownOS.Windows) + supportedOss = (OsDetector.KnownOS.LINUX, OsDetector.KnownOS.WINDOWS) fixedName = gui.TextField( order=2,