mirror of
https://github.com/BerriAI/litellm.git
synced 2025-04-26 03:04:13 +00:00
working scim transformation
This commit is contained in:
parent
4deb795105
commit
24978c92ca
2 changed files with 853 additions and 208 deletions
|
@ -23,7 +23,7 @@ from fastapi import (
|
||||||
from pydantic import BaseModel, EmailStr, Field
|
from pydantic import BaseModel, EmailStr, Field
|
||||||
|
|
||||||
from litellm._logging import verbose_proxy_logger
|
from litellm._logging import verbose_proxy_logger
|
||||||
from litellm.proxy._types import NewUserRequest
|
from litellm.proxy._types import LiteLLM_UserTable, NewUserRequest, NewUserResponse
|
||||||
from litellm.proxy.management_endpoints.internal_user_endpoints import new_user
|
from litellm.proxy.management_endpoints.internal_user_endpoints import new_user
|
||||||
|
|
||||||
scim_router = APIRouter(
|
scim_router = APIRouter(
|
||||||
|
@ -41,9 +41,9 @@ class SCIMResource(BaseModel):
|
||||||
|
|
||||||
|
|
||||||
class SCIMUserName(BaseModel):
|
class SCIMUserName(BaseModel):
|
||||||
|
familyName: str
|
||||||
|
givenName: str
|
||||||
formatted: Optional[str] = None
|
formatted: Optional[str] = None
|
||||||
familyName: Optional[str] = None
|
|
||||||
givenName: Optional[str] = None
|
|
||||||
middleName: Optional[str] = None
|
middleName: Optional[str] = None
|
||||||
honorificPrefix: Optional[str] = None
|
honorificPrefix: Optional[str] = None
|
||||||
honorificSuffix: Optional[str] = None
|
honorificSuffix: Optional[str] = None
|
||||||
|
@ -63,7 +63,7 @@ class SCIMUserGroup(BaseModel):
|
||||||
|
|
||||||
class SCIMUser(SCIMResource):
|
class SCIMUser(SCIMResource):
|
||||||
userName: str
|
userName: str
|
||||||
name: Optional[SCIMUserName] = None
|
name: SCIMUserName
|
||||||
displayName: Optional[str] = None
|
displayName: Optional[str] = None
|
||||||
active: bool = True
|
active: bool = True
|
||||||
emails: Optional[List[SCIMUserEmail]] = None
|
emails: Optional[List[SCIMUserEmail]] = None
|
||||||
|
@ -89,6 +89,88 @@ class SCIMListResponse(BaseModel):
|
||||||
Resources: List[Union[SCIMUser, SCIMGroup]]
|
Resources: List[Union[SCIMUser, SCIMGroup]]
|
||||||
|
|
||||||
|
|
||||||
|
class ScimTransformations:
|
||||||
|
DEFAULT_SCIM_NAME = "Unknown User"
|
||||||
|
DEFAULT_SCIM_FAMILY_NAME = "Unknown Family Name"
|
||||||
|
DEFAULT_SCIM_DISPLAY_NAME = "Unknown Display Name"
|
||||||
|
|
||||||
|
@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
|
||||||
|
"""
|
||||||
|
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
|
||||||
|
"""
|
||||||
|
if user.user_alias and len(user.user_alias) > 0:
|
||||||
|
return user.user_alias
|
||||||
|
return ScimTransformations.DEFAULT_SCIM_NAME
|
||||||
|
|
||||||
|
|
||||||
# User Endpoints
|
# User Endpoints
|
||||||
@scim_router.get(
|
@scim_router.get(
|
||||||
"/Users",
|
"/Users",
|
||||||
|
@ -121,11 +203,13 @@ async def get_users(
|
||||||
where_conditions["user_email"] = email
|
where_conditions["user_email"] = email
|
||||||
|
|
||||||
# Get users from database
|
# Get users from database
|
||||||
users = await prisma_client.db.litellm_usertable.find_many(
|
users: List[LiteLLM_UserTable] = (
|
||||||
where=where_conditions,
|
await prisma_client.db.litellm_usertable.find_many(
|
||||||
skip=(startIndex - 1),
|
where=where_conditions,
|
||||||
take=count,
|
skip=(startIndex - 1),
|
||||||
order={"created_at": "desc"},
|
take=count,
|
||||||
|
order={"created_at": "desc"},
|
||||||
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
# Get total count for pagination
|
# Get total count for pagination
|
||||||
|
@ -136,36 +220,8 @@ async def get_users(
|
||||||
# Convert to SCIM format
|
# Convert to SCIM format
|
||||||
scim_users = []
|
scim_users = []
|
||||||
for user in users:
|
for user in users:
|
||||||
emails = []
|
scim_user = await ScimTransformations.transform_litellm_user_to_scim_user(
|
||||||
if user.user_email:
|
user=user
|
||||||
emails.append(SCIMUserEmail(value=user.user_email, primary=True))
|
|
||||||
|
|
||||||
# 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
|
|
||||||
|
|
||||||
scim_user = SCIMUser(
|
|
||||||
schemas=["urn:ietf:params:scim:schemas:core:2.0:User"],
|
|
||||||
id=user.user_id,
|
|
||||||
userName=user.user_id,
|
|
||||||
displayName=user.user_email or user.user_id,
|
|
||||||
emails=emails,
|
|
||||||
groups=groups,
|
|
||||||
active=True,
|
|
||||||
meta={
|
|
||||||
"resourceType": "User",
|
|
||||||
"created": user_created_at,
|
|
||||||
"lastModified": user_updated_at,
|
|
||||||
},
|
|
||||||
)
|
)
|
||||||
scim_users.append(scim_user)
|
scim_users.append(scim_user)
|
||||||
|
|
||||||
|
@ -209,37 +265,8 @@ async def get_user(
|
||||||
)
|
)
|
||||||
|
|
||||||
# Convert to SCIM format
|
# Convert to SCIM format
|
||||||
emails = []
|
scim_user = await ScimTransformations.transform_litellm_user_to_scim_user(user)
|
||||||
if user.user_email:
|
return scim_user
|
||||||
emails.append(SCIMUserEmail(value=user.user_email, primary=True))
|
|
||||||
|
|
||||||
# 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
|
|
||||||
|
|
||||||
return SCIMUser(
|
|
||||||
schemas=["urn:ietf:params:scim:schemas:core:2.0:User"],
|
|
||||||
id=user.user_id,
|
|
||||||
userName=user.user_id,
|
|
||||||
displayName=user.user_email or user.user_id,
|
|
||||||
emails=emails,
|
|
||||||
groups=groups,
|
|
||||||
active=True,
|
|
||||||
meta={
|
|
||||||
"resourceType": "User",
|
|
||||||
"created": user_created_at,
|
|
||||||
"lastModified": user_updated_at,
|
|
||||||
},
|
|
||||||
)
|
|
||||||
|
|
||||||
except HTTPException:
|
except HTTPException:
|
||||||
raise
|
raise
|
||||||
|
@ -291,55 +318,14 @@ async def create_user(
|
||||||
data=NewUserRequest(
|
data=NewUserRequest(
|
||||||
user_id=user_id,
|
user_id=user_id,
|
||||||
user_email=user_email,
|
user_email=user_email,
|
||||||
|
user_alias=user.name.givenName,
|
||||||
|
teams=[group.value for group in user.groups] if user.groups else None,
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
# If teams were specified, add user to teams
|
scim_user = await ScimTransformations.transform_litellm_user_to_scim_user(
|
||||||
if user.groups:
|
user=created_user
|
||||||
for group in user.groups:
|
|
||||||
team_id = group.value
|
|
||||||
# Check if team exists
|
|
||||||
team = await prisma_client.db.litellm_teamtable.find_unique(
|
|
||||||
where={"team_id": team_id}
|
|
||||||
)
|
|
||||||
if team:
|
|
||||||
# Update team members to include this user
|
|
||||||
current_members = team.members or []
|
|
||||||
if user_id not in current_members:
|
|
||||||
await prisma_client.db.litellm_teamtable.update(
|
|
||||||
where={"team_id": team_id},
|
|
||||||
data={"members": {"push": user_id}},
|
|
||||||
)
|
|
||||||
|
|
||||||
# Construct SCIM response
|
|
||||||
emails = []
|
|
||||||
if user_email:
|
|
||||||
emails.append(SCIMUserEmail(value=user_email, primary=True))
|
|
||||||
|
|
||||||
user_created_at = (
|
|
||||||
created_user.created_at.isoformat()
|
|
||||||
if created_user.created_at
|
|
||||||
else datetime.now(timezone.utc).isoformat()
|
|
||||||
)
|
|
||||||
user_updated_at = (
|
|
||||||
created_user.updated_at.isoformat()
|
|
||||||
if created_user.updated_at
|
|
||||||
else datetime.now(timezone.utc).isoformat()
|
|
||||||
)
|
|
||||||
|
|
||||||
return SCIMUser(
|
|
||||||
schemas=["urn:ietf:params:scim:schemas:core:2.0:User"],
|
|
||||||
id=user_id,
|
|
||||||
userName=user_id,
|
|
||||||
displayName=user_email or user_id,
|
|
||||||
emails=emails,
|
|
||||||
groups=user.groups,
|
|
||||||
active=True,
|
|
||||||
meta={
|
|
||||||
"resourceType": "User",
|
|
||||||
"created": user_created_at,
|
|
||||||
"lastModified": user_updated_at,
|
|
||||||
},
|
|
||||||
)
|
)
|
||||||
|
return scim_user
|
||||||
|
|
||||||
except HTTPException:
|
except HTTPException:
|
||||||
raise
|
raise
|
||||||
|
@ -365,99 +351,8 @@ async def update_user(
|
||||||
|
|
||||||
if prisma_client is None:
|
if prisma_client is None:
|
||||||
raise HTTPException(status_code=500, detail={"error": "No database connected"})
|
raise HTTPException(status_code=500, detail={"error": "No database connected"})
|
||||||
|
|
||||||
try:
|
try:
|
||||||
# Check if user exists
|
return None
|
||||||
existing_user = await prisma_client.db.litellm_usertable.find_unique(
|
|
||||||
where={"user_id": user_id}
|
|
||||||
)
|
|
||||||
|
|
||||||
if not existing_user:
|
|
||||||
raise HTTPException(
|
|
||||||
status_code=404, detail={"error": f"User not found with ID: {user_id}"}
|
|
||||||
)
|
|
||||||
|
|
||||||
# Extract email from SCIM user
|
|
||||||
user_email = None
|
|
||||||
if user.emails and len(user.emails) > 0:
|
|
||||||
user_email = user.emails[0].value
|
|
||||||
|
|
||||||
# Prepare update data
|
|
||||||
existing_metadata = existing_user.metadata if existing_user.metadata else {}
|
|
||||||
update_data = {
|
|
||||||
"user_email": user_email,
|
|
||||||
"metadata": {**existing_metadata, "scim_data": user.model_dump()},
|
|
||||||
}
|
|
||||||
|
|
||||||
# Update user in database
|
|
||||||
updated_user = await prisma_client.db.litellm_usertable.update(
|
|
||||||
where={"user_id": user_id}, data=update_data
|
|
||||||
)
|
|
||||||
|
|
||||||
# Handle group memberships if provided
|
|
||||||
if user.groups:
|
|
||||||
# Get current teams
|
|
||||||
current_teams = existing_user.teams or []
|
|
||||||
|
|
||||||
# Teams to add user to
|
|
||||||
requested_teams = [group.value for group in user.groups]
|
|
||||||
|
|
||||||
# Add user to requested teams
|
|
||||||
for team_id in requested_teams:
|
|
||||||
if team_id not in current_teams:
|
|
||||||
team = await prisma_client.db.litellm_teamtable.find_unique(
|
|
||||||
where={"team_id": team_id}
|
|
||||||
)
|
|
||||||
if team:
|
|
||||||
current_members = team.members or []
|
|
||||||
if user_id not in current_members:
|
|
||||||
await prisma_client.db.litellm_teamtable.update(
|
|
||||||
where={"team_id": team_id},
|
|
||||||
data={"members": {"push": user_id}},
|
|
||||||
)
|
|
||||||
|
|
||||||
# Remove user from teams not in the request
|
|
||||||
for team_id in current_teams:
|
|
||||||
if team_id not in requested_teams:
|
|
||||||
team = await prisma_client.db.litellm_teamtable.find_unique(
|
|
||||||
where={"team_id": team_id}
|
|
||||||
)
|
|
||||||
if team:
|
|
||||||
current_members = team.members or []
|
|
||||||
if user_id in current_members:
|
|
||||||
new_members = [m for m in current_members if m != user_id]
|
|
||||||
await prisma_client.db.litellm_teamtable.update(
|
|
||||||
where={"team_id": team_id},
|
|
||||||
data={"members": new_members},
|
|
||||||
)
|
|
||||||
|
|
||||||
# Construct SCIM response
|
|
||||||
emails = []
|
|
||||||
if user_email:
|
|
||||||
emails.append(SCIMUserEmail(value=user_email, primary=True))
|
|
||||||
|
|
||||||
user_created_at = (
|
|
||||||
updated_user.created_at.isoformat() if updated_user.created_at else None
|
|
||||||
)
|
|
||||||
user_updated_at = (
|
|
||||||
updated_user.updated_at.isoformat() if updated_user.updated_at else None
|
|
||||||
)
|
|
||||||
|
|
||||||
return SCIMUser(
|
|
||||||
schemas=["urn:ietf:params:scim:schemas:core:2.0:User"],
|
|
||||||
id=user_id,
|
|
||||||
userName=user_id,
|
|
||||||
displayName=user_email or user_id,
|
|
||||||
emails=emails,
|
|
||||||
groups=user.groups,
|
|
||||||
active=user.active,
|
|
||||||
meta={
|
|
||||||
"resourceType": "User",
|
|
||||||
"created": user_created_at,
|
|
||||||
"lastModified": user_updated_at,
|
|
||||||
},
|
|
||||||
)
|
|
||||||
|
|
||||||
except HTTPException:
|
except HTTPException:
|
||||||
raise
|
raise
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
|
|
750
tests/scim_tests/scim_e2e_test.json
Normal file
750
tests/scim_tests/scim_e2e_test.json
Normal file
|
@ -0,0 +1,750 @@
|
||||||
|
{
|
||||||
|
"version": "1.0",
|
||||||
|
"exported_at": 1715608731,
|
||||||
|
"name": "Okta SCIM 2.0 SPEC Test",
|
||||||
|
"description": "Basic tests to see if your SCIM server will work with Okta",
|
||||||
|
"trigger_url": "https://api.runscope.com/radar/37d9f10e-e250-4071-9cec-1fa30e56b42b/trigger",
|
||||||
|
"steps": [
|
||||||
|
{
|
||||||
|
"step_type": "request",
|
||||||
|
"skipped": false,
|
||||||
|
"note": "Required Test: Test Users endpoint",
|
||||||
|
"auth": {},
|
||||||
|
"multipart_form": [],
|
||||||
|
"headers": {
|
||||||
|
"Accept-Charset": [
|
||||||
|
"utf-8"
|
||||||
|
],
|
||||||
|
"Content-Type": [
|
||||||
|
"application/scim+json; charset=utf-8"
|
||||||
|
],
|
||||||
|
"Accept": [
|
||||||
|
"application/scim+json"
|
||||||
|
],
|
||||||
|
"Authorization": [
|
||||||
|
"{{auth}}"
|
||||||
|
],
|
||||||
|
"User-Agent": [
|
||||||
|
"OKTA SCIM Integration"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"method": "GET",
|
||||||
|
"url": "{{SCIMBaseURL}}/Users?count=1&startIndex=1",
|
||||||
|
"assertions": [
|
||||||
|
{
|
||||||
|
"comparison": "equal_number",
|
||||||
|
"source": "response_status",
|
||||||
|
"value": "200"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"comparison": "not_empty",
|
||||||
|
"source": "response_json",
|
||||||
|
"value": null,
|
||||||
|
"property": "Resources"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"comparison": "has_value",
|
||||||
|
"source": "response_json",
|
||||||
|
"value": "urn:ietf:params:scim:api:messages:2.0:ListResponse",
|
||||||
|
"property": "schemas"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"comparison": "is_a_number",
|
||||||
|
"source": "response_json",
|
||||||
|
"value": null,
|
||||||
|
"property": "itemsPerPage"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"comparison": "is_a_number",
|
||||||
|
"source": "response_json",
|
||||||
|
"value": null,
|
||||||
|
"property": "startIndex"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"comparison": "is_a_number",
|
||||||
|
"source": "response_json",
|
||||||
|
"value": null,
|
||||||
|
"property": "totalResults"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"comparison": "not_empty",
|
||||||
|
"source": "response_json",
|
||||||
|
"value": null,
|
||||||
|
"property": "Resources[0].id"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"comparison": "not_empty",
|
||||||
|
"source": "response_json",
|
||||||
|
"value": null,
|
||||||
|
"property": "Resources[0].name.familyName"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"comparison": "not_empty",
|
||||||
|
"source": "response_json",
|
||||||
|
"value": null,
|
||||||
|
"property": "Resources[0].name.givenName"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"comparison": "not_empty",
|
||||||
|
"source": "response_json",
|
||||||
|
"value": null,
|
||||||
|
"property": "Resources[0].userName"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"comparison": "not_empty",
|
||||||
|
"source": "response_json",
|
||||||
|
"value": null,
|
||||||
|
"property": "Resources[0].active"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"comparison": "not_empty",
|
||||||
|
"source": "response_json",
|
||||||
|
"value": null,
|
||||||
|
"property": "Resources[0].emails[0].value"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"variables": [
|
||||||
|
{
|
||||||
|
"source": "response_json",
|
||||||
|
"name": "ISVUserid",
|
||||||
|
"property": "Resources[0].id"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"scripts": [],
|
||||||
|
"before_scripts": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"step_type": "pause",
|
||||||
|
"skipped": false,
|
||||||
|
"duration": 5
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"step_type": "request",
|
||||||
|
"skipped": false,
|
||||||
|
"note": "Required Test: Get Users/{{id}} ",
|
||||||
|
"auth": {},
|
||||||
|
"multipart_form": [],
|
||||||
|
"headers": {
|
||||||
|
"Accept-Charset": [
|
||||||
|
"utf-8"
|
||||||
|
],
|
||||||
|
"Content-Type": [
|
||||||
|
"application/scim+json; charset=utf-8"
|
||||||
|
],
|
||||||
|
"Accept": [
|
||||||
|
"application/scim+json"
|
||||||
|
],
|
||||||
|
"Authorization": [
|
||||||
|
"{{auth}}"
|
||||||
|
],
|
||||||
|
"User-Agent": [
|
||||||
|
"OKTA SCIM Integration"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"method": "GET",
|
||||||
|
"url": "{{SCIMBaseURL}}/Users/{{ISVUserid}}",
|
||||||
|
"assertions": [
|
||||||
|
{
|
||||||
|
"comparison": "equal_number",
|
||||||
|
"source": "response_status",
|
||||||
|
"value": "200"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"comparison": "not_empty",
|
||||||
|
"source": "response_json",
|
||||||
|
"value": null,
|
||||||
|
"property": "id"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"comparison": "not_empty",
|
||||||
|
"source": "response_json",
|
||||||
|
"value": null,
|
||||||
|
"property": "name.familyName"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"comparison": "not_empty",
|
||||||
|
"source": "response_json",
|
||||||
|
"value": null,
|
||||||
|
"property": "name.givenName"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"comparison": "not_empty",
|
||||||
|
"source": "response_json",
|
||||||
|
"value": null,
|
||||||
|
"property": "userName"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"comparison": "not_empty",
|
||||||
|
"source": "response_json",
|
||||||
|
"value": null,
|
||||||
|
"property": "active"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"comparison": "not_empty",
|
||||||
|
"source": "response_json",
|
||||||
|
"value": null,
|
||||||
|
"property": "emails[0].value"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"comparison": "equal",
|
||||||
|
"source": "response_json",
|
||||||
|
"value": "{{ISVUserid}}",
|
||||||
|
"property": "id"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"variables": [],
|
||||||
|
"scripts": [],
|
||||||
|
"before_scripts": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"step_type": "pause",
|
||||||
|
"skipped": false,
|
||||||
|
"duration": 5
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"step_type": "request",
|
||||||
|
"skipped": false,
|
||||||
|
"note": "Required Test: Test invalid User by username",
|
||||||
|
"auth": {},
|
||||||
|
"multipart_form": [],
|
||||||
|
"headers": {
|
||||||
|
"Accept-Charset": [
|
||||||
|
"utf-8"
|
||||||
|
],
|
||||||
|
"Content-Type": [
|
||||||
|
"application/scim+json; charset=utf-8"
|
||||||
|
],
|
||||||
|
"Accept": [
|
||||||
|
"application/scim+json"
|
||||||
|
],
|
||||||
|
"Authorization": [
|
||||||
|
"{{auth}}"
|
||||||
|
],
|
||||||
|
"User-Agent": [
|
||||||
|
"OKTA SCIM Integration"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"method": "GET",
|
||||||
|
"url": "{{SCIMBaseURL}}/Users?filter=userName eq \"{{InvalidUserEmail}}\"",
|
||||||
|
"assertions": [
|
||||||
|
{
|
||||||
|
"comparison": "equal_number",
|
||||||
|
"source": "response_status",
|
||||||
|
"value": "200"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"comparison": "has_value",
|
||||||
|
"source": "response_json",
|
||||||
|
"value": "urn:ietf:params:scim:api:messages:2.0:ListResponse",
|
||||||
|
"property": "schemas"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"comparison": "equal",
|
||||||
|
"source": "response_json",
|
||||||
|
"value": "0",
|
||||||
|
"property": "totalResults"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"variables": [],
|
||||||
|
"scripts": [],
|
||||||
|
"before_scripts": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"step_type": "pause",
|
||||||
|
"skipped": false,
|
||||||
|
"duration": 5
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"step_type": "request",
|
||||||
|
"skipped": false,
|
||||||
|
"note": "Required Test: Test invalid User by ID",
|
||||||
|
"auth": {},
|
||||||
|
"multipart_form": [],
|
||||||
|
"headers": {
|
||||||
|
"Accept-Charset": [
|
||||||
|
"utf-8"
|
||||||
|
],
|
||||||
|
"Content-Type": [
|
||||||
|
"application/scim+json; charset=utf-8"
|
||||||
|
],
|
||||||
|
"Authorization": [
|
||||||
|
"{{auth}}"
|
||||||
|
],
|
||||||
|
"Accept": [
|
||||||
|
"application/scim+json"
|
||||||
|
],
|
||||||
|
"User-Agent": [
|
||||||
|
"OKTA SCIM Integration"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"method": "GET",
|
||||||
|
"url": "{{SCIMBaseURL}}/Users/{{UserIdThatDoesNotExist}}",
|
||||||
|
"assertions": [
|
||||||
|
{
|
||||||
|
"comparison": "equal_number",
|
||||||
|
"source": "response_status",
|
||||||
|
"value": "404"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"comparison": "not_empty",
|
||||||
|
"source": "response_json",
|
||||||
|
"value": null,
|
||||||
|
"property": "detail"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"comparison": "has_value",
|
||||||
|
"source": "response_json",
|
||||||
|
"value": "urn:ietf:params:scim:api:messages:2.0:Error",
|
||||||
|
"property": "schemas"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"variables": [],
|
||||||
|
"scripts": [],
|
||||||
|
"before_scripts": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"step_type": "pause",
|
||||||
|
"skipped": false,
|
||||||
|
"duration": 5
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"step_type": "request",
|
||||||
|
"skipped": false,
|
||||||
|
"note": "Required Test: Make sure random user doesn't exist",
|
||||||
|
"auth": {},
|
||||||
|
"multipart_form": [],
|
||||||
|
"headers": {
|
||||||
|
"Accept-Charset": [
|
||||||
|
"utf-8"
|
||||||
|
],
|
||||||
|
"Content-Type": [
|
||||||
|
"application/scim+json; charset=utf-8"
|
||||||
|
],
|
||||||
|
"Authorization": [
|
||||||
|
"{{auth}}"
|
||||||
|
],
|
||||||
|
"Accept": [
|
||||||
|
"application/scim+json"
|
||||||
|
],
|
||||||
|
"User-Agent": [
|
||||||
|
"OKTA SCIM Integration"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"method": "GET",
|
||||||
|
"url": "{{SCIMBaseURL}}/Users?filter=userName eq \"{{randomEmail}}\"",
|
||||||
|
"assertions": [
|
||||||
|
{
|
||||||
|
"comparison": "equal_number",
|
||||||
|
"source": "response_status",
|
||||||
|
"value": "200"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"comparison": "equal_number",
|
||||||
|
"source": "response_json",
|
||||||
|
"value": "0",
|
||||||
|
"property": "totalResults"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"comparison": "has_value",
|
||||||
|
"source": "response_json",
|
||||||
|
"value": "urn:ietf:params:scim:api:messages:2.0:ListResponse",
|
||||||
|
"property": "schemas"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"variables": [],
|
||||||
|
"scripts": [],
|
||||||
|
"before_scripts": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"step_type": "pause",
|
||||||
|
"skipped": false,
|
||||||
|
"duration": 5
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"step_type": "request",
|
||||||
|
"skipped": false,
|
||||||
|
"note": "Required Test: Create Okta user with realistic values",
|
||||||
|
"auth": {},
|
||||||
|
"body": "{\"schemas\":[\"urn:ietf:params:scim:schemas:core:2.0:User\"],\"userName\":\"{{randomUsername}}\",\"name\":{\"givenName\":\"{{randomGivenName}}\",\"familyName\":\"{{randomFamilyName}}\"},\"emails\":[{\"primary\":true,\"value\":\"{{randomEmail}}\",\"type\":\"work\"}],\"displayName\":\"{{randomGivenName}} {{randomFamilyName}}\",\"active\":true}",
|
||||||
|
"form": {},
|
||||||
|
"multipart_form": [],
|
||||||
|
"binary_body": null,
|
||||||
|
"headers": {
|
||||||
|
"Content-Type": [
|
||||||
|
"application/json"
|
||||||
|
],
|
||||||
|
"Authorization": [
|
||||||
|
"{{auth}}"
|
||||||
|
],
|
||||||
|
"Accept": [
|
||||||
|
"application/scim+json; charset=utf-8"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"method": "POST",
|
||||||
|
"url": "{{SCIMBaseURL}}/Users",
|
||||||
|
"assertions": [
|
||||||
|
{
|
||||||
|
"comparison": "equal_number",
|
||||||
|
"source": "response_status",
|
||||||
|
"value": "201"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"comparison": "equal",
|
||||||
|
"source": "response_json",
|
||||||
|
"value": "true",
|
||||||
|
"property": "active"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"comparison": "not_empty",
|
||||||
|
"source": "response_json",
|
||||||
|
"value": null,
|
||||||
|
"property": "id"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"comparison": "equal",
|
||||||
|
"source": "response_json",
|
||||||
|
"value": "{{randomFamilyName}}",
|
||||||
|
"property": "name.familyName"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"comparison": "equal",
|
||||||
|
"source": "response_json",
|
||||||
|
"value": "{{randomGivenName}}",
|
||||||
|
"property": "name.givenName"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"comparison": "contains",
|
||||||
|
"source": "response_json",
|
||||||
|
"value": "urn:ietf:params:scim:schemas:core:2.0:User",
|
||||||
|
"property": "schemas"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"comparison": "equal",
|
||||||
|
"source": "response_json",
|
||||||
|
"value": "{{randomUsername}}",
|
||||||
|
"property": "userName"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"variables": [
|
||||||
|
{
|
||||||
|
"source": "response_json",
|
||||||
|
"name": "idUserOne",
|
||||||
|
"property": "id"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"source": "response_json",
|
||||||
|
"name": "randomUserEmail",
|
||||||
|
"property": "emails[0].value"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"scripts": [
|
||||||
|
""
|
||||||
|
],
|
||||||
|
"before_scripts": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"step_type": "pause",
|
||||||
|
"skipped": false,
|
||||||
|
"duration": 5
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"step_type": "request",
|
||||||
|
"skipped": false,
|
||||||
|
"note": "Required Test: Verify that user was created",
|
||||||
|
"auth": {},
|
||||||
|
"multipart_form": [],
|
||||||
|
"headers": {
|
||||||
|
"Accept-Charset": [
|
||||||
|
"utf-8"
|
||||||
|
],
|
||||||
|
"Content-Type": [
|
||||||
|
"application/scim+json; charset=utf-8"
|
||||||
|
],
|
||||||
|
"Authorization": [
|
||||||
|
"{{auth}}"
|
||||||
|
],
|
||||||
|
"Accept": [
|
||||||
|
"application/scim+json"
|
||||||
|
],
|
||||||
|
"User-Agent": [
|
||||||
|
"OKTA SCIM Integration"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"method": "GET",
|
||||||
|
"url": "{{SCIMBaseURL}}/Users/{{idUserOne}}",
|
||||||
|
"assertions": [
|
||||||
|
{
|
||||||
|
"comparison": "equal_number",
|
||||||
|
"source": "response_status",
|
||||||
|
"value": "200"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"comparison": "equal",
|
||||||
|
"source": "response_json",
|
||||||
|
"value": "{{randomUsername}}",
|
||||||
|
"property": "userName"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"comparison": "equal",
|
||||||
|
"source": "response_json",
|
||||||
|
"value": "{{randomFamilyName}}",
|
||||||
|
"property": "name.familyName"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"comparison": "equal",
|
||||||
|
"source": "response_json",
|
||||||
|
"value": "{{randomGivenName}}",
|
||||||
|
"property": "name.givenName"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"variables": [],
|
||||||
|
"scripts": [],
|
||||||
|
"before_scripts": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"step_type": "pause",
|
||||||
|
"skipped": false,
|
||||||
|
"duration": 10
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"step_type": "request",
|
||||||
|
"skipped": false,
|
||||||
|
"note": "Required Test: Expect failure when recreating user with same values",
|
||||||
|
"auth": {},
|
||||||
|
"body": "{\"schemas\":[\"urn:ietf:params:scim:schemas:core:2.0:User\"],\"userName\":\"{{randomUsername}}\",\"name\":{\"givenName\":\"{{randomGivenName}}\",\"familyName\":\"{{randomFamilyName}}\"},\"emails\":[{\"primary\":true,\"value\":\"{{randomUsername}}\",\"type\":\"work\"}],\"displayName\":\"{{randomGivenName}} {{randomFamilyName}}\",\"active\":true}",
|
||||||
|
"form": {},
|
||||||
|
"multipart_form": [],
|
||||||
|
"binary_body": null,
|
||||||
|
"headers": {
|
||||||
|
"Content-Type": [
|
||||||
|
"application/json"
|
||||||
|
],
|
||||||
|
"Authorization": [
|
||||||
|
"{{auth}}"
|
||||||
|
],
|
||||||
|
"Accept": [
|
||||||
|
"application/scim+json; charset=utf-8"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"method": "POST",
|
||||||
|
"url": "{{SCIMBaseURL}}/Users",
|
||||||
|
"assertions": [
|
||||||
|
{
|
||||||
|
"comparison": "equal_number",
|
||||||
|
"source": "response_status",
|
||||||
|
"value": "409"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"variables": [],
|
||||||
|
"scripts": [],
|
||||||
|
"before_scripts": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"step_type": "pause",
|
||||||
|
"skipped": false,
|
||||||
|
"duration": 5
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"step_type": "request",
|
||||||
|
"skipped": false,
|
||||||
|
"note": "Required Test: Username Case Sensitivity Check",
|
||||||
|
"auth": {},
|
||||||
|
"multipart_form": [],
|
||||||
|
"headers": {
|
||||||
|
"Accept-Charset": [
|
||||||
|
"utf-8"
|
||||||
|
],
|
||||||
|
"Content-Type": [
|
||||||
|
"application/scim+json; charset=utf-8"
|
||||||
|
],
|
||||||
|
"Authorization": [
|
||||||
|
"{{auth}}"
|
||||||
|
],
|
||||||
|
"Accept": [
|
||||||
|
"application/scim+json"
|
||||||
|
],
|
||||||
|
"User-Agent": [
|
||||||
|
"OKTA SCIM Integration"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"method": "GET",
|
||||||
|
"url": "{{SCIMBaseURL}}/Users?filter=userName eq \"{{randomUsernameCaps}}\"",
|
||||||
|
"assertions": [
|
||||||
|
{
|
||||||
|
"comparison": "equal_number",
|
||||||
|
"source": "response_status",
|
||||||
|
"value": "200"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"variables": [],
|
||||||
|
"scripts": [],
|
||||||
|
"before_scripts": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"step_type": "pause",
|
||||||
|
"skipped": false,
|
||||||
|
"duration": 5
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"step_type": "request",
|
||||||
|
"skipped": false,
|
||||||
|
"note": "Optional Test: Verify Groups endpoint",
|
||||||
|
"auth": {},
|
||||||
|
"multipart_form": [],
|
||||||
|
"headers": {
|
||||||
|
"Accept-Charset": [
|
||||||
|
"utf-8"
|
||||||
|
],
|
||||||
|
"Content-Type": [
|
||||||
|
"application/scim+json; charset=utf-8"
|
||||||
|
],
|
||||||
|
"Accept": [
|
||||||
|
"application/scim+json"
|
||||||
|
],
|
||||||
|
"Authorization": [
|
||||||
|
"{{auth}}"
|
||||||
|
],
|
||||||
|
"User-Agent": [
|
||||||
|
"OKTA SCIM Integration"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"method": "GET",
|
||||||
|
"url": "{{SCIMBaseURL}}/Groups",
|
||||||
|
"assertions": [
|
||||||
|
{
|
||||||
|
"comparison": "equal_number",
|
||||||
|
"source": "response_status",
|
||||||
|
"value": "200"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"comparison": "is_less_than",
|
||||||
|
"source": "response_time",
|
||||||
|
"value": "600"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"variables": [],
|
||||||
|
"scripts": [
|
||||||
|
"var data = JSON.parse(response.body);\nvar max = data.totalResults;\nvar res = data.Resources;\nvar exists = false;\n\nif (max === 0)\n\tassert(\"nogroups\", \"No Groups found in the endpoint\");\nelse if (max >= 1 && Array.isArray(res)) {\n exists = true;\n assert.ok(exists, \"Resources is of type Array\");\n\tlog(exists);\n}"
|
||||||
|
],
|
||||||
|
"before_scripts": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"step_type": "pause",
|
||||||
|
"skipped": false,
|
||||||
|
"duration": 5
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"step_type": "request",
|
||||||
|
"skipped": false,
|
||||||
|
"note": "Required Test: Check status 401",
|
||||||
|
"multipart_form": [],
|
||||||
|
"headers": {
|
||||||
|
"Accept": [
|
||||||
|
"application/scim+json"
|
||||||
|
],
|
||||||
|
"Accept-Charset": [
|
||||||
|
"utf-8"
|
||||||
|
],
|
||||||
|
"Authorization": [
|
||||||
|
"non-token"
|
||||||
|
],
|
||||||
|
"Content-Type": [
|
||||||
|
"application/scim+json; charset=utf-8"
|
||||||
|
],
|
||||||
|
"User-Agent": [
|
||||||
|
"OKTA SCIM Integration"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"auth": {},
|
||||||
|
"method": "GET",
|
||||||
|
"url": "{{SCIMBaseURL}}/Users?filter=userName eq \"{{randomUsernameCaps}}\"",
|
||||||
|
"assertions": [
|
||||||
|
{
|
||||||
|
"comparison": "equal_number",
|
||||||
|
"source": "response_status",
|
||||||
|
"value": "401"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"comparison": "not_empty",
|
||||||
|
"source": "response_json",
|
||||||
|
"value": null,
|
||||||
|
"property": "detail"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"comparison": "equal",
|
||||||
|
"source": "response_json",
|
||||||
|
"value": "401",
|
||||||
|
"property": "status"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"comparison": "has_value",
|
||||||
|
"source": "response_json",
|
||||||
|
"value": "urn:ietf:params:scim:api:messages:2.0:Error",
|
||||||
|
"property": "schemas"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"variables": [],
|
||||||
|
"scripts": [],
|
||||||
|
"before_scripts": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"step_type": "pause",
|
||||||
|
"skipped": false,
|
||||||
|
"duration": 5
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"step_type": "request",
|
||||||
|
"skipped": false,
|
||||||
|
"note": "Required Test: Check status 404",
|
||||||
|
"multipart_form": [],
|
||||||
|
"headers": {
|
||||||
|
"Accept": [
|
||||||
|
"application/scim+json"
|
||||||
|
],
|
||||||
|
"Accept-Charset": [
|
||||||
|
"utf-8"
|
||||||
|
],
|
||||||
|
"Authorization": [
|
||||||
|
"{{auth}}"
|
||||||
|
],
|
||||||
|
"Content-Type": [
|
||||||
|
"application/scim+json; charset=utf-8"
|
||||||
|
],
|
||||||
|
"User-Agent": [
|
||||||
|
"OKTA SCIM Integration"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"auth": {},
|
||||||
|
"method": "GET",
|
||||||
|
"url": "{{SCIMBaseURL}}/Users/00919288221112222",
|
||||||
|
"assertions": [
|
||||||
|
{
|
||||||
|
"comparison": "equal_number",
|
||||||
|
"source": "response_status",
|
||||||
|
"value": "404"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"comparison": "not_empty",
|
||||||
|
"source": "response_json",
|
||||||
|
"value": null,
|
||||||
|
"property": "detail"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"comparison": "equal",
|
||||||
|
"source": "response_json",
|
||||||
|
"value": "404",
|
||||||
|
"property": "status"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"comparison": "has_value",
|
||||||
|
"source": "response_json",
|
||||||
|
"value": "urn:ietf:params:scim:api:messages:2.0:Error",
|
||||||
|
"property": "schemas"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"variables": [],
|
||||||
|
"scripts": [],
|
||||||
|
"before_scripts": []
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
Loading…
Add table
Add a link
Reference in a new issue