def do_direct_authorization(self, session): """ Direct Authorization, more info: https://vk.com/dev/auth_direct """ logger.info('Doing direct authorization, app_id=%s', self.app_id) auth_data = { 'client_id': self.app_id, 'client_secret': self._client_secret, 'username': self._login, 'password': self._password, 'grant_type': 'password', '2fa_supported': self._two_fa_supported, 'scope': self.scope, 'v': self.api_version } response = session.post(url=self.DIRECT_AUTHORIZE_URL, data=stringify_values(auth_data)) try: response_json = response.json() except ValueError: # not JSON in response error_message = 'OAuth2 grant access error' logger.error(response.text) raise VkAuthError(error_message) else: if 'access_token' in response_json: return response_json if response_json['error'] == 'need_validation': return self.direct_auth_require_2fa(session, auth_data) elif response_json['error'] == 'need_captcha': return self.direct_auth_require_captcha( session, response_json, auth_data) else: error_message = 'VK error: [{}] {}'.format( response_json['error'], response_json['error_description']) raise VkAuthError(error_message)
def require_auth_captcha(self, query_params, form_text, login_form_data, session): """Resolve auth captcha case :param query_params: dict: response query params, for example: {'s': '0', 'email': 'my@email', 'dif': '1', 'role': 'fast', 'sid': '1'} :param form_text: str: raw form html data :param login_form_data: dict :param session: requests.Session :return: :raise VkAuthError: """ logger.info('Captcha is needed') action_url = parse_form_action_url(form_text) logger.debug('form_url %s', action_url) if not action_url: raise VkAuthError('Cannot find form action url') captcha_url = '%s?s=%s&sid=%s' % (self.CAPTCHA_URI, query_params['s'], query_params['sid']) logger.info('Captcha url %s', captcha_url) login_form_data['captcha_sid'] = query_params['sid'] login_form_data['captcha_key'] = self.get_captcha_key(captcha_url) response = session.post(action_url, login_form_data) return response
def _get_access_token(self): """Get access token using app_id, login and password OR service token (service token docs: https://vk.com/dev/service_token """ if self._service_token: logger.info('Use service token: %s', 5 * '*' + self._service_token[50:]) return self._service_token if not all([self.app_id, self._login, self._password]): raise ValueError( 'app_id=%s, login=%s password=%s (masked) must be given' % (self.app_id, self._login, '*' * len(self._password) if self._password else 'None')) logger.info("Getting access token for user '%s'" % self._login) with self.http_session as s: self.do_login(http_session=s) url_query_params = self.do_oauth2_authorization(session=s) logger.debug('url_query_params: %s', url_query_params) if 'access_token' in url_query_params: logger.info('Access token has been gotten') return url_query_params['access_token'] else: raise VkAuthError('OAuth2 authorization error. Url params: %s' % url_query_params)
def require_auth_captcha(self, response, query_params, login_form_data, http_session): """Resolve auth captcha case :param response: http response :param query_params: dict: response query params, for example: {'s': '0', 'email': 'my@email', 'dif': '1', 'role': 'fast', 'sid': '1'} :param login_form_data: dict :param http_session: requests.Session :return: :raise VkAuthError: """ logger.info('Captcha is needed. Query params: %s', query_params) form_text = response.text action_url = parse_form_action_url(form_text) logger.debug('form action url: %s', action_url) if not action_url: raise VkAuthError('Cannot find form action url') captcha_sid, captcha_url = parse_captcha_html( html=response.text, response_url=response.url) logger.info('Captcha url %s', captcha_url) login_form_data['captcha_sid'] = captcha_sid login_form_data['captcha_key'] = self.get_captcha_key(captcha_url) response = http_session.post(action_url, login_form_data) return response
def get_access_token(self): """ Get access token using app id and user login and password if no stored token provided else use stored token as access token """ if not all([self.app_id, self._login, self._password ]) and not self._stored_token: raise ValueError( 'app_id=%s, login=%s password=%s (masked) must be given' % (self.app_id, self._login, bool(self._password))) logger.info("Getting access token for user '%s'" % self._login) with VerboseHTTPSession() as s: if self._stored_token: url_query_params = {'access_token': self._stored_token} self._stored_token = None else: self.do_login(session=s) url_query_params = self.do_oauth2_authorization(session=s) logger.debug('url_query_params: %s', url_query_params) if 'access_token' in url_query_params: logger.info('Done') return url_query_params['access_token'] else: raise VkAuthError('OAuth2 authorization error')
def get_2fa_code(self): if self.interactive: auth_check_code = raw_input('Auth check code: ') return auth_check_code.strip() else: raise VkAuthError( 'Auth check code is needed (SMS, Google Authenticator or ' 'one-time password). ' 'Use interactive mode to enter the code manually')
def do_login(self, session): """Do vk login :param session: vk_requests.utils.LoggingSession: http session """ response = session.get(self.LOGIN_URL) login_form_action = parse_form_action_url(response.text) if not login_form_action: raise VkAuthError('VK changed login flow') login_form_data = {'email': self._login, 'pass': self._password} response = session.post(login_form_action, login_form_data) logger.debug('Cookies: %s', session.cookies) response_url_query = parse_url_query_params(response.url, fragment=False) act = response_url_query.get('act') logger.debug('response_url_query: %s', response_url_query) # Check response url query params firstly if 'sid' in response_url_query: self.require_auth_captcha(query_params=response_url_query, form_text=response.text, login_form_data=login_form_data, session=session) elif act == 'authcheck': self.require_sms_code(html=response.text, session=session) elif act == 'security_check': self.require_phone_number(html=response.text, session=session) session_cookies = ('remixsid' in session.cookies, 'remixsid6' in session.cookies) if any(session_cookies): # Session is already established logger.info('Session is already established') return None else: message = 'Authorization error (incorrect password)' logger.error(message) raise VkAuthError(message)
def get_captcha_key(self, captcha_image_url): """Read CAPTCHA key from user input""" if self.interactive: print( 'Open CAPTCHA image url in your browser and enter it below: ', captcha_image_url) captcha_key = raw_input('Enter CAPTCHA key: ') return captcha_key else: raise VkAuthError( 'Captcha is required. Use interactive mode to enter it ' 'manually')
def do_login(self, http_session): """Do vk login :param http_session: vk_requests.utils.VerboseHTTPSession: http session """ response = http_session.get(self.LOGIN_URL) action_url = parse_form_action_url(response.text) # Stop login it action url is not found if not action_url: logger.debug(response.text) raise VkParseError("Can't parse form action url") login_form_data = {'email': self._login, 'pass': self._password} login_response = http_session.post(action_url, login_form_data) logger.debug('Cookies: %s', http_session.cookies) response_url_query = parse_url_query_params(login_response.url, fragment=False) logger.debug('response_url_query: %s', response_url_query) act = response_url_query.get('act') # Check response url query params firstly if 'sid' in response_url_query: self.require_auth_captcha(query_params=response_url_query, form_text=login_response.text, login_form_data=login_form_data, http_session=http_session) elif act == 'authcheck': self.require_2fa(html=login_response.text, http_session=http_session) elif act == 'security_check': self.require_phone_number(html=login_response.text, session=http_session) session_cookies = ('remixsid' in http_session.cookies, 'remixsid6' in http_session.cookies) if any(session_cookies): logger.info('VK session is established') return True else: message = 'Authorization error: incorrect password or ' \ 'authentication code' logger.error(message) raise VkAuthError(message)
def direct_auth_require_captcha(self, session, response, auth_data): logger.info('Captcha is needed. Response: %s', response) captcha_url = response['captcha_img'] logger.info('Captcha url %s', captcha_url) auth_data['captcha_sid'] = response['captcha_sid'] auth_data['captcha_key'] = self.get_captcha_key(captcha_url) response = session.post(url=self.DIRECT_AUTHORIZE_URL, data=stringify_values(auth_data)) try: response_json = response.json() except ValueError: # not JSON in response error_message = 'OAuth2 grant access error' logger.error(response.text) raise VkAuthError(error_message) return response_json
def direct_auth_require_2fa(self, session, auth_data): if self._two_fa_force_sms: auth_data['force_sms'] = self._two_fa_force_sms session.post(url=self.DIRECT_AUTHORIZE_URL, data=stringify_values(auth_data)) logger.info( 'User enabled 2 factors authentication. Auth check code is needed ' '(SMS, Google Authenticator or one-time password generated by vk)') auth_data['code'] = self.get_2fa_code() response = session.post(url=self.DIRECT_AUTHORIZE_URL, data=stringify_values(auth_data)) try: response_json = response.json() except ValueError: # not JSON in response error_message = 'OAuth2 grant access error' logger.error(response.text) raise VkAuthError(error_message) return response_json
def do_implicit_flow_authorization(self, session): """ Standard OAuth2 authorization method. It's used for getting access token More info: https://vk.com/dev/implicit_flow_user """ logger.info('Doing implicit flow authorization, app_id=%s', self.app_id) auth_data = { 'client_id': self.app_id, 'display': 'mobile', 'response_type': 'token', 'scope': self.scope, 'redirect_uri': 'https://oauth.vk.com/blank.html', 'v': self.api_version } response = session.post(url=self.AUTHORIZE_URL, data=stringify_values(auth_data)) url_query_params = parse_url_query_params(response.url) if 'expires_in' in url_query_params: logger.info('Token will be expired in %s sec.' % url_query_params['expires_in']) if 'access_token' in url_query_params: return url_query_params # Permissions are needed logger.info('Getting permissions') action_url = parse_form_action_url(response.text) logger.debug('Response form action: %s', action_url) if action_url: response = session.get(action_url) url_query_params = parse_url_query_params(response.url) return url_query_params try: response_json = response.json() except ValueError: # not JSON in response error_message = 'OAuth2 grant access error' logger.error(response.text) else: error_message = 'VK error: [{}] {}'.format( response_json['error'], response_json['error_description']) logger.error('Permissions obtained') raise VkAuthError(error_message)
def do_oauth2_authorization(self, session): """ OAuth2. More info: https://vk.com/dev/auth_mobile """ logger.info('Doing oauth2') auth_data = { 'client_id': self.app_id, 'display': 'mobile', 'response_type': 'token', 'scope': self.scope, 'v': self.api_version } response = session.post(url=self.AUTHORIZE_URL, data=stringify_values(auth_data)) url_query_params = parse_url_query_params(response.url) if 'expires_in' in url_query_params: logger.info('Token will be expired in %s sec.' % url_query_params['expires_in']) if 'access_token' in url_query_params: return url_query_params # Permissions is needed logger.info('Getting permissions') form_action = parse_form_action_url(response.text) logger.debug('Response form action: %s', form_action) if form_action: response = session.get(form_action) url_query_params = parse_url_query_params(response.url) return url_query_params try: response_json = response.json() except ValueError: # not JSON in response error_message = 'OAuth2 grant access error' logger.error(response.text) else: error_message = 'VK error: [{}] {}'.format( response_json['error'], response_json['error_description']) logger.error('Permissions obtained') raise VkAuthError(error_message)
def require_phone_number(self, html, session): logger.info( 'Auth requires phone number. You do login from unusual place') # Raises VkPageWarningsError in case of warnings # NOTE: we check only 'security_check' case on warnings for now # in future it might be extended for other cases as well check_html_warnings(html=html) # Determine form action url action_url = parse_form_action_url(html) # Get masked phone from html to make things more clear phone_prefix, phone_suffix = parse_masked_phone_number(html) if self._phone_number: code = self._phone_number[len(phone_prefix):-len(phone_suffix)] else: if self.interactive: prompt = 'Enter missing digits of your phone number (%s****%s): '\ % (phone_prefix, phone_suffix) code = raw_input(prompt) else: raise VkAuthError( 'Phone number is required. Create an API instance using ' 'phone_number parameter or use interactive mode') params = parse_url_query_params(action_url, fragment=False) auth_data = { 'code': code, 'act': 'security_check', 'hash': params['hash'] } response = session.post(url=self.LOGIN_URL + action_url, data=auth_data) logger.debug('require_phone_number resp: %s', response.text)
def get_2fa_code(self): raise VkAuthError('Auth check code is needed ' '(SMS, Google Authenticator or one-time password)')
def get_sms_code(self): raise VkAuthError('Auth check code is needed')