Skip to content

Commit

Permalink
Fix OTP issues and upgrade to Python 11.
Browse files Browse the repository at this point in the history
  • Loading branch information
elahd authored Jul 28, 2023
1 parent 137b552 commit 5d50133
Show file tree
Hide file tree
Showing 5 changed files with 63 additions and 22 deletions.
4 changes: 2 additions & 2 deletions .devcontainer.json
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
{
"image": "mcr.microsoft.com/vscode/devcontainers/python:0-3.10-bullseye",
"image": "mcr.microsoft.com/vscode/devcontainers/python:0-3.11-bullseye",
"name": "pyalarmdotcomajax",
"postCreateCommand": "scripts/setup.sh",
"customizations": {
Expand Down Expand Up @@ -44,4 +44,4 @@
"features": {
"github-cli": "latest"
}
}
}
2 changes: 1 addition & 1 deletion .pre-commit-config.yaml
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
default_language_version:
python: python3.10
python: python3.11

repos:
- repo: https://github.com/charliermarsh/ruff-pre-commit
Expand Down
71 changes: 56 additions & 15 deletions pyalarmdotcomajax/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@
ExtendedProperties,
)

__version__ = "0.4.14"
__version__ = "0.4.15"

log = logging.getLogger(__name__)

Expand Down Expand Up @@ -81,10 +81,10 @@ class OtpType(Enum):

# https://www.alarm.com/web/system/assets/customer-ember/enums/TwoFactorAuthenticationType.js

DISABLED = 0
APP = 1
SMS = 2
EMAIL = 4
disabled = 0
app = 1
sms = 2
email = 4


class AlarmController:
Expand All @@ -107,7 +107,7 @@ class AlarmController:
"{}web/api/engines/twoFactorAuthentication/twoFactorAuthentications/{}"
)
LOGIN_2FA_TRUST_URL_TEMPLATE = "{}web/api/engines/twoFactorAuthentication/twoFactorAuthentications/{}/trustTwoFactorDevice"
LOGIN_2FA_REQUEST_OTP_SMS_URL_TEMPLATE = "{}web/api/engines/twoFactorAuthentication/twoFactorAuthentications/{}/sendTwoFactorAuthenticationCode"
LOGIN_2FA_REQUEST_OTP_SMS_URL_TEMPLATE = "{}web/api/engines/twoFactorAuthentication/twoFactorAuthentications/{}/sendTwoFactorAuthenticationCodeViaSms"
LOGIN_2FA_REQUEST_OTP_EMAIL_URL_TEMPLATE = "{}web/api/engines/twoFactorAuthentication/twoFactorAuthentications/{}/sendTwoFactorAuthenticationCodeViaEmail"

VIEWSTATE_FIELD = "__VIEWSTATE"
Expand Down Expand Up @@ -224,8 +224,8 @@ async def async_login(self, request_otp: bool = True) -> AuthResult:
log.debug("Two factor authentication code or cookie required.")

if request_otp and self._two_factor_method in [
OtpType.SMS,
OtpType.EMAIL,
OtpType.sms,
OtpType.email,
]:
await self.async_request_otp()

Expand All @@ -248,7 +248,7 @@ async def async_request_otp(self) -> str | None:

request_url = (
self.LOGIN_2FA_REQUEST_OTP_EMAIL_URL_TEMPLATE
if self._two_factor_method == OtpType.EMAIL
if self._two_factor_method == OtpType.email
else self.LOGIN_2FA_REQUEST_OTP_SMS_URL_TEMPLATE
)

Expand Down Expand Up @@ -863,18 +863,59 @@ async def _async_requires_2fa(self) -> bool | None:
self._update_antiforgery_token(resp)
json_rsp = await resp.json()

log.debug(json_rsp)

if isinstance(
factor_id := json_rsp.get("data", {}).get("id"), int
):
self._factor_type_id = factor_id
self._two_factor_method = OtpType(
json_rsp.get("data", {})
.get("attributes", {})
.get("twoFactorType")

if not (
attribs := json_rsp.get("data", {}).get("attributes")
):
raise UnexpectedDataStructure(
"Could not find expected data in two-factor"
" authentication details."
)

if attribs.get("showSuggestedSetup") is True:
raise NagScreen

enabled_otp_types_bitmask = attribs.get(
"enabledTwoFactorTypes"
)
log.debug(
"Requires 2FA. Using method %s", self._two_factor_method

enabled_2fa_methods = [
otp_type
for otp_type in OtpType
if bool(enabled_otp_types_bitmask & otp_type.value)
]

if (
(OtpType.disabled in enabled_2fa_methods)
or (attribs.get("isCurrentDeviceTrusted") is True)
or not enabled_otp_types_bitmask
or not enabled_2fa_methods
):
# 2FA is disabled, we can skip 2FA altogether.
return False

log.info(
"Requires two-factor authentication. Enabled methods"
f" are {enabled_2fa_methods}"
)

# We have proper 2FA type selection in the 5.x releases. Since this 4.x release is a hotfix, we're going to be lazy here and will select an OTP method for the user.
supported_types = [
int(otp_type.value)
for otp_type in OtpType
if bool(enabled_otp_types_bitmask & otp_type.value)
]

self._two_factor_method = OtpType(min(supported_types))

log.debug("Using method %s", self._two_factor_method)

return True

log.debug("Does not require 2FA.")
Expand Down
2 changes: 1 addition & 1 deletion pyalarmdotcomajax/devices/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,10 @@
from __future__ import annotations

import asyncio
import logging
from collections.abc import Callable
from dataclasses import dataclass
from enum import Enum
import logging
from typing import Any, Protocol, TypedDict, final

import aiohttp
Expand Down
6 changes: 3 additions & 3 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,12 @@ build-backend = "setuptools.build_meta"

[tool.black]
line-length = 88
target-version = ["py310"]
target-version = ["py311"]
exclude = 'generated'
preview = "True"

[tool.mypy]
python_version = "3.10"
python_version = "3.11"
show_error_codes = true
# follow_imports = "silent"
ignore_missing_imports = true
Expand Down Expand Up @@ -40,7 +40,7 @@ norecursedirs = [".git", "testing_config"]
asyncio_mode = "auto"

[tool.ruff]
target-version = "py310"
target-version = "py311"
exclude = ["examples", "tests", "pylint"]
select = [
"B007", # Loop control variable {name} not used within loop body
Expand Down

0 comments on commit 5d50133

Please sign in to comment.