website/integrations: Improve discord role sync policies (#10219)
Co-authored-by: Tana M Berry <tanamarieberry@yahoo.com> Co-authored-by: Marc 'risson' Schmitt <marc.schmitt@risson.space>
This commit is contained in:
@ -56,10 +56,10 @@ For more details on how-to have the new source display on the Login Page see [he
|
||||
### Checking for membership of a Discord Guild
|
||||
|
||||
:::info
|
||||
Ensure that the Discord OAuth source in 'Federation & Social login' has the additional `guilds` scope added under the 'Protocol settings'.
|
||||
Ensure that the Discord OAuth source in **Federation & Social login** has the additional `guilds guilds.members.read` scopes added under **Protocol settings**.
|
||||
:::
|
||||
|
||||
Create a new 'Expression Policy' with the content below, adjusting the variables where required:
|
||||
Create a new **Expression Policy** with the content below, adjusting the variables where required:
|
||||
|
||||
```python
|
||||
# To get the guild ID number for the parameters, open Discord, go to Settings > Advanced and enable developer mode.
|
||||
@ -98,10 +98,10 @@ Now bind this policy to the chosen enrollment and authentication flows for the D
|
||||
### Checking for membership of a Discord Guild role
|
||||
|
||||
:::info
|
||||
Ensure that the Discord OAuth source in 'Federation & Social login' has the additional `guilds guilds.members.read` scopes added under the 'Protocol settings'.
|
||||
Ensure that the Discord OAuth source in **Federation & Social login** has the additional `guilds guilds.members.read` scopes added under **Protocol settings**.
|
||||
:::
|
||||
|
||||
Create a new 'Expression Policy' with the content below, adjusting the variables where required:
|
||||
Create a new **Expression Policy** with the content below, adjusting the variables where required:
|
||||
|
||||
```python
|
||||
# To get the role and guild ID numbers for the parameters, open Discord, go to Settings > Advanced and
|
||||
@ -158,32 +158,33 @@ Now bind this policy to the chosen enrollment and authentication flows for the D
|
||||
### Syncing Discord roles to authentik groups
|
||||
|
||||
:::info
|
||||
Ensure that the Discord OAuth source in 'Federation & Social login' has the additional `guilds.members.read` scopes added under the 'Protocol settings'.
|
||||
Ensure that the Discord OAuth source in **Federation & Social login** has the additional `guilds.members.read` scopes added under **Protocol settings**.
|
||||
:::
|
||||
|
||||
Create a new 'Expression Policy' with the content below, adjusting the variables where required.
|
||||
:::info
|
||||
Any authentik role that you want to sync with a Discord role needs to have the **attribute** `discord_role_id` with a value of the Discord role's ID set.
|
||||
This setting can be found under `Authentik > Admin Interface > Directory > Groups > YOUR_GROUP > Attributes`
|
||||
Example: `discord_role_id: "<ROLE ID>"`
|
||||
:::
|
||||
|
||||
The following two policies allow you to synchronize roles in a Discord guild with roles in authentik.
|
||||
Whenever a user enrolls or signs in to authentik via a Discord source, these policies will check the user's Discord roles and apply the user's authentik roles accordingly.
|
||||
All roles with the attribute `discord_role_id` defined will be added or removed depending on whether the user is a member of the defined Discord role.
|
||||
|
||||
Create a new **Expression Policy** with the content below, adjusting the variables where required.
|
||||
|
||||
#### Sync on enrollment
|
||||
|
||||
The following policy will apply the above behaviour when a user enrolls.
|
||||
|
||||
```python
|
||||
# To get the role and guild ID numbers for the parameters, open Discord, go to Settings > Advanced and
|
||||
# enable developer mode.
|
||||
# Right-click on the server/guild title and select "Copy ID" to get the guild ID.
|
||||
# Right-click on the server/guild title and select server settings > roles, right click on the role and click
|
||||
# "Copy ID" to get the role ID.
|
||||
|
||||
from authentik.core.models import Group
|
||||
|
||||
GUILD_ID = "<YOUR GUILD ID>"
|
||||
MAPPED_ROLES = {
|
||||
"<Discord Role Id 1>": Group.objects.get_or_create(name="<Authentik Role Name 1>")[0],
|
||||
"<Discord Role Id 2>": Group.objects.get_or_create(name="<Authentik Role Name 2>")[0],
|
||||
# You can add mapped roles by copying the above line and adjusting to your needs
|
||||
}
|
||||
|
||||
# Only change below here if you know what you are doing.
|
||||
GUILD_API_URL = "https://discord.com/api/users/@me/guilds/{guild_id}/member"
|
||||
|
||||
### CONFIG ###
|
||||
guild_id = "<YOUR GUILD ID>"
|
||||
##############
|
||||
|
||||
# Ensure flow is only run during OAuth logins via Discord
|
||||
if context["source"].provider_type != "discord":
|
||||
return True
|
||||
@ -194,29 +195,43 @@ if not connection:
|
||||
return False
|
||||
access_token = connection.access_token
|
||||
|
||||
guild_member_info = requests.get(
|
||||
GUILD_API_URL.format(guild_id=GUILD_ID),
|
||||
headers={"Authorization": "Bearer " + access_token},
|
||||
).json()
|
||||
guild_member_request = requests.get(
|
||||
GUILD_API_URL.format(guild_id=guild_id),
|
||||
headers={
|
||||
"Authorization": f"Bearer {access_token}",
|
||||
},
|
||||
)
|
||||
guild_member_info = guild_member_request.json()
|
||||
|
||||
# Ensure we are not being ratelimited
|
||||
if guild_member_request.status_code == 429:
|
||||
ak_message(f"Discord is throttling this connection. Retry in {int(guild_member_info['retry_after'])}s")
|
||||
return False
|
||||
|
||||
# Ensure user is a member of the guild
|
||||
if "code" in guild_member_info:
|
||||
if guild_member_info["code"] == 10004:
|
||||
ak_message("User is not a member of the guild")
|
||||
else:
|
||||
ak_create_event(
|
||||
"discord_error", source=context["source"], code=guild_member_info["code"]
|
||||
)
|
||||
ak_create_event("discord_error", source=context["source"], code=guild_member_info["code"])
|
||||
ak_message("Discord API error, try again later.")
|
||||
return False
|
||||
|
||||
# Add all mapped roles the user has in the guild
|
||||
groups_to_add = []
|
||||
for role_id in MAPPED_ROLES:
|
||||
if role_id in guild_member_info["roles"]:
|
||||
groups_to_add.append(MAPPED_ROLES[role_id])
|
||||
# Get all discord_groups
|
||||
discord_groups = Group.objects.filter(attributes__discord_role_id__isnull=False)
|
||||
|
||||
# Filter matching roles based on guild_member_info['roles']
|
||||
user_groups_discord_updated = discord_groups.filter(attributes__discord_role_id__in=guild_member_info["roles"])
|
||||
|
||||
# Set matchin_roles in flow context
|
||||
request.context["flow_plan"].context["groups"] = user_groups_discord_updated
|
||||
|
||||
# Create event with roles added
|
||||
ak_create_event(
|
||||
"discord_role_sync",
|
||||
user_discord_roles_added=", ".join(str(group) for group in user_groups_discord_updated),
|
||||
)
|
||||
|
||||
request.context["flow_plan"].context["groups"] = groups_to_add
|
||||
return True
|
||||
|
||||
```
|
||||
@ -225,25 +240,16 @@ Now bind this policy to the chosen enrollment flows for the Discord OAuth source
|
||||
|
||||
#### Sync on authentication
|
||||
|
||||
The following policy will apply the above behaviour when a user logs in.
|
||||
|
||||
```python
|
||||
# To get the role and guild ID numbers for the parameters, open Discord, go to Settings > Advanced and
|
||||
# enable developer mode.
|
||||
# Right-click on the server/guild title and select "Copy ID" to get the guild ID.
|
||||
# Right-click on the server/guild title and select server settings > roles, right click on the role and click
|
||||
# "Copy ID" to get the role ID.
|
||||
|
||||
from authentik.core.models import Group
|
||||
|
||||
GUILD_ID = "<YOUR GUILD ID>"
|
||||
MAPPED_ROLES = {
|
||||
"<Discord Role Id 1>": Group.objects.get_or_create(name="<Authentik Role Name 1>")[0],
|
||||
"<Discord Role Id 2>": Group.objects.get_or_create(name="<Authentik Role Name 2>")[0],
|
||||
# You can add mapped roles by copying the above line and adjusting to your needs
|
||||
}
|
||||
|
||||
# Only change below here if you know what you are doing.
|
||||
GUILD_API_URL = "https://discord.com/api/users/@me/guilds/{guild_id}/member"
|
||||
|
||||
### CONFIG ###
|
||||
guild_id = "<YOUR GUILD ID>"
|
||||
##############
|
||||
|
||||
# Ensure flow is only run during OAuth logins via Discord
|
||||
if context["source"].provider_type != "discord":
|
||||
return True
|
||||
@ -254,35 +260,50 @@ if not connection:
|
||||
return False
|
||||
access_token = connection.access_token
|
||||
|
||||
guild_member_info = requests.get(
|
||||
GUILD_API_URL.format(guild_id=GUILD_ID),
|
||||
headers={"Authorization": "Bearer " + access_token},
|
||||
).json()
|
||||
guild_member_request = requests.get(
|
||||
GUILD_API_URL.format(guild_id=guild_id),
|
||||
headers={
|
||||
"Authorization": f"Bearer {access_token}"
|
||||
},
|
||||
)
|
||||
guild_member_info = guild_member_request.json()
|
||||
|
||||
# Ensure we are not being ratelimited
|
||||
if guild_member_request.status_code == 429:
|
||||
ak_message(f"Discord is throttling this connection. Retry in {int(guild_member_info['retry_after'])}s")
|
||||
return False
|
||||
|
||||
# Ensure user is a member of the guild
|
||||
if "code" in guild_member_info:
|
||||
if guild_member_info["code"] == 10004:
|
||||
ak_message("User is not a member of the guild")
|
||||
else:
|
||||
ak_create_event(
|
||||
"discord_error", source=context["source"], code=guild_member_info["code"]
|
||||
)
|
||||
ak_create_event("discord_error", source=context["source"], code=guild_member_info["code"])
|
||||
ak_message("Discord API error, try again later.")
|
||||
return False
|
||||
|
||||
# Get the user's current roles and remove all roles we want to remap
|
||||
new_groups = [
|
||||
role for role in request.user.ak_groups.all() if role not in MAPPED_ROLES.values()
|
||||
]
|
||||
# Get all discord_groups
|
||||
discord_groups = Group.objects.filter(attributes__discord_role_id__isnull=False)
|
||||
|
||||
# Add back mapped roles which the user has in the guild
|
||||
for role_id in MAPPED_ROLES:
|
||||
if role_id in guild_member_info["roles"]:
|
||||
new_groups.append(MAPPED_ROLES[role_id])
|
||||
# Split user groups into discord groups and non discord groups
|
||||
user_groups_non_discord = request.user.ak_groups.exclude(pk__in=discord_groups.values_list("pk", flat=True))
|
||||
user_groups_discord = list(request.user.ak_groups.filter(pk__in=discord_groups.values_list("pk", flat=True)))
|
||||
|
||||
# Filter matching roles based on guild_member_info['roles']
|
||||
user_groups_discord_updated = discord_groups.filter(attributes__discord_role_id__in=guild_member_info["roles"])
|
||||
|
||||
# Combine user_groups_non_discord and matching_roles
|
||||
user_groups_updated = user_groups_non_discord.union(user_groups_discord_updated)
|
||||
|
||||
# Update user's groups
|
||||
request.user.ak_groups.set(new_groups)
|
||||
request.user.save()
|
||||
request.user.ak_groups.set(user_groups_updated)
|
||||
|
||||
# Create event with roles changed
|
||||
ak_create_event(
|
||||
"discord_role_sync",
|
||||
user_discord_roles_before=", ".join(str(group) for group in user_groups_discord),
|
||||
user_discord_roles_after=", ".join(str(group) for group in user_groups_discord_updated),
|
||||
)
|
||||
|
||||
return True
|
||||
|
||||
@ -293,14 +314,14 @@ Now bind this policy to the chosen authentication flows for the Discord OAuth so
|
||||
### Store OAuth info in attribute and create avatar attribute from Discord avatar
|
||||
|
||||
:::info
|
||||
Ensure that the Discord OAuth source in 'Federation & Social login' has the additional `guilds.members.read` scopes added under the 'Protocol settings'.
|
||||
Ensure that the Discord OAuth source in **Federation & Social login** has the additional `guilds.members.read` scopes added under **Protocol settings**.
|
||||
:::
|
||||
|
||||
:::info
|
||||
In order to use the created attribute in authentik you will have to set authentik configuration arguments found at: https://docs.goauthentik.io/docs/core/settings#avatars
|
||||
:::
|
||||
|
||||
Create a new 'Expression Policy' with the content below, adjusting the variables where required:
|
||||
Create a new **Expression Policy** with the content below, adjusting the variables where required:
|
||||
|
||||
```python
|
||||
import base64
|
||||
|
||||
Reference in New Issue
Block a user