Skip to content

Commit 4ac98bf

Browse files
committed
Rename "account" to "sql-remctl"
The program that started its life out as "account" has grown to be much larger, and can take on the identity of password, and soon database.
1 parent 2a67f03 commit 4ac98bf

File tree

3 files changed

+188
-187
lines changed

3 files changed

+188
-187
lines changed

account

-186
This file was deleted.

account

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
sql-remctl

password

+1-1
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
account
1+
sql-remctl

sql-remctl

+186
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,186 @@
1+
#!/usr/bin/env python
2+
"""
3+
sql.mit.edu account management system
4+
5+
This module contains convenience methods for user account
6+
manipulation, including but not limited account creation, and password
7+
generation.
8+
"""
9+
10+
import random
11+
import string
12+
import sys
13+
import os
14+
import json
15+
16+
from sqlalchemy.orm.exc import MultipleResultsFound, NoResultFound
17+
from sqlalchemy.exc import IntegrityError
18+
19+
import database
20+
21+
def format_response(inp):
22+
"""
23+
Format the dictionary representing the response into a valid,
24+
machine-readable format. This includes injecting a "status" field.
25+
Currently, the machine-readable format is json, but this may
26+
change in the future.
27+
"""
28+
inp['status'] = 0
29+
if 'error' in inp:
30+
inp['status'] = 1
31+
return json.dumps(inp)
32+
33+
def generate_password(length=10):
34+
"""
35+
Generate a random password of the specified length (10 by default)
36+
using the ascii letters, digits, and some special characters.
37+
"""
38+
available_chars = string.ascii_letters + string.digits + '!@#$%^&*()'
39+
return ''.join([random.SystemRandom().choice(available_chars) for _ in xrange(length)])
40+
41+
def ensure_authorized(original_function):
42+
def ensure_inner(*args, **kwargs):
43+
username, target = args[0], args[1]
44+
if not is_authorized(username, target):
45+
raise Exception("User '%s' not authorized for '%s'" %
46+
(username, target))
47+
return original_function(*args, **kwargs)
48+
return ensure_inner
49+
50+
def get_user(s, target):
51+
try:
52+
user = s.query(database.User).filter_by(Username=target).one()
53+
except MultipleResultsFound, e:
54+
raise Exception('Fatal error: username uniqueness constraint was violated.')
55+
except NoResultFound, e:
56+
raise Excepton("User '%s' is not signed up for a sql account" % (target,))
57+
return user
58+
59+
@ensure_authorized
60+
def account_create(username, target, args):
61+
"""
62+
Create the specified target account, if the username (originator)
63+
is authorized.
64+
"""
65+
s = database.get_session()
66+
password = generate_password()
67+
user = database.User(target, password, 'Tester created by achernya',
68+
69+
s.add(user)
70+
s.add(database.UserQuota(user))
71+
s.add(database.UserStat(user))
72+
try:
73+
s.commit()
74+
except IntegrityError, e:
75+
return {'error': "User '%s' already has a sql account!" % (target,)}
76+
77+
result = s.execute(
78+
"CREATE USER :user@'%' IDENTIFIED BY :pass",
79+
{'user': target, 'pass': password}
80+
)
81+
82+
return {'password': password}
83+
84+
@ensure_authorized
85+
def account_delete(username, target, args):
86+
s = database.get_session()
87+
user = get_user(s, target)
88+
s.delete(user)
89+
s.commit()
90+
91+
result = s.execute(
92+
"DROP USER :user@'%'",
93+
{'user': target}
94+
)
95+
96+
return {}
97+
98+
def whoami(*args):
99+
kerberos_name = os.environ['REMOTE_USER']
100+
username, _ = string.split(kerberos_name, '@', 2)
101+
s = database.get_session()
102+
exists = True
103+
try:
104+
s.query(database.User).filter_by(Username=username).one()
105+
except:
106+
exists = False
107+
return {'krb5_princ': kerberos_name, 'username': username, 'exists': exists}
108+
109+
def is_auth(username, target, args):
110+
return {'result': is_authorized(username, target)}
111+
112+
@ensure_authorized
113+
def password_set(username, target, args):
114+
s = database.get_session()
115+
user = get_user(s, target)
116+
new_password = args[0]
117+
user.set_password(new_password)
118+
s.commit()
119+
result = s.execute(
120+
"SET PASSWORD FOR :user@'%' = PASSWORD(:pass)",
121+
{'user': target, 'pass': new_password})
122+
return {}
123+
124+
def password_set_random(username, target, args):
125+
new_password = generate_password()
126+
password_set(username, target, [new_password])
127+
return {'password': new_password}
128+
129+
def is_authorized(username, target):
130+
# THE RULES:
131+
# -- a user is authorized for itself
132+
# -- a user is authorized on lockers they have an 'a' bit on
133+
# -- the sql maintainer team is authorized on all queries
134+
# -- all else is unauthorized
135+
if username == target:
136+
return True
137+
if target == 'tester-achernya':
138+
return True
139+
return False
140+
141+
def main():
142+
# Figure out which function we are supposed to run
143+
argv = sys.argv
144+
argc = len(argv)
145+
if argc == 1:
146+
print format_response(
147+
{'error': 'No operation specified. Try `remctl sql help`.'})
148+
sys.exit(1)
149+
base = os.path.basename(argv[0])
150+
mode = argv[1]
151+
account = {'create': account_create,
152+
'delete': account_delete,
153+
'whoami': whoami,
154+
'is-auth': is_auth,
155+
}
156+
password = {'set': password_set,
157+
'generate': password_set_random,
158+
}
159+
ops = {'account': account, 'password': password}
160+
op = ops.get(base, {}).get(mode, None)
161+
if op == None:
162+
print format_response(
163+
{'error': "Operation '%s %s' not known. Try `remctl sql help`."
164+
% (base, mode,)})
165+
sys.exit(1)
166+
# Now, figure out what the target locker is. It's possible there
167+
# isn't one, in which case we use the username as the sole argument
168+
username = whoami()['username']
169+
target = None
170+
args = None
171+
# Horrible special case: we don't actually want whoami to take a target, so append ''
172+
if base == 'account' and mode == 'whoami':
173+
argv += ['']
174+
try:
175+
target, args = argv[2], argv[3:]
176+
except:
177+
print format_response({'error': 'Insufficient arguments specified'})
178+
sys.exit(1)
179+
try:
180+
print format_response(op(username, target, args))
181+
except Exception as e:
182+
print format_response({'error': str(e)})
183+
sys.exit(1)
184+
185+
if __name__ == '__main__':
186+
main()

0 commit comments

Comments
 (0)