Skip to content
This repository was archived by the owner on Aug 27, 2022. It is now read-only.

Commit 0f0e393

Browse files
authored
Fix #43
1 parent 861202b commit 0f0e393

File tree

1 file changed

+141
-71
lines changed

1 file changed

+141
-71
lines changed

custom_components/authenticated/sensor.py

Lines changed: 141 additions & 71 deletions
Original file line numberDiff line numberDiff line change
@@ -63,13 +63,20 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
6363

6464
out = str(hass.config.path(OUTFILE))
6565

66-
sensor = Authenticated(hass, notify, out, exclude, config[CONF_PROVIDER])
66+
sensor = AuthenticatedSensor(hass, notify, out, exclude, config[CONF_PROVIDER])
6767
sensor.initial_run()
6868

6969
add_devices([sensor], True)
7070

7171

72-
class Authenticated(Entity):
72+
class AuthenticatedBaseException(Exception):
73+
"""Base exception for Authenticated."""
74+
75+
class AuthenticatedRateLimitException(AuthenticatedBaseException):
76+
"""Ratelimit exception."""
77+
78+
79+
class AuthenticatedSensor(Entity):
7380
"""Representation of a Sensor."""
7481

7582
def __init__(self, hass, notify, out, exclude, provider):
@@ -93,7 +100,6 @@ def initial_run(self):
93100
_LOGGER.debug('File has not been created, no data pressent.')
94101

95102
for access in tokens:
96-
accessdata = tokens[access]
97103
if access in self.exclude:
98104
continue
99105

@@ -102,35 +108,40 @@ def initial_run(self):
102108
except ValueError:
103109
continue
104110

105-
if access in self.stored:
106-
store = self.stored[access]
107-
access_data = {}
108-
access_data["last_used_ip"] = access
109-
access_data["user_id"] = store.get("user_id")
110-
111-
if store.get("last_used_at") is not None:
112-
access_data["last_used_at"] = store["last_used_at"]
113-
elif store.get("last_authenticated") is not None:
114-
access_data["last_used_at"] = store["last_authenticated"]
115-
else:
116-
access_data["last_used_at"] = None
117-
118-
if store.get("prev_used_at") is not None:
119-
access_data["prev_used_at"] = store["prev_used_at"]
120-
elif store.get("previous_authenticated_time") is not None:
121-
access_data["prev_used_at"] = store["previous_authenticated_time"]
122-
else:
123-
access_data["prev_used_at"] = None
111+
accessdata = AuthenticatedData(access, tokens[access])
124112

125-
else:
126-
access_data = {
127-
"last_used_ip": access,
128-
"user_id": accessdata["user_id"],
129-
"last_used_at": accessdata["last_used_at"],
130-
"prev_used_at": None
131-
}
132-
ipaddress = IPAddress(access_data, users, self.provider, False)
133-
ipaddress.lookup()
113+
if accessdata.ipaddr in self.stored:
114+
store = AuthenticatedData(accessdata.ipaddr, self.stored[access])
115+
accessdata.ipaddr = access
116+
117+
if store.user_id is not None:
118+
accessdata.user_id = store.user_id
119+
120+
if store.hostname is not None:
121+
accessdata.hostname = store.hostname
122+
123+
if store.country is not None:
124+
accessdata.country = store.country
125+
126+
if store.region is not None:
127+
accessdata.region = store.region
128+
129+
if store.city is not None:
130+
accessdata.city = store.city
131+
132+
if store.last_access is not None:
133+
accessdata.last_access = store.last_access
134+
elif store.attributes.get("last_authenticated") is not None:
135+
accessdata.last_access = store.attributes["last_authenticated"]
136+
137+
if store.prev_access is not None:
138+
accessdata.prev_access = store.prev_access
139+
elif store.attributes.get("previous_authenticated_time") is not None:
140+
accessdata.prev_access = store.attributes["last_authenticated"]
141+
142+
ipaddress = IPData(accessdata, users, self.provider, False)
143+
if accessdata.ipaddr not in self.stored:
144+
ipaddress.lookup()
134145
self.hass.data[PLATFORM_NAME][access] = ipaddress
135146
self.write_to_file()
136147

@@ -139,7 +150,6 @@ def update(self):
139150
updated = False
140151
users, tokens = load_authentications(self.hass.config.path(".storage/auth"))
141152
for access in tokens:
142-
accessdata = tokens[access]
143153
try:
144154
ValidateIP(access)
145155
except ValueError:
@@ -163,22 +173,20 @@ def update(self):
163173
else:
164174
updated = True
165175
_LOGGER.warning("New successfull login from unknown IP (%s)", access)
166-
access_data = {
167-
"last_used_ip": access,
168-
"user_id": accessdata["user_id"],
169-
"last_used_at": accessdata["last_used_at"],
170-
"prev_used_at": None
171-
}
172-
ipaddress = IPAddress(access_data, users, self.provider)
176+
accessdata = AuthenticatedData(access, tokens[access])
177+
ipaddress = IPData(accessdata, users, self.provider)
173178
ipaddress.lookup()
174179
if ipaddress.new_ip:
175180
if self.notify:
176181
ipaddress.notify(self.hass)
177182
ipaddress.new_ip = False
178183

184+
if ipaddress.hostname is None:
185+
ipaddress.hostname = get_hostname(ipaddress.ip_address)
186+
179187
self.hass.data[PLATFORM_NAME][access] = ipaddress
180188

181-
for ipaddr in sorted(tokens,key=lambda x:tokens[x]['last_used_at'], reverse=True):
189+
for ipaddr in sorted(tokens, key=lambda x:tokens[x]['last_used_at'], reverse=True):
182190
self.last_ip = self.hass.data[PLATFORM_NAME][ipaddr]
183191
break
184192
self._state = self.last_ip.ip_address
@@ -230,6 +238,7 @@ def write_to_file(self):
230238
"last_used_at": known.last_used_at,
231239
"prev_used_at": known.prev_used_at,
232240
"country": known.country,
241+
"hostname": known.hostname,
233242
"region": known.region,
234243
"city": known.city
235244
}
@@ -308,20 +317,35 @@ def load_authentications(authfile):
308317
return users, tokens_cleaned
309318

310319

311-
class IPAddress:
320+
class AuthenticatedData:
321+
"""Data class for autenticated values."""
322+
323+
def __init__(self, ipaddr, attributes):
324+
"""Initialize."""
325+
self.ipaddr = ipaddr
326+
self.attributes = attributes
327+
self.last_access = attributes.get("last_used_at")
328+
self.prev_access = attributes.get("prev_used_at")
329+
self.country = attributes.get("country")
330+
self.region = attributes.get("region")
331+
self.city = attributes.get("city")
332+
self.user_id = attributes.get("user_id")
333+
self.hostname = attributes.get("hostname")
334+
335+
336+
class IPData:
312337
"""IP Address class."""
313338
def __init__(self, access_data, users, provider, new=True):
314339
self.all_users = users
315-
self.access_data = access_data
316340
self.provider = provider
317-
self.ip_address = access_data.get("last_used_ip")
318-
self.last_used_at = access_data.get("last_used_at")
319-
self.prev_used_at = access_data.get("prev_used_at")
320-
self.user_id = access_data.get("user_id")
321-
self.hostname = None
322-
self.city = None
323-
self.region = None
324-
self.country = None
341+
self.ip_address = access_data.ipaddr
342+
self.last_used_at = access_data.last_access
343+
self.prev_used_at = access_data.prev_access
344+
self.user_id = access_data.user_id
345+
self.hostname = access_data.hostname
346+
self.city = access_data.city
347+
self.region = access_data.region
348+
self.country = access_data.country
325349
self.new_ip = new
326350

327351
@property
@@ -330,12 +354,11 @@ def username(self):
330354
if self.user_id is None:
331355
return "Unknown"
332356
elif self.user_id in self.all_users:
333-
return self.all_users[self.access_data["user_id"]]
357+
return self.all_users[self.user_id]
334358
return "Unknown"
335359

336360
def lookup(self):
337361
"""Look up data for the IP address."""
338-
self.hostname = get_hostname(self.ip_address)
339362
geo = get_geo_data(self.ip_address, self.provider)
340363
if geo["result"]:
341364
self.country = geo.get("data", {}).get("country")
@@ -363,11 +386,12 @@ def notify(self, hass):
363386
last_used_at = ""
364387
message = """
365388
**IP Address:** {}
389+
**Username:** {}
366390
{}
367391
{}
368392
{}
369393
{}
370-
""".format(self.ip_address, country, region, city, last_used_at)
394+
""".format(self.ip_address, self.username, country, region, city, last_used_at.replace("T", " "))
371395
notify(message, title='New successful login', notification_id=self.ip_address)
372396

373397

@@ -385,29 +409,23 @@ def __init__(self, ipaddr):
385409
def country(self):
386410
"""Return country name or None."""
387411
if self.result:
388-
if "country_name" in self.result:
389-
return self.result["country_name"]
390-
elif "country" in self.result:
412+
if self.result.get("country") is not None:
391413
return self.result["country"]
392414
return None
393415

394416
@property
395417
def region(self):
396418
"""Return region name or None."""
397419
if self.result:
398-
if "subdivision_1_name" in self.result:
399-
return self.result["subdivision_1_name"]
400-
elif "region" in self.result:
420+
if self.result.get("region") is not None:
401421
return self.result["region"]
402422
return None
403423

404424
@property
405425
def city(self):
406426
"""Return city name or None."""
407427
if self.result:
408-
if "city_name" in self.result:
409-
return self.result["city_name"]
410-
elif "city" in self.result:
428+
if self.result.get("city") is not None:
411429
return self.result["city"]
412430
return None
413431

@@ -427,26 +445,50 @@ def update_geo_info(self):
427445
self.result = {}
428446
try:
429447
api = self.url.format(self.ipaddr)
430-
data = requests.get(api, timeout=5).json()
448+
header = {
449+
"user-agent": "Home Assistant/Python"
450+
}
451+
data = requests.get(api, headers=header, timeout=5).json()
431452

432-
if 'reserved' in str(data):
433-
return
434-
elif data.get('status') != 'success':
453+
if data.get("error"):
454+
if data.get("reason") == "RateLimited":
455+
raise AuthenticatedRateLimitException(
456+
"RatelimitError, try a different provider.")
457+
458+
elif data.get("status", "success") == "error":
435459
return
436-
elif 'Private' in data.get('org'):
460+
461+
elif data.get("reserved"):
437462
return
438463

439-
if data.get('data') is not None:
440-
data = data["data"]
464+
elif data.get("status", "success") == "fail":
465+
raise AuthenticatedBaseException(
466+
"[{}] - {}".format(self.ipaddr, data.get("message", "Unkown error.")))
467+
441468
self.result = data
442-
except Exception: # pylint: disable=broad-except
443-
return
469+
self.parse_data()
470+
except AuthenticatedRateLimitException as exception:
471+
_LOGGER.error(exception)
472+
except AuthenticatedBaseException as exception:
473+
_LOGGER.error(exception)
474+
except requests.exceptions.ConnectionError:
475+
pass
444476

477+
def parse_data(self):
478+
"""Parse data from geoprovider."""
479+
self.result = self.result
445480

446481
class IPApi(GeoProvider):
447482
"""IPApi class."""
448483
url = "https://ipapi.co/{}/json"
449484

485+
@property
486+
def country(self):
487+
"""Return country name or None."""
488+
if self.result:
489+
if self.result.get("country_name") is not None:
490+
return self.result["country_name"]
491+
return None
450492

451493
class ExtremeIPLookup(GeoProvider):
452494
"""IPApi class."""
@@ -456,3 +498,31 @@ class ExtremeIPLookup(GeoProvider):
456498
class IPVigilante(GeoProvider):
457499
"""IPVigilante class."""
458500
url = "https://ipvigilante.com/json/{}"
501+
502+
def parse_data(self):
503+
"""Parse data from geoprovider."""
504+
self.result = self.result.get("data", {})
505+
506+
@property
507+
def country(self):
508+
"""Return country name or None."""
509+
if self.result:
510+
if self.result.get("country_name") is not None:
511+
return self.result["country_name"]
512+
return None
513+
514+
@property
515+
def region(self):
516+
"""Return region name or None."""
517+
if self.result:
518+
if self.result.get("subdivision_1_name") is not None:
519+
return self.result["subdivision_1_name"]
520+
return None
521+
522+
@property
523+
def city(self):
524+
"""Return city name or None."""
525+
if self.result:
526+
if self.result.get("city_name") is not None:
527+
return self.result["city_name"]
528+
return None

0 commit comments

Comments
 (0)