What maps to Entitlements?

Initial requirements are to map

  1. Groups

    • The google account is added to Groups as a Member

  2. Drive. (Shared Drive)

    • The google account is added to Shared Drive as a Permission

This means we treat Group, Drive as Entitlements (for CRUD Operations)

Member and Permission as a subcategory of Entitlements which are supported through changes in Accounts.

Google Schema

Drive:

Expand here to view Drive schema
{
  "kind": "drive#drive",
  "id": string,
  "name": string,
  "themeId": string,
  "colorRgb": string,
  "backgroundImageFile": {
    "id": string,
    "xCoordinate": float,
    "yCoordinate": float,
    "width": float
  },
  "backgroundImageLink": string,
  "capabilities": {
    "canAddChildren": boolean,
    "canChangeCopyRequiresWriterPermissionRestriction": boolean,
    "canChangeDomainUsersOnlyRestriction": boolean,
    "canChangeDriveBackground": boolean,
    "canChangeDriveMembersOnlyRestriction": boolean,
    "canComment": boolean,
    "canCopy": boolean,
    "canDeleteChildren": boolean,
    "canDeleteDrive": boolean,
    "canDownload": boolean,
    "canEdit": boolean,
    "canListChildren": boolean,
    "canManageMembers": boolean,
    "canReadRevisions": boolean,
    "canRename": boolean,
    "canRenameDrive": boolean,
    "canShare": boolean,
    "canTrashChildren": boolean
  },
  "createdTime": datetime,
  "hidden": boolean,
  "restrictions": {
    "adminManagedRestrictions": boolean,
    "copyRequiresWriterPermission": boolean,
    "domainUsersOnly": boolean,
    "driveMembersOnly": boolean
  }
}
JSON

Groups:

Expand here to view Groups schema
{
  "kind": "admin#directory#group",
  "id": string,
  "email": string,
  "name": string,
  "description": string,
  "adminCreated": boolean,
  "directMembersCount": string,
  "etag": string,
  "aliases": [
    string
  ],
  "nonEditableAliases": [
    string
  ]
}
JSON

Permission:

Expand here to view Permission schema
{
  "kind": "drive#permission",
  "id": string,
  "type": string,
  "emailAddress": string,
  "domain": string,
  "role": string,
  "view": string,
  "allowFileDiscovery": boolean,
  "displayName": string,
  "photoLink": string,
  "expirationTime": datetime,
  "teamDrivePermissionDetails": [
    {
      "teamDrivePermissionType": string,
      "role": string,
      "inheritedFrom": string,
      "inherited": boolean
    }
  ],
  "permissionDetails": [
    {
      "permissionType": string,
      "role": string,
      "inheritedFrom": string,
      "inherited": boolean
    }
  ],
  "deleted": boolean
}
JSON

Member:

Expand here to view Member Schema
{
  "kind": "directory#member",
  "email": string,
  "role": string,
  "etag": string,
  "type": string,
  "status": string,
  "delivery_settings": string,
  "id": string
}
JSON

Schema Design

Kind

To identify the Kind of Entitlement, we define ‘Kind’ .

The format below is <Name>(<the corresponding google kind>, <the corresponding google children>)

Drive("drive#drive", "drive#permission"), 
Group("directory#groups", "directory#member")
JAVA

Entitlement

kind = <Kind>
id = <Kind>~<id>~<role>
displayName = <Kind>~<name>~<role>
permissionId = <permission/member id>
XQUERY

Drive

kind = Drive
id = Drive~<teamdrive id>~<role>
displayName = Drive~<drive name>~<role>
permissionId = null
XQUERY

Group

kind = Group
id = Group~<group id>~<role>
displayName = Group~<group name>~<role>
permissionId = null
XQUERY

Permission

kind = Drive
id = Drive~<teamdrive id>~<role>
displayName = Drive~<drive name>~<role>
permissionId = permissionId
XQUERY

Member

kind = Group
id = Group~<group id>~<role>
displayName = Group~<group name>~<role>
permissionId = permissionId
XQUERY

SCIM Schema

{
  "schemas" : [
    "urn:ietf:params:scim:schemas:core:2.0:Schema"
  ],
  "id" : "urn:sath:params:scim:api:google:1.0:Entitlement",
  "name" : "Entitlement",
  "description" : "To update google Entitlements like group members/ teamdrive permissions",
  "attributes" : [
    {
      "name" : "displayName",
      "multiValued" : false,
      "description" : "The displayName of the Entitlement. Format: Kind~<Entitlement Name>. When this is a permission/member, Format should include Role. Format: Kind~<Entitlement Name>~<Role>. 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" : "permissionId",
      "multiValued" : false,
      "description" : "Permission ID when an Entitlement is granted to user.  This will be used to revoke entitlements. It will be returned only during membership creation. Client should save this information and send it to Splice during revocation. This is only applicable when an account is assigned an entitlement. OPTIONAL.",
      "caseExact" : true
    },
    {
      "name" : "requestId",
      "multiValued" : false,
      "description" : "A unique ID related to user request.  This will be used to avoid duplicate resources created for same request when creating Shared Drive. It is highly recommended that this is populated during shared drive creation.  A random UUID will be created if this value is not provided. OPTIONAL.",
      "returned" : "never",
      "caseExact" : 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
    }
  ]
}
JSON

Resource Type

{
  "schemas" : [
    "urn:ietf:params:scim:schemas:core:2.0:ResourceType"
  ],
  "id" : "Entitlement",
  "name" : "Entitlement",
  "description" : "This resource creates/modifies/deletes Entitlements in Google Workspace 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:google:1.0:Entitlement"
}
JSON

Implementation

The following methods of the target system connector interface defined in the connector SPI needs to be implemented for Account and Entitlement resources

Note that although we create these endpoints for all the Entitlements, the Entitlements are added to a User in Account Resource using PATCH operation for IDHub.

Create Resource

public String postResource(String resource) {
    create access credentials to the Google API 
    create Entitlement in Google using API calls to Group/Drive
    throw appropriate exceptions if there is parsing error, conflicts or missing attributes
    return created Entitlement
}
JAVA

Update Resource

public String putResource(String id, String resource) {
	create access credentials to the Google API 
    update Entitlement in Google using API to update Group/Drive
    throw appropriate exceptions if there is parsing error, conflicts or missing attributes
    return created Entitlement
}
JAVA

Patch Resource

public String patchResource(String id, String resource){
	throw Not Implemented Exception
}
JAVA

Delete Resource

public void deleteResource(String id) {
	create access credentials to the Google API 
    delete Entitlement in Google using API to delete  Group/Drive. 
    throw appropriate exceptions if there is parsing error, conflicts or missing attributes
}
JAVA

Get Resource

public String getResourceById(String id, Set<String> attributes, Set<String> excludedAttributes) {
	create access credentials to the Google API 
    get the Entitlement from Google using API. 
    This calls internally API for Drive / Group depending on the Kind in ID to fetch the resource.
     [<Future Scope>
        If the attributes != null, include the specific attributes in the object
        If the excludedAttributes != null, exclude those attributes from the object]
    throw appropriate exceptions if any. Not found exception if the Entitlement is not found in Google.
}
JAVA

Search Resource

Current scope is to use this as a list operation ( search all ) with ONLY pagination support.

public String searchResource(String resourceName, String filters) {
	The filter parameter is ignored
		return ALL(*) the instances of the given resource types .
		Use token to paginate over the list if there are multiple pages.
	return the list of resource objects matching the filter
}
JAVA

Search will return more results in cases where the pagination falls in-between roles.

e.g. Lets say there are 2 Drives and 1 Groups. Drives have 6 roles and Groups have 3 roles

Overall, there are 15 Entitlements (2*6 + 1*3)

  • Drive1~role1, Drive1~role2, Drive1~role3, Drive1~role4, Drive1~role5, Drive1~role6

  • Drive2~role1, Drive2~role2, Drive2~role3, Drive2~role4, Drive2~role5, Drive2~role6

  • Group1~roleA, Group1~roleB, Group1~roleC

Lets say the pagination count requested is 7. Ideally we should return only 7 entitlements, but, since the Entitlement combined with Role is relevant only to IDHUB and not to Google, and the AccessToken returned by Google is for the next set of Groups, we need to return 12 Entitlements instead of 7. This needs to be handled by Connector or IDHub.

At this point, the maximum number of extra entitlements returned will by 5 since Google Drive has 6 roles.

<Future scope> - Support All the Search Operations

References

https://developers.google.com/admin-sdk/directory/reference/rest/v1/groups

https://developers.google.com/admin-sdk/directory/reference/rest/v1/members

https://developers.google.com/admin-sdk/directory/v1/list-query-operators

https://developers.google.com/admin-sdk/directory/v1/guides/manage-groups

https://developers.google.com/admin-sdk/directory/v1/guides/manage-group-members

https://developers.google.com/drive/api/v3/reference/drives

https://developers.google.com/drive/api/v3/reference/permissions

https://developers.google.com/drive/api/v3/ref-roles