IDHub Splice Implementation

Maven Parent Project

https://git.sath.com/idhub-connectors/idhub-connector-parent-project

Below is an example pom.xml inheriting from the idhub-splice-parent-project

https://www.baeldung.com/jackson, https://www.baeldung.com/jackson-object-mapper-tutorial, https://fasterxml.github.io/jackson-databind/javadoc/2.11/com/fasterxml/jackson/databind/JsonNode.html

It is intended that you use Jackson to manipulate JSON. Above are some links to help you familiarize yourself with Jackson’s use.

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <!-- We inherit from the spring-starter-parent so that
     spring is configured and available to us. -->
    <parent>
        <groupId>com.sath.idhub</groupId>
        <artifactId>idhub-splice-parent-project</artifactId>
        <version>4.0.6</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>

    <artifactId>project-name</artifactId>
    <version>1.0.0</version>
    <name>Project Name</name>
    <description>A description</description>

    <!-- Look in the sath maven repo for dependencies -->
    <repositories>
        <repository>
            <id>repo.sath.com</id>
            <name>Sath Nexus Repository</name>
            <url>https://repo.sath.com/repository/maven-releases/</url>
        </repository>
    </repositories>

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-actuator</artifactId>
        </dependency>
    </dependencies>
</project>
XML

Building the Connector Application

Inheriting from the IDHub Splice Parent Project and enabling the build-connector profile in Maven will cause Maven to assemble the following items into a fat jar during the package phase:

  • the splice that inherits from the IDHub Splice Parent Project

  • the connector application

  • all transitive dependencies

This jar file will be named with the following format:

${project.artifactId}-${project.version}-with-connector-application-${idhub.connection-application.version}.jar

Example Name

your-splice-1.0.0-with-connector-application-2.0.0.jar

Assembling the Connector

This POM also includes instructions to run a Maven Assembly that will assemble the files described below into a zip archive at the relative locations listed. This also happens on the Maven package phase.

File(s)

Output Location

Repackaged Jar

/

/lib

/lib

/conf

/conf

/src/main/resources/idhub-connector

/

application.yml

The following properties must be configured for the connector to work

Spring:
  security:
    oauth2:
      resourceserver:
        jwt:
          jwk-set-uri: https://URL
idhub:
  hostname: https://URL
  realm: Realm-Name
  clientId: Client-Id
  accessToken: Token
  refreshToken: Token
YAML

Where

  • jwk-set-uri is the uri of the JWK set endpoint
    https://environment.iamsath.com/auth/realms/IDHub/protocol/openid-connect/certs

  • hostname is the host name of the authorization server

  • realm is the name of the realm the connector belongs to

  • client-id is the client-id to be used for authentication

  • accessToken is the access token

  • refreshToken is the refresh token

Port

Additionally, the port can be specified, the default is 8080

server:
   port: 8080 
YAML

IDHub Connector SPI (Service Provider Interface)

The IDHub Connector SPI is a library project providing the interfaces, data classes, and exceptions necessary to implement a Splice for the IDHub Connector Application.

https://git.sath.com/idhub-connectors/connector-spi-java

Common Connector Service API

The Common Connector service interface provides a single class that returns the ApplicationInformation data type, which describes information common to all the SCIM Resource Services provided by the Splice to the IDHub Connector Application.

/**
 * The interface for providing information about a common connector service
 */
public interface CommonConnectorService {
    /**
     * This method returns the SCIM Application Information in SCIM+JSON format
     * @return The SCIM Application Information in SCIM+JSON format.
     */
    ApplicationInformation getApplicationInformation();
}
JAVA

SCIM Resource Service

The SCIM Resource Service interface provides methods that will be used by the IDHub Connector Application to implement each required SCIM Endpoint.

see: ScimResourceService.java for more information. The javadocs available in this file will be downloaded by maven and made available to you through your IDE.

If a name is provided fro the resource via @Service(“resource name”) it shall match the SCIM Resource Information resourceName provided by ScimResourceService.getScimResourceServiceInformation()

API

package com.sath.idhub.connector.interfaces;

import com.sath.idhub.connector.datatypes.AsynchronousSearchRequest;
import com.sath.idhub.connector.datatypes.CallbackRegistration;
import com.sath.idhub.connector.datatypes.IdhubObject;
import com.sath.idhub.connector.datatypes.ResourceServiceInformation;
import com.sath.idhub.connector.exceptions.scim.*;
import com.sath.idhub.connector.datatypes.scim.*;
import org.springframework.boot.actuate.health.Health;
import org.springframework.lang.NonNull;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Service;

import java.time.format.DateTimeFormatter;
import java.util.List;
import java.util.Set;
import java.util.concurrent.Future;

/**
 * This interface provides for the necessary functions of a SCIM Resource Service.
 */
@SuppressWarnings({"RedundantThrows", "unused"})
@Service
public interface ScimResourceService
{
    DateTimeFormatter dateTimeFormat = DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ss.SSSXX");

	Schema getSchema()
		throws InternalServerErrorException, NotImplementedException;

	ResourceType getResourceType()
		throws InternalServerErrorException, NotImplementedException;

	ResourceServiceInformation getScimResourceServiceInformation()
		throws InternalServerErrorException, NotImplementedException;

	Health getHealth()
		throws InternalServerErrorException, NotImplementedException;

	IdhubObject postResource(@NonNull String resource)
		throws BadRequestException, ConflictException, InternalServerErrorException, NotImplementedException;

	IdhubObject getResource(
		@NonNull String id, @NonNull Set<String> attributes,
		@NonNull Set<String> excludedAttributes
	)
		throws NotFoundException, InternalServerErrorException, NotImplementedException;

	IdhubObject putResource(@NonNull String id, @NonNull String resource)
		throws BadRequestException, ConflictException, NotFoundException, InternalServerErrorException,
		NotImplementedException;

	IdhubObject patchResource(@NonNull String id, @NonNull PatchOp patchOperations)
		throws BadRequestException, ConflictException, NotFoundException, InternalServerErrorException,
		NotImplementedException;

	void deleteResource(@NonNull String id)
		throws ConflictException, NotFoundException, InternalServerErrorException,
		NotImplementedException;

	ListResponse searchResource(@NonNull SearchRequest searchRequest)
		throws BadRequestException, PayloadTooLargeException, InternalServerErrorException, NotImplementedException;

	@Async
	Future<ListResponse> asyncSearchResource(@NonNull AsynchronousSearchRequest asynchronousSearchRequest)
		throws BadRequestException, PayloadTooLargeException, InternalServerErrorException, NotImplementedException;

	CallbackRegistration registerCallback(@NonNull CallbackRegistration callbackRegistration)
		throws BadRequestException, ConflictException, InternalServerErrorException, NotImplementedException;

	void unregisterCallback(@NonNull CallbackRegistration callbackRegistration)
		throws BadRequestException, ConflictException, NotFoundException, InternalServerErrorException,
		NotImplementedException;

	ListResponse getRegisteredCallbacks()
		throws InternalServerErrorException, NotImplementedException;
}
JAVA

SCIM Object

public class ScimObject
{
	/**
	 * The SCIM schemas attribute
	 */
	public final List<String> schemas;

	/**
	 * The SCIM id of the object
	 */
	public final String id;

	/**
	 * The external id of the object, most commonly the id in the target system
	 */
	public final String externalId;

	/**
	 * The meta data required by SCIM
	 */
	public final Meta meta;
}
JAVA

IDHub Object

Your datatypes are required to extend IdhubObject

public class IdhubObject extends ScimObject
{
	private final int hashCode;

	/**
	 * The unique name of the object in IDHub, should be identical to the SCIM id.
	 */
	public final String name;

	/**
	 * The display name to be shown in IDHub
	 */
	public final String displayName;

	/**
	 * The description to be shown in IDHub
	 */
	public final String description;
}
JAVA

Schema getSchema()

To implement fill out and return a Schema object.

Implements GET /scim/v2/Schemas/{id} where {id} is the service name

ResourceType getResourceType()

To implement fill out and return a ResourceType object.

Partially Implements /scim/v2/ResourceTypes
The connector application will aggregate the results of calls to this function on to each service.

ResourceServiceInformation getScimResourceServiceInformation()

To implement fill out and return a ResourceServiceInformation object.

Returns a SCIM Resource Service Information in SCIM+JSON format.
See: urn:sath:params:scim:api:core:1.0:ScimResourceServiceInformation

Health getHealth()

This method implements a spring actuator /health endpoint.

To implement fill out and return a Health object.

public Object postResource(String resource)

To implement follow the instructions in the javadocs.

This method shall return an Object that represents your resource that can be serialized by Jackson into SCIM+JSON that conforms to the Schema provided by the getSchema() method or a String containing SCIM+JSON conforming to the Schema.

public Object getResource(String id, Set<String> attributes, Set<String> excludedAttributes)

To implement follow the instructions in the javadocs.

This method shall return an Object that represents the Resource identified by id that can be serialized by Jackson into SCIM+JSON that conforms to the Schema provided by the getSchema() method or a String containing SCIM+JSON conforming to the Schema.

public Object putResource(String id, String resource)

To implement follow the instructions in the javadocs.

This method should get the resource identified by id and return an Object representing it that can be serialized by Jackson into SCIM+JSON that conforms to the Schema provided by the getSchema() method or a String containing SCIM+JSON conforming to the Schema.

public Object patchResource(String id, String patchOp)

To implement see the javadocs and the link below.

You may specify in the ScimResourceInformation that this operation is not supported.

public Object deleteResource(String id)

To implement follow the instructions in the javadocs.

This method should delete the resource or throw an error.

public Object searchResource(String searchRequest)

To implement you must follow the instructions on the javadoc and the standard referenced below.

Future<ListResponse> asyncSearchResource(RearchRequest searchRequest)

To implement follow the instructions in the javadocs.

See https://www.baeldung.com/spring-async for more information.

boolean registerCallback(CallbackRegistration callbackRegistration)

To implement follow the instructions in the javadocs.

The purpose of this method is to support giving event driven updates to IDHub.

boolean unregisterCallback(CallbackRegistration callbackRegistration)

To implement follow the instruction in the javadocs.

The purpose of this method is to support terminating event driven updates to IDHub.

ListResponse getRegisteredCallbacks()

To implement follow the instructions in the javadocs.

The purpose of this method is to allow the connector to query for active callbacks.


Implementing a Splice

Implementing a splice is as easy as inheriting from the idhub-splice-parent-project and providing three (3) classes. One that implements CommonConnectorService and two that implement ScimResourceService. As per the warning below, you must implement one SCIM Resource Service that exposes an Account service and one that exposes an Entitlement service

You must be connector to the lab.sath.com VPN in order to fetch the maven resources correctly. If try to download resources from sath’s Maven repo while not on the VPN, Cloudflare will inject a login or some other security measure and that will be what is stored by maven. You will then have to find and remove the corrupt files from your local maven repo (~/.m2/com/sath/idhub/) and download them again.

External Configuration File

It is mandatory to externalize any and all properties such that any application specific configuration can be modified without needing to recompile code. All the provided data types can serialize and de-serialize to/from a file.

It is mandatory to externalize any and all properties such that any application specific configuration can be modified without needing to recompile code.

DateTime Format

Resources that use the datetime type should use this java Simple Date Format yyyy-MM-dd'T'HH:mm:ss.SSSXXX”. This is provided as dateTimeformat in the ScimResourceService interface.

Use of the date time format above is required.


Common

This schema is a reconstruction of the common attributes used by SCIM. All the schema you define essentially inherit from this schema. Therefore every schema you define will have these attributes.

Many of these attributes are mandatory. You must return the required attributes in your data types / SCIM+JSON.

urn:ietf:params:scim:schemas:core:2.0:Common
{
  "schemas" : [ "urn:ietf:params:scim:schemas:core:2.0:Schema" ],
  "id" : "urn:ietf:params:scim:schemas:core:2.0:Common",
  "name" : "CommonAttributes",
  "description" : "Common Attributes",
  "attributes" : [
    {
      "name" : "schemas",
      "multiValued" : true,
      "description" : "The URIs of the supported schemas",
      "required" : true,
      "caseExact" : true,
      "mutability" : "readOnly",
      "returned" : "always"
    },
    {
      "name" : "id",
      "type" : "string",
      "multiValued" : false,
      "description" : "A unique identifier for a SCIM resource as defined by the service provider.",
      "required" : true,
      "caseExact" : true,
      "mutability" : "readOnly",
      "returned" : "always",
      "uniqueness" : "server"
    },
    {
        "name" : "externalId",
        "multiValued" : false,
        "description" : "A String that is an identifier for the resource as defined by the provisioning client.",
        "caseExact" : true
    },
    {
        "name" : "meta",
        "type" : "complex",
        "multiValued" : false,
        "description" : "A complex attribute containing resource metadata.",
        "required" : true,
        "caseExact" : true,
        "mutability" : "readOnly",
        "subAttributes" : [
            {
                "name" : "resourceType",
                "multiValued" : false,
                "description" : "A String that is an identifier for the resource as defined by the provisioning client.",
                "required" : true,
                "caseExact" : true,
                "mutability" : "readOnly"
            },
            {
                "name" : "created",
                "type" : "DateTime",
                "multiValued" : false,
                "description" : "The \"DateTime\" that the resource was added to the service provider.",
                "required" : true,
                "caseExact" : true,
                "mutability" : "readOnly"
            },
            {
                "name" : "lastModified",
                "type" : "DateTime",
                "multiValued" : false,
                "description" : "The most recent DateTime that the details of this resource were updated at the service provider.",
                "required" : true,
                "caseExact" : true,
                "mutability" : "readOnly"
            },
            {
                "name" : "location",
                "multiValued" : false,
                "description" : "The URI of the resource being returned.",
                "required" : true,
                "caseExact" : true,
                "mutability" : "readOnly"
            },
            {
                "name" : "version",
                "multiValued" : false,
                "description" : "The version of the resource being returned.  This value must be the same as the entity-tag (ETag) HTTP response header.",
                "caseExact" : true,
                "mutability" : "readOnly"
            }
        ]
    }
  ]
}
CODE

IDHub ConnectorApplication Implementation

IDHub Provided Schema

Application Information

urn:sath:params:scim:api:core:1.0:ApplicationInformation
{
  "schemas" : [
    "urn:ietf:params:scim:schemas:core:2.0:Schema"
  ],
  "id" : "urn:sath:params:scim:api:core:1.0:ApplicationInformation",
  "name" : "ApplicationInformation",
  "description" : "ApplicationInformation",
  "attributes" : [
    {
      "name" : "applicationName",
      "multiValued" : false,
      "description" : "The name of the application.  REQUIRED.",
      "caseExact" : true,
      "required" : true
    },
    {
      "name" : "attributeMappings",
      "multiValued" : false,
      "type" : "complex",
      "description" : "The attribute mappings to be used by IDHub to create the associated account form.  OPTIONAL.",
      "subAttributes" : [
        {
          "name" : "applicationFieldName",
          "multiValued" : false,
          "description" : "The name of the field in the application.  REQUIRED.",
          "caseExact" : true,
          "required" : true
        },
        {
          "name" : "ideFieldName",
          "multiValued" : false,
          "description" : "The name of the field in the IDE.  OPTIONAL."
        },
        {
          "name" : "synchronization",
          "multiValued" : false,
          "description" : "Specifies which system(s) if any should be updated for this attribute.  OPTIONAL.",
          "canonicalValues" : [
            "to idhub",
            "to application",
            "bi-directional",
            "no updates"
          ]
        },
        {
          "name" : "dataType",
          "multiValued" : false,
          "description" : "The data type of the attribute.  REQUIRED.",
          "required" : true,
          "canonicalValues" : [
            "number",
            "string",
            "bi-directional",
            "date"
          ]
        },
        {
          "name" : "required",
          "multiValued" : false,
          "type" : "boolean",
          "description" : "Whether or not the attribute is required.  OPTIONAL."
        },
        {
          "name" : "reconciliationKey",
          "multiValued" : false,
          "type" : "boolean",
          "description" : "Whether or not the attribute is a reconciliation key.  OPTIONAL."
        },
        {
          "name" : "uniqueField",
          "multiValued" : false,
          "type" : "boolean",
          "description" : "Whether or not the field is required to be unique.  OPTIONAL."
        },
        {
          "name" : "multiValue",
          "multiValued" : false,
          "type" : "boolean",
          "description" : "Whether or not the field is multi-valued.  OPTIONAL."
        }
      ]
    },
    {
      "name" : "businessOwner",
      "multiValued" : false,
      "description" : "The name of the business owner for this application.  OPTIONAL.",
      "caseExact" : true
    },
    {
      "name" : "description",
      "multiValued" : false,
      "description" : "A description of the application.  OPTIONAL.",
      "caseExact" : true
    },
    {
      "name" : "externalId",
      "multiValued" : false,
      "description" : "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.\n",
      "mutability" : "readOnly",
      "returned" : "always",
      "caseExact" : true
    },
    {
      "name" : "id",
      "multiValued" : false,
      "description" : "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.",
      "mutability" : "readOnly",
      "returned" : "always",
      "caseExact" : true,
      "required" : true
    },
    {
      "name" : "itOwner",
      "multiValued" : false,
      "description" : "The name of the IT owner for this application.  OPTIONAL.",
      "caseExact" : true
    },
    {
      "name" : "maximumNumberOfOperations",
      "multiValued" : false,
      "type" : "integer",
      "description" : "The maximum payload size in bytes.  REQUIRED.",
      "required" : true
    },
    {
      "name" : "maximumPayloadSize",
      "multiValued" : false,
      "type" : "integer",
      "description" : "The maximum payload size in bytes.  REQUIRED.",
      "required" : true
    },
    {
      "name" : "meta",
      "multiValued" : false,
      "description" : "A complex attribute containing resource metadata.",
      "mutability" : "readOnly",
      "subAttributes" : [
        {
          "name" : "resourceType",
          "multiValued" : false,
          "description" : "The name of the resource type of the resource.",
          "mutability" : "readOnly",
          "caseExact" : true
        },
        {
          "name" : "created",
          "multiValued" : false,
          "type" : "dateTime",
          "description" : "The \"DateTime\" that the resource was added to the service provider."
        },
        {
          "name" : "lastModified",
          "multiValued" : false,
          "type" : "dateTime",
          "description" : "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\"."
        },
        {
          "name" : "location",
          "multiValued" : false,
          "description" : "The name of the resource type of the resource.",
          "mutability" : "readOnly",
          "caseExact" : true
        },
        {
          "name" : "version",
          "multiValued" : false,
          "description" : "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).",
          "mutability" : "readOnly",
          "caseExact" : true
        }
      ]
    },
    {
      "name" : "schema",
      "multiValued" : true,
      "description" : "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.",
      "mutability" : "readOnly",
      "returned" : "always",
      "caseExact" : true,
      "required" : true
    },
    {
      "name" : "sortSupported",
      "multiValued" : false,
      "type" : "boolean",
      "description" : "Whether or not this resource service supports patch operations. REQUIRED."
    }
  ]
}
JSON

SCIM Resource Service Information

urn:sath:params:scim:api:core:1.0:ScimResourceServiceInformation
{
  "schemas" : [
    "urn:ietf:params:scim:schemas:core:2.0:Schema"
  ],
  "id" : "urn:sath:params:scim:api:core:1.0:ScimResourceServiceInformation",
  "name" : "ScimResourceServiceInformation",
  "description" : "SCIM Resource Service Information",
  "attributes" : [
    {
      "name" : "description",
      "multiValued" : false,
      "description" : "The description of the resource exposed via this service.  REQUIRED.",
      "caseExact" : true,
      "required" : true
    },
    {
      "name" : "externalId",
      "multiValued" : false,
      "description" : "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.\n",
      "mutability" : "readOnly",
      "returned" : "always",
      "caseExact" : true
    },
    {
      "name" : "id",
      "multiValued" : false,
      "description" : "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.",
      "mutability" : "readOnly",
      "returned" : "always",
      "caseExact" : true,
      "required" : true
    },
    {
      "name" : "meta",
      "multiValued" : false,
      "description" : "A complex attribute containing resource metadata.",
      "mutability" : "readOnly",
      "subAttributes" : [
        {
          "name" : "resourceType",
          "multiValued" : false,
          "description" : "The name of the resource type of the resource.",
          "mutability" : "readOnly",
          "caseExact" : true
        },
        {
          "name" : "created",
          "multiValued" : false,
          "type" : "dateTime",
          "description" : "The \"DateTime\" that the resource was added to the service provider."
        },
        {
          "name" : "lastModified",
          "multiValued" : false,
          "type" : "dateTime",
          "description" : "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\"."
        },
        {
          "name" : "location",
          "multiValued" : false,
          "description" : "The name of the resource type of the resource.",
          "mutability" : "readOnly",
          "caseExact" : true
        },
        {
          "name" : "version",
          "multiValued" : false,
          "description" : "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).",
          "mutability" : "readOnly",
          "caseExact" : true
        }
      ]
    },
    {
      "name" : "patchSupported",
      "multiValued" : false,
      "type" : "boolean",
      "description" : "The name of the service account used to connect to the application.  REQUIRED.",
      "required" : true
    },
    {
      "name" : "resourceName",
      "multiValued" : false,
      "description" : "The name of the resource exposed via this service.  REQUIRED.",
      "caseExact" : true,
      "required" : true
    },
    {
      "name" : "schema",
      "multiValued" : true,
      "description" : "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.",
      "mutability" : "readOnly",
      "returned" : "always",
      "caseExact" : true,
      "required" : true
    },
    {
      "name" : "serviceAccount",
      "multiValued" : false,
      "description" : "The name of the service account used to connect to the application.  REQUIRED.",
      "caseExact" : true
    },
    {
      "name" : "sortSupported",
      "multiValued" : false,
      "type" : "boolean",
      "description" : "The name of the service account used to connect to the application.  REQUIRED.",
      "required" : true
    },
    {
      "name" : "version",
      "multiValued" : false,
      "description" : "The version of the service.  REQUIRED.",
      "caseExact" : true,
      "required" : true
    }
  ]
}
JSON

UTF-8

Everything should be encoded in UTF-8

SCIM Required Rest Endpoints

All the endpoints must return SCIM Error Messages in SCIM+JSON format.

All the SCIM endpoints needs to add meta information as appropriate. This should be done by the connector.

https://tools.ietf.org/html/rfc7643#section-3.1

GET Endpoints

/scim/v2/ServiceProviderConfig

This endpoint returns a SCIM Service Provider Config

https://tools.ietf.org/html/rfc7644#section-3.7.4

maxOperations and maxPayloadSize are mandatory!

As bulk operations are not currently supported by the interface max operations should be 1.

Example Service Provider Config SCIM
{
    "schemas": ["urn:ietf:params:scim:schemas:core:2.0:ServiceProviderConfig"],
    "documentationUri": "no documentation",
    "patch": {
      "supported":false
    },
    "bulk": {
      "supported":false,
      "maxOperations":0,
      "maxPayloadSize":0
    },
    "filter": {
      "supported":false,
      "maxResults": 0
    },
    "changePassword": {
      "supported":false
    },
    "sort": {
      "supported":false
    },
    "etag": {
      "supported":false
    },
    "authenticationSchemes": [
      {
        "name": "OAuth Bearer Token",
        "description": "Authentication scheme using the OAuth Bearer Token Standard",
        "specUri": "http://www.rfc-editor.org/info/rfc6750",
        "documentationUri": "no documentation",
        "type": "oauthbearertoken",
        "primary": true
      },
      {
        "name": "HTTP Basic",
        "description": "Authentication scheme using the HTTP Basic Standard",
        "specUri": "http://www.rfc-editor.org/info/rfc2617",
        "documentationUri": "no documentation",
        "type": "httpbasic"
       }
    ],
    "meta": {
      "location": "scim/v2/ServiceProviderConfig",
      "resourceType": "ServiceProviderConfig",
      "created": "2019-09-03T00:00:00Z",
      "lastModified": "2019-09-03T00:00:00Z",
      "version": "W\/\"3694e05e9dff594\""
    }
  }
JSON
urn:ietf:params:scim:schemas:core:2.0:ServiceProviderConfig
{
  "schemas": [
    "urn:ietf:params:scim:schemas:core:2.0:Schema"
  ],
  "id": "urn:ietf:params:scim:schemas:core:2.0:ServiceProviderConfig",
  "name": "ServiceProviderConfig",
  "description": "Service Provider Config Schema",
  "attributes": [
    {
      "name": "documentationUri",
      "multivalued": false,
      "description": "An HTTP-addressable URL pointing to the service provider's human-consumable help documentation.  OPTIONAL."
    },
    {
      "name": "patch",
      "multivalued": false,
      "type": "complex",
      "description": "A complex type that specifies PATCH configuration options. REQUIRED.  See Section 3.5.2 of [RFC7644].",
      "subAttributes": [
        {
          "name": "supported",
          "type": "boolean",
          "description": "A Boolean value specifying whether or not the operation is supported.  REQUIRED.",
          "required": true
        }
      ]
    },
    {
      "name": "bulk",
      "multivalued": false,
      "type": "complex",
      "description": "A complex type that specifies bulk configuration options.  See Section 3.7 of [RFC7644].  REQUIRED.",
      "subAttributes": [
        {
          "name": "supported",
          "multiValued": false,
          "type": "boolean",
          "description": "A Boolean value specifying whether or not the operation is supported.  REQUIRED.",
          "required": true
        },
        {
          "name": "maxOperations",
          "multiValued": false,
          "type": "integer",
          "description": "An integer value specifying the maximum number of operations.  REQUIRED.",
          "required": true
        },
        {
          "name": "maxPayloadSize",
          "multiValued": false,
          "type": "integer",
          "description": "An integer value specifying the maximum payload size in bytes.",
          "required": true
        }
      ]
    },
    {
      "name": "filter",
      "multivalued": false,
      "type": "complex",
      "description": "A complex type that specifies FILTER options.  REQUIRED.  See Section 3.4.2.2 of [RFC7644].",
      "subAttributes": [
        {
          "name": "supported",
          "multivalued": false,
          "type": "boolean",
          "description": "A Boolean value specifying whether or not the operation is supported.  REQUIRED.",
          "required": true
        },
        {
          "name": "maxResults",
          "multivalued": false,
          "type": "integer",
          "description": "An integer value specifying the maximum number of resources returned in a response.  REQUIRED.",
          "required": true
        }
      ]
    },
    {
      "name": "changePassword",
      "multivalued": false,
      "type": "complex",
      "description": "A complex type that specifies configuration options related to changing a password.  REQUIRED.",
      "subAttributes": [
        {
          "name": "supported",
          "multivalued": false,
          "type": "boolean",
          "description": "A Boolean value specifying whether or not the operation is supported.  REQUIRED.",
          "required": true
        }
      ]
    },
    {
      "name": "sort",
      "multivalued": false,
      "type": "complex",
      "description": "A complex type that specifies Sort configuration options. REQUIRED.",
      "subAttributes": [
        {
          "name": "supported",
          "multivalued": false,
          "type": "boolean",
          "description": "A Boolean value specifying whether or not the operation is supported.  REQUIRED.",
          "required": true
        }
      ]
    },
    {
      "name": "etag",
      "multivalued": false,
      "type": "complex",
      "description": "A complex type that specifies ETag configuration options. REQUIRED.",
      "subAttributes": [
        {
          "name": "supported",
          "multivalued": false,
          "type": "boolean",
          "description": "A Boolean value specifying whether or not the operation is supported.  REQUIRED.",
          "required": true
        }
      ]
    },
    {
      "name": "authenticationSchemes",
      "multivalued": false,
      "type": "complex",
      "description": "A multi-valued complex type that specifies supported authentication scheme properties.  To enable seamless discovery of configurations, the service provider SHOULD, with the appropriate security considerations, make the authenticationSchemes attribute publicly accessible without prior authentication.  REQUIRED.",
      "subAttributes": [
        {
          "name": "type",
          "multivalued": false,
          "description": "The authentication scheme.  This specification defines the values \"oauth\", \"oauth2\", \"oauthbearertoken\", \"httpbasic\", and \"httpdigest\".  REQUIRED.\n",
          "required": true
        },
        {
          "name": "name",
          "multivalued": false,
          "description": "The common authentication scheme name, e.g., HTTP Basic. REQUIRED.",
          "required": true
        },
        {
          "name": "description",
          "multivalued": false,
          "description": "A description of the authentication scheme. REQUIRED.",
          "required": true
        },
        {
          "name": "specUri",
          "multivalued": false,
          "description": "An HTTP-addressable URL pointing to the authentication scheme's specification.  OPTIONAL."
        },
        {
          "name": "documentationUri",
          "multivalued": false,
          "description": "An HTTP-addressable URL pointing to the authentication scheme's usage documentation.  OPTIONAL."
        }
      ]
    }
  ]
}
JSON

/scim/v2/ResourceTypes

Returns a SCIM+JSON ListResponse of the SCIM Resource Types supported.

This should be implemented in the connector.
For each SCIM Service it should

  1. take the name provided by the SCIM Resource Service Information and map it

    1. id

    2. name

  2. Take the same name with the letter s appended as the endpoint.

  3. Take the schema and schema extention from the schema provided by the getSchema() for that SCIM Resource Service.

  4. The meta should use scim/v2/{Resource} for location with a resourceType of ResourceType

Example ResourceTypes SCIM.
{
  "schemas": [
    "urn:ietf:params:scim:api:messages:2.0:ListResponse"
  ],
  "totalResults": 2,
  "itemsPerPage": 10,
  "startIndex": 1,
  "Resources": [
    {
      "schemas": [
        "urn:ietf:params:scim:schemas:core:2.0:ResourceType"
      ],
      "id": "User",
      "name": "User",
      "endpoint": "/Users",
      "description": "User Account",
      "schema": "urn:ietf:params:scim:schemas:core:2.0:User",
      "schemaExtensions": [
        {
          "schema": "urn:ietf:params:scim:schemas:extension:enterprise:2.0:User",
          "required": true
        }
      ],
      "meta": {
        "location": "https://example.com/v2/ResourceTypes/User",
        "resourceType": "ResourceType"
      }
    },
    {
      "schemas": [
        "urn:ietf:params:scim:schemas:core:2.0:ResourceType"
      ],
      "id": "Group",
      "name": "Group",
      "endpoint": "/Groups",
      "description": "Group",
      "schema": "urn:ietf:params:scim:schemas:core:2.0:Group",
      "meta": {
        "location": "https://example.com/v2/ResourceTypes/Group",
        "resourceType": "ResourceType"
      }
    }
  ]
}
JSON
urn:ietf:params:scim:schemas:core:2.0:ResourceType
{
  "id" : "urn:ietf:params:scim:schemas:core:2.0:ResourceType",
  "name" : "ResourceType",
  "description" : "Resource Type Schema",
  "attributes" : [
    {
      "name" : "id",
      "multiValued" : false,
      "description" : "The resource type's server unique id.  This is often the same value as the \"name\" attribute.  OPTIONAL.",
      "mutability" : "readOnly",
      "returned" : "always"
    },
    {
      "name" : "name",
      "multiValued" : false,
      "description" : "The resource type name.  When applicable, service providers MUST specify the name, e.g., \"User\" or \"Group\".  This name is referenced by the \"meta.resourceType\" attribute in all resources. REQUIRED.",
      "required" : true,
      "mutability" : "readOnly"
    },
    {
      "name" : "description",
      "multiValued" : false,
      "description" : "The resource type's human-readable description.  When applicable, service providers MUST specify the description.  OPTIONAL.",
      "mutability" : "readOnly"
    },
    {
      "name" : "endpoint",
      "multiValued" : false,
      "description" : "The resource type's HTTP-addressable endpoint relative to the Base URL of the service provider, e.g., \"Users\".  REQUIRED.",
      "required" : true,
      "mutability" : "readOnly"
    },
    {
      "name" : "schema",
      "multiValued" : false,
      "description" : "The resource type's primary/base schema URI, e.g.,\n      \"urn:ietf:params:scim:schemas:core:2.0:User\".  This MUST be equal\n      to the \"id\" attribute of the associated \"Schema\" resource.\n      REQUIRED.",
      "required" : true,
      "mutability" : "readOnly"
    },
    {
      "name" : "schemaExtensions",
      "multiValued" : false,
      "description" : "A list of URIs of the resource type's schema extensions. OPTIONAL.\n\n      schema  The URI of an extended schema, e.g., \"urn:edu:2.0:Staff\". This MUST be equal to the \"id\" attribute of a \"Schema\" resource.  REQUIRED.\n\n      required  A Boolean value that specifies whether or not the schema extension is required for the resource type.  If true, a resource of this type MUST include this schema extension and also include any attributes declared as required in this schema extension.  If false, a resource of this type MAY omit this schema extension.  REQUIRED.",
      "mutability" : "readOnly"
    }
  ]
}
JSON

/scim/v2/Schemas

This endpoint returns a JSON list of all the SCIM Schema supported by the server.

This should be implemented at the connector level using the schemas returned by ScimResourceService.getSchema() for each ScimResourceService.

/scim/v2/Schemas/{id}

Implementer returns the full schema with the ID matching {id}

This should be implemented at the connector level using the schemas returned by ScimResourceService.getSchema() for each ScimResourceService.

They should probably be cached in a map or something for easy retrieval.

/scim/v2/{Resource Name}

This endpoint returns a SCIM List Response of all the resources of type {Resource Name}

The connector should convert calls to this with parameters into a search request

/scim/v2/{Resource Name}/{id}

Get the specific resource by {id}

The connector needs to support the attribute and excludedAttribute parameters here. Attribute names should come in standard attribute notation

{urn}:{Attribute name}.{Sub-Attribute name}

see https://tools.ietf.org/html/rfc7644#section-3.10

For both attribute and excludedAttribute each, the connector should verify the urn matches the schema attribute is supported by the resource, and if so remove the urn from each attribute name and provide them as a set.

/scim/v2/{Resource Name}/Me

Required to be implemented may return a 501 (not implemented) response

Not implemented in the connector.

POST Endpoints

/scim/v2/{Resource Name}

This endpoint is creates a new resource in the connected application according to the rules outlines in the link above.

/scim/v2/{Resource Name}/.search

This endpoint is required to be queried with a SCIM Search Request and required to respond with a SCIM List Response

https://tools.ietf.org/html/rfc7644#section-3.4.2.4

Pagination is required to be supported by the SCIM Specification

This is aliased by /scim/v2/{Resource Name}?

Parameters: attributes, exludedAttributes, filter, sortBy, sortOrder, startIndex, count

needs to be implemented in the connector to take the parameters and construct a search request and send it into the search method on the SCIM Resource Service

urn:ietf:params:scim:api:messages:2.0:ListResponse
{
  "id" : "urn:ietf:params:scim:api:messages:2.0:ListResponse",
  "name" : "List Response",
  "description" : "Search Request",
  "attributes" : [
    {
      "name" : "totalResults",
      "multiValued" : false,
      "type" : "integer",
      "description" : "The total number of results returned by the list or query operation.  The value may be larger than the number of resources returned, such as when returning a single page (see Section 3.4.2.4) of results where multiple pages are available.     REQUIRED.",
      "required" : true,
      "caseExact" : true
    },
    {
      "name" : "resources",
      "multiValued" : true,
      "type" : "complex",
      "description" : "A multi-valued list of complex objects containing the requested resources.  This MAY be a subset of the full set of resources if pagination (Section 3.4.2.4) is requested.  REQUIRED if "totalResults" is non-zero.",
      "caseExact" : true
    },
    {
      "name" : "startIndex",
      "multiValued" : false,
      "type" : "integer",
      "description" : "The 1-based index of the first result in the current set of list results.  REQUIRED when partial results are returned due to pagination.",
      "caseExact" : true
    },
    {
      "name" : "itemsPerPage",
      "multiValued" : false,
      "type" : "integer",
      "description" : "An integer indicating the 1-based index of the first query result.  See Section 3.4.2.4.  OPTIONAL.",
      "caseExact" : true
    }
  ]
}
JSON
urn:ietf:params:scim:api:messages:2.0:SearchRequest
{
  "schemas" : [
    "urn:ietf:params:scim:schemas:core:2.0:Schema"
  ],
  "id" : "urn:ietf:params:scim:api:messages:2.0:SearchRequest",
  "name" : "Search Request",
  "description" : "Search Request",
  "attributes" : [
    {
      "name" : "attributes",
      "multiValued" : true,
      "description" : "A multi-valued list of strings indicating the names of resource attributes to return in the response, overriding the set of attributes that would be returned by default.  Attribute names MUST be in standard attribute notation (Section 3.10) form.  See Section 3.9 for additional retrieval query parameters.  OPTIONAL."
    },
    {
      "name" : "excludedAttributes",
      "multiValued" : true,
      "description" : "A multi-valued list of strings indicating the names of resource attributes to be removed from the default set of attributes to return.  This parameter SHALL have no effect on attributes whose schema \"returned\" setting is \"always\" (see Sections 2.2 and 7 of [RFC7643]).  Attribute names MUST be in standard attribute notation (Section 3.10) form.  See Section 3.9 for additional retrieval query parameters.  OPTIONAL."
    },
    {
      "name" : "filter",
      "multiValued" : false,
      "description" : "The filter string used to request a subset of resources.  The filter string MUST be a valid filter (Section 3.4.2.2) expression. OPTIONAL.",
      "caseExact" : true
    },
    {
      "name" : "sortBy",
      "multiValued" : false,
      "description" : "A string indicating the attribute whose value SHALL be used to order the returned responses.  The \"sortBy\" attribute MUST be in standard attribute notation (Section 3.10) form.  See Section 3.4.2.3.  OPTIONAL.",
      "caseExact" : true
    },
    {
      "name" : "sortOrder",
      "multiValued" : false,
      "description" : "A string indicating the order in which the \"sortBy\" parameter is applied.  Allowed values are \"ascending\" and \"descending\".  See Section 3.4.2.3.  OPTIONAL.",
      "caseExact" : true
    },
    {
      "name" : "startIndex",
      "multiValued" : false,
      "type" : "integer",
      "description" : "An integer indicating the 1-based index of the first query result.  See Section 3.4.2.4.  OPTIONAL.",
      "caseExact" : true
    },
    {
      "name" : "count",
      "type" : "integer",
      "multiValued" : false,
      "description" : "An integer indicating the desired maximum number of query results per page.  See Section 3.4.2.4.  OPTIONAL.",
      "caseExact" : true
    }
  ]
}
JSON

/scim/v2/Bulk

Bulk endpoint can be opted out of in service provider config

Not in the user interface provided.

The connector’s GET /scim/v2/ServiceProviderConfig endpoint should specify that bulk is not supported

/scim/v2/{Resource Name}/Me

This endpoint is required to be implemented but may return a 501 (not implemented) response.

Not implemented in the connector.

DELETE Endpoints

These endpoints are used to delete resources

/scim/v2/{Resource Name}/{id}

This endpoint deletes the resource specified by {id} from the target system

/scim/v2/{Resource Name}/Me

Required to be implemented may return a 501 (not implemented) response

Not implemented in the connector.

PUT Endpoints

Used to modify resources

/scim/v2/{Resource Name}/{id}

This endpoint modifies a specific resource by id.

/scim/v2/{Resource Name}/Me

Required to be implemented may return a 501 (not implemented) response

Not implemented in the connector.

PATCH Endpoints

Can opt out of patch endpoints in the service provider config

/scim/v2/{Resource Name}/{id}

This endpoint is used to modify a resource using the patch syntax.

/scim/v2/{Resource Name}/Me

Required to be implemented may return a 501 (not implemented) response

Not implemented in the connector.

SCIM Error Messages

urn:ietf:params:scim:api:messages:2.0:Error
{
  "id": "urn:ietf:params:scim:api:messages:2.0:Error",
  "name": "Error",
  "description": "Error",
  "attributes": [
    {
      "name": "status",
      "type": "string",
      "multiValued": false,
      "description": "The HTTP status code (see Section 6 of [RFC7231]) expressed as a JSON string.  REQUIRED.",
      "required": true
    },
    {
      "name": "scimType",
      "type": "string",
      "multiValued": false,
      "description": "A SCIM detail error keyword.  See Table 9.  OPTIONAL.",
      "required": false
    },
    {
      "name": "detail",
      "type": "string",
      "multiValued": false,
      "description": "A detailed human-readable message.  OPTIONAL.",
      "required": false
    }
  ]
}
CODE

307 (temporary redirect)

Applies to:

GET, POST, PUT, PATCH, DELETE

Recommended Response:

the same HTTP request at the location identified. The client SHOULD NOT use the location provided in the response as a permanent reference to the resource and SHOULD continue to use the original request URI [RFC7231].

308 (permanent redirect)

Applies to:

GET, POST, PUT, PATCH, DELETE

Recommended Response:

The client is directed to repeat the same HTTP request at the location identified. The client SHOULD use the location provided in the response as the permanent reference to the resource [RFC7538].

400 (bad request)

Applies to:

GET, POST, PUT, PATCH, DELETE

Recommend Response:

Request is unparsable, syntactically incorrect, or violates schema.

401 (Unauthorized)

Applies to:

GET, POST, PUT, PATCH, DELETE

Recommended Response:

Authorization failure. The authorization header is invalid or missing.

403 (Forbidden)

Applies to:

GET, POST, PUT, PATCH, DELETE

Recommended Response:

Operation is not permitted based on the supplied authorization.

404 (Not Found)

Applies to:

GET, POST, PUT, PATCH, DELETE

Recommended Response:

Specified resource (e.g., User) or endpoint does not exist.

409 (Conflict)

Applies to:

POST, PUT, PATCH, DELETE

Recommended Response:

The specified version number does not match the resource's latest version number, or a service provider refused to create a new, duplicate resource.

412 (Precondition Failed)

Applies to:

GET, POST, PUT, PATCH, DELETE

Recommended Response:

Failed to update. Resource has changed on the server.

413 (Payload Too Large)

Applies to:

POST

Recommended Response:

{"maxOperations": 1000,"maxPayloadSize": 1048576}

500 (Internal Server Error)

Applies to:

GET, POST, PUT, PATCH, DELETE

Recommended Response:

An internal error. Implementers SHOULD provide descriptive debugging advice.

501 (Not Implemented)

Applies to:

GET, POST, PUT, PATCH, DELETE

Recommended Response:

Service provider does not support the request operation, e.g., PATCH.