Start fleshing out support for robots.
This commit is contained in:
parent
f8ae8ed6cd
commit
026ed7ffb4
5 changed files with 176 additions and 28 deletions
|
@ -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)
|
||||
|
|
102
data/model.py
102
data/model.py
|
@ -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)
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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():
|
||||
|
|
|
@ -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)
|
||||
|
|
Reference in a new issue