Skip to content

Commit

Permalink
Merge pull request #167 from SpongePowered/feature/group-pingbacks
Browse files Browse the repository at this point in the history
Add Auth groups to Discourse SSO pingbacks
  • Loading branch information
lukegb committed Mar 17, 2018
2 parents 1906e9a + b823f63 commit f2f97b9
Show file tree
Hide file tree
Showing 8 changed files with 957 additions and 46 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -11,3 +11,4 @@ media/
.idea
*.iml
.DS_Store
.pytest_cache
32 changes: 32 additions & 0 deletions spongeauth/accounts/migrations/0007_group_add_internal_name.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
# Generated by Django 2.0.2 on 2018-03-17 14:54

import accounts.models
from django.db import migrations, models


def populate_internal_name(apps, schema_editor):
Group = apps.get_model('accounts', 'Group')
for group in Group.objects.all():
group.internal_name = group.name.lower().replace(' ', '_')
group.save()


class Migration(migrations.Migration):

dependencies = [
('accounts', '0006_add_username_insensitive_index'),
]

operations = [
migrations.AddField(
model_name='group',
name='internal_name',
field=models.CharField(default='', max_length=20, unique=True, validators=[accounts.models.validate_username]),
),
migrations.RunPython(populate_internal_name),
migrations.AlterField(
model_name='group',
name='internal_name',
field=models.CharField(max_length=20, unique=True, validators=[accounts.models.validate_username]),
),
]
63 changes: 33 additions & 30 deletions spongeauth/accounts/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,36 +17,6 @@
logger = logging.getLogger(__name__)


class Group(models.Model):
name = models.CharField(max_length=80, unique=True)

internal_only = models.BooleanField(null=False, default=True)

def __str__(self):
return self.name


class UserManager(BaseUserManager):
def create_user(self, username, email, password=None, **kwargs):
user = self.model(
username=username,
email=self.normalize_email(email),
**kwargs)
user.set_password(password)
user.save(using=self._db)
return user

def create_superuser(self, username, email, password):
user = self.create_user(username, email, password)
user.is_active = True
user.is_admin = True
user.save(using=self._db)
return user

def get_by_natural_key(self, username):
return self.get(username__iexact=username)


def validate_username(username):
errs = []
if len(username) < 3:
Expand Down Expand Up @@ -79,6 +49,39 @@ def validate_username(username):
raise ValidationError(errs)


class Group(models.Model):
name = models.CharField(max_length=80, unique=True)
internal_name = models.CharField(
max_length=20, unique=True, blank=False, null=False,
validators=[validate_username])

internal_only = models.BooleanField(null=False, default=True)

def __str__(self):
return self.name


class UserManager(BaseUserManager):
def create_user(self, username, email, password=None, **kwargs):
user = self.model(
username=username,
email=self.normalize_email(email),
**kwargs)
user.set_password(password)
user.save(using=self._db)
return user

def create_superuser(self, username, email, password):
user = self.create_user(username, email, password)
user.is_active = True
user.is_admin = True
user.save(using=self._db)
return user

def get_by_natural_key(self, username):
return self.get(username__iexact=username)


class User(AbstractBaseUser):
username = models.CharField(
max_length=20, unique=True, blank=False, null=False,
Expand Down
1 change: 1 addition & 0 deletions spongeauth/accounts/tests/factories.py
Original file line number Diff line number Diff line change
Expand Up @@ -43,3 +43,4 @@ class Meta:
model = models.Group

name = factory.Faker('user_name')
internal_name = factory.Faker('user_name')
46 changes: 44 additions & 2 deletions spongeauth/sso/tests/test_make_payload.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,9 @@ def test_builds_payload(self):
'username': user.username,
'external_id': user.id,
'moderator': False,
'admin': False
'admin': False,
'add_groups': '',
'remove_groups': '',
}

def test_builds_payload_not_activated(self):
Expand All @@ -52,5 +54,45 @@ def test_builds_payload_not_activated(self):
'username': user.username,
'external_id': user.id,
'moderator': False,
'admin': False
'admin': False,
'add_groups': '',
'remove_groups': '',
}

def test_sends_groups(self):
user = accounts.tests.factories.UserFactory.create()
int_group_in = accounts.tests.factories.GroupFactory.create(
internal_only=True)
int_group_not_in = accounts.tests.factories.GroupFactory.create(
internal_only=True)
group1_in = accounts.tests.factories.GroupFactory.create(
internal_only=False)
group2_in = accounts.tests.factories.GroupFactory.create(
internal_only=False)
group1_not_in = accounts.tests.factories.GroupFactory.create(
internal_only=False)
group2_not_in = accounts.tests.factories.GroupFactory.create(
internal_only=False)
user.groups.set([int_group_in, group1_in, group2_in])
user.save()

del int_group_not_in # Unused.

payload = utils.make_payload(user, 'nonce-nce', self.request)
assert payload == {
'nonce': 'nonce-nce',
'email': user.email,
'require_activation': 'false',
'custom.user_field_1': user.mc_username,
'custom.user_field_2': user.irc_nick,
'custom.user_field_3': user.gh_username,
'name': user.username,
'username': user.username,
'external_id': user.id,
'moderator': False,
'admin': False,
'add_groups': ','.join(sorted([group1_in.internal_name,
group2_in.internal_name])),
'remove_groups': ','.join(sorted([group1_not_in.internal_name,
group2_not_in.internal_name])),
}
13 changes: 11 additions & 2 deletions spongeauth/sso/tests/test_send_update_ping.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,12 @@ def test_send_update_ping(settings):
fake_discourse_signer = unittest.mock.MagicMock()
fake_discourse_signer.sign.return_value = (
'payload', 'signature')
fake_group = unittest.mock.MagicMock()
filt_group = fake_group.objects.filter.return_value.order_by.return_value
filt_group.filter.return_value.values_list.return_value = [
'aardvark', 'banana', 'carrot']
filt_group.exclude.return_value.values_list.return_value = [
'gingerbread', 'horseradish', 'indigo']

user = UserFactory.build(
pk=10101,
Expand All @@ -20,7 +26,8 @@ def test_send_update_ping(settings):

settings.DISCOURSE_SERVER = 'http://discourse.example.com'
settings.DISCOURSE_API_KEY = 'discourse-api-key'
send_update_ping(user, send_post=fake_send_post, sso=fake_discourse_signer)
send_update_ping(user, send_post=fake_send_post, sso=fake_discourse_signer,
group=fake_group)

fake_send_post.assert_called_once_with(
'http://discourse.example.com/admin/users/sync_sso',
Expand All @@ -40,5 +47,7 @@ def test_send_update_ping(settings):
'custom.user_field_2': 'XxXmeepXxX',
'custom.user_field_3': 'meeep',
'moderator': False,
'admin': False
'admin': False,
'add_groups': 'aardvark,banana,carrot',
'remove_groups': 'gingerbread,horseradish,indigo',
})
17 changes: 14 additions & 3 deletions spongeauth/sso/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,17 +2,25 @@

import requests

from accounts.models import Group
from . import discourse_sso


def _cast_bool(b):
return str(bool(b)).lower()


def make_payload(user, nonce, request=None):
def make_payload(user, nonce, request=None, group=None):
group = group or Group
avatar_url = user.avatar.get_absolute_url()
if request is not None:
avatar_url = request.build_absolute_uri(avatar_url)
relevant_groups = group.objects.filter(internal_only=False).order_by(
'internal_name')
add_groups = relevant_groups.filter(user=user).values_list(
'internal_name', flat=True)
remove_groups = relevant_groups.exclude(user=user).values_list(
'internal_name', flat=True)
payload = {
'nonce': nonce,
'email': user.email,
Expand All @@ -25,15 +33,18 @@ def make_payload(user, nonce, request=None):
'custom.user_field_3': user.gh_username,
'admin': user.is_admin,
'moderator': user.is_admin or user.is_staff,
'add_groups': ','.join(add_groups),
'remove_groups': ','.join(remove_groups),
}
return payload


def send_update_ping(user, send_post=None, sso=None):
def send_update_ping(user, send_post=None, sso=None, group=None):
send_post = send_post or requests.post
sso = sso or discourse_sso.DiscourseSigner(settings.DISCOURSE_SSO_SECRET)

out_payload, out_signature = sso.sign(make_payload(user, str(user.pk)))
out_payload, out_signature = sso.sign(make_payload(user, str(user.pk),
group=group))

resp = send_post(
'{}/admin/users/sync_sso'.format(settings.DISCOURSE_SERVER),
Expand Down
Loading

0 comments on commit f2f97b9

Please sign in to comment.