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:
parent
0ef1902957
commit
33b43b75c0
9 changed files with 103 additions and 26 deletions
|
@ -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
9
app.py
|
@ -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)
|
||||||
|
|
|
@ -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
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -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
|
||||||
|
|
||||||
|
|
|
@ -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
13
license.py
Normal 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
BIN
license.pyc
Normal file
Binary file not shown.
|
@ -34,3 +34,4 @@ blinker
|
||||||
raven
|
raven
|
||||||
python-ldap
|
python-ldap
|
||||||
unidecode
|
unidecode
|
||||||
|
pycrypto
|
||||||
|
|
38
tools/createlicense.py
Normal file
38
tools/createlicense.py
Normal 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)
|
Reference in a new issue