Exemplo n.º 1
0
    def execute_platform_action(self, action, delete_policykit_post=True):
        from policyengine.models import LogAPICall, CommunityUser
        from policyengine.views import clean_up_proposals

        logger.info('here')
        obj = action

        if not obj.community_origin or (obj.community_origin and obj.community_revert):
            logger.info('EXECUTING ACTION BELOW:')
            call = self.API + obj.ACTION
            logger.info(call)

            obj_fields = []
            for f in obj._meta.get_fields():
                if f.name not in ['polymorphic_ctype',
                                  'community',
                                  'initiator',
                                  'communityapi_ptr',
                                  'platformaction',
                                  'platformactionbundle',
                                  'community_revert',
                                  'community_origin',
                                  'is_bundled'
                                  ]:
                    obj_fields.append(f.name)

            data = {}

            for item in obj_fields:
                try:
                    if item != 'id':
                        value = getattr(obj, item)
                        data[item] = value
                except obj.DoesNotExist:
                    continue

            res = LogAPICall.make_api_call(self, data, call)

            if delete_policykit_post:
                posted_action = None
                if action.is_bundled:
                    bundle = action.platformactionbundle_set.all()
                    if bundle.exists():
                        posted_action = bundle[0]
                else:
                    posted_action = action

                if posted_action.community_post:
                    data = {}
                    call = 'channels/{0}/messages/{1}'.format(obj.channel, posted_action.community_post)
                    _ = LogAPICall.make_api_call(self, data, call)

            if res['ok']:
                clean_up_proposals(action, True)
            else:
                error_message = res['error']
                logger.info(error_message)
                clean_up_proposals(action, False)
        else:
            clean_up_proposals(action, True)
Exemplo n.º 2
0
    def _execute_platform_action(self, action, delete_policykit_post=True):
        from policyengine.models import LogAPICall

        obj = action

        if not obj.community_origin or (obj.community_origin and obj.community_revert):
            call = obj.ACTION

            obj_fields = []
            for f in obj._meta.get_fields():
                if f.name not in ['polymorphic_ctype',
                                  'community',
                                  'initiator',
                                  'communityapi_ptr',
                                  'governableaction',
                                  'governableactionbundle',
                                  'community_revert',
                                  'community_origin',
                                  'is_bundled'
                                  ]:
                    obj_fields.append(f.name)

            data = {}

            for item in obj_fields:
                try:
                    if item != 'id':
                        value = getattr(obj, item)
                        data[item] = value
                except obj.DoesNotExist:
                    continue

            res = LogAPICall.make_api_call(self, data, call)

            if delete_policykit_post:
                posted_action = None
                if action.is_bundled:
                    bundle = action.governableactionbundle_set.all()
                    if bundle.exists():
                        posted_action = bundle[0]
                else:
                    posted_action = action

                for e in Proposal.objects.filter(action=posted_action):
                    if e.vote_post_id:
                        data = {}
                        call = 'posts/{0}.json'.format(e.vote_post_id)
                        _ = LogAPICall.make_api_call(self, data, call)

            if not res['ok']:
                error_message = res['error']
                logger.info(error_message)
Exemplo n.º 3
0
    def post_message(self, text, users=[], post_type="channel", channel=None, thread_ts=None, reply_broadcast=False):
        """
        POST TYPES:
        mpim = multi person message
        im = direct message(s)
        channel = post in channel
        ephemeral = ephemeral post(s) in channel that is only visible to one user
        """
        usernames = [user.username for user in users or []]
        if len(usernames) == 0 and post_type in ["mpim", "im", "ephemeral"]:
            raise Exception(f"user(s) required for post type '{post_type}'")
        if channel is None and post_type in ["channel", "ephemeral"]:
            raise Exception(f"channel required for post type '{post_type}'")

        values = {"text": text}

        if post_type == "mpim":
            # post to group message
            values["users"] = usernames
            response = LogAPICall.make_api_call(self, values, "slack.post-message")
            return [response["ts"]]

        if post_type == "im":
            # message each user individually
            posts = []
            for username in usernames:
                values["users"] = [username]
                response = LogAPICall.make_api_call(self, values, "slack.post-message")
                posts.append(response["ts"])
            return posts

        if post_type == "ephemeral":
            posts = []
            for username in usernames:
                values["user"] = username
                values["channel"] = channel
                response = self.__make_generic_api_call("chat.postEphemeral", values)
                posts.append(response["message_ts"])
            return posts

        if post_type == "channel":
            values["channel"] = channel
            if thread_ts:
                # post message in response as a threaded message reply
                values["thread_ts"] = thread_ts
                values["reply_broadcast"] = reply_broadcast

            response = LogAPICall.make_api_call(self, values, "slack.post-message")
            return [response["ts"]]

        return []
Exemplo n.º 4
0
def initiate_action_vote(policy, action, users, template=None):
    from policyengine.models import LogAPICall

    policy_message_default = "This action is governed by the following policy: " + policy.description + '. Vote by replying +1 or -1 to this post.'

    if not template:
        policy_message = policy_message_default
    else:
        policy_message = template

    values = {
        'ad': False,
        'api_type': 'json',
        'kind': 'self',
        'sr': action.community.community_name,
        'text': policy_message,
        'title': 'Vote on action'
    }

    res = LogAPICall.make_api_call(policy.community, values, 'api/submit')

    logger.info(res)

    action.community_post = res['json']['data']['name']
    action.save()
Exemplo n.º 5
0
 def __make_generic_api_call(self, method: str, values):
     """Make any Slack method request using Metagov action 'slack.method' """
     cleaned = {k: v
                for k, v in values.items()
                if v is not None} if values else {}
     return LogAPICall.make_api_call(self, {
         "method_name": method,
         **cleaned
     }, SLACK_METHOD_ACTION)
Exemplo n.º 6
0
    def __make_generic_api_call(self, method: str, values):
        """Make any Slack method request using Metagov action 'slack.method' """
        cleaned = {k: v
                   for k, v in values.items()
                   if v is not None} if values else {}

        # Use LogAPICall so the API call is recorded in the database. This gets used by the `is_policykit_action` helper to determine,
        # in the next few seconds when we receive an event, that this call was initiated by PolicyKIt- NOT a user- so we shouldn't govern it.
        return LogAPICall.make_api_call(self,
                                        values={
                                            "method_name": method,
                                            **cleaned
                                        },
                                        call=method)
Exemplo n.º 7
0
def initiate_action_vote(proposal, users, text=None):
    from policyengine.models import LogAPICall
    policy = proposal.policy
    action = proposal.action
    policy_message = text or default_boolean_vote_message(proposal.policy)

    values = {
        'ad': False,
        'api_type': 'json',
        'kind': 'self',
        'sr': action.community.community_name,
        'text': policy_message,
        'title': 'Vote on action'
    }

    res = LogAPICall.make_api_call(policy.community, values, 'api/submit')

    logger.info(res)

    proposal.vote_post_id = res['json']['data']['name']
    proposal.save()
Exemplo n.º 8
0
    def _execute_platform_action(self, action, delete_policykit_post=True):
        from policyengine.models import LogAPICall

        logger.info(action)

        obj = action

        if not obj.community_origin or (obj.community_origin
                                        and obj.community_revert):
            logger.info('EXECUTING ACTION BELOW:')
            call = obj.ACTION
            logger.info(call)

            obj_fields = []
            for f in obj._meta.get_fields():
                if f.name not in [
                        'polymorphic_ctype', 'community', 'initiator',
                        'communityaction_ptr', 'governableaction',
                        'governableactionbundle', 'community_revert',
                        'community_origin', 'is_bundled', 'data',
                        'vote_post_id', 'name'
                ]:
                    obj_fields.append(f.name)

            data = {}

            for item in obj_fields:
                try:
                    if item != 'id':
                        value = getattr(obj, item)
                        data[item] = value
                except obj.DoesNotExist:
                    continue

            data['sr'] = action.community.community_name
            data['api_type'] = 'json'

            res = LogAPICall.make_api_call(self, data, call, action=action)

            logger.info(res)

            # delete PolicyKit Post
            logger.info('delete policykit post')
            if delete_policykit_post:
                posted_action = None
                if action.is_bundled:
                    bundle = action.governableactionbundle_set.all()
                    if bundle.exists():
                        posted_action = bundle[0]
                else:
                    posted_action = action

                for e in Proposal.objects.filter(action=posted_action):
                    if e.vote_post_id:
                        values = {'id': e.vote_post_id}
                        call = 'api/remove'
                        _ = LogAPICall.make_api_call(self, values, call)

            # approve post
            logger.info('approve executed post')
            action.community.make_call('api/approve',
                                       {'id': res['json']['data']['name']})
Exemplo n.º 9
0
def post_policy(policy, action, users=None, post_type='channel', template=None, channel=None):
    from policyengine.models import LogAPICall, PlatformActionBundle

    if action.action_type == "PlatformActionBundle" and action.bundle_type == PlatformActionBundle.ELECTION:
        policy_message_default = "This action is governed by the following policy: " + policy.explanation + '. Decide between options below:\n'

        bundled_actions = action.bundled_actions.all()
        for num, a in enumerate(bundled_actions):
            policy_message_default += ':' + NUMBERS[num] + ': ' + str(a) + '\n'
    else:
        policy_message_default = "This action is governed by the following policy: " + policy.description + '. Vote with :thumbsup: or :thumbsdown: on this post.'

    values = {'token': policy.community.access_token}

    if not template:
        policy_message = policy_message_default
    else:
        policy_message = template

    values['text'] = policy_message

    # mpim - all users
    # im each user
    # channel all users
    # channel ephemeral users

    if post_type == "mpim":
        api_call = 'chat.postMessage'
        usernames = [user.username for user in users]
        info = {'token': policy.community.access_token}
        info['users'] = ','.join(usernames)
        call = policy.community.API + 'conversations.open'
        res = LogAPICall.make_api_call(policy.community, info, call)
        channel = res['channel']['id']
        values['channel'] = channel

        call = policy.community_integration.API + api_call
        res = LogAPICall.make_api_call(policy.community, values, call)

        action.community_post = res['ts']
        action.save()

    elif post_type == 'im':
        api_call = 'chat.postMessage'
        usernames = [user.username for user in users]

        for username in usernames:
            info = {'token': policy.community.access_token}
            info['users'] = username
            call = policy.community.API + 'conversations.open'
            res = LogAPICall.make_api_call(policy.community, info, call)
            channel = res['channel']['id']
            values['channel'] = channel

            call = policy.community.API + api_call
            res = LogAPICall.make_api_call(policy.community, values, call)

            action.community_post = res['ts']
            action.save()

    elif post_type == 'ephemeral':
        api_call = 'chat.postEphemeral'
        usernames = [user.username for user in users]

        for username in usernames:
            values['user'] = username

            if channel:
                values['channel'] = channel
            else:
                if action.action_type == "PlatformAction":
                    values['channel'] = action.channel
                else:
                    a = action.bundled_actions.all()[0]
                    values['channel'] = a.channel
            call = policy.community.API + api_call

            res = LogAPICall.make_api_call(policy.community, values, call)

            action.community_post = res['ts']
            action.save()
    elif post_type == 'channel':
        api_call = 'chat.postMessage'
        if channel:
            values['channel'] = channel
        else:
            if action.action_type == "PlatformAction":
                values['channel'] = action.channel
            else:
                a = action.bundled_actions.all()[0]
                values['channel'] = a.channel

        call = policy.community.API + api_call
        res = LogAPICall.make_api_call(policy.community, values, call)

        action.community_post = res['ts']
        action.save()
Exemplo n.º 10
0
    def execute_platform_action(self, action, delete_policykit_post=True):

        from policyengine.models import LogAPICall, CommunityUser
        from policyengine.views import clean_up_proposals

        logger.info('here')
        obj = action

        if not obj.community_origin or (obj.community_origin
                                        and obj.community_revert):
            logger.info('EXECUTING ACTION BELOW:')
            call = obj.ACTION
            logger.info(call)

            obj_fields = []
            for f in obj._meta.get_fields():
                if f.name not in [
                        'polymorphic_ctype', 'community', 'initiator',
                        'communityapi_ptr', 'platformaction',
                        'platformactionbundle', 'community_revert',
                        'community_origin', 'is_bundled', 'proposal',
                        'platformaction_ptr', 'data', 'community_post'
                ]:
                    obj_fields.append(f.name)

            data = {}

            if obj.AUTH == "user":
                data['token'] = action.proposal.author.access_token
                if not data['token']:
                    admin_user = CommunityUser.objects.filter(
                        is_community_admin=True)[0]
                    data['token'] = admin_user.access_token
            elif obj.AUTH == "admin_bot":
                if action.proposal.author.is_community_admin:
                    data['token'] = action.proposal.author.access_token
                else:
                    data['token'] = self.access_token
            elif obj.AUTH == "admin_user":
                admin_user = CommunityUser.objects.filter(
                    is_community_admin=True)[0]
                data['token'] = admin_user.access_token
            else:
                data['token'] = self.access_token

            for item in obj_fields:
                try:
                    if item != 'id':
                        value = getattr(obj, item)
                        data[item] = value
                except obj.DoesNotExist:
                    continue

            res = LogAPICall.make_api_call(self, data, call)

            # delete PolicyKit Post
            if delete_policykit_post:
                posted_action = None
                if action.is_bundled:
                    bundle = action.platformactionbundle_set.all()
                    if bundle.exists():
                        posted_action = bundle[0]
                else:
                    posted_action = action

                if posted_action.community_post:
                    admin_user = CommunityUser.objects.filter(
                        is_community_admin=True)[0]
                    values = {
                        'token': admin_user.access_token,
                        'ts': posted_action.community_post,
                        'channel': obj.channel
                    }
                    call = 'chat.delete'
                    _ = LogAPICall.make_api_call(self, values, call)

            if res['ok']:
                clean_up_proposals(action, True)
            else:
                error_message = res['error']
                logger.info(error_message)
                clean_up_proposals(action, False)

        else:
            clean_up_proposals(action, True)
Exemplo n.º 11
0
def slack_install(request):
    logger.debug(f"Slack installation completed: {request.GET}")

    # metagov identifier for the "parent community" to install Slack to
    metagov_community_slug = request.GET.get("community")
    is_new_community = False
    try:
        community = Community.objects.get(metagov_slug=metagov_community_slug)
    except Community.DoesNotExist:
        logger.debug(
            f"Community not found: {metagov_community_slug}, creating it")
        is_new_community = True
        community = Community.objects.create(
            metagov_slug=metagov_community_slug)

    # if we're enabling an integration for an existing community, so redirect to the settings page
    redirect_route = "/login" if is_new_community else "/main/settings"

    expected_state = request.session.get("community_install_state")
    if expected_state is None or request.GET.get("state") is None or (
            not request.GET.get("state") == expected_state):
        logger.error(f"expected {expected_state}")
        return redirect(f"{redirect_route}?error=bad_state")

    if request.GET.get("error"):
        return redirect(f"{redirect_route}?error={request.GET.get('error')}")

    # TODO(issue): stop passing user id and token
    user_id = request.GET.get("user_id")
    user_token = request.GET.get("user_token")

    # Get team info from Slack
    response = requests.post(
        f"{settings.METAGOV_URL}/api/internal/action/slack.method",
        json={"parameters": {
            "method_name": "team.info"
        }},
        headers={"X-Metagov-Community": metagov_community_slug},
    )
    if not response.ok:
        return redirect(f"{redirect_route}?error=server_error")
    data = response.json()
    team = data["team"]
    team_id = team["id"]
    readable_name = team["name"]

    # Set readable_name for Community
    if not community.readable_name:
        community.readable_name = readable_name
        community.save()

    user_group, _ = CommunityRole.objects.get_or_create(
        role_name="Base User", name="Slack: " + readable_name + ": Base User")

    slack_community = SlackCommunity.objects.filter(team_id=team_id).first()
    if slack_community is None:
        logger.debug(f"Creating new SlackCommunity under {community}")
        slack_community = SlackCommunity.objects.create(
            community=community,
            community_name=readable_name,
            team_id=team_id,
            base_role=user_group,
        )
        user_group.community = slack_community
        user_group.save()

        # get the list of users, create SlackUser object for each user
        logger.debug(f"Fetching user list for {slack_community}...")
        from policyengine.models import LogAPICall

        response = LogAPICall.make_api_call(slack_community,
                                            {"method_name": "users.list"},
                                            SLACK_METHOD_ACTION)
        for new_user in response["members"]:
            if (not new_user["deleted"]) and (not new_user["is_bot"]) and (
                    new_user["id"] != "USLACKBOT"):
                u, _ = SlackUser.objects.get_or_create(
                    username=new_user["id"],
                    readable_name=new_user["real_name"],
                    avatar=new_user["profile"]["image_24"],
                    community=slack_community,
                )
                if user_token and user_id and new_user["id"] == user_id:
                    logger.debug(
                        f"Storing access_token for installing user ({user_id})"
                    )
                    # Installer has is_community_admin because they are an admin in Slack, AND we requested special user scopes from them
                    u.is_community_admin = True
                    u.access_token = user_token
                    u.save()

        context = {
            "server_url": SERVER_URL,
            "starterkits": get_starterkits_info(),
            "community_name": slack_community.community_name,
            "creator_token": user_token,
            "platform": "slack",
            # redirect to settings page or login page depending on whether it's a new community
            "redirect": redirect_route
        }
        return render(request, "policyadmin/init_starterkit.html", context)

    else:
        logger.debug("community already exists, updating name..")
        slack_community.community_name = readable_name
        slack_community.save()
        slack_community.community.readable_name = readable_name
        slack_community.community.save()

        # Store token for the user who (re)installed Slack
        if user_token and user_id:
            installer = SlackUser.objects.filter(community=slack_community,
                                                 username=user_id).first()
            if installer is not None:
                logger.debug(
                    f"Storing access_token for installing user ({user_id})")
                # Installer has is_community_admin because they are an admin in Slack, AND we requested special user scopes from them
                installer.is_community_admin = True
                installer.access_token = user_token
                installer.save()
            else:
                logger.debug(
                    f"User '{user_id}' is re-installing but no SlackUser exists for them, creating one.."
                )
                response = slack_community.make_call("slack.method", {
                    "method_name": "users.info",
                    "user": user_id
                })
                user_info = response["user"]
                user_fields = get_slack_user_fields(user_info)
                user_fields["is_community_admin"] = True
                user_fields["access_token"] = user_token
                SlackUser.objects.update_or_create(
                    community=slack_community,
                    username=user_info["id"],
                    defaults=user_fields,
                )

        return redirect(f"{redirect_route}?success=true")