Skip to content

Commit

Permalink
Early SmartZone stuff: login + enough methods for Home Assistant to work
Browse files Browse the repository at this point in the history
  • Loading branch information
ms264556 committed Jan 22, 2024
1 parent 1d8a930 commit f0e766e
Show file tree
Hide file tree
Showing 5 changed files with 1,130 additions and 591 deletions.
134 changes: 127 additions & 7 deletions aioruckus/ajaxsession.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
ERROR_CONNECT_EOF,
ERROR_CONNECT_TEMPORARY,
ERROR_CONNECT_TIMEOUT,
ERROR_LOGIN_INCORRECT,
ERROR_LOGIN_INCORRECT
)

if TYPE_CHECKING:
Expand All @@ -40,11 +40,20 @@ def __init__(
self.password = password
self.__auto_cleanup_websession = auto_cleanup_websession

self.__login_url = None
# Common Session State
self.base_url = None
self._api = None

# ZoneDirector / Unleashed Session State
self.__login_url = None
self.cmdstat_url = None
self.conf_url = None

# SmartZone State
self.__service_ticket = None

# API Implementation

async def __aenter__(self) -> "AjaxSession":
await self.login()
return self
Expand All @@ -59,6 +68,9 @@ async def login(self) -> None:
async with self.websession.head(
f"https://{self.host}", timeout=3, allow_redirects=False
) as head:
if (head.status == 400):
# Request Refused - maybe SmartZone
return await self.sz_login()
redirect_to = head.headers["Location"]
if urlparse(redirect_to).path:
self.__login_url = redirect_to
Expand All @@ -74,6 +86,8 @@ async def login(self) -> None:
raise ConnectionRefusedError(ERROR_CONNECT_TEMPORARY)
self.cmdstat_url = self.base_url + "/_cmdstat.jsp"
self.conf_url = self.base_url + "/_conf.jsp"
except KeyError as kerr:
raise ConnectionError(ERROR_CONNECT_EOF) from kerr
except aiohttp.client_exceptions.ClientConnectorError as cerr:
raise ConnectionError(ERROR_CONNECT_EOF) from cerr
except asyncio.exceptions.TimeoutError as terr:
Expand Down Expand Up @@ -116,7 +130,55 @@ async def login(self) -> None:
# token page is a redirect, maybe temporary Unleashed Rebuilding placeholder
# page is showing
raise ConnectionRefusedError(ERROR_CONNECT_TEMPORARY)

# pylint: disable=import-outside-toplevel
from .nativeajaxapi import NativeAjaxApi
self._api = NativeAjaxApi(self)
return self

async def sz_login(self) -> None:
"""Create SmartZone session."""
try:
base_url = f"https://{self.host}:8443/wsg/api/public"
async with self.websession.get(
f"{base_url}/apiInfo", timeout=3, allow_redirects=False
) as api_info:
api_versions = await api_info.json()
self.base_url = f"{base_url}/{api_versions['apiSupportVersions'][-1]}"
jsessionid = api_info.cookies["JSESSIONID"]
self.websession.cookie_jar.update_cookies({jsessionid.key: jsessionid.value})
self.websession.headers["Content-Type"] = "application/json;charset=UTF-8"
async with self.websession.post(
f"{self.base_url}/serviceTicket",
json={
"username": self.username,
"password": self.password
},
timeout=3,
allow_redirects=False
) as service_ticket:
ticket_info = await service_ticket.json()
if service_ticket.status != 200:
errorCode = ticket_info["errorCode"]
if (200 <= errorCode < 300):
raise AuthenticationError(ticket_info["errorType"])
else:
raise ConnectionError(ticket_info["errorType"])
self.__service_ticket = ticket_info["serviceTicket"]
# pylint: disable=import-outside-toplevel
from .smartzoneajaxapi import SmartZoneAjaxApi
self._api = SmartZoneAjaxApi(self)
return self
except KeyError as kerr:
raise ConnectionError(ERROR_CONNECT_EOF) from kerr
except IndexError as ierr:
raise ConnectionError(ERROR_CONNECT_EOF) from ierr
except aiohttp.ContentTypeError as cterr:
raise ConnectionError(ERROR_CONNECT_EOF) from cterr
except aiohttp.client_exceptions.ClientConnectorError as cerr:
raise ConnectionError(ERROR_CONNECT_EOF) from cerr
except asyncio.exceptions.TimeoutError as terr:
raise ConnectionError(ERROR_CONNECT_TIMEOUT) from terr

async def close(self) -> None:
"""Logout of ZoneDirector/Unleashed and close websessiom"""
Expand Down Expand Up @@ -166,10 +228,6 @@ async def request(
@property
def api(self) -> "RuckusAjaxApi":
"""Return a RuckusApi instance."""
if not self._api:
# pylint: disable=import-outside-toplevel
from .ruckusajaxapi import RuckusAjaxApi
self._api = RuckusAjaxApi(self)
return self._api

@classmethod
Expand Down Expand Up @@ -212,4 +270,66 @@ async def request_file(self, file_url: str, timeout: int | None = None, retrying
await self.login() # try logging in again, then retry post
return await self.request_file(file_url, timeout, retrying=True)
return await response.read()


async def sz_query(
self,
cmd: str,
query: dict = None,
timeout: int | None = None,
retrying: bool = False
) -> dict:
return (await self.sz_post(f"query/{cmd}", query))["list"]

async def sz_get(
self,
cmd: str,
uri_params: dict = None,
timeout: int | None = None,
retrying: bool = False
) -> dict:
"""Get SZ Data"""
params = {"serviceTicket": self.__service_ticket}
if uri_params and isinstance(uri_params, dict):
params.update(uri_params)
async with self.websession.get(
f"{self.base_url}/{cmd}",
params=params,
timeout=timeout,
allow_redirects=False
) as response:
if response.status != 200:
# assume session is dead and re-login
if retrying:
# we tried logging in again, but the redirect still happens.
# an exception should have been raised from the login!
raise AuthenticationError(ERROR_POST_REDIRECTED)
await self.sz_login() # try logging in again, then retry post
return await self.sz_get(cmd, uri_params, timeout, retrying=True)
result_json = await response.json()
return result_json

async def sz_post(
self,
cmd: str,
json: dict = None,
timeout: int | None = None,
retrying: bool = False
) -> dict:
"""Post SZ Data"""
async with self.websession.post(
f"{self.base_url}/{cmd}",
params={"serviceTicket": self.__service_ticket},
json=json or {},
timeout=timeout,
allow_redirects=False
) as response:
if response.status != 200:
# assume session is dead and re-login
if retrying:
# we tried logging in again, but the redirect still happens.
# an exception should have been raised from the login!
raise AuthenticationError(ERROR_POST_REDIRECTED)
await self.sz_login() # try logging in again, then retry post
return await self.sz_post(cmd, json, timeout, retrying=True)
result_json = await response.json()
return result_json
169 changes: 84 additions & 85 deletions aioruckus/const.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,7 @@
ERROR_ALLOCATED_AP = "AP already in another AP Group"
ERROR_INVALID_MAC = "Invalid MAC"
ERROR_INVALID_WLAN = "Invalid WLAN"
ERROR_PASSPHRASE_LEN = "Passphrase can only contain between 8 and 63 characters or 64 " \
"HEX characters, space is not allowed"
ERROR_PASSPHRASE_LEN = "Passphrase can only contain between 8 and 63 characters or 64 HEX characters, space is not allowed"
ERROR_PASSPHRASE_JS = "Embedding html or javascript code, e.g. < />, is not allowed"
ERROR_PASSPHRASE_MISSING = "WPA2 and Mixed WPA2/3 WLANs require a passphrase"
ERROR_SAEPASSPHRASE_MISSING = "WPA3 and Mixed WPA2/3 WLANs require an SAE passphrase"
Expand Down Expand Up @@ -44,89 +43,89 @@ class WlanEncryption(Enum):
WPA3 = "wpa3"

URL_FILTERING_CATEGORIES = {
'1': 'Real Estate',
'2': 'Computer and Internet Security',
'3': 'Financial Services',
'4': 'Business and Economy',
'5': 'Computer and Internet Info',
'6': 'Auctions',
'7': 'Shopping',
'8': 'Cult and Occult',
'9': 'Travel',
'10': 'Abused Drugs',
'11': 'Adult and Pornography',
'12': 'Home and Garden',
'13': 'Military',
'14': 'Social Networking',
'15': 'Dead Sites',
'16': 'Stock and Advice Tools',
'17': 'Training and Tools',
'18': 'Dating',
'19': 'Sex Education',
'20': 'Religion',
'21': 'Entertainment and Arts',
'22': 'Personal sites and Blogs',
'23': 'Legal',
'24': 'Local Information',
'25': 'Streaming Media',
'26': 'Job Search',
'27': 'Gambling',
'28': 'Translation',
'29': 'Reference and Research',
'30': 'Shareware and Freeware',
'31': 'Peer to Peer',
'32': 'Marijuana',
'33': 'Hacking',
'34': 'Games',
'35': 'Philosophy and Political Advocacy',
'36': 'Weapons',
'37': 'Pay to Surf',
'38': 'Hunting and Fishing',
'39': 'Society',
'40': 'Educational Institutions',
'41': 'Online Greeting cards',
'42': 'Sports',
'43': 'Swimsuits & Intimate Apparel',
'44': 'Questionable',
'45': 'Kids',
'46': 'Hate and Racism',
'47': 'Personal Storage',
'48': 'Violence',
'49': 'Keyloggers and Monitoring',
'50': 'Search Engines',
'51': 'Internet Portals',
'52': 'Web Advertisements',
'53': 'Cheating',
'54': 'Gross',
'55': 'Web based Email',
'56': 'Malware Sites',
'57': 'Phishing and Other Frauds',
'58': 'Proxy Avoidance and Anonymizers',
'59': 'Spyware and Adware',
'60': 'Music',
'61': 'Government',
'62': 'Nudity',
'63': 'News and Media',
'64': 'Illegal',
'65': 'Content Delivery Networks',
'66': 'Internet Communications',
'67': 'Bot Nets',
'68': 'Abortion',
'69': 'Health and Medicine',
'70': 'Confirmed SPAM Sources',
'71': 'SPAM URLs',
'72': 'Unconfirmed SPAM Sources',
'73': 'Open HTTP Proxies',
'74': 'Dynamic Comment',
'75': 'Parked Domains',
'76': 'Alcohol and Tobacco',
'77': 'Private IP Addresses',
'78': 'Image and Video Search',
'79': 'Fashion and Beauty',
'80': 'Recreation and Hobbies',
'81': 'Motor Vehicles',
'82': 'Web Hosting',
'83': 'Food and Dining'
"1": "Real Estate",
"2": "Computer and Internet Security",
"3": "Financial Services",
"4": "Business and Economy",
"5": "Computer and Internet Info",
"6": "Auctions",
"7": "Shopping",
"8": "Cult and Occult",
"9": "Travel",
"10": "Abused Drugs",
"11": "Adult and Pornography",
"12": "Home and Garden",
"13": "Military",
"14": "Social Networking",
"15": "Dead Sites",
"16": "Stock and Advice Tools",
"17": "Training and Tools",
"18": "Dating",
"19": "Sex Education",
"20": "Religion",
"21": "Entertainment and Arts",
"22": "Personal sites and Blogs",
"23": "Legal",
"24": "Local Information",
"25": "Streaming Media",
"26": "Job Search",
"27": "Gambling",
"28": "Translation",
"29": "Reference and Research",
"30": "Shareware and Freeware",
"31": "Peer to Peer",
"32": "Marijuana",
"33": "Hacking",
"34": "Games",
"35": "Philosophy and Political Advocacy",
"36": "Weapons",
"37": "Pay to Surf",
"38": "Hunting and Fishing",
"39": "Society",
"40": "Educational Institutions",
"41": "Online Greeting cards",
"42": "Sports",
"43": "Swimsuits & Intimate Apparel",
"44": "Questionable",
"45": "Kids",
"46": "Hate and Racism",
"47": "Personal Storage",
"48": "Violence",
"49": "Keyloggers and Monitoring",
"50": "Search Engines",
"51": "Internet Portals",
"52": "Web Advertisements",
"53": "Cheating",
"54": "Gross",
"55": "Web based Email",
"56": "Malware Sites",
"57": "Phishing and Other Frauds",
"58": "Proxy Avoidance and Anonymizers",
"59": "Spyware and Adware",
"60": "Music",
"61": "Government",
"62": "Nudity",
"63": "News and Media",
"64": "Illegal",
"65": "Content Delivery Networks",
"66": "Internet Communications",
"67": "Bot Nets",
"68": "Abortion",
"69": "Health and Medicine",
"70": "Confirmed SPAM Sources",
"71": "SPAM URLs",
"72": "Unconfirmed SPAM Sources",
"73": "Open HTTP Proxies",
"74": "Dynamic Comment",
"75": "Parked Domains",
"76": "Alcohol and Tobacco",
"77": "Private IP Addresses",
"78": "Image and Video Search",
"79": "Fashion and Beauty",
"80": "Recreation and Hobbies",
"81": "Motor Vehicles",
"82": "Web Hosting",
"83": "Food and Dining",
}

class UrlFilteringGroups(Enum):
Expand Down
Loading

0 comments on commit f0e766e

Please sign in to comment.