hydrus/include/ClientNetworkingLogin.py

851 lines
27 KiB
Python
Raw Normal View History

2017-09-13 20:50:41 +00:00
import ClientConstants as CC
2017-10-18 19:41:25 +00:00
import ClientDefaults
import ClientDownloading
import ClientNetworking
import ClientNetworkingDomain
2017-09-13 20:50:41 +00:00
import HydrusConstants as HC
import HydrusGlobals as HG
import HydrusData
import HydrusExceptions
2017-10-18 19:41:25 +00:00
import HydrusSerialisable
2017-11-01 20:37:39 +00:00
import os
2017-10-18 19:41:25 +00:00
import json
import requests
import threading
import time
import urllib
2017-09-13 20:50:41 +00:00
VALIDITY_VALID = 0
VALIDITY_UNTESTED = 1
VALIDITY_INVALID = 2
# make this serialisable
class LoginCredentials( object ):
def __init__( self ):
self._credentials = {} # user-facing name (unique) : string
2017-11-01 20:37:39 +00:00
self._validity = VALIDITY_UNTESTED
2017-09-13 20:50:41 +00:00
2017-11-01 20:37:39 +00:00
def GetCredential( self, name ):
return self._credentials[ name ]
2017-09-13 20:50:41 +00:00
2017-10-18 19:41:25 +00:00
class NetworkLoginManager( HydrusSerialisable.SerialisableBase ):
SERIALISABLE_TYPE = HydrusSerialisable.SERIALISABLE_TYPE_NETWORK_LOGIN_MANAGER
2017-11-29 21:48:23 +00:00
SERIALISABLE_NAME = 'Login Manager'
2017-10-18 19:41:25 +00:00
SERIALISABLE_VERSION = 1
2017-11-15 22:35:49 +00:00
SESSION_TIMEOUT = 60 * 45
2017-10-18 19:41:25 +00:00
def __init__( self ):
HydrusSerialisable.SerialisableBase.__init__( self )
2017-11-01 20:37:39 +00:00
# needs _dirty and setdirty and be on that serialisation check and so on
2017-10-18 19:41:25 +00:00
self.engine = None
self._lock = threading.Lock()
2017-11-01 20:37:39 +00:00
self._domains_to_login_scripts_and_credentials = {}
2017-10-18 19:41:25 +00:00
2017-10-25 21:45:15 +00:00
self._hydrus_login_script = LoginScriptHydrus()
# as a login script can apply to multiple places, the actual credentials should be a separate object
# this makes script import/export privacy a little easier!
# these credentials should have validity tracking too
# the script failing vs the credentials failing are different things, wew
# track recent error at the script level? some sensible way of dealing with 'domain is currently down, so try again later'
# maybe this should be at the domain manager's validity level, yeah.
2017-10-18 19:41:25 +00:00
# so, we fetch all the logins, ask them for the network contexts so we can set up the dict
# variables from old object here
self._error_names = set()
2017-10-25 21:45:15 +00:00
# should this be handled in the session manager? yeah, prob
2017-10-18 19:41:25 +00:00
self._network_contexts_to_session_timeouts = {}
2017-11-01 20:37:39 +00:00
def _GetLoginNetworkContext( self, network_context ):
2017-10-18 19:41:25 +00:00
2017-11-01 20:37:39 +00:00
nc_domain = network_context.context_data
domains = ClientNetworkingDomain.ConvertDomainIntoAllApplicableDomains( nc_domain )
for domain in domains:
2017-10-25 21:45:15 +00:00
2017-11-01 20:37:39 +00:00
if domain in self._domains_to_login_scripts_and_credentials:
2017-10-25 21:45:15 +00:00
2017-11-01 20:37:39 +00:00
return domain
2017-10-25 21:45:15 +00:00
2017-10-18 19:41:25 +00:00
2017-10-25 21:45:15 +00:00
return None
2017-10-18 19:41:25 +00:00
2017-10-25 21:45:15 +00:00
def _GetSerialisableInfo( self ):
2017-10-18 19:41:25 +00:00
2017-10-25 21:45:15 +00:00
return {}
2017-10-18 19:41:25 +00:00
2017-10-25 21:45:15 +00:00
def _InitialiseFromSerialisableInfo( self, serialisable_info ):
2017-10-18 19:41:25 +00:00
2017-10-25 21:45:15 +00:00
self._network_contexts_to_logins = {}
2017-10-18 19:41:25 +00:00
2017-10-25 21:45:15 +00:00
def CanLogin( self, network_context ):
2017-10-18 19:41:25 +00:00
with self._lock:
if network_context.context_type == CC.NETWORK_CONTEXT_DOMAIN:
2017-11-01 20:37:39 +00:00
login_network_context = self._GetLoginNetworkContext( network_context )
if login_network_context is None:
return False
( login_script, credentials ) = self._domains_to_login_scripts_and_credentials[ login_network_context.context_data ]
2017-10-18 19:41:25 +00:00
2017-11-01 20:37:39 +00:00
( result, reason ) = login_script.CanLogin( credentials )
return result
2017-10-18 19:41:25 +00:00
elif network_context.context_type == CC.NETWORK_CONTEXT_HYDRUS:
service_key = network_context.context_data
2017-10-25 21:45:15 +00:00
services_manager = self.engine.controller.services_manager
if not services_manager.ServiceExists( service_key ):
return False
service = services_manager.GetService( service_key )
return service.IsFunctional( ignore_account = True )
2017-10-18 19:41:25 +00:00
return False
2017-10-25 21:45:15 +00:00
def GenerateLoginProcess( self, network_context ):
with self._lock:
2017-11-01 20:37:39 +00:00
if network_context.context_type == CC.NETWORK_CONTEXT_DOMAIN:
2017-10-25 21:45:15 +00:00
2017-11-01 20:37:39 +00:00
login_network_context = self._GetLoginNetworkContext( network_context )
if login_network_context is None:
raise HydrusExceptions.DataMissing()
( login_script, credentials ) = self._domains_to_login_scripts_and_credentials[ login_network_context.context_data ]
login_process = LoginProcessDomain( self.engine, login_network_context, login_script, credentials )
elif network_context.context_type == CC.NETWORK_CONTEXT_HYDRUS:
login_process = LoginProcessHydrus( self.engine, network_context, self._hydrus_login_script )
return login_process
2017-10-25 21:45:15 +00:00
def NeedsLogin( self, network_context ):
with self._lock:
2017-11-01 20:37:39 +00:00
if network_context.context_type == CC.NETWORK_CONTEXT_DOMAIN:
2017-10-25 21:45:15 +00:00
2017-11-01 20:37:39 +00:00
login_network_context = self._GetLoginNetworkContext( network_context )
if login_network_context is None:
return False
( login_script, credentials ) = self._domains_to_login_scripts_and_credentials[ login_network_context.context_data ]
return login_script.IsLoggedIn( self.engine, login_network_context )
elif network_context.context_type == CC.NETWORK_CONTEXT_HYDRUS:
return not self._hydrus_login_script.IsLoggedIn( self.engine, network_context )
2017-10-25 21:45:15 +00:00
2017-10-18 19:41:25 +00:00
# these methods are from the old object:
def _GetCookiesDict( self, network_context ):
2017-10-25 21:45:15 +00:00
session = self.engine.session_manager.GetSession( network_context )
2017-10-18 19:41:25 +00:00
cookies = session.cookies
cookies.clear_expired_cookies()
domains = cookies.list_domains()
for domain in domains:
if domain.endswith( network_context.context_data ):
return cookies.get_dict( domain )
return {}
def _IsLoggedIn( self, network_context, required_cookies ):
cookie_dict = self._GetCookiesDict( network_context )
for name in required_cookies:
if name not in cookie_dict:
return False
return True
def EnsureLoggedIn( self, name ):
with self._lock:
if name in self._error_names:
raise Exception( name + ' could not establish a session! This ugly error is temporary due to the network engine rewrite. Please restart the client to reattempt this network context.' )
if name == 'hentai foundry':
network_context = ClientNetworking.NetworkContext( CC.NETWORK_CONTEXT_DOMAIN, 'hentai-foundry.com' )
required_cookies = [ 'PHPSESSID', 'YII_CSRF_TOKEN' ]
elif name == 'pixiv':
network_context = ClientNetworking.NetworkContext( CC.NETWORK_CONTEXT_DOMAIN, 'pixiv.net' )
required_cookies = [ 'PHPSESSID' ]
if self._IsLoggedIn( network_context, required_cookies ):
return
try:
if name == 'hentai foundry':
self.LoginHF( network_context )
elif name == 'pixiv':
result = self.engine.controller.Read( 'serialisable_simple', 'pixiv_account' )
if result is None:
raise HydrusExceptions.DataMissing( 'You need to set up your pixiv credentials in services->manage pixiv account.' )
( pixiv_id, password ) = result
self.LoginPixiv( network_context, pixiv_id, password )
if not self._IsLoggedIn( network_context, required_cookies ):
raise Exception( name + ' login did not work correctly!' )
HydrusData.Print( 'Successfully logged into ' + name + '.' )
except:
self._error_names.add( name )
raise
def LoginHF( self, network_context ):
2017-10-25 21:45:15 +00:00
session = self.engine.session_manager.GetSession( network_context )
2017-10-18 19:41:25 +00:00
2017-12-06 22:06:56 +00:00
num_attempts = 0
while True:
try:
response = session.get( 'https://www.hentai-foundry.com/', timeout = 10 )
break
except requests.exceptions.ConnectionError, requests.exceptions.ConnectTimeout:
if num_attempts < 3:
num_attempts += 1
time.sleep( 3 )
else:
raise HydrusExceptions.ConnectionException( 'Could not connect to HF to log in!' )
2017-10-18 19:41:25 +00:00
time.sleep( 1 )
response = session.get( 'https://www.hentai-foundry.com/?enterAgree=1' )
time.sleep( 1 )
cookie_dict = self._GetCookiesDict( network_context )
raw_csrf = cookie_dict[ 'YII_CSRF_TOKEN' ] # 19b05b536885ec60b8b37650a32f8deb11c08cd1s%3A40%3A%222917dcfbfbf2eda2c1fbe43f4d4c4ec4b6902b32%22%3B
processed_csrf = urllib.unquote( raw_csrf ) # 19b05b536885ec60b8b37650a32f8deb11c08cd1s:40:"2917dcfbfbf2eda2c1fbe43f4d4c4ec4b6902b32";
csrf_token = processed_csrf.split( '"' )[1] # the 2917... bit
hentai_foundry_form_info = ClientDefaults.GetDefaultHentaiFoundryInfo()
hentai_foundry_form_info[ 'YII_CSRF_TOKEN' ] = csrf_token
response = session.post( 'http://www.hentai-foundry.com/site/filters', data = hentai_foundry_form_info )
time.sleep( 1 )
# This updated login form is cobbled together from the example in PixivUtil2
# it is breddy shid because I'm not using mechanize or similar browser emulation (like requests's sessions) yet
# Pixiv 400s if cookies and referrers aren't passed correctly
# I am leaving this as a mess with the hope the eventual login engine will replace it
def LoginPixiv( self, network_context, pixiv_id, password ):
2017-10-25 21:45:15 +00:00
session = self.engine.session_manager.GetSession( network_context )
2017-10-18 19:41:25 +00:00
response = session.get( 'https://accounts.pixiv.net/login' )
soup = ClientDownloading.GetSoup( response.content )
# some whocking 20kb bit of json tucked inside a hidden form input wew lad
i = soup.find( 'input', id = 'init-config' )
raw_json = i['value']
j = json.loads( raw_json )
if 'pixivAccount.postKey' not in j:
raise HydrusExceptions.ForbiddenException( 'When trying to log into Pixiv, I could not find the POST key! This is a problem with hydrus\'s pixiv parsing, not your login! Please contact hydrus dev!' )
post_key = j[ 'pixivAccount.postKey' ]
form_fields = {}
form_fields[ 'pixiv_id' ] = pixiv_id
form_fields[ 'password' ] = password
form_fields[ 'captcha' ] = ''
form_fields[ 'g_recaptcha_response' ] = ''
form_fields[ 'return_to' ] = 'https://www.pixiv.net'
form_fields[ 'lang' ] = 'en'
form_fields[ 'post_key' ] = post_key
form_fields[ 'source' ] = 'pc'
headers = {}
headers[ 'referer' ] = "https://accounts.pixiv.net/login?lang=en^source=pc&view_type=page&ref=wwwtop_accounts_index"
headers[ 'origin' ] = "https://accounts.pixiv.net"
session.post( 'https://accounts.pixiv.net/api/login?lang=en', data = form_fields, headers = headers )
time.sleep( 1 )
def TestPixiv( self, pixiv_id, password ):
# this is just an ugly copy, but fuck it for the minute
# we'll figure out a proper testing engine later with the login engine and tie the manage gui into it as well
session = requests.Session()
response = session.get( 'https://accounts.pixiv.net/login' )
soup = ClientDownloading.GetSoup( response.content )
# some whocking 20kb bit of json tucked inside a hidden form input wew lad
i = soup.find( 'input', id = 'init-config' )
raw_json = i['value']
j = json.loads( raw_json )
if 'pixivAccount.postKey' not in j:
return ( False, 'When trying to log into Pixiv, I could not find the POST key! This is a problem with hydrus\'s pixiv parsing, not your login! Please contact hydrus dev!' )
post_key = j[ 'pixivAccount.postKey' ]
form_fields = {}
form_fields[ 'pixiv_id' ] = pixiv_id
form_fields[ 'password' ] = password
form_fields[ 'captcha' ] = ''
form_fields[ 'g_recaptcha_response' ] = ''
form_fields[ 'return_to' ] = 'https://www.pixiv.net'
form_fields[ 'lang' ] = 'en'
form_fields[ 'post_key' ] = post_key
form_fields[ 'source' ] = 'pc'
headers = {}
headers[ 'referer' ] = "https://accounts.pixiv.net/login?lang=en^source=pc&view_type=page&ref=wwwtop_accounts_index"
headers[ 'origin' ] = "https://accounts.pixiv.net"
r = session.post( 'https://accounts.pixiv.net/api/login?lang=en', data = form_fields, headers = headers )
if not r.ok:
HydrusData.ShowText( r.content )
return ( False, 'Login request failed! Info printed to log.' )
cookies = session.cookies
cookies.clear_expired_cookies()
domains = cookies.list_domains()
for domain in domains:
if domain.endswith( 'pixiv.net' ):
d = cookies.get_dict( domain )
if 'PHPSESSID' not in d:
HydrusData.ShowText( r.content )
return ( False, 'Pixiv login failed to establish session! Info printed to log.' )
return ( True, '' )
HydrusData.ShowText( r.content )
return ( False, 'Pixiv login failed to establish session! Info printed to log.' )
2017-10-25 21:45:15 +00:00
class LoginProcess( object ):
def __init__( self, engine, network_context, login_script ):
self.engine = engine
self.network_context = network_context
self.login_script = login_script
self._done = False
2017-11-01 20:37:39 +00:00
def _Start( self ):
raise NotImplementedError()
2017-10-25 21:45:15 +00:00
def IsDone( self ):
return self._done
def Start( self ):
try:
2017-11-01 20:37:39 +00:00
self._Start()
2017-10-25 21:45:15 +00:00
finally:
self._done = True
2017-11-01 20:37:39 +00:00
class LoginProcessDomain( LoginProcess ):
def __init__( self, engine, network_context, login_script, credentials ):
LoginProcess.__init__( self, engine, network_context, login_script )
self.credentials = credentials
def _Start( self ):
self.login_script.Start( self.engine, self.network_context, self.credentials )
class LoginProcessHydrus( LoginProcess ):
def _Start( self ):
self.login_script.Start( self.engine, self.network_context )
2017-10-25 21:45:15 +00:00
class LoginScriptHydrus( object ):
2017-11-01 20:37:39 +00:00
def _IsLoggedIn( self, session ):
2017-10-25 21:45:15 +00:00
cookies = session.cookies
cookies.clear_expired_cookies()
return 'session_key' in cookies
2017-11-01 20:37:39 +00:00
def IsLoggedIn( self, engine, network_context ):
2017-10-25 21:45:15 +00:00
2017-11-01 20:37:39 +00:00
session = engine.session_manager.GetSession( network_context )
return self._IsLoggedIn( session )
2017-10-25 21:45:15 +00:00
def Start( self, engine, network_context ):
service_key = network_context.context_data
service = engine.controller.services_manager.GetService( service_key )
base_url = service.GetBaseURL()
url = base_url + 'session_key'
access_key = service.GetCredentials().GetAccessKey()
network_job = ClientNetworking.NetworkJobHydrus( service_key, 'GET', url )
network_job.SetForLogin( True )
network_job.AddAdditionalHeader( 'Hydrus-Key', access_key.encode( 'hex' ) )
engine.AddJob( network_job )
try:
network_job.WaitUntilDone()
session = engine.session_manager.GetSession( network_context )
2017-11-01 20:37:39 +00:00
if self._IsLoggedIn( session ):
2017-10-25 21:45:15 +00:00
HydrusData.Print( 'Successfully logged into ' + service.GetName() + '.' )
else:
service.DelayFutureRequests( 'Could not log in for unknown reason.' )
except Exception as e:
e_string = str( e )
service.DelayFutureRequests( e_string )
2017-10-18 19:41:25 +00:00
HydrusSerialisable.SERIALISABLE_TYPES_TO_OBJECT_TYPES[ HydrusSerialisable.SERIALISABLE_TYPE_NETWORK_LOGIN_MANAGER ] = NetworkLoginManager
2017-09-13 20:50:41 +00:00
# make this serialisable
2017-11-01 20:37:39 +00:00
class LoginScriptDomain( object ):
2017-09-13 20:50:41 +00:00
def __init__( self ):
2017-11-01 20:37:39 +00:00
self._name = 'gelbooru v2.0 login script'
2017-09-13 20:50:41 +00:00
self._login_steps = []
self._validity = VALIDITY_UNTESTED
2017-10-25 21:45:15 +00:00
self._error_reason = ''
2017-11-01 20:37:39 +00:00
self._expected_cookies_for_login = [] # [ name, stringmatch ]
2017-10-25 21:45:15 +00:00
def _IsLoggedIn( self, network_context, session ):
2017-11-01 20:37:39 +00:00
# this is more complicated for sadpanda, right?
# I may need some way to have an override of some kind that is like 'this login script specifically logs in to one domain, although it applies to others'
# need to research sadpanda exact mechanism--is it IP based?
# this should also return ( result, reason ) for testing and other purposes
cookies = session.cookies
cookies.clear_expired_cookies()
search_domain = network_context.context_data
for ( name, string_match ) in self._expected_cookies_for_login:
try:
cookie_text = ClientNetworkingDomain.GetCookie( cookies, search_domain, name )
except HydrusExceptions.DataMissing as e:
return False
2017-11-22 21:03:07 +00:00
try:
string_match.Test( cookie_text )
except HydrusExceptions.StringMatchException:
2017-11-01 20:37:39 +00:00
return False
return True
def CanLogin( self, credentials ):
if self._validity == VALIDITY_INVALID:
return ( False, 'Script is not valid: ' + self._error_reason )
for step in self._login_steps:
try:
step.TestCredentials( credentials )
except HydrusExceptions.ValidationException as e:
return ( False, str( e ) )
return True
def GetExpectedCredentialDestinations( self, domain ):
# for step in steps, say where each named credential is going
# return a dict like:
# login.pixiv.net : username, password
# evilsite.bg.cx : username, password
# This'll be presented on the cred entering form so it can't be missed
2017-10-25 21:45:15 +00:00
pass
def GetRequiredCredentials( self ):
2017-09-13 20:50:41 +00:00
2017-10-25 21:45:15 +00:00
required_creds = []
2017-10-18 19:41:25 +00:00
2017-10-25 21:45:15 +00:00
for step in self._login_steps:
2017-11-01 20:37:39 +00:00
required_creds.extend( step.GetRequiredCredentials() ) # [ ( credential_type, name, arg_name, string_match ) ] with an order
2017-10-25 21:45:15 +00:00
return required_creds
2017-11-01 20:37:39 +00:00
def IsLoggedIn( self, engine, network_context ):
session = engine.session_manager.GetSession( network_context )
2017-10-25 21:45:15 +00:00
return self._IsLoggedIn( network_context, session )
2017-09-13 20:50:41 +00:00
2017-11-01 20:37:39 +00:00
def Start( self, engine, domain, network_context, credentials ):
# don't mess with the domain--assume that we are given precisely the right domain
2017-09-13 20:50:41 +00:00
# this maybe takes some job_key or something so it can present to the user login process status
# this will be needed in the dialog where we test this. we need good feedback on how it is going
2017-10-18 19:41:25 +00:00
# irl, this could be a 'login popup' message as well, just to inform the user on the progress of any delay
2017-09-13 20:50:41 +00:00
2017-10-25 21:45:15 +00:00
temp_variables = {}
2017-09-13 20:50:41 +00:00
for step in self._login_steps:
try:
2017-10-25 21:45:15 +00:00
step.Start( engine, credentials, temp_variables )
2017-09-13 20:50:41 +00:00
2017-11-01 20:37:39 +00:00
except HydrusExceptions.ValidationException as e:
2017-09-13 20:50:41 +00:00
2017-11-01 20:37:39 +00:00
self._error_reason = str( e )
2017-10-18 19:41:25 +00:00
self._validity = VALIDITY_INVALID
2017-11-01 20:37:39 +00:00
engine.login_manager.SetDirty()
2017-10-18 19:41:25 +00:00
except Exception as e:
# set error info
2017-09-13 20:50:41 +00:00
self._validity = VALIDITY_INVALID
2017-10-25 21:45:15 +00:00
# inform login manager that I'm dirty and need to be saved
2017-09-13 20:50:41 +00:00
return False
2017-10-25 21:45:15 +00:00
# test session logged in status here, erroring gracefully
2017-09-13 20:50:41 +00:00
2017-10-25 21:45:15 +00:00
return True
2017-09-13 20:50:41 +00:00
LOGIN_PARAMETER_TYPE_PARAMETER = 0
LOGIN_PARAMETER_TYPE_COOKIE = 1
LOGIN_PARAMETER_TYPE_HEADER = 2
# make this serialisable
class LoginStep( object ):
def __init__( self ):
2017-11-01 20:37:39 +00:00
self._name = 'hit home page to establish session'
2017-09-13 20:50:41 +00:00
self._method = None # get/post
2017-11-01 20:37:39 +00:00
self._domain_string_converter = None
self._query = 'login.php'
2017-09-13 20:50:41 +00:00
2017-11-01 20:37:39 +00:00
self._statics = [] # arg name | string
2017-09-13 20:50:41 +00:00
2017-11-01 20:37:39 +00:00
self._required_credentials = [] # type | user-facing name (unique) | arg name | string match
2017-09-13 20:50:41 +00:00
2017-11-01 20:37:39 +00:00
self._required_temps = [] # arg name
2017-09-13 20:50:41 +00:00
self._expected_cookies = [] # name | string match
2017-11-01 20:37:39 +00:00
self._content_parsing_nodes = []
2017-09-13 20:50:41 +00:00
2017-11-01 20:37:39 +00:00
def _TestCredentials( self, credentials ):
for ( credential_type, pretty_name, arg_name, string_match ) in self._required_credentials:
if arg_name not in credentials:
raise HydrusExceptions.ValidationException( 'The credential \'' + pretty_name + '\' was missing!' )
arg_value = credentials.GetCredential( arg_name )
2017-11-22 21:03:07 +00:00
try:
string_match.Test( arg_name )
except HydrusExceptions.StringMatchException as e:
reason = unicode( e )
2017-11-01 20:37:39 +00:00
raise HydrusExceptions.ValidationException( 'The credential \'' + pretty_name + '\' did not match requirements:' + os.linesep + reason )
2017-09-13 20:50:41 +00:00
2017-11-01 20:37:39 +00:00
def GetRequiredCredentials( self ):
return list( self._required_credentials )
def Start( self, engine, domain, credentials, temp_variables ):
# e.g. converting 'website.com' to 'login.website.com'
url_base = self._domain_string_converter.Convert( domain )
arguments = {}
arguments.update( self._statics )
self._TestCredentials( credentials )
for ( credential_type, pretty_name, arg_name, string_match ) in self._required_credentials:
arguments[ arg_name ] = credentials.GetCredential( arg_name )
for name in self._required_temps:
if name not in temp_variables:
raise HydrusExceptions.ValidationException( 'The temporary variable \'' + name + '\' was not found!' )
arguments[ name ] = temp_variables[ name ]
if self._method == 'POST':
pass # make it into body
elif self._method == 'GET':
pass # make it into query
2017-09-13 20:50:41 +00:00
# construct the url, failing if creds or temps missing
# hit the url, failing on connection fault or whatever
2017-11-01 20:37:39 +00:00
for parsing_node in self._content_parsing_nodes:
try:
parsing_node.Vetoes()
except HydrusExceptions.VetoException as e:
2017-12-13 22:33:07 +00:00
raise HydrusExceptions.ValidationException( unicode( e ) )
2017-11-01 20:37:39 +00:00
# if content type is a temp variable:
# get it and add to temp_variables
pass
2017-09-13 20:50:41 +00:00
2017-11-01 20:37:39 +00:00
def TestCredentials( self, credentials ):
2017-09-13 20:50:41 +00:00
2017-11-01 20:37:39 +00:00
self._TestCredentials( credentials )
2017-09-13 20:50:41 +00:00