diff --git a/.scrutinizer.yml b/.scrutinizer.yml index fc6e121..1e0c604 100644 --- a/.scrutinizer.yml +++ b/.scrutinizer.yml @@ -5,5 +5,5 @@ filter: - 'steam/protobufs/*' tools: external_code_coverage: - timeout: 600 - runs: 1 + timeout: 300 + runs: 4 diff --git a/README.rst b/README.rst index 65cff8c..6d08794 100644 --- a/README.rst +++ b/README.rst @@ -2,7 +2,7 @@ A python module for interacting with various parts of Steam_. -Supports Python ``2.7+`` and ``3.4+``. +Supports Python ``2.7+`` and ``3.3+``. Documentation: http://steam.readthedocs.io/en/latest/ diff --git a/docs/intro.rst b/docs/intro.rst index f657a53..c377611 100644 --- a/docs/intro.rst +++ b/docs/intro.rst @@ -9,7 +9,7 @@ Intro - steam |version| A python module for interacting with various parts of Steam_. -Supports Python ``2.7+`` and ``3.4+``. +Supports Python ``2.7+`` and ``3.3+``. Main features ============= diff --git a/setup.py b/setup.py index 0cae798..a86c050 100644 --- a/setup.py +++ b/setup.py @@ -47,6 +47,7 @@ setup( 'License :: OSI Approved :: MIT License', 'Operating System :: OS Independent', 'Programming Language :: Python :: 2.7', + 'Programming Language :: Python :: 3.3', 'Programming Language :: Python :: 3.4', 'Programming Language :: Python :: 3.5', ], diff --git a/steam/webauth.py b/steam/webauth.py index 3cc9513..16b2063 100644 --- a/steam/webauth.py +++ b/steam/webauth.py @@ -12,6 +12,11 @@ The session can be used to access ``steamcommunity.com``, ``store.steampowered.c .. note:: If you are using :class:`steam.client.SteamClient`, use :meth:`steam.client.builtins.web.Web.get_web_session()` +.. note:: + If you need to authenticate as a mobile device for things like trading confirmations + use :class:`MobileWebAuth` instead. The login process is identical, and in addition + you will get :attr:`.oauth_token`. + Example usage: @@ -49,8 +54,9 @@ Alternatively, if Steam Guard is not enabled on the account: The :class:`WebAuth` instance should be discarded once a session is obtained as it is not reusable. """ -from time import time import sys +import json +from time import time from base64 import b64encode import requests @@ -58,8 +64,8 @@ from cryptography.hazmat.primitives.asymmetric.rsa import RSAPublicNumbers from cryptography.hazmat.primitives.asymmetric.padding import PKCS1v15 from steam.core.crypto import backend +from steam import SteamID, webapi from steam.util.web import make_requests_session -from steam import SteamID if sys.version_info < (3,): intBase = long @@ -77,6 +83,10 @@ class WebAuth(object): def __init__(self, username, password): self.__dict__.update(locals()) self.session = make_requests_session() + self._session_setup() + + def _session_setup(self): + pass @property def captcha_url(self): @@ -84,7 +94,7 @@ class WebAuth(object): if self.captcha_gid == -1: return None else: - return "https://store.steampowered.com/login/rendercaptcha/?gid=%s" % self.captcha_gid + return "https://steamcommunity.com/login/rendercaptcha/?gid=%s" % self.captcha_gid def get_rsa_key(self, username): """Get rsa key for a given username @@ -96,6 +106,7 @@ class WebAuth(object): :raises HTTPError: any problem with http request, timeouts, 5xx, 4xx etc """ try: +<<<<<<< HEAD resp = self.session.post('https://store.steampowered.com/login/getrsakey/', timeout=15, data={ @@ -103,6 +114,15 @@ class WebAuth(object): 'donotchache': int(time() * 1000), }, ).json() +======= + resp = self.session.post('https://steamcommunity.com/login/getrsakey/', + timeout=15, + data={ + 'username': username, + 'donotchache': int(time() * 1000), + }, + ).json() +>>>>>>> refs/remotes/ValvePython/master except requests.exceptions.RequestException as e: raise HTTPError(str(e)) @@ -119,6 +139,29 @@ class WebAuth(object): self.key = backend.load_rsa_public_numbers(nums) self.timestamp = resp['timestamp'] + def _send_login(self, captcha='', email_code='', twofactor_code=''): + data = { + 'username' : self.username, + "password": b64encode(self.key.encrypt(self.password.encode('ascii'), PKCS1v15())), + "emailauth": email_code, + "emailsteamid": str(self.steamid) if email_code else '', + "twofactorcode": twofactor_code, + "captchagid": self.captcha_gid, + "captcha_text": captcha, + "loginfriendlyname": "python-steam webauth", + "rsatimestamp": self.timestamp, + "remember_login": 'true', + "donotcache": int(time() * 100000), + } + + try: + return self.session.post('https://steamcommunity.com/login/dologin/', data=data, timeout=15).json() + except requests.exceptions.RequestException as e: + raise HTTPError(str(e)) + + def _finalize_login(self, login_response): + self.steamid = SteamID(login_response['transfer_parameters']['steamid']) + def login(self, captcha='', email_code='', twofactor_code='', language='english'): """Attempts web login and returns on a session with cookies set @@ -142,6 +185,7 @@ class WebAuth(object): return self.session self._load_key() +<<<<<<< HEAD data = { 'username': self.username, @@ -161,6 +205,9 @@ class WebAuth(object): resp = self.session.post('https://store.steampowered.com/login/dologin/', data=data, timeout=15).json() except requests.exceptions.RequestException as e: raise HTTPError(str(e)) +======= + resp = self._send_login(captcha=captcha, email_code=email_code, twofactor_code=twofactor_code) +>>>>>>> refs/remotes/ValvePython/master self.captcha_gid = -1 @@ -168,9 +215,6 @@ class WebAuth(object): self.complete = True self.password = None - data = resp['transfer_parameters'] - self.steamid = SteamID(data['steamid']) - for cookie in list(self.session.cookies): for domain in ['store.steampowered.com', 'help.steampowered.com', 'steamcommunity.com']: self.session.cookies.set(cookie.name, cookie.value, domain=domain, secure=cookie.secure) @@ -179,6 +223,8 @@ class WebAuth(object): self.session.cookies.set('Steam_Language', language, domain=domain) self.session.cookies.set('birthtime', '-3333', domain=domain) + self._finalize_login(resp) + return self.session else: if resp.get('captcha_needed', False): @@ -196,6 +242,44 @@ class WebAuth(object): return None +class MobileWebAuth(WebAuth): + """Identical to :class:`WebAuth`, except it authenticates as a mobile device.""" + oauth_token = None #: holds oauth_token after successful login + + def _send_login(self, captcha='', email_code='', twofactor_code=''): + data = { + 'username' : self.username, + "password": b64encode(self.key.encrypt(self.password.encode('ascii'), PKCS1v15())), + "emailauth": email_code, + "emailsteamid": str(self.steamid) if email_code else '', + "twofactorcode": twofactor_code, + "captchagid": self.captcha_gid, + "captcha_text": captcha, + "loginfriendlyname": "python-steam webauth", + "rsatimestamp": self.timestamp, + "remember_login": 'true', + "donotcache": int(time() * 100000), + "oauth_client_id": "DE45CD61", + "oauth_scope": "read_profile write_profile read_client write_client", + } + + self.session.cookies.set('mobileClientVersion', '0 (2.1.3)') + self.session.cookies.set('mobileClient', 'android') + + try: + return self.session.post('https://steamcommunity.com/login/dologin/', data=data, timeout=15).json() + except requests.exceptions.RequestException as e: + raise HTTPError(str(e)) + finally: + self.session.cookies.pop('mobileClientVersion', None) + self.session.cookies.pop('mobileClient', None) + + def _finalize_login(self, login_response): + data = json.loads(login_response['oauth']) + self.steamid = SteamID(data['steamid']) + self.oauth_token = data['oauth_token'] + + class WebAuthException(Exception): pass diff --git a/tests/generete_webauth_vcr.py b/tests/generete_webauth_vcr.py index 1d5e950..d46d193 100644 --- a/tests/generete_webauth_vcr.py +++ b/tests/generete_webauth_vcr.py @@ -21,15 +21,16 @@ from steam import webauth as wa # any personal info. MAKE SURE TO CHECK THE VCR BEFORE COMMIT TO REPO def request_scrubber(r): + r['headers'].pop('set-cokie', None) r.body = '' return r def response_scrubber(r): - if 'set-cookie' in r['headers']: + if 'set-cookie' in r['headers'] and 'steamLogin' in ''.join(r['headers']['set-cookie']): r['headers']['set-cookie'] = [ 'steamLogin=0%7C%7C{}; path=/; httponly'.format('A'*16), 'steamLoginSecure=0%7C%7C{}; path=/; httponly; secure'.format('B'*16), - 'steamMachineAuth=0%7C%7C{}; path=/; httponly'.format('C'*16), + 'steamMachineAuth0={}; path=/; httponly'.format('C'*16), ] if r.get('body', ''): @@ -47,6 +48,7 @@ def response_scrubber(r): r['body']['string'] = body r['headers']['content-length'] = [str(len(body))] + print("--- response ---------") print(r) return r diff --git a/tests/test_webauth.py b/tests/test_webauth.py index a7f9fb6..588ec2a 100644 --- a/tests/test_webauth.py +++ b/tests/test_webauth.py @@ -38,7 +38,7 @@ class WACase(unittest.TestCase): for domain in s.cookies.list_domains(): self.assertEqual(s.cookies.get('steamLogin', domain=domain), '0%7C%7C{}'.format('A'*16)) self.assertEqual(s.cookies.get('steamLoginSecure', domain=domain), '0%7C%7C{}'.format('B'*16)) - self.assertEqual(s.cookies.get('steamMachineAuth', domain=domain), '0%7C%7C{}'.format('C'*16)) + self.assertEqual(s.cookies.get('steamMachineAuth0', domain=domain), 'C'*16) self.assertEqual(s, user.login()) diff --git a/vcr/webauth_user_pass_only_fail.yaml b/vcr/webauth_user_pass_only_fail.yaml index dbf2562..63a1b05 100644 --- a/vcr/webauth_user_pass_only_fail.yaml +++ b/vcr/webauth_user_pass_only_fail.yaml @@ -7,19 +7,26 @@ interactions: Connection: [keep-alive] Content-Length: ['46'] Content-Type: [application/x-www-form-urlencoded] - User-Agent: [python-steam/0.7.4 python-requests/2.9.1] + User-Agent: [python-steam/0.8.3 python-requests/2.9.1] method: POST - uri: https://store.steampowered.com/login/getrsakey/ + uri: https://steamcommunity.com/login/getrsakey/ response: - body: {string: !!python/unicode '{"timestamp": "75683600000", "token_gid": 0, - "publickey_mod": "C91B651503A9831EB1708001E9D77FC618D472D29467B7CA31984918BECED0E1E54687EC744B93A10E12D3107B0CC7533923593600D98CCAA921914FB16B4D9910EB7CD556663778C081A22848DCCAB178772C88F24373679441AF0D7042208FE24AB9ADCB17F8C147425ED0C88B67E3E4C997511AB1B3A42F6CE2D0EC1D16FDEFE40D92C778EF45DACAD2AC10177D03B5736C6C339EAD8F03768036E3546A070D5A1FC04BA30495BEAA4A342D842A59971A179A39B6AA532F3BEEDE50596F52F15C9DF7D1DF4EFB470ACB2C36C005FDBDB5A3AB7D9A3D665120B0E04DD782C5E3CFBDA922761ADE5B09D742ED54C198C11A60C77725E4C001AE68C9855D5ED5", + body: {string: !!python/unicode '{"timestamp": "259640400000", "token_gid": 0, + "publickey_mod": "CA419D47649D81ACB6A748C80BEF0F8CBB87B4B64ED810EE1A60A47A18A511EFEB52936FBC0986714BB0D725643FE255E39FC3426E9BA8B331C3598B96E9BC18E6E2B86AE1ED7E8E96E8FF1CB7705D3C138C56FF6F07B5F62F1F3199116CB4058E23CD62C79BC840C630BD0FECF59F3D368007A7F12E5A7306DB70E5EEF3430D11FD60745495CB4E0F4167637D1DED0A02DADC28C06E4B97456FCB316163D5C97BE2ECF3A0C1E6BC53C59BF703F1C7187B5D165497AB4337D46A2B15D1811F65F5C46E58D7A85685409C6C2CBB81B397CE341CD33468ED62289C356A82C270258E361C629396012FACD282BDBC7440D6AD7AEDF9C94AF1596FDEA572EA873273", "publickey_exp": "010001", "success": true}'} headers: cache-control: [no-cache] connection: [keep-alive] - content-length: ['621'] + content-length: ['622'] + content-security-policy: ['script-src ''self'' ''unsafe-inline'' ''unsafe-eval'' + https://steamcommunity-a.akamaihd.net/ https://api.steampowered.com/ *.google-analytics.com + https://www.google.com https://www.gstatic.com https://apis.google.com; + object-src ''none''; connect-src ''self'' http://steamcommunity.com https://steamcommunity.com + https://api.steampowered.com/; frame-src ''self'' http://store.steampowered.com/ + https://store.steampowered.com/ http://www.youtube.com https://www.youtube.com + https://www.google.com https://sketchfab.com https://help.steampowered.com/;'] content-type: [application/json; charset=utf-8] - date: ['Fri, 13 May 2016 03:01:26 GMT'] + date: ['Sat, 04 Jun 2016 00:05:21 GMT'] expires: ['Mon, 26 Jul 1997 05:00:00 GMT'] server: [Apache] x-frame-options: [DENY] @@ -30,11 +37,11 @@ interactions: Accept: ['*/*'] Accept-Encoding: ['gzip, deflate'] Connection: [keep-alive] - Content-Length: ['578'] + Content-Length: ['586'] Content-Type: [application/x-www-form-urlencoded] - User-Agent: [python-steam/0.7.4 python-requests/2.9.1] + User-Agent: [python-steam/0.8.3 python-requests/2.9.1] method: POST - uri: https://store.steampowered.com/login/dologin/ + uri: https://steamcommunity.com/login/dologin/ response: body: {string: !!python/unicode '{"captcha_gid": -1, "success": false, "requires_twofactor": false, "captcha_needed": false, "clear_password_field": true, "message": "Incorrect @@ -43,8 +50,15 @@ interactions: cache-control: [no-cache] connection: [keep-alive] content-length: ['152'] + content-security-policy: ['script-src ''self'' ''unsafe-inline'' ''unsafe-eval'' + https://steamcommunity-a.akamaihd.net/ https://api.steampowered.com/ *.google-analytics.com + https://www.google.com https://www.gstatic.com https://apis.google.com; + object-src ''none''; connect-src ''self'' http://steamcommunity.com https://steamcommunity.com + https://api.steampowered.com/; frame-src ''self'' http://store.steampowered.com/ + https://store.steampowered.com/ http://www.youtube.com https://www.youtube.com + https://www.google.com https://sketchfab.com https://help.steampowered.com/;'] content-type: [application/json; charset=utf-8] - date: ['Fri, 13 May 2016 03:01:27 GMT'] + date: ['Sat, 04 Jun 2016 00:05:22 GMT'] expires: ['Mon, 26 Jul 1997 05:00:00 GMT'] server: [Apache] x-frame-options: [DENY] diff --git a/vcr/webauth_user_pass_only_success.yaml b/vcr/webauth_user_pass_only_success.yaml index 6ba8e3d..9d71cd3 100644 --- a/vcr/webauth_user_pass_only_success.yaml +++ b/vcr/webauth_user_pass_only_success.yaml @@ -7,19 +7,26 @@ interactions: Connection: [keep-alive] Content-Length: ['46'] Content-Type: [application/x-www-form-urlencoded] - User-Agent: [python-steam/0.7.4 python-requests/2.9.1] + User-Agent: [python-steam/0.8.3 python-requests/2.9.1] method: POST - uri: https://store.steampowered.com/login/getrsakey/ + uri: https://steamcommunity.com/login/getrsakey/ response: - body: {string: !!python/unicode '{"timestamp": "75683600000", "token_gid": 0, - "publickey_mod": "C91B651503A9831EB1708001E9D77FC618D472D29467B7CA31984918BECED0E1E54687EC744B93A10E12D3107B0CC7533923593600D98CCAA921914FB16B4D9910EB7CD556663778C081A22848DCCAB178772C88F24373679441AF0D7042208FE24AB9ADCB17F8C147425ED0C88B67E3E4C997511AB1B3A42F6CE2D0EC1D16FDEFE40D92C778EF45DACAD2AC10177D03B5736C6C339EAD8F03768036E3546A070D5A1FC04BA30495BEAA4A342D842A59971A179A39B6AA532F3BEEDE50596F52F15C9DF7D1DF4EFB470ACB2C36C005FDBDB5A3AB7D9A3D665120B0E04DD782C5E3CFBDA922761ADE5B09D742ED54C198C11A60C77725E4C001AE68C9855D5ED5", + body: {string: !!python/unicode '{"timestamp": "263253650000", "token_gid": 0, + "publickey_mod": "E0E0F924CDF326B86350CB6EDB534ED68A20C11FA1BF77DE7C4AE5B83A5264D7594511F17E6A46EB33908AAAE13A37653478949885B4780EEF23094AB153414AB30AD668CEEDB923B05373E62A7A7B5DB3DFF72FC9F79B6D6159CBA292EE21037701566EADA53C6BEEFCF8075245DC9577DB9357B551B8DC1339BFA0146FFF79F21BCEAB16E9DF638AC5E172A8FA7BA37F608478C12C6DF203533E08CF04971A644CD2E17B7A33C19AE7DB14A80D34A44DB30F37A8EB480A908F4449A80E64CB065C86309AFB942DF2B2256A3E56B9E6E66362454F9AFAC0817AD4143177A44ECE79D7058179A3FA13156DCAA2614792FDCB8186D678151AB6984FFE1D47BE3B", "publickey_exp": "010001", "success": true}'} headers: cache-control: [no-cache] connection: [keep-alive] - content-length: ['621'] + content-length: ['622'] + content-security-policy: ['script-src ''self'' ''unsafe-inline'' ''unsafe-eval'' + https://steamcommunity-a.akamaihd.net/ https://api.steampowered.com/ *.google-analytics.com + https://www.google.com https://www.gstatic.com https://apis.google.com; + object-src ''none''; connect-src ''self'' http://steamcommunity.com https://steamcommunity.com + https://api.steampowered.com/; frame-src ''self'' http://store.steampowered.com/ + https://store.steampowered.com/ http://www.youtube.com https://www.youtube.com + https://www.google.com https://sketchfab.com https://help.steampowered.com/;'] content-type: [application/json; charset=utf-8] - date: ['Fri, 13 May 2016 03:01:24 GMT'] + date: ['Sat, 04 Jun 2016 00:17:23 GMT'] expires: ['Mon, 26 Jul 1997 05:00:00 GMT'] server: [Apache] x-frame-options: [DENY] @@ -30,28 +37,34 @@ interactions: Accept: ['*/*'] Accept-Encoding: ['gzip, deflate'] Connection: [keep-alive] - Content-Length: ['588'] + Content-Length: ['574'] Content-Type: [application/x-www-form-urlencoded] - User-Agent: [python-steam/0.7.4 python-requests/2.9.1] + User-Agent: [python-steam/0.8.3 python-requests/2.9.1] method: POST - uri: https://store.steampowered.com/login/dologin/ + uri: https://steamcommunity.com/login/dologin/ response: body: {string: !!python/unicode '{"requires_twofactor": false, "login_complete": - true, "transfer_urls": ["https://steamcommunity.com/login/transfer", "https://help.steampowered.com/login/transfer"], + true, "transfer_urls": ["https://store.steampowered.com/login/transfer", "https://help.steampowered.com/login/transfer"], "transfer_parameters": {"steamid": "0", "remember_login": false, "token": "AAAAAAAAAAAAAAAA", "token_secure": "BBBBBBBBBBBBBBBB", "auth": "ZZZZZZZZZZZZZZZZ"}, "success": true}'} headers: cache-control: [no-cache] connection: [keep-alive] - content-length: ['341'] + content-length: ['345'] + content-security-policy: ['script-src ''self'' ''unsafe-inline'' ''unsafe-eval'' + https://steamcommunity-a.akamaihd.net/ https://api.steampowered.com/ *.google-analytics.com + https://www.google.com https://www.gstatic.com https://apis.google.com; + object-src ''none''; connect-src ''self'' http://steamcommunity.com https://steamcommunity.com + https://api.steampowered.com/; frame-src ''self'' http://store.steampowered.com/ + https://store.steampowered.com/ http://www.youtube.com https://www.youtube.com + https://www.google.com https://sketchfab.com https://help.steampowered.com/;'] content-type: [application/json; charset=utf-8] - date: ['Fri, 13 May 2016 03:01:25 GMT'] + date: ['Sat, 04 Jun 2016 00:17:24 GMT'] expires: ['Mon, 26 Jul 1997 05:00:00 GMT'] server: [Apache] set-cookie: [steamLogin=0%7C%7CAAAAAAAAAAAAAAAA; path=/; httponly, steamLoginSecure=0%7C%7CBBBBBBBBBBBBBBBB; - path=/; httponly; secure, steamMachineAuth=0%7C%7CCCCCCCCCCCCCCCCC; path=/; - httponly] + path=/; httponly; secure, steamMachineAuth0=CCCCCCCCCCCCCCCC; path=/; httponly] x-frame-options: [DENY] status: {code: 200, message: OK} version: 1