From 1c4a4dd2597dd85710b171bd704c09d5ec3c5ce0 Mon Sep 17 00:00:00 2001 From: Thomas Steen Rasmussen Date: Fri, 13 Apr 2018 20:22:19 +0200 Subject: [PATCH 1/9] change irc channel stuff so each team can have both a private and a public irc channel, introduce the concept of a volunteer channel which all teammembers of all teams get access to --- src/bornhack/environment_settings.py.dist | 1 + src/events/handler.py | 8 +- src/ircbot/irc3module.py | 209 ++++++++++++------ src/profiles/signal_handlers.py | 17 +- .../migrations/0038_auto_20180412_1844.py | 45 ++++ src/teams/migrations/0039_fix_irc_channels.py | 33 +++ .../migrations/0040_auto_20180412_2109.py | 31 +++ .../migrations/0041_auto_20180412_2231.py | 24 ++ .../migrations/0042_auto_20180413_1933.py | 30 +++ src/teams/models.py | 87 +++++--- src/teams/signal_handlers.py | 15 +- src/teams/templates/team_detail.html | 11 +- src/teams/utils.py | 18 ++ src/teams/views.py | 2 +- .../management/commands/bootstrap-devsite.py | 4 +- 15 files changed, 420 insertions(+), 115 deletions(-) create mode 100644 src/teams/migrations/0038_auto_20180412_1844.py create mode 100644 src/teams/migrations/0039_fix_irc_channels.py create mode 100644 src/teams/migrations/0040_auto_20180412_2109.py create mode 100644 src/teams/migrations/0041_auto_20180412_2231.py create mode 100644 src/teams/migrations/0042_auto_20180413_1933.py create mode 100644 src/teams/utils.py diff --git a/src/bornhack/environment_settings.py.dist b/src/bornhack/environment_settings.py.dist index 8f2bc189..191c3d5e 100644 --- a/src/bornhack/environment_settings.py.dist +++ b/src/bornhack/environment_settings.py.dist @@ -88,4 +88,5 @@ IRCBOT_SERVER_HOSTNAME='{{ django_ircbot_server }}' IRCBOT_SERVER_PORT=6697 IRCBOT_SERVER_USETLS=True IRCBOT_PUBLIC_CHANNEL='{{ django_ircbot_public_channel }}' +IRCBOT_VOLUNTEER_CHANNEL='{{ django_ircbot_volunteer_channel }}' diff --git a/src/events/handler.py b/src/events/handler.py index b38cb152..77d21e04 100644 --- a/src/events/handler.py +++ b/src/events/handler.py @@ -24,7 +24,7 @@ def handle_team_event(eventtype, irc_message=None, irc_timeout=60, email_templat if not eventtype.teams: # no routes found for this eventtype, do nothing - logger.error("No routes round for eventtype %s" % eventtype) + #logger.error("No routes round for eventtype %s" % eventtype) return # loop over routes (teams) for this eventtype @@ -48,13 +48,13 @@ def team_irc_notification(team, eventtype, irc_message=None, irc_timeout=60): logger.error("IRC notifications not enabled for eventtype %s" % eventtype) return - if not team.irc_channel or not team.irc_channel_name: - logger.error("team %s is not IRC enabled" % team) + if not team.private_irc_channel_name or not team.private_irc_channel_bot: + logger.error("team %s does not have a private IRC channel" % team) return # send an IRC message to the the channel for this team add_irc_message( - target=team.irc_channel_name, + target=team.private_irc_channel_name, message=irc_message, timeout=60 ) diff --git a/src/ircbot/irc3module.py b/src/ircbot/irc3module.py index d8b421a0..bd57ae17 100644 --- a/src/ircbot/irc3module.py +++ b/src/ircbot/irc3module.py @@ -4,6 +4,8 @@ from teams.models import Team, TeamMember from django.conf import settings from django.utils import timezone from events.models import Routing +from teams.utils import get_team_from_irc_channel + import logging logger = logging.getLogger("bornhack.%s" % __name__) @@ -63,7 +65,7 @@ class Plugin(object): """Triggered when a channel is joined by someone, including the bot itself""" if mask.nick == self.bot.nick: # the bot just joined a channel - if channel in self.get_managed_team_channels(): + if channel in self.get_managed_team_channels() or channel == settings.IRCBOT_PUBLIC_CHANNEL or channel == settings.IRCBOT_VOLUNTEER_CHANNEL: logger.debug("Just joined a channel I am supposed to be managing, asking ChanServ for info about %s" % channel) self.bot.privmsg(settings.IRCBOT_CHANSERV_MASK, "info %s" % channel) return @@ -93,7 +95,7 @@ class Plugin(object): ############################################################################################### - ### custom irc3 methods + ### custom irc3 methods below here @irc3.extend def do_stuff(self): @@ -151,7 +153,7 @@ class Plugin(object): Compare the list of IRC channels the bot is currently in with the list of IRC channels the bot is supposed to be in. Join or part channels as needed. """ - desired_channel_list = list(set(list(self.get_managed_team_channels()) + list(self.get_unmanaged_team_channels()) + [settings.IRCBOT_PUBLIC_CHANNEL])) + desired_channel_list = self.bot.get_desired_channel_list() #logger.debug("Inside check_irc_channels(), desired_channel_list is: %s and self.bot.channels is: %s" % (desired_channel_list, self.bot.channels.keys())) # loop over desired_channel_list, join as needed @@ -167,70 +169,122 @@ class Plugin(object): self.bot.part(channel, "I am no longer needed here") + @irc3.extend + def get_desired_channel_list(self): + """ + Return a list of strings of all the IRC channels the bot is supposed to be in + """ + desired_channel_list = self.get_managed_team_channels() + desired_channel_list += self.get_unmanaged_team_channels() + desired_channel_list.append(settings.IRCBOT_PUBLIC_CHANNEL) + desired_channel_list.append(settings.IRCBOT_VOLUNTEER_CHANNEL) + return desired_channel_list + + @irc3.extend def get_managed_team_channels(self): """ - Return a unique list of team IRC channels which the bot is supposed to be managing. + Return a list of team IRC channels which the bot is supposed to be managing. """ - return Team.objects.filter( - irc_channel=True, - irc_channel_managed=True - ).values_list("irc_channel_name", flat=True) + pubchans = Team.objects.filter( + public_irc_channel_name__isnull=False, + public_irc_channel_bot=True, + public_irc_channel_managed=True + ).values_list("public_irc_channel_name", flat=True) + + privchans = Team.objects.filter( + private_irc_channel_name__isnull=False, + private_irc_channel_bot=True, + private_irc_channel_managed=True + ).values_list("private_irc_channel_name", flat=True) + + return list(pubchans) + list(privchans) @irc3.extend def get_unmanaged_team_channels(self): """ - Return a unique list of team IRC channels which the bot is not supposed to be managing. + Return a list of team IRC channels which the bot is supposed to be in, but not managing. """ - return Team.objects.filter( - irc_channel=True, - irc_channel_managed=False - ).values_list("irc_channel_name", flat=True) + pubchans = Team.objects.filter( + public_irc_channel_name__isnull=False, + public_irc_channel_bot=True, + public_irc_channel_managed=False + ).values_list("public_irc_channel_name", flat=True) + + privchans = Team.objects.filter( + private_irc_channel_name__isnull=False, + private_irc_channel_bot=True, + private_irc_channel_managed=False + ).values_list("private_irc_channel_name", flat=True) + + return list(pubchans) + list(privchans) @irc3.extend - def setup_private_channel(self, team): + def setup_private_channel(self, channel): """ - Configures a private team IRC channel by setting modes and adding all members to ACL + Configures a private IRC channel by setting modes and adding all members to ACL if it is a team channel """ + logger.debug("Inside setup_private_channel() for %s" % channel) + # basic private channel modes - self.bot.privmsg(settings.IRCBOT_CHANSERV_MASK, "SET %s mlock +inpst" % team.irc_channel_name) - self.bot.privmsg(settings.IRCBOT_CHANSERV_MASK, "SET %s SECURE on" % team.irc_channel_name) - self.bot.privmsg(settings.IRCBOT_CHANSERV_MASK, "SET %s RESTRICTED on" % team.irc_channel_name) + self.bot.privmsg(settings.IRCBOT_CHANSERV_MASK, "SET %s mlock +inpst" % channel) + self.bot.privmsg(settings.IRCBOT_CHANSERV_MASK, "SET %s SECURE on" % channel) + self.bot.privmsg(settings.IRCBOT_CHANSERV_MASK, "SET %s RESTRICTED on" % channel) # add the bot to the ACL - self.bot.add_user_to_team_channel_acl( + self.bot.add_user_to_channel_acl( username=settings.IRCBOT_NICK, - channel=team.irc_channel_name + channel=channel, + invite=True ) + team = get_team_from_irc_channel(channel) + if team: + # this is a team channel, add team members to channel ACL + self.bot.add_team_members_to_channel_acl(team) + # make sure private_irc_channel_fix_needed is set to False and save + team.private_irc_channel_fix_needed=False + team.save() + + + @irc3.extend + def setup_public_channel(self, channel): + """ + Configures a public IRC channel by setting modes and giving all team members +oO if it is a team channel + """ + logger.debug("Inside setup_public_channel() for %s" % channel) + + # basic private channel modes + self.bot.privmsg(settings.IRCBOT_CHANSERV_MASK, "SET %s mlock +nt-lk" % channel) + self.bot.privmsg(settings.IRCBOT_CHANSERV_MASK, "SET %s SECURE off" % channel) + self.bot.privmsg(settings.IRCBOT_CHANSERV_MASK, "SET %s RESTRICTED off" % channel) + + team = get_team_from_irc_channel(channel) + if team: + # add members to ACL + self.bot.add_team_members_to_channel_acl(team) + # make sure public_irc_channel_fix_needed is set to False and save + team.public_irc_channel_fix_needed=False + team.save() + + + @irc3.extend + def add_team_members_to_channel_acl(self, team): + """ + Handles initial ACL for team channels. + Sets membership.irc_acl_fix_needed=True for each approved teammember with a NickServ username + """ # add all members to the acl for membership in team.memberships.all(): if membership.approved and membership.user.profile.nickserv_username: - self.bot.add_user_to_team_channel_acl( - username=membership.user.profile.nickserv_username, - channel=membership.team.irc_channel_name, - ) - - # mark membership as irc_channel_acl_ok=True and save - membership.irc_channel_acl_ok=True + membership.irc_acl_fix_needed=True membership.save() @irc3.extend - def setup_public_channel(self, team): - """ - Configures a public team IRC channel (by unsetting SECURE and RESTRICTED modes used by private channels and setting mlock back to the default +nt-lk) - """ - # basic private channel modes - self.bot.privmsg(settings.IRCBOT_CHANSERV_MASK, "SET %s mlock +nt-lk" % team.irc_channel_name) - self.bot.privmsg(settings.IRCBOT_CHANSERV_MASK, "SET %s SECURE off" % team.irc_channel_name) - self.bot.privmsg(settings.IRCBOT_CHANSERV_MASK, "SET %s RESTRICTED off" % team.irc_channel_name) - - - @irc3.extend - def add_user_to_team_channel_acl(self, username, channel): + def add_user_to_channel_acl(self, username, channel, invite): """ Add user to team IRC channel ACL """ @@ -243,21 +297,20 @@ class Plugin(object): }, ) - # also add autoinvite for this username - self.bot.mode(channel, '+I', '$a:%s' % username) + if invite: + # also add autoinvite for this username + self.bot.mode(channel, '+I', '$a:%s' % username) @irc3.extend def fix_missing_acls(self): """ Called periodically by do_stuff() - Loops over TeamMember objects and adds and removes ACL entries as needed + Loops over TeamMember objects and adds ACL entries as needed + Loops over Team objects and fixes permissions and ACLS as needed """ missing_acls = TeamMember.objects.filter( - team__irc_channel=True, - team__irc_channel_managed=True, - team__irc_channel_private=True, - irc_channel_acl_ok=False + irc_acl_fix_needed=True ).exclude( user__profile__nickserv_username='' ) @@ -267,14 +320,41 @@ class Plugin(object): logger.debug("Found %s memberships which need IRC ACL fixing.." % missing_acls.count()) for membership in missing_acls: - self.bot.add_user_to_team_channel_acl( + # add to team public channel? + if membership.team.public_channel_name and membership.publíc_channel_managed: + self.bot.add_user_to_channel_acl( + username=membership.user.profile.nickserv_username, + channel=membership.team.public_irc_channel_name, + invite=False + ) + + # add to team private channel? + if membership.team.private_channel_name and membership.private_channel_managed: + self.bot.add_user_to_channel_acl( + username=membership.user.profile.nickserv_username, + channel=membership.team.private_irc_channel_name, + invite=True + ) + + # add to volunteer channel + self.bot.add_user_to_channel_acl( username=membership.user.profile.nickserv_username, - channel=membership.team.irc_channel_name, + chanel=settings.IRCBOT_VOLUNTEER_CHANNEL, + invite=True ) + # mark membership as irc_channel_acl_ok=True and save - membership.irc_channel_acl_ok=True + membership.irc_acl_fix_neede=False membership.save() + for team in Team.objects.filter(private_irc_channel_fix_needed=True): + logger.debug("Team %s private IRC channel %s needs ACL fixing" % (team, team.private_irc_channel_name)) + self.bot.setup_private_channel(team.private_irc_channel_name) + + for team in Team.objects.filter(public_irc_channel_fix_needed=True): + logger.debug("Team %s public IRC channel %s needs ACL fixing" % (team, team.public_irc_channel_name)) + self.bot.setup_public_channel(team.public_irc_channel_name) + ############################################################################################### ### services (ChanServ & NickServ) methods @@ -294,8 +374,8 @@ class Plugin(object): # the irc channel is not registered channel = match.group(1) # get a list of the channels we are supposed to be managing - if channel in self.bot.get_managed_team_channels(): - # we want to register this channel! but we can only do so if we have a @ in the channel + if channel in self.bot.get_managed_team_channels() or channel == settings.IRCBOT_VOLUNTEER_CHANNEL: + # we want to register this channel! though we can only do so if we have a @ in the channel if self.bot.nick in self.bot.channels[channel].modes['@']: logger.debug("ChanServ says channel %s is not registered, bot is supposed to be managing this channel, registering it with chanserv" % channel) self.bot.privmsg(settings.IRCBOT_CHANSERV_MASK, "register %s" % channel) @@ -314,27 +394,30 @@ class Plugin(object): botnick = match.group(2) logger.debug("Channel %s was registered with ChanServ, looking up Team..." % channel) - # if this channel is a private team IRC channel set modes and add initial ACL - try: - team = Team.objects.get(irc_channel_name=channel) - except Team.DoesNotExist: - logger.debug("Unable to find Team matching IRC channel %s" % channel) + team = get_team_from_irc_channel(channel) + if team: + if team.private_irc_channel_name == channel: + # set private channel modes, +I and ACL + self.bot.setup_private_channel(channel) + else: + # set public channel modes and +oO for all members + self.bot.setup_public_channel(channel) return + logger.debug("Unable to find Team matching IRC channel %s" % channel) - if not team.irc_channel_private: - # this channel is not private, no mode change and ACL needed + # check if this channel is the volunteer channel + if channel == settings.IRCBOT_VOLUNTEER_CHANNEL: + logger.debug("%s is the volunteer channel, setting up" channel) + self.bot.setup_private_channel(channel) + # lets handle the volunteer channels initial ACL manually.. return - # set channel modes and ACL - self.bot.setup_private_channel(team) - return - logger.debug("Unhandled ChanServ message: %s" % kwargs['data']) @irc3.extend def handle_nickserv_privmsg(self, **kwargs): - """th + """ Handles messages from NickServ on networks with Services. """ logger.debug("Got a message from NickServ") diff --git a/src/profiles/signal_handlers.py b/src/profiles/signal_handlers.py index 67802844..f8f63063 100644 --- a/src/profiles/signal_handlers.py +++ b/src/profiles/signal_handlers.py @@ -57,24 +57,29 @@ def public_credit_name_changed(instance, original): def nickserv_username_changed(instance, original): """ - Check if profile.nickserv_username was changed, and uncheck irc_channel_acl_ok if so + Check if profile.nickserv_username was changed, and check irc_acl_fix_needed if so This will be picked up by the IRC bot and fixed as needed """ if instance.nickserv_username and original and instance.nickserv_username != original.nickserv_username: - logger.debug("profile.nickserv_username changed for user %s, setting irc_channel_acl_ok=False" % instance.user.username) + logger.debug("profile.nickserv_username changed for user %s, setting membership.irc_acl_fix_needed=True" % instance.user.username) # find team memberships for this user from teams.models import TeamMember memberships = TeamMember.objects.filter( user=instance.user, approved=True, - team__irc_channel=True, - team__irc_channel_managed=True, - team__irc_channel_private=True, ) # loop over memberships for membership in memberships: - membership.irc_channel_acl_ok = False + if not membership.team.public_irc_channel_name and not membership.team.private_irc_channel_name: + # no irc channels for this team + continue + if not membership.team.public_irc_channel_managed and not membership.team.private_irc_channel_managed: + # irc channel(s) are not managed for this team + continue + + # ok, mark this membership as in need of fixing + membership.irc_acl_fix_needed = False membership.save() diff --git a/src/teams/migrations/0038_auto_20180412_1844.py b/src/teams/migrations/0038_auto_20180412_1844.py new file mode 100644 index 00000000..31a4c9ea --- /dev/null +++ b/src/teams/migrations/0038_auto_20180412_1844.py @@ -0,0 +1,45 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.10.5 on 2018-04-12 16:44 +from __future__ import unicode_literals + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('teams', '0037_auto_20180408_1416'), + ] + + operations = [ + migrations.AddField( + model_name='team', + name='private_irc_channel_bot', + field=models.BooleanField(default=False, help_text='Check to make the bot join the teams private IRC channel. Leave unchecked to disable the IRC bot for this channel.'), + ), + migrations.AddField( + model_name='team', + name='private_irc_channel_managed', + field=models.BooleanField(default=False, help_text='Check to make the bot manage the private IRC channel by registering it with NickServ, setting +I and maintaining the ACL.'), + ), + migrations.AddField( + model_name='team', + name='private_irc_channel_name', + field=models.CharField(blank=True, help_text='The private IRC channel for this team. Will be shown to team members on the team page. Leave empty if the team has no private IRC channel.', max_length=50, null=True, unique=True), + ), + migrations.AddField( + model_name='team', + name='public_irc_channel_bot', + field=models.BooleanField(default=False, help_text='Check to make the bot join the teams public IRC channel. Leave unchecked to disable the IRC bot for this channel.'), + ), + migrations.AddField( + model_name='team', + name='public_irc_channel_managed', + field=models.BooleanField(default=False, help_text='Check to make the bot manage the teams public IRC channel by registering it with NickServ.'), + ), + migrations.AddField( + model_name='team', + name='public_irc_channel_name', + field=models.CharField(blank=True, help_text='The public IRC channel for this team. Will be shown on the team page so people know how to reach the team. Leave empty if the team has no public IRC channel.', max_length=50, null=True, unique=True), + ), + ] diff --git a/src/teams/migrations/0039_fix_irc_channels.py b/src/teams/migrations/0039_fix_irc_channels.py new file mode 100644 index 00000000..cff67d54 --- /dev/null +++ b/src/teams/migrations/0039_fix_irc_channels.py @@ -0,0 +1,33 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.10.5 on 2018-04-12 15:46 +from __future__ import unicode_literals + +from django.db import migrations + +def fix_irc_channels(apps, schema_editor): + Team = apps.get_model('teams', 'Team') + for team in Team.objects.filter(irc_channel=True): + print("fixing irc channel for team %s" % team.name) + if team.irc_channel_private: + team.private_irc_channel_name=team.irc_channel_name + if team.irc_channel_managed: + team.private_irc_channel_managed=True + team.private_irc_channel_bot=True + else: + team.public_irc_channel_name=team.irc_channel_name + if team.irc_channel_managed: + team.public_irc_channel_managed=True + team.public_irc_channel_bot=True + team.save() + + +class Migration(migrations.Migration): + + dependencies = [ + ('teams', '0038_auto_20180412_1844'), + ] + + operations = [ + migrations.RunPython(fix_irc_channels), + ] + diff --git a/src/teams/migrations/0040_auto_20180412_2109.py b/src/teams/migrations/0040_auto_20180412_2109.py new file mode 100644 index 00000000..dc0db141 --- /dev/null +++ b/src/teams/migrations/0040_auto_20180412_2109.py @@ -0,0 +1,31 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.10.5 on 2018-04-12 19:09 +from __future__ import unicode_literals + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('teams', '0039_fix_irc_channels'), + ] + + operations = [ + migrations.RemoveField( + model_name='team', + name='irc_channel', + ), + migrations.RemoveField( + model_name='team', + name='irc_channel_managed', + ), + migrations.RemoveField( + model_name='team', + name='irc_channel_name', + ), + migrations.RemoveField( + model_name='team', + name='irc_channel_private', + ), + ] diff --git a/src/teams/migrations/0041_auto_20180412_2231.py b/src/teams/migrations/0041_auto_20180412_2231.py new file mode 100644 index 00000000..933f4868 --- /dev/null +++ b/src/teams/migrations/0041_auto_20180412_2231.py @@ -0,0 +1,24 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.10.5 on 2018-04-12 20:31 +from __future__ import unicode_literals + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('teams', '0040_auto_20180412_2109'), + ] + + operations = [ + migrations.RemoveField( + model_name='teammember', + name='irc_channel_acl_ok', + ), + migrations.AddField( + model_name='teammember', + name='irc_acl_fix_needed', + field=models.BooleanField(default=False, help_text='Maintained by the IRC bot, manual editing should not be needed. Will be set to true when a teammember sets or changes NickServ username, and back to false after the ACL has been fixed by the bot.'), + ), + ] diff --git a/src/teams/migrations/0042_auto_20180413_1933.py b/src/teams/migrations/0042_auto_20180413_1933.py new file mode 100644 index 00000000..279fda50 --- /dev/null +++ b/src/teams/migrations/0042_auto_20180413_1933.py @@ -0,0 +1,30 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.10.5 on 2018-04-13 17:33 +from __future__ import unicode_literals + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('teams', '0041_auto_20180412_2231'), + ] + + operations = [ + migrations.AddField( + model_name='team', + name='private_irc_channel_fix_needed', + field=models.BooleanField(default=False, help_text='Used to indicate to the IRC bot that this teams private IRC channel is in need of a permissions and ACL fix.'), + ), + migrations.AddField( + model_name='team', + name='public_irc_channel_fix_needed', + field=models.BooleanField(default=False, help_text='Used to indicate to the IRC bot that this teams public IRC channel is in need of a permissions and ACL fix.'), + ), + migrations.AlterField( + model_name='team', + name='public_irc_channel_managed', + field=models.BooleanField(default=False, help_text='Check to make the bot manage the teams public IRC channel by registering it with NickServ and setting +Oo for all teammembers.'), + ), + ] diff --git a/src/teams/models.py b/src/teams/models.py index aaaa550b..94affcff 100644 --- a/src/teams/models.py +++ b/src/teams/models.py @@ -6,6 +6,7 @@ from utils.models import CampRelatedModel from django.core.exceptions import ValidationError from django.contrib.auth.models import User from django.core.urlresolvers import reverse_lazy +from django.conf import settings import logging logger = logging.getLogger("bornhack.%s" % __name__) @@ -61,25 +62,44 @@ class Team(CampRelatedModel): ) # IRC related fields - irc_channel = models.BooleanField( - default=False, - help_text='Check to make the IRC bot join the team IRC channel. Leave unchecked to disable IRC bot functionality for this team entirely.', - ) - - irc_channel_name = models.TextField( - default='', + public_irc_channel_name = models.CharField( blank=True, - help_text='Team IRC channel. Leave blank to generate channel name automatically, based on camp shortslug and team shortslug.', + null=True, + unique=True, + max_length=50, + help_text='The public IRC channel for this team. Will be shown on the team page so people know how to reach the team. Leave empty if the team has no public IRC channel.' + ) + public_irc_channel_bot = models.BooleanField( + default=False, + help_text='Check to make the bot join the teams public IRC channel. Leave unchecked to disable the IRC bot for this channel.' + ) + public_irc_channel_managed = models.BooleanField( + default=False, + help_text='Check to make the bot manage the teams public IRC channel by registering it with NickServ and setting +Oo for all teammembers.' + ) + public_irc_channel_fix_needed = models.BooleanField( + default=False, + help_text='Used to indicate to the IRC bot that this teams public IRC channel is in need of a permissions and ACL fix.' ) - irc_channel_managed = models.BooleanField( - default=True, - help_text='Check to make the bot manage the team IRC channel. The bot will register the channel with ChanServ if possible, and manage ACLs as needed.', + private_irc_channel_name = models.CharField( + blank=True, + null=True, + unique=True, + max_length=50, + help_text='The private IRC channel for this team. Will be shown to team members on the team page. Leave empty if the team has no private IRC channel.' ) - - irc_channel_private = models.BooleanField( - default=True, - help_text='Check to make the IRC channel secret and +i (private for team members only using an ACL). Leave unchecked to make the IRC channel public and open for everyone.' + private_irc_channel_bot = models.BooleanField( + default=False, + help_text='Check to make the bot join the teams private IRC channel. Leave unchecked to disable the IRC bot for this channel.' + ) + private_irc_channel_managed = models.BooleanField( + default=False, + help_text='Check to make the bot manage the private IRC channel by registering it with NickServ, setting +I and maintaining the ACL.' + ) + private_irc_channel_fix_needed = models.BooleanField( + default=False, + help_text='Used to indicate to the IRC bot that this teams private IRC channel is in need of a permissions and ACL fix.' ) class Meta: @@ -95,23 +115,36 @@ class Team(CampRelatedModel): slug = slugify(self.name) self.slug = slug + # set shortslug if needed if not self.shortslug: self.shortslug = self.slug - # generate IRC channel name if needed - if self.irc_channel and not self.irc_channel_name: - self.irc_channel_name = "#%s-%s" % (self.camp.shortslug, self.shortslug) - super().save(**kwargs) def clean(self): - # make sure the irc channel name is prefixed with a # if it is set - if self.irc_channel_name and self.irc_channel_name[0] != "#": - self.irc_channel_name = "#%s" % self.irc_channel_name + # make sure the public irc channel name is prefixed with a # if it is set + if self.public_irc_channel_name and self.public_irc_channel_name[0] != "#": + self.public_irc_channel_name = "#%s" % self.public_irc_channel_name - if self.irc_channel_name: - if Team.objects.filter(irc_channel_name=self.irc_channel_name).exclude(pk=self.pk).exists(): - raise ValidationError("This IRC channel name is already in use") + # make sure the private irc channel name is prefixed with a # if it is set + if self.private_irc_channel_name and self.private_irc_channel_name[0] != "#": + self.private_irc_channel_name = "#%s" % self.private_irc_channel_name + + # make sure the channel names are not reserved + if self.public_irc_channel_name == settings.IRCBOT_PUBLIC_CHANNEL or self.public_irc_channel_name == settings.IRCBOT_VOLUNTEER_CHANNEL: + raise ValidationError('The public IRC channel name is reserved') + if self.private_irc_channel_name == settings.IRCBOT_PUBLIC_CHANNEL or self.private_irc_channel_name == settings.IRCBOT_VOLUNTEER_CHANNEL: + raise ValidationError('The private IRC channel name is reserved') + + # make sure public_irc_channel_name is unique + if self.public_irc_channel_name: + if Team.objects.filter(private_irc_channel_name=self.public_irc_channel_name).exclude(pk=self.pk).exists(): + raise ValidationError('The public IRC channel name is already in use!') + + # make sure private_irc_channel_name is unique + if self.private_irc_channel_name: + if Team.objects.filter(public_irc_channel_name=self.private_irc_channel_name).exclude(pk=self.pk).exists(): + raise ValidationError('The private IRC channel name is already in use!') @property def memberships(self): @@ -201,9 +234,9 @@ class TeamMember(CampRelatedModel): help_text="True if this teammember is responsible for this Team. False if not." ) - irc_channel_acl_ok = models.BooleanField( + irc_acl_fix_needed = models.BooleanField( default=False, - help_text="Maintained by the IRC bot, do not edit manually. True if the teammembers NickServ username has been added to the Team IRC channels ACL.", + help_text='Maintained by the IRC bot, manual editing should not be needed. Will be set to true when a teammember sets or changes NickServ username, and back to false after the ACL has been fixed by the bot.', ) class Meta: diff --git a/src/teams/signal_handlers.py b/src/teams/signal_handlers.py index c55a2af7..1a0fa6a6 100644 --- a/src/teams/signal_handlers.py +++ b/src/teams/signal_handlers.py @@ -15,17 +15,16 @@ def teammember_saved(sender, instance, created, **kwargs): if not add_new_membership_email(instance): logger.error('Error adding email to outgoing queue') - # if this team has a private and bot-managed IRC channel check if we need to add this member to ACL - if instance.team.irc_channel and instance.team.irc_channel_managed and instance.team.irc_channel_private: - # if this membership is approved and the member has entered a nickserv_username which not yet been added to the ACL - if instance.approved and instance.user.profile.nickserv_username and not instance.irc_channel_acl_ok: - add_team_channel_acl(instance) def teammember_deleted(sender, instance, **kwargs): """ This signal handler is called whenever a TeamMember instance is deleted """ - if instance.irc_channel_acl_ok and instance.team.irc_channel and instance.team.irc_channel_managed and instance.team.irc_channel_private: - # TODO: we have an ACL entry that needs to be deleted but the bot does not handle it automatically - add_irc_message(instance.team.irc_channel_name, "Teammember %s removed from team. Please remove NickServ user %s from IRC channel ACL manually!" % (instance.user.get_public_credit_name, instance.user.profile.nickserv_username)) + if instance.team.private_irc_channel_name and instance.team.private_irc_channel_managed: + # TODO: remove user from private channel ACL + pass + + if instance.team.public_irc_channel_name and instance.team.public_irc_channel_managed: + # TODO: remove user from public channel ACL + pass diff --git a/src/teams/templates/team_detail.html b/src/teams/templates/team_detail.html index 6f98a05d..9d581363 100644 --- a/src/teams/templates/team_detail.html +++ b/src/teams/templates/team_detail.html @@ -28,15 +28,16 @@ Team: {{ team.name }} | {{ block.super }} {% endif %}
IRC Channel
- {% if team.irc_channel and request.user in team.approved_members.all %} -

The {{ team.name }} Team IRC channel is {{ team.irc_channel_name }} on {{ IRCBOT_SERVER_HOSTNAME }}. - {% if team.irc_channel_private %}The channel is not open to the public. Enter your nickserv username in your Profile to get access.{% else %}The IRC channel is open for everyone to join.{% endif %}

- {% elif team.irc_channel and not team.irc_channel_private %} -

The {{ team.name }} Team IRC channel is {{ team.irc_channel_name }} on {{ IRCBOT_SERVER_HOSTNAME }} and it is open for everyone to join.

+ {% if team.public_irc_channel_name %} +

The {{ team.name }} Team public IRC channel is {{ team.public_irc_channel_name }} on {{ IRCBOT_SERVER_HOSTNAME }}. {% else %}

The {{ team.name }} Team does not have a public IRC channel, but it can be reached through our main IRC channel {{ IRCBOT_PUBLIC_CHANNEL }} on {{ IRCBOT_SERVER_HOSTNAME }}.

{% endif %} + {% if request.user in team.approved_members.all and team.private_irc_channel_name %} +

The {{ team.name }} Team private IRC channel is {{ team.private_irc_channel_name }} on {{ IRCBOT_SERVER_HOSTNAME }}.

+ {% endif %} +

{{ team.name }} Team Members

diff --git a/src/teams/utils.py b/src/teams/utils.py new file mode 100644 index 00000000..ecc9ddff --- /dev/null +++ b/src/teams/utils.py @@ -0,0 +1,18 @@ +from .models import Team + +def get_team_from_irc_channel(channel): + """ + Returns a Team object given an IRC channel name, if possible + """ + # check if this channel is a private_irc_channel for a team + try: + return Team.objects.get(private_irc_channel_name=channel) + except Team.DoesNotExist: + pass + + # check if this channel is a public_irc_channel for a team + try: + return Team.objects.get(public_irc_channel_name=channel) + except Team.DoesNotExist: + return False + diff --git a/src/teams/views.py b/src/teams/views.py index 98d29488..e893a314 100644 --- a/src/teams/views.py +++ b/src/teams/views.py @@ -70,7 +70,7 @@ class TeamDetailView(CampViewMixin, DetailView): class TeamManageView(CampViewMixin, EnsureTeamResponsibleMixin, UpdateView): model = Team template_name = "team_manage.html" - fields = ['description', 'needs_members', 'irc_channel', 'irc_channel_name', 'irc_channel_managed', 'irc_channel_private'] + fields = ['description', 'needs_members', 'public_irc_channel_name', 'public_irc_channel_bot', 'public_irc_channel_managed', 'private_irc_channel_name', 'private_irc_channel_bot', 'private_irc_channel_managed'] slug_url_kwarg = 'team_slug' def get_success_url(self): diff --git a/src/utils/management/commands/bootstrap-devsite.py b/src/utils/management/commands/bootstrap-devsite.py index a3370a3d..ca982bd9 100644 --- a/src/utils/management/commands/bootstrap-devsite.py +++ b/src/utils/management/commands/bootstrap-devsite.py @@ -1631,8 +1631,10 @@ Please note that sleeping in the parking lot is not permitted. If you want to sl ) - self.output("marking 2016 as read_only...") + self.output("marking 2016 and 2017 as read_only...") camp2016.read_only = True camp2016.save() + camp2017.read_only = True + camp2017.save() self.output("done!") From 4d8b70f278db6516ca972867466a080a62e2dda7 Mon Sep 17 00:00:00 2001 From: Thomas Steen Rasmussen Date: Fri, 13 Apr 2018 20:29:57 +0200 Subject: [PATCH 2/9] typo --- src/ircbot/irc3module.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ircbot/irc3module.py b/src/ircbot/irc3module.py index bd57ae17..8d5104a8 100644 --- a/src/ircbot/irc3module.py +++ b/src/ircbot/irc3module.py @@ -407,7 +407,7 @@ class Plugin(object): # check if this channel is the volunteer channel if channel == settings.IRCBOT_VOLUNTEER_CHANNEL: - logger.debug("%s is the volunteer channel, setting up" channel) + logger.debug("%s is the volunteer channel, setting up" % channel) self.bot.setup_private_channel(channel) # lets handle the volunteer channels initial ACL manually.. return From 43aefc880ae3975d97d408ff9fb3a977fece9984 Mon Sep 17 00:00:00 2001 From: Thomas Steen Rasmussen Date: Fri, 13 Apr 2018 20:55:25 +0200 Subject: [PATCH 3/9] add a few things to the TeamAdmin --- src/teams/admin.py | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/src/teams/admin.py b/src/teams/admin.py index 842c728d..0539b6d0 100644 --- a/src/teams/admin.py +++ b/src/teams/admin.py @@ -25,13 +25,24 @@ class TeamAdmin(admin.ModelAdmin): 'camp', 'get_responsible', 'needs_members', + 'public_irc_channel_name', + 'public_irc_channel_bot', + 'public_irc_channel_managed', + 'private_irc_channel_name', + 'private_irc_channel_bot', + 'private_irc_channel_managed', ] list_filter = [ CampPropertyListFilter, 'needs_members', + 'public_irc_channel_bot', + 'public_irc_channel_managed', + 'private_irc_channel_bot', + 'private_irc_channel_managed', ] + @admin.register(TeamMember) class TeamMemberAdmin(admin.ModelAdmin): list_filter = [ From a978ccb6ea7eac1f4b301258bb9079261004d0a1 Mon Sep 17 00:00:00 2001 From: Thomas Steen Rasmussen Date: Fri, 13 Apr 2018 21:29:19 +0200 Subject: [PATCH 4/9] fix the irc acl method, indentation and too early return --- src/ircbot/irc3module.py | 64 +++++++++++++++++++++------------------- 1 file changed, 33 insertions(+), 31 deletions(-) diff --git a/src/ircbot/irc3module.py b/src/ircbot/irc3module.py index 8d5104a8..a8389815 100644 --- a/src/ircbot/irc3module.py +++ b/src/ircbot/irc3module.py @@ -309,51 +309,53 @@ class Plugin(object): Loops over TeamMember objects and adds ACL entries as needed Loops over Team objects and fixes permissions and ACLS as needed """ + # first find all TeamMember objects which needs a loving hand missing_acls = TeamMember.objects.filter( irc_acl_fix_needed=True ).exclude( user__profile__nickserv_username='' ) - if not missing_acls: - return + # loop over them and fix what needs to be fixed + if missing_acls: + logger.debug("Found %s memberships which need IRC ACL fixing.." % missing_acls.count()) + for membership in missing_acls: + # add to team public channel? + if membership.team.public_channel_name and membership.publíc_channel_managed: + self.bot.add_user_to_channel_acl( + username=membership.user.profile.nickserv_username, + channel=membership.team.public_irc_channel_name, + invite=False + ) - logger.debug("Found %s memberships which need IRC ACL fixing.." % missing_acls.count()) - for membership in missing_acls: - # add to team public channel? - if membership.team.public_channel_name and membership.publíc_channel_managed: + # add to team private channel? + if membership.team.private_channel_name and membership.private_channel_managed: + self.bot.add_user_to_channel_acl( + username=membership.user.profile.nickserv_username, + channel=membership.team.private_irc_channel_name, + invite=True + ) + + # add to volunteer channel self.bot.add_user_to_channel_acl( username=membership.user.profile.nickserv_username, - channel=membership.team.public_irc_channel_name, - invite=False - ) - - # add to team private channel? - if membership.team.private_channel_name and membership.private_channel_managed: - self.bot.add_user_to_channel_acl( - username=membership.user.profile.nickserv_username, - channel=membership.team.private_irc_channel_name, + chanel=settings.IRCBOT_VOLUNTEER_CHANNEL, invite=True ) - # add to volunteer channel - self.bot.add_user_to_channel_acl( - username=membership.user.profile.nickserv_username, - chanel=settings.IRCBOT_VOLUNTEER_CHANNEL, - invite=True - ) + # mark membership as irc_channel_acl_ok=True and save + membership.irc_acl_fix_neede=False + membership.save() - # mark membership as irc_channel_acl_ok=True and save - membership.irc_acl_fix_neede=False - membership.save() + # loop over teams where the private channel needs fixing + for team in Team.objects.filter(private_irc_channel_fix_needed=True): + logger.debug("Team %s private IRC channel %s needs ACL fixing" % (team, team.private_irc_channel_name)) + self.bot.setup_private_channel(team.private_irc_channel_name) - for team in Team.objects.filter(private_irc_channel_fix_needed=True): - logger.debug("Team %s private IRC channel %s needs ACL fixing" % (team, team.private_irc_channel_name)) - self.bot.setup_private_channel(team.private_irc_channel_name) - - for team in Team.objects.filter(public_irc_channel_fix_needed=True): - logger.debug("Team %s public IRC channel %s needs ACL fixing" % (team, team.public_irc_channel_name)) - self.bot.setup_public_channel(team.public_irc_channel_name) + # loop over teams where the public channel needs fixing + for team in Team.objects.filter(public_irc_channel_fix_needed=True): + logger.debug("Team %s public IRC channel %s needs ACL fixing" % (team, team.public_irc_channel_name)) + self.bot.setup_public_channel(team.public_irc_channel_name) ############################################################################################### From fa6981ea22d228e4ed438d557a046b49bc6ca2c7 Mon Sep 17 00:00:00 2001 From: Thomas Steen Rasmussen Date: Fri, 13 Apr 2018 21:47:36 +0200 Subject: [PATCH 5/9] stupid typo --- src/ircbot/irc3module.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/ircbot/irc3module.py b/src/ircbot/irc3module.py index a8389815..98f8574d 100644 --- a/src/ircbot/irc3module.py +++ b/src/ircbot/irc3module.py @@ -321,7 +321,7 @@ class Plugin(object): logger.debug("Found %s memberships which need IRC ACL fixing.." % missing_acls.count()) for membership in missing_acls: # add to team public channel? - if membership.team.public_channel_name and membership.publíc_channel_managed: + if membership.team.public_irc_channel_name and membership.publíc_irc_channel_managed: self.bot.add_user_to_channel_acl( username=membership.user.profile.nickserv_username, channel=membership.team.public_irc_channel_name, @@ -329,7 +329,7 @@ class Plugin(object): ) # add to team private channel? - if membership.team.private_channel_name and membership.private_channel_managed: + if membership.team.private_irc_channel_name and membership.private_irc_channel_managed: self.bot.add_user_to_channel_acl( username=membership.user.profile.nickserv_username, channel=membership.team.private_irc_channel_name, From 65e225f4b126bf1426666f001ae3e1facdd68184 Mon Sep 17 00:00:00 2001 From: Thomas Steen Rasmussen Date: Fri, 13 Apr 2018 21:54:24 +0200 Subject: [PATCH 6/9] another stupid typo --- src/ircbot/irc3module.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ircbot/irc3module.py b/src/ircbot/irc3module.py index 98f8574d..274fef5e 100644 --- a/src/ircbot/irc3module.py +++ b/src/ircbot/irc3module.py @@ -321,7 +321,7 @@ class Plugin(object): logger.debug("Found %s memberships which need IRC ACL fixing.." % missing_acls.count()) for membership in missing_acls: # add to team public channel? - if membership.team.public_irc_channel_name and membership.publíc_irc_channel_managed: + if membership.team.public_irc_channel_name and membership.public_irc_channel_managed: self.bot.add_user_to_channel_acl( username=membership.user.profile.nickserv_username, channel=membership.team.public_irc_channel_name, From f73c6a1a115177ddffeae8905b869f2d8a4c82e4 Mon Sep 17 00:00:00 2001 From: Thomas Steen Rasmussen Date: Fri, 13 Apr 2018 22:01:05 +0200 Subject: [PATCH 7/9] this is a property of team --- src/ircbot/irc3module.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/ircbot/irc3module.py b/src/ircbot/irc3module.py index 274fef5e..894c609d 100644 --- a/src/ircbot/irc3module.py +++ b/src/ircbot/irc3module.py @@ -321,7 +321,7 @@ class Plugin(object): logger.debug("Found %s memberships which need IRC ACL fixing.." % missing_acls.count()) for membership in missing_acls: # add to team public channel? - if membership.team.public_irc_channel_name and membership.public_irc_channel_managed: + if membership.team.public_irc_channel_name and membership.team.public_irc_channel_managed: self.bot.add_user_to_channel_acl( username=membership.user.profile.nickserv_username, channel=membership.team.public_irc_channel_name, @@ -329,7 +329,7 @@ class Plugin(object): ) # add to team private channel? - if membership.team.private_irc_channel_name and membership.private_irc_channel_managed: + if membership.team.private_irc_channel_name and membership.team.private_irc_channel_managed: self.bot.add_user_to_channel_acl( username=membership.user.profile.nickserv_username, channel=membership.team.private_irc_channel_name, From 1604d29771a00990d1c1963143346e4d4502d222 Mon Sep 17 00:00:00 2001 From: Thomas Steen Rasmussen Date: Fri, 13 Apr 2018 22:12:29 +0200 Subject: [PATCH 8/9] another typo --- src/ircbot/irc3module.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ircbot/irc3module.py b/src/ircbot/irc3module.py index 894c609d..507ce223 100644 --- a/src/ircbot/irc3module.py +++ b/src/ircbot/irc3module.py @@ -339,7 +339,7 @@ class Plugin(object): # add to volunteer channel self.bot.add_user_to_channel_acl( username=membership.user.profile.nickserv_username, - chanel=settings.IRCBOT_VOLUNTEER_CHANNEL, + channel=settings.IRCBOT_VOLUNTEER_CHANNEL, invite=True ) From 3df21f0fccc3fe91f785b09e3e5529f574c645af Mon Sep 17 00:00:00 2001 From: Thomas Steen Rasmussen Date: Fri, 13 Apr 2018 22:31:50 +0200 Subject: [PATCH 9/9] one more typo fix --- src/ircbot/irc3module.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ircbot/irc3module.py b/src/ircbot/irc3module.py index 507ce223..bd6e386d 100644 --- a/src/ircbot/irc3module.py +++ b/src/ircbot/irc3module.py @@ -344,7 +344,7 @@ class Plugin(object): ) # mark membership as irc_channel_acl_ok=True and save - membership.irc_acl_fix_neede=False + membership.irc_acl_fix_needed=False membership.save() # loop over teams where the private channel needs fixing