1. Project Structure

The project contains configuration files and source code folders. 

2. Configuration (conf folder)

All configuration should be placed into conf older which contains subfolders:

  1. connection.json
  2. schemas(Folder)
  3. scimConfigs(Folder)

connection.json

connection.json will contain LDAP connection details in below format

{
"connection": {
"HOST": "10.2.9.80",
"PORT": "389",
"BIND_DN": "cn=Directory Manager",
"CREDENTIALS": "/1+Y82GktNgEePqM/gTi+nFIBTXa2sEL"
}
}

For each ldap server there will be seperate instance of the ldap so we dont need extra connection.json

schemas Folder

This folder will contain schema for the different resources used in scim gateway. The naming convention of each file should be <ResourceName>Schema.json .

A sample for AccountSchemas is given below

{
  "id": "urn:sath:params:scim:schemas:core:1.0:Account",
  "name": "Account",
  "description": "User Account",
  "attributes": [
    {
      "name": "Fname",
      "type": "string",
      "required": true
    },
    {
      "name": "Lname",
      "type": "string",
      "required": true
    },
    {
      "name": "username",
      "type": "string",
      "required": true
    },
    {
      "name": "displayName",
      "type": "string",
      "required": false
    },
    {
      "name": "emails",
      "type": "string",
      "required": true
    },
    {
      "name": "phones",
      "type": "string",
      "required": true
    }
  ],
  "meta": {
    "resourceType": "Schema",
    "location": "/v2/Schemas/urn:sath:params:scim:schemas:core:1.0:Account"
  }
}


CODE



A schema file will contain the following attributes
1. id: id of a schema. for sath defined schemas the id should be look like "urn:sath:params:scim:schemas:core:1.0:<ResourceName>".
2. name: name of the resource type for which this schema is defined.
3. description: the description of the schema.

4. attributes: the attributes of resource types the attributes is an object containing a name, type, required attribute which servers purpose of naming and define the type of each attribute and check whether the attribute is required or not.

5. meta: metadata of the schema containing resource type and location of the schema.


scimConfigs

This folder contains the configuration for each resource type(schema). The naming convention of each file should be <ResourceName>.json .

The sample file is like:

{
  "schemas": "urn:sath:params:scim:schemas:core:1.0:Account",
  "matching-attributes": [
    "uid",
    "mail"
  ],
  "attribute-map": {
    "Fname": {
      "attribute": "Fname",
      "type": ""
    },
    "Lname": {
      "attribute": "Lname",
      "type": ""
    },
    "givenName": {
      "attribute": "Fname",
      "type": ""
    },
    "sn": {
      "type": "",
      "query": ".Lname"
    },
    "displayName": {
      "attribute": "displayName"
    },
    "name": {
      "attribute": "displayName"
    },
    "id": {
      "attribute": "userName"
    },
    "userName": {
      "attribute": "userName"
    },
    "email": {
      "attribute": "email",
      "type": "",
      "query": "{mail: .emails[].value}"
    },
    "phone": {
      "attribute": "phone",
      "type": ""
    },
    "title": {
      "attribute": "title",
      "type": ""
    },
    "addresses": {
      "attribute": "addresses",
      "type": ""
    },
    "department": {
      "attribute": "department",
      "type": ""
    },
    "userType": {
      "attribute": "userType",
      "type": ""
    },
    "organization": {
      "attribute": "organization",
      "type": ""
    },
    "employeeNumber": {
      "attribute": "employeeNumber",
      "type": ""
    },
    "manager": {
      "attribute": "manager",
      "type": ""
    }
  },
  "scripts": {
  "insert": ["dn: cn=${this.uid},ou=${this.ou},dc=iamsath,dc=com\nobjectClass: top\n#person\nobjectClass: person\nsn: ${scim.name}\ncn: ${scim.Fname} ${scim.Lname}\ntelephoneNumber: ${scim.phone}\nseeAlsodescription:\n#org person\nobjectClass: organizationalPerson\ntitle: ${scim.title}\nx121Address:\nregisteredAddress:\ndestinationIndicator:\npreferredDeliveryMethod:\ntelexNumber:\nteletexTerminalIdentifier:\ninternationaliSDNNumber:\nfacsimileTelephoneNumber:\nstreet: ${scim.addresses.streetAddress}\npostOfficeBox:\npostalCode: ${scim.addresses.postalCode}\npostalAddress: ${scim.addresses.region}\nphysicalDeliveryOfficeName:\nou: People\nst: ${scim.addresses.region}\nl: ${scim.addresses.locality}\n# inet org person\nobjectClass: inetorgperson\naudio:\nbusinessCategory:\ncarLicense:\ndepartmentNumber: ${scim.department}\ndisplayName: ${scim.displayName}\nemployeeNumber: ${scim.employeeNumber}\nemployeeType: ${scim.userType}\ngivenName: ${scim.givenName}\nhomePhone: ${scim.phone}\nhomePostalAddress: ${scim.addresses.region}\ninitials:\nlabeledURI:\nmail: ${scim.email}\no: ${scim.organization}\npager:\nroomNumber:\nsecretary:\nuid: ${scim.userName}"],
  "update": ["dn: cn=${this.uid},ou=${this.ou},dc=iamsath,dc=com\nobjectClass: top\n#person\nobjectClass: person\nsn: ${scim.name}\ncn: ${scim.Fname} ${scim.Lname}\ntelephoneNumber: ${scim.phone}\nseeAlsodescription:\n#org person\nobjectClass: organizationalPerson\ntitle: ${scim.title}\nx121Address:\nregisteredAddress:\ndestinationIndicator:\npreferredDeliveryMethod:\ntelexNumber:\nteletexTerminalIdentifier:\ninternationaliSDNNumber:\nfacsimileTelephoneNumber:\nstreet: ${scim.addresses.streetAddress}\npostOfficeBox:\npostalCode: ${scim.addresses.postalCode}\npostalAddress: ${scim.addresses.region}\nphysicalDeliveryOfficeName:\nou: People\nst: ${scim.addresses.region}\nl: ${scim.addresses.locality}\n# inet org person\nobjectClass: inetorgperson\naudio:\nbusinessCategory:\ncarLicense:\ndepartmentNumber: ${scim.department}\ndisplayName: ${scim.displayName}\nemployeeNumber: ${scim.employeeNumber}\nemployeeType: ${scim.userType}\ngivenName: ${scim.givenName}\nhomePhone: ${scim.phone}\nhomePostalAddress: ${scim.addresses.region}\ninitials:\nlabeledURI:\nmail: ${scim.email}\no: ${scim.organization}\npager:\nroomNumber:\nsecretary:\nuid: ${scim.userName}"],
    "delete": [],
  "select": [],
  "mappingToExistingToResponse": [
    {
      "id": "cn=${ldif.cn}, ou=${ldif.ou}, dc=iamsath, dc=com",
      "externalId": "${ldif.dn}",
      "userName": "${ldif.uid}",
      "givenName": "${ldif.givenName}",
      "displayName": "${ldif.displayName}",
      "name": "${ldif.displayName}",
      "emails": "${ldif.mail}",
      "userType": "${ldif.employeeType}",
      "title": "${ldif.title}",
      "department": "${ldif.departmentNumber}",
      "organization": "${ldif.o}",
      "phone": "${ldif.telephoneNumber}",
      "employeeNumber": "${ldif.employeeNumber}",
      "manager": "${ldif.manager}",
      "addresses": {
        "region": "${ldif.postalAddress}",
        "locality": "${ldif.l}",
        "postalCode": "${ldif.postalCode}",
        "streetAddress": "${ldif.street}"
      }
    }

  ]
},
  "configs": {
    "OBJECT_CLASS_PRESENT": "(objectClass=person)"
  }
}

CODE


Thie file is a JSON object containing the following attributes:

schemas: schema id for which this configuration is defined. e.g for Account schema shown above the attribute value will be "urn:sath:params:scim:schemas:core:1.0:Account".

matching-attributes: the unique attribute name which can be used as the id for rest operation of scim.


attribute-map: attribute mapping for converting the scim schema which is coming as request body for each rest API to a separate conical object which can be used for target system CRUD operations. 

For each attribute, mapping can be one of the two types.

                              1.First Type 

                                                         conicalAttributeName: {

                                                                    "attribute": "scimAttributeName",

                                                           }


                             2. Second Type:

                                                        conicalAttributeName: {

                                                                    "query": "jq query to fetch data from scim json object",

                                                       }


scripts: This part will contain the target system native mapping for a different type of CRUD operations like insert update delete select.

                                            insert mapping for insertion operation. mapping should be string type formate.

                                            update mapping for the update operation. mapping should be string type formate.

                                            delete mapping for deletion operation. mapping should be string type formate.

                                            select mapping for selection operation. mapping should be string type formate.

                                            mappingToExistingToResponse contains the mapping from native to intermediate JSON as a JSON formate.

For LDAP the script format will be

 "scripts": {
  "insert": ["dn: cn=${this.uid},ou=${this.ou},dc=iamsath,dc=com\nobjectClass: top\n#person\nobjectClass: person\nsn: ${scim.name}\ncn: ${scim.Fname} ${scim.Lname}\ntelephoneNumber: ${scim.phone}\nseeAlsodescription:\n#org person\nobjectClass: organizationalPerson\ntitle: ${scim.title}\nx121Address:\nregisteredAddress:\ndestinationIndicator:\npreferredDeliveryMethod:\ntelexNumber:\nteletexTerminalIdentifier:\ninternationaliSDNNumber:\nfacsimileTelephoneNumber:\nstreet: ${scim.addresses.streetAddress}\npostOfficeBox:\npostalCode: ${scim.addresses.postalCode}\npostalAddress: ${scim.addresses.region}\nphysicalDeliveryOfficeName:\nou: People\nst: ${scim.addresses.region}\nl: ${scim.addresses.locality}\n# inet org person\nobjectClass: inetorgperson\naudio:\nbusinessCategory:\ncarLicense:\ndepartmentNumber: ${scim.department}\ndisplayName: ${scim.displayName}\nemployeeNumber: ${scim.employeeNumber}\nemployeeType: ${scim.userType}\ngivenName: ${scim.givenName}\nhomePhone: ${scim.phone}\nhomePostalAddress: ${scim.addresses.region}\ninitials:\nlabeledURI:\nmail: ${scim.email}\no: ${scim.organization}\npager:\nroomNumber:\nsecretary:\nuid: ${scim.userName}"],
  "update": ["dn: cn=${this.uid},ou=${this.ou},dc=iamsath,dc=com\nobjectClass: top\n#person\nobjectClass: person\nsn: ${scim.name}\ncn: ${scim.Fname} ${scim.Lname}\ntelephoneNumber: ${scim.phone}\nseeAlsodescription:\n#org person\nobjectClass: organizationalPerson\ntitle: ${scim.title}\nx121Address:\nregisteredAddress:\ndestinationIndicator:\npreferredDeliveryMethod:\ntelexNumber:\nteletexTerminalIdentifier:\ninternationaliSDNNumber:\nfacsimileTelephoneNumber:\nstreet: ${scim.addresses.streetAddress}\npostOfficeBox:\npostalCode: ${scim.addresses.postalCode}\npostalAddress: ${scim.addresses.region}\nphysicalDeliveryOfficeName:\nou: People\nst: ${scim.addresses.region}\nl: ${scim.addresses.locality}\n# inet org person\nobjectClass: inetorgperson\naudio:\nbusinessCategory:\ncarLicense:\ndepartmentNumber: ${scim.department}\ndisplayName: ${scim.displayName}\nemployeeNumber: ${scim.employeeNumber}\nemployeeType: ${scim.userType}\ngivenName: ${scim.givenName}\nhomePhone: ${scim.phone}\nhomePostalAddress: ${scim.addresses.region}\ninitials:\nlabeledURI:\nmail: ${scim.email}\no: ${scim.organization}\npager:\nroomNumber:\nsecretary:\nuid: ${scim.userName}"],
    "delete": [],
  "select": [],
  "mappingToExistingToResponse": [
    {
      "id": "cn=${ldif.cn}, ou=${ldif.ou}, dc=iamsath, dc=com",
      "externalId": "${ldif.dn}",
      "userName": "${ldif.uid}",
      "givenName": "${ldif.givenName}",
      "displayName": "${ldif.displayName}",
      "name": "${ldif.displayName}",
      "emails": "${ldif.mail}",
      "userType": "${ldif.employeeType}",
      "title": "${ldif.title}",
      "department": "${ldif.departmentNumber}",
      "organization": "${ldif.o}",
      "phone": "${ldif.telephoneNumber}",
      "employeeNumber": "${ldif.employeeNumber}",
      "manager": "${ldif.manager}",
      "addresses": {
        "region": "${ldif.postalAddress}",
        "locality": "${ldif.l}",
        "postalCode": "${ldif.postalCode}",
        "streetAddress": "${ldif.street}"
      }
    }

  ]
}
CODE

Note: all the above attributes are array to pass multiple mappings.

configs: this will contain configuration related to the target system.

for example for LDAP config will like:

"configs": {
"OBJECT_CLASS_PRESENT": "(objectClass=person)"
}

3. How to add a new resource.


Steps:

  1. create a schema for resource in the schemas folder in a separate JSON file. schema structure is shown above.
  2. create a config JSON for attribute mapping, Scripts and conical resource creation in scimConfigs.
  3. add a connection.json containing LDAP server details if a connection.json does not exist.

we can call all rest endpoints like

GET- /ResourceName  → This endpoint returns all the information of Resource

GET - /ResourceName/{id}  → This endpoint returns the information of  individual Resource by {id}.

POST - /ResourceName ->This post-operation should create a new Resource from the SCIM data received as the request Body.

PUT - /ResourceName/{id} → This PUT operation should modify  the information of  individual Resource by {id}.

DELETE  /ResourceName/{id} → This DELETE operation should delete the information of individual Resource by {id}.


Example of POST call for Resource Account:

URL: http://localhost:8080/scim/v2/Account   

body: the body will contain all data for the resource we need to create.

{
"schemas": "urn:sath:params:scim:schemas:core:1.0:Account",
"meta":{
"resourceType": "User",
"created":"2011-08-01T18:29:49.793Z",
"lastModified":"2011-08-01T18:29:49.793Z",
"location":"https://example.com/v2/Users/2819c223...",
"version":"W\/\"f250dd84f0671c3\""
},
"Fname":"Aman",
"Lname": "Singh",
"userName":"asingh",
"displayName":"Amar Singh",
"phone": "+1 5555551111",
"email": "amansingh@sath.com",
"title":"System Engineer",
"addresses":{
"region":"Kolkata",
"locality":"west-bengal",
"postalCode":"700064",
"countryCode":"IN",
"country":"India",
"streetAddress":"Street 31"
},
"department":"Research",
"userType":"EMP",
"organization":"Exelon",
"employeeNumber":"132356456",
"manager":"Alexa"
}


CODE

response: if resource creation is successful then we will return created schema of resource else error with error code.

{
"addresses": {
"streetAddress": "Street 31",
"postalCode": "700064",
"locality": "west-bengal",
"region": "Kolkata"
},
"manager": "",
"displayName": "Amar Singh",
"givenName": "Aman",
"externalId": "cn=Aman Singh,ou=People,dc=iamsath,dc=com",
"userName": "asingh",
"title": "System Engineer",
"employeeNumber": "132356456",
"emails": "amansingh@sath.com",
"phone": "+1 5555551111",
"meta": {
"location": "/v2/Schemas/urn:sath:params:scim:schemas:core:1.0:Account",
"resourceType": "Schema"
},
"organization": "Exelon",
"schemas": "urn:sath:params:scim:schemas:core:1.0:Account",
"name": "Amar Singh",
"id": "cn=Aman Singh, ou=People, dc=iamsath, dc=com",
"userType": "EMP",
"department": "Research"
}
CODE


Example of PATCH call for Resource Account:

URL: http://localhost:8080/scim/v2/Account/amansingh@sath.com//   URL id should contain the value for identifying the resource uniquely and the attribute of this value should be one of the matching attributes of the config file.

body: the body will contain only for those data for the resource which we need to modify.

{
 "schemas": "urn:sath:params:scim:schemas:core:1.0:Account",
  "email": "akash@sath.com",
  "title":"System Engineer",
  "department":"Service",
  "userType":"EMP",
  "organization":"Sath"
}
CODE

response: if resource modification is successful then we will return created schema of resource else error with error code.


{
    "addresses": {
        "streetAddress": "Street 31",
        "postalCode": "700064",
        "locality": "west-bengal",
        "region": "Kolkata"
    },
    "manager": "",
    "displayName": "Amar Singh",
    "givenName": "Aman",
    "externalId": "cn=Aman Singh,ou=People,dc=iamsath,dc=com",
    "userName": "asingh",
    "title": "System Engineer",
    "employeeNumber": "132356456",
    "emails": "amansingh@sath.com",
    "phone": "+1 5555551111",
    "meta": {
        "location": "/v2/Schemas/urn:sath:params:scim:schemas:core:1.0:Account",
        "resourceType": "Schema"
    },
    "organization": "Sath",
    "schemas": "urn:sath:params:scim:schemas:core:1.0:Account",
    "name": "Amar Singh",
    "id": "cn=Aman Singh, ou=People, dc=iamsath, dc=com",
    "userType": "EMP",
    "department": "Service"
}

CODE

Example of PUT call for Resource Account:

URL: http://localhost:8080/scim/v2/Account/amansingh@sath.com//   URL id should contain the value for identifying the resource uniquely and the attribute of this value should be one of the matching attributes of the config file.

body: the body will contain all data for the resource we need to replace. This body will contain the whole document of Account to update here.


 {
 "schemas": "urn:sath:params:scim:schemas:core:1.0:Account",
  "meta":{
    "resourceType": "User",
    "created":"2011-08-01T18:29:49.793Z",
    "lastModified":"2011-08-01T18:29:49.793Z",
    "location":"https://example.com/v2/Users/2819c223...",
    "version":"W\/\"f250dd84f0671c3\""
  },
  "Fname":"Aman",
  "Lname": "Singh",
  "userName":"asingh",
  "displayName":"Amar Singh",
  "phone": "+1 5555551111",
  "email": "amansingh@sath.com",
  "title":"Senior System Engineer",
  "addresses":{
  	"region":"Kolkata",
  	"locality":"west-bengal",
  	"postalCode":"700064",
  	"countryCode":"IN",
  	"country":"India",
  	"streetAddress":"Street 31"
  },
  "department":"Research",
  "userType":"EMP",
  "organization":"Exelon",
  "employeeNumber":"132356456",
  "manager":"Alexa"
}
CODE

response: if resource replacement is successful then we will return created schema of resource else error with error code.


   {
    "addresses": {
        "streetAddress": "Street 31",
        "postalCode": "700064",
        "locality": "west-bengal",
        "region": "Kolkata"
    },
    "manager": "",
    "displayName": "Amar Singh",
    "givenName": "Aman",
    "externalId": "cn=Aman Singh,ou=People,dc=iamsath,dc=com",
    "userName": "asingh",
    "title": "Senior System Engineer",
    "employeeNumber": "132356456",
    "emails": "amansingh@sath.com",
    "phone": "+1 5555551111",
    "meta": {
        "location": "/v2/Schemas/urn:sath:params:scim:schemas:core:1.0:Account",
        "resourceType": "Schema"
    },
    "organization": "Exelon",
    "schemas": "urn:sath:params:scim:schemas:core:1.0:Account",
    "name": "Amar Singh",
    "id": "cn=Aman Singh, ou=People, dc=iamsath, dc=com",
    "userType": "EMP",
    "department": "Research"
}


CODE

Example of GET call for Resource Account:

URL: http://localhost:8080/scim/v2/Account/amansingh@sath.com//   URL id should contain the value for identifying the resource uniquely and the attribute of this value should be one of the matching attributes of the config file.

response: if resource searching is successful then we will return the schema of resource else error with error code.

 {
    "addresses": {
        "streetAddress": "Street 31",
        "postalCode": "700064",
        "locality": "west-bengal",
        "region": "Kolkata"
    },
    "manager": "",
    "displayName": "Amar Singh",
    "givenName": "Aman",
    "externalId": "cn=Aman Singh,ou=People,dc=iamsath,dc=com",
    "userName": "asingh",
    "title": "Senior System Engineer",
    "employeeNumber": "132356456",
    "emails": "amansingh@sath.com",
    "phone": "+1 5555551111",
    "organization": "Exelon",
    "schemas": "urn:sath:params:scim:schemas:core:1.0:Account",
    "name": "Amar Singh",
    "id": "cn=Aman Singh, ou=People, dc=iamsath, dc=com",
    "userType": "EMP",
    "department": "Research"
}

CODE

Example of DELETE call for Resource Account:

URL: http://localhost:8080/scim/v2/Account/amansingh@sath.com//   URL id should contain the value for identifying the resource uniquely and the attribute of this value should be one of the matching attributes of the config file.

response: if resource deletion is successful then we will return the Deleted message else error with error code.

 Deleted
CODE

Example of GET ALL call for Resource Account:

URL: http://localhost:8080/scim/v2/Account//   

response: if all resource searching is successful then we will return the schema of all resources else error with error code.

 {
    "Account": [
        {
            "addresses": {
                "streetAddress": "",
                "postalCode": "",
                "locality": "",
                "region": ""
            },
            "manager": "",
            "displayName": "Jane Doe",
            "givenName": "",
            "externalId": "cn=Jane Doe,ou=People,dc=iamsath,dc=com",
            "userName": "",
            "title": "",
            "employeeNumber": "",
            "emails": "",
            "phone": "",
            "organization": "Google",
            "schemas": "urn:sath:params:scim:schemas:core:1.0:Account",
            "name": "Jane Doe",
            "id": "cn=Jane Doe, ou=, dc=iamsath, dc=com",
            "userType": "",
            "department": ""
        },
        {
            "addresses": {
                "streetAddress": "null",
                "postalCode": "700064",
                "locality": "west-bengal",
                "region": "Kolkata"
            },
            "manager": "",
            "displayName": "Aman Singh",
            "givenName": "akash",
            "externalId": "cn=akash Sharma,ou=People,dc=iamsath,dc=com",
            "userName": "akash",
            "title": "Engineer",
            "employeeNumber": "null",
            "emails": "asingh@sath.com",
            "phone": "+1 5555551111",
            "organization": "Exelon",
            "schemas": "urn:sath:params:scim:schemas:core:1.0:Account",
            "name": "Aman Singh",
            "id": "cn=akash Sharma, ou=People, dc=iamsath, dc=com",
            "userType": "EMP",
            "department": "Research"
        },
        {
            "addresses": {
                "streetAddress": "Street 31",
                "postalCode": "700064",
                "locality": "west-bengal",
                "region": "Kolkata"
            },
            "manager": "",
            "displayName": "Amar Singh",
            "givenName": "Aman",
            "externalId": "cn=Aman Singh,ou=People,dc=iamsath,dc=com",
            "userName": "asingh",
            "title": "Senior System Engineer",
            "employeeNumber": "132356456",
            "emails": "amansingh@sath.com",
            "phone": "+1 5555551111",
            "organization": "Exelon",
            "schemas": "urn:sath:params:scim:schemas:core:1.0:Account",
            "name": "Amar Singh",
            "id": "cn=Aman Singh, ou=People, dc=iamsath, dc=com",
            "userType": "EMP",
            "department": "Research"
        }
    ],
    "schemas": "urn:sath:params:scim:schemas:core:1.0:Account"
}
CODE

4. Deployment:

This project can be deployed using docker via git pipeline.