SO #57225262—Inputting list of passwords in a class

Stack Overflow question ‘Inputting list of passwords in a class’ is more interesting as an object-oriented design (OOD) exercise than as a debugging exercise, but it also provides an opportunity to practise some Python programming.

The requirements identified in the problem are:

  • represent a set of credentials comprised of a username and a password;
  • represent a user account made up of an identifier and one active set of credentials and zero or more inactive credentials;
  • store user accounts that can be retrieved subsequently by username;
  • validate a given set of credentials, using the stored user accounts.

Additionally, a user account has the following constraints:

  • It must be uniquely identifiable by the username of the active set of credentials.
  • A set of credentials must be used only once for a given user account.

The design model consists of multiple classes, each addressing a single responsibility as follows.

Class Responsibility
Credentials represent a set of credentials
UserAccount represent a user account
AccountStore store user accounts, enforcing the uniqueness constraint
Authentication validate supplied credentials

The program code implements the model very faithfully and uses the same class names, thus making it easy to reason about. It has been tested with Python 2.7.15rc1 and has been checked for PEP8 compliance.

NOTES

For brevity, the solution presented here does not:

  • implement best practices used for security in real-world scenarios, such as salting and hashing;
  • enforce uniqueness of user account identifiers;
  • consider performance factors.

In lieu of conventional unit tests, a main routine exercises use-cases to ensure that the program works properly.

class Credentials:

    def __init__(self, username, password):
        self.username = username
        self.password = password

    def has_username(self, username):
        return self.username == username

    def matches(self, credentials):
        return self.username == credentials.username and \
            self.password == credentials.password

class UserAccount:

    def __init__(self, user_id):
        self.user_id = user_id
        self.active_credentials = None
        self.past_credentials = []

    def add(self, credentials):
        self._check_uniqueness(credentials)
        if self.active_credentials is None:
            self.active_credentials = credentials
        else:
            self.past_credentials.append(self.active_credentials)
            self.active_credentials = credentials

    def has_username(self, username):
        return self.active_credentials.has_username(username)

    def has_same_username(self, user_account):
        return self.has_username(user_account.active_credentials.username)

    def has_credentials(self, credentials):
        return self.active_credentials is not None and \
            self.active_credentials.matches(credentials)

    def _check_uniqueness(self, credentials):
        if self.has_credentials(credentials):
            raise Exception('These credentials are currently in use.')
        for c in self.past_credentials:
            if c.matches(credentials):
                raise Exception(
                        'These credentials have been used in the past.')

class AccountStore:

    def __init__(self):
        self.user_accounts = []

    def add(self, user_account):
        self._check_uniqueness(user_account)
        self.user_accounts.append(user_account)

    def find_by_username(self, username):
        for ua in self.user_accounts:
            if ua.has_username(username):
                return ua
        return None

    def _check_uniqueness(self, user_account):
        for ua in self.user_accounts:
            if ua.has_same_username(user_account):
                raise Exception(
                        'An account with the same username is already active.')

class Authentication:

    def __init__(self, account_store):
        self.account_store = account_store

    def validate(self, credentials):
        user_account = self.account_store.find_by_username(
                credentials.username)
        if user_account is None:
            return False
        return user_account.has_credentials(credentials)

if __name__ == '__main__':
    credentials = Credentials('user1', 'password1')
    user_account = UserAccount(101)
    user_account.add(credentials)

    account_store = AccountStore()
    account_store.add(user_account)

    user_account1 = account_store.find_by_username('user1')
    print 'user_account1', user_account1

    user_account2 = account_store.find_by_username('user2')
    print 'user_account2', user_account2

    authentication = Authentication(account_store)
    print 'Expecting True...', authentication.validate(
            Credentials('user1', 'password1'))
    print 'Expecting False...', authentication.validate(
            Credentials('user2', 'password1'))
    print 'Expecting False...', authentication.validate(
            Credentials('user1', 'password2'))

    user_account.add(Credentials('user1', 'password2'))
    print 'Expecting True...', authentication.validate(
            Credentials('user1', 'password2'))
    print 'Expecting False...', authentication.validate(
            Credentials('user1', 'password1'))

    try:
        user_account.add(Credentials('user1', 'password1'))
    except Exception:
        print 'Expecting exception... Pass'

    try:
        user_account.add(Credentials('user2', 'password1'))
        print 'Not expecting exception... Pass'
        print 'Expecting True...', authentication.validate(
                Credentials('user2', 'password1'))
    except Exception:
        print 'Not expecting exception... Fail'

    try:
        user_account1 = UserAccount(102)
        user_account1.add(Credentials('user1', 'whatever'))
        account_store.add(user_account1)
        print 'Expecting True...', authentication.validate(
                Credentials('user1', 'whatever'))
    except Exception:
        print 'Not expecting exception... Fail'

    try:
        user_account2 = UserAccount(103)
        user_account2.add(Credentials('user1', 'whatever'))
        account_store.add(user_account1)
        print 'Expecting exception... Fail'
    except Exception:
        print 'Expecting exception... Pass'


The output of the program is:

EY@LENNY:~/Source/junk/python/pwman$ python all.py
user_account1 <__main__.UserAccount instance at 0x7faa36f11170>
user_account2 None
Expecting True... True
Expecting False... False
Expecting False... False
Expecting True... True
Expecting False... False
Expecting exception... Pass
Not expecting exception... Pass
Expecting True... True
Expecting True... True
Expecting exception... Pass
EY@LENNY:~/Source/junk/python/pwman$

Leave a comment

Leave a Reply

This site uses Akismet to reduce spam. Learn how your comment data is processed.