Overview

Entitlement resource in IDHub maps to Group and Space in Confluence.

  • Groups contain members that indicate the User Accounts that are part of the Group. After creating a Group in Confluence, the Group is updated using PATCH to add User Accounts as members.

  • Spaces contain permissions for different users/groups. The user/group having “read space” permission is able to access the space. The user/group having “administer space” permission can modify the space permissions. When creating a space if no permission is specified, then all permissions for all existing groups and admin users are created. Permissions for a group/user can be revoked/modified/added as per requirement.

Note : Only users that have “read space” permissions are considered for current scope.

Entitlement Resource Schema Configuration

After creating a directory in the Confluence Admin account for a specific domain, the provisioning APIs for the Group Resource can be called using the directoryId.

Entitlement Resource Type

The Entitlement resource type schema is as follows :

Entitlement Resource Type
{
  "schemas" : [
    "urn:ietf:params:scim:schemas:core:2.0:ResourceType"
  ],
  "id" : "Entitlement",
  "name" : "Entitlement",
  "description" : "This resource creates/modifies/deletes Entitlements in Confluence and returns your query to you in some form depending on the normal format of the endpoint (Resource or ListResponse).",
  "endpoint" : "Entitlements",
  "schema" : "urn:sath:params:scim:api:confluence:1.0:Entitlement"
}
CODE

Entitlement Schema Representation

The Entitlement schema is as follows :

Entitlement Schema
{
  "schemas" : [
    "urn:ietf:params:scim:schemas:core:2.0:Schema"
  ],
  "id" : "urn:sath:params:scim:api:confluence:1.0:Entitlement",
  "name" : "Entitlement",
  "description" : "To perform operations on confluence Entitlements like group membership/ space permissions",
  "attributes" : [
    {
      "name" : "description",
      "multiValued" : false,
      "description" : "The description of the Entitlement. If Kind = GROUP, description is This is a Confluence Group. If Kind = SPACE, description is This is a Confluence Space. OPTIONAL."
    },
    {
      "name" : "displayName",
      "multiValued" : false,
      "description" : "The displayName of the Entitlement. Format: Kind~<Entitlement Name>. REQUIRED.",
      "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" : "The complex id of the Entitlement. Format: Kind~<EntitlementID>.REQUIRED.",
      "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" : "permissions",
      "multiValued" : true,
      "description" : "The permissions for the Space Entitlement of the user. OPTIONAL",
      "subAttributes" : [
        {
          "name" : "id",
          "multiValued" : false,
          "description" : "The id of the permission. REQUIRED.",
          "required" : true
        },
        {
          "name" : "type",
          "multiValued" : false,
          "description" : "The operation type of the permission. REQUIRED.",
          "required" : true
        },
        {
          "name" : "target",
          "multiValued" : false,
          "description" : "The target type of the permission. REQUIRED.",
          "required" : true
        },
        {
          "name" : "identifierId",
          "multiValued" : false,
          "description" : "The id of the user who has the permission. REQUIRED.",
          "required" : true
        },
        {
          "name" : "identifierName",
          "multiValued" : false,
          "description" : "The display name of the user who has the permission. REQUIRED.",
          "required" : true
        }
      ]
    },
    {
      "name" : "schemas",
      "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
    }
  ]
}
CODE

Schema to create different entitlements

For creating different entitlements, the payload should be compatible with the schema representation of the different resources as in Confluence.

Note : 1. CRUD operations on entitlements are not part of current scope

2. Only global spaces are in scope

Confluence schema of Group and Space for creation

Group Schema Representation

Space Schema Representation

{
    "displayName":"<string"
}
CODE
{
  "key": "TESTSP1",
  "name": "Test Space 1"
}
CODE

Permission Schema

The following combinations of operation.key and operation.target values are valid for the operation object:

'create': 'page', 'blogpost', 'comment', 'attachment'
'read': 'space'
'delete': 'page', 'blogpost', 'comment', 'attachment'
'export': 'space'
'administer': 'space'
CODE
Schema for Space to add Permissions

Schema Representation

Sample Permission Payload

{
  "id": <integer>,
  "subject": {
    "type": "<"group"/"user">",
    "identifier": "<{groupName}/{groupId}/{userAccountId}/"anonymous">"
  },
  "operation": {
    "key": "<"administer"/"copy"/"create"/"delete"/"export"/"move"/"purge"/"purge_version"/"read"/"restore"/"update"/"use">",
    "target": "<"page"/"blogpost"/"comment"/"attachment"/"space">"
  },
  "_links": {}
}
CODE
{
  "id": 2154,
  "subject": {
    "type": "group",
    "identifier": "trusted-users-ff8121ca-af70-4fc7-870d-fb1e02934aeb"
  },
  "operation": {
    "key": "read",
    "target": "space"
  },
  "_links": {}
}
CODE

Note : Space permissions cannot be modified in free Atlassian Access

Implementation

The following methods of the target system connector interface defined in the connector SPI needs to be implemented for Entitlement(Group, Space) resource.

Get Schema

public Schema getSchema() throws InternalServerErrorException, NotImplementedException {
    create desired IDHub entitlement Schema
    handle the respective errors, if any
    return schema definition  
}
CODE

Get Resource Type

public ResourceType getResourceType() throws InternalServerErrorException, NotImplementedException {
    create desired IDHub entitlement resource type
    handle the respective errors, if any
    return resource type definition  
}
CODE

Get SCIM Resource Service Information

public ResourceServiceInformation getScimResourceServiceInformation() throws InternalServerErrorException, NotImplementedException {
    call https://api.atlassian.com/scim/directory/{directoryId}/ServiceProviderConfig with Authorization and Accept header
    if error occurs during fetching SCIM information
	    throw InternalServerErrorException
	if no logic is implemented 
	    throw NotImplementedException
    return fetched SCIM information for resource
}
CODE

Get Health

public Health getHealth()
		throws InternalServerErrorException, NotImplementedException {
	implement /health actuator to fetch the health of the target system 
	if error occurs during fetching health information
	    throw InternalServerErrorException
	if no logic is implemented 
	    throw NotImplementedException
    return fetched health information in SCIM+JSON format or String containing SCIM+JSON
}
CODE

Create Resource

public String postResource(String resource) throws BadRequestException, ConflictException, InternalServerErrorException, NotImplementedException {
    //for Group resource
    create a JSON object containing displayName as mandatory parameter
    call https://api.atlassian.com/scim/directory/{directoryId}/Groups with Authorization, Accept and Content as headers and JSON user object as the payload 
    from the api response, contruct the IDHubObject by mapping parameters with the resonse object
    //for Space resource - not in scope
    create a JSON object with spackeKey, name as mandatory parameters. If no permissions are specified, permissions for all existing groups and admin users are created.
    call https://your-domain.atlassian.net/wiki/rest/api/space with Authorization, Accept and Content as headers and JSON user object as the payload 
    from the api response, contruct the IDHubObject by mapping parameters with the resonse object
    //for adding Space permissions separately - cannot be done on free Atlassian access
    create a JSON object with id, subject object containing type as User/Group and identifier as the corresponding Id of the type, operation object containing key and target as mandatory parameters
    call https://your-domain.atlassian.net/wiki/rest/api/space/{spaceKey}/permission with Authorization, Accept and Content as headers and JSON user object as the payload
    if resource is unparsable, syntactically incorrect, or violates schema
	    throw BadRequestException
	if api or service provider refuses to create a new, duplicate resource
	    throw ConflictException
    if error occurs during resource creation
	    throw InternalServerErrorException
	if no logic is implemented 
	    throw NotImplementedException
    return the IDHubObject in string format
}
CODE

Get Resource

public String getResource(String id, Set<String> attributes, Set<String> excludedAttributes) throws NotFoundException, InternalServerErrorException, NotImplementedException {
    //for Group resource
    call https://api.atlassian.com/scim/directory/{directoryId}/Groups/{id} with Authorization and Accept as headers
    from the api response, contruct the IDHubObject by mapping parameters with the resonse object
    //for Space resource
    call https://your-domain.atlassian.net/wiki/rest/api/space/{spaceKey} with Authorization and Accept as headers
    from the api response, contruct the IDHubObject by mapping parameters with the resonse object
    if no resource exists for the given id
	    throw NotFoundException
	if error occurs during resource fetch
	    throw InternalServerErrorException
	if no logic is implemented 
	    throw NotImplementedException 
    return the IDHubObject in string format
}
CODE

Replace Resource

public String putResource(String id, String resource) throws BadRequestException, ConflictException, NotFoundException, InternalServerErrorException, NotImplementedException {
    throw NotImplementedException for both Group and Space
}
CODE

Update Resource

public String patchResource(String id, PatchOp patchOperations) throws BadRequestException, ConflictException, NotFoundException, InternalServerErrorException, NotImplementedException {
    throw NotImplementedException for both Group and Space
}
CODE

Delete Resource 

public void deleteResource(String id) throws ConflictException, NotFoundException, InternalServerErrorException, NotImplementedException {
    //for Group resource
    call https://api.atlassian.com/scim/directory/{directoryId}/Groups/{id} with Authorization as header
    //for Space resource
    throw NotImplementedException 
    if no resource exists for the given id
	    throw NotFoundException
	if error occurs during resource creation
	    throw InternalServerErrorException
	if no logic is implemented 
	    throw NotImplementedException
}
CODE

Search Resource

public ListResponse searchResource(SearchRequest searchRequest) throws BadRequestException, PayloadTooLargeException, InternalServerErrorException, NotImplementedException {
    if the filter parameter is empty
		return all(*) instances of the given resource type are returned
	//for group resource
	call https://api.atlassian.com/scim/directory/{directoryId}/Groups with Authorization and Accept as headers
	//for space resource
    call https://your-domain.atlassian.net/wiki/rest/api/search?cql={cql} with Authorization and Accept as headers where cql=type=space can be used for querying spaces 
    in case of multiple pages, pagination can be done using start and limit in query parameters
    if searchRequest is unparsable, syntactically incorrect, or violates schema
	    throw BadRequestException
    if payload size in bytes exceeds the max payload size
	    throw PayloadTooLargeException
	if error occurs during resource search
	    throw InternalServerErrorException
	if no logic is implemented 
	    throw NotImplementedException
    return the list of resource objects matching the searchRequest
}
CODE