Skip to content

Commit

Permalink
feat: Implement Meta and Resource classes
Browse files Browse the repository at this point in the history
And tests validation of RFC7643 user examples.
  • Loading branch information
azmeuk committed May 24, 2024
1 parent 3cd4c05 commit e007bfc
Show file tree
Hide file tree
Showing 6 changed files with 302 additions and 11 deletions.
4 changes: 4 additions & 0 deletions pydantic_scim2/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@
from .enterprise_user import Manager
from .group import Group
from .group import GroupMember
from .resource import Meta
from .resource import Resource
from .resource_type import ResourceType
from .resource_type import SchemaExtension
from .responses import ListResponse
Expand Down Expand Up @@ -66,4 +68,6 @@
"Role",
"X509Certificate",
"User",
"Resource",
"Meta",
]
12 changes: 10 additions & 2 deletions pydantic_scim2/group.py
Original file line number Diff line number Diff line change
@@ -1,14 +1,22 @@
from typing import List
from typing import Optional
from typing import Tuple

from pydantic import AnyUrl
from pydantic import BaseModel
from pydantic import ConfigDict
from pydantic import Field


class GroupMember(BaseModel):
model_config = ConfigDict(populate_by_name=True)

value: Optional[str] = None
display: Optional[str] = None
ref: Optional[AnyUrl] = Field(
None,
alias="$ref",
description="The URI of the SCIM resource representing the User's manager. REQUIRED.",
)


class Group(BaseModel):
Expand All @@ -19,4 +27,4 @@ class Group(BaseModel):
members: Optional[List[GroupMember]] = Field(
None, description="A list of members of the Group."
)
schemas: Tuple[str] = ("urn:ietf:params:scim:schemas:core:2.0:Group",)
schemas: List[str] = {"urn:ietf:params:scim:schemas:core:2.0:Group"}
122 changes: 122 additions & 0 deletions pydantic_scim2/resource.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@
from datetime import datetime
from typing import List
from typing import Optional

from pydantic import BaseModel


class Meta(BaseModel):
"""All "meta" sub-attributes are assigned by the service provider (have a
"mutability" of "readOnly"), and all of these sub-attributes have a
"returned" characteristic of "default".
This attribute SHALL be
ignored when provided by clients. "meta" contains the following
sub-attributes:
"""

resourceType: str
"""The name of the resource type of the resource.
This attribute has a mutability of "readOnly" and "caseExact" as
"true".
"""

created: datetime
"""The "DateTime" that the resource was added to the service provider.
This attribute MUST be a DateTime.
"""

lastModified: datetime
"""The most recent DateTime that the details of this resource were updated
at the service provider.
If this resource has never been modified since its initial creation,
the value MUST be the same as the value of "created".
"""

location: str
"""The URI of the resource being returned.
This value MUST be the same as the "Content-Location" HTTP response
header (see Section 3.1.4.2 of [RFC7231]).
"""

version: str
"""The version of the resource being returned.
This value must be the same as the entity-tag (ETag) HTTP response
header (see Sections 2.1 and 2.3 of [RFC7232]). This attribute has
"caseExact" as "true". Service provider support for this attribute
is optional and subject to the service provider's support for
versioning (see Section 3.14 of [RFC7644]). If a service provider
provides "version" (entity-tag) for a representation and the
generation of that entity-tag does not satisfy all of the
characteristics of a strong validator (see Section 2.1 of
[RFC7232]), then the origin server MUST mark the "version" (entity-
tag) as weak by prefixing its opaque value with "W/" (case
sensitive).
"""


class Resource(BaseModel):
schemas: List[str]
"""The "schemas" attribute is a REQUIRED attribute and is an array of
Strings containing URIs that are used to indicate the namespaces of the
SCIM schemas that define the attributes present in the current JSON
structure.
This attribute may be used by parsers to define the attributes
present in the JSON structure that is the body to an HTTP request or
response. Each String value must be a unique URI. All
representations of SCIM schemas MUST include a non-empty array with
value(s) of the URIs supported by that representation. The
"schemas" attribute for a resource MUST only contain values defined
as "schema" and "schemaExtensions" for the resource's defined
"resourceType". Duplicate values MUST NOT be included. Value order
is not specified and MUST NOT impact behavior.
"""

# Common attributes as defined by
# https://www.rfc-editor.org/rfc/rfc7643#section-3.1
id: str
"""A unique identifier for a SCIM resource as defined by the service
provider.
Each representation of the resource MUST include a non-empty "id"
value. This identifier MUST be unique across the SCIM service
provider's entire set of resources. It MUST be a stable, non-
reassignable identifier that does not change when the same resource
is returned in subsequent requests. The value of the "id" attribute
is always issued by the service provider and MUST NOT be specified
by the client. The string "bulkId" is a reserved keyword and MUST
NOT be used within any unique identifier value. The attribute
characteristics are "caseExact" as "true", a mutability of
"readOnly", and a "returned" characteristic of "always". See
Section 9 for additional considerations regarding privacy.
"""

externalId: Optional[str] = None
"""A String that is an identifier for the resource as defined by the
provisioning client.
The "externalId" may simplify identification of a resource between
the provisioning client and the service provider by allowing the
client to use a filter to locate the resource with an identifier
from the provisioning domain, obviating the need to store a local
mapping between the provisioning domain's identifier of the resource
and the identifier used by the service provider. Each resource MAY
include a non-empty "externalId" value. The value of the
"externalId" attribute is always issued by the provisioning client
and MUST NOT be specified by the service provider. The service
provider MUST always interpret the externalId as scoped to the
provisioning domain. While the server does not enforce uniqueness,
it is assumed that the value's uniqueness is controlled by the
client setting the value. See Section 9 for additional
considerations regarding privacy. This attribute has "caseExact" as
"true" and a mutability of "readWrite". This attribute is OPTIONAL.
"""

meta: Meta
"""A complex attribute containing resource metadata."""
5 changes: 2 additions & 3 deletions pydantic_scim2/responses.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@
from typing import Any
from typing import List
from typing import Optional
from typing import Tuple
from typing import Union

from pydantic import BaseModel
Expand All @@ -19,7 +18,7 @@
class SCIMError(BaseModel):
detail: str
status: int
schemas: Tuple[str] = ("urn:ietf:params:scim:api:messages:2.0:Error",)
schemas: List[str] = {"urn:ietf:params:scim:api:messages:2.0:Error"}

@classmethod
def not_found(cls, detail: str = "Not found") -> "SCIMError":
Expand Down Expand Up @@ -72,7 +71,7 @@ class ListResponse(BaseModel):
Discriminator(get_model_name),
]
]
schemas: Tuple[str] = ("urn:ietf:params:scim:api:messages:2.0:ListResponse",)
schemas: List[str] = {"urn:ietf:params:scim:api:messages:2.0:ListResponse"}

@classmethod
def for_users(
Expand Down
14 changes: 8 additions & 6 deletions pydantic_scim2/user.py
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
from enum import Enum
from typing import List
from typing import Optional
from typing import Set

from pydantic import AnyUrl
from pydantic import BaseModel
from pydantic import EmailStr
from pydantic import Field

from pydantic_scim2.group import GroupMember
from .group import GroupMember
from .resource import Resource


class Name(BaseModel):
Expand Down Expand Up @@ -163,6 +163,10 @@ class Address(BaseModel):
None,
description="A label indicating the attribute's function, e.g., 'work' or 'home'.",
)
primary: Optional[bool] = Field(
None,
description="A Boolean value indicating the 'primary' or preferred attribute value for this attribute, e.g., the preferred photo or thumbnail. The primary attribute value 'true' MUST appear no more than once.",
)


class Entitlement(BaseModel):
Expand Down Expand Up @@ -210,13 +214,11 @@ class X509Certificate(BaseModel):
)


class User(BaseModel):
class User(Resource):
userName: str = Field(
...,
description="Unique identifier for the User, typically used by the user to directly authenticate to the service provider. Each User MUST include a non-empty userName value. This identifier MUST be unique across the service provider's entire set of Users. REQUIRED.",
)
# Seems required by okta, but not in the json schema spec.
id: Optional[str] = None
name: Optional[Name] = Field(
None,
description="The components of the user's real name. Providers MAY return just the full name as a single string in the formatted sub-attribute, or they MAY return just the individual component attributes using the other sub-attributes, or they MAY return both. If both variants are returned, they SHOULD be describing the same name, with the formatted name indicating how the component attributes should be combined.",
Expand Down Expand Up @@ -292,4 +294,4 @@ class User(BaseModel):
x509Certificates: Optional[List[X509Certificate]] = Field(
None, description="A list of certificates issued to the User."
)
schemas: Set[str] = ("urn:ietf:params:scim:schemas:core:2.0:User",)
schemas: List[str] = {"urn:ietf:params:scim:schemas:core:2.0:User"}
Loading

0 comments on commit e007bfc

Please sign in to comment.