Browse Source

[2713] Add -u/--user and -p/--password arguments

But essentially, this is a full rewrite of the tool, since to handle it nicely I had to get rid of most of the functions and make a somewhat nicer class of it.

Also added a -q option while i was at it
Jelte Jansen 12 years ago
parent
commit
0da895c79d

+ 107 - 60
src/bin/usermgr/b10-cmdctl-usermgr.py.in

@@ -23,11 +23,8 @@ import random
 from hashlib import sha1
 import csv
 import getpass
-
-#remove
-import getopt
 from optparse import OptionParser, OptionValueError
-
+import os
 import sys; sys.path.append ('@@PYTHONPATH@@')
 import isc.util.process
 
@@ -36,78 +33,128 @@ isc.util.process.rename()
 VERSION_STRING = 'b10-cmdctl-usermgr @PACKAGE_VERSION@'
 DEFAULT_FILE = 'cmdctl-accounts.csv'
 
-def gen_password_hash(password):
-    salt = "".join(chr(random.randint(33, 127)) for x in range(64))
-    saltedpwd = sha1((password + salt).encode()).hexdigest()
-    return salt, saltedpwd
-
-def username_exist(name, filename):
-    # The file may doesn't exist.
-    exist = False
-    csvfile = None
-    try:
-        csvfile = open(filename)
-        reader = csv.reader(csvfile)
-        for row in reader:
-            if name == row[0]:
-                exist = True
-                break
-    except Exception:
-        pass
-
-    if csvfile:
+class UserManager:
+    def __init__(self, options):
+        self.options = options
+
+    def print(self, msg):
+        if not self.options.quiet:
+            print(msg)
+
+    def gen_password_hash(self, password):
+        salt = "".join(chr(random.randint(33, 127)) for x in range(64))
+        saltedpwd = sha1((password + salt).encode()).hexdigest()
+        return salt, saltedpwd
+
+    def username_exists(self, name):
+        if not os.path.exists(self.options.output_file):
+            return False
+
+        exist = False
+        csvfile = None
+        with open(self.options.output_file) as csvfile:
+            reader = csv.reader(csvfile)
+            for row in reader:
+                if name == row[0]:
+                    exist = True
+                    break
+        return exist
+
+    def save_userinfo(self, username, pw, salt, filename):
+        csvfile = open(filename, 'a')
+        writer = csv.writer(csvfile)
+        writer.writerow([username, pw, salt])
         csvfile.close()
-    return exist
-
-def save_userinfo(username, pw, salt, filename):
-    csvfile = open(filename, 'a')
-    writer = csv.writer(csvfile)
-    writer.writerow([username, pw, salt])
-    csvfile.close()
-    print("\n create new account successfully! \n")
-
-def set_options(parser):
-    parser.add_option("-f", "--file",
-                      dest="output_file", default=DEFAULT_FILE,
-                      help="Specify the file to append user name and password"
-                     )
-
-def main():
-    parser = OptionParser(version = VERSION_STRING)
-    set_options(parser)
-    (options, _) = parser.parse_args()
-
-    filename = options.output_file
-
-    try:
+        self.print("User added\n")
+
+    def add_user(self, name, password):
+        """
+        Add the given username/password combination to the file.
+        First checks if the username exists, and returns False if so.
+        If not, it is added, and this method returns True.
+        """
+        if self.username_exists(name):
+            return False
+        salt, pw = self.gen_password_hash(password)
+        self.save_userinfo(name, pw, salt, self.options.output_file)
+        return True
+
+    def interactive_mode(self):
         while True :
-            name = input("Desired Login Name:")
+            name = input("Username to add: ")
             if name == '':
-                print("error, user name can't be empty")
+                print("error, username can't be empty")
                 continue
 
-            if username_exist(name, filename):
-                 print("user name already exists!")
+            if self.username_exists(name):
+                 print("user already exists!")
                  continue
 
             while True:
-                pwd1 = getpass.getpass("Choose a password:")
-                pwd2 = getpass.getpass("Re-enter password:")
+                pwd1 = getpass.getpass("Choose a password: ")
+                pwd2 = getpass.getpass("Re-enter password: ")
                 if pwd1 != pwd2:
-                    print("password is not same, please input again")
+                    print("passwords do not match, try again")
                 else:
                     break;
 
-            salt, pw = gen_password_hash(pwd1)
-            save_userinfo(name, pw, salt, filename)
-            inputdata = input('continue to create new account by input \'y\' or \'Y\':')
+            if not self.add_user(name, pwd1):
+                self.print("Error: username exists")
+
+            inputdata = input('Add another user (y/n)? ')
             if inputdata not in ['y', 'Y']:
                 break
 
-    except KeyboardInterrupt:
-        pass
+    def run(self):
+        self.print("Using accounts file: " + self.options.output_file)
+        filename = self.options.output_file
+
+        if self.options.username is not None or\
+           self.options.password is not None:
+            if self.options.username is None:
+                self.print("Error: password but no username given")
+                return 1
+            if self.options.password is None:
+                self.print("Error: username but no password given")
+                return 1
+            if not self.add_user(self.options.username,
+                                 self.options.password):
+                self.print("Error: username exists")
+        else:
+            try:
+                self.interactive_mode()
+            except KeyboardInterrupt:
+                pass
+        return 0
+
+def set_options(parser):
+    parser.add_option("-f", "--file",
+                      dest="output_file", default=DEFAULT_FILE,
+                      help="Specify the file to append user name and password"
+                     )
+    parser.add_option("-u", "--username",
+                      dest="username", default=None,
+                      help="Specify username to add"
+                     )
+    parser.add_option("-p", "--password",
+                      dest="password", default=None,
+                      help="Specify password to add"
+                     )
+    parser.add_option("-q", "--quiet",
+                      dest="quiet", action="store_true", default=False,
+                      help="Quiet mode, don't print any output"
+                     )
+
+def main():
+    parser = OptionParser(version = VERSION_STRING)
+    set_options(parser)
+    (options, _) = parser.parse_args()
+
+    usermgr = UserManager(options)
+    return usermgr.run()
 
 
 if __name__ == '__main__':
-    main()
+    result = main()
+    sys.exit(result)
 

+ 77 - 8
src/bin/usermgr/tests/b10-cmdctl-usermgr_test.py

@@ -13,10 +13,12 @@
 # NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION
 # WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
 
+import csv
+from hashlib import sha1
+import imp
 import os
-import unittest
 import subprocess
-import imp
+import unittest
 
 def run(command):
     """
@@ -44,9 +46,28 @@ class TestUserMgr(unittest.TestCase):
 
     def check_output_file(self, expected_content):
         self.assertTrue(os.path.exists(self.OUTPUT_FILE))
-        with open(self.OUTPUT_FILE, 'r') as f:
-            content = f.readlines()
-        self.assertEqual(expected_content, content)
+
+        csv_entries = []
+        with open(self.OUTPUT_FILE) as csvfile:
+            reader = csv.reader(csvfile)
+            for row in reader:
+                csv_entries.append(row)
+
+        self.assertEqual(len(expected_content), len(csv_entries))
+        csv_entries.reverse()
+        for expected_entry in expected_content:
+            expected_name = expected_entry[0]
+            expected_pass = expected_entry[1]
+
+            csv_entry = csv_entries.pop()
+            entry_name = csv_entry[0]
+            entry_salt = csv_entry[2]
+            entry_hash = csv_entry[1]
+
+            self.assertEqual(expected_name, entry_name)
+            expected_hash =\
+                sha1((expected_pass + entry_salt).encode()).hexdigest()
+            self.assertEqual(expected_hash, entry_hash)
 
     def run_check(self, expected_returncode, expected_stdout, expected_stderr, command):
         """
@@ -59,11 +80,11 @@ class TestUserMgr(unittest.TestCase):
                          May be None, in which case the check is skipped.
         """
         (returncode, stdout, stderr) = run(command)
-        self.assertEqual(expected_returncode, returncode, " ".join(command))
-        if expected_stdout is not None:
-            self.assertEqual(expected_stdout, stdout.decode())
         if expected_stderr is not None:
             self.assertEqual(expected_stderr, stderr.decode())
+        if expected_stdout is not None:
+            self.assertEqual(expected_stdout, stdout.decode())
+        self.assertEqual(expected_returncode, returncode, " ".join(command))
 
     def test_help(self):
         self.run_check(0,
@@ -74,10 +95,58 @@ Options:
   -h, --help            show this help message and exit
   -f OUTPUT_FILE, --file=OUTPUT_FILE
                         Specify the file to append user name and password
+  -u USERNAME, --username=USERNAME
+                        Specify username to add
+  -p PASSWORD, --password=PASSWORD
+                        Specify password to add
+  -q, --quiet           Quiet mode, don't print any output
 ''',
                        '',
                        [self.TOOL, '-h'])
 
+    def test_create_users(self):
+        """
+        Test that a file is created, and users are added.
+        Also tests quiet mode for adding a user to an existing file.
+        """
+        # content is a list of (user, pass) tuples
+        expected_content = []
+
+        # Creating a file
+        self.run_check(0,
+                       'Using accounts file: test_users.csv\n'
+                       'User added\n\n',
+                       '',
+                       [ self.TOOL,
+                         '-f', self.OUTPUT_FILE,
+                         '-u', 'user1',
+                         '-p', 'pass1' ])
+        expected_content.append(('user1', 'pass1'))
+        self.check_output_file(expected_content)
+
+        # Add to existing file
+        self.run_check(0,
+                       'Using accounts file: test_users.csv\n'
+                       'User added\n\n',
+                       '',
+                       [ self.TOOL,
+                         '-f', self.OUTPUT_FILE,
+                         '-u', 'user2',
+                         '-p', 'pass2' ])
+        expected_content.append(('user2', 'pass2'))
+        self.check_output_file(expected_content)
+
+        # Quiet mode
+        self.run_check(0,
+                       '',
+                       '',
+                       [ self.TOOL, '-q',
+                         '-f', self.OUTPUT_FILE,
+                         '-u', 'user3',
+                         '-p', 'pass3' ])
+        expected_content.append(('user3', 'pass3'))
+        self.check_output_file(expected_content)
+
     def test_default_file(self):
         """
         Check the default file is the correct one.