File size: 5,587 Bytes
469eae6
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
from typing import List, Union

from litellm.proxy._types import (
    LiteLLM_TeamTable,
    LiteLLM_UserTable,
    Member,
    NewUserResponse,
)
from litellm.types.proxy.management_endpoints.scim_v2 import *


class ScimTransformations:
    DEFAULT_SCIM_NAME = "Unknown User"
    DEFAULT_SCIM_FAMILY_NAME = "Unknown Family Name"
    DEFAULT_SCIM_DISPLAY_NAME = "Unknown Display Name"
    DEFAULT_SCIM_MEMBER_VALUE = "Unknown Member Value"

    @staticmethod
    async def transform_litellm_user_to_scim_user(
        user: Union[LiteLLM_UserTable, NewUserResponse],
    ) -> SCIMUser:
        from litellm.proxy.proxy_server import prisma_client

        if prisma_client is None:
            raise HTTPException(
                status_code=500, detail={"error": "No database connected"}
            )

        # Get user's teams/groups
        groups = []
        for team_id in user.teams or []:
            team = await prisma_client.db.litellm_teamtable.find_unique(
                where={"team_id": team_id}
            )
            if team:
                team_alias = getattr(team, "team_alias", team.team_id)
                groups.append(SCIMUserGroup(value=team.team_id, display=team_alias))

        user_created_at = user.created_at.isoformat() if user.created_at else None
        user_updated_at = user.updated_at.isoformat() if user.updated_at else None

        emails = []
        if user.user_email:
            emails.append(SCIMUserEmail(value=user.user_email, primary=True))

        return SCIMUser(
            schemas=["urn:ietf:params:scim:schemas:core:2.0:User"],
            id=user.user_id,
            userName=ScimTransformations._get_scim_user_name(user),
            displayName=ScimTransformations._get_scim_user_name(user),
            name=SCIMUserName(
                familyName=ScimTransformations._get_scim_family_name(user),
                givenName=ScimTransformations._get_scim_given_name(user),
            ),
            emails=emails,
            groups=groups,
            active=True,
            meta={
                "resourceType": "User",
                "created": user_created_at,
                "lastModified": user_updated_at,
            },
        )

    @staticmethod
    def _get_scim_user_name(user: Union[LiteLLM_UserTable, NewUserResponse]) -> str:
        """
        SCIM requires a display name with length > 0

        We use the same userName and displayName for SCIM users
        """
        if user.user_email and len(user.user_email) > 0:
            return user.user_email
        return ScimTransformations.DEFAULT_SCIM_DISPLAY_NAME

    @staticmethod
    def _get_scim_family_name(user: Union[LiteLLM_UserTable, NewUserResponse]) -> str:
        """
        SCIM requires a family name with length > 0
        """
        metadata = user.metadata or {}
        if "scim_metadata" in metadata:
            scim_metadata: LiteLLM_UserScimMetadata = LiteLLM_UserScimMetadata(
                **metadata["scim_metadata"]
            )
            if scim_metadata.familyName and len(scim_metadata.familyName) > 0:
                return scim_metadata.familyName

        if user.user_alias and len(user.user_alias) > 0:
            return user.user_alias
        return ScimTransformations.DEFAULT_SCIM_FAMILY_NAME

    @staticmethod
    def _get_scim_given_name(user: Union[LiteLLM_UserTable, NewUserResponse]) -> str:
        """
        SCIM requires a given name with length > 0
        """
        metadata = user.metadata or {}
        if "scim_metadata" in metadata:
            scim_metadata: LiteLLM_UserScimMetadata = LiteLLM_UserScimMetadata(
                **metadata["scim_metadata"]
            )
            if scim_metadata.givenName and len(scim_metadata.givenName) > 0:
                return scim_metadata.givenName

        if user.user_alias and len(user.user_alias) > 0:
            return user.user_alias or ScimTransformations.DEFAULT_SCIM_NAME
        return ScimTransformations.DEFAULT_SCIM_NAME

    @staticmethod
    async def transform_litellm_team_to_scim_group(
        team: Union[LiteLLM_TeamTable, dict],
    ) -> SCIMGroup:
        from litellm.proxy.proxy_server import prisma_client

        if prisma_client is None:
            raise HTTPException(
                status_code=500, detail={"error": "No database connected"}
            )

        if isinstance(team, dict):
            team = LiteLLM_TeamTable(**team)

        # Get team members
        scim_members: List[SCIMMember] = []
        for member in team.members_with_roles or []:
            scim_members.append(
                SCIMMember(
                    value=ScimTransformations._get_scim_member_value(member),
                    display=member.user_email,
                )
            )

        team_alias = getattr(team, "team_alias", team.team_id)
        team_created_at = team.created_at.isoformat() if team.created_at else None
        team_updated_at = team.updated_at.isoformat() if team.updated_at else None

        return SCIMGroup(
            schemas=["urn:ietf:params:scim:schemas:core:2.0:Group"],
            id=team.team_id,
            displayName=team_alias,
            members=scim_members,
            meta={
                "resourceType": "Group",
                "created": team_created_at,
                "lastModified": team_updated_at,
            },
        )

    @staticmethod
    def _get_scim_member_value(member: Member) -> str:
        if member.user_email:
            return member.user_email
        return ScimTransformations.DEFAULT_SCIM_MEMBER_VALUE