diff --git a/config/settings/base.py b/config/settings/base.py
index c75575e1ef624df8b8fb214669104945be426247..b843f2e70d9e86026a8f0e840c86e576ee4ed749 100644
--- a/config/settings/base.py
+++ b/config/settings/base.py
@@ -128,9 +128,9 @@ LVALERT_OVERSEER_INSTANCES = [
 
 # Access and authorization ----------------------------------------------------
 # Some proper names related to authorization
-LVC_GROUP = 'Communities:LSCVirgoLIGOGroupMembers'
-LVEM_GROUP = 'gw-astronomy:LV-EM'
-LVEM_OBSERVERS_GROUP = 'gw-astronomy:LV-EM:Observers'
+LVC_GROUP = 'internal_users'
+LVEM_GROUP = 'lvem_users'
+LVEM_OBSERVERS_GROUP = 'lvem_observers'
 PUBLIC_GROUP = 'public_users'
 PRIORITY_USERS_GROUP = 'priority_users'
 
@@ -363,7 +363,7 @@ INSTALLED_APPS = [
 # Aliases for django-extensions shell_plus
 SHELL_PLUS_MODEL_ALIASES = {
     # Two 'Group' models - auth.Group and gracedb.Group
-    'auth': {'Group': 'AuthGroup'},
+    'auth': {'Group': 'DjangoGroup'},
     # Superevents models which have the same name as
     # models in the events app
     'superevents': {
diff --git a/gracedb/ligoauth/managers.py b/gracedb/ligoauth/managers.py
new file mode 100644
index 0000000000000000000000000000000000000000..ed5ed9a5fccba7204aa8f7a90bfdfada388c886b
--- /dev/null
+++ b/gracedb/ligoauth/managers.py
@@ -0,0 +1,15 @@
+from django.db import models
+
+# Custom managers for the AuthGroup model
+class LdapGroupManager(models.Manager):
+    """Groups whose membership is managed through an LDAP"""
+    def get_queryset(self):
+        return super(LdapGroupManager, self).get_queryset().filter(
+            ldap_name__isnull=False)
+
+
+class TagGroupManager(models.Manager):
+    """Groups who have an associated tag for allowing log viewing"""
+    def get_queryset(self):
+        return super(TagGroupManager, self).get_queryset().filter(
+            tag__isnull=False)
diff --git a/gracedb/ligoauth/migrations/0037_authgroup.py b/gracedb/ligoauth/migrations/0037_authgroup.py
new file mode 100644
index 0000000000000000000000000000000000000000..b26228886d73b23227a57b128ca4a1a0646519c7
--- /dev/null
+++ b/gracedb/ligoauth/migrations/0037_authgroup.py
@@ -0,0 +1,28 @@
+# -*- coding: utf-8 -*-
+# Generated by Django 1.11.20 on 2019-05-28 18:16
+from __future__ import unicode_literals
+
+from django.db import migrations, models
+import django.db.models.deletion
+
+
+class Migration(migrations.Migration):
+
+    dependencies = [
+        ('events', '0033_pipelinelog_and_pipeline_enabled'),
+        ('auth', '0023_add_manage_pipeline_permissions'),
+        ('ligoauth', '0036_add_cwb_cert'),
+    ]
+
+    operations = [
+        migrations.CreateModel(
+            name='AuthGroup',
+            fields=[
+                ('group_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='auth.Group')),
+                ('description', models.TextField()),
+                ('ldap_name', models.CharField(max_length=50, null=True, unique=True)),
+                ('tag', models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, to='events.Tag')),
+            ],
+            bases=('auth.group',),
+        ),
+    ]
diff --git a/gracedb/ligoauth/migrations/0038_populate_authgroup.py b/gracedb/ligoauth/migrations/0038_populate_authgroup.py
new file mode 100644
index 0000000000000000000000000000000000000000..acfcdc19fae2e52c8a824eb3e98c5c33efafe3d1
--- /dev/null
+++ b/gracedb/ligoauth/migrations/0038_populate_authgroup.py
@@ -0,0 +1,156 @@
+# -*- coding: utf-8 -*-
+# Generated by Django 1.11.20 on 2019-05-28 18:18
+from __future__ import unicode_literals
+
+from django.db import migrations
+
+GROUP_DATA = [
+    {
+        'name': 'executives',
+        'description': ('"Executive users" who can expose and hide events, '
+            'superevents, and log messages. This is a holdover from O1/O2 and '
+            'should be deleted in favor of the "access_managers" group once '
+            'the permissions for the events app can be reworked to be more '
+            'like the superevents app.'),
+    },
+    {
+        'name': 'Communities:LSCVirgoLIGOGroupMembers',
+        'ldap_name': 'Communities:LSCVirgoLIGOGroupMembers',
+        'new_name': 'internal_users',
+        'description': 'Internal users (LIGO, Virgo, and soon, KAGRA).',
+    },
+    {
+        'name': 'gw-astronomy:LV-EM',
+        'ldap_name': 'gw-astronomy:LV-EM',
+        'new_name': 'lvem_users',
+        'description': ('LV-EM group; not sure why this exists exactly, but '
+            'these users have been allowed to see MDC events historically.'),
+    },
+    {
+        'name': 'public_users',
+        'tag_name': 'public',
+        'description': ('Public group, used for unauthenticated users to map '
+            'into the django-guardian AnonymousUser account to allow '
+            'object permissions.'),
+    },
+    {
+        'name': 'h1_control_room',
+        'description': ('Users in the Hanford (H1) control room. Membership '
+            'in this group is set on a per-request basis by some middleware '
+            'which checks the user\'s IP versus a specific IP in the Django '
+            'settings. This group may not be used going forward since '
+            'operator signoffs do not appear to be used in O3.'),
+    },
+    {
+        'name': 'l1_control_room',
+        'description': ('Users in the Livingston (L1) control room. Membership'
+            ' in this group is set on a per-request basis by some middleware '
+            'which checks the user\'s IP versus a specific IP in the Django '
+            'settings. This group may not be used going forward since '
+            'operator signoffs do not appear to be used in O3.'),
+    },
+    {
+        'name': 'v1_control_room',
+        'description': ('Users in the Virgo (V1) control room. Membership '
+            'in this group is set on a per-request basis by some middleware '
+            'which checks the user\'s IP versus a specific IP in the Django '
+            'settings. This group may not be used going forward since '
+            'operator signoffs do not appear to be used in O3.'),
+    },
+    {
+        'name': 'gw-astronomy:LV-EM:Observers',
+        'ldap_name': 'gw-astronomy:LV-EM:Observers',
+        'new_name': 'lvem_observers',
+        'tag_name': 'lvem',
+        'description': ('LV-EM observers group. This is the primary group '
+            'used for allowing access to LV-EM members.'),
+    },
+    {
+        'name': 'em_advocates',
+        'ldap_name': 'Communities:LVC:GraceDB:GraceDBAdvocates',
+        'description': ('EM follow-up advocates; responsible for managing '
+            'rapid response to new candidate events/superevents and '
+            'performing advocate signoffs.'),
+    },
+    {
+        'name': 'superevent_managers',
+        'description': ('Users responsible for creating and updating both '
+            'production and MDC superevents.'),
+    },
+    {
+        'name': 'access_managers',
+        'description': ('Users responsible for allowing non-internal access '
+            'to superevents and log messages. At present, they have no '
+            'ability to modify event permissions, although this should '
+            'change in the future when permissions are consolidated.'),
+    },
+    {
+        'name': 'priority_users',
+        'description': ('Users allowed to access the priority API (which '
+            'does not exist at present [June 2019]).'),
+    },
+    {
+        'name': 'raven_users',
+        'description': ('Users in charge of managing the RAVEN pipeline. '
+            'Have permission to upload external events.'),
+    },
+]
+
+
+def create_authgroups(apps, schema_editor):
+    DjangoGroup = apps.get_model('auth', 'Group')
+    AuthGroup = apps.get_model('ligoauth', 'AuthGroup')
+    Tag = apps.get_model('events', 'Tag')
+
+    # Create AuthGroup instances
+    for group in GROUP_DATA:
+        g = DjangoGroup.objects.get(name=group['name'])
+        ag = AuthGroup(group_ptr=g)
+        ag.description = group['description']
+
+        # Update from base class
+        ag.__dict__.update(g.__dict__)
+
+        # Save
+        ag.save()
+
+        # Add optional extras and save
+        if 'new_name' in group:
+            ag.name = group['new_name']
+        if 'ldap_name' in group:
+            ag.ldap_name = group['ldap_name']
+        if 'tag_name' in group:
+            tag, _ = Tag.objects.get_or_create(name=group['tag_name'])
+            ag.tag = tag
+        ag.save()
+
+
+def delete_authgroups(apps, schema_editor):
+    AuthGroup = apps.get_model('ligoauth', 'AuthGroup')
+
+    # Loop over groups and delete AuthGroup
+    for group in GROUP_DATA:
+        # Get AuthGroup
+        group_name = group['name']
+        if 'new_name' in group:
+            group_name = group['new_name']
+        ag = AuthGroup.objects.get(name=group_name)
+
+        # Reset name if needed
+        if 'new_name' in group:
+            ag.name = group['name']
+            ag.save()
+
+        # Delete AuthGroup and keep DjangoGroup base class
+        ag.delete(keep_parents=True)
+
+
+class Migration(migrations.Migration):
+
+    dependencies = [
+        ('ligoauth', '0037_authgroup'),
+    ]
+
+    operations = [
+        migrations.RunPython(create_authgroups, delete_authgroups),
+    ]
diff --git a/gracedb/ligoauth/models.py b/gracedb/ligoauth/models.py
index 4f270fa52a86c939aaa1076c5662ffbeb42e4a3e..ec228312df85dca26de0e25a4ce4b530b1642d60 100644
--- a/gracedb/ligoauth/models.py
+++ b/gracedb/ligoauth/models.py
@@ -1,8 +1,10 @@
-
 from __future__ import unicode_literals
 
 from django.db import models
-from django.contrib.auth.models import User
+from django.contrib.auth.models import User, Group
+
+from .managers import LdapGroupManager, TagGroupManager
+
 
 # There seems to be a LOT of duplication here and I don't know
 # if that is good or bad.  Normally, that seems bad...
@@ -84,3 +86,22 @@ def certdn_to_user(dn, username=None):
     except IndexError:
         return None
 
+
+class AuthGroup(Group):
+    """Enhanced version of Django Group model"""
+    # Description of the group
+    description = models.TextField(blank=False)
+    # The group's name in some LDAP (likely the LIGO LDAP). This will be used
+    # to correlated group memberships in the LDAP as retrieved from an LDAP
+    # query or from a Shibboleth session to groups in this service
+    # If this is null, the group is manually managed and does not inherit its
+    # membership from an LDAP.
+    ldap_name = models.CharField(max_length=50, unique=True, null=True)
+    # Tag used to expose access to log messages for group; if null, there is no
+    # such tag and access is not granted via this mechanism
+    tag = models.ForeignKey('events.Tag', null=True)
+
+    # Add custom managers, must manually define objects as well
+    objects = models.Manager()
+    ldap_objects = LdapGroupManager()
+    tag_objects = TagGroupManager()