Skip to content

REST services for Resources Management

This page describes how to create a REST service so that it can be used by the Authorization Service to provide a lifecycle for a particular type of computing resources.

If you are a service manager and you would like to get a lifecycle management for the resources provided by your service (as is currently the case, for example, for computing accounts, Oracle accounts or websites) you can provide a REST interface over your service, that the Authorization Service will call to create, update and delete resources.

In particular, you will need to provide:

  • A way to retrieve the schema of the resources managed by service.
  • Methods for the actual resources management operations (create, read, update, delete).

Creating a Schema

The schema defining your computing resource can be added to the Authorization Service by posting to the https://authorization-service-api.web.cern.ch/api/v1.0/{resourceType} API endpoint.

The service will issue a GET operation to a certain path (configurable) to retrieve a description of the resources managed by your REST service and related resources you might be interested in.

For example, if your system manages objects of type Website, but you also want to have a person as owner of the website, you will have to provide a schema description of the Person type, even if your system will not actually be creating or deleting accounts or identities.

Your schema must provide a list of Types, each with a name and a set of properties, as explained below.

Type

name (string): the name of the type, e.g. "person" or "website".

properties: an array of properties.

Properties

name (string): the name of the property, e.g. "id", "login".

property_type (string): the type of the property. The possible values are:

  • string: a string value.
  • number: a numeric value (usually an integer).
  • boolean: true | false.
  • dateTime: a date time, represented with an ISO 8601 formatted string: "2009-02-15T00:00:00Z".
  • reference: the identifier of another object.

array (boolean, optional): true if the attribute is an array, false if it’s a single value (default = false).

id (boolean, optional): true if the attribute is the identifier of the object. Each type must have one and only one attribute with ID = true. This attribute will contain a GUID with the object identifier (default = false).

Example

This is an example of the json serialization of a schema:

[
  {
    "name": "person",
    "properties": [
      {
        "name": "id",
        "property_type": "String",
        "id": true
      },
      {
        "name": "name",
        "property_type": "String",
      }
    ]
  },
  {
    "name": "website",
    "properties": [
      {
        "name": "id",
        "property_type": "String",
        "id": true
      },
      {
        "name": "name",
        "property_type": "String",
      },
      {
        "name": "Owner",
        "property_type": "Reference"
      },
      {
        "name": "Aliases",
        "property_type": "String",
        "array": true
      }
    ]
  }
]

The ID attribute

Each type must define an ID attribute, that will be populated by the Authorization Service with a unique identifier (a GUID).

Your service needs to store this identifier and be able to identify the objects it manages based on that identifier.

How to validate your schema

Important: here is a checklist with the most common problems we have encountered while working with resources schemas:

  • For the ID property:
    • Each type in your schema must define a single ID property.
    • All types in your schema must define the same ID property. If type A defines an ID property named X, type B must define a property with name X as the ID property as well.
    • The type of the ID property must be "String"
  • For all properties:
    • The "property_type" must be "String", "Number", "Boolean", "DateTime", "Reference" or "Binary". All other values are invalid.
    • If a property references another object, its type must be "Reference". For example, if a Group has a property called "members", the "property_type" must be simply "Reference", and not "User". The type of the referenced object cannot be enforced in the schema.
    • If several types define a property with the same name, the definition must be identical across types. You cannot define a property with name "X" and property_type "String" in type A and then define a property with name "X" and property_type "Boolean" in type B. The same goes for the "array" value, i.e. the property must always be an array or never.

There is a REST types schema validation tool in the Cern.Mim.ManagementAgents project on Gitlab.
Please note that the tool requires PowerShell 7, and can be run with

> .\Validate-RestMimSchema.ps1 <schema url or file path>

The tool performs the checks described above, and if it finds any issue to fix in your schema it will output a warning.

Resource Management Operations

The Authorization Service will call rest methods on some base address (e.g. https://websites-management.cern.ch/api) followed by the name of the resource as defined in the schema. For example, to get the complete list of websites in the system, it will issue a GET operation at https://websites-management.cern.ch/api/website.

Operation Verb URL Status codes and return values
Get all resources of type website GET /website 200 + json serialization of envelope with result, resources, pagination and delta data.
Create a new website POST /website 201 (Created) + envelope with json serialization of created object.
Modify an existing website PUT or PATCH /website/id 200 + envelope with json serialization of created object.
Delete a website DELETE website/id 204 (No Content).

To update an existing resource, the service can either issue a PUT or a PATCH operation. This can be configured when the REST service is being setup, and cannot be changed later.

Return data is always expected to be contained in an envelope. This is for uniformity with other system parts and to provide support for pagination and delta operations in GET methods.

Write operations (create, update, delete) should be reasonably fast, because each write operation is blocking for the Authorization Service synchronization mechanism. If an operation (e.g. the creation of a resource) takes a long time, it is better to define a “status” attribute, which should be set to “pending” when the service receives a POST call, and is then updated by the system until it reaches a final “completed” state. This allows the POST call to return immediately; if later the resource ends in a “failed” state, it can simply be deleted, so that the service will re-try to create it at the next round.

Create (POST)

To create an object, the system will issue a POST with the full definition of the object in the body.

The expected response is an envelope with the created object in the data field.

Example:

Method: POST /api/website

Request body:

{
    "id" : "fa58fb40-e2c2-42db-8e76-a6aa6b1bfab5",    
    "name" : "some-website",
    "owner" : "bdc32740-1dcd-4d3a-a491-9fdc364b9e1d",
    "aliases" : [
        "a-site-about-something",
        "an-amazing-site"
    ]
}

Response body:

{
    "data" : {
        "id" : "fa58fb40-e2c2-42db-8e76-a6aa6b1bfab5",    
        "name" : "some-website",
        "owner" : "bdc32740-1dcd-4d3a-a491-9fdc364b9e1d",
        "aliases" : [
            "a-site-about-something",
            "an-amazing-site"
        ]
    }
}

Update (PUT)

To update an object through a PUT operation, the system will issue a PUT with the full definition of the object in the body.

The expected response is an envelope with the full definition of the updated object in the data field.

Example:

Method: POST /api/website/fa58fb40-e2c2-42db-8e76-a6aa6b1bfab5

Request body:

{
    "id" : "fa58fb40-e2c2-42db-8e76-a6aa6b1bfab5",    
    "name" : "the-name-was-changed",
    "owner" : "bdc32740-1dcd-4d3a-a491-9fdc364b9e1d",
    "aliases" : [
        "a-site-about-something",
        "an-amazing-site"
    ]
}

Response body:

{
    "data" : {
        "id" : "fa58fb40-e2c2-42db-8e76-a6aa6b1bfab5",    
        "name" : "the-name-was-changed",
        "owner" : "bdc32740-1dcd-4d3a-a491-9fdc364b9e1d",
        "aliases" : [
            "a-site-about-something",
            "an-amazing-site"
        ]
    }
}

Update (PATCH)

To update an object through a PATCH operation, the system will issue a PATCH with the object's changed attributes in the body.

The expected response is an envelope with the full definition of the updated object in the data field.

Example:

Method: PATCH /api/website/fa58fb40-e2c2-42db-8e76-a6aa6b1bfab5

Request body:

{
    {
        "op": "replace",
        "path": "/name",
        "value": "the-name-was-changed"
    }
}

Response body:

{
    "data" : {
        "id" : "fa58fb40-e2c2-42db-8e76-a6aa6b1bfab5",    
        "name" : "the-name-was-changed",
        "owner" : "bdc32740-1dcd-4d3a-a491-9fdc364b9e1d",
        "aliases" : [
            "a-site-about-something",
            "an-amazing-site"
        ]
    }
}

Delete (DELETE)

To delete an object, the system will issue a DELETE with no body.

Example:

Method: DELETE /api/website/fa58fb40-e2c2-42db-8e76-a6aa6b1bfab5

Read operations (GET)

After the Authorization Service has created, updated and deleted the resouces it needed to, it reads back data from the REST service, to determine if the objects are actually in the state they are expected to be.

This can be done with full import operations, where the service asks the REST API “give me all the resources as they are now”, or with delta import operations, where the REST API asks the external system “give me all the changes since a certain situation”.

Reading is the most critical operation in terms of performance for the synchronization process, and therefore it's also the one that requires more work to be supported.

The two key features that you will need to implement are pagination and support for delta operations.

Pagination

The data is always expected to be contained in an envelope, which must have this format:

{
  // Pagination information
  "pagination": {
    // Relative URL to invoke to continue the enumeration. REQUIRED.
    // The Authorization Service will keep calling GET with the 'next' URL until this field is empty.
    "next": "/api/person?limit=5&lastId=id005&nextDelta=eyJQZXJzb25zRGVsdGEiOjE1LCJXZWJzaXRlc0RlbHRhIjowfQ==",
    // Total number of objects. Optional, useful for troubleshooting.
    "total": 15,
    // Maximum number of objects returned. Optional, useful for troubleshooting.
    "limit": 5
  },
  "delta": {
    // Information that your service will need to receive to provide a delta import from the current situation.
    // If your service supports delta operations, this field is REQUIRED.
    "token": "eyJQZXJzb25zRGVsdGEiOjE1LCJXZWJzaXRlc0RlbHRhIjowfQ=="
  },
  // An array with the actual objects
  "data": [
    {
      "id": "id001",
      "name": "Amelia Gabriela"
    },
    // more objects ...
    {
      "id": "id005",
      "name": "Clotilda Karin"
    }
  ]
}

The REST API must return in the pagination:next field the relative URL to call to continue the enumeration.

Full Import

To retrieve the complete list of persons, the MA will start with this call:

GET /api/person?limit=1000

The REST API will return:

{
  "data": [ /* objects here... */ ],
  "pagination": {
    "next": "/api/person?limit=1000&lastId=id005&nextDelta=eyJQZXJzb25zRGVsdGEiOjE1LCJXZWJzaXRlc0RlbHRhIjowfQ=="
  },
  "delta": {
    "token": "eyJQZXJzb25zRGVsdGEiOjE1LCJXZWJzaXRlc0RlbHRhIjowfQ=="
  }
}

Since the pagination:next field is not empty, the Authorization Service will call:

GET /api/person?limit=5&lastId=id005&nextDelta=eyJQZXJzb25zRGVsdGEiOjE1LCJXZWJzaXRlc0RlbHRhIjowfQ==

Note that the REST API is passing back to itself the value of the delta token, so that continuing the operation can be completely stateless for the REST service.

If the REST API returns:

{
  "data": [ /* more objects here... */ ],
  "pagination": {
    "next": null
  },
  "delta": {
    "token": "eyJQZXJzb25zRGVsdGEiOjE1LCJXZWJzaXRlc0RlbHRhIjowfQ=="
  }
}

Since the pagination:next field is empty, the Authorization Service will consider that the import operation is completed.

Delta Import

Delta import operations are used by MIM to ask to the REST service what has changed since the last import operation.

If the last import operation was the full import of the previous example, the first delta import call will be

GET /api/person?limit=1000&delta=eyJQZXJzb25zRGVsdGEiOjE1LCJXZWJzaXRlc0RlbHRhIjowfQ==

The presence of the delta parameter indicates to the REST service that this is a delta operation. Therefore, the service must return to MIM all the changes since the moment indicated by the value of the delta parameter in the query string.

Let’s suppose that since the moment indicated by the delta value, there have been 3 changes in the connected system:

The person with ID id1001 was created. The person with ID id1002 was modified. The person with ID id1003 was deleted.

The REST API must return something like this:

{
  "data": [ // note that now we have operation type and object definition
    {
      "operation": "add",
      "object": {
        "ID": "id1001",
        "Name": "Keyser Söze"
      }
    },
    {
      "operation": "modify",
      "object": {
        "ID": "id1002",
        "Name": "Roger Verbal Kint"
      }
    },
    {
      "operation": "delete",
      "object": {
        "ID": "id1003" // only ID is relevant in case of delete
      }
    }
   ],
  "pagination": {
    "total": 3,
    "limit": 1000,
    "token": null
  },
  "delta": {
    // a new token that represents the data to pass back at the next delta
    // operation
    "token": "QWxsIHdvcmsgYW5kIG5vIHBsYXkgbWFrZXMgSmFjayBhIGR1bGwgYm95Lg=="
  }
}

Note that now the data array does not simply contain objects, but the operation type and the object definition. In case of added or modified object, the REST service must return the definition of the whole object in its current state, while for deleted objects only the ID is relevant.