diff --git a/src/bornhack/environment_settings.py.dist b/src/bornhack/environment_settings.py.dist index 484b54a1..042ce179 100644 --- a/src/bornhack/environment_settings.py.dist +++ b/src/bornhack/environment_settings.py.dist @@ -89,4 +89,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..bd6e386d 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,37 +297,65 @@ 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 """ + # first find all TeamMember objects which needs a loving hand 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='' ) - 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_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, + invite=False + ) - 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( - 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.save() + # add to team private channel? + 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, + invite=True + ) + + # add to volunteer channel + self.bot.add_user_to_channel_acl( + username=membership.user.profile.nickserv_username, + channel=settings.IRCBOT_VOLUNTEER_CHANNEL, + invite=True + ) + + # mark membership as irc_channel_acl_ok=True and save + membership.irc_acl_fix_needed=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) + + # 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) ############################################################################################### @@ -294,8 +376,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 +396,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/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 = [ 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 3e263ba3..03b8968b 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.urls 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 45a9b66e..f47b33c7 100644 --- a/src/teams/views.py +++ b/src/teams/views.py @@ -69,7 +69,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!")