Flesh out some of the organization methods and fix the models.
This commit is contained in:
parent
ecc4ad6e0f
commit
4c0f987af3
5 changed files with 181 additions and 64 deletions
|
@ -38,9 +38,16 @@ class User(BaseModel):
|
||||||
|
|
||||||
|
|
||||||
class Team(BaseModel):
|
class Team(BaseModel):
|
||||||
name = CharField()
|
name = CharField(index=True)
|
||||||
organization = ForeignKeyField(User, index=True)
|
organization = ForeignKeyField(User, index=True)
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
database = db
|
||||||
|
indexes = (
|
||||||
|
# A team name must be unique within an organization
|
||||||
|
(('name', 'organization'), True),
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
class TeamMember(BaseModel):
|
class TeamMember(BaseModel):
|
||||||
user = ForeignKeyField(User, index=True)
|
user = ForeignKeyField(User, index=True)
|
||||||
|
@ -97,13 +104,15 @@ class Role(BaseModel):
|
||||||
|
|
||||||
|
|
||||||
class RepositoryPermission(BaseModel):
|
class RepositoryPermission(BaseModel):
|
||||||
user = ForeignKeyField(User, index=True)
|
team = ForeignKeyField(Team, index=True, null=True)
|
||||||
|
user = ForeignKeyField(User, index=True, null=True)
|
||||||
repository = ForeignKeyField(Repository, index=True)
|
repository = ForeignKeyField(Repository, index=True)
|
||||||
role = ForeignKeyField(Role)
|
role = ForeignKeyField(Role)
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
database = db
|
database = db
|
||||||
indexes = (
|
indexes = (
|
||||||
|
(('team', 'repository'), True),
|
||||||
(('user', 'repository'), True),
|
(('user', 'repository'), True),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -116,7 +125,6 @@ class TeamPermission(BaseModel):
|
||||||
class Meta:
|
class Meta:
|
||||||
database = db
|
database = db
|
||||||
indexes = (
|
indexes = (
|
||||||
# A team may only have one permission level in an org
|
|
||||||
(('team', 'organization'), True),
|
(('team', 'organization'), True),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
194
data/model.py
194
data/model.py
|
@ -22,6 +22,14 @@ class InvalidUsernameException(DataModelException):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class InvalidOrganizationException(DataModelException):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class InvalidTeamException(DataModelException):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
class InvalidPasswordException(DataModelException):
|
class InvalidPasswordException(DataModelException):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
@ -73,6 +81,58 @@ def create_user(username, password, email):
|
||||||
raise DataModelException(ex.message)
|
raise DataModelException(ex.message)
|
||||||
|
|
||||||
|
|
||||||
|
def create_organization(name, email, creating_user):
|
||||||
|
try:
|
||||||
|
# Create the org
|
||||||
|
new_org = create_user(name, None, email)
|
||||||
|
new_org.organization = True
|
||||||
|
new_org.save()
|
||||||
|
|
||||||
|
# Create a team for the owners
|
||||||
|
owners_team = create_team('Owners', new_org)
|
||||||
|
|
||||||
|
# Add the user who created the org to the owners
|
||||||
|
add_user_to_team(creating_user, owners_team)
|
||||||
|
|
||||||
|
# Give the owners team admin access to the namespace
|
||||||
|
set_team_org_permission(owners_team, new_org, 'admin')
|
||||||
|
|
||||||
|
return new_org
|
||||||
|
except InvalidUsernameException:
|
||||||
|
raise InvalidOrganizationException('Invalid organization name: %s' % name)
|
||||||
|
|
||||||
|
|
||||||
|
def create_team(name, org):
|
||||||
|
if not validate_username(name):
|
||||||
|
raise InvalidTeamException('Invalid team name: %s' % name)
|
||||||
|
|
||||||
|
if not org.organization:
|
||||||
|
raise InvalidOrganizationException('User with name %s is not an org.' %
|
||||||
|
org.username)
|
||||||
|
|
||||||
|
return Team.create(name=name, organization=org)
|
||||||
|
|
||||||
|
|
||||||
|
def add_user_to_team(user, team):
|
||||||
|
return TeamMember.create(user=user, team=team)
|
||||||
|
|
||||||
|
|
||||||
|
def set_team_org_permission(team, org, role_name):
|
||||||
|
new_role = Role.get(Role.name == role_name)
|
||||||
|
|
||||||
|
# Fetch any existing permission for this user on the repo
|
||||||
|
try:
|
||||||
|
perm = TeamPermission.get(TeamPermission.team == team,
|
||||||
|
TeamPermission.organization == org)
|
||||||
|
perm.role = new_role
|
||||||
|
perm.save()
|
||||||
|
return perm
|
||||||
|
except TeamPermission.DoesNotExist:
|
||||||
|
new_perm = TeamPermission.create(team=team, organization=org,
|
||||||
|
role=new_role)
|
||||||
|
return new_perm
|
||||||
|
|
||||||
|
|
||||||
def create_federated_user(username, email, service_name, service_id):
|
def create_federated_user(username, email, service_name, service_id):
|
||||||
new_user = create_user(username, None, email)
|
new_user = create_user(username, None, email)
|
||||||
new_user.verified = True
|
new_user.verified = True
|
||||||
|
@ -166,35 +226,27 @@ def verify_user(username, password):
|
||||||
# We weren't able to authorize the user
|
# We weren't able to authorize the user
|
||||||
return None
|
return None
|
||||||
|
|
||||||
class dotdict(dict):
|
|
||||||
def __getattr__(self, name):
|
|
||||||
return self[name]
|
|
||||||
|
|
||||||
|
|
||||||
def get_user_organizations(username):
|
def get_user_organizations(username):
|
||||||
# TODO: return the orgs that the user is apart of.
|
UserAlias = User.alias()
|
||||||
return [dotdict({
|
all_teams = User.select().join(Team).join(TeamMember)
|
||||||
'username': 'testorg',
|
with_user = all_teams.join(UserAlias, on=(UserAlias.id == TeamMember.user))
|
||||||
'email': 'testorg@quay.io'
|
return with_user.where(User.organization == True,
|
||||||
})]
|
UserAlias.username == username)
|
||||||
|
|
||||||
|
|
||||||
def lookup_organization(name, username=None):
|
def get_organization(name):
|
||||||
if name == 'testorg':
|
try:
|
||||||
return dotdict({
|
return User.get(username=name, organization=True)
|
||||||
'username': 'testorg',
|
except User.DoesNotExist:
|
||||||
'email': 'testorg@quay.io'
|
raise InvalidOrganizationException('Organization does not exist: %s' %
|
||||||
})
|
name)
|
||||||
|
|
||||||
return None
|
|
||||||
|
|
||||||
|
|
||||||
def get_user_teams(username, organization):
|
def get_user_teams_within_org(username, organization):
|
||||||
# TODO: return the teams that the user is apart of.
|
joined = Team.select().join(TeamMember).join(User)
|
||||||
return [dotdict({
|
return joined.where(Team.organization == organization,
|
||||||
'id': 1234,
|
User.username == username)
|
||||||
'name': 'Owners'
|
|
||||||
})]
|
|
||||||
|
|
||||||
|
|
||||||
def get_visible_repositories(username=None, include_public=True, limit=None,
|
def get_visible_repositories(username=None, include_public=True, limit=None,
|
||||||
|
@ -328,6 +380,8 @@ def create_repository(namespace, name, owner, visibility='private'):
|
||||||
repo = Repository.create(namespace=namespace, name=name,
|
repo = Repository.create(namespace=namespace, name=name,
|
||||||
visibility=private)
|
visibility=private)
|
||||||
admin = Role.get(name='admin')
|
admin = Role.get(name='admin')
|
||||||
|
|
||||||
|
if owner and not owner.organization:
|
||||||
permission = RepositoryPermission.create(user=owner, repository=repo,
|
permission = RepositoryPermission.create(user=owner, repository=repo,
|
||||||
role=admin)
|
role=admin)
|
||||||
return repo
|
return repo
|
||||||
|
@ -451,19 +505,23 @@ def delete_all_repository_tags(namespace_name, repository_name):
|
||||||
RepositoryTag.delete().where(RepositoryTag.repository == repo)
|
RepositoryTag.delete().where(RepositoryTag.repository == repo)
|
||||||
|
|
||||||
|
|
||||||
def user_permission_repo_query(username, namespace_name, repository_name):
|
def __entity_permission_repo_query(entity_id, entity_table,
|
||||||
selected = RepositoryPermission.select(User, Repository, Role,
|
entity_id_property, namespace_name,
|
||||||
|
repository_name):
|
||||||
|
""" This method works for both users and teams. """
|
||||||
|
selected = RepositoryPermission.select(entity_table, Repository, Role,
|
||||||
RepositoryPermission)
|
RepositoryPermission)
|
||||||
with_user = selected.join(User)
|
with_user = selected.join(entity_table)
|
||||||
with_role = with_user.switch(RepositoryPermission).join(Role)
|
with_role = with_user.switch(RepositoryPermission).join(Role)
|
||||||
with_repo = with_role.switch(RepositoryPermission).join(Repository)
|
with_repo = with_role.switch(RepositoryPermission).join(Repository)
|
||||||
return with_repo.where(Repository.name == repository_name,
|
return with_repo.where(Repository.name == repository_name,
|
||||||
Repository.namespace == namespace_name,
|
Repository.namespace == namespace_name,
|
||||||
User.username == username)
|
entity_id_property == entity_id)
|
||||||
|
|
||||||
|
|
||||||
def get_user_reponame_permission(username, namespace_name, repository_name):
|
def get_user_reponame_permission(username, namespace_name, repository_name):
|
||||||
fetched = list(user_permission_repo_query(username, namespace_name,
|
fetched = list(__entity_permission_repo_query(username, User, User.username,
|
||||||
|
namespace_name,
|
||||||
repository_name))
|
repository_name))
|
||||||
if not fetched:
|
if not fetched:
|
||||||
raise DataModelException('User does not have permission for repo.')
|
raise DataModelException('User does not have permission for repo.')
|
||||||
|
@ -471,34 +529,22 @@ def get_user_reponame_permission(username, namespace_name, repository_name):
|
||||||
return fetched[0]
|
return fetched[0]
|
||||||
|
|
||||||
|
|
||||||
def set_user_repo_permission(username, namespace_name, repository_name,
|
def get_team_reponame_permission(team_name, namespace_name, repository_name):
|
||||||
role_name):
|
fetched = list(__entity_permission_repo_query(team_name, Team, Team.name,
|
||||||
if username == namespace_name:
|
namespace_name,
|
||||||
raise DataModelException('Namespace owner must always be admin.')
|
repository_name))
|
||||||
|
if not fetched:
|
||||||
|
raise DataModelException('Team does not have permission for repo.')
|
||||||
|
|
||||||
user = User.get(User.username == username)
|
return fetched[0]
|
||||||
repo = Repository.get(Repository.name == repository_name,
|
|
||||||
Repository.namespace == namespace_name)
|
|
||||||
new_role = Role.get(Role.name == role_name)
|
|
||||||
|
|
||||||
# Fetch any existing permission for this user on the repo
|
|
||||||
try:
|
|
||||||
perm = RepositoryPermission.get(RepositoryPermission.user == user,
|
|
||||||
RepositoryPermission.repository == repo)
|
|
||||||
perm.role = new_role
|
|
||||||
perm.save()
|
|
||||||
return perm
|
|
||||||
except RepositoryPermission.DoesNotExist:
|
|
||||||
new_perm = RepositoryPermission.create(repository=repo, user=user,
|
|
||||||
role=new_role)
|
|
||||||
return new_perm
|
|
||||||
|
|
||||||
|
|
||||||
def delete_user_permission(username, namespace_name, repository_name):
|
def delete_user_permission(username, namespace_name, repository_name):
|
||||||
if username == namespace_name:
|
if username == namespace_name:
|
||||||
raise DataModelException('Namespace owner must always be admin.')
|
raise DataModelException('Namespace owner must always be admin.')
|
||||||
|
|
||||||
fetched = list(user_permission_repo_query(username, namespace_name,
|
fetched = list(__entity_permission_repo_query(username, User, User.username,
|
||||||
|
namespace_name,
|
||||||
repository_name))
|
repository_name))
|
||||||
if not fetched:
|
if not fetched:
|
||||||
raise DataModelException('User does not have permission for repo.')
|
raise DataModelException('User does not have permission for repo.')
|
||||||
|
@ -506,6 +552,56 @@ def delete_user_permission(username, namespace_name, repository_name):
|
||||||
fetched[0].delete_instance()
|
fetched[0].delete_instance()
|
||||||
|
|
||||||
|
|
||||||
|
def delete_team_permission(team_name, namespace_name, repository_name):
|
||||||
|
fetched = list(__entity_permission_repo_query(team_name, Team, Team.name,
|
||||||
|
namespace_name,
|
||||||
|
repository_name))
|
||||||
|
if not fetched:
|
||||||
|
raise DataModelException('Team does not have permission for repo.')
|
||||||
|
|
||||||
|
fetched[0].delete_instance()
|
||||||
|
|
||||||
|
|
||||||
|
def __set_entity_repo_permission(entity_id, entity_table, entity_id_property,
|
||||||
|
permission_entity_property, namespace_name,
|
||||||
|
repository_name, role_name):
|
||||||
|
entity = entity_table.get(entity_id_property == entity_id)
|
||||||
|
repo = Repository.get(Repository.name == repository_name,
|
||||||
|
Repository.namespace == namespace_name)
|
||||||
|
new_role = Role.get(Role.name == role_name)
|
||||||
|
|
||||||
|
# Fetch any existing permission for this user on the repo
|
||||||
|
try:
|
||||||
|
entity_attr = getattr(RepositoryPermission, permission_entity_property)
|
||||||
|
perm = RepositoryPermission.get(entity_attr == entity,
|
||||||
|
RepositoryPermission.repository == repo)
|
||||||
|
perm.role = new_role
|
||||||
|
perm.save()
|
||||||
|
return perm
|
||||||
|
except RepositoryPermission.DoesNotExist:
|
||||||
|
set_entity_kwargs = {permission_entity_property: entity}
|
||||||
|
new_perm = RepositoryPermission.create(repository=repo, role=new_role,
|
||||||
|
**set_entity_kwargs)
|
||||||
|
return new_perm
|
||||||
|
|
||||||
|
|
||||||
|
def set_user_repo_permission(username, namespace_name, repository_name,
|
||||||
|
role_name):
|
||||||
|
if username == namespace_name:
|
||||||
|
raise DataModelException('Namespace owner must always be admin.')
|
||||||
|
|
||||||
|
return __set_entity_repo_permission(username, User, User.username, 'user',
|
||||||
|
namespace_name, repository_name,
|
||||||
|
role_name)
|
||||||
|
|
||||||
|
|
||||||
|
def set_team_repo_permission(team_name, namespace_name, repository_name,
|
||||||
|
role_name):
|
||||||
|
return __set_entity_repo_permission(team_name, Team, Team.name, 'team',
|
||||||
|
namespace_name, repository_name,
|
||||||
|
role_name)
|
||||||
|
|
||||||
|
|
||||||
def purge_repository(namespace_name, repository_name):
|
def purge_repository(namespace_name, repository_name):
|
||||||
fetched = Repository.get(Repository.name == repository_name,
|
fetched = Repository.get(Repository.name == repository_name,
|
||||||
Repository.namespace == namespace_name)
|
Repository.namespace == namespace_name)
|
||||||
|
|
|
@ -208,11 +208,11 @@ def get_organization(orgname):
|
||||||
abort(404)
|
abort(404)
|
||||||
|
|
||||||
user = current_user.db_user()
|
user = current_user.db_user()
|
||||||
organization = model.lookup_organization(orgname, username = user.username)
|
org = model.get_organization(orgname)
|
||||||
if not organization:
|
if not org:
|
||||||
abort(404)
|
abort(404)
|
||||||
|
|
||||||
teams = model.get_user_teams(user.username, organization)
|
teams = model.get_user_teams_within_org(user.username, org)
|
||||||
return jsonify(org_view(organization, teams))
|
return jsonify(org_view(organization, teams))
|
||||||
|
|
||||||
|
|
||||||
|
|
13
initdb.py
13
initdb.py
|
@ -148,6 +148,19 @@ if __name__ == '__main__':
|
||||||
'Empty repository which is building.',
|
'Empty repository which is building.',
|
||||||
False, [], (0, [], None))
|
False, [], (0, [], None))
|
||||||
|
|
||||||
|
|
||||||
|
org = model.create_organization('devtableorg', 'quay@devtable.com',
|
||||||
|
new_user_1)
|
||||||
|
|
||||||
|
org_repo = __generate_repository(org, 'orgrepo',
|
||||||
|
'Repository owned by an org.', False,
|
||||||
|
[], (4, [], ['latest', 'prod']))
|
||||||
|
|
||||||
|
reader_team = model.create_team('Readers', org)
|
||||||
|
model.set_team_repo_permission(reader_team.name, org_repo.namespace,
|
||||||
|
org_repo.name, 'read')
|
||||||
|
model.add_user_to_team(new_user_2, reader_team)
|
||||||
|
|
||||||
token = model.create_access_token(building, 'write')
|
token = model.create_access_token(building, 'write')
|
||||||
tag = 'ci.devtable.com:5000/%s/%s' % (building.namespace, building.name)
|
tag = 'ci.devtable.com:5000/%s/%s' % (building.namespace, building.name)
|
||||||
build = model.create_repository_build(building, token, '123-45-6789', tag)
|
build = model.create_repository_build(building, token, '123-45-6789', tag)
|
||||||
|
|
Binary file not shown.
Reference in a new issue