Add TeamSync database and API support
Teams can now have a TeamSync entry in the database, indicating how they are synced via an external group. If found, then the user membership of the team cannot be changed via the API.
This commit is contained in:
parent
d718829f5d
commit
f5a854c189
5 changed files with 131 additions and 13 deletions
|
@ -1,19 +1,22 @@
|
|||
""" Create, list and manage an organization's teams. """
|
||||
|
||||
from functools import wraps
|
||||
|
||||
from flask import request
|
||||
|
||||
import features
|
||||
|
||||
from endpoints.api import (resource, nickname, ApiResource, validate_json_request, request_error,
|
||||
log_action, internal_only, require_scope, path_param, query_param,
|
||||
truthy_bool, parse_args, require_user_admin, show_if)
|
||||
from endpoints.exception import Unauthorized, NotFound
|
||||
from app import avatar, authentication
|
||||
from auth.permissions import AdministerOrganizationPermission, ViewTeamPermission
|
||||
from auth.auth_context import get_authenticated_user
|
||||
from auth import scopes
|
||||
from data import model
|
||||
from endpoints.api import (resource, nickname, ApiResource, validate_json_request, request_error,
|
||||
log_action, internal_only, require_scope, path_param, query_param,
|
||||
truthy_bool, parse_args, require_user_admin, show_if, format_date)
|
||||
from endpoints.exception import Unauthorized, NotFound, InvalidRequest
|
||||
from util.useremails import send_org_invite_email
|
||||
from app import avatar
|
||||
from util.names import parse_robot_username
|
||||
|
||||
def permission_view(permission):
|
||||
return {
|
||||
|
@ -24,7 +27,6 @@ def permission_view(permission):
|
|||
'role': permission.role.name
|
||||
}
|
||||
|
||||
|
||||
def try_accept_invite(code, user):
|
||||
(team, inviter) = model.team.confirm_team_invite(code, user)
|
||||
|
||||
|
@ -40,7 +42,6 @@ def try_accept_invite(code, user):
|
|||
|
||||
return team
|
||||
|
||||
|
||||
def handle_addinvite_team(inviter, team, user=None, email=None):
|
||||
requires_invite = features.MAILING and features.REQUIRE_TEAM_INVITE
|
||||
invite = model.team.add_or_invite_to_team(inviter, team, user, email,
|
||||
|
@ -82,7 +83,6 @@ def member_view(member, invited=False):
|
|||
'invited': invited,
|
||||
}
|
||||
|
||||
|
||||
def invite_view(invite):
|
||||
if invite.user:
|
||||
return member_view(invite.user, invited=True)
|
||||
|
@ -94,6 +94,26 @@ def invite_view(invite):
|
|||
'invited': True
|
||||
}
|
||||
|
||||
def disallow_for_synced_team(except_robots=False):
|
||||
""" Disallows the decorated operation for a team that is marked as being synced from an internal
|
||||
auth provider such as LDAP. If except_robots is True, then the operation is allowed if the
|
||||
member specified on the operation is a robot account.
|
||||
"""
|
||||
def inner(func):
|
||||
@wraps(func)
|
||||
def wrapper(self, *args, **kwargs):
|
||||
# Team syncing can only be enabled if we have a federated service.
|
||||
if authentication.federated_service:
|
||||
orgname = kwargs['orgname']
|
||||
teamname = kwargs['teamname']
|
||||
if model.team.get_team_sync_information(orgname, teamname):
|
||||
if not except_robots or not parse_robot_username(kwargs.get('membername', '')):
|
||||
raise InvalidRequest('Cannot call this method on an auth-synced team')
|
||||
|
||||
return func(self, *args, **kwargs)
|
||||
return wrapper
|
||||
return inner
|
||||
|
||||
|
||||
@resource('/v1/organization/<orgname>/team/<teamname>')
|
||||
@path_param('orgname', 'The name of the organization')
|
||||
|
@ -214,6 +234,14 @@ class TeamMemberList(ApiResource):
|
|||
'can_edit': edit_permission.can()
|
||||
}
|
||||
|
||||
sync_info = model.team.get_team_sync_information(orgname, teamname)
|
||||
if sync_info is not None:
|
||||
data['synced'] = {
|
||||
'last_updated': format_date(sync_info.last_updated),
|
||||
'service': sync_info.service.name,
|
||||
'config': sync_info.config,
|
||||
}
|
||||
|
||||
return data
|
||||
|
||||
raise Unauthorized()
|
||||
|
@ -228,6 +256,7 @@ class TeamMember(ApiResource):
|
|||
|
||||
@require_scope(scopes.ORG_ADMIN)
|
||||
@nickname('updateOrganizationTeamMember')
|
||||
@disallow_for_synced_team(except_robots=True)
|
||||
def put(self, orgname, teamname, membername):
|
||||
""" Adds or invites a member to an existing team. """
|
||||
permission = AdministerOrganizationPermission(orgname)
|
||||
|
@ -265,6 +294,7 @@ class TeamMember(ApiResource):
|
|||
|
||||
@require_scope(scopes.ORG_ADMIN)
|
||||
@nickname('deleteOrganizationTeamMember')
|
||||
@disallow_for_synced_team(except_robots=True)
|
||||
def delete(self, orgname, teamname, membername):
|
||||
""" Delete a member of a team. If the user is merely invited to join
|
||||
the team, then the invite is removed instead.
|
||||
|
@ -308,6 +338,7 @@ class InviteTeamMember(ApiResource):
|
|||
""" Resource for inviting a team member via email address. """
|
||||
@require_scope(scopes.ORG_ADMIN)
|
||||
@nickname('inviteTeamMemberEmail')
|
||||
@disallow_for_synced_team()
|
||||
def put(self, orgname, teamname, email):
|
||||
""" Invites an email address to an existing team. """
|
||||
permission = AdministerOrganizationPermission(orgname)
|
||||
|
@ -407,7 +438,7 @@ class TeamMemberInvite(ApiResource):
|
|||
@nickname('declineOrganizationTeamInvite')
|
||||
@require_user_admin
|
||||
def delete(self, code):
|
||||
""" Delete an existing member of a team. """
|
||||
""" Delete an existing invitation to join a team. """
|
||||
(team, inviter) = model.team.delete_team_invite(code, user_obj=get_authenticated_user())
|
||||
|
||||
model.notification.delete_matching_notifications(get_authenticated_user(), 'org_team_invite',
|
||||
|
|
Reference in a new issue