Пример #1
0
class MockMilleniumPatronAPI(MilleniumPatronAPI):

    """This mocks the API on a higher level than the HTTP level.

    It is not used in the tests of the MilleniumPatronAPI class.  It
    is used in the Adobe Vendor ID tests but maybe it shouldn't.
    """

    # This user's card has expired.
    user1 = PatronData(
        permanent_id="12345",
        authorization_identifier="0",
        username="******",
        authorization_expires = datetime.datetime(2015, 4, 1)
    )
    
    # This user's card still has ten days on it.
    the_future = datetime.datetime.utcnow() + datetime.timedelta(days=10)
    user2 = PatronData(
        permanent_id="67890",
        authorization_identifier="5",
        username="******",
        authorization_expires = the_future,
    )

    users = [user1, user2]

    def __init__(self):
        pass

    def remote_authenticate(self, barcode, pin):
        """A barcode that's 14 digits long is treated as valid,
        no matter which PIN is used.

        That's so real barcode/PIN combos can be passed through to
        third parties.

        Otherwise, valid test PIN is the first character of the barcode
        repeated four times.

        """
        u = self.dump(barcode)
        if 'ERRNUM' in u:
            return False
        return len(barcode) == 14 or pin == barcode[0] * 4

    def remote_patron_lookup(self, patron_or_patrondata):
        # We have a couple custom barcodes.
        look_for = patron_or_patrondata.authorization_identifier
        for u in self.users:
            if u.authorization_identifier == look_for:
                return u
        return None
Пример #2
0
    def remote_authenticate(self, username, password):
        """Does the Millenium Patron API approve of these credentials?

        :return: False if the credentials are invalid. If they are
        valid, a PatronData that serves only to indicate which
        authorization identifier the patron prefers.
        """
        if self.auth_mode == self.PIN_AUTHENTICATION_MODE:
            path = "%(barcode)s/%(pin)s/pintest" % dict(
                barcode=username, pin=password
            )
            url = self.root + path
            response = self.request(url)
            data = dict(self._extract_text_nodes(response.content))
            if data.get('RETCOD') == '0':
                return PatronData(authorization_identifier=username, complete=False)
            return False
        elif self.auth_mode == self.FAMILY_NAME_AUTHENTICATION_MODE:

            patrondata = self._remote_patron_lookup(username)
            if not patrondata:
                # The patron doesn't even exist.
                return False

            # The patron exists; but do the last names match?
            if self.family_name_match(patrondata.personal_name, password):
                # Since this is a complete PatronData, we'll be able
                # to update their account without making a separate
                # call to /dump.
                return patrondata
        return False
Пример #3
0
    def remote_authenticate(self, username, password):
        if not username or not password:
            return None

        now = datetime.datetime.utcnow()
        one_day = datetime.timedelta(days=1)

        patrondata = PatronData(authorization_identifier=username,
                                permanent_id=username + "_id",
                                username=username + "_username")
        if self.valid_patron(username, password, self.patrons):
            # The patron's authorization expires tomorrow.
            patrondata.authorization_expires = now + one_day
        elif self.valid_patron(username, password, self.expired_patrons):
            # The patron's authorization expired yesterday.
            patrondata.authorization_expires = now - one_day
        elif self.valid_patron(username, password, self.patrons_with_fines):
            # The patron has racked up huge fines.
            patrondata.fines = Decimal(12345678.90)
        else:
            return None
        return patrondata
Пример #4
0
    def remote_authenticate(self, username, password):
        # All FirstBook credentials are in upper-case.
        username = username.upper()

        # If they fail a PIN test, there is no authenticated patron.
        if not self.remote_pin_test(username, password):
            return None

        # FirstBook keeps track of absolutely no information
        # about the patron other than the permanent ID,
        # which is also the authorization identifier.
        return PatronData(
            permanent_id=username,
            authorization_identifier=username,
        )
    def remote_authenticate(self, username, password):
        """Does the Millenium Patron API approve of these credentials?

        :return: False if the credentials are invalid. If they are
        valid, a PatronData that serves only to indicate which
        authorization identifier the patron prefers.
        """
        path = "%(barcode)s/%(pin)s/pintest" % dict(barcode=username,
                                                    pin=password)
        url = self.root + path
        response = self.request(url)
        data = dict(self._extract_text_nodes(response.content))
        if data.get('RETCOD') == '0':
            return PatronData(authorization_identifier=username,
                              complete=False)
        return False
Пример #6
0
 def remote_authenticate(self, username, password):
     # Create XML doc for request
     authorization_request = self.create_authorize_request(username, password)
     # Post request to the server
     response = self.post_request(authorization_request)
     # Parse response from server
     authorized, patron_name, library_identifier = self.parse_authorize_response(response.content)
     if not authorized:
         return False
     # Kansas auth gives very little data about the patron. Only name and a library identifier.
     return PatronData(
         permanent_id=username,
         authorization_identifier=username,
         personal_name=patron_name,
         library_identifier=library_identifier,
         complete=True
     )
Пример #7
0
    def remote_authenticate(self, username, password):
        "Fake 'remote' authentication."
        if not username or not password:
            return None

        if not self.valid_patron(username, password):
            return None

        username = self.test_identifier
        patrondata = PatronData(
            authorization_identifier=username,
            permanent_id=username + "_id",
            username=username + "_username",
            authorization_expires=None,
            fines=None,
        )
        return patrondata
Пример #8
0
    def generate_patrondata(cls, authorization_identifier):

        if authorization_identifier.endswith("_username"):
            username = authorization_identifier
            identifier = authorization_identifier[:-9]
        else:
            identifier = authorization_identifier
            username = authorization_identifier + "_username"

        personal_name = "PersonalName" + identifier

        patrondata = PatronData(
            authorization_identifier=identifier,
            permanent_id=identifier + "_id",
            username=username,
            personal_name=personal_name,
            authorization_expires=None,
            fines=None,
        )
        return patrondata
    def remote_authenticate(self, username, password):
        "Fake 'remote' authentication."
        if not username or (self.collects_password and not password):
            return None

        if not self.valid_patron(username, password):
            return None

        if username.endswith("_username"):
            username = username
            identifier = username[:-9]
        else:
            identifier = username
            username = identifier + "_username"

        patrondata = PatronData(
            authorization_identifier=identifier,
            permanent_id=identifier + "_id",
            username=username,
            authorization_expires=None,
            fines=None,
        )
        return patrondata
Пример #10
0
    def patron_dump_to_patrondata(self, current_identifier, content):
        """Convert an HTML patron dump to a PatronData object.

        :param current_identifier: Either the authorization identifier
        the patron just logged in with, or the one currently
        associated with their Patron record. Keeping track of this
        ensures we don't change a patron's preferred authorization
        identifier out from under them.

        :param content: The HTML document containing the patron dump.
        """
        # If we don't see these fields, erase any previous value
        # rather than leaving the old value in place. This shouldn't
        # happen (unless the expiration date changes to an invalid
        # date), but just to be safe.
        permanent_id = PatronData.NO_VALUE
        username = authorization_expires = personal_name = PatronData.NO_VALUE
        email_address = fines = external_type = PatronData.NO_VALUE
        block_reason = PatronData.NO_VALUE

        potential_identifiers = []
        for k, v in self._extract_text_nodes(content):
            if k == self.BARCODE_FIELD:
                if any(x.search(v) for x in self.blacklist):
                    # This barcode contains a blacklisted
                    # string. Ignore it, even if this means the patron
                    # ends up with no barcode whatsoever.
                    continue
                # We'll figure out which barcode is the 'right' one
                # later.
                potential_identifiers.append(v)
                # The millenium API doesn't care about spaces, so we add
                # a version of the barcode without spaces to our identifers
                # list as well.
                if " " in v:
                    potential_identifiers.append(v.replace(" ", ""))
            elif k == self.RECORD_NUMBER_FIELD:
                permanent_id = v
            elif k == self.USERNAME_FIELD:
                username = v
            elif k == self.PERSONAL_NAME_FIELD:
                personal_name = v
            elif k == self.EMAIL_ADDRESS_FIELD:
                email_address = v
            elif k == self.FINES_FIELD:
                try:
                    fines = MoneyUtility.parse(v)
                except ValueError:
                    self.log.warn(
                        'Malformed fine amount for patron: "%s". Treating as no fines.'
                    )
                    fines = Money("0", "USD")
            elif k == self.BLOCK_FIELD:
                block_reason = self._patron_block_reason(self.block_types, v)
            elif k == self.EXPIRATION_FIELD:
                try:
                    expires = datetime.datetime.strptime(
                        v, self.EXPIRATION_DATE_FORMAT).date()
                    authorization_expires = expires
                except ValueError:
                    self.log.warn(
                        'Malformed expiration date for patron: "%s". Treating as unexpirable.',
                        v)
            elif k == self.PATRON_TYPE_FIELD:
                external_type = v
            elif k == self.ERROR_MESSAGE_FIELD:
                # An error has occured. Most likely the patron lookup
                # failed.
                return None

        # Set the library identifier field
        library_identifier = None
        for k, v in self._extract_text_nodes(content):
            if k == self.library_identifier_field:
                library_identifier = v.strip()

        # We may now have multiple authorization
        # identifiers. PatronData expects the best authorization
        # identifier to show up first in the list.
        #
        # The last identifier in the list is probably the most recently
        # added one. In the absence of any other information, it's the
        # one we should choose.
        potential_identifiers.reverse()

        authorization_identifiers = potential_identifiers
        if not authorization_identifiers:
            authorization_identifiers = PatronData.NO_VALUE
        elif current_identifier in authorization_identifiers:
            # Don't rock the boat. The patron is used to using this
            # identifier and there's no need to change it. Move the
            # currently used identifier to the front of the list.
            authorization_identifiers.remove(current_identifier)
            authorization_identifiers.insert(0, current_identifier)

        data = PatronData(permanent_id=permanent_id,
                          authorization_identifier=authorization_identifiers,
                          username=username,
                          personal_name=personal_name,
                          email_address=email_address,
                          authorization_expires=authorization_expires,
                          external_type=external_type,
                          fines=fines,
                          block_reason=block_reason,
                          library_identifier=library_identifier,
                          complete=True)
        return data