jessecooper / pyetrade

Python E-Trade API Wrapper
GNU General Public License v3.0
205 stars 96 forks source link

User Login via Selenium #46

Closed PaulNobrega closed 3 years ago

PaulNobrega commented 3 years ago

Good library. I would suggest a modification to your ETradeOAuth class to support automated login. Below is a working test script to benefit others if you decided to not add the feature.

from rauth import OAuth1Service
import undetected_chromedriver.v2 as uc
from selenium.webdriver.common.keys import Keys
from selenium.webdriver.common.action_chains import ActionChains
import time
import xmltodict
import json

class AutomatedLogin():
    def __init__(self, consumer_key, consumer_secret, web_username, web_password):
        self.consumer_key = consumer_key
        self.consumer_secret = consumer_secret
        self.web_username = web_username
        self.web_password = web_password
        self.service = OAuth1Service(
                  name='etrade',
                  consumer_key=consumer_key,
                  consumer_secret=consumer_secret,
                  request_token_url='https://apisb.etrade.com/oauth/request_token',
                  access_token_url='https://apisb.etrade.com/oauth/access_token',
                  authorize_url='https://us.etrade.com/e/t/etws/authorize?key={}&token={}',
                  base_url='https://apisb.etrade.com')
        self.oauth_token, self.oauth_token_secret = self.service.get_request_token(params={'oauth_callback': 'oob', 'format': 'json'})
        self.auth_url = self.service.authorize_url.format(consumer_key, self.oauth_token)
        self.verifier = self.__get_verifier(headless=True, action_delay_sec=2)
        if self.verifier:
            self.session = self.service.get_auth_session(self.oauth_token, self.oauth_token_secret, params={'oauth_verifier': self.verifier})

    def __get_verifier(self, headless=True, action_delay_sec=2):
        if headless:
            options = uc.ChromeOptions()
            options.headless = True
            options.add_argument('--headless')
            driver = uc.Chrome(options=options)
            print(f'Headless web login with action_delay set to: {action_delay_sec} seconds')
        else:
            driver = uc.Chrome()
        web_action = ActionChains(driver)

        try:
            with driver:
                driver.get(self.auth_url)
                # log in
                username = driver.find_element_by_name("USER")
                password = driver.find_element_by_name("PASSWORD")
                username.send_keys(web_username)
                password.send_keys(web_password)
                driver.find_element_by_id("logon_button").click()
                time.sleep(action_delay_sec)
                web_action.send_keys(Keys.TAB).send_keys(Keys.RETURN).perform()
                return driver.find_element_by_tag_name("input").get_attribute("value")
        except Exception as e:
            print(str(e))
            return

# User Parameters
consumer_key = 'dxxx...xxx007'
consumer_secret = 'e7xxx...xxxa4e8'
web_username = 'user_name'
web_password = 'password'

# Automated Login
login = AutomatedLogin(consumer_key, consumer_secret, web_username, web_password)

# Test Session
url = 'https://apisb.etrade.com/v1/accounts/list'
resp = login.session.get(url, params={'format': 'json'})
resp_json = json.dumps(xmltodict.parse(resp.text))
print(resp_json)
1rocketdude commented 3 years ago

we discussed this idea a couple of years back and decided that having automated logins was a security risk with large negative ramifications. That said, I consider having the ability to use autologin a useful tool in someone's toolbox. Having the web_username and web_password in plaintext anywhere is asking for trouble, especially as much of this codebase ends up in revision control system somewhere.

Potentially, is this an external library that could be packaged into a module which depends on pyetrade? Then, a user would have to go to the extra work, and acknowledge the security risks without burdening the pyetrade modules?

thanks for providing the code - in the mean time, users could use this approach themselves.

On Mar 14, 2021, at 9:38 AM, PaulNobrega @.***> wrote:

Good library. I would suggest a modification to your ETradeOAuth class to support automated login. Below is a working test script to benefit others if you decided to not add the feature.

from rauth import OAuth1Service import undetected_chromedriver.v2 as uc from selenium.webdriver.common.keys import Keys from selenium.webdriver.common.action_chains import ActionChains import time import xmltodict import json

class AutomatedLogin(): def init(self, consumer_key, consumer_secret, web_username, web_password): self.consumer_key = consumer_key self.consumer_secret = consumer_secret self.web_username = web_username self.web_password = web_password self.service = OAuth1Service( name='etrade', consumer_key=consumer_key, consumer_secret=consumer_secret, request_token_url='https://apisb.etrade.com/oauth/request_token', access_token_url='https://apisb.etrade.com/oauth/access_token', authorize_url='https://us.etrade.com/e/t/etws/authorize?key={}&token={}', base_url='https://apisb.etrade.com') self.oauth_token, self.oauth_token_secret = self.service.get_request_token(params={'oauth_callback': 'oob', 'format': 'json'}) self.auth_url = self.service.authorize_url.format(consumer_key, self.oauth_token) self.verifier = self.__get_verifier(headless=True, action_delay_sec=2) if self.verifier: self.session = self.service.get_auth_session(self.oauth_token, self.oauth_token_secret, params={'oauth_verifier': self.verifier})

def __get_verifier(self, headless=True, action_delay_sec=2):
    if headless:
        options = uc.ChromeOptions()
        options.headless = True
        options.add_argument('--headless')
        driver = uc.Chrome(options=options)
        print(f'Headless web login with action_delay set to: {action_delay_sec} seconds')
    else:
        driver = uc.Chrome()
    web_action = ActionChains(driver)

    try:
        with driver:
            driver.get(self.auth_url)
            # log in
            username = driver.find_element_by_name("USER")
            password = driver.find_element_by_name("PASSWORD")
            username.send_keys(web_username)
            password.send_keys(web_password)
            driver.find_element_by_id("logon_button").click()
            time.sleep(action_delay_sec)
            web_action.send_keys(Keys.TAB).send_keys(Keys.RETURN).perform()
            return driver.find_element_by_tag_name("input").get_attribute("value")
    except Exception as e:
        print(str(e))
        return

User Parameters

consumer_key = 'dxxx...xxx007' consumer_secret = 'e7xxx...xxxa4e8' web_username = 'user_name' web_password = 'password'

Automated Login

login = AutomatedLogin(consumer_key, consumer_secret, web_username, web_password)

Test Session

url = 'https://apisb.etrade.com/v1/accounts/list' resp = login.session.get(url, params={'format': 'json'}) resp_json = json.dumps(xmltodict.parse(resp.text)) print(resp_json) — You are receiving this because you are subscribed to this thread. Reply to this email directly, view it on GitHub https://github.com/jessecooper/pyetrade/issues/46, or unsubscribe https://github.com/notifications/unsubscribe-auth/ABE3CHFI6BR4ZUIVEDI2PWDTDTJ7JANCNFSM4ZFCY2WA.

jessecooper commented 3 years ago

Thanks for the example. As @1rocketdude said this has been talked about in the past and is something the application leveraging pyetrade should implement. There has been a suggestion to create a discussion board around pyetrade for topics like this. I am going to look into this and any suggestions around this are appreciated.