Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

providers/enterprise: import user/group data when manually linking objects #10089

Merged
merged 5 commits into from
Jun 14, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
from authentik.core.api.used_by import UsedByMixin
from authentik.core.api.users import UserGroupSerializer
from authentik.enterprise.providers.google_workspace.models import GoogleWorkspaceProviderGroup
from authentik.lib.sync.outgoing.api import OutgoingSyncConnectionCreateMixin


class GoogleWorkspaceProviderGroupSerializer(ModelSerializer):
Expand All @@ -30,6 +31,7 @@ class Meta:

class GoogleWorkspaceProviderGroupViewSet(
mixins.CreateModelMixin,
OutgoingSyncConnectionCreateMixin,
mixins.RetrieveModelMixin,
mixins.DestroyModelMixin,
UsedByMixin,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
from authentik.core.api.groups import GroupMemberSerializer
from authentik.core.api.used_by import UsedByMixin
from authentik.enterprise.providers.google_workspace.models import GoogleWorkspaceProviderUser
from authentik.lib.sync.outgoing.api import OutgoingSyncConnectionCreateMixin


class GoogleWorkspaceProviderUserSerializer(ModelSerializer):
Expand All @@ -30,6 +31,7 @@ class Meta:

class GoogleWorkspaceProviderUserViewSet(
mixins.CreateModelMixin,
OutgoingSyncConnectionCreateMixin,
mixins.RetrieveModelMixin,
mixins.DestroyModelMixin,
UsedByMixin,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -214,3 +214,7 @@
google_id=google_id,
attributes=group,
)

def update_single_attribute(self, connection: GoogleWorkspaceProviderUser):
group = self.directory_service.groups().get(connection.google_id)
connection.attributes = group

Check warning on line 220 in authentik/enterprise/providers/google_workspace/clients/groups.py

View check run for this annotation

Codecov / codecov/patch

authentik/enterprise/providers/google_workspace/clients/groups.py#L219-L220

Added lines #L219 - L220 were not covered by tests
Original file line number Diff line number Diff line change
Expand Up @@ -119,3 +119,7 @@
google_id=email,
attributes=user,
)

def update_single_attribute(self, connection: GoogleWorkspaceProviderUser):
user = self.directory_service.users().get(connection.google_id)
connection.attributes = user

Check warning on line 125 in authentik/enterprise/providers/google_workspace/clients/users.py

View check run for this annotation

Codecov / codecov/patch

authentik/enterprise/providers/google_workspace/clients/users.py#L124-L125

Added lines #L124 - L125 were not covered by tests
111 changes: 56 additions & 55 deletions authentik/enterprise/providers/google_workspace/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,58 @@ def default_scopes() -> list[str]:
]


class GoogleWorkspaceProviderUser(SerializerModel):
"""Mapping of a user and provider to a Google user ID"""

id = models.UUIDField(primary_key=True, editable=False, default=uuid4)
google_id = models.TextField()
user = models.ForeignKey(User, on_delete=models.CASCADE)
provider = models.ForeignKey("GoogleWorkspaceProvider", on_delete=models.CASCADE)
attributes = models.JSONField(default=dict)

@property
def serializer(self) -> type[Serializer]:
from authentik.enterprise.providers.google_workspace.api.users import (
GoogleWorkspaceProviderUserSerializer,
)

return GoogleWorkspaceProviderUserSerializer

class Meta:
verbose_name = _("Google Workspace Provider User")
verbose_name_plural = _("Google Workspace Provider Users")
unique_together = (("google_id", "user", "provider"),)

def __str__(self) -> str:
return f"Google Workspace Provider User {self.user_id} to {self.provider_id}"


class GoogleWorkspaceProviderGroup(SerializerModel):
"""Mapping of a group and provider to a Google group ID"""

id = models.UUIDField(primary_key=True, editable=False, default=uuid4)
google_id = models.TextField()
group = models.ForeignKey(Group, on_delete=models.CASCADE)
provider = models.ForeignKey("GoogleWorkspaceProvider", on_delete=models.CASCADE)
attributes = models.JSONField(default=dict)

@property
def serializer(self) -> type[Serializer]:
from authentik.enterprise.providers.google_workspace.api.groups import (
GoogleWorkspaceProviderGroupSerializer,
)

return GoogleWorkspaceProviderGroupSerializer

class Meta:
verbose_name = _("Google Workspace Provider Group")
verbose_name_plural = _("Google Workspace Provider Groups")
unique_together = (("google_id", "group", "provider"),)

def __str__(self) -> str:
return f"Google Workspace Provider Group {self.group_id} to {self.provider_id}"


class GoogleWorkspaceProvider(OutgoingSyncProvider, BackchannelProvider):
"""Sync users from authentik into Google Workspace."""

Expand Down Expand Up @@ -59,15 +111,16 @@ class GoogleWorkspaceProvider(OutgoingSyncProvider, BackchannelProvider):
)

def client_for_model(
self, model: type[User | Group]
self,
model: type[User | Group | GoogleWorkspaceProviderUser | GoogleWorkspaceProviderGroup],
) -> BaseOutgoingSyncClient[User | Group, Any, Any, Self]:
if issubclass(model, User):
if issubclass(model, User | GoogleWorkspaceProviderUser):
from authentik.enterprise.providers.google_workspace.clients.users import (
GoogleWorkspaceUserClient,
)

return GoogleWorkspaceUserClient(self)
if issubclass(model, Group):
if issubclass(model, Group | GoogleWorkspaceProviderGroup):
from authentik.enterprise.providers.google_workspace.clients.groups import (
GoogleWorkspaceGroupClient,
)
Expand Down Expand Up @@ -144,55 +197,3 @@ def __str__(self):
class Meta:
verbose_name = _("Google Workspace Provider Mapping")
verbose_name_plural = _("Google Workspace Provider Mappings")


class GoogleWorkspaceProviderUser(SerializerModel):
"""Mapping of a user and provider to a Google user ID"""

id = models.UUIDField(primary_key=True, editable=False, default=uuid4)
google_id = models.TextField()
user = models.ForeignKey(User, on_delete=models.CASCADE)
provider = models.ForeignKey(GoogleWorkspaceProvider, on_delete=models.CASCADE)
attributes = models.JSONField(default=dict)

@property
def serializer(self) -> type[Serializer]:
from authentik.enterprise.providers.google_workspace.api.users import (
GoogleWorkspaceProviderUserSerializer,
)

return GoogleWorkspaceProviderUserSerializer

class Meta:
verbose_name = _("Google Workspace Provider User")
verbose_name_plural = _("Google Workspace Provider Users")
unique_together = (("google_id", "user", "provider"),)

def __str__(self) -> str:
return f"Google Workspace Provider User {self.user_id} to {self.provider_id}"


class GoogleWorkspaceProviderGroup(SerializerModel):
"""Mapping of a group and provider to a Google group ID"""

id = models.UUIDField(primary_key=True, editable=False, default=uuid4)
google_id = models.TextField()
group = models.ForeignKey(Group, on_delete=models.CASCADE)
provider = models.ForeignKey(GoogleWorkspaceProvider, on_delete=models.CASCADE)
attributes = models.JSONField(default=dict)

@property
def serializer(self) -> type[Serializer]:
from authentik.enterprise.providers.google_workspace.api.groups import (
GoogleWorkspaceProviderGroupSerializer,
)

return GoogleWorkspaceProviderGroupSerializer

class Meta:
verbose_name = _("Google Workspace Provider Group")
verbose_name_plural = _("Google Workspace Provider Groups")
unique_together = (("google_id", "group", "provider"),)

def __str__(self) -> str:
return f"Google Workspace Provider Group {self.group_id} to {self.provider_id}"
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
from authentik.core.api.used_by import UsedByMixin
from authentik.core.api.users import UserGroupSerializer
from authentik.enterprise.providers.microsoft_entra.models import MicrosoftEntraProviderGroup
from authentik.lib.sync.outgoing.api import OutgoingSyncConnectionCreateMixin


class MicrosoftEntraProviderGroupSerializer(ModelSerializer):
Expand All @@ -30,6 +31,7 @@ class Meta:

class MicrosoftEntraProviderGroupViewSet(
mixins.CreateModelMixin,
OutgoingSyncConnectionCreateMixin,
mixins.RetrieveModelMixin,
mixins.DestroyModelMixin,
UsedByMixin,
Expand Down
2 changes: 2 additions & 0 deletions authentik/enterprise/providers/microsoft_entra/api/users.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
from authentik.core.api.groups import GroupMemberSerializer
from authentik.core.api.used_by import UsedByMixin
from authentik.enterprise.providers.microsoft_entra.models import MicrosoftEntraProviderUser
from authentik.lib.sync.outgoing.api import OutgoingSyncConnectionCreateMixin


class MicrosoftEntraProviderUserSerializer(ModelSerializer):
Expand All @@ -29,6 +30,7 @@ class Meta:


class MicrosoftEntraProviderUserViewSet(
OutgoingSyncConnectionCreateMixin,
mixins.CreateModelMixin,
mixins.RetrieveModelMixin,
mixins.DestroyModelMixin,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -226,3 +226,7 @@
microsoft_id=group.id,
attributes=self.entity_as_dict(group),
)

def update_single_attribute(self, connection: MicrosoftEntraProviderGroup):
data = self._request(self.client.groups.by_group_id(connection.microsoft_id).get())
connection.attributes = self.entity_as_dict(data)

Check warning on line 232 in authentik/enterprise/providers/microsoft_entra/clients/groups.py

View check run for this annotation

Codecov / codecov/patch

authentik/enterprise/providers/microsoft_entra/clients/groups.py#L231-L232

Added lines #L231 - L232 were not covered by tests
47 changes: 41 additions & 6 deletions authentik/enterprise/providers/microsoft_entra/clients/users.py
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,26 @@
microsoft_user.delete()
return response

def get_select_fields(self) -> list[str]:
"""All fields that should be selected when we fetch user data."""
# TODO: Make this customizable in the future
return [
# Default fields
"businessPhones",
"displayName",
"givenName",
"jobTitle",
"mail",
"mobilePhone",
"officeLocation",
"preferredLanguage",
"surname",
"userPrincipalName",
"id",
# Required for logging into M365 using authentik
"onPremisesImmutableId",
]

def create(self, user: User):
"""Create user from scratch and create a connection object"""
microsoft_user = self.to_schema(user, None)
Expand All @@ -75,12 +95,12 @@
response = self._request(self.client.users.post(microsoft_user))
except ObjectExistsSyncException:
# user already exists in microsoft entra, so we can connect them manually
query_params = UsersRequestBuilder.UsersRequestBuilderGetQueryParameters()(
filter=f"mail eq '{microsoft_user.mail}'",
)
request_configuration = (
UsersRequestBuilder.UsersRequestBuilderGetRequestConfiguration(
query_parameters=query_params,
query_parameters=UsersRequestBuilder.UsersRequestBuilderGetQueryParameters(
filter=f"mail eq '{microsoft_user.mail}'",
select=self.get_select_fields(),
),
)
)
user_data = self._request(self.client.users.get(request_configuration))
Expand All @@ -99,7 +119,6 @@
except TransientSyncException as exc:
raise exc
else:
print(self.entity_as_dict(response))
return MicrosoftEntraProviderUser.objects.create(
provider=self.provider,
user=user,
Expand All @@ -120,7 +139,12 @@

def discover(self):
"""Iterate through all users and connect them with authentik users if possible"""
users = self._request(self.client.users.get())
request_configuration = UsersRequestBuilder.UsersRequestBuilderGetRequestConfiguration(
query_parameters=UsersRequestBuilder.UsersRequestBuilderGetQueryParameters(
select=self.get_select_fields(),
),
)
users = self._request(self.client.users.get(request_configuration))
next_link = True
while next_link:
for user in users.value:
Expand All @@ -141,3 +165,14 @@
microsoft_id=user.id,
attributes=self.entity_as_dict(user),
)

def update_single_attribute(self, connection: MicrosoftEntraProviderUser):
request_configuration = UsersRequestBuilder.UsersRequestBuilderGetRequestConfiguration(

Check warning on line 170 in authentik/enterprise/providers/microsoft_entra/clients/users.py

View check run for this annotation

Codecov / codecov/patch

authentik/enterprise/providers/microsoft_entra/clients/users.py#L170

Added line #L170 was not covered by tests
query_parameters=UsersRequestBuilder.UsersRequestBuilderGetQueryParameters(
select=self.get_select_fields(),
),
)
data = self._request(

Check warning on line 175 in authentik/enterprise/providers/microsoft_entra/clients/users.py

View check run for this annotation

Codecov / codecov/patch

authentik/enterprise/providers/microsoft_entra/clients/users.py#L175

Added line #L175 was not covered by tests
self.client.users.by_user_id(connection.microsoft_id).get(request_configuration)
)
connection.attributes = self.entity_as_dict(data)

Check warning on line 178 in authentik/enterprise/providers/microsoft_entra/clients/users.py

View check run for this annotation

Codecov / codecov/patch

authentik/enterprise/providers/microsoft_entra/clients/users.py#L178

Added line #L178 was not covered by tests
Loading
Loading