Passed
Push — master ( 53016e...38fde4 )
by Oleksandr
02:52
created

tabpy.utils.tabpy_user   A

Complexity

Total Complexity 16

Size/Duplication

Total Lines 135
Duplicated Lines 0 %

Importance

Changes 0
Metric Value
wmc 16
eloc 73
dl 0
loc 135
rs 10
c 0
b 0
f 0

6 Functions

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