website/integrations: add three more policy-expressions to discord-docs (#5760)
* - Add two policy-examples for syncing discord roles to authentik groups during enrollment or authentication - Add policy to store oauth-info and create an authentik-avatar-attribute during enrollment or authentication * Fix issues and lint - Fixed issue with wrong return during provider-check - Lint using black Signed-off-by: Keyinator <k3yinator@gmail.com> * Fix capitalization and punctuation Co-authored-by: Tana M Berry <tanamarieberry@yahoo.com> Signed-off-by: Keyinator <k3yinator@gmail.com> * Fix documentation link and add explanation to MAPPED_ROLES attribute --------- Signed-off-by: Keyinator <k3yinator@gmail.com> Co-authored-by: Tana M Berry <tanamarieberry@yahoo.com>
This commit is contained in:
		| @ -154,3 +154,209 @@ return user_matched | |||||||
| ``` | ``` | ||||||
|  |  | ||||||
| Now bind this policy to the chosen enrollment and authentication flows for the Discord OAuth source. | Now bind this policy to the chosen enrollment and authentication flows for the Discord OAuth source. | ||||||
|  |  | ||||||
|  | ### 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'. | ||||||
|  | ::: | ||||||
|  |  | ||||||
|  | Create a new 'Expression Policy' with the content below, adjusting the variables where required. | ||||||
|  |  | ||||||
|  | #### Sync on enrollment | ||||||
|  |  | ||||||
|  | ```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" | ||||||
|  |  | ||||||
|  | # Ensure flow is only run during OAuth logins via Discord | ||||||
|  | if context["source"].provider_type != "discord": | ||||||
|  |     return True | ||||||
|  |  | ||||||
|  | # Get the user-source connection object from the context, and get the access token | ||||||
|  | connection = context.get("goauthentik.io/sources/connection") | ||||||
|  | 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() | ||||||
|  |  | ||||||
|  | # 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_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]) | ||||||
|  |  | ||||||
|  | request.context["flow_plan"].context["groups"] = groups_to_add | ||||||
|  | return True | ||||||
|  |  | ||||||
|  | ``` | ||||||
|  |  | ||||||
|  | Now bind this policy to the chosen enrollment flows for the Discord OAuth source. | ||||||
|  |  | ||||||
|  | #### Sync on authentication | ||||||
|  |  | ||||||
|  | ```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" | ||||||
|  |  | ||||||
|  | # Ensure flow is only run during OAuth logins via Discord | ||||||
|  | if context["source"].provider_type != "discord": | ||||||
|  |     return True | ||||||
|  |  | ||||||
|  | # Get the user-source connection object from the context, and get the access token | ||||||
|  | connection = context.get("goauthentik.io/sources/connection") | ||||||
|  | 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() | ||||||
|  |  | ||||||
|  | # 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_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() | ||||||
|  | ] | ||||||
|  |  | ||||||
|  | # 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]) | ||||||
|  |  | ||||||
|  | # Update user's groups | ||||||
|  | request.user.ak_groups.set(new_groups) | ||||||
|  | request.user.save() | ||||||
|  |  | ||||||
|  | return True | ||||||
|  |  | ||||||
|  | ``` | ||||||
|  |  | ||||||
|  | Now bind this policy to the chosen authentication flows for the Discord OAuth source. | ||||||
|  |  | ||||||
|  | ### 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'. | ||||||
|  | ::: | ||||||
|  |  | ||||||
|  | :::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: | ||||||
|  |  | ||||||
|  | ```python | ||||||
|  | import base64 | ||||||
|  | import requests | ||||||
|  |  | ||||||
|  | AVATAR_SIZE = "64"  # Valid values: 16,32,64,128,256,512,1024 | ||||||
|  |  | ||||||
|  | # Only change below here if you know what you are doing. | ||||||
|  | AVATAR_URL = "https://cdn.discordapp.com/avatars/{id}/{avatar}.png?site={avatar_size}" | ||||||
|  | AVATAR_STREAM_CONTENT = "data:image/png;base64,{base64_string}"  # Converts base64 image into html syntax useable with authentik's avatar attributes feature | ||||||
|  |  | ||||||
|  |  | ||||||
|  | def get_as_base64(url): | ||||||
|  |     """Returns the base64 content of the url""" | ||||||
|  |     return base64.b64encode(requests.get(url).content) | ||||||
|  |  | ||||||
|  |  | ||||||
|  | def get_avatar_from_avatar_url(url): | ||||||
|  |     """Returns an authentik-avatar-attributes-compatible string from an image url""" | ||||||
|  |     cut_url = f"{url}?size=64" | ||||||
|  |     return AVATAR_STREAM_CONTENT.format( | ||||||
|  |         base64_string=(get_as_base64(cut_url).decode("utf-8")) | ||||||
|  |     ) | ||||||
|  |  | ||||||
|  |  | ||||||
|  | # Ensure flow is only run during OAuth logins via Discord | ||||||
|  | if context["source"].provider_type != "discord": | ||||||
|  |     return True | ||||||
|  |  | ||||||
|  | user = request.user | ||||||
|  | userinfo = request.context["oauth_userinfo"] | ||||||
|  |  | ||||||
|  | # Assigns the discord attributes to the user | ||||||
|  | user.attributes["discord"] = { | ||||||
|  |     "id": userinfo["id"], | ||||||
|  |     "username": userinfo["username"], | ||||||
|  |     "discriminator": userinfo["discriminator"], | ||||||
|  |     "email": userinfo["email"], | ||||||
|  |     "avatar": userinfo["avatar"], | ||||||
|  |     "avatar_url": ( | ||||||
|  |         AVATAR_URL.format( | ||||||
|  |             id=userinfo["id"], avatar=userinfo["avatar"], avatar_size=AVATAR_SIZE | ||||||
|  |         ) | ||||||
|  |         if userinfo["avatar"] | ||||||
|  |         else None | ||||||
|  |     ), | ||||||
|  | } | ||||||
|  |  | ||||||
|  | # If the user has an avatar, assign it to the user | ||||||
|  | avatar_url = user.attributes["discord"].get("avatar_url", None) | ||||||
|  | if avatar_url is not None: | ||||||
|  |     user.attributes["avatar"] = get_avatar_from_avatar_url(avatar_url) | ||||||
|  |  | ||||||
|  | user.save() | ||||||
|  | return True | ||||||
|  |  | ||||||
|  | ``` | ||||||
|  |  | ||||||
|  | Now bind this policy to the chosen enrollment and authentication flows for the Discord OAuth source. | ||||||
|  | |||||||
		Reference in New Issue
	
	Block a user
	 Keyinator
					Keyinator