Browse Source

Merge branch 'master' of https://github.com/ValvePython/steam

pull/35/head
Philipp Joos 9 years ago
parent
commit
5757f5309d
  1. 16
      README.rst
  2. 28
      steam/client/__init__.py
  3. 51
      steam/guard.py
  4. 4
      tests/test_guard.py

16
README.rst

@ -9,14 +9,14 @@ Documentation: http://steam.readthedocs.io/en/latest/
Key features Key features
------------ ------------
* `SteamAuthenticator <http://valvepython.github.io/steam/api/steam.guard.html>`_ - enable/disable/manage 2FA on account and generate codes * `SteamAuthenticator <http://steam.readthedocs.io/en/latest/api/steam.guard.html>`_ - enable/disable/manage 2FA on account and generate codes
* `SteamClient <http://valvepython.github.io/steam/api/steam.client.html>`_ - communication with the steam network based on ``gevent``. * `SteamClient <http://steam.readthedocs.io/en/latest/api/steam.client.html>`_ - communication with the steam network based on ``gevent``.
* `SteamID <http://valvepython.github.io/steam/api/steam.client.html>`_ - convert between the various ID representations with ease * `SteamID <http://steam.readthedocs.io/en/latest/api/steam.client.html>`_ - convert between the various ID representations with ease
* `WebAPI <http://valvepython.github.io/steam/api/steam.webapi.html>`_ - simple API for Steam's Web API with automatic population of interfaces * `WebAPI <http://steam.readthedocs.io/en/latest/api/steam.webapi.html>`_ - simple API for Steam's Web API with automatic population of interfaces
* `WebAuth <http://valvepython.github.io/steam/api/steam.webauth.html>`_ - authentication for access to ``store.steampowered.com`` and ``steamcommunity.com`` * `WebAuth <http://steam.readthedocs.io/en/latest/api/steam.webauth.html>`_ - authentication for access to ``store.steampowered.com`` and ``steamcommunity.com``
Checkout the `User guide <http://valvepython.github.io/steam/user_guide.html>`_ for examples, Checkout the `User guide <http://steam.readthedocs.io/en/latest/steam/user_guide.html>`_ for examples,
or the `API Reference <http://valvepython.github.io/steam/api/index.html>`_ for details. or the `API Reference <http://steam.readthedocs.io/en/latest/steam/api/index.html>`_ for details.
For questions, issues or general curiosity visit the repo at `https://github.com/ValvePython/steam <https://github.com/ValvePython/steam>`_. For questions, issues or general curiosity visit the repo at `https://github.com/ValvePython/steam <https://github.com/ValvePython/steam>`_.

28
steam/client/__init__.py

@ -12,17 +12,17 @@ Events
| ``auth_code_required`` - either email code or 2FA code is needed for login | ``auth_code_required`` - either email code or 2FA code is needed for login
| ``logged_on`` - after successful login, client can send messages | ``logged_on`` - after successful login, client can send messages
| ``new_login_key`` - after new login key has been received and acknowledged | ``new_login_key`` - after new login key has been received and acknowledged
| :class:`EMsg <steam.enums.emsg.EMsg>` - all messages are emitted with their ``EMsg`` | :class:`.EMsg` - all messages are emitted with their :class:`.EMsg` number
.. note:: .. note::
Mixins can emitter additional events. See their docs pages for details. Mixins can emitter additional events. See their docs pages for details.
.. note:: .. note::
Additional features are located in separate submodules. All functionality from ``bultins`` is inherited by default. Additional features are located in separate submodules. All functionality from :mod:`.builtins` is inherited by default.
.. note:: .. note::
Optional features are available as ``mixins``. This allows the client to remain light yet flexible. Optional features are available as :mod:`.mixins`. This allows the client to remain light yet flexible.
""" """
import os import os
@ -85,12 +85,12 @@ class SteamClient(CMClient, BuiltinBase):
self.credential_location = path self.credential_location = path
def connect(self, *args, **kwargs): def connect(self, *args, **kwargs):
"""Attempt to establish connection, see :method:`.CMClient.connect`""" """Attempt to establish connection, see :meth:`.CMClient.connect`"""
self._bootstrap_cm_list_from_file() self._bootstrap_cm_list_from_file()
CMClient.connect(self, *args, **kwargs) CMClient.connect(self, *args, **kwargs)
def disconnect(self, *args, **kwargs): def disconnect(self, *args, **kwargs):
"""Close connection, see :method:`.CMClient.disconnect`""" """Close connection, see :meth:`.CMClient.disconnect`"""
self.logged_on = False self.logged_on = False
CMClient.disconnect(self, *args, **kwargs) CMClient.disconnect(self, *args, **kwargs)
@ -211,12 +211,12 @@ class SteamClient(CMClient, BuiltinBase):
def reconnect(self, maxdelay=30, retry=0): def reconnect(self, maxdelay=30, retry=0):
"""Implements explonential backoff delay before attempting to connect. """Implements explonential backoff delay before attempting to connect.
It is otherwise identical to calling :meth:`steam.core.cm.CMClient.connect`. It is otherwise identical to calling :meth:`.CMClient.connect`.
The delay is reset upon a successful login. The delay is reset upon a successful login.
:param maxdelay: maximum delay in seconds before connect (0-120s) :param maxdelay: maximum delay in seconds before connect (0-120s)
:type maxdelay: :class:`int` :type maxdelay: :class:`int`
:param retry: see :meth:`steam.core.cm.CMClient.connect` :param retry: see :meth:`.CMClient.connect`
:type retry: :class:`int` :type retry: :class:`int`
:return: successful connection :return: successful connection
:rtype: :class:`bool` :rtype: :class:`bool`
@ -233,7 +233,7 @@ class SteamClient(CMClient, BuiltinBase):
Send a message to CM Send a message to CM
:param message: a message instance :param message: a message instance
:type message: :class:`steam.core.msg.Msg`, :class:`steam.core.msg.MsgProto` :type message: :class:`.Msg`, :class:`.MsgProto`
""" """
if not self.connected: if not self.connected:
raise RuntimeError("Cannot send message while not connected") raise RuntimeError("Cannot send message while not connected")
@ -247,7 +247,7 @@ class SteamClient(CMClient, BuiltinBase):
Not all messages are jobs, you'll have to find out which are which Not all messages are jobs, you'll have to find out which are which
:param message: a message instance :param message: a message instance
:type message: :class:`steam.core.msg.Msg`, :class:`steam.core.msg.MsgProto` :type message: :class:`.Msg`, :class:`.MsgProto`
:return: ``jobid`` event identifier :return: ``jobid`` event identifier
:rtype: :class:`str` :rtype: :class:`str`
@ -285,13 +285,13 @@ class SteamClient(CMClient, BuiltinBase):
Not all messages are jobs, you'll have to find out which are which Not all messages are jobs, you'll have to find out which are which
:param message: a message instance :param message: a message instance
:type message: :class:`steam.core.msg.Msg`, :class:`steam.core.msg.MsgProto` :type message: :class:`.Msg`, :class:`.MsgProto`
:param timeout: (optional) seconds to wait :param timeout: (optional) seconds to wait
:type timeout: :class:`int` :type timeout: :class:`int`
:param raises: (optional) On timeout if ``False`` return ``None``, else raise ``gevent.Timeout`` :param raises: (optional) On timeout if ``False`` return ``None``, else raise ``gevent.Timeout``
:type raises: :class:`bool` :type raises: :class:`bool`
:return: response proto message :return: response proto message
:rtype: :class:`steam.core.msg.Msg`, :class:`steam.core.msg.MsgProto` :rtype: :class:`.Msg`, :class:`.MsgProto`
:raises: ``gevent.Timeout`` :raises: ``gevent.Timeout``
""" """
job_id = self.send_job(message) job_id = self.send_job(message)
@ -305,15 +305,15 @@ class SteamClient(CMClient, BuiltinBase):
Send a message to CM and wait for a defined answer. Send a message to CM and wait for a defined answer.
:param message: a message instance :param message: a message instance
:type message: :class:`steam.core.msg.Msg`, :class:`steam.core.msg.MsgProto` :type message: :class:`.Msg`, :class:`.MsgProto`
:param response_emsg: emsg to wait for :param response_emsg: emsg to wait for
:type response_emsg: :class:`steam.enums.emsg.EMsg`,:class:`int` :type response_emsg: :class:`.EMsg`,:class:`int`
:param timeout: (optional) seconds to wait :param timeout: (optional) seconds to wait
:type timeout: :class:`int` :type timeout: :class:`int`
:param raises: (optional) On timeout if ``False`` return ``None``, else raise ``gevent.Timeout`` :param raises: (optional) On timeout if ``False`` return ``None``, else raise ``gevent.Timeout``
:type raises: :class:`bool` :type raises: :class:`bool`
:return: response proto message :return: response proto message
:rtype: :class:`steam.core.msg.Msg`, :class:`steam.core.msg.MsgProto` :rtype: :class:`.Msg`, :class:`.MsgProto`
:raises: ``gevent.Timeout`` :raises: ``gevent.Timeout``
""" """
self.send(message) self.send(message)

51
steam/guard.py

@ -105,14 +105,6 @@ class SteamAuthenticator(object):
self.get_time() if timestamp is None else timestamp) self.get_time() if timestamp is None else timestamp)
def _send_request(self, action, params): def _send_request(self, action, params):
action_map = {
'add': 'AddAuthenticator',
'finalize': 'FinalizeAddAuthenticator',
'remove': 'RemoveAuthenticator',
'status': 'QueryStatus',
'createcodes': 'CreateEmergencyCodes',
'destroycodes': 'DestroyEmergencyCodes',
}
medium = self.medium medium = self.medium
if isinstance(medium, MobileWebAuth): if isinstance(medium, MobileWebAuth):
@ -123,7 +115,7 @@ class SteamAuthenticator(object):
params['http_timeout'] = 10 params['http_timeout'] = 10
try: try:
resp = webapi.post('ITwoFactorService', action_map[action], 1, params=params) resp = webapi.post('ITwoFactorService', action, 1, params=params)
except requests.exceptions.RequestException as exp: except requests.exceptions.RequestException as exp:
raise SteamAuthenticatorError("Error adding via WebAPI: %s" % str(exp)) raise SteamAuthenticatorError("Error adding via WebAPI: %s" % str(exp))
@ -132,14 +124,14 @@ class SteamAuthenticator(object):
if not medium.logged_on: if not medium.logged_on:
raise SteamAuthenticatorError("SteamClient instance not logged in") raise SteamAuthenticatorError("SteamClient instance not logged in")
resp = medium.unified_messages.send_and_wait("TwoFactor.%s#1" % action_map[action], resp = medium.unified_messages.send_and_wait("TwoFactor.%s#1" % action,
params, timeout=10) params, timeout=10)
if resp is None: if resp is None:
raise SteamAuthenticatorError("Failed to add authenticator. Request timeout") raise SteamAuthenticatorError("Failed to add authenticator. Request timeout")
resp = proto_to_dict(resp) resp = proto_to_dict(resp)
if action == 'add': if action == 'AddAuthenticator':
for key in ['shared_secret', 'identity_secret', 'secret_1']: for key in ['shared_secret', 'identity_secret', 'secret_1']:
resp[key] = b64encode(resp[key]) resp[key] = b64encode(resp[key])
@ -151,23 +143,18 @@ class SteamAuthenticator(object):
:raises: :class:`SteamAuthenticatorError` :raises: :class:`SteamAuthenticatorError`
""" """
params = { resp = self._send_request('AddAuthenticator', {
'steamid': self.medium.steam_id, 'steamid': self.medium.steam_id,
'authenticator_time': int(time()), 'authenticator_time': int(time()),
'authenticator_type': int(ETwoFactorTokenType.ValveMobileApp), 'authenticator_type': int(ETwoFactorTokenType.ValveMobileApp),
'device_identifier': generate_device_id(self.medium.steam_id), 'device_identifier': generate_device_id(self.medium.steam_id),
'sms_phone_id': '1', 'sms_phone_id': '1',
} })
resp = self._send_request('add', params)
if resp['status'] != EResult.OK: if resp['status'] != EResult.OK:
raise SteamAuthenticatorError("Failed to add authenticator. Error: %s" % repr(EResult(resp['status']))) raise SteamAuthenticatorError("Failed to add authenticator. Error: %s" % repr(EResult(resp['status'])))
for key in ['shared_secret', 'identity_secret', 'serial_number', 'secret_1', 'revocation_code', 'token_gid']: self.secrets = resp
if key in resp:
self.secrets[key] = resp[key]
self.steam_time_offset = int(resp['server_time']) - time() self.steam_time_offset = int(resp['server_time']) - time()
def finalize(self, activation_code): def finalize(self, activation_code):
@ -177,14 +164,12 @@ class SteamAuthenticator(object):
:type activation_code: str :type activation_code: str
:raises: :class:`SteamAuthenticatorError` :raises: :class:`SteamAuthenticatorError`
""" """
params = { resp = self._send_request('FinalizeAddAuthenticator', {
'steamid': self.medium.steam_id, 'steamid': self.medium.steam_id,
'authenticator_time': int(time()), 'authenticator_time': int(time()),
'authenticator_code': self.get_code(), 'authenticator_code': self.get_code(),
'activation_code': activation_code, 'activation_code': activation_code,
} })
resp = self._send_request('finalize', params)
if resp['status'] != EResult.TwoFactorActivationCodeMismatch and resp.get('want_more', False) and self._finalize_attempts: if resp['status'] != EResult.TwoFactorActivationCodeMismatch and resp.get('want_more', False) and self._finalize_attempts:
self.steam_time_offset += 30 self.steam_time_offset += 30
@ -208,13 +193,11 @@ class SteamAuthenticator(object):
if not self.secrets: if not self.secrets:
raise SteamAuthenticatorError("No authenticator secrets available?") raise SteamAuthenticatorError("No authenticator secrets available?")
params = { resp = self._send_request('RemoveAuthenticator', {
'steamid': self.medium.steam_id, 'steamid': self.medium.steam_id,
'revocation_code': self.revocation_code, 'revocation_code': self.revocation_code,
'steamguard_scheme': 1, 'steamguard_scheme': 1,
} })
resp = self._send_request('remove', params)
if not resp['success']: if not resp['success']:
raise SteamAuthenticatorError("Failed to remove authenticator. (attempts remaining: %s)" % ( raise SteamAuthenticatorError("Failed to remove authenticator. (attempts remaining: %s)" % (
@ -230,8 +213,7 @@ class SteamAuthenticator(object):
:return: dict with status parameters :return: dict with status parameters
:rtype: dict :rtype: dict
""" """
params = {'steamid': self.medium.steam_id} return self._send_request('QueryStatus', {'steamid': self.medium.steam_id})
return self._send_request('status', params)
def create_emergency_codes(self): def create_emergency_codes(self):
"""Generate emergency codes """Generate emergency codes
@ -240,15 +222,14 @@ class SteamAuthenticator(object):
:return: list of codes :return: list of codes
:rtype: list :rtype: list
""" """
return self._send_request('createcodes', {}).get('code', []) return self._send_request('CreateEmergencyCodes', {}).get('code', [])
def destroy_emergency_codes(self): def destroy_emergency_codes(self):
"""Destroy all emergency codes """Destroy all emergency codes
:raises: :class:`SteamAuthenticatorError` :raises: :class:`SteamAuthenticatorError`
""" """
params = {'steamid': self.medium.steam_id} self._send_request('DestroyEmergencyCodes', {'steamid': self.medium.steam_id})
self._send_request('destroycodes', params)
class SteamAuthenticatorError(Exception): class SteamAuthenticatorError(Exception):
@ -290,15 +271,15 @@ def generate_twofactor_code_for_time(shared_secret, timestamp):
return code return code
def generate_confirmation_key(identity_secret, timestamp, tag=''): def generate_confirmation_key(identity_secret, tag, timestamp):
"""Generate confirmation key for trades. Can only be used once. """Generate confirmation key for trades. Can only be used once.
:param identity_secret: authenticator identity secret :param identity_secret: authenticator identity secret
:type identity_secret: bytes :type identity_secret: bytes
:param timestamp: timestamp to use for generating key
:type timestamp: int
:param tag: tag identifies what the request, see list below :param tag: tag identifies what the request, see list below
:type tag: str :type tag: str
:param timestamp: timestamp to use for generating key
:type timestamp: int
:return: confirmation key :return: confirmation key
:rtype: bytes :rtype: bytes

4
tests/test_guard.py

@ -12,8 +12,8 @@ class TCguard(unittest.TestCase):
self.assertEqual(code, '94R9D') self.assertEqual(code, '94R9D')
def test_generate_confirmation_key(self): def test_generate_confirmation_key(self):
key = g.generate_confirmation_key(b'itsmemario', 100000) key = g.generate_confirmation_key(b'itsmemario', '', 100000)
self.assertEqual(key, b'\xed\xb5\xe5\xad\x8f\xf1\x99\x01\xc8-w\xd6\xb5 p\xccz\xd7\xd1\x05') self.assertEqual(key, b'\xed\xb5\xe5\xad\x8f\xf1\x99\x01\xc8-w\xd6\xb5 p\xccz\xd7\xd1\x05')
key = g.generate_confirmation_key(b'itsmemario', 100000, 'allow') key = g.generate_confirmation_key(b'itsmemario', 'allow', 100000)
self.assertEqual(key, b"Q'\x06\x80\xe1g\xa8m$\xb2hV\xe6g\x8b'\x8f\xf1L\xb0") self.assertEqual(key, b"Q'\x06\x80\xe1g\xa8m$\xb2hV\xe6g\x8b'\x8f\xf1L\xb0")

Loading…
Cancel
Save