Eliminate a lot of the if cases in create_user by separating them out. Add a limit to the number of users which can be created based on the license. Add support for creating and loading licenses.

This commit is contained in:
Jake Moshenko 2014-05-28 13:51:52 -04:00
parent 0ef1902957
commit 33b43b75c0
9 changed files with 103 additions and 26 deletions

View file

@ -47,6 +47,7 @@ ADD templates templates
ADD util util ADD util util
ADD workers workers ADD workers workers
ADD license.pyc license.pyc
ADD app.py app.py ADD app.py app.py
ADD application.py application.py ADD application.py application.py
ADD config.py config.py ADD config.py config.py

9
app.py
View file

@ -19,9 +19,12 @@ from util.queuemetrics import QueueMetrics
from data.billing import Billing from data.billing import Billing
from data.buildlogs import BuildLogs from data.buildlogs import BuildLogs
from data.queue import WorkQueue from data.queue import WorkQueue
from license import load_license
from datetime import datetime
OVERRIDE_CONFIG_FILENAME = 'conf/stack/config.py' OVERRIDE_CONFIG_FILENAME = 'conf/stack/config.py'
LICENSE_FILENAME = 'conf/stack/license.enc'
app = Flask(__name__) app = Flask(__name__)
@ -41,6 +44,12 @@ else:
logger.debug('Applying config file: %s', OVERRIDE_CONFIG_FILENAME) logger.debug('Applying config file: %s', OVERRIDE_CONFIG_FILENAME)
app.config.from_pyfile(OVERRIDE_CONFIG_FILENAME) app.config.from_pyfile(OVERRIDE_CONFIG_FILENAME)
logger.debug('Applying license config from: %s', LICENSE_FILENAME)
app.config.update(load_license(LICENSE_FILENAME))
if app.config.get('LICENSE_EXPIRATION', datetime.min) < datetime.utcnow():
raise RuntimeError('License has expired, please contact support@quay.io')
features.import_features(app.config) features.import_features(app.config)
Principal(app, use_sessions=False) Principal(app, use_sessions=False)

View file

@ -64,7 +64,33 @@ class InvalidBuildTriggerException(DataModelException):
pass pass
def create_user(username, password, email, add_change_pw_notification=True): class TooManyUsersException(DataModelException):
pass
def is_create_user_allowed():
return get_active_user_count() < config.app_config['LICENSE_USER_LIMIT']
def create_user(username, password, email):
""" Creates a regular user, if allowed. """
if not validate_password(password):
raise InvalidPasswordException(INVALID_PASSWORD_MESSAGE)
if not is_create_user_allowed():
raise TooManyUsersException()
created = _create_user(username, email)
# Store the password hash
pw_hash = bcrypt.hashpw(password, bcrypt.gensalt())
created.password_hash = pw_hash
created.save()
return created
def _create_user(username, email):
if not validate_email(email): if not validate_email(email):
raise InvalidEmailAddressException('Invalid email address: %s' % email) raise InvalidEmailAddressException('Invalid email address: %s' % email)
@ -72,10 +98,6 @@ def create_user(username, password, email, add_change_pw_notification=True):
if not username_valid: if not username_valid:
raise InvalidUsernameException('Invalid username %s: %s' % (username, username_issue)) raise InvalidUsernameException('Invalid username %s: %s' % (username, username_issue))
# We allow password none for the federated login case.
if password is not None and not validate_password(password):
raise InvalidPasswordException(INVALID_PASSWORD_MESSAGE)
try: try:
existing = User.get((User.username == username) | (User.email == email)) existing = User.get((User.username == username) | (User.email == email))
@ -94,18 +116,7 @@ def create_user(username, password, email, add_change_pw_notification=True):
pass pass
try: try:
pw_hash = None new_user = User.create(username=username, email=email)
if password is not None:
pw_hash = bcrypt.hashpw(password, bcrypt.gensalt())
new_user = User.create(username=username, password_hash=pw_hash,
email=email)
# If the password is None, then add a notification for the user to change
# their password ASAP.
if not pw_hash and add_change_pw_notification:
create_notification('password_required', new_user)
return new_user return new_user
except Exception as ex: except Exception as ex:
raise DataModelException(ex.message) raise DataModelException(ex.message)
@ -122,7 +133,7 @@ def is_username_unique(test_username):
def create_organization(name, email, creating_user): def create_organization(name, email, creating_user):
try: try:
# Create the org # Create the org
new_org = create_user(name, None, email, add_change_pw_notification=False) new_org = _create_user(name, email)
new_org.organization = True new_org.organization = True
new_org.save() new_org.save()
@ -335,8 +346,11 @@ def set_team_org_permission(team, team_role_name, set_by_username):
return team return team
def create_federated_user(username, email, service_name, service_id): def create_federated_user(username, email, service_name, service_id, set_password_notification):
new_user = create_user(username, None, email) if not is_create_user_allowed():
raise TooManyUsersException()
new_user = _create_user(username, email)
new_user.verified = True new_user.verified = True
new_user.save() new_user.save()
@ -344,6 +358,9 @@ def create_federated_user(username, email, service_name, service_id):
FederatedLogin.create(user=new_user, service=service, FederatedLogin.create(user=new_user, service=service,
service_ident=service_id) service_ident=service_id)
if set_password_notification:
create_notification('password_required', new_user)
return new_user return new_user

View file

@ -92,14 +92,12 @@ class LDAPUsers(object):
logger.error('Unable to pick a username for user: %s', username) logger.error('Unable to pick a username for user: %s', username)
return None return None
db_user = model.create_user(valid_username, None, email, add_change_pw_notification=False) db_user = model.create_federated_user(valid_username, email, 'ldap', username,
db_user.verified = True set_password_notification=False)
model.attach_federated_login(db_user, 'ldap', username)
else: else:
# Update the db attributes from ldap # Update the db attributes from ldap
db_user.email = email db_user.email = email
db_user.save()
db_user.save()
return db_user return db_user

View file

@ -87,7 +87,7 @@ def github_oauth_callback():
# try to create the user # try to create the user
try: try:
to_login = model.create_federated_user(username, found_email, 'github', to_login = model.create_federated_user(username, found_email, 'github',
github_id) github_id, set_password_notification=True)
# Success, tell analytics # Success, tell analytics
analytics.track(to_login.username, 'register', {'service': 'github'}) analytics.track(to_login.username, 'register', {'service': 'github'})

13
license.py Normal file
View file

@ -0,0 +1,13 @@
import pickle
from Crypto.PublicKey import RSA
n = 24311791124264168943780535074639421876317270880681911499019414944027362498498429776192966738844514582251884695124256895677070273097239290537016363098432785034818859765271229653729724078304186025013011992335454557504431888746007324285000011384941749613875855493086506022340155196030616409545906383713728780211095701026770053812741971198465120292345817928060114890913931047021503727972067476586739126160044293621653486418983183727572502888923949587290840425930251185737996066354726953382305020440374552871209809125535533731995494145421279907938079885061852265339259634996180877443852561265066616143910755505151318370667L
e = 65537L
def load_license(license_path):
decryptor = RSA.construct((n, e))
with open(license_path, 'rb') as encrypted_license:
decrypted_data = decryptor.encrypt(encrypted_license.read(), 0)
return pickle.loads(decrypted_data[0])

BIN
license.pyc Normal file

Binary file not shown.

View file

@ -34,3 +34,4 @@ blinker
raven raven
python-ldap python-ldap
unidecode unidecode
pycrypto

38
tools/createlicense.py Normal file
View file

@ -0,0 +1,38 @@
import argparse
import pickle
from Crypto.PublicKey import RSA
from datetime import datetime, timedelta
def encrypt(message, output_filename):
private_key_file = 'conf/stack/license_key'
with open(private_key_file, 'r') as private_key:
encryptor = RSA.importKey(private_key)
encrypted_data = encryptor.decrypt(message)
with open(output_filename, 'wb') as encrypted_file:
encrypted_file.write(encrypted_data)
parser = argparse.ArgumentParser(description='Create a license file.')
parser.add_argument('--users', type=int, default=20,
help='Number of users allowed by the license')
parser.add_argument('--days', type=int, default=30,
help='Number of days for which the license is valid')
parser.add_argument('--warn', type=int, default=7,
help='Number of days prior to expiration to warn users')
parser.add_argument('--output', type=str, required=True,
help='File in which to store the license')
if __name__ == "__main__":
args = parser.parse_args()
print ('Creating license for %s users for %s days in file: %s' %
(args.users, args.days, args.output))
license_data = {
'LICENSE_EXPIRATION': datetime.utcnow() + timedelta(days=args.days),
'LICENSE_USER_LIMIT': args.users,
'LICENSE_EXPIRATION_WARNING': datetime.utcnow() + timedelta(days=(args.days - args.warn)),
}
encrypt(pickle.dumps(license_data, 2), args.output)