Skip to content

fix: fix authentication failures #37

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
61 changes: 35 additions & 26 deletions pyW215/pyW215.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,6 @@
ON = 'ON'
OFF = 'OFF'


class SmartPlug(object):
"""
Class to access:
Expand Down Expand Up @@ -84,7 +83,7 @@ def controlParameters(self, module, status):
<OPStatus>{}</OPStatus><Controller>1</Controller>'''.format(self.moduleParameters(module), status)
else:
return '''{}<NickName>Socket 1</NickName><Description>Socket 1</Description>
<OPStatus>{}</OPStatus>'''.format(self.moduleParameters(module), status)
<OPStatus>{}</OPStatus><Controller>1</Controller>'''.format(self.moduleParameters(module), status)

def radioParameters(self, radio):
"""Returns RadioID as XML.
Expand Down Expand Up @@ -131,8 +130,8 @@ def SOAPAction(self, Action, responseElement, params="", recursive=False):
self.authenticated = self.auth()
auth = self.authenticated
# If not legacy protocol, ensure auth() is called for every call
if not self.use_legacy_protocol:
self.authenticated = None
#if not self.use_legacy_protocol:
# self.authenticated = None

if auth is None:
return None
Expand Down Expand Up @@ -178,6 +177,11 @@ def SOAPAction(self, Action, responseElement, params="", recursive=False):
_LOGGER.warning("Could not find %s in response." % responseElement)
self._error_report = True
return None

if value == 'ERROR' and self._error_report is False:
_LOGGER.warning("Value of response element %s was \"ERROR\"." % responseElement)
self._error_report = True
return None

self._error_report = False
return value
Expand Down Expand Up @@ -335,8 +339,10 @@ def auth(self):
CookieResponse = root.find('.//{http://purenetworks.com/HNAP1/}Cookie')
PublickeyResponse = root.find('.//{http://purenetworks.com/HNAP1/}PublicKey')

if (
ChallengeResponse == None or CookieResponse == None or PublickeyResponse == None) and self._error_report is False:
if (ChallengeResponse == None or CookieResponse == None or PublickeyResponse == None) and self._error_report is False:
print( 'payload\n', payload.decode(), '\n' )
print( 'headers\n', headers, '\n' )
print( 'xmlData\n', xmlData, '\n' )
_LOGGER.warning("Failed to receive initial authentication from smartplug.")
self._error_report = True
return None
Expand All @@ -352,11 +358,16 @@ def auth(self):
PrivateKey = hmac.new((Publickey + self.password).encode(), (Challenge).encode(), digestmod=hashlib.md5).hexdigest().upper()
login_pwd = hmac.new(PrivateKey.encode(), Challenge.encode(), digestmod=hashlib.md5).hexdigest().upper()

time_stamp = str(round(time.time() / 1e6))
action_url = '"http://purenetworks.com/HNAP1/Login"'
AUTHKey = hmac.new(PrivateKey.encode(), (time_stamp + action_url).encode(), digestmod=hashlib.md5).hexdigest().upper() + " " + time_stamp

response_payload = self.auth_payload(login_pwd)

# Build response to initial request
headers = {'Content-Type': '"text/xml; charset=utf-8"',
'SOAPAction': '"http://purenetworks.com/HNAP1/Login"',
'HNAP_AUTH': '"{}"'.format(PrivateKey),
'HNAP_AUTH': '"{}"'.format(AUTHKey),
'Cookie': 'uid={}'.format(Cookie)}
response = urlopen(Request(self.url, response_payload, headers))
xmlData = response.read().decode()
Expand All @@ -376,18 +387,17 @@ def auth(self):
def initial_auth_payload(self):
"""Return the initial authentication payload."""

return b'''<?xml version="1.0" encoding="utf-8"?>
<soap:Envelope xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">
<soap:Body>
return '''<?xml version="1.0" encoding="utf-8"?>
<soap:Envelope xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">
<soap:Body>
<Login xmlns="http://purenetworks.com/HNAP1/">
<Action>request</Action>
<Username>admin</Username>
<LoginPassword/>
<Captcha/>
<Action>request</Action>
<Username>{}</Username>
<LoginPassword></LoginPassword>
<Captcha></Captcha>
</Login>
</soap:Body>
</soap:Envelope>
'''
</soap:Body>
</soap:Envelope>'''.format(self.user).encode()

def auth_payload(self, login_pwd):
"""Generate a new payload containing generated hash information.
Expand All @@ -397,16 +407,15 @@ def auth_payload(self, login_pwd):
"""

payload = '''<?xml version="1.0" encoding="utf-8"?>
<soap:Envelope xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">
<soap:Body>
<soap:Envelope xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">
<soap:Body>
<Login xmlns="http://purenetworks.com/HNAP1/">
<Action>login</Action>
<Username>{}</Username>
<LoginPassword>{}</LoginPassword>
<Captcha/>
<Action>login</Action>
<Username>{}</Username>
<LoginPassword>{}</LoginPassword>
<Captcha></Captcha>
</Login>
</soap:Body>
</soap:Envelope>
'''.format(self.user, login_pwd)
</soap:Body>
</soap:Envelope>'''.format(self.user, login_pwd)

return payload.encode()