Beispiel #1
0
def increment_error_count(key):
    """
    :param str key: e.g. ol.exceptions or el.internal-errors-segmented
    """
    top_url_path = 'none'
    page_class = 'none'
    if web.ctx and hasattr(web.ctx, 'path') and web.ctx.path:
        top_url_path = _get_top_level_path_for_metric(web.ctx.path)
        page_class = _get_path_page_name(web.ctx.path)

    exception_type, exception_value, tback = sys.exc_info()
    exception_type_name = exception_type.__name__
    # Log exception file
    path = find_topmost_useful_file(exception_value, tback)
    path = os.path.split(path)

    # log just filename, unless it's code.py (cause that's useless!)
    ol_file = path[1]
    if path[1] in ('code.py', 'index.html', 'edit.html', 'view.html'):
        ol_file = os.path.split(path[0])[1] + '_' + _encode_key_part(path[1])

    metric_parts = [
        top_url_path,
        'class_' + page_class,
        ol_file,
        exception_type_name,
        'count',
    ]
    metric = '.'.join([_encode_key_part(p) for p in metric_parts])
    graphite_stats.increment(key + '.' + metric)
Beispiel #2
0
 def POST_leave_waitinglist(self, edition, user, i):
     waitinglist.leave_waitinglist(user.key, edition.key)
     stats.increment('ol.loans.leaveWaitlist')
     if i.get("redirect"):
         raise web.redirect(i.redirect)
     else:
         raise web.redirect(edition.url())
Beispiel #3
0
    def POST(self):
        form = web.input()
        email = form.get("email", "")
        topic = form.get("topic", "")
        description = form.get("question", "")
        url = form.get("url", "")
        user = accounts.get_current_user()
        useragent = web.ctx.env.get("HTTP_USER_AGENT","")
        if not all([email, topic, description]):
            return ""

        default_assignees = config.get("support_default_assignees",{})
        topic_key = str(topic.replace(" ","_").lower())
        if topic_key in default_assignees:
            assignee = default_assignees.get(topic_key)
        else:
            assignee = default_assignees.get("default", "*****@*****.**")
        stats.increment("ol.support.all")
        subject = "Support case *%s*"%topic

        url = web.ctx.home + url
        displayname = user and user.get_name() or ""
        username = user and user.get_username() or ""

        message = SUPPORT_EMAIL_TEMPLATE % locals()
        sendmail(email, assignee, subject, message)
        return render_template("email/case_created", assignee)
Beispiel #4
0
 def POST_leave_waitinglist(self, edition, user, i):
     waitinglist.leave_waitinglist(user.key, edition.key)
     stats.increment('ol.loans.leaveWaitlist')
     if i.get("redirect"):
         raise web.redirect(i.redirect)
     else:
         raise web.redirect(edition.url())
Beispiel #5
0
    def POST(self):
        form = web.input()
        email = form.get("email", "")
        topic = form.get("topic", "")
        description = form.get("question", "")
        url = form.get("url", "")
        user = accounts.get_current_user()
        useragent = web.ctx.env.get("HTTP_USER_AGENT", "")
        if not all([email, topic, description]):
            return ""

        default_assignees = config.get("support_default_assignees", {})
        topic_key = str(topic.replace(" ", "_").lower())
        if topic_key in default_assignees:
            assignee = default_assignees.get(topic_key)
        else:
            assignee = default_assignees.get("default",
                                             "*****@*****.**")
        stats.increment("ol.support.all")
        subject = "Support case *%s*" % topic

        url = web.ctx.home + url
        displayname = user and user.get_name() or ""
        username = user and user.get_username() or ""

        message = SUPPORT_EMAIL_TEMPLATE % locals()
        sendmail(email, assignee, subject, message)
        return render_template("email/case_created", assignee)
Beispiel #6
0
 def unlink(self):
     """Careful, this will save any other changes to the ol user object as
     well
     """
     _ol_account = web.ctx.site.store.get(self._key)
     _ol_account['internetarchive_itemname'] = None
     web.ctx.site.store[self._key] = _ol_account
     self.internetarchive_itemname = None
     stats.increment('ol.account.xauth.unlinked')
Beispiel #7
0
 def unlink(self):
     """Careful, this will save any other changes to the ol user object as
     well
     """
     _ol_account = web.ctx.site.store.get(self._key)
     _ol_account['internetarchive_itemname'] = None
     web.ctx.site.store[self._key] = _ol_account
     self.internetarchive_itemname = None
     stats.increment('ol.account.xauth.unlinked')
Beispiel #8
0
    def link(self, itemname):
        """Careful, this will save any other changes to the ol user object as
        well
        """
        itemname = itemname if itemname.startswith('@') else '@%s' % itemname

        _ol_account = web.ctx.site.store.get(self._key)
        _ol_account['internetarchive_itemname'] = itemname
        web.ctx.site.store[self._key] = _ol_account
        self.internetarchive_itemname = itemname
        stats.increment('ol.account.xauth.linked')
Beispiel #9
0
    def link(self, itemname):
        """Careful, this will save any other changes to the ol user object as
        well
        """
        itemname = itemname if itemname.startswith('@') else '@%s' % itemname

        _ol_account = web.ctx.site.store.get(self._key)
        _ol_account['internetarchive_itemname'] = itemname
        web.ctx.site.store[self._key] = _ol_account
        self.internetarchive_itemname = itemname
        stats.increment('ol.account.xauth.linked')
Beispiel #10
0
def setup():
    "Basic startup status for the server"
    global status_info, feature_flags
    host = socket.gethostname()
    status_info = {
        "Software version": get_software_version(),
        "Python version": sys.version.split()[0],
        "Host": host,
        "Start time": datetime.datetime.utcnow(),
    }
    feature_flags = get_features_enabled()

    # Host is e.g. ol-web4.blah.archive.org ; we just want the first subdomain
    first_subdomain = host.split('.')[0] or 'unknown'
    stats.increment('ol.servers.%s.started' % first_subdomain)
Beispiel #11
0
    def POST(self):
        # if not support_db:
        #     return "Couldn't initialise connection to support database"
        form = web.input()
        email = form.get("email", "")
        topic = form.get("topic", "")
        description = form.get("question", "")
        url = form.get("url", "")
        user = accounts.get_current_user()
        useragent = web.ctx.env.get("HTTP_USER_AGENT", "")
        if not all([email, topic, description]):
            return ""

        default_assignees = config.get("support_default_assignees", {})
        topic_key = str(topic.replace(" ", "_").lower())
        if topic_key in default_assignees:
            # This is set to False to prevent cases from being created
            # even if there is a designated assignee. This prevents
            # the database from being updated.
            create_case = False
            assignee = default_assignees.get(topic_key)
        else:
            create_case = False
            assignee = default_assignees.get("default", "*****@*****.**")
        if create_case:
            c = support_db.create_case(
                creator_name=user and user.get_name() or "",
                creator_email=email,
                creator_useragent=useragent,
                creator_username=user and user.get_username() or "",
                subject=topic,
                description=description,
                url=url,
                assignee=assignee,
            )
            stats.increment("support.all")
        else:
            stats.increment("support.all")
            subject = "Support case *%s*" % topic

            url = web.ctx.home + url
            displayname = user and user.get_name() or ""
            username = user and user.get_username() or ""

            message = SUPPORT_EMAIL_TEMPLATE % locals()
            sendmail(email, assignee, subject, message)
        return render_template("email/case_created", assignee)
Beispiel #12
0
    def POST(self):
        form = web.input()
        patron_name = form.get("name", "")
        email = form.get("email", "")
        topic = form.get("topic", "")
        description = form.get("question", "")
        url = form.get("url", "")
        user = accounts.get_current_user()
        useragent = web.ctx.env.get("HTTP_USER_AGENT", "")
        if not all([email, topic, description]):
            return ""

        hashed_ip = hashlib.md5(web.ctx.ip.encode('utf-8')).hexdigest()
        has_emailed_recently = get_memcache().get('contact-POST-%s' %
                                                  hashed_ip)
        if has_emailed_recently:
            recap = get_recaptcha()
            if recap and not recap.validate():
                return render_template(
                    "message.html",
                    'Recaptcha solution was incorrect',
                    ('Please <a href="javascript:history.back()">go back</a> and try '
                     'again.'),
                )

        default_assignees = config.get("support_default_assignees", {})
        topic_key = str(topic.replace(" ", "_").lower())
        if topic_key in default_assignees:
            assignee = default_assignees.get(topic_key)
        else:
            assignee = default_assignees.get("default",
                                             "*****@*****.**")
        stats.increment("ol.support.all")
        subject = "Support case *%s*" % topic

        url = web.ctx.home + url
        displayname = user and user.get_name() or ""
        username = user and user.get_username() or ""

        message = SUPPORT_EMAIL_TEMPLATE % locals()
        sendmail(email, assignee, subject, message)

        get_memcache().set('contact-POST-%s' % hashed_ip,
                           "true",
                           time=15 * MINUTE_SECS)
        return render_template("email/case_created", assignee)
Beispiel #13
0
    def POST(self):
        # if not support_db:
        #     return "Couldn't initialise connection to support database"
        form = web.input()
        email = form.get("email", "")
        topic = form.get("topic", "")
        description = form.get("question", "")
        url = form.get("url", "")
        user = accounts.get_current_user()
        useragent = web.ctx.env.get("HTTP_USER_AGENT", "")
        if not all([email, topic, description]):
            return ""

        default_assignees = config.get("support_default_assignees", {})
        topic_key = str(topic.replace(" ", "_").lower())
        if topic_key in default_assignees:
            # This is set to False to prevent cases from being created
            # even if there is a designated assignee. This prevents
            # the database from being updated.
            create_case = False
            assignee = default_assignees.get(topic_key)
        else:
            create_case = False
            assignee = default_assignees.get("default", "*****@*****.**")
        if create_case:
            c = support_db.create_case(
                creator_name=user and user.get_name() or "",
                creator_email=email,
                creator_useragent=useragent,
                creator_username=user and user.get_username() or "",
                subject=topic,
                description=description,
                url=url,
                assignee=assignee)
            stats.increment("support.all")
        else:
            stats.increment("support.all")
            subject = "Support case *%s*" % topic
            message = "A new support case has been filed\n\nTopic: %s\n\nDescription:\n%s" % (
                topic, description)
            web.sendmail(email, assignee, subject, message)
        return render_template("email/case_created", assignee)
Beispiel #14
0
    def POST(self):
        if not support_db:
            return "Couldn't initialise connection to support database"
        form = web.input()
        email = form.get("email", "")
        topic = form.get("topic", "")
        description = form.get("question", "")
        url = form.get("url", "")
        user = accounts.get_current_user()
        useragent = web.ctx.env.get("HTTP_USER_AGENT","")
        if not all([email, topic, description]):
            return ""

        default_assignees = config.get("support_default_assignees",{})
        topic_key = topic.replace(" ","_").lower()
        if topic_key in default_assignees:
            create_case = True
            assignee = default_assignees.get(topic_key)
        else:
            create_case = False
            assignee = default_assignees.get("default", "*****@*****.**")
        if create_case:
            c = support_db.create_case(creator_name      = user and user.get_name() or "",
                                       creator_email     = email,
                                       creator_useragent = useragent,
                                       creator_username  = user and user.get_username() or "",
                                       subject           = topic,
                                       description       = description,
                                       url               = url,
                                       assignee          = assignee)
            stats.increment("support.all")
        else:
            stats.increment("support.all")
            subject = "Support case *%s*"%topic
            message = "A new support case has been filed\n\nTopic: %s\n\nDescription:\n%s"%(topic, description)
            web.sendmail(email, assignee, subject, message)
        
        return render_template("email/case_created", assignee)
Beispiel #15
0
def stats_hook():
    """web.py unload hook to add X-OL-Stats header.

    This info can be written to lighttpd access log for collecting

    Also, send stats to graphite using statsd
    """
    stats_summary = stats.stats_summary()
    update_all_stats(stats_summary)
    try:
        if "stats-header" in web.ctx.features:
            web.header("X-OL-Stats", format_stats(stats_summary))
    except Exception as e:
        # don't let errors in stats collection break the app.
        print(str(e), file=web.debug)

    # This name is misleading. It gets incremented for more than just pages.
    # E.g. *.json requests (even ajax), image requests. Although I can't
    # see any *.js requests? So not sure exactly when we're called here.
    graphite_stats.increment('ol.pageviews')

    memcache_hits = 0
    memcache_misses = 0
    for s in web.ctx.get("stats", []):
        if s.name == 'memcache.get':
            if s.data['hit']:
                memcache_hits += 1
            else:
                memcache_misses += 1

    if memcache_hits:
        graphite_stats.increment('ol.memcache.hits', memcache_hits, rate=0.025)
    if memcache_misses:
        graphite_stats.increment('ol.memcache.misses',
                                 memcache_misses,
                                 rate=0.025)

    for name, value in stats_summary.items():
        name = name.replace(".", "_")
        time = value.get("time", 0.0) * 1000
        key = 'ol.' + name
        graphite_stats.put(key, time)
Beispiel #16
0
def audit_accounts(email, password, require_link=False,
                   s3_access_key=None, s3_secret_key=None, test=False):
    """Performs an audit of the IA or OL account having this email.

    The audit:
    - verifies the password is correct for this account
    - aborts if any sort of error (e.g. account blocked, unverified)
    - reports whether the account is linked (to a secondary account)
    - if unlinked, reports whether a secondary account exists w/
      matching email

    Args:
        email (unicode)
        password (unicode)
        require_link (bool) - if True, returns `accounts_not_connected`
                              if accounts are not linked
        test (bool) - not currently used; is there to allow testing in
                      the absence of archive.org dependency
    """
    from openlibrary.core import lending

    if s3_access_key and s3_secret_key:
        r = InternetArchiveAccount.s3auth(s3_access_key, s3_secret_key)
        if not r.get('authorized', False):
            return {'error': 'invalid_s3keys'}
        ia_login = "******"
        email = r['username']
    else:
        if not valid_email(email):
            return {'error': 'invalid_email'}
        ia_login = InternetArchiveAccount.authenticate(email, password)

    if any(ia_login == err for err
            in ['account_blocked', 'account_locked']):
        return {'error': 'account_locked'}

    if ia_login != "ok":
        # Prioritize returning other errors over `account_not_found`
        if ia_login != "account_not_found":
            return {'error': ia_login}
        return {'error': 'account_not_found'}

    else:
        ia_account = InternetArchiveAccount.get(email=email, test=test)

        # 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

        # The fact that there is no link implies no Open Library
        # account exists containing a link to this Internet Archive
        # account...
        if not link:
            # then check if there's an Open Library account which shares
            # the same email as this IA account.
            ol_account = OpenLibraryAccount.get(email=email, test=test)

            # If an Open Library account with a matching email account exist...
            if ol_account:
                # Check whether it is linked already, i.e. has an itemname
                # set. We already determined that no OL account is
                # linked to our IA account. Therefore this Open
                # Library account having the same email as our IA
                # account must have been linked to a different
                # Internet Archive account.
                if ol_account.itemname:
                    return {'error': 'wrong_ia_account'}

        # At this point, it must either be the case that (a)
        # `ol_account` already links to our IA account (in which case
        # `link` has a correct value), (b) that an unlinked
        # `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, assuming
        # lending.config_ia_auth_only is enabled, we need to create
        # and link it.
        if not ol_account:
            if not password:
                raise {'error': 'link_attempt_requires_password'}
            try:
                ol_account = OpenLibraryAccount.create(
                    ia_account.itemname, email, password,
                    displayname=ia_account.screenname,
                    verified=True, retries=5, test=test)
            except ValueError as e:
                return {'error': 'max_retries_exceeded'}

            ol_account.link(ia_account.itemname)
            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 not ol_account.itemname:
                ol_account.link(ia_account.itemname)
                stats.increment('ol.account.xauth.auto-linked')
            if not ol_account.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'}

    if require_link:
        ol_account = OpenLibraryAccount.get(link=ia_account.itemname, test=test)
        if ol_account and not ol_account.itemname:
            return {'error': 'accounts_not_connected'}

    # 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,
    }
Beispiel #17
0
 def POST_join_waitinglist(self, edition, user):
     waitinglist.join_waitinglist(user.key, edition.key)
     stats.increment('ol.loans.joinWaitlist')
     raise web.redirect(edition.url())
Beispiel #18
0
    def POST(self, key):
        """Called when the user wants to borrow the edition"""

        i = web.input(action='borrow', format=None, ol_host=None)

        if i.ol_host:
            ol_host = i.ol_host
        else:
            ol_host = 'openlibrary.org'

        edition = web.ctx.site.get(key)
        if not edition:
            raise web.notfound()

        # Make a call to availability v2 update the subjects according
        # to result if `open`, redirect to bookreader
        response = lending.get_availability_of_ocaid(edition.ocaid)
        availability = response[edition.ocaid] if response else {}
        if availability and availability['status'] == 'open':
            raise web.seeother('https://archive.org/stream/' + edition.ocaid +
                               '?ref=ol')

        error_redirect = ('https://archive.org/stream/' + edition.ocaid +
                          '?ref=ol')
        user = accounts.get_current_user()

        if user:
            account = OpenLibraryAccount.get_by_email(user.email)
            ia_itemname = account.itemname if account else None
        if not user or not ia_itemname:
            web.setcookie(config.login_cookie_name, "", expires=-1)
            raise web.seeother("/account/login?redirect=%s/borrow?action=%s" %
                               (edition.url(), i.action))

        action = i.action

        # Intercept a 'borrow' action if the user has already
        # borrowed the book and convert to a 'read' action.
        # Added so that direct bookreader links being routed through
        # here can use a single action of 'borrow', regardless of
        # whether the book has been checked out or not.
        if action == 'borrow' and user.has_borrowed(edition):
            action = 'read'

        if action == 'borrow':
            resource_type = i.format or 'bookreader'

            if resource_type not in ['epub', 'pdf', 'bookreader']:
                raise web.seeother(error_redirect)

            user_meets_borrow_criteria = user_can_borrow_edition(
                user, edition, resource_type)

            if user_meets_borrow_criteria:
                # This must be called before the loan is initiated,
                # otherwise the user's waitlist status will be cleared
                # upon loan creation
                track_loan = False if is_users_turn_to_borrow(
                    user, edition) else True

                loan = lending.create_loan(identifier=edition.ocaid,
                                           resource_type=resource_type,
                                           user_key=ia_itemname,
                                           book_key=key)

                if loan:
                    loan_link = loan['loan_link']
                    if resource_type == 'bookreader':
                        if track_loan:
                            # As of 2017-12-14, Petabox will be
                            # responsible for tracking borrows which
                            # are the result of waitlist redemptions,
                            # so we don't want to track them here to
                            # avoid double accounting. When a reader
                            # is at the head of a waitlist and goes to
                            # claim their loan, Petabox now checks
                            # whether the waitlist was initiated from
                            # OL, and if it was, petabox tracks
                            # ol.loans.bookreader accordingly via
                            # lending.create_loan.
                            stats.increment('ol.loans.bookreader')

                        raise web.seeother(
                            make_bookreader_auth_link(
                                loan.get_key(), edition.ocaid,
                                '/stream/' + edition.ocaid, ol_host))
                    elif resource_type == 'pdf':
                        stats.increment('ol.loans.pdf')
                        raise web.seeother(loan_link)
                    elif resource_type == 'epub':
                        stats.increment('ol.loans.epub')
                        raise web.seeother(loan_link)
                else:
                    raise web.seeother(error_redirect)
            else:
                raise web.seeother(error_redirect)

        elif action == 'return':
            # Check that this user has the loan
            user.update_loan_status()
            loans = get_loans(user)

            # We pick the first loan that the user has for this book that is returnable.
            # Assumes a user can't borrow multiple formats (resource_type) of the same book.
            user_loan = None
            for loan in loans:
                # Handle the case of multiple edition records for the same
                # ocaid and the user borrowed from one and returning from another
                has_loan = (loan['book'] == edition.key
                            or loan['ocaid'] == edition.ocaid)
                if has_loan:
                    user_loan = loan
                    break

            if not user_loan:
                # $$$ add error message
                raise web.seeother(error_redirect)

            user_loan.return_loan()

            # Show the page with "you've returned this". Use a dummy slug.
            # $$$ this would do better in a session variable that can be cleared
            #     after the message is shown once
            raise web.seeother(edition.url())

        elif action == 'read':
            # Look for loans for this book
            user.update_loan_status()
            loans = get_loans(user)
            for loan in loans:
                if loan['book'] == edition.key:
                    raise web.seeother(
                        make_bookreader_auth_link(loan['_key'], edition.ocaid,
                                                  '/stream/' + edition.ocaid,
                                                  ol_host))
        elif action == 'join-waitinglist':
            return self.POST_join_waitinglist(edition, user)
        elif action == 'leave-waitinglist':
            return self.POST_leave_waitinglist(edition, user, i)

        # Action not recognized
        raise web.seeother(error_redirect)
Beispiel #19
0
    def POST(self, key):
        """Called when the user wants to borrow the edition"""
        
        i = web.input(action='borrow', format=None, ol_host=None)

        if i.ol_host:
            ol_host = i.ol_host
        else:        
            ol_host = 'openlibrary.org'
        
        edition = web.ctx.site.get(key)
        if not edition:
            raise web.notfound()
        error_redirect = edition.url("/borrow")
        
        user = accounts.get_current_user()
        if not user:
            raise web.seeother(error_redirect)

        if i.action == 'borrow':
            resource_type = i.format
            
            if resource_type not in ['epub', 'pdf', 'bookreader']:
                raise web.seeother(error_redirect)

            if resource_type == 'bookreader':
                #ab.convert("borrow-layout")
                pass
            
            if user_can_borrow_edition(user, edition, resource_type):

                # XXX-Anand - Sept 2014: 
                # Extra check to avoid book being given to someone when people are already 
                # waiting on it. There are some reports of this happening.
                # Redirect back to the same page if there is waitinglist.
                wl = edition.get_waitinglist()
                if wl and (wl[0].get_user_key() != user.key or wl[0]['status'] != 'available'):
                    raise web.seeother(error_redirect)

                loan = lending.create_loan(
                    identifier=edition.ocaid,
                    resource_type=resource_type,
                    user_key=user.key,
                    book_key=key)
                loan_link = loan['loan_link']
                
                if resource_type == 'bookreader':
                    stats.increment('ol.loans.bookreader')
                elif resource_type == 'pdf':
                    stats.increment('ol.loans.pdf')
                elif resource_type == 'epub':
                    stats.increment('ol.loans.epub')
                    
                if resource_type == 'bookreader':
                    raise web.seeother(make_bookreader_auth_link(loan.get_key(), edition.ocaid, '/stream/' + edition.ocaid, ol_host))
                else:
                    raise web.seeother(loan_link)
            else:
                # Send to the borrow page
                raise web.seeother(error_redirect)
                
        elif i.action == 'return':
            # Check that this user has the loan
            user.update_loan_status()
            loans = get_loans(user)

            # We pick the first loan that the user has for this book that is returnable.
            # Assumes a user can't borrow multiple formats (resource_type) of the same book.
            user_loan = None
            for loan in loans:
                # Handle the case of multiple edition records for the same 
                # ocaid and the user borrowed from one and returning from another
                has_loan = (loan['book'] == edition.key or loan['ocaid'] == edition.ocaid)
                if has_loan and can_return_resource_type(loan['resource_type']):
                    user_loan = loan
                    break
                    
            if not user_loan:
                # $$$ add error message
                raise web.seeother(error_redirect)
                
            user_loan.return_loan()
            
            # Show the page with "you've returned this"
            # $$$ this would do better in a session variable that can be cleared
            #     after the message is shown once
            raise web.seeother(edition.url('/borrow?r=t'))
            
        elif i.action == 'read':
            # Look for loans for this book
            user.update_loan_status()
            loans = get_loans(user)
            for loan in loans:
                if loan['book'] == edition.key:
                    raise web.seeother(make_bookreader_auth_link(loan['_key'], edition.ocaid, '/stream/' + edition.ocaid, ol_host))
        elif i.action == 'join-waitinglist':
            return self.POST_join_waitinglist(edition, user)
        elif i.action == 'leave-waitinglist':
            return self.POST_leave_waitinglist(edition, user, i)

        # Action not recognized
        raise web.seeother(error_redirect)
Beispiel #20
0
    def POST(self, key):
        """Called when the user wants to borrow the edition"""

        i = web.input(action='borrow', format=None, ol_host=None)

        if i.ol_host:
            ol_host = i.ol_host
        else:
            ol_host = 'openlibrary.org'

        edition = web.ctx.site.get(key)
        if not edition:
            raise web.notfound()
        error_redirect = edition.url("/borrow")

        user = accounts.get_current_user()
        if not user:
            raise web.seeother(error_redirect)

        if i.action == 'borrow':
            resource_type = i.format

            if resource_type not in ['epub', 'pdf', 'bookreader']:
                raise web.seeother(error_redirect)

            if resource_type == 'bookreader':
                ab.convert("borrow-layout")

            if user_can_borrow_edition(user, edition, resource_type):
                loan = Loan(user.key, key, resource_type)
                if resource_type == 'bookreader':
                    # The loan expiry should be utc isoformat
                    loan.expiry = datetime.datetime.utcfromtimestamp(
                        time.time() + bookreader_loan_seconds).isoformat()
                loan_link = loan.make_offer(
                )  # generate the link and record that loan offer occurred

                # $$$ Record fact that user has done a borrow - how do I write into user? do I need permissions?
                # if not user.has_borrowed:
                #   user.has_borrowed = True
                #   user.save()

                if resource_type == 'bookreader':
                    stats.increment('loans.bookreader')
                elif resource_type == 'pdf':
                    stats.increment('loans.pdf')
                elif resource_type == 'epub':
                    stats.increment('loans.epub')

                if resource_type == 'bookreader':
                    raise web.seeother(
                        make_bookreader_auth_link(loan.get_key(),
                                                  edition.ocaid,
                                                  '/stream/' + edition.ocaid,
                                                  ol_host))
                else:
                    raise web.seeother(loan_link)
            else:
                # Send to the borrow page
                raise web.seeother(error_redirect)

        elif i.action == 'return':
            # Check that this user has the loan
            user.update_loan_status()
            loans = get_loans(user)

            # We pick the first loan that the user has for this book that is returnable.
            # Assumes a user can't borrow multiple formats (resource_type) of the same book.
            user_loan = None
            for loan in loans:
                # Handle the case of multiple edition records for the same
                # ocaid and the user borrowed from one and returning from another
                has_loan = (loan['book'] == edition.key
                            or loan['ocaid'] == edition.ocaid)
                if has_loan and can_return_resource_type(
                        loan['resource_type']):
                    user_loan = loan
                    break

            if not user_loan:
                # $$$ add error message
                raise web.seeother(error_redirect)

            # They have it -- return it
            return_resource(user_loan['resource_id'])

            # Show the page with "you've returned this"
            # $$$ this would do better in a session variable that can be cleared
            #     after the message is shown once
            raise web.seeother(edition.url('/borrow?r=t'))

        elif i.action == 'read':
            # Look for loans for this book
            user.update_loan_status()
            loans = get_loans(user)
            for loan in loans:
                if loan['book'] == edition.key:
                    raise web.seeother(
                        make_bookreader_auth_link(loan['_key'], edition.ocaid,
                                                  '/stream/' + edition.ocaid,
                                                  ol_host))
        elif i.action == 'join-waitinglist':
            return self.POST_join_waitinglist(edition, user)
        elif i.action == 'leave-waitinglist':
            return self.POST_leave_waitinglist(edition, user, i)

        # Action not recognized
        raise web.seeother(error_redirect)
Beispiel #21
0
    def POST(self, key):
        """Called when the user wants to borrow the edition"""
        
        i = web.input(action='borrow', format=None, ol_host=None)

        if i.ol_host:
            ol_host = i.ol_host
        else:        
            ol_host = 'openlibrary.org'
        
        edition = web.ctx.site.get(key)
        if not edition:
            raise web.notfound()
        error_redirect = edition.url("/borrow")
        
        user = accounts.get_current_user()
        if not user:
            raise web.seeother(error_redirect)

        if i.action == 'borrow':
            resource_type = i.format
            
            if resource_type not in ['epub', 'pdf', 'bookreader']:
                raise web.seeother(error_redirect)
            
            if user_can_borrow_edition(user, edition, resource_type):
                loan = Loan(user.key, key, resource_type)
                if resource_type == 'bookreader':
                    # The loan expiry should be utc isoformat
                    loan.expiry = datetime.datetime.utcfromtimestamp(time.time() + bookreader_loan_seconds).isoformat()
                loan_link = loan.make_offer() # generate the link and record that loan offer occurred
                                
                # $$$ Record fact that user has done a borrow - how do I write into user? do I need permissions?
                # if not user.has_borrowed:
                #   user.has_borrowed = True
                #   user.save()
                
                if resource_type == 'bookreader':
                    stats.increment('loans.bookreader')
                elif resource_type == 'pdf':
                    stats.increment('loans.pdf')
                elif resource_type == 'epub':
                    stats.increment('loans.epub')
                    
                if resource_type == 'bookreader':
                    raise web.seeother(make_bookreader_auth_link(loan.get_key(), edition.ocaid, '/stream/' + edition.ocaid, ol_host))
                else:
                    raise web.seeother(loan_link)
            else:
                # Send to the borrow page
                raise web.seeother(error_redirect)
                
        elif i.action == 'return':
            # Check that this user has the loan
            user.update_loan_status()
            loans = get_loans(user)

            # We pick the first loan that the user has for this book that is returnable.
            # Assumes a user can't borrow multiple formats (resource_type) of the same book.
            user_loan = None
            for loan in loans:
                # Handle the case of multiple edition records for the same 
                # ocaid and the user borrowed from one and returning from another
                has_loan = (loan['book'] == edition.key or loan['ocaid'] == edition.ocaid)
                if has_loan and can_return_resource_type(loan['resource_type']):
                    user_loan = loan
                    break
                    
            if not user_loan:
                # $$$ add error message
                raise web.seeother(error_redirect)
                
            # They have it -- return it
            return_resource(user_loan['resource_id'])
            
            # Show the page with "you've returned this"
            # $$$ this would do better in a session variable that can be cleared
            #     after the message is shown once
            raise web.seeother(edition.url('/borrow?r=t'))
            
        elif i.action == 'read':
            # Look for loans for this book
            user.update_loan_status()
            loans = get_loans(user)
            for loan in loans:
                if loan['book'] == edition.key:
                    raise web.seeother(make_bookreader_auth_link(loan['_key'], edition.ocaid, '/stream/' + edition.ocaid, ol_host))
        elif i.action == 'join-waitinglist':
            return self.POST_join_waitinglist(edition, user)
        elif i.action == 'leave-waitinglist':
            return self.POST_leave_waitinglist(edition, user, i)

        # Action not recognized
        raise web.seeother(error_redirect)
Beispiel #22
0
def page_unload_hook():
    web.ctx.graphiteRequestStats.request_unloaded()
    graphite_stats.increment(web.ctx.graphiteRequestStats.to_metric())
Beispiel #23
0
def audit_accounts(email, password, test=False):
    """Performs an audit of the IA or OL account having this email.

    The audit:
    - verifies the password is correct for this account
    - aborts if any sort of error (e.g. account blocked, unverified)
    - reports whether the account is linked (to a secondary account)
    - if unlinked, reports whether a secondary account exists w/
      matching email

    Args:
        email (unicode)
        password (unicode)
        test (bool) - not currently used; is there to allow testing in
                      the absence of archive.org dependency
    """
    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']
    ]):
        return {'error': 'account_locked'}

    # One of the accounts must authenticate w/o error
    if "ok" not in (ol_login, ia_login):
        for resp in (ol_login, ia_login):
            if resp != "account_not_found":
                return {'error': resp}
        return {'error': 'account_not_found'}

    elif ia_login == "ok":
        ia_account = InternetArchiveAccount.get(email=email, test=test)

        audit['authenticated'] = 'ia'
        audit['has_ia'] = email

        # Get the OL account which links to this IA account, if
        # one exists (i.e. this IA account could be linked to an
        # OL account with a different email)
        ol_account = OpenLibraryAccount.get(link=ia_account.itemname,
                                            test=test)
        link = ol_account.itemname if ol_account else None

        # If no linked account was found but there's an ol_account
        # having the same email as this IA account, mark them to
        # be linked
        if not link:
            ol_account = OpenLibraryAccount.get(email=email, test=test)
            link = ia_account.itemname if ol_account else None

        # So long as there's either a linked OL account, or an OL
        # account with the same email, set them as linked (and let the
        # finalize logic link them, if necessary)
        if ol_account:
            if not ol_account.verified:
                return {'error': 'account_not_verified'}
            if ol_account.blocked:
                return {'error': 'account_blocked'}
            audit['link'] = link
            audit['has_ol'] = ol_account.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)

        # Should get the IA account linked to this OL account, if one
        # exists. However, xauthn API doesn't yet support `info` by
        # itemname. Fortunately, we have all the info we need at this
        # stage for the client to be satisfied.
        if ol_account.itemname:
            audit['has_ia'] = ol_account.itemname  # XXX should be email
            audit['link'] = ol_account.itemname
            return audit  # special case; unable to get ia acc

        # 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': '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')

    return audit
Beispiel #24
0
 def POST_join_waitinglist(self, edition, user):
     waitinglist.join_waitinglist(user.key, edition.key)
     stats.increment('ol.loans.joinWaitlist')
     raise web.redirect(edition.url())
Beispiel #25
0
    def POST(self, key):
        """Called when the user wants to borrow the edition"""

        i = web.input(action='borrow', format=None, ol_host=None)

        if i.ol_host:
            ol_host = i.ol_host
        else:
            ol_host = 'openlibrary.org'

        edition = web.ctx.site.get(key)
        if not edition:
            raise web.notfound()

        # Make a call to availability v2 update the subjects according
        # to result if `open`, redirect to bookreader
        response = lending.get_availability_of_ocaid(edition.ocaid)
        availability = response[edition.ocaid] if response else {}
        if availability and availability['status'] == 'open':
            raise web.seeother('https://archive.org/stream/' + edition.ocaid + '?ref=ol')

        error_redirect = ('https://archive.org/stream/' + edition.ocaid + '?ref=ol')
        user = accounts.get_current_user()

        if user:
            account = OpenLibraryAccount.get_by_email(user.email)
            ia_itemname = account.itemname if account else None
        if not user or not ia_itemname:
            web.setcookie(config.login_cookie_name, "", expires=-1)
            raise web.seeother("/account/login?redirect=%s/borrow?action=%s" % (
                edition.url(), i.action))

        action = i.action

        # Intercept a 'borrow' action if the user has already
        # borrowed the book and convert to a 'read' action.
        # Added so that direct bookreader links being routed through
        # here can use a single action of 'borrow', regardless of
        # whether the book has been checked out or not.
        if action == 'borrow' and user.has_borrowed(edition):
            action = 'read'

        if action == 'borrow':
            resource_type = i.format or 'bookreader'

            if resource_type not in ['epub', 'pdf', 'bookreader']:
                raise web.seeother(error_redirect)

            user_meets_borrow_criteria = user_can_borrow_edition(user, edition, resource_type)

            if user_meets_borrow_criteria:
                # This must be called before the loan is initiated,
                # otherwise the user's waitlist status will be cleared
                # upon loan creation
                track_loan = False if is_users_turn_to_borrow(user, edition) else True

                loan = lending.create_loan(
                    identifier=edition.ocaid,
                    resource_type=resource_type,
                    user_key=ia_itemname,
                    book_key=key)

                if loan:
                    loan_link = loan['loan_link']
                    if resource_type == 'bookreader':
                        if track_loan:
                            # As of 2017-12-14, Petabox will be
                            # responsible for tracking borrows which
                            # are the result of waitlist redemptions,
                            # so we don't want to track them here to
                            # avoid double accounting. When a reader
                            # is at the head of a waitlist and goes to
                            # claim their loan, Petabox now checks
                            # whether the waitlist was initiated from
                            # OL, and if it was, petabox tracks
                            # ol.loans.bookreader accordingly via
                            # lending.create_loan.
                            stats.increment('ol.loans.bookreader')

                        raise web.seeother(make_bookreader_auth_link(
                            loan.get_key(), edition.ocaid,
                            '/stream/' + edition.ocaid, ol_host,
                            ia_userid=ia_itemname))
                    elif resource_type == 'pdf':
                        stats.increment('ol.loans.pdf')
                        raise web.seeother(loan_link)
                    elif resource_type == 'epub':
                        stats.increment('ol.loans.epub')
                        raise web.seeother(loan_link)
                else:
                    raise web.seeother(error_redirect)
            else:
                raise web.seeother(error_redirect)

        elif action == 'return':
            # Check that this user has the loan
            user.update_loan_status()
            loans = get_loans(user)

            # We pick the first loan that the user has for this book that is returnable.
            # Assumes a user can't borrow multiple formats (resource_type) of the same book.
            user_loan = None
            for loan in loans:
                # Handle the case of multiple edition records for the same
                # ocaid and the user borrowed from one and returning from another
                has_loan = (loan['book'] == edition.key or loan['ocaid'] == edition.ocaid)
                if has_loan:
                    user_loan = loan
                    break

            if not user_loan:
                # $$$ add error message
                raise web.seeother(error_redirect)

            user_loan.return_loan()

            # Show the page with "you've returned this". Use a dummy slug.
            # $$$ this would do better in a session variable that can be cleared
            #     after the message is shown once
            raise web.seeother(edition.url())

        elif action == 'read':
            # Look for loans for this book
            user.update_loan_status()
            loans = get_loans(user)
            for loan in loans:
                if loan['book'] == edition.key:
                    raise web.seeother(make_bookreader_auth_link(
                        loan['_key'], edition.ocaid, '/stream/' + edition.ocaid,
                        ol_host, ia_userid=ia_itemname
                    ))
        elif action == 'join-waitinglist':
            return self.POST_join_waitinglist(edition, user)
        elif action == 'leave-waitinglist':
            return self.POST_leave_waitinglist(edition, user, i)

        # Action not recognized
        raise web.seeother(error_redirect)
Beispiel #26
0
def create_accounts(email, password, username="", test=False):
    """Retrieves the IA or OL account having correct email and password
    credentials
    """
    retries = 0 if test else 10
    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'}
Beispiel #27
0
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'}

                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'}
Beispiel #28
0
def audit_accounts(email,
                   password,
                   require_link=False,
                   s3_access_key=None,
                   s3_secret_key=None,
                   test=False):
    """Performs an audit of the IA or OL account having this email.

    The audit:
    - verifies the password is correct for this account
    - aborts if any sort of error (e.g. account blocked, unverified)
    - reports whether the account is linked (to a secondary account)
    - if unlinked, reports whether a secondary account exists w/
      matching email

    Args:
        email (unicode)
        password (unicode)
        require_link (bool) - if True, returns `accounts_not_connected`
                              if accounts are not linked
        test (bool) - not currently used; is there to allow testing in
                      the absence of archive.org dependency
    """
    from openlibrary.core import lending

    if s3_access_key and s3_secret_key:
        r = InternetArchiveAccount.s3auth(s3_access_key, s3_secret_key)
        if not r.get('authorized', False):
            return {'error': 'invalid_s3keys'}
        ia_login = "******"
        email = r['username']
    else:
        if not valid_email(email):
            return {'error': 'invalid_email'}
        ia_login = InternetArchiveAccount.authenticate(email, password)

    if any(ia_login == err for err in ['account_blocked', 'account_locked']):
        return {'error': 'account_locked'}

    if ia_login != "ok":
        # Prioritize returning other errors over `account_not_found`
        if ia_login != "account_not_found":
            return {'error': ia_login}
        return {'error': 'account_not_found'}

    else:
        ia_account = InternetArchiveAccount.get(email=email, test=test)

        # 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

        # The fact that there is no link implies no Open Library
        # account exists containing a link to this Internet Archive
        # account...
        if not link:
            # then check if there's an Open Library account which shares
            # the same email as this IA account.
            ol_account = OpenLibraryAccount.get(email=email, test=test)

            # If an Open Library account with a matching email account exist...
            if ol_account:
                # Check whether it is linked already, i.e. has an itemname
                # set. We already determined that no OL account is
                # linked to our IA account. Therefore this Open
                # Library account having the same email as our IA
                # account must have been linked to a different
                # Internet Archive account.
                if ol_account.itemname:
                    return {'error': 'wrong_ia_account'}

        # At this point, it must either be the case that (a)
        # `ol_account` already links to our IA account (in which case
        # `link` has a correct value), (b) that an unlinked
        # `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, assuming
        # lending.config_ia_auth_only is enabled, we need to create
        # and link it.
        if not ol_account:
            if not password:
                raise {'error': 'link_attempt_requires_password'}
            try:
                ol_account = OpenLibraryAccount.create(
                    ia_account.itemname,
                    email,
                    password,
                    displayname=ia_account.screenname,
                    verified=True,
                    retries=5,
                    test=test)
            except ValueError as e:
                return {'error': 'max_retries_exceeded'}

            ol_account.link(ia_account.itemname)
            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 not ol_account.itemname:
                ol_account.link(ia_account.itemname)
                stats.increment('ol.account.xauth.auto-linked')
            if not ol_account.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'}

    if require_link:
        ol_account = OpenLibraryAccount.get(link=ia_account.itemname,
                                            test=test)
        if ol_account and not ol_account.itemname:
            return {'error': 'accounts_not_connected'}

    # 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,
    }
Beispiel #29
0
    def POST(self, key):
        """Called when the user wants to borrow the edition"""

        i = web.input(action='borrow', format=None, ol_host=None, _autoReadAloud=None, q="")

        ol_host = i.ol_host or 'openlibrary.org'
        action = i.action
        edition = web.ctx.site.get(key)
        if not edition:
            raise web.notfound()

        archive_url = get_bookreader_stream_url(edition.ocaid) + '?ref=ol'
        if i._autoReadAloud is not None:
            archive_url += '&_autoReadAloud=show'

        if i.q:
            _q = urllib.parse.quote(i.q, safe='')
            archive_url += "#page/-/mode/2up/search/%s" % _q

        # Make a call to availability v2 update the subjects according
        # to result if `open`, redirect to bookreader
        response = lending.get_availability_of_ocaid(edition.ocaid)
        availability = response[edition.ocaid] if response else {}
        if availability and availability['status'] == 'open':
            raise web.seeother(archive_url)

        error_redirect = (archive_url)
        user = accounts.get_current_user()

        if user:
            account = OpenLibraryAccount.get_by_email(user.email)
            ia_itemname = account.itemname if account else None
            s3_keys = web.ctx.site.store.get(account._key).get('s3_keys')
        if not user or not ia_itemname or not s3_keys:
            web.setcookie(config.login_cookie_name, "", expires=-1)
            redirect_url = "/account/login?redirect=%s/borrow?action=%s" % (
                edition.url(), action)
            if i._autoReadAloud is not None:
                redirect_url += '&_autoReadAloud=' + i._autoReadAloud
            raise web.seeother(redirect_url)

        if action == 'return':
            lending.s3_loan_api(edition.ocaid, s3_keys, action='return_loan')
            stats.increment('ol.loans.return')
            raise web.seeother(edition.url())
        elif action == 'join-waitinglist':
            lending.s3_loan_api(edition.ocaid, s3_keys, action='join_waitlist')
            stats.increment('ol.loans.joinWaitlist')
            raise web.redirect(edition.url())
        elif action == 'leave-waitinglist':
            lending.s3_loan_api(edition.ocaid, s3_keys, action='leave_waitlist')
            stats.increment('ol.loans.leaveWaitlist')
            raise web.redirect(edition.url())

        # Intercept a 'borrow' action if the user has already
        # borrowed the book and convert to a 'read' action.
        # Added so that direct bookreader links being routed through
        # here can use a single action of 'borrow', regardless of
        # whether the book has been checked out or not.
        elif user.has_borrowed(edition):
            action = 'read'

        elif action in ('borrow', 'browse'):
            borrow_access = user_can_borrow_edition(user, edition)

            if not (s3_keys or borrow_access):
                raise web.seeother(error_redirect)

            lending.s3_loan_api(edition.ocaid, s3_keys, action='%s_book' % borrow_access)
            stats.increment('ol.loans.bookreader')
            stats.increment('ol.loans.%s' % borrow_access)
            action = 'read'

        if action == 'read':
            bookPath = '/stream/' + edition.ocaid
            if i._autoReadAloud is not None:
                bookPath += '?_autoReadAloud=show'

            # Look for loans for this book
            user.update_loan_status()
            loans = get_loans(user)
            for loan in loans:
                if loan['book'] == edition.key:
                    raise web.seeother(make_bookreader_auth_link(
                        loan['_key'], edition.ocaid, bookPath,
                        ol_host, ia_userid=ia_itemname
                    ))

        # Action not recognized
        raise web.seeother(error_redirect)