Code review changes

This commit is contained in:
Joseph Schorr 2014-09-08 17:20:01 -04:00
parent fa1abd5eda
commit 7c45aca405
8 changed files with 177 additions and 48 deletions

View file

@ -41,6 +41,7 @@ def upgrade():
{'id':41, 'name':'org_invite_team_member'},
{'id':42, 'name':'org_team_member_invite_accepted'},
{'id':43, 'name':'org_team_member_invite_declined'},
{'id':44, 'name':'org_delete_team_member_invite'},
])
@ -70,3 +71,8 @@ def downgrade():
(logentrykind.delete()
.where(logentrykind.c.name == op.inline_literal('org_team_member_invite_declined')))
)
op.execute(
(logentrykind.delete()
.where(logentrykind.c.name == op.inline_literal('org_delete_team_member_invite')))
)

View file

@ -331,13 +331,7 @@ def remove_team(org_name, team_name, removed_by_username):
def add_or_invite_to_team(inviter, team, user=None, email=None):
# If the user is a member of the organization, then we simply add the
# user directly to the team. Otherwise, an invite is created for the user/email.
# We return None if the user was directly added and the invite object if the user was invited.
if email:
try:
user = User.get(email=email)
except User.DoesNotExist:
pass
# We return None if the user was directly added and the invite object if the user was invited.
requires_invite = True
if user:
orgname = team.organization.username
@ -1918,6 +1912,19 @@ def confirm_email_authorization_for_repo(code):
return found
def delete_team_email_invite(team, email):
found = TeamMemberInvite.get(TeamMemberInvite.email == email, TeamMemberInvite.team == team)
found.delete_instance()
def delete_team_user_invite(team, user):
try:
found = TeamMemberInvite.get(TeamMemberInvite.user == user, TeamMemberInvite.team == team)
except TeamMemberInvite.DoesNotExist:
return False
found.delete_instance()
return True
def lookup_team_invites(user):
return TeamMemberInvite.select().where(TeamMemberInvite.user == user)

View file

@ -211,7 +211,11 @@ class TeamMember(ApiResource):
return member_view(user, invited=False)
# User was invited.
log_action('org_invite_team_member', orgname, {'member': membername, 'team': teamname})
log_action('org_invite_team_member', orgname, {
'user': membername,
'member': membername,
'team': teamname
})
return member_view(user, invited=True)
raise Unauthorized()
@ -219,11 +223,35 @@ class TeamMember(ApiResource):
@require_scope(scopes.ORG_ADMIN)
@nickname('deleteOrganizationTeamMember')
def delete(self, orgname, teamname, membername):
""" Delete an existing member of a team. """
""" Delete a member of a team. If the user is merely invited to join
the team, then the invite is removed instead.
"""
permission = AdministerOrganizationPermission(orgname)
if permission.can():
# Remote the user from the team.
invoking_user = get_authenticated_user().username
# Find the team.
try:
team = model.get_organization_team(orgname, teamname)
except model.InvalidTeamException:
raise NotFound()
# Find the member.
member = model.get_user(membername)
if not member:
raise NotFound()
# First attempt to delete an invite for the user to this team. If none found,
# then we try to remove the user directly.
if model.delete_team_user_invite(team, member):
log_action('org_delete_team_member_invite', orgname, {
'user': membername,
'team': teamname,
'member': membername
})
return 'Deleted', 204
model.remove_user_from_team(orgname, teamname, membername, invoking_user)
log_action('org_remove_team_member', orgname, {'member': membername, 'team': teamname})
return 'Deleted', 204
@ -251,11 +279,40 @@ class InviteTeamMember(ApiResource):
# Invite the email to the team.
inviter = get_authenticated_user()
invite = handle_addinvite_team(inviter, team, email=email)
log_action('org_invite_team_member', orgname, {'email': email, 'team': teamname})
log_action('org_invite_team_member', orgname, {
'email': email,
'team': teamname,
'member': email
})
return invite_view(invite)
raise Unauthorized()
@require_scope(scopes.ORG_ADMIN)
@nickname('deleteTeamMemberEmailInvite')
def delete(self, orgname, teamname, email):
""" Delete an invite of an email address to join a team. """
permission = AdministerOrganizationPermission(orgname)
if permission.can():
team = None
# Find the team.
try:
team = model.get_organization_team(orgname, teamname)
except model.InvalidTeamException:
raise NotFound()
# Delete the invite.
model.delete_team_email_invite(team, email)
log_action('org_delete_team_member_invite', orgname, {
'email': email,
'team': teamname,
'member': email
})
return 'Deleted', 204
raise Unauthorized()
@resource('/v1/teaminvite/<code>')
@internal_only
@ -269,7 +326,7 @@ class TeamMemberInvite(ApiResource):
try:
(team, inviter) = model.confirm_team_invite(code, get_authenticated_user())
except model.DataModelException:
raise NotFound()
raise NotFound()
model.delete_matching_notifications(get_authenticated_user(), 'org_team_invite', code=code)

View file

@ -213,6 +213,7 @@ def initialize_database():
LogEntryKind.create(name='org_create_team')
LogEntryKind.create(name='org_delete_team')
LogEntryKind.create(name='org_invite_team_member')
LogEntryKind.create(name='org_delete_team_member_invite')
LogEntryKind.create(name='org_add_team_member')
LogEntryKind.create(name='org_team_member_invite_accepted')
LogEntryKind.create(name='org_team_member_invite_declined')

View file

@ -562,6 +562,8 @@ quayApp = angular.module('quay', quayDependencies, function($provide, cfpLoading
var fieldIcons = {
'inviter': 'user',
'username': 'user',
'user': 'user',
'email': 'envelope',
'activating_username': 'user',
'delegate_user': 'user',
'delegate_team': 'group',
@ -2364,7 +2366,7 @@ quayApp.directive('signinForm', function () {
// forms get removed before the location changes.
$timeout(function() {
var redirectUrl = getRedirectUrl();
if (redirectUrl == $location.path()) {
if (redirectUrl == $location.path() || redirectUrl == null) {
return;
}
window.location = (redirectUrl ? redirectUrl : '/');
@ -2733,7 +2735,20 @@ quayApp.directive('logsView', function () {
'org_delete_team': 'Delete team: {team}',
'org_add_team_member': 'Add member {member} to team {team}',
'org_remove_team_member': 'Remove member {member} from team {team}',
'org_invite_team_member': 'Invite user {member} to team {team}',
'org_invite_team_member': function(metadata) {
if (metadata.user) {
return 'Invite {user} to team {team}';
} else {
return 'Invite {email} to team {team}';
}
},
'org_delete_team_member_invite': function(metadata) {
if (metadata.user) {
return 'Rescind invite of {user} to team {team}';
} else {
return 'Rescind invite of {email} to team {team}';
}
},
'org_team_member_invite_accepted': 'User {member}, invited by {inviter}, joined team {team}',
'org_team_member_invite_declined': 'User {member}, invited by {inviter}, declined to join team {team}',
@ -2819,6 +2834,7 @@ quayApp.directive('logsView', function () {
'org_delete_team': 'Delete team',
'org_add_team_member': 'Add team member',
'org_invite_team_member': 'Invite team member',
'org_delete_team_member_invite': 'Rescind team member invitation',
'org_remove_team_member': 'Remove team member',
'org_team_member_invite_accepted': 'Team invite accepted',
'org_team_member_invite_declined': 'Team invite declined',

View file

@ -1,4 +1,5 @@
function SignInCtrl($scope, $location) {
$scope.redirectUrl = '/';
}
function GuideCtrl() {
@ -2337,6 +2338,31 @@ function TeamViewCtrl($rootScope, $scope, Restangular, ApiService, $routeParams)
}, errorHandler);
};
$scope.revokeInvite = function(inviteInfo) {
if (inviteInfo.kind == 'invite') {
// E-mail invite.
$scope.revokeEmailInvite(inviteInfo.email);
} else {
// User invite.
$scope.removeMember(inviteInfo.name);
}
};
$scope.revokeEmailInvite = function(email) {
var params = {
'orgname': orgname,
'teamname': teamname,
'email': email
};
ApiService.deleteTeamMemberEmailInvite(null, params).then(function(resp) {
if (!$scope.memberMap[email]) { return; }
var index = $.inArray($scope.memberMap[email], $scope.members);
$scope.members.splice(index, 1);
delete $scope.memberMap[email];
}, ApiService.errorDisplay('Cannot revoke team invite'));
};
$scope.removeMember = function(username) {
var params = {
'orgname': orgname,
@ -2345,17 +2371,11 @@ function TeamViewCtrl($rootScope, $scope, Restangular, ApiService, $routeParams)
};
ApiService.deleteOrganizationTeamMember(null, params).then(function(resp) {
for (var i = $scope.members.length - 1; i >= 0; --i) {
var current = $scope.members[i];
if (current.name == username) {
$scope.members.splice(i, 1);
delete $scope.memberMap[username];
break;
}
}
}, function() {
$('#cannotChangeMembersModal').modal({});
});
if (!$scope.memberMap[username]) { return; }
var index = $.inArray($scope.memberMap[username], $scope.members);
$scope.members.splice(index, 1);
delete $scope.memberMap[username];
}, ApiService.errorDisplay('Cannot remove team member'));
};
$scope.updateForDescription = function(content) {
@ -2738,6 +2758,9 @@ function ConfirmInviteCtrl($scope, $location, UserService, ApiService, Notificat
UserService.updateUserIn($scope, function(user) {
if (!user.anonymous && !$scope.loading) {
// Make sure to not redirect now that we have logged in. We'll conduct the redirect
// manually.
$scope.redirectUrl = null;
$scope.loading = true;
var params = {
@ -2754,5 +2777,5 @@ function ConfirmInviteCtrl($scope, $location, UserService, ApiService, Notificat
}
});
$scope.redirectUrl = 'confirminvite?code=' + $location.search()['code'];
$scope.redirectUrl = window.location.href;
}

View file

@ -38,7 +38,7 @@
<span class="entity-reference" entity="member" namespace="organization.name" show-gravatar="true" gravatar-size="32"></span>
</td>
<td>
<span class="delete-ui" delete-title="'Remove ' + member.name + ' From Team'" button-title="'Remove'"
<span class="delete-ui" delete-title="'Remove ' + member.name + ' from team'" button-title="'Remove'"
perform-delete="removeMember(member.name)" ng-if="canEditMembers"></span>
</td>
</tr>
@ -53,7 +53,7 @@
<span class="entity-reference" entity="member" namespace="organization.name"></span>
</td>
<td>
<span class="delete-ui" delete-title="'Remove ' + member.name + ' From Team'" button-title="'Remove'"
<span class="delete-ui" delete-title="'Remove ' + member.name + ' from team'" button-title="'Remove'"
perform-delete="removeMember(member.name)" ng-if="canEditMembers"></span>
</td>
</tr>
@ -74,6 +74,8 @@
</span>
</td>
<td>
<span class="delete-ui" delete-title="'Revoke invite to join team'" button-title="'Revoke'"
perform-delete="revokeInvite(member)" ng-if="canEditMembers"></span>
</td>
</tr>
</table>

View file

@ -131,6 +131,10 @@ class ApiTestCase(unittest.TestCase):
def deleteResponse(self, resource_name, params={}, expected_code=204):
rv = self.app.delete(self.url_for(resource_name, params))
if rv.status_code != expected_code:
print 'Mismatch data for resource DELETE %s: %s' % (resource_name, rv.data)
self.assertEquals(rv.status_code, expected_code)
return rv.data
@ -827,27 +831,6 @@ class TestAcceptTeamMemberInvite(ApiTestCase):
self.fail(membername + ' not found in team: ' + json.dumps(data))
def test_accept_wronguser(self):
self.login(ADMIN_ACCESS_USER)
# Create the invite.
membername = NO_ACCESS_USER
response = self.putJsonResponse(TeamMember,
params=dict(orgname=ORGANIZATION, teamname='owners',
membername=membername))
self.assertEquals(True, response['invited'])
# Try to accept the invite.
user = model.get_user(membername)
invites = list(model.lookup_team_invites(user))
self.assertEquals(1, len(invites))
self.putResponse(TeamMemberInvite,
params=dict(code=invites[0].invite_token),
expected_code=404)
def test_accept(self):
self.login(ADMIN_ACCESS_USER)
@ -935,6 +918,40 @@ class TestDeclineTeamMemberInvite(ApiTestCase):
class TestDeleteOrganizationTeamMember(ApiTestCase):
def test_deletememberinvite(self):
self.login(ADMIN_ACCESS_USER)
membername = NO_ACCESS_USER
response = self.putJsonResponse(TeamMember,
params=dict(orgname=ORGANIZATION, teamname='readers',
membername=membername))
self.assertEquals(True, response['invited'])
# Verify the invite was added.
json = self.getJsonResponse(TeamMemberList,
params=dict(orgname=ORGANIZATION,
teamname='readers',
includePending=True))
assert len(json['members']) == 3
# Delete the invite.
self.deleteResponse(TeamMember,
params=dict(orgname=ORGANIZATION, teamname='readers',
membername=membername))
# Verify the user was removed from the team.
json = self.getJsonResponse(TeamMemberList,
params=dict(orgname=ORGANIZATION,
teamname='readers',
includePending=True))
assert len(json['members']) == 2
def test_deletemember(self):
self.login(ADMIN_ACCESS_USER)