1
0
mirror of https://github.com/dkmstr/openuds.git synced 2025-03-13 08:58:35 +03:00

Updated os_detector to use Sec-Ch-Ua-Platform and Sec-Ch-Ua for os detection if available instead of user agent (left user agent as fallback anyway). Better for chrome bases browsers.

This commit is contained in:
Adolfo Gómez García 2023-12-26 17:22:35 +01:00
parent c5171c014b
commit 974f652df0
No known key found for this signature in database
GPG Key ID: DD1ABF20724CDA23
5 changed files with 71 additions and 46 deletions

View File

@ -53,7 +53,7 @@ MOBILE_OS_LIST: typing.Final[tuple[types.os.KnownOS, ...]] = tuple(set(ALL_OS_LI
DEFAULT_OS: typing.Final[types.os.KnownOS] = types.os.KnownOS.WINDOWS
knownBrowsers = tuple(types.os.KnownBrowser)
knownBrowsers: typing.Final[tuple[types.os.KnownBrowser, ...]] = tuple(types.os.KnownBrowser)
browsersREs: dict[types.os.KnownBrowser, tuple] = {
types.os.KnownBrowser.FIREFOX: (re.compile(r'Firefox/([0-9.]+)'),),

View File

@ -41,7 +41,7 @@ class DetectedOsInfo(typing.NamedTuple):
class KnownOS(enum.Enum):
LINUX = ('Linux', 'armv7l')
LINUX = ('Linux',) # previusly got 'armv7l'
CHROME_OS = ('CrOS',)
WINDOWS_PHONE = ('Windows Phone',)
WINDOWS = ('Windows',)
@ -58,15 +58,16 @@ class KnownOS(enum.Enum):
def __str__(self):
return self.os_name()
# Order is important here, as we will use the first match
class KnownBrowser(enum.StrEnum):
# Known browsers
FIREFOX = 'Firefox'
SEAMONKEY = 'Seamonkey'
CHROME = 'Chrome'
CHROMIUM = 'Chromium'
EDGE = 'Microsoft Edge'
SAFARI = 'Safari'
OPERA = 'Opera'
CHROME = 'Chrome'
CHROMIUM = 'Chromium'
IEXPLORER = 'Explorer'
EDGE = 'Edge'
OTHER = 'Other'

View File

@ -41,50 +41,76 @@ from uds.core import types, consts
logger = logging.getLogger(__name__)
def getOsFromUA(
ua: typing.Optional[str],
def detect_os(
headers: collections.abc.Mapping[str, typing.Any],
) -> types.os.DetectedOsInfo:
"""
Basic OS Client detector (very basic indeed :-))
"""
ua = ua or types.os.KnownOS.UNKNOWN.value[0]
ua = (headers.get('User-Agent') or types.os.KnownOS.UNKNOWN.value[0])
res = types.os.DetectedOsInfo(os=types.os.KnownOS.UNKNOWN, browser=types.os.KnownBrowser.OTHER, version='0.0')
found: bool = False
for os in consts.os.KNOWN_OS_LIST:
if found:
break
for osName in os.value:
if osName in ua:
res = res._replace(os=os)
found = True
res = types.os.DetectedOsInfo(
os=types.os.KnownOS.UNKNOWN, browser=types.os.KnownBrowser.OTHER, version='0.0'
)
# First, try to detect from Sec-Ch-Ua-Platform
# Remember all Sec... headers are only available on secure connections
secChUaPlatform = headers.get('Sec-Ch-Ua-Platform')
found = types.os.KnownOS.UNKNOWN
if secChUaPlatform is not None:
# Strip initial and final " chars if present
secChUaPlatform = secChUaPlatform.strip('"')
for os in consts.os.KNOWN_OS_LIST:
if secChUaPlatform in os.value:
found = os
break
else: # Try to detect from User-Agent
ual = ua.lower()
for os in consts.os.KNOWN_OS_LIST:
if os.os_name().lower() in ual:
found = os
break
match = None
# If we found a known OS, store it
if found != types.os.KnownOS.UNKNOWN:
res = res._replace(os=found)
ruleKey, ruleValue = None, None
for ruleKey, ruleValue in consts.os.browserRules.items():
must, mustNot = ruleValue
# Try to detect browser from Sec-Ch-Ua first
secChUa = headers.get('Sec-Ch-Ua')
if secChUa is not None:
for browser in consts.os.knownBrowsers:
if browser in secChUa:
res = res._replace(browser=browser)
break
else:
# Try to detect browser from User-Agent
match = None
for mustRe in consts.os.browsersREs[must]:
match = mustRe.search(ua)
if match is None:
continue
# Check against no maching rules
for mustNotREs in mustNot:
for cre in consts.os.browsersREs[mustNotREs]:
if cre.search(ua) is not None:
match = None
break
ruleKey, ruleValue = None, None
for ruleKey, ruleValue in consts.os.browserRules.items():
must, mustNot = ruleValue
for mustRe in consts.os.browsersREs[must]:
match = mustRe.search(ua)
if match is None:
continue
# Check against no maching rules
for mustNotREs in mustNot:
for cre in consts.os.browsersREs[mustNotREs]:
if cre.search(ua) is not None:
match = None
break
if match is None:
break
if match is not None:
break
if match is not None:
break
if match is not None:
break
if match is not None:
res = res._replace(browser=ruleKey, version=match.groups(1)[0])
if match is not None:
res = res._replace(browser=ruleKey, version=match.groups(1)[0])
logger.debug('Detected: %s %s', res.os, res.browser)
return res

View File

@ -136,7 +136,7 @@ def _process_request(request: 'ExtendedHttpRequest') -> typing.Optional['HttpRes
request.authorized = request.session.get(AUTHORIZED_KEY, False)
# Ensures request contains os
request.os = OsDetector.getOsFromUA(request.META.get('HTTP_USER_AGENT', 'Unknown'))
request.os = OsDetector.detect_os(request.headers)
# Ensures that requests contains the valid user
_get_user(request)

View File

@ -40,7 +40,7 @@ from django.views.decorators.cache import never_cache
from django.views.decorators.csrf import csrf_exempt
from uds.web.util import errors
from uds.core import auths, types
from uds.core import auths, types, exceptions
from uds.core.auths.auth import (
webLogin,
webLogout,
@ -107,14 +107,12 @@ def authCallback_stage2(request: 'ExtendedHttpRequestWithUser', ticketId: str) -
result = authenticateViaCallback(authenticator, params, request)
# os = OsDetector.getOsFromUA(request.META['HTTP_USER_AGENT'])
if result.url:
raise auths.exceptions.Redirect(result.url)
raise exceptions.auth.Redirect(result.url)
if result.user is None:
authLogLogin(request, authenticator, f'{params}', 'Invalid at auth callback')
raise auths.exceptions.InvalidUserException()
raise exceptions.auth.InvalidUserException()
response = HttpResponseRedirect(reverse('page.index'))
@ -131,9 +129,9 @@ def authCallback_stage2(request: 'ExtendedHttpRequestWithUser', ticketId: str) -
response = HttpResponseRedirect(reverse('page.mfa'))
return response
except auths.exceptions.Redirect as e:
except exceptions.auth.Redirect as e:
return HttpResponseRedirect(request.build_absolute_uri(str(e)) if e.args and e.args[0] else '/')
except auths.exceptions.Logout as e:
except exceptions.auth.Logout as e:
return webLogout(
request,
request.build_absolute_uri(str(e)) if e.args and e.args[0] else None,
@ -214,7 +212,7 @@ def ticketAuth(
password = CryptoManager().decrypt(data['password'])
except Exception:
logger.error('Ticket stored is not valid')
raise auths.exceptions.InvalidUserException() from None
raise exceptions.auth.InvalidUserException() from None
auth = Authenticator.objects.get(uuid=auth)
# If user does not exists in DB, create it right now
@ -232,7 +230,7 @@ def ticketAuth(
usr = auth.getOrCreateUser(username, realname)
if usr is None or State.isActive(usr.state) is False: # If user is inactive, raise an exception
raise auths.exceptions.InvalidUserException()
raise exceptions.auth.InvalidUserException()
# Add groups to user (replace existing groups)
usr.groups.set(grps) # type: ignore