From 1702018257b6b1bc6ff503a62210e1835e45c14d Mon Sep 17 00:00:00 2001 From: Philipp Date: Wed, 1 Jun 2016 12:43:11 +0200 Subject: [PATCH 1/5] Create mobileauth.py --- steam/mobileauth.py | 135 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 135 insertions(+) create mode 100644 steam/mobileauth.py diff --git a/steam/mobileauth.py b/steam/mobileauth.py new file mode 100644 index 0000000..6ef7582 --- /dev/null +++ b/steam/mobileauth.py @@ -0,0 +1,135 @@ +# -*- coding: utf-8 -*- +from time import time +import sys +from base64 import b64encode +import requests + +from cryptography.hazmat.primitives.asymmetric.rsa import RSAPublicNumbers +from cryptography.hazmat.primitives.asymmetric.padding import PKCS1v15 +from steam.core.crypto import backend + +from steam.util.web import make_requests_session +from steam import SteamID + +if sys.version_info < (3,): + intBase = long +else: + intBase = int + + +class MobileAuth(object): + key = None + complete = False #: whether authentication has been completed successfully + session = None #: :class:`requests.Session` (with auth cookies after auth is complete) + captcha_gid = -1 + steamid = None #: :class:`steam.steamid.SteamID` (after auth is complete) + + def __init__(self, username, password): + self.__dict__.update(locals()) + self.session = make_requests_session() + + @property + def captcha_url(self): + if self.captcha_gid == -1: + return None + else: + return "https://store.steampowered.com/login/rendercaptcha/?gid=%s" % self.captcha_gid + + def get_rsa_key(self, username): + try: + resp = self.session.post('https://steamcommunity.com/mobilelogin/getrsakey/', + timeout=15, + data={ + 'username': username, + 'donotchache': int(time() * 1000), + }, + ).json() + except requests.exceptions.RequestException as e: + raise HTTPError(str(e)) + + return resp + + def _load_key(self): + if not self.key: + resp = self.get_rsa_key(self.username) + + nums = RSAPublicNumbers(intBase(resp['publickey_exp'], 16), + intBase(resp['publickey_mod'], 16), + ) + + self.key = backend.load_rsa_public_numbers(nums) + self.timestamp = resp['timestamp'] + + def login(self, captcha='', email_code='', twofactor_code='', language='english'): + if self.complete: + return self.session + + for domain in ['store.steampowered.com', 'help.steampowered.com', 'steamcommunity.com']: + self.session.cookies.set('forceMobile', '1', domain=domain, secure=False) + self.session.cookies.set('mobileClientVersion', '0 (2.1.3)', domain=domain, secure=False) + self.session.cookies.set('mobileClient', 'android', domain=domain, secure=False) + self.session.cookies.set('Steam_Language', 'english', domain=domain, secure=False) + self.session.cookies.set('dob', '', domain=domain, secure=False) + + self._load_key() + + 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), + } + data['oauth_client_id'] = 'DE45CD61' + data['oauth_scope'] = 'read_profile write_profile read_client write_client' + data['loginfriendlyname'] = '#login_emailauth_friendlyname_mobile' + + try: + resp = self.session.post('https://steamcommunity.com/mobilelogin/dologin/', data=data, timeout=15).json() + except requests.exceptions.RequestException as e: + raise HTTPError(str(e)) + + self.captcha_gid = -1 + + if resp['success'] and resp['login_complete']: + self.complete = True + self.password = None + return resp + else: + if resp.get('captcha_needed', False): + self.captcha_gid = resp['captcha_gid'] + + raise CaptchaRequired(resp['message']) + elif resp.get('emailauth_needed', False): + self.steamid = SteamID(resp['emailsteamid']) + raise EmailCodeRequired(resp['message']) + elif resp.get('requires_twofactor', False): + raise TwoFactorCodeRequired(resp['message']) + else: + raise LoginIncorrect(resp['message']) + + return None + +class MobileWebAuthException(Exception): + pass + +class HTTPError(MobileWebAuthException): + pass + +class LoginIncorrect(MobileWebAuthException): + pass + +class CaptchaRequired(MobileWebAuthException): + pass + +class EmailCodeRequired(MobileWebAuthException): + pass + +class TwoFactorCodeRequired(MobileWebAuthException): + pass From 8db821491411e3415be0539d5810d901ce24afdf Mon Sep 17 00:00:00 2001 From: Philipp Date: Wed, 1 Jun 2016 12:45:04 +0200 Subject: [PATCH 2/5] Create twofactorservice.py --- steam/twofactorservice.py | 1 + 1 file changed, 1 insertion(+) create mode 100644 steam/twofactorservice.py diff --git a/steam/twofactorservice.py b/steam/twofactorservice.py new file mode 100644 index 0000000..4d42a89 --- /dev/null +++ b/steam/twofactorservice.py @@ -0,0 +1 @@ +from guard import * From b2f619f824888b90fae97531ec00f8f8d1e613e0 Mon Sep 17 00:00:00 2001 From: Philipp Date: Wed, 1 Jun 2016 13:03:29 +0200 Subject: [PATCH 3/5] Update mobileauth.py --- steam/mobileauth.py | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/steam/mobileauth.py b/steam/mobileauth.py index 6ef7582..e86693b 100644 --- a/steam/mobileauth.py +++ b/steam/mobileauth.py @@ -59,6 +59,22 @@ class MobileAuth(object): self.key = backend.load_rsa_public_numbers(nums) self.timestamp = resp['timestamp'] + + def request(self, uri, data): + if not self.complete: + return None + + headers = { + 'X-Requested-With': 'com.valvesoftware.android.steam.community', + 'User-agent': 'Mozilla/5.0 (Linux; U; Android 4.1.1; en-us; Google Nexus 4 - 4.1.1 - API 16 - 768x1280 Build/JRO03S) AppleWebKit/534.30 (KHTML, like Gecko) Version/4.0 Mobile Safari/534.30' + } + + try: + response = self.session.post(uri, data=data, headers=headers) + except requests.exceptions.RequestException as e: + raise HTTPError(str(e)) + else: + return response def login(self, captcha='', email_code='', twofactor_code='', language='english'): if self.complete: From bc4532fb3db39ab75a0f0266e7e0911bdc2c337e Mon Sep 17 00:00:00 2001 From: Philipp Date: Wed, 1 Jun 2016 13:30:23 +0200 Subject: [PATCH 4/5] Update mobileauth.py --- steam/mobileauth.py | 24 +++++++++++++++++++++++- 1 file changed, 23 insertions(+), 1 deletion(-) diff --git a/steam/mobileauth.py b/steam/mobileauth.py index e86693b..8ac2b0d 100644 --- a/steam/mobileauth.py +++ b/steam/mobileauth.py @@ -23,7 +23,7 @@ class MobileAuth(object): session = None #: :class:`requests.Session` (with auth cookies after auth is complete) captcha_gid = -1 steamid = None #: :class:`steam.steamid.SteamID` (after auth is complete) - + oauth = {} def __init__(self, username, password): self.__dict__.update(locals()) self.session = make_requests_session() @@ -76,6 +76,19 @@ class MobileAuth(object): else: return response + def refreshSession(self, oauth_token=None): + oauth_token = oauth_token or self.oauth['oauth_token'] + response = self.request('https://api.steampowered.com/IMobileAuthService/GetWGToken/v0001', {'access_token': oauth_token}) + try: + data = json.loads(response) + except Exception, e: + raise RefreshSessionFailed(str(e)) + else: + self.oauth['wgtoken'] = data['response']['token'] + self.oauth['wgtoken_secure'] = data['response']['token_secure'] + self.session.cookies.set('steamLogin', '%s||%s' % (self.steamid, sself.oauth['wgtoken']), domain=domain, secure=False) + self.session.cookies.set('steamLoginSecure', '%s||%s' % (self.steamid, self.oauth['wgtoken_secure']), domain=domain, secure=True) + def login(self, captcha='', email_code='', twofactor_code='', language='english'): if self.complete: return self.session @@ -116,6 +129,12 @@ class MobileAuth(object): if resp['success'] and resp['login_complete']: self.complete = True self.password = None + self.steamid = SteamID(resp['oauth']['steamid']) + self.oauth = resp['oauth'] + for domain in ['store.steampowered.com', 'help.steampowered.com', 'steamcommunity.com']: + self.session.cookies.set('steamLogin', '%s||%s' % (self.steamid, resp['oauth']['wgtoken']), domain=domain, secure=False) + self.session.cookies.set('steamLoginSecure', '%s||%s' % (self.steamid, data['oauth']['wgtoken_secure']), domain=domain, secure=True) + return resp else: if resp.get('captcha_needed', False): @@ -149,3 +168,6 @@ class EmailCodeRequired(MobileWebAuthException): class TwoFactorCodeRequired(MobileWebAuthException): pass + +class RefreshSessionFailed(MobileWebAuthException): + pass From 3396d86aedf366c6472131beceb5363b06c055eb Mon Sep 17 00:00:00 2001 From: Philipp Date: Wed, 1 Jun 2016 13:55:51 +0200 Subject: [PATCH 5/5] Update and rename twofactorservice.py to authenticator.py --- steam/authenticator.py | 86 +++++++++++++++++++++++++++++++++++++++ steam/twofactorservice.py | 1 - 2 files changed, 86 insertions(+), 1 deletion(-) create mode 100644 steam/authenticator.py delete mode 100644 steam/twofactorservice.py diff --git a/steam/authenticator.py b/steam/authenticator.py new file mode 100644 index 0000000..13f2e86 --- /dev/null +++ b/steam/authenticator.py @@ -0,0 +1,86 @@ +import json + +from guard import * +from mobileauth import MobileAuth + +class MobileAuthenticator: + def __init__(self, username, password, authenticatorCredentials=False): + self.username = username + self.password = password + self.ready = None + self.mobile = MobileAuth(username, password) + self.credentials = authenticatorCredentials or { } + + def login(self): + if self.ready != None: + return False + + if 'secret' in self.credentials.keys(): + code = generate_twofactor_code(self.credentials.get('secret')) + self.mobile.login(twofactor_code=code) + return True + else: + try: + self.mobile.login() + except EmailCodeRequired: + raise AuthenticatorAlreadyActive('Two factor authentication already active') + except TwoFactorCodeRequired + raise AuthenticatorAlreadyActive('Two factor authentication already active') + else: + self.ready = False + return True + + def addAuthenticator(self): + if self.ready != False: + return None + + + data = { + 'steamid': self.mobile.steamid, + 'sms_phone_id': 1, + 'access_token': self.mobile.oauth['oauth_token'], + 'authenticator_time': get_time_offset(), + 'authenticator_type': 1, + 'device_identifier': generate_device_id(self.mobile.steamid) + } + + response = self.mobile.request('https://api.steampowered.com/ITwoFactorService/AddAuthenticator/v1/', data) + if response.status_code == 200: + responseData = json.loads(response.text) + self.credentials = responseData['response'] + self.credentials['secret'] = self.credentials['uri'].split('?secret=')[1].split('&issuer')[0] + return True + else: + return [False, responseData] + + def finalizeAuthenticator(self, smsCode=None, tries=1): + if not smsCode or self.ready != False: + return None + + timestamp = get_time_offset() + + data = { + 'steamid': self.mobile.steamid, + 'access_token': self.mobile.oauth['oauth_token'], + 'authenticator_time': timestamp, + 'authenticator_code': generate_twofactor_code_for_time(self.credentials['secret'], timestamp), + 'activation_code': smsCode + } + response = self.mobile.request('https://api.steampowered.com/ITwoFactorService/FinalizeAddAuthenticator/v1/', data) + if response.status_code == 200: + responseData = json.loads(response.text) + if responseData['success']: + return True + else: + if responseData['want_more'] and tries < 30: + return self.finalizeAuthenticator(smsCode, tries) + else: + return False + else: + return False + +class MobileAuthenticatorException(Exception): + pass + +class AuthenticatorAlreadyActive(MobileAuthenticatorException) + pass diff --git a/steam/twofactorservice.py b/steam/twofactorservice.py deleted file mode 100644 index 4d42a89..0000000 --- a/steam/twofactorservice.py +++ /dev/null @@ -1 +0,0 @@ -from guard import *