diff --git a/openlibrary/accounts/model.py b/openlibrary/accounts/model.py index a776c8960787..f4f1f16a95ca 100644 --- a/openlibrary/accounts/model.py +++ b/openlibrary/accounts/model.py @@ -590,34 +590,21 @@ def audit_accounts(email, password, require_link=False, test=False): if not valid_email(email): return {'error': 'invalid_email'} - audit = { - 'authenticated': False, - 'has_ia': False, - 'has_ol': False, - 'link': False - } - - ol_login = OpenLibraryAccount.authenticate(email, password) ia_login = InternetArchiveAccount.authenticate(email, password) - if any([err in (ol_login, ia_login) for err - in ['account_blocked', 'account_locked']]): + if any(ia_login == err for err + in ['account_blocked', 'account_locked']): return {'error': 'account_locked'} - if "ok" not in (ol_login, ia_login): - # Neither login succeeded. Prioritize returning other errors - # over `account_not_found` - for resp in (ol_login, ia_login): - if resp != "account_not_found": - return {'error': resp} + if ia_login != "ok": + # Prioritize returning other errors over `account_not_found` + if resp != "account_not_found": + return {'error': resp} return {'error': 'account_not_found'} - if ia_login == "ok": + else: ia_account = InternetArchiveAccount.get(email=email, test=test) - audit['authenticated'] = 'ia' # XXX authenticated_with - audit['has_ia'] = email # XXX change to ia_email - # Get the OL account which links to this IA account ol_account = OpenLibraryAccount.get(link=ia_account.itemname, test=test) link = ol_account.itemname if ol_account else None @@ -647,252 +634,60 @@ def audit_accounts(email, password, require_link=False, test=False): # `ol_account` shares the same email as our IA account and # thus can and should be safely linked to our IA account, or # (c) no `ol_account` which is linked or can be linked has - # been found and therefore, asuming + # been found and therefore, assuming # lending.config_ia_auth_only is enabled, we need to create # and link it. if not ol_account: - if lending.config_ia_auth_only: - try: - ol_account = OpenLibraryAccount.create( - ia_account.itemname, email, password, - displayname=ia_account.screenname, - verified=True, test=test) - except ValueError as e: - return {'error': 'max_retries_exceeded', 'msg': str(e)} - - if not ol_account.itemname: - ol_account.link(ia_account.itemname) - stats.increment('ol.account.xauth.ia-auto-created-ol') - - audit['link'] = ia_account.itemname - audit['has_ol'] = ol_account.email - web.ctx.conn.set_auth_token(ol_account.generate_login_code()) + try: + ol_account = OpenLibraryAccount.create( + ia_account.itemname, email, password, + displayname=ia_account.screenname, + verified=True, test=test) + except ValueError as e: + return {'error': 'max_retries_exceeded'} + + stats.increment('ol.account.xauth.ia-auto-created-ol') # So long as there's either a linked OL account, or an unlinked OL # account with the same email, set them as linked (and let the # finalize logic link them, if needed) else: - if lending.config_ia_auth_only: - if not ol_account.itemname: - ol_account.link(ia_account.itemname) - stats.increment('ol.account.xauth.auto-linked') + if not ol_account.itemname: + ol_account.link(ia_account.itemname) + stats.increment('ol.account.xauth.auto-linked') if not ol_account.verified: - if lending.config_ia_auth_only: - # The IA account is activated (verifying the - # integrity of their email), so we make a judgement - # call to safely activate them. - ol_account.activate() - else: - return {'error': 'ia_account_not_verified'} + # The IA account is activated (verifying the + # integrity of their email), so we make a judgement + # call to safely activate them. + ol_account.activate() if ol_account.blocked: return {'error': 'account_blocked'} - audit['link'] = ia_account.itemname - audit['has_ol'] = ol_account.email # XXX ol_email - - # When a user logs in with OL credentials, the - # web.ctx.site.login() is called with their OL user - # credentials, which internally sets an auth_token - # enabling the user's session. The web.ctx.site.login - # method requires OL credentials which are not present in - # the case where a user logs in with their IA - # credentials. As a result, when users login with their - # valid IA credentials, the following kludge allows us to - # fetch the OL account linked to their IA account, bypass - # this web.ctx.site.login method (which requires OL - # credentials), and directly set an auth_token to - # enable the user's session. - web.ctx.conn.set_auth_token(ol_account.generate_login_code()) - - elif ol_login == "ok": - ol_account = OpenLibraryAccount.get(email=email, test=test) - audit['authenticated'] = 'ol' - audit['has_ol'] = email - ia_account = InternetArchiveAccount.get(email=email, test=test) - if ol_account.itemname: - audit['has_ia'] = ol_account.itemname - audit['link'] = ol_account.itemname - - if lending.config_ia_auth_only: - # When enabled, this will prevent users from logging in - # with their Open Library credentials and instead require - # Internet Archive (Archive.org) credentials. - return {'error': 'ia_login_only'} - - # If the OL account is not linked but there exists an IA - # account having the same email, - elif ia_account: - if not ia_account.verified: - return {'error': 'ia_account_not_verified'} - if ia_account.locked: - return {'error': 'account_blocked'} - audit['has_ia'] = ia_account.itemname - audit['link'] = ia_account.itemname - - # Links the accounts if they can be and are not already: - if (audit['has_ia'] and audit['has_ol'] and audit['authenticated']): - ol_account = OpenLibraryAccount.get(email=audit['has_ol'], test=test) - if not ol_account.itemname: - ol_account.link(audit['link']) - stats.increment('ol.account.xauth.auto-linked') - - if require_link and not audit.get('link', None): + if require_link and not ol_account.itemname: return {'error': 'accounts_not_connected'} - return audit - -def create_accounts(email, password, username="", retries=10, test=False): - """Retrieves the IA or OL account having correct email and password - credentials - """ - retries = 0 if test else retries - audit = audit_accounts(email, password) - - if 'error' in audit or (audit['link'] and audit['authenticated']): - return audit - - ia_account = (InternetArchiveAccount.get(email=audit['has_ia']) - if audit.get('has_ia', False) else None) - ol_account = (OpenLibraryAccount.get(email=audit['has_ol']) - if audit.get('has_ol', False) else None) - - # Make sure at least one account exists - # XXX should never get here, move to audit - if ia_account and ol_account: - if not audit['link']: - if ia_account.locked or ol_account.blocked(): - return {'error': 'account_blocked'} - audit['link'] = ia_account.itemname - ol_account.link(ia_account.itemname) - stats.increment('ol.account.xauth.auto-linked') - return audit - elif not (ia_account or ol_account): - return {'error': 'account_not_found'} - - # Create and link new account - if email and password: - if ol_account: - try: - ol_account_username = ( - username or ol_account.displayname - or ol_account.username) - - ia_account = InternetArchiveAccount.create( - ol_account_username, email, password, - retries=retries, verified=True, test=test) - - audit['link'] = ia_account.itemname - audit['has_ia'] = ia_account.email - audit['has_ol'] = ol_account.email - if not test: - ol_account.link(ia_account.itemname) - stats.increment('ol.account.xauth.ol-created-ia') - return audit - except ValueError as e: - return {'error': 'max_retries_exceeded', 'msg': str(e)} - elif ia_account: - try: - # always take screen name - ia_account_screenname = ia_account.screenname - ia_account_itemname = ia_account.itemname - ol_account = OpenLibraryAccount.create( - ia_account_itemname, email, password, - displayname=ia_account_screenname, - retries=retries, verified=True, test=test) - audit['has_ol'] = ol_account.email - audit['has_ia'] = ia_account.email - audit['link'] = ia_account.itemname - if not test: - ol_account.link(ia_account.itemname) - stats.increment('ol.account.xauth.ia-created-ol') - return audit - except ValueError as e: - return {'error': 'max_retries_exceeded', 'msg': str(e)} - return {'error': 'account_not_found'} - return {'error': 'missing_fields'} - -def link_accounts(email, password, bridgeEmail="", bridgePassword="", - test=False): - """Takes the correct email and password for an archive.org or - openlibrary.org account and then links this account to an existing - account for the complimentary service (using the bridge credentials) - Args: - email (unicode) - the email of the user's primary account - password (unicode) - the password for the user's primary account - bridgeEmail (unicode) - the email of the secondary account which - to connect - bridgePassword (unicode) - the password of the secondary - account to connect - test (bool) - isn't currently used in the link_accounts case - because linking is a safely reversable operation. - Test *is* used in the create_accounts case - (where it prevents real accounts from being - created) - """ - audit = audit_accounts(email, password) - - if 'error' in audit or (audit['link'] and audit['authenticated']): - return audit - - ia_account = (InternetArchiveAccount.get(email=audit['has_ia']) - if audit.get('has_ia', False) else None) - ol_account = (OpenLibraryAccount.get(email=audit['has_ol']) - if audit.get('has_ol', False) else None) - - # Make sure at least one account exists - # XXX should never get here, move to audit - if ia_account and ol_account: - if not audit['link']: - if ia_account.locked or ol_account.blocked(): - return {'error': 'account_blocked'} - audit['link'] = ia_account.itemname - ol_account.link(ia_account.itemname) - if not test: - stats.increment('ol.account.xauth.auto-linked') - return audit - elif not (ia_account or ol_account): - return {'error': 'account_not_found'} - - # Link existing accounts - if bridgeEmail and bridgePassword: - if not valid_email(bridgeEmail): - return {'error': 'invalid_bridgeEmail'} - if ol_account: - _res = InternetArchiveAccount.authenticate( - email=bridgeEmail, password=bridgePassword, test=test) - if _res == "ok": - ia_account = InternetArchiveAccount.get( - email=bridgeEmail, test=test) - if OpenLibraryAccount.get_by_link(ia_account.itemname): - return {'error': 'account_already_linked'} - - ol_account.link(ia_account.itemname) - audit['link'] = ia_account.itemname - audit['has_ia'] = ia_account.email - audit['has_ol'] = ol_account.email - if not test: - stats.increment('ol.account.xauth.ol-existing-ia') - return audit - return {'error': _res} - elif ia_account: - _resp = OpenLibraryAccount.authenticate( - bridgeEmail, bridgePassword) - if _resp == "ok": - ol_account = OpenLibraryAccount.get( - email=bridgeEmail, test=test) - if ol_account.itemname: - return {'error': 'account_already_linked'} + # When a user logs in with OL credentials, the + # web.ctx.site.login() is called with their OL user + # credentials, which internally sets an auth_token + # enabling the user's session. The web.ctx.site.login + # method requires OL credentials which are not present in + # the case where a user logs in with their IA + # credentials. As a result, when users login with their + # valid IA credentials, the following kludge allows us to + # fetch the OL account linked to their IA account, bypass + # this web.ctx.site.login method (which requires OL + # credentials), and directly set an auth_token to + # enable the user's session. + web.ctx.conn.set_auth_token(ol_account.generate_login_code()) + return { + 'authenticated': True, + 'ia_email': ia_account.email, + 'ol_email': ol_account.email, + 'ia_username': ia_account.screenname, + 'ol_username': ol_account.username, + 'link': ol_account.itemname, + } - audit['has_ia'] = ia_account.email - audit['has_ol'] = ol_account.email - audit['link'] = ia_account.itemname - ol_account.link(ia_account.itemname) - if not test: - stats.increment('ol.account.xauth.ia-existing-ol') - return audit - return {'error': _resp} - return {'error': 'account_not_found'} - return {'error': 'missing_fields'} @public def get_internet_archive_id(key): diff --git a/openlibrary/core/lending.py b/openlibrary/core/lending.py index 857f7b72984d..eb9888629c4b 100644 --- a/openlibrary/core/lending.py +++ b/openlibrary/core/lending.py @@ -42,7 +42,6 @@ config_ia_access_secret = None config_ia_ol_shared_key = None config_ia_ol_xauth_s3 = None -config_ia_auth_only = None config_http_request_timeout = None config_content_server = None config_loanstatus_url = None @@ -57,7 +56,7 @@ def setup(config): config_ia_ol_shared_key, config_ia_ol_xauth_s3, \ config_internal_tests_api_key, config_ia_loan_api_url, \ config_ia_availability_api_url, config_ia_xauth_api_url, \ - config_http_request_timeout, config_ia_auth_only + config_http_request_timeout if config.get("content_server"): try: @@ -67,13 +66,6 @@ def setup(config): else: logger.error('content_server unassigned') - _ia_auth_only = False - if config.get('ia_auth_only'): - try: - _ia_auth_only = bool(int(config.get('ia_auth_only'))) - except: - logger.exception('Invalid value for ia_auth_only -- should be int 1 or 0') - config_loanstatus_url = config.get('loanstatus_url') config_bookreader_host = config.get('bookreader_host', 'archive.org') config_ia_loan_api_url = config.get('ia_loan_api_url') @@ -85,7 +77,7 @@ def setup(config): config_ia_ol_xauth_s3 = config.get('ia_ol_xauth_s3') config_internal_tests_api_key = config.get('internal_tests_api_key') config_http_request_timeout = config.get('http_request_timeout') - config_ia_auth_only = False or _ia_auth_only + def is_borrowable(identifiers, acs=False, restricted=False): """Takes a list of archive.org ocaids and returns json indicating diff --git a/openlibrary/plugins/upstream/account.py b/openlibrary/plugins/upstream/account.py index 759bfea8e052..8e3a41147276 100644 --- a/openlibrary/plugins/upstream/account.py +++ b/openlibrary/plugins/upstream/account.py @@ -23,7 +23,7 @@ from openlibrary import accounts from openlibrary.accounts import ( - audit_accounts, link_accounts, create_accounts, + audit_accounts, Account, OpenLibraryAccount, InternetArchiveAccount, valid_email ) @@ -39,6 +39,25 @@ create_link_doc = accounts.create_link_doc sendmail = accounts.sendmail +LOGIN_ERRORS = { + "invalid_email": "The email address you entered is invalid", + "account_blocked": "This account has been blocked", + "account_locked": "This account has been blocked", + "account_not_found": "No account was found with this email. Please try again", + "account_incorrect_password": "The password you entered is incorrect. Please try again", + "account_bad_password": "Wrong password. Please try again", + "account_not_verified": "Please verify your Open Library account before logging in", + "ia_account_not_verified": "Please verify your Internet Archive account before logging in", + "accounts_not_connected": "This account hasn't been connected yet", + "invalid_bridgeEmail": "Failed to link account: invalid email", + "account_already_linked": "This account has already been linked", + "missing_fields": "Please fill out all fields and try again", + "email_registered": "This email is already registered", + "username_registered": "This username is already registered", + "ia_login_only": "Sorry, you must use your Internet Archive email and password to log in", + "max_retries_exceeded": "A problem occurred and we were unable to log you in.", + "wrong_ia_account": "An Open Library account with this email is already linked to a different Internet Archive account. Please contact info@openlibrary.org." + } class availability(delegate.page): path = "/internal/fake/availability" @@ -213,31 +232,26 @@ def POST(self): f.note = utils.get_error("account_create_tos_not_selected") return render['account/create'](f) - if lending.config_ia_auth_only: - ia_account = InternetArchiveAccount.get(email=i.email) - ol_account = OpenLibraryAccount.get(email=i.email) - # Require email to not already be used in IA or OL - if ia_account or ol_account: - f.note = utils.get_error("email_not_already_used") # email_should_not_already_be_used - return render['account/create'](f) + ia_account = InternetArchiveAccount.get(email=i.email) + ol_account = OpenLibraryAccount.get(email=i.email) + # Require email to not already be used in IA or OL + if ia_account or ol_account: + # email_should_not_already_be_used + f.note = utils.get_error("email_not_already_used") + return render['account/create'](f) + + try: # Create ia_account: require they activate via IA email # and then login to OL. Logging in after activation with # IA credentials will auto create and link OL account. ia_account = InternetArchiveAccount.create( - username=i.username, email=i.email, password=i.password, + screenname=i.username, email=i.email, password=i.password, verified=False) - - try: - accounts.register(username=i.username, - email=i.email, - password=i.password, - displayname=i.displayname) - except ClientException, e: - f.note = str(e) + except ValueError as e: + f.note = LOGIN_ERRORS['max_retries_exceeded'] return render['account/create'](f) - send_verification_email(i.username, i.email) return render['account/verify'](username=i.username, email=i.email) del delegate.pages['/account/register'] @@ -253,30 +267,10 @@ class account_login(delegate.page): """ path = "/account/login" - LOGIN_ERRORS = { - "invalid_email": "The email address you entered is invalid", - "account_blocked": "This account has been blocked", - "account_locked": "This account has been blocked", - "account_not_found": "No account was found with this email. Please try again", - "account_incorrect_password": "The password you entered is incorrect. Please try again", - "account_bad_password": "Wrong password. Please try again", - "account_not_verified": "Please verify your Open Library account before logging in", - "ia_account_not_verified": "Please verify your Internet Archive account before logging in", - "accounts_not_connected": "This account hasn't been connected yet", - "invalid_bridgeEmail": "Failed to link account: invalid email", - "account_already_linked": "This account has already been linked", - "missing_fields": "Please fill out all fields and try again", - "email_registered": "This email is already registered", - "username_registered": "This username is already registered", - "ia_login_only": "Sorry, you must use your Internet Archive email and password to log in", - "max_retries_exceeded": "A problem occurred and we were unable to log you in.", - "wrong_ia_account": "An Open Library account with this email is already linked to a different Internet Archive account. Please contact info@openlibrary.org." - } - def render_error(self, error_key, i): f = forms.Login() f.fill(i) - f.note = self.LOGIN_ERRORS[error_key] + f.note = LOGIN_ERRORS[error_key] return render.login(f) def GET(self): @@ -571,41 +565,6 @@ def POST(self, code): return render_template("account/password/reset_success", username=username) -class account_connect(delegate.page): - - path = "/account/connect" - - def POST(self): - """When a user logs in with either an OL or IA account which have not - been linked, and if the user's credentials for this account - have been verified, the next step is for the user to (a) - connect their account to an account for whichever service is - missing, or (b) to create a new account for this service and - then link them. The /account/connect endpoint handles this - linking case and dispatches to the correct method (either - 'link' or 'create' depending on the parameters POSTed to the - endpoint). - - Note: Emails are case sensitive behind the scenes and - functions which require them as lower will make them so - """ - - i = web.input(email="", password="", username="", - bridgeEmail="", bridgePassword="", - token="", service="link") - test = 'openlibrary' if i.token == lending.config_internal_tests_api_key else None - if i.service == "link": - result = link_accounts(i.get('email'), i.password, - bridgeEmail=i.bridgeEmail, - bridgePassword=i.bridgePassword) - elif i.service == "create": - result = create_accounts(i.get('email'), i.password, - username=i.username, test=test) - else: - result = {'error': 'invalid_option'} - return delegate.RawText(simplejson.dumps(result), - content_type="application/json") - class account_audit(delegate.page): path = "/account/audit"