mirror of
https://github.com/goauthentik/authentik.git
synced 2026-06-17 19:09:11 +03:00
providers/scim: add webex compatibility mode (#21208)
* providers/scim: add webex compatibility mode Signed-off-by: Jens Langhammer <jens@goauthentik.io> * migrate Signed-off-by: Jens Langhammer <jens@goauthentik.io> --------- Signed-off-by: Jens Langhammer <jens@goauthentik.io>
This commit is contained in:
@@ -54,6 +54,13 @@ class SCIMGroupClient(SCIMClient[Group, SCIMProviderGroup, SCIMGroupSchema]):
|
||||
["group", "provider", "connection"],
|
||||
)
|
||||
|
||||
def _create_group_member(self, id: str) -> GroupMember:
|
||||
member = GroupMember(value=id)
|
||||
# https://developer.webex.com/admin/docs/api/v1/scim-2-groups/create-a-group
|
||||
if self.provider.compatibility_mode == SCIMCompatibilityMode.WEBEX:
|
||||
member.type = "user"
|
||||
return member
|
||||
|
||||
def to_schema(self, obj: Group, connection: SCIMProviderGroup) -> SCIMGroupSchema:
|
||||
"""Convert authentik user into SCIM"""
|
||||
raw_scim_group = super().to_schema(obj, connection)
|
||||
@@ -77,9 +84,7 @@ class SCIMGroupClient(SCIMClient[Group, SCIMProviderGroup, SCIMGroupSchema]):
|
||||
members = []
|
||||
for user in connections:
|
||||
members.append(
|
||||
GroupMember(
|
||||
value=user.scim_id,
|
||||
)
|
||||
self._create_group_member(user.scim_id),
|
||||
)
|
||||
if members:
|
||||
scim_group.members = members
|
||||
@@ -322,7 +327,7 @@ class SCIMGroupClient(SCIMClient[Group, SCIMProviderGroup, SCIMGroupSchema]):
|
||||
op=PatchOp.add,
|
||||
path="members",
|
||||
value=[
|
||||
GroupMember(value=x).model_dump(
|
||||
self._create_group_member(x).model_dump(
|
||||
mode="json",
|
||||
exclude_unset=True,
|
||||
)
|
||||
@@ -335,7 +340,7 @@ class SCIMGroupClient(SCIMClient[Group, SCIMProviderGroup, SCIMGroupSchema]):
|
||||
op=PatchOp.remove,
|
||||
path="members",
|
||||
value=[
|
||||
GroupMember(value=x).model_dump(
|
||||
self._create_group_member(x).model_dump(
|
||||
mode="json",
|
||||
exclude_unset=True,
|
||||
)
|
||||
@@ -363,7 +368,7 @@ class SCIMGroupClient(SCIMClient[Group, SCIMProviderGroup, SCIMGroupSchema]):
|
||||
op=PatchOp.add,
|
||||
path="members",
|
||||
value=[
|
||||
GroupMember(value=x).model_dump(
|
||||
self._create_group_member(x).model_dump(
|
||||
mode="json",
|
||||
exclude_unset=True,
|
||||
)
|
||||
@@ -391,7 +396,7 @@ class SCIMGroupClient(SCIMClient[Group, SCIMProviderGroup, SCIMGroupSchema]):
|
||||
op=PatchOp.remove,
|
||||
path="members",
|
||||
value=[
|
||||
GroupMember(value=x).model_dump(
|
||||
self._create_group_member(x).model_dump(
|
||||
mode="json",
|
||||
exclude_unset=True,
|
||||
)
|
||||
|
||||
@@ -84,4 +84,21 @@ class Migration(migrations.Migration):
|
||||
to="authentik_core.group",
|
||||
),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name="scimprovider",
|
||||
name="compatibility_mode",
|
||||
field=models.CharField(
|
||||
choices=[
|
||||
("default", "Default"),
|
||||
("aws", "AWS"),
|
||||
("slack", "Slack"),
|
||||
("sfdc", "Salesforce"),
|
||||
("webex", "Webex"),
|
||||
],
|
||||
default="default",
|
||||
help_text="Alter authentik behavior for vendor-specific SCIM implementations.",
|
||||
max_length=30,
|
||||
verbose_name="SCIM Compatibility Mode",
|
||||
),
|
||||
),
|
||||
]
|
||||
|
||||
@@ -82,6 +82,7 @@ class SCIMCompatibilityMode(models.TextChoices):
|
||||
AWS = "aws", _("AWS")
|
||||
SLACK = "slack", _("Slack")
|
||||
SALESFORCE = "sfdc", _("Salesforce")
|
||||
WEBEX = "webex", _("Webex")
|
||||
|
||||
|
||||
class SCIMProvider(OutgoingSyncProvider, BackchannelProvider):
|
||||
|
||||
@@ -7,7 +7,7 @@ from authentik.blueprints.tests import apply_blueprint
|
||||
from authentik.core.models import Application, Group, User
|
||||
from authentik.lib.generators import generate_id
|
||||
from authentik.providers.scim.clients.schema import ServiceProviderConfiguration
|
||||
from authentik.providers.scim.models import SCIMMapping, SCIMProvider
|
||||
from authentik.providers.scim.models import SCIMCompatibilityMode, SCIMMapping, SCIMProvider
|
||||
from authentik.providers.scim.tasks import scim_sync
|
||||
from authentik.tenants.models import Tenant
|
||||
|
||||
@@ -26,12 +26,13 @@ class SCIMMembershipTests(TestCase):
|
||||
Tenant.objects.update(avatars="none")
|
||||
|
||||
@apply_blueprint("system/providers-scim.yaml")
|
||||
def configure(self) -> None:
|
||||
def configure(self, **kwargs) -> None:
|
||||
"""Configure provider"""
|
||||
self.provider: SCIMProvider = SCIMProvider.objects.create(
|
||||
name=generate_id(),
|
||||
url="https://localhost",
|
||||
token=generate_id(),
|
||||
**kwargs,
|
||||
)
|
||||
self.app: Application = Application.objects.create(
|
||||
name=generate_id(),
|
||||
@@ -353,3 +354,113 @@ class SCIMMembershipTests(TestCase):
|
||||
]
|
||||
},
|
||||
)
|
||||
|
||||
def test_member_add_save_compat_webex(self):
|
||||
"""Test member add + save"""
|
||||
config = ServiceProviderConfiguration.default()
|
||||
|
||||
config.patch.supported = True
|
||||
user_scim_id = generate_id()
|
||||
group_scim_id = generate_id()
|
||||
uid = generate_id()
|
||||
group = Group.objects.create(
|
||||
name=uid,
|
||||
)
|
||||
|
||||
user = User.objects.create(username=generate_id())
|
||||
|
||||
# Test initial sync of group creation
|
||||
with Mocker() as mocker:
|
||||
mocker.get(
|
||||
"https://localhost/ServiceProviderConfig",
|
||||
json=config.model_dump(),
|
||||
)
|
||||
mocker.post(
|
||||
"https://localhost/Users",
|
||||
json={
|
||||
"id": user_scim_id,
|
||||
},
|
||||
)
|
||||
mocker.post(
|
||||
"https://localhost/Groups",
|
||||
json={
|
||||
"id": group_scim_id,
|
||||
},
|
||||
)
|
||||
|
||||
self.configure(compatibility_mode=SCIMCompatibilityMode.WEBEX)
|
||||
scim_sync.send(self.provider.pk)
|
||||
|
||||
self.assertEqual(mocker.call_count, 3)
|
||||
self.assertEqual(mocker.request_history[0].method, "GET")
|
||||
self.assertEqual(mocker.request_history[1].method, "POST")
|
||||
self.assertEqual(mocker.request_history[2].method, "POST")
|
||||
self.assertJSONEqual(
|
||||
mocker.request_history[1].body,
|
||||
{
|
||||
"schemas": ["urn:ietf:params:scim:schemas:core:2.0:User"],
|
||||
"emails": [],
|
||||
"active": True,
|
||||
"externalId": user.uid,
|
||||
"name": {"familyName": " ", "formatted": " ", "givenName": ""},
|
||||
"displayName": "",
|
||||
"userName": user.username,
|
||||
},
|
||||
)
|
||||
self.assertJSONEqual(
|
||||
mocker.request_history[2].body,
|
||||
{
|
||||
"schemas": ["urn:ietf:params:scim:schemas:core:2.0:Group"],
|
||||
"externalId": str(group.pk),
|
||||
"displayName": group.name,
|
||||
},
|
||||
)
|
||||
|
||||
with Mocker() as mocker:
|
||||
mocker.get(
|
||||
"https://localhost/ServiceProviderConfig",
|
||||
json=config.model_dump(),
|
||||
)
|
||||
mocker.get(
|
||||
f"https://localhost/Groups/{group_scim_id}",
|
||||
json={},
|
||||
)
|
||||
mocker.patch(
|
||||
f"https://localhost/Groups/{group_scim_id}",
|
||||
json={},
|
||||
)
|
||||
group.users.add(user)
|
||||
group.save()
|
||||
self.assertEqual(mocker.call_count, 3)
|
||||
self.assertEqual(mocker.request_history[0].method, "PATCH")
|
||||
self.assertEqual(mocker.request_history[1].method, "PATCH")
|
||||
self.assertEqual(mocker.request_history[2].method, "GET")
|
||||
self.assertJSONEqual(
|
||||
mocker.request_history[0].body,
|
||||
{
|
||||
"schemas": ["urn:ietf:params:scim:api:messages:2.0:PatchOp"],
|
||||
"Operations": [
|
||||
{
|
||||
"op": "add",
|
||||
"path": "members",
|
||||
"value": [{"value": user_scim_id, "type": "user"}],
|
||||
}
|
||||
],
|
||||
},
|
||||
)
|
||||
self.assertJSONEqual(
|
||||
mocker.request_history[1].body,
|
||||
{
|
||||
"Operations": [
|
||||
{
|
||||
"op": "replace",
|
||||
"value": {
|
||||
"id": group_scim_id,
|
||||
"displayName": group.name,
|
||||
"schemas": ["urn:ietf:params:scim:schemas:core:2.0:Group"],
|
||||
"externalId": str(group.pk),
|
||||
},
|
||||
}
|
||||
]
|
||||
},
|
||||
)
|
||||
|
||||
@@ -11057,7 +11057,8 @@
|
||||
"default",
|
||||
"aws",
|
||||
"slack",
|
||||
"sfdc"
|
||||
"sfdc",
|
||||
"webex"
|
||||
],
|
||||
"title": "SCIM Compatibility Mode",
|
||||
"description": "Alter authentik behavior for vendor-specific SCIM implementations."
|
||||
|
||||
@@ -25,6 +25,7 @@ const (
|
||||
COMPATIBILITYMODEENUM_AWS CompatibilityModeEnum = "aws"
|
||||
COMPATIBILITYMODEENUM_SLACK CompatibilityModeEnum = "slack"
|
||||
COMPATIBILITYMODEENUM_SFDC CompatibilityModeEnum = "sfdc"
|
||||
COMPATIBILITYMODEENUM_WEBEX CompatibilityModeEnum = "webex"
|
||||
)
|
||||
|
||||
// All allowed values of CompatibilityModeEnum enum
|
||||
@@ -33,6 +34,7 @@ var AllowedCompatibilityModeEnumEnumValues = []CompatibilityModeEnum{
|
||||
"aws",
|
||||
"slack",
|
||||
"sfdc",
|
||||
"webex",
|
||||
}
|
||||
|
||||
func (v *CompatibilityModeEnum) UnmarshalJSON(src []byte) error {
|
||||
|
||||
@@ -21,6 +21,8 @@ pub enum CompatibilityModeEnum {
|
||||
Slack,
|
||||
#[serde(rename = "sfdc")]
|
||||
Sfdc,
|
||||
#[serde(rename = "webex")]
|
||||
Webex,
|
||||
}
|
||||
|
||||
impl std::fmt::Display for CompatibilityModeEnum {
|
||||
@@ -30,6 +32,7 @@ impl std::fmt::Display for CompatibilityModeEnum {
|
||||
Self::Aws => write!(f, "aws"),
|
||||
Self::Slack => write!(f, "slack"),
|
||||
Self::Sfdc => write!(f, "sfdc"),
|
||||
Self::Webex => write!(f, "webex"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -22,6 +22,7 @@ export const CompatibilityModeEnum = {
|
||||
Aws: 'aws',
|
||||
Slack: 'slack',
|
||||
Sfdc: 'sfdc',
|
||||
Webex: 'webex',
|
||||
UnknownDefaultOpenApi: '11184809'
|
||||
} as const;
|
||||
export type CompatibilityModeEnum = typeof CompatibilityModeEnum[keyof typeof CompatibilityModeEnum];
|
||||
|
||||
@@ -35773,6 +35773,7 @@ components:
|
||||
- aws
|
||||
- slack
|
||||
- sfdc
|
||||
- webex
|
||||
type: string
|
||||
Config:
|
||||
type: object
|
||||
|
||||
@@ -198,6 +198,11 @@ export function renderForm({ provider = {}, errors = {}, update }: SCIMProviderF
|
||||
value: CompatibilityModeEnum.Sfdc,
|
||||
description: html`${msg("Altered behavior for usage with Salesforce.")}`,
|
||||
},
|
||||
{
|
||||
label: msg("Webex"),
|
||||
value: CompatibilityModeEnum.Webex,
|
||||
description: html`${msg("Altered behavior for usage with Cisco Webex.")}`,
|
||||
},
|
||||
]}
|
||||
help=${msg(
|
||||
"Alter authentik's behavior for vendor-specific SCIM implementations.",
|
||||
|
||||
Reference in New Issue
Block a user