Start fleshing out support for robots.

This commit is contained in:
yackob03 2013-11-20 16:13:03 -05:00
parent f8ae8ed6cd
commit 026ed7ffb4
5 changed files with 176 additions and 28 deletions

View file

@ -22,6 +22,14 @@ def close_db(exc):
app.teardown_request(close_db)
def random_string_generator(length=16):
def random_string():
random = SystemRandom()
return ''.join([random.choice(string.ascii_uppercase + string.digits)
for _ in range(length)])
return random_string
class BaseModel(Model):
class Meta:
database = db
@ -30,10 +38,12 @@ class BaseModel(Model):
class User(BaseModel):
username = CharField(unique=True, index=True)
password_hash = CharField(null=True)
email = CharField(unique=True, index=True)
email = CharField(unique=True, index=True,
default=random_string_generator(length=64))
verified = BooleanField(default=False)
stripe_id = CharField(index=True, null=True)
organization = BooleanField(default=False, index=True)
robot = BooleanField(default=False, index=True)
invoice_email = BooleanField(default=False)
@ -123,14 +133,6 @@ class RepositoryPermission(BaseModel):
)
def random_string_generator(length=16):
def random_string():
random = SystemRandom()
return ''.join([random.choice(string.ascii_uppercase + string.digits)
for x in range(length)])
return random_string
class Webhook(BaseModel):
public_id = CharField(default=random_string_generator(length=64),
unique=True, index=True)

View file

@ -6,6 +6,7 @@ import json
from database import *
from util.validation import *
from util.names import format_robot_username
logger = logging.getLogger(__name__)
@ -27,6 +28,10 @@ class InvalidOrganizationException(DataModelException):
pass
class InvalidRobotException(DataModelException):
pass
class InvalidTeamException(DataModelException):
pass
@ -60,7 +65,7 @@ def create_user(username, password, email):
try:
existing = User.get((User.username == username) | (User.email == email))
logger.debug('Existing user with same username or email.')
logger.info('Existing user with same username or email.')
# A user already exists with either the same username or email
if existing.username == username:
@ -104,6 +109,65 @@ def create_organization(name, email, creating_user):
raise InvalidOrganizationException('Invalid organization name: %s' % name)
def create_robot(robot_shortname, parent):
if not validate_username(robot_shortname):
raise InvalidRobotException('The name for the robot \'%s\' is invalid.' %
robot_shortname)
username = format_robot_username(parent.username, robot_shortname)
try:
User.get(User.username == username)
msg = 'Existing robot with name: %s' % username
logger.info(msg)
raise InvalidRobotException(msg)
except User.DoesNotExist:
pass
try:
created = User.create(username=username, robot=True)
service = LoginService.get(name='quayrobot')
password = created.email
FederatedLogin.create(user=created, service=service,
service_ident=password)
return created, password
except Exception as ex:
raise DataModelException(ex.message)
def verify_robot(robot_username, password):
joined = User.select().join(FederatedLogin).join(LoginService)
found = list(joined.where(FederatedLogin.service_ident == password,
LoginService.name == 'quayrobot',
User.username == robot_username))
if not found:
msg = ('Could not find robot with username: %s and supplied password.' %
robot_username)
raise InvalidRobotException(msg)
return found[0]
def delete_robot(robot_username):
try:
robot = User.get(username=robot_username, robot=True)
robot.delete_instance(recursive=True, delete_nullable=True)
except User.DoesNotExist:
raise InvalidRobotException('Could not find robot with username: %s' %
robot_username)
def list_entity_robots(entity_name):
selected = User.select(User.username, FederatedLogin.service_ident)
joined = selected.join(FederatedLogin)
return joined.where(User.robot == True,
User.username ** (entity_name + '+%')).tuples()
def convert_user_to_organization(user, admin_user):
# Change the user to an organization.
user.organization = True
@ -123,6 +187,7 @@ def convert_user_to_organization(user, admin_user):
return user
def create_team(name, org, team_role_name, description=''):
if not validate_username(name):
raise InvalidTeamException('Invalid team name: %s' % name)
@ -136,16 +201,16 @@ def create_team(name, org, team_role_name, description=''):
description=description)
def __get_user_admin_teams(org_name, team_name, username):
Org = User.alias()
user_teams = Team.select().join(TeamMember).join(User)
with_org = user_teams.switch(Team).join(Org,
on=(Org.id == Team.organization))
with_role = with_org.switch(Team).join(TeamRole)
admin_teams = with_role.where(User.username == username,
Org.username == org_name,
TeamRole.name == 'admin')
return admin_teams
def __get_user_admin_teams(org_name, username):
Org = User.alias()
user_teams = Team.select().join(TeamMember).join(User)
with_org = user_teams.switch(Team).join(Org,
on=(Org.id == Team.organization))
with_role = with_org.switch(Team).join(TeamRole)
admin_teams = with_role.where(User.username == username,
Org.username == org_name,
TeamRole.name == 'admin')
return admin_teams
def remove_team(org_name, team_name, removed_by_username):
@ -228,15 +293,16 @@ def set_team_org_permission(team, team_role_name, set_by_username):
def create_federated_user(username, email, service_name, service_id):
new_user = create_user(username, None, email)
new_user.verified = True
new_user.save()
new_user = create_user(username, None, email)
new_user.verified = True
new_user.save()
service = LoginService.get(LoginService.name == service_name)
federated_user = FederatedLogin.create(user=new_user, service=service,
service_ident=service_id)
service = LoginService.get(LoginService.name == service_name)
FederatedLogin.create(user=new_user, service=service,
service_ident=service_id)
return new_user
return new_user
def verify_federated_login(service_name, service_id):
selected = FederatedLogin.select(FederatedLogin, User)

View file

@ -15,7 +15,7 @@ from data.queue import dockerfile_build_queue
from data.plans import USER_PLANS, BUSINESS_PLANS, get_plan
from app import app
from util.email import send_confirmation_email, send_recovery_email
from util.names import parse_repository_name
from util.names import parse_repository_name, format_robot_username
from util.gravatar import compute_hash
from auth.permissions import (ReadRepositoryPermission,
ModifyRepositoryPermission,
@ -1501,3 +1501,78 @@ def get_org_subscription(orgname):
})
abort(403)
def robot_view(name, password):
return {
'name': name,
'password': password,
}
@app.route('/api/user/robots', methods=['GET'])
@api_login_required
def get_user_robots():
user = current_user.db_user()
robots = model.list_entity_robots(user.username)
return jsonify({
'robots': [robot_view(name, password) for name, password in robots]
})
@app.route('/api/organization/<orgname>/robots', methods=['GET'])
@api_login_required
def get_org_robots(orgname):
permission = AdministerOrganizationPermission(orgname)
if permission.can():
robots = model.list_entity_robots(orgname)
return jsonify({
'robots': [robot_view(name, password) for name, password in robots]
})
abort(403)
@app.route('/api/user/robots/<robot_shortname>', methods=['PUT'])
@api_login_required
def create_robot(robot_shortname):
parent = current_user.db_user()
robot, password = model.create_robot(robot_shortname, parent)
resp = jsonify(robot_view(robot.username, password))
resp.status_code = 201
return resp
@app.route('/api/organization/<orgname>/robots/<robot_shortname>',
methods=['PUT'])
@api_login_required
def create_org_robot(orgname, robot_shortname):
permission = AdministerOrganizationPermission(orgname)
if permission.can():
parent = model.get_organization(orgname)
robot, password = model.create_robot(robot_shortname, parent)
resp = jsonify(robot_view(robot.username, password))
resp.status_code = 201
return resp
abort(403)
@app.route('/api/user/robots/<robot_shortname>', methods=['DELETE'])
@api_login_required
def delete_robot(robot_shortname):
parent = current_user.db_user()
model.delete_robot(format_robot_username(parent.username, robot_shortname))
return make_response('No Content', 204)
@app.route('/api/organization/<orgname>/robots/<robot_shortname>',
methods=['DELETE'])
@api_login_required
def delete_org_robot(orgname, robot_shortname):
permission = AdministerOrganizationPermission(orgname)
if permission.can():
model.delete_robot(format_robot_username(orgname, robot_shortname))
return make_response('No Content', 204)
abort(403)

View file

@ -109,6 +109,7 @@ def initialize_database():
Visibility.create(name='public')
Visibility.create(name='private')
LoginService.create(name='github')
LoginService.create(name='quayrobot')
def wipe_database():

View file

@ -20,3 +20,7 @@ def parse_repository_name(f):
(namespace, repository) = parse_namespace_repository(repository)
return f(namespace, repository, *args, **kwargs)
return wrapper
def format_robot_username(parent_username, robot_shortname):
return '%s+%s' % (parent_username, robot_shortname)