mirror of
https://github.com/ammaraskar/pyCraft.git
synced 2024-12-22 08:38:08 +01:00
Fix various problems in minecraft.authentication and its tests:
- Return value of _make_request() is treated as a requests.Request, when it is in fact a requests.Response. - Some tests in test_authentication use assertRaises() incorrectly, resulting in testing code that never gets run. - Other miscellaneous errors exposed by the above changes. Additionally: - YggdrasilError instances now have fields with specific error information, and _raise_from_response() populates them. (This will be useful for later changes.)
This commit is contained in:
parent
73672401ef
commit
66a0603acf
@ -110,11 +110,11 @@ class AuthenticationToken(object):
|
|||||||
"password": password
|
"password": password
|
||||||
}
|
}
|
||||||
|
|
||||||
req = _make_request(AUTH_SERVER, "authenticate", payload)
|
res = _make_request(AUTH_SERVER, "authenticate", payload)
|
||||||
|
|
||||||
_raise_from_request(req)
|
_raise_from_response(res)
|
||||||
|
|
||||||
json_resp = req.json()
|
json_resp = res.json()
|
||||||
|
|
||||||
self.username = username
|
self.username = username
|
||||||
self.access_token = json_resp["accessToken"]
|
self.access_token = json_resp["accessToken"]
|
||||||
@ -145,13 +145,13 @@ class AuthenticationToken(object):
|
|||||||
if self.client_token is None:
|
if self.client_token is None:
|
||||||
raise ValueError("'client_token' is not set!")
|
raise ValueError("'client_token' is not set!")
|
||||||
|
|
||||||
req = _make_request(AUTH_SERVER,
|
res = _make_request(AUTH_SERVER,
|
||||||
"refresh", {"accessToken": self.access_token,
|
"refresh", {"accessToken": self.access_token,
|
||||||
"clientToken": self.client_token})
|
"clientToken": self.client_token})
|
||||||
|
|
||||||
_raise_from_request(req)
|
_raise_from_response(res)
|
||||||
|
|
||||||
json_resp = req.json()
|
json_resp = res.json()
|
||||||
|
|
||||||
self.access_token = json_resp["accessToken"]
|
self.access_token = json_resp["accessToken"]
|
||||||
self.client_token = json_resp["clientToken"]
|
self.client_token = json_resp["clientToken"]
|
||||||
@ -177,12 +177,12 @@ class AuthenticationToken(object):
|
|||||||
if self.access_token is None:
|
if self.access_token is None:
|
||||||
raise ValueError("'access_token' not set!")
|
raise ValueError("'access_token' not set!")
|
||||||
|
|
||||||
req = _make_request(AUTH_SERVER, "validate",
|
res = _make_request(AUTH_SERVER, "validate",
|
||||||
{"accessToken": self.access_token})
|
{"accessToken": self.access_token})
|
||||||
|
|
||||||
# Validate returns 204 to indicate success
|
# Validate returns 204 to indicate success
|
||||||
# http://wiki.vg/Authentication#Response_3
|
# http://wiki.vg/Authentication#Response_3
|
||||||
if req.status_code == 204:
|
if res.status_code == 204:
|
||||||
return True
|
return True
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
@ -202,10 +202,10 @@ class AuthenticationToken(object):
|
|||||||
Raises:
|
Raises:
|
||||||
minecraft.exceptions.YggdrasilError
|
minecraft.exceptions.YggdrasilError
|
||||||
"""
|
"""
|
||||||
req = _make_request(AUTH_SERVER, "signout",
|
res = _make_request(AUTH_SERVER, "signout",
|
||||||
{"username": username, "password": password})
|
{"username": username, "password": password})
|
||||||
|
|
||||||
if _raise_from_request(req) is None:
|
if _raise_from_response(res) is None:
|
||||||
return True
|
return True
|
||||||
|
|
||||||
def invalidate(self):
|
def invalidate(self):
|
||||||
@ -219,12 +219,12 @@ class AuthenticationToken(object):
|
|||||||
Raises:
|
Raises:
|
||||||
:class:`minecraft.exceptions.YggdrasilError`
|
:class:`minecraft.exceptions.YggdrasilError`
|
||||||
"""
|
"""
|
||||||
req = _make_request(AUTH_SERVER, "invalidate",
|
res = _make_request(AUTH_SERVER, "invalidate",
|
||||||
{"accessToken": self.access_token,
|
{"accessToken": self.access_token,
|
||||||
"clientToken": self.client_token})
|
"clientToken": self.client_token})
|
||||||
|
|
||||||
if req.status_code != 204:
|
if res.status_code != 204:
|
||||||
_raise_from_request(req)
|
_raise_from_response(res)
|
||||||
return True
|
return True
|
||||||
|
|
||||||
def join(self, server_id):
|
def join(self, server_id):
|
||||||
@ -246,13 +246,13 @@ class AuthenticationToken(object):
|
|||||||
err = "AuthenticationToken hasn't been authenticated yet!"
|
err = "AuthenticationToken hasn't been authenticated yet!"
|
||||||
raise YggdrasilError(err)
|
raise YggdrasilError(err)
|
||||||
|
|
||||||
req = _make_request(SESSION_SERVER, "join",
|
res = _make_request(SESSION_SERVER, "join",
|
||||||
{"accessToken": self.access_token,
|
{"accessToken": self.access_token,
|
||||||
"selectedProfile": self.profile.to_dict(),
|
"selectedProfile": self.profile.to_dict(),
|
||||||
"serverId": server_id})
|
"serverId": server_id})
|
||||||
|
|
||||||
if req.status_code != 204:
|
if res.status_code != 204:
|
||||||
_raise_from_request(req)
|
_raise_from_response(res)
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
|
||||||
@ -268,31 +268,39 @@ def _make_request(server, endpoint, data):
|
|||||||
Returns:
|
Returns:
|
||||||
A `requests.Request` object.
|
A `requests.Request` object.
|
||||||
"""
|
"""
|
||||||
req = requests.post(server + "/" + endpoint, data=json.dumps(data),
|
res = requests.post(server + "/" + endpoint, data=json.dumps(data),
|
||||||
headers=HEADERS)
|
headers=HEADERS)
|
||||||
return req
|
return res
|
||||||
|
|
||||||
|
|
||||||
def _raise_from_request(req):
|
def _raise_from_response(res):
|
||||||
"""
|
"""
|
||||||
Raises an appropriate `YggdrasilError` based on the `status_code` and
|
Raises an appropriate `YggdrasilError` based on the `status_code` and
|
||||||
`json` of a `requests.Request` object.
|
`json` of a `requests.Request` object.
|
||||||
"""
|
"""
|
||||||
if req.status_code == requests.codes['ok']:
|
if res.status_code == requests.codes['ok']:
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
exception = YggdrasilError()
|
||||||
|
exception.status_code = res.status_code
|
||||||
|
|
||||||
try:
|
try:
|
||||||
json_resp = req.json()
|
json_resp = res.json()
|
||||||
|
if not ("error" in json_resp and "errorMessage" in json_resp):
|
||||||
if "error" not in json_resp and "errorMessage" not in json_resp:
|
raise ValueError
|
||||||
raise YggdrasilError("Malformed error message.")
|
except ValueError:
|
||||||
|
message = "[{status_code}] Malformed error message: '{response_text}'"
|
||||||
|
message = message.format(status_code=str(res.status_code),
|
||||||
|
response_text=res.text)
|
||||||
|
exception.args = (message,)
|
||||||
|
else:
|
||||||
message = "[{status_code}] {error}: '{error_message}'"
|
message = "[{status_code}] {error}: '{error_message}'"
|
||||||
message = message.format(status_code=str(req.status_code),
|
message = message.format(status_code=str(res.status_code),
|
||||||
error=json_resp["error"],
|
error=json_resp["error"],
|
||||||
error_message=json_resp["errorMessage"])
|
error_message=json_resp["errorMessage"])
|
||||||
except ValueError as e:
|
exception.args = (message,)
|
||||||
message = "Unknown requests error. Status code: {}. Error: {}"
|
exception.yggdrasil_error = json_resp["error"]
|
||||||
message.format(str(req.status_code), e)
|
exception.yggdrasil_message = json_resp["errorMessage"]
|
||||||
|
exception.yggdrasil_cause = json_resp.get("cause")
|
||||||
|
|
||||||
raise YggdrasilError(message)
|
raise exception
|
||||||
|
@ -7,6 +7,23 @@ class YggdrasilError(Exception):
|
|||||||
"""
|
"""
|
||||||
Base `Exception` for the Yggdrasil authentication service.
|
Base `Exception` for the Yggdrasil authentication service.
|
||||||
"""
|
"""
|
||||||
|
def __init__(
|
||||||
|
self,
|
||||||
|
message=None,
|
||||||
|
status_code=None,
|
||||||
|
yggdrasil_error=None,
|
||||||
|
yggdrasil_message=None,
|
||||||
|
yggdrasil_cause=None,
|
||||||
|
*args
|
||||||
|
):
|
||||||
|
if message is not None:
|
||||||
|
args = (message,) + args
|
||||||
|
super(YggdrasilError, self).__init__(*args)
|
||||||
|
|
||||||
|
self.status_code = status_code
|
||||||
|
self.yggdrasil_error = yggdrasil_error
|
||||||
|
self.yggdrasil_message = yggdrasil_message
|
||||||
|
self.yggdrasil_cause = yggdrasil_cause
|
||||||
|
|
||||||
|
|
||||||
class VersionMismatch(Exception):
|
class VersionMismatch(Exception):
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
from minecraft.authentication import Profile
|
from minecraft.authentication import Profile
|
||||||
from minecraft.authentication import AuthenticationToken
|
from minecraft.authentication import AuthenticationToken
|
||||||
from minecraft.authentication import _make_request
|
from minecraft.authentication import _make_request
|
||||||
from minecraft.authentication import _raise_from_request
|
from minecraft.authentication import _raise_from_response
|
||||||
from minecraft.exceptions import YggdrasilError
|
from minecraft.exceptions import YggdrasilError
|
||||||
|
|
||||||
import requests
|
import requests
|
||||||
@ -179,11 +179,11 @@ class AuthenticateAuthenticationToken(unittest.TestCase):
|
|||||||
a = AuthenticationToken()
|
a = AuthenticationToken()
|
||||||
|
|
||||||
# We assume these aren't actual, valid credentials.
|
# We assume these aren't actual, valid credentials.
|
||||||
with self.assertRaises(YggdrasilError) as e:
|
with self.assertRaises(YggdrasilError) as cm:
|
||||||
a.authenticate("Billy", "The Goat")
|
a.authenticate("Billy", "The Goat")
|
||||||
|
|
||||||
err = "Invalid Credentials. Invalid username or password."
|
err = "Invalid credentials. Invalid username or password."
|
||||||
self.assertEqual(e.error, err)
|
self.assertEqual(cm.exception.yggdrasil_message, err)
|
||||||
|
|
||||||
@unittest.skipIf(should_skip_cred_test(),
|
@unittest.skipIf(should_skip_cred_test(),
|
||||||
"Need credentials to perform test.")
|
"Need credentials to perform test.")
|
||||||
@ -196,8 +196,8 @@ class AuthenticateAuthenticationToken(unittest.TestCase):
|
|||||||
|
|
||||||
class MakeRequest(unittest.TestCase):
|
class MakeRequest(unittest.TestCase):
|
||||||
def test_make_request_http_method(self):
|
def test_make_request_http_method(self):
|
||||||
req = _make_request(AUTHSERVER, "authenticate", {"Billy": "Bob"})
|
res = _make_request(AUTHSERVER, "authenticate", {"Billy": "Bob"})
|
||||||
self.assertEqual(req.request.method, "POST")
|
self.assertEqual(res.request.method, "POST")
|
||||||
|
|
||||||
def test_make_request_json_dump(self):
|
def test_make_request_json_dump(self):
|
||||||
data = {"Marie": "McGee",
|
data = {"Marie": "McGee",
|
||||||
@ -208,63 +208,72 @@ class MakeRequest(unittest.TestCase):
|
|||||||
"Listly": ["listling1", 2, "listling 3"]
|
"Listly": ["listling1", 2, "listling 3"]
|
||||||
}
|
}
|
||||||
|
|
||||||
req = _make_request(AUTHSERVER, "authenticate", data)
|
res = _make_request(AUTHSERVER, "authenticate", data)
|
||||||
self.assertEqual(req.request.body, json.dumps(data))
|
self.assertEqual(res.request.body, json.dumps(data))
|
||||||
|
|
||||||
def test_make_request_url(self):
|
def test_make_request_url(self):
|
||||||
URL = "https://authserver.mojang.com/authenticate"
|
URL = "https://authserver.mojang.com/authenticate"
|
||||||
req = _make_request(AUTHSERVER, "authenticate", {"Darling": "Diary"})
|
res = _make_request(AUTHSERVER, "authenticate", {"Darling": "Diary"})
|
||||||
self.assertEqual(req.request.url, URL)
|
self.assertEqual(res.request.url, URL)
|
||||||
|
|
||||||
|
|
||||||
class RaiseFromRequest(unittest.TestCase):
|
class RaiseFromRequest(unittest.TestCase):
|
||||||
def test_raise_from_erroneous_request(self):
|
def test_raise_from_erroneous_request(self):
|
||||||
err_req = requests.Request()
|
err_res = mock.NonCallableMock(requests.Response)
|
||||||
err_req.status_code = 401
|
err_res.status_code = 401
|
||||||
err_req.json = mock.MagicMock(
|
err_res.json = mock.MagicMock(
|
||||||
return_value={"error": "ThisIsAnException",
|
return_value={"error": "ThisIsAnException",
|
||||||
"errorMessage": "Went wrong."})
|
"errorMessage": "Went wrong."})
|
||||||
|
err_res.text = json.dumps(err_res.json())
|
||||||
|
|
||||||
with self.assertRaises(YggdrasilError) as e:
|
with self.assertRaises(YggdrasilError) as cm:
|
||||||
_raise_from_request(err_req)
|
_raise_from_response(err_res)
|
||||||
self.assertEqual(e, "[401]) ThisIsAnException: Went Wrong.")
|
|
||||||
|
message = "[401] ThisIsAnException: 'Went wrong.'"
|
||||||
|
self.assertEqual(str(cm.exception), message)
|
||||||
|
|
||||||
def test_raise_invalid_json(self):
|
def test_raise_invalid_json(self):
|
||||||
err_req = requests.Request()
|
err_res = mock.NonCallableMock(requests.Response)
|
||||||
err_req.status_code = 401
|
err_res.status_code = 401
|
||||||
err_req.json = mock.MagicMock(
|
err_res.json = mock.MagicMock(
|
||||||
side_effect=ValueError("no json could be decoded")
|
side_effect=ValueError("no json could be decoded")
|
||||||
)
|
)
|
||||||
|
err_res.text = "{sample invalid json}"
|
||||||
|
|
||||||
with self.assertRaises(YggdrasilError) as e:
|
with self.assertRaises(YggdrasilError) as cm:
|
||||||
_raise_from_request(err_req)
|
_raise_from_response(err_res)
|
||||||
self.assertTrue("Unknown requests error" in e)
|
|
||||||
|
|
||||||
def test_raise_from_erroneous_request_without_error(self):
|
message_start = "[401] Malformed error message"
|
||||||
err_req = requests.Request()
|
self.assertTrue(str(cm.exception).startswith(message_start))
|
||||||
err_req.status_code = 401
|
|
||||||
err_req.json = mock.MagicMock(return_value={"goldfish": "are pretty."})
|
|
||||||
|
|
||||||
with self.assertRaises(YggdrasilError) as e:
|
def test_raise_from_erroneous_response_without_error(self):
|
||||||
_raise_from_request(err_req)
|
err_res = mock.NonCallableMock(requests.Response)
|
||||||
|
err_res.status_code = 401
|
||||||
|
err_res.json = mock.MagicMock(return_value={"goldfish": "are pretty."})
|
||||||
|
err_res.text = json.dumps(err_res.json())
|
||||||
|
|
||||||
self.assertEqual(e, "Malformed error message.")
|
with self.assertRaises(YggdrasilError) as cm:
|
||||||
|
_raise_from_response(err_res)
|
||||||
|
|
||||||
def test_raise_from_healthy_request(self):
|
message_start = "[401] Malformed error message"
|
||||||
req = requests.Request()
|
self.assertTrue(str(cm.exception).startswith(message_start))
|
||||||
req.status_code = 200
|
|
||||||
req.json = mock.MagicMock(return_value={"vegetables": "are healthy."})
|
|
||||||
|
|
||||||
self.assertIs(_raise_from_request(req), None)
|
def test_raise_from_healthy_response(self):
|
||||||
|
res = mock.NonCallableMock(requests.Response)
|
||||||
|
res.status_code = 200
|
||||||
|
res.json = mock.MagicMock(return_value={"vegetables": "are healthy."})
|
||||||
|
res.text = json.dumps(res.json())
|
||||||
|
|
||||||
|
self.assertIs(_raise_from_response(res), None)
|
||||||
|
|
||||||
|
|
||||||
class NormalConnectionProcedure(unittest.TestCase):
|
class NormalConnectionProcedure(unittest.TestCase):
|
||||||
def test_login_connect_and_logout(self):
|
def test_login_connect_and_logout(self):
|
||||||
a = AuthenticationToken()
|
a = AuthenticationToken()
|
||||||
|
|
||||||
successful_req = requests.Request()
|
successful_res = mock.NonCallableMock(requests.Response)
|
||||||
successful_req.status_code = 200
|
successful_res.status_code = 200
|
||||||
successful_req.json = mock.MagicMock(
|
successful_res.json = mock.MagicMock(
|
||||||
return_value={"accessToken": "token",
|
return_value={"accessToken": "token",
|
||||||
"clientToken": "token",
|
"clientToken": "token",
|
||||||
"selectedProfile": {
|
"selectedProfile": {
|
||||||
@ -272,33 +281,35 @@ class NormalConnectionProcedure(unittest.TestCase):
|
|||||||
"name": "asdf"
|
"name": "asdf"
|
||||||
}}
|
}}
|
||||||
)
|
)
|
||||||
|
successful_res.text = json.dumps(successful_res.json())
|
||||||
|
|
||||||
error_req = requests.Request()
|
error_res = mock.NonCallableMock(requests.Response)
|
||||||
error_req.status_code = 400
|
error_res.status_code = 400
|
||||||
error_req.json = mock.MagicMock(
|
error_res.json = mock.MagicMock(
|
||||||
return_value={
|
return_value={
|
||||||
"error": "invalid request",
|
"error": "invalid request",
|
||||||
"errorMessage": "invalid request"
|
"errorMessage": "invalid request"
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
error_res.text = json.dumps(error_res.json())
|
||||||
|
|
||||||
def mocked_make_request(server, endpoint, data):
|
def mocked_make_request(server, endpoint, data):
|
||||||
if endpoint == "authenticate":
|
if endpoint == "authenticate":
|
||||||
return successful_req
|
return successful_res
|
||||||
if endpoint == "refresh" and data["accessToken"] == "token":
|
if endpoint == "refresh" and data["accessToken"] == "token":
|
||||||
return successful_req
|
return successful_res
|
||||||
if (endpoint == "validate" and data["accessToken"] == "token") \
|
if (endpoint == "validate" and data["accessToken"] == "token") \
|
||||||
or endpoint == "join":
|
or endpoint == "join":
|
||||||
r = requests.Request()
|
r = requests.Response()
|
||||||
r.status_code = 204
|
r.status_code = 204
|
||||||
r.raise_for_status = mock.MagicMock(return_value=None)
|
r.raise_for_status = mock.MagicMock(return_value=None)
|
||||||
return r
|
return r
|
||||||
if endpoint == "signout":
|
if endpoint == "signout":
|
||||||
return successful_req
|
return successful_res
|
||||||
if endpoint == "invalidate":
|
if endpoint == "invalidate":
|
||||||
return successful_req
|
return successful_res
|
||||||
|
|
||||||
return error_req
|
return error_res
|
||||||
|
|
||||||
# Test a successful sequence of events
|
# Test a successful sequence of events
|
||||||
with mock.patch("minecraft.authentication._make_request",
|
with mock.patch("minecraft.authentication._make_request",
|
||||||
@ -327,7 +338,7 @@ class NormalConnectionProcedure(unittest.TestCase):
|
|||||||
|
|
||||||
# Failures
|
# Failures
|
||||||
with mock.patch("minecraft.authentication._make_request",
|
with mock.patch("minecraft.authentication._make_request",
|
||||||
return_value=error_req) as _make_request_mock:
|
return_value=error_res) as _make_request_mock:
|
||||||
self.assertFalse(a.authenticated)
|
self.assertFalse(a.authenticated)
|
||||||
|
|
||||||
a.client_token = "token"
|
a.client_token = "token"
|
||||||
|
@ -9,7 +9,7 @@ class RaiseYggdrasilError(unittest.TestCase):
|
|||||||
raise YggdrasilError
|
raise YggdrasilError
|
||||||
|
|
||||||
def test_raise_yggdrasil_error_message(self):
|
def test_raise_yggdrasil_error_message(self):
|
||||||
with self.assertRaises(YggdrasilError) as e:
|
with self.assertRaises(YggdrasilError) as cm:
|
||||||
raise YggdrasilError("Error!")
|
raise YggdrasilError("Error!")
|
||||||
|
|
||||||
self.assertEqual(str(e.exception), "Error!")
|
self.assertEqual(str(cm.exception), "Error!")
|
||||||
|
Loading…
Reference in New Issue
Block a user