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)
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)
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 []
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()
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)
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)
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()
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']})
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()
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)
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")