From b92fe17e3c15f2a94596659baf5693acfaa42785 Mon Sep 17 00:00:00 2001 From: yackob03 Date: Thu, 31 Oct 2013 17:36:53 -0400 Subject: [PATCH 1/2] Remove some unused model methods. --- data/model.py | 14 -------------- 1 file changed, 14 deletions(-) diff --git a/data/model.py b/data/model.py index a46ca1912..14c17ebf2 100644 --- a/data/model.py +++ b/data/model.py @@ -415,12 +415,6 @@ def delete_all_repository_tags(namespace_name, repository_name): RepositoryTag.delete().where(RepositoryTag.repository == repo) -def get_user_repo_permissions(user, repository): - select = RepositoryPermission.select() - return select.where(RepositoryPermission.user == user, - RepositoryPermission.repository == repository) - - def user_permission_repo_query(username, namespace_name, repository_name): selected = RepositoryPermission.select(User, Repository, Role, RepositoryPermission) @@ -554,14 +548,6 @@ def load_token_data(code): raise InvalidTokenException('Invalid delegate token code: %s' % code) -def get_repository_build(request_dbid): - try: - return RepositoryBuild.get(RepositoryBuild.id == request_dbid) - except RepositoryBuild.DoesNotExist: - msg = 'Unable to locate a build by id: %s' % request_dbid - raise InvalidRepositoryBuildException(msg) - - def list_repository_builds(namespace_name, repository_name, include_inactive=True): joined = RepositoryBuild.select().join(Repository) From 4c0f987af34a6d1c493dd4119bda896bb0cd532a Mon Sep 17 00:00:00 2001 From: yackob03 Date: Fri, 1 Nov 2013 19:34:17 -0400 Subject: [PATCH 2/2] Flesh out some of the organization methods and fix the models. --- data/database.py | 14 ++- data/model.py | 212 +++++++++++++++++++++++++++++++++------------- endpoints/api.py | 6 +- initdb.py | 13 +++ test/data/test.db | Bin 94208 -> 99328 bytes 5 files changed, 181 insertions(+), 64 deletions(-) diff --git a/data/database.py b/data/database.py index 911256dc0..c56918071 100644 --- a/data/database.py +++ b/data/database.py @@ -38,9 +38,16 @@ class User(BaseModel): class Team(BaseModel): - name = CharField() + name = CharField(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): user = ForeignKeyField(User, index=True) @@ -97,13 +104,15 @@ class Role(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) role = ForeignKeyField(Role) class Meta: database = db indexes = ( + (('team', 'repository'), True), (('user', 'repository'), True), ) @@ -116,7 +125,6 @@ class TeamPermission(BaseModel): class Meta: database = db indexes = ( - # A team may only have one permission level in an org (('team', 'organization'), True), ) diff --git a/data/model.py b/data/model.py index 67e47a09c..d65a028b2 100644 --- a/data/model.py +++ b/data/model.py @@ -22,6 +22,14 @@ class InvalidUsernameException(DataModelException): pass +class InvalidOrganizationException(DataModelException): + pass + + +class InvalidTeamException(DataModelException): + pass + + class InvalidPasswordException(DataModelException): pass @@ -73,6 +81,58 @@ def create_user(username, password, email): 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): new_user = create_user(username, None, email) new_user.verified = True @@ -166,35 +226,27 @@ def verify_user(username, password): # We weren't able to authorize the user return None -class dotdict(dict): - def __getattr__(self, name): - return self[name] - def get_user_organizations(username): - # TODO: return the orgs that the user is apart of. - return [dotdict({ - 'username': 'testorg', - 'email': 'testorg@quay.io' - })] + UserAlias = User.alias() + all_teams = User.select().join(Team).join(TeamMember) + with_user = all_teams.join(UserAlias, on=(UserAlias.id == TeamMember.user)) + return with_user.where(User.organization == True, + UserAlias.username == username) -def lookup_organization(name, username=None): - if name == 'testorg': - return dotdict({ - 'username': 'testorg', - 'email': 'testorg@quay.io' - }) +def get_organization(name): + try: + return User.get(username=name, organization=True) + except User.DoesNotExist: + raise InvalidOrganizationException('Organization does not exist: %s' % + name) + - return None - - -def get_user_teams(username, organization): - # TODO: return the teams that the user is apart of. - return [dotdict({ - 'id': 1234, - 'name': 'Owners' - })] +def get_user_teams_within_org(username, organization): + joined = Team.select().join(TeamMember).join(User) + return joined.where(Team.organization == organization, + User.username == username) def get_visible_repositories(username=None, include_public=True, limit=None, @@ -328,8 +380,10 @@ def create_repository(namespace, name, owner, visibility='private'): repo = Repository.create(namespace=namespace, name=name, visibility=private) admin = Role.get(name='admin') - permission = RepositoryPermission.create(user=owner, repository=repo, - role=admin) + + if owner and not owner.organization: + permission = RepositoryPermission.create(user=owner, repository=repo, + role=admin) return repo @@ -451,59 +505,101 @@ def delete_all_repository_tags(namespace_name, repository_name): RepositoryTag.delete().where(RepositoryTag.repository == repo) -def user_permission_repo_query(username, namespace_name, repository_name): - selected = RepositoryPermission.select(User, Repository, Role, +def __entity_permission_repo_query(entity_id, entity_table, + entity_id_property, namespace_name, + repository_name): + """ This method works for both users and teams. """ + selected = RepositoryPermission.select(entity_table, Repository, Role, RepositoryPermission) - with_user = selected.join(User) + with_user = selected.join(entity_table) with_role = with_user.switch(RepositoryPermission).join(Role) with_repo = with_role.switch(RepositoryPermission).join(Repository) return with_repo.where(Repository.name == repository_name, Repository.namespace == namespace_name, - User.username == username) + entity_id_property == entity_id) def get_user_reponame_permission(username, namespace_name, repository_name): - fetched = list(user_permission_repo_query(username, namespace_name, - repository_name)) + fetched = list(__entity_permission_repo_query(username, User, User.username, + namespace_name, + repository_name)) if not fetched: raise DataModelException('User does not have permission for repo.') return fetched[0] +def get_team_reponame_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.') + + return fetched[0] + + +def delete_user_permission(username, namespace_name, repository_name): + if username == namespace_name: + raise DataModelException('Namespace owner must always be admin.') + + fetched = list(__entity_permission_repo_query(username, User, User.username, + namespace_name, + repository_name)) + if not fetched: + raise DataModelException('User does not have permission for repo.') + + 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.') - user = User.get(User.username == username) - 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 + return __set_entity_repo_permission(username, User, User.username, 'user', + namespace_name, repository_name, + role_name) -def delete_user_permission(username, namespace_name, repository_name): - if username == namespace_name: - raise DataModelException('Namespace owner must always be admin.') - - fetched = list(user_permission_repo_query(username, namespace_name, - repository_name)) - if not fetched: - raise DataModelException('User does not have permission for repo.') - - fetched[0].delete_instance() +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): diff --git a/endpoints/api.py b/endpoints/api.py index 1ebe4f2a2..53099d030 100644 --- a/endpoints/api.py +++ b/endpoints/api.py @@ -208,11 +208,11 @@ def get_organization(orgname): abort(404) user = current_user.db_user() - organization = model.lookup_organization(orgname, username = user.username) - if not organization: + org = model.get_organization(orgname) + if not org: 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)) diff --git a/initdb.py b/initdb.py index c5afbdc93..8120ea48a 100644 --- a/initdb.py +++ b/initdb.py @@ -148,6 +148,19 @@ if __name__ == '__main__': 'Empty repository which is building.', 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') tag = 'ci.devtable.com:5000/%s/%s' % (building.namespace, building.name) build = model.create_repository_build(building, token, '123-45-6789', tag) diff --git a/test/data/test.db b/test/data/test.db index 28a606a85160fae0f68d4f97a746f2625ed915b7..242e6c95aa5262320826917830ed7ce05a62ae35 100644 GIT binary patch delta 4750 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 delta 2503 zcmbtUdr*|u6~E`6ZyykJMHd$YWQib%z_)J}PD{UC{gZB2Y-9#wY#IF@|8n}$okV7QoT4Kt9M6Rm#XoCnMC%+69lZ_$N9e8e zRD$Gb1PSp3G10->I4Rx8(aUfzdV`TlI?i9g!&po}H#>|O+cqy>-@GEp(U@d+Bvn+j z%y%rW^(|RayP$FDI@{`X&XVfN@>N;>MOBTq(xR=^&eo2N8+@IsmOfwA+_rk_q7HS7 z7%hqxd!19JKG%R9DY+uLi}%8QHFmb=z?3+H*= zRgMLfuEkrbGsO*OG(~rf;pcdbqWdF?Ygw_xe97=0ei@96xQ=%*BkAfWyMfm2zh(k*hqoQMjOoVr( z39GULx0_?ck*-vjD*oJc6Q+pK7rbB8uXYhmVa*HdtmS zLLi~JWo1*R=oyTDW@A%ZYkTvi){R>-#D>8N3hmgS3Yio{lsIueTtp0cDZG-QXF(0F z8B&4b>mN=5n`ZF4VReEYFERWK|Ak-RExdx4sQkZ1ap~vNNz==)7k`f@@O8Y2?~rCa z278YuDRpIqi@o#9-9_0>Urm9}Ki9F;S5;;AJm)Pb_LfzY*Hk--OKRu)$`_UtR(c)f z#SWj{U0YLHwWz4bv&8Ny)H`f;XNKLD;j~-ro*b7u$CZ`o@Hm_{NASTfPGbK5>)_C@ zRw-2W)n{#B7ZXNeMewYhN@nX$f6X=ME!1$aE*0>D&|~G6z9fb#jYz4 zBmcv_a4|Aw6y~cl=-bb^Ity~N97BBXeWUmx@pdtKwNP9eOBS9_a(JObG~6$TS&uqt z`FBB~lrL(7jW;OikEw4z!rPScaTFJ?XDCLzz=Gj7PGO(lATHd>B=hqj^S|R?sllkV zsFlu>`MW62|II<>r`TXP43T-5JcUpU%lg{P!&U|MVs%|U<-atSU`ZCi;?Rn$39ZPg z83fNI6ZjJe7KHZX{1^hCnV`%>FfX)dN_C+MDiB~0;QRPptS8SKV9NG5r@>;ez>5~G zfX&}%T-~I>?BJ}g^pNP%TV`5dn>Jt?U*FiWqN$w*tX&iH#K{L{(eWUOn&b__e3J7p zFx4a9GlL5XLgkVXQLu14C;xz>glr*>lK1x!swLDxh9MMS0q>E92-rc2FJQQ5JbV)- zDExzvc`Y8sH5kV~Al-2ei==G>%u`5FDXo*B0xIPC(4$-)nFKyGp;gaq0h zH@LLN!f%K_APsRuMJfT52NIyx^spoijDH(n;tXV@8GcO5b3X>+bL0vOWW(IYi$fl@ zKrsfCZ28e-%Bw_1TENeHvgC>=m=1-ZiIHzc!AbDSm6Jh*qL4wH{OM$vp`~ZSx23_V zdEbHZ?P*X)0r{mNNu$zm*q;K2ZTazhBI7ph=DECpPvoV%lGpMk-om%?Z}B7^&0pg` z;6eT#|B(NM-$4aq(2hP_i!b9(Xy`6ba%Uh>PqTt1P-j1bv7p*rs@<*Ho;uHz<7&Yo zZM9G_ENY@qva_A$v`W?gJ;UqM`UB&LAsv8x`3>T#+bms&6$6Q5%8wRj#HaGsxFu6a;q+ns+uw5acy0X_!Ko$ zi{NSNjHj=2s_7+4V4Q5M165iVfywByt7(Nw!0;amX2{GsIFGrw?FkvCOUY9L@I;P) zRQb}^q2YBe92maU0FfwvI}zp$tpp{)96=v~eF`?{gcIDz{k)&;lM%hJQ~I}qL3Xr5 n>2UW3xC`=lI~2&B^kPV}YiJWFMm=B-!yM`(8_Jv>_~pL=fW4>@