Passed
Push — master ( 227024...1d0698 )
by Oleksandr
02:44
created

tabpy.utils.user_management.process_command()   A

Complexity

Conditions 3

Size

Total Lines 8
Code Lines 7

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
eloc 7
dl 0
loc 8
rs 10
c 0
b 0
f 0
cc 3
nop 2
1
'''
2
Utility for managing user names and passwords for TabPy.
3
'''
4
5
from argparse import ArgumentParser
6
import logging
7
import os
8
import secrets
9
import sys
10
from tabpy.tabpy_server.app.util import parse_pwd_file
11
from tabpy.tabpy_server.handlers.util import hash_password
12
13
logger = logging.getLogger(__name__)
14
15
16
def build_cli_parser():
17
    parser = ArgumentParser(
18
        description=__doc__,
19
        epilog='''
20
            For more information about how to configure and
21
            use authentication for TabPy read the documentation
22
            at https://github.com/tableau/TabPy
23
            ''',
24
        argument_default=None,
25
        add_help=True)
26
    parser.add_argument(
27
        'command',
28
        choices=['add', 'update'],
29
        help='Command to execute')
30
    parser.add_argument(
31
        '-u',
32
        '--username',
33
        help='Username to add to passwords file')
34
    parser.add_argument(
35
        '-f',
36
        '--pwdfile',
37
        help='Fully qualified path to passwords file')
38
    parser.add_argument(
39
        '-p',
40
        '--password',
41
        help=('Password for the username. If not specified a password will '
42
              'be generated'))
43
    return parser
44
45
46
def check_args(args):
47
    if (args.username is None) or (args.pwdfile is None):
48
        return False
49
50
    return True
51
52
53
def generate_password(pwd_len=16):
54
    # List of characters to generate password from.
55
    # We want to avoid to use similarly looking pairs like
56
    # (O, 0), (1, l), etc.
57
    lower_case_letters = 'abcdefghijkmnpqrstuvwxyz'
58
    upper_case_letters = 'ABCDEFGHIJKLMPQRSTUVWXYZ'
59
    digits = '23456789'
60
61
    # and for punctuation we want to exclude some characters
62
    # like inverted comma which can be hard to find and/or
63
    # type
64
    # change this string if you are supporting an
65
    # international keyboard with differing keys available
66
    punctuation = '!#$%&()*+,-./:;<=>?@[\\]^_{|}~'
67
68
    # we also want to try to have more letters and digits in
69
    # generated password than punctuation marks
70
    password_chars =\
71
        lower_case_letters + lower_case_letters +\
72
        upper_case_letters + upper_case_letters +\
73
        digits + digits +\
74
        punctuation
75
    pwd = ''.join(secrets.choice(password_chars) for i in range(pwd_len))
76
    logger.info(f'Generated password: "{pwd}"')
77
    return pwd
78
79
80
def store_passwords_file(pwdfile, credentials):
81
    with open(pwdfile, 'wt') as f:
82
        for username, pwd in credentials.items():
83
            f.write(f'{username} {pwd}\n')
84
    return True
85
86
87
def add_user(args, credentials):
88
    username = args.username.lower()
89
    logger.info(f'Adding username "{username}"')
90
91
    if username in credentials:
92
        logger.error(f'Can\'t add username {username} as it is already present'
93
                     ' in passwords file. Do you want to run the '
94
                     '"update" command instead?')
95
        return False
96
97
    password = args.password
98
    logger.info(f'Adding username "{username}" with password "{password}"...')
99
    credentials[username] = hash_password(username, password)
100
101
    if(store_passwords_file(args.pwdfile, credentials)):
102
        logger.info(f'Added username "{username}" with password "{password}"')
103
    else:
104
        logger.info(
105
            f'Could not add username "{username}" , '
106
            f'password "{password}" to file')
107
108
109
def update_user(args, credentials):
110
    username = args.username.lower()
111
    logger.info(f'Updating username "{username}"')
112
113
    if username not in credentials:
114
        logger.error(f'Username "{username}" not found in passwords file. '
115
                     'Do you want to run "add" command instead?')
116
        return False
117
118
    password = args.password
119
    logger.info(f'Updating username "{username}" password  to "{password}"')
120
    credentials[username] = hash_password(username, password)
121
    return store_passwords_file(args.pwdfile, credentials)
122
123
124
def process_command(args, credentials):
125
    if args.command == 'add':
126
        return add_user(args, credentials)
127
    elif args.command == 'update':
128
        return update_user(args, credentials)
129
    else:
130
        logger.error(f'Unknown command "{args.command}"')
131
        return False
132
133
134
def main():
135
    logging.basicConfig(level=logging.DEBUG, format="%(message)s")
136
137
    parser = build_cli_parser()
138
    args = parser.parse_args()
139
    if not check_args(args):
140
        parser.print_help()
141
        return
142
143
    succeeded, credentials = parse_pwd_file(args.pwdfile)
144
    if not succeeded and args.command != 'add':
145
        return
146
147
    if args.password is None:
148
        args.password = generate_password()
149
150
    process_command(args, credentials)
151
    return
152
153
154
if __name__ == '__main__':
155
    main()
156