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

This commit is contained in:
Thomas Steen Rasmussen 2018-04-13 20:22:19 +02:00
parent cefdaaea97
commit 1c4a4dd259
15 changed files with 420 additions and 115 deletions

View file

@ -88,4 +88,5 @@ IRCBOT_SERVER_HOSTNAME='{{ django_ircbot_server }}'
IRCBOT_SERVER_PORT=6697 IRCBOT_SERVER_PORT=6697
IRCBOT_SERVER_USETLS=True IRCBOT_SERVER_USETLS=True
IRCBOT_PUBLIC_CHANNEL='{{ django_ircbot_public_channel }}' IRCBOT_PUBLIC_CHANNEL='{{ django_ircbot_public_channel }}'
IRCBOT_VOLUNTEER_CHANNEL='{{ django_ircbot_volunteer_channel }}'

View file

@ -24,7 +24,7 @@ def handle_team_event(eventtype, irc_message=None, irc_timeout=60, email_templat
if not eventtype.teams: if not eventtype.teams:
# no routes found for this eventtype, do nothing # 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 return
# loop over routes (teams) for this eventtype # 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) logger.error("IRC notifications not enabled for eventtype %s" % eventtype)
return return
if not team.irc_channel or not team.irc_channel_name: if not team.private_irc_channel_name or not team.private_irc_channel_bot:
logger.error("team %s is not IRC enabled" % team) logger.error("team %s does not have a private IRC channel" % team)
return return
# send an IRC message to the the channel for this team # send an IRC message to the the channel for this team
add_irc_message( add_irc_message(
target=team.irc_channel_name, target=team.private_irc_channel_name,
message=irc_message, message=irc_message,
timeout=60 timeout=60
) )

View file

@ -4,6 +4,8 @@ from teams.models import Team, TeamMember
from django.conf import settings from django.conf import settings
from django.utils import timezone from django.utils import timezone
from events.models import Routing from events.models import Routing
from teams.utils import get_team_from_irc_channel
import logging import logging
logger = logging.getLogger("bornhack.%s" % __name__) 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""" """Triggered when a channel is joined by someone, including the bot itself"""
if mask.nick == self.bot.nick: if mask.nick == self.bot.nick:
# the bot just joined a channel # 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) 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) self.bot.privmsg(settings.IRCBOT_CHANSERV_MASK, "info %s" % channel)
return return
@ -93,7 +95,7 @@ class Plugin(object):
############################################################################################### ###############################################################################################
### custom irc3 methods ### custom irc3 methods below here
@irc3.extend @irc3.extend
def do_stuff(self): 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. 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. 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())) #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 # 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") 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 @irc3.extend
def get_managed_team_channels(self): 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( pubchans = Team.objects.filter(
irc_channel=True, public_irc_channel_name__isnull=False,
irc_channel_managed=True public_irc_channel_bot=True,
).values_list("irc_channel_name", flat=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 @irc3.extend
def get_unmanaged_team_channels(self): 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( pubchans = Team.objects.filter(
irc_channel=True, public_irc_channel_name__isnull=False,
irc_channel_managed=False public_irc_channel_bot=True,
).values_list("irc_channel_name", flat=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 @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 # 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 mlock +inpst" % channel)
self.bot.privmsg(settings.IRCBOT_CHANSERV_MASK, "SET %s SECURE on" % team.irc_channel_name) self.bot.privmsg(settings.IRCBOT_CHANSERV_MASK, "SET %s SECURE on" % channel)
self.bot.privmsg(settings.IRCBOT_CHANSERV_MASK, "SET %s RESTRICTED on" % team.irc_channel_name) self.bot.privmsg(settings.IRCBOT_CHANSERV_MASK, "SET %s RESTRICTED on" % channel)
# add the bot to the ACL # 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, 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 # add all members to the acl
for membership in team.memberships.all(): for membership in team.memberships.all():
if membership.approved and membership.user.profile.nickserv_username: if membership.approved and membership.user.profile.nickserv_username:
self.bot.add_user_to_team_channel_acl( membership.irc_acl_fix_needed=True
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() membership.save()
@irc3.extend @irc3.extend
def setup_public_channel(self, team): def add_user_to_channel_acl(self, username, channel, invite):
"""
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):
""" """
Add user to team IRC channel ACL Add user to team IRC channel ACL
""" """
@ -243,21 +297,20 @@ class Plugin(object):
}, },
) )
# also add autoinvite for this username if invite:
self.bot.mode(channel, '+I', '$a:%s' % username) # also add autoinvite for this username
self.bot.mode(channel, '+I', '$a:%s' % username)
@irc3.extend @irc3.extend
def fix_missing_acls(self): def fix_missing_acls(self):
""" """
Called periodically by do_stuff() 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( missing_acls = TeamMember.objects.filter(
team__irc_channel=True, irc_acl_fix_needed=True
team__irc_channel_managed=True,
team__irc_channel_private=True,
irc_channel_acl_ok=False
).exclude( ).exclude(
user__profile__nickserv_username='' user__profile__nickserv_username=''
) )
@ -267,14 +320,41 @@ class Plugin(object):
logger.debug("Found %s memberships which need IRC ACL fixing.." % missing_acls.count()) logger.debug("Found %s memberships which need IRC ACL fixing.." % missing_acls.count())
for membership in missing_acls: 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, 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 # mark membership as irc_channel_acl_ok=True and save
membership.irc_channel_acl_ok=True membership.irc_acl_fix_neede=False
membership.save() 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 ### services (ChanServ & NickServ) methods
@ -294,8 +374,8 @@ class Plugin(object):
# the irc channel is not registered # the irc channel is not registered
channel = match.group(1) channel = match.group(1)
# get a list of the channels we are supposed to be managing # get a list of the channels we are supposed to be managing
if channel in self.bot.get_managed_team_channels(): if channel in self.bot.get_managed_team_channels() or channel == settings.IRCBOT_VOLUNTEER_CHANNEL:
# we want to register this channel! but we can only do so if we have a @ in the 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['@']: 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) 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) self.bot.privmsg(settings.IRCBOT_CHANSERV_MASK, "register %s" % channel)
@ -314,27 +394,30 @@ class Plugin(object):
botnick = match.group(2) botnick = match.group(2)
logger.debug("Channel %s was registered with ChanServ, looking up Team..." % channel) 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 team = get_team_from_irc_channel(channel)
try: if team:
team = Team.objects.get(irc_channel_name=channel) if team.private_irc_channel_name == channel:
except Team.DoesNotExist: # set private channel modes, +I and ACL
logger.debug("Unable to find Team matching IRC channel %s" % channel) self.bot.setup_private_channel(channel)
else:
# set public channel modes and +oO for all members
self.bot.setup_public_channel(channel)
return return
logger.debug("Unable to find Team matching IRC channel %s" % channel)
if not team.irc_channel_private: # check if this channel is the volunteer channel
# this channel is not private, no mode change and ACL needed 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 return
# set channel modes and ACL
self.bot.setup_private_channel(team)
return
logger.debug("Unhandled ChanServ message: %s" % kwargs['data']) logger.debug("Unhandled ChanServ message: %s" % kwargs['data'])
@irc3.extend @irc3.extend
def handle_nickserv_privmsg(self, **kwargs): def handle_nickserv_privmsg(self, **kwargs):
"""th """
Handles messages from NickServ on networks with Services. Handles messages from NickServ on networks with Services.
""" """
logger.debug("Got a message from NickServ") logger.debug("Got a message from NickServ")

View file

@ -57,24 +57,29 @@ def public_credit_name_changed(instance, original):
def nickserv_username_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 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: 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 # find team memberships for this user
from teams.models import TeamMember from teams.models import TeamMember
memberships = TeamMember.objects.filter( memberships = TeamMember.objects.filter(
user=instance.user, user=instance.user,
approved=True, approved=True,
team__irc_channel=True,
team__irc_channel_managed=True,
team__irc_channel_private=True,
) )
# loop over memberships # loop over memberships
for membership in 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() membership.save()

View file

@ -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),
),
]

View file

@ -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),
]

View file

@ -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',
),
]

View file

@ -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.'),
),
]

View file

@ -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.'),
),
]

View file

@ -6,6 +6,7 @@ from utils.models import CampRelatedModel
from django.core.exceptions import ValidationError from django.core.exceptions import ValidationError
from django.contrib.auth.models import User from django.contrib.auth.models import User
from django.core.urlresolvers import reverse_lazy from django.core.urlresolvers import reverse_lazy
from django.conf import settings
import logging import logging
logger = logging.getLogger("bornhack.%s" % __name__) logger = logging.getLogger("bornhack.%s" % __name__)
@ -61,25 +62,44 @@ class Team(CampRelatedModel):
) )
# IRC related fields # IRC related fields
irc_channel = models.BooleanField( public_irc_channel_name = models.CharField(
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='',
blank=True, 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( private_irc_channel_name = models.CharField(
default=True, blank=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.', 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.'
) )
private_irc_channel_bot = models.BooleanField(
irc_channel_private = models.BooleanField( default=False,
default=True, help_text='Check to make the bot join the teams private IRC channel. Leave unchecked to disable the IRC bot for this channel.'
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_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: class Meta:
@ -95,23 +115,36 @@ class Team(CampRelatedModel):
slug = slugify(self.name) slug = slugify(self.name)
self.slug = slug self.slug = slug
# set shortslug if needed
if not self.shortslug: if not self.shortslug:
self.shortslug = self.slug 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) super().save(**kwargs)
def clean(self): def clean(self):
# make sure the irc channel name is prefixed with a # if it is set # make sure the public irc channel name is prefixed with a # if it is set
if self.irc_channel_name and self.irc_channel_name[0] != "#": if self.public_irc_channel_name and self.public_irc_channel_name[0] != "#":
self.irc_channel_name = "#%s" % self.irc_channel_name self.public_irc_channel_name = "#%s" % self.public_irc_channel_name
if self.irc_channel_name: # make sure the private irc channel name is prefixed with a # if it is set
if Team.objects.filter(irc_channel_name=self.irc_channel_name).exclude(pk=self.pk).exists(): if self.private_irc_channel_name and self.private_irc_channel_name[0] != "#":
raise ValidationError("This IRC channel name is already in use") 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 @property
def memberships(self): def memberships(self):
@ -201,9 +234,9 @@ class TeamMember(CampRelatedModel):
help_text="True if this teammember is responsible for this Team. False if not." 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, 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: class Meta:

View file

@ -15,17 +15,16 @@ def teammember_saved(sender, instance, created, **kwargs):
if not add_new_membership_email(instance): if not add_new_membership_email(instance):
logger.error('Error adding email to outgoing queue') 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): def teammember_deleted(sender, instance, **kwargs):
""" """
This signal handler is called whenever a TeamMember instance is deleted 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: if instance.team.private_irc_channel_name and instance.team.private_irc_channel_managed:
# TODO: we have an ACL entry that needs to be deleted but the bot does not handle it automatically # TODO: remove user from private channel ACL
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)) pass
if instance.team.public_irc_channel_name and instance.team.public_irc_channel_managed:
# TODO: remove user from public channel ACL
pass

View file

@ -28,15 +28,16 @@ Team: {{ team.name }} | {{ block.super }}
{% endif %} {% endif %}
<h5>IRC Channel</h5> <h5>IRC Channel</h5>
{% if team.irc_channel and request.user in team.approved_members.all %} {% if team.public_irc_channel_name %}
<p>The {{ team.name }} Team IRC channel is <a href="irc://{{ IRCBOT_SERVER_HOSTNAME }}/{{ team.irc_channel_name }}">{{ team.irc_channel_name }} on {{ IRCBOT_SERVER_HOSTNAME }}</a>. <p>The {{ team.name }} Team public IRC channel is <a href="irc://{{ IRCBOT_SERVER_HOSTNAME }}/{{ team.public_irc_channel_name }}">{{ team.public_irc_channel_name }} on {{ IRCBOT_SERVER_HOSTNAME }}</a>.
{% if team.irc_channel_private %}The channel is not open to the public. Enter your nickserv username in your <a href="{% url 'profiles:detail' %}">Profile</a> to get access.{% else %}The IRC channel is open for everyone to join.{% endif %}</p>
{% elif team.irc_channel and not team.irc_channel_private %}
<p>The {{ team.name }} Team IRC channel is <a href="irc://{{ IRCBOT_SERVER_HOSTNAME }}/{{ team.irc_channel_name }}">{{ team.irc_channel_name }} on {{ IRCBOT_SERVER_HOSTNAME }}</a> and it is open for everyone to join.</p>
{% else %} {% else %}
<p>The {{ team.name }} Team does not have a public IRC channel, but it can be reached through our main IRC channel <a href="irc://{{ IRCBOT_SERVER_HOSTNAME }}/{{ IRCBOT_PUBLIC_CHANNEL }}">{{ IRCBOT_PUBLIC_CHANNEL }} on {{ IRCBOT_SERVER_HOSTNAME }}</a>.</p> <p>The {{ team.name }} Team does not have a public IRC channel, but it can be reached through our main IRC channel <a href="irc://{{ IRCBOT_SERVER_HOSTNAME }}/{{ IRCBOT_PUBLIC_CHANNEL }}">{{ IRCBOT_PUBLIC_CHANNEL }} on {{ IRCBOT_SERVER_HOSTNAME }}</a>.</p>
{% endif %} {% endif %}
{% if request.user in team.approved_members.all and team.private_irc_channel_name %}
<p>The {{ team.name }} Team private IRC channel is <a href="irc://{{ IRCBOT_SERVER_HOSTNAME }}/{{ team.private_irc_channel_name }}">{{ team.private_irc_channel_name }} on {{ IRCBOT_SERVER_HOSTNAME }}</a>.</p>
{% endif %}
<hr> <hr>
<h4>{{ team.name }} Team Members</h4> <h4>{{ team.name }} Team Members</h4>

18
src/teams/utils.py Normal file
View file

@ -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

View file

@ -70,7 +70,7 @@ class TeamDetailView(CampViewMixin, DetailView):
class TeamManageView(CampViewMixin, EnsureTeamResponsibleMixin, UpdateView): class TeamManageView(CampViewMixin, EnsureTeamResponsibleMixin, UpdateView):
model = Team model = Team
template_name = "team_manage.html" 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' slug_url_kwarg = 'team_slug'
def get_success_url(self): def get_success_url(self):

View file

@ -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.read_only = True
camp2016.save() camp2016.save()
camp2017.read_only = True
camp2017.save()
self.output("done!") self.output("done!")