Scim Interface and resource in OM (#21512)

* SCIM interface

* removed unwanted code

* remove SCIM registration in OM

* reverted scim configuration settings, as it is not needed

* Added security context to create user

* Added scimusername in user jsons

* added externalid, scimUsername in createUser

* Added security context on create and update groups

* Added jakarta imports

* Authorization added

* Added role, policy and bot for SCIM
This commit is contained in:
Ajith Prasad 2025-06-13 13:12:41 +05:30 committed by GitHub
parent f44d81ddf2
commit 023abfda1d
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
30 changed files with 1069 additions and 6 deletions

View File

@ -32,6 +32,7 @@ import org.openmetadata.schema.api.security.AuthorizerConfiguration;
import org.openmetadata.schema.api.security.OpsConfig;
import org.openmetadata.schema.api.security.jwt.JWTTokenConfiguration;
import org.openmetadata.schema.configuration.LimitsConfiguration;
import org.openmetadata.schema.security.scim.ScimConfiguration;
import org.openmetadata.schema.security.secrets.SecretsManagerConfiguration;
import org.openmetadata.schema.service.configuration.elasticsearch.ElasticSearchConfiguration;
import org.openmetadata.service.config.OMWebConfiguration;
@ -142,6 +143,9 @@ public class OpenMetadataApplicationConfig extends Configuration {
@Valid
private ObjectStorageConfiguration objectStorage;
@JsonProperty("scimConfiguration")
private ScimConfiguration scimConfiguration;
@Override
public String toString() {
return "catalogConfig{"

View File

@ -0,0 +1,358 @@
package org.openmetadata.service.resources.scim;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.responses.ApiResponse;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.ws.rs.Consumes;
import jakarta.ws.rs.DELETE;
import jakarta.ws.rs.GET;
import jakarta.ws.rs.PATCH;
import jakarta.ws.rs.POST;
import jakarta.ws.rs.PUT;
import jakarta.ws.rs.Path;
import jakarta.ws.rs.PathParam;
import jakarta.ws.rs.Produces;
import jakarta.ws.rs.core.Context;
import jakarta.ws.rs.core.Response;
import jakarta.ws.rs.core.SecurityContext;
import jakarta.ws.rs.core.UriInfo;
import java.util.List;
import org.openmetadata.schema.EntityInterface;
import org.openmetadata.schema.api.scim.ScimGroup;
import org.openmetadata.schema.api.scim.ScimPatchOp;
import org.openmetadata.schema.api.scim.ScimUser;
import org.openmetadata.schema.type.EntityReference;
import org.openmetadata.schema.type.MetadataOperation;
import org.openmetadata.schema.type.TagLabel;
import org.openmetadata.service.scim.ScimProvisioningService;
import org.openmetadata.service.security.Authorizer;
import org.openmetadata.service.security.policyevaluator.OperationContext;
import org.openmetadata.service.security.policyevaluator.ResourceContextInterface;
@Path("/v1/scim")
@Tag(name = "SCIM", description = "SCIM 2.0 compliant user and group provisioning endpoints.")
@Produces({"application/json", "application/scim+json"})
@Consumes({"application/json", "application/scim+json"})
public class ScimResource {
private static final String SCIM_RESOURCE_NAME = "scim";
private final ScimProvisioningService provisioningService;
protected final Authorizer authorizer;
public ScimResource(ScimProvisioningService provisioningService, Authorizer authorizer) {
this.provisioningService = provisioningService;
this.authorizer = authorizer;
}
@GET
@Path("/")
@Operation(
operationId = "getServiceProviderConfig",
summary = "Get SCIM Service Provider Config",
description = "Returns the SCIM service provider configuration.",
responses =
@ApiResponse(responseCode = "200", description = "SCIM Service Provider Configuration"))
public Response getServiceProviderConfig(@Context SecurityContext securityContext) {
authorizer.authorize(
securityContext,
new OperationContext(SCIM_RESOURCE_NAME, MetadataOperation.VIEW_SCIM),
new ScimResourceContext());
return provisioningService.getServiceProviderConfig();
}
@GET
@Path("/ServiceProviderConfig")
@Operation(
operationId = "getServiceProviderConfigAlias",
summary = "Alias endpoint for SCIM Service Provider Config",
description = "Alias endpoint for service provider configuration.",
responses =
@ApiResponse(responseCode = "200", description = "SCIM Service Provider Configuration"))
public Response getServiceProviderConfigAlias(@Context SecurityContext securityContext) {
return getServiceProviderConfig(securityContext);
}
@GET
@Path("/Users")
@Operation(
operationId = "listScimUsers",
summary = "List SCIM users",
description = "Lists SCIM users based on optional filters.",
responses = @ApiResponse(responseCode = "200", description = "List of SCIM Users"))
public Response listUsers(@Context UriInfo uriInfo, @Context SecurityContext securityContext) {
authorizer.authorize(
securityContext,
new OperationContext(SCIM_RESOURCE_NAME, MetadataOperation.VIEW_SCIM),
new ScimResourceContext());
return provisioningService.listUsers(uriInfo);
}
@POST
@Path("/Users")
@Operation(
operationId = "createScimUser",
summary = "Create SCIM user",
description = "Creates a new SCIM user.",
responses = {
@ApiResponse(responseCode = "201", description = "User created"),
@ApiResponse(responseCode = "400", description = "Invalid user input")
})
public Response createUser(
ScimUser user, @Context UriInfo uriInfo, @Context SecurityContext securityContext) {
authorizer.authorize(
securityContext,
new OperationContext(SCIM_RESOURCE_NAME, MetadataOperation.CREATE_SCIM),
new ScimResourceContext());
return provisioningService.createUser(user, uriInfo, securityContext);
}
@PUT
@Path("/Users/{id}")
@Operation(
operationId = "updateScimUser",
summary = "Update SCIM user",
description = "Updates a SCIM user identified by ID.",
responses = {
@ApiResponse(responseCode = "200", description = "User updated"),
@ApiResponse(responseCode = "404", description = "User not found")
})
public Response updateUser(
@Parameter(description = "SCIM User ID") @PathParam("id") String id,
ScimUser user,
@Context UriInfo uriInfo,
@Context SecurityContext securityContext) {
authorizer.authorize(
securityContext,
new OperationContext(SCIM_RESOURCE_NAME, MetadataOperation.EDIT_SCIM),
new ScimResourceContext());
return provisioningService.updateUser(id, user, uriInfo);
}
@DELETE
@Path("/Users/{id}")
@Operation(
operationId = "deleteScimUser",
summary = "Delete SCIM user",
description = "Deletes a SCIM user identified by ID.",
responses = {
@ApiResponse(responseCode = "200", description = "User deleted"),
@ApiResponse(responseCode = "404", description = "User not found")
})
public Response deleteUser(
@Parameter(description = "SCIM User ID") @PathParam("id") String id,
@Context UriInfo uriInfo,
@Context SecurityContext securityContext) {
authorizer.authorize(
securityContext,
new OperationContext(SCIM_RESOURCE_NAME, MetadataOperation.DELETE_SCIM),
new ScimResourceContext());
return provisioningService.deleteUser(id, uriInfo, securityContext);
}
@PATCH
@Path("/Users/{id}")
@Operation(
operationId = "patchScimUser",
summary = "Patch SCIM user",
description = "Patch updates to a SCIM user identified by ID.",
responses = {
@ApiResponse(responseCode = "200", description = "User patched"),
@ApiResponse(responseCode = "404", description = "User not found")
})
public Response patchUser(
@Parameter(description = "SCIM User ID") @PathParam("id") String id,
ScimPatchOp request,
@Context UriInfo uriInfo,
@Context SecurityContext securityContext) {
authorizer.authorize(
securityContext,
new OperationContext(SCIM_RESOURCE_NAME, MetadataOperation.EDIT_SCIM),
new ScimResourceContext());
return provisioningService.patchUser(id, request, uriInfo, securityContext);
}
@GET
@Path("/Users/{id}")
@Operation(
operationId = "getScimUser",
summary = "Get SCIM user by ID",
description = "Retrieves a SCIM user identified by ID.",
responses = {
@ApiResponse(responseCode = "200", description = "User found"),
@ApiResponse(responseCode = "404", description = "User not found")
})
public Response getUser(
@Parameter(description = "SCIM User ID") @PathParam("id") String id,
@Context UriInfo uriInfo,
@Context SecurityContext securityContext) {
authorizer.authorize(
securityContext,
new OperationContext(SCIM_RESOURCE_NAME, MetadataOperation.VIEW_SCIM),
new ScimResourceContext());
return provisioningService.getUser(id, uriInfo);
}
@GET
@Path("/Groups")
@Operation(
operationId = "listScimGroups",
summary = "List SCIM groups",
description = "Lists SCIM groups based on optional filters.",
responses = @ApiResponse(responseCode = "200", description = "List of SCIM Groups"))
public Response listGroups(@Context UriInfo uriInfo, @Context SecurityContext securityContext) {
authorizer.authorize(
securityContext,
new OperationContext(SCIM_RESOURCE_NAME, MetadataOperation.VIEW_SCIM),
new ScimResourceContext());
return provisioningService.listGroups(uriInfo);
}
@POST
@Path("/Groups")
@Operation(
operationId = "createScimGroup",
summary = "Create SCIM group",
description = "Creates a new SCIM group.",
responses = {
@ApiResponse(responseCode = "201", description = "Group created"),
@ApiResponse(responseCode = "400", description = "Invalid group input")
})
public Response createGroup(
ScimGroup group, @Context UriInfo uriInfo, @Context SecurityContext securityContext) {
authorizer.authorize(
securityContext,
new OperationContext(SCIM_RESOURCE_NAME, MetadataOperation.CREATE_SCIM),
new ScimResourceContext());
return provisioningService.createGroup(group, uriInfo, securityContext);
}
@PUT
@Path("/Groups/{id}")
@Operation(
operationId = "updateScimGroup",
summary = "Update SCIM group",
description = "Updates a SCIM group identified by ID.",
responses = {
@ApiResponse(responseCode = "200", description = "Group updated"),
@ApiResponse(responseCode = "404", description = "Group not found")
})
public Response updateGroup(
@Parameter(description = "SCIM Group ID") @PathParam("id") String id,
ScimGroup group,
@Context UriInfo uriInfo,
@Context SecurityContext securityContext) {
authorizer.authorize(
securityContext,
new OperationContext(SCIM_RESOURCE_NAME, MetadataOperation.EDIT_SCIM),
new ScimResourceContext());
return provisioningService.updateGroup(id, group, uriInfo, securityContext);
}
@GET
@Path("/Groups/{id}")
@Operation(
operationId = "getScimGroup",
summary = "Get SCIM group by ID",
description = "Retrieves a SCIM group identified by ID.",
responses = {
@ApiResponse(responseCode = "200", description = "Group found"),
@ApiResponse(responseCode = "404", description = "Group not found")
})
public Response getGroup(
@Parameter(description = "SCIM Group ID") @PathParam("id") String id,
@Context UriInfo uriInfo,
@Context SecurityContext securityContext) {
authorizer.authorize(
securityContext,
new OperationContext(SCIM_RESOURCE_NAME, MetadataOperation.VIEW_SCIM),
new ScimResourceContext());
return provisioningService.getGroup(id, uriInfo);
}
@PATCH
@Path("/Groups/{id}")
@Operation(
operationId = "patchScimGroup",
summary = "Patch SCIM group",
description = "Patch updates to a SCIM group identified by ID.",
responses = {
@ApiResponse(responseCode = "200", description = "Group patched"),
@ApiResponse(responseCode = "404", description = "Group not found")
})
public Response patchGroup(
@Parameter(description = "SCIM Group ID") @PathParam("id") String id,
ScimPatchOp request,
@Context UriInfo uriInfo,
@Context SecurityContext securityContext) {
authorizer.authorize(
securityContext,
new OperationContext(SCIM_RESOURCE_NAME, MetadataOperation.EDIT_SCIM),
new ScimResourceContext());
return provisioningService.patchGroup(id, request, uriInfo, securityContext);
}
@DELETE
@Path("/Groups/{id}")
@Operation(
operationId = "deleteScimGroup",
summary = "Delete SCIM group",
description = "Deletes a SCIM group identified by ID.",
responses = {
@ApiResponse(responseCode = "200", description = "Group deleted"),
@ApiResponse(responseCode = "404", description = "Group not found")
})
public Response deleteGroup(
@Parameter(description = "SCIM Group ID") @PathParam("id") String id,
@Context UriInfo uriInfo,
@Context SecurityContext securityContext) {
authorizer.authorize(
securityContext,
new OperationContext(SCIM_RESOURCE_NAME, MetadataOperation.DELETE_SCIM),
new ScimResourceContext());
return provisioningService.deleteGroup(id, uriInfo, securityContext);
}
@GET
@Path("/Schemas")
@Operation(
operationId = "getScimSchemas",
summary = "Get SCIM schemas",
description = "Returns supported SCIM schemas.",
responses = @ApiResponse(responseCode = "200", description = "SCIM schemas"))
public Response getSchemas(@Context SecurityContext securityContext) {
authorizer.authorize(
securityContext,
new OperationContext(SCIM_RESOURCE_NAME, MetadataOperation.VIEW_SCIM),
new ScimResourceContext());
return provisioningService.getSchemas();
}
static class ScimResourceContext implements ResourceContextInterface {
@Override
public String getResource() {
return SCIM_RESOURCE_NAME;
}
@Override
public List<EntityReference> getOwners() {
return null;
}
@Override
public List<TagLabel> getTags() {
return null;
}
@Override
public EntityInterface getEntity() {
return null;
}
@Override
public EntityReference getDomain() {
return null;
}
}
}

View File

@ -0,0 +1,42 @@
package org.openmetadata.service.scim;
import jakarta.ws.rs.core.Response;
import jakarta.ws.rs.core.SecurityContext;
import jakarta.ws.rs.core.UriInfo;
import org.openmetadata.schema.api.scim.ScimGroup;
import org.openmetadata.schema.api.scim.ScimPatchOp;
import org.openmetadata.schema.api.scim.ScimUser;
public interface ScimProvisioningService {
Response listUsers(UriInfo uriInfo);
Response createUser(ScimUser user, UriInfo uriInfo, SecurityContext securityContext);
Response getUser(String id, UriInfo uriInfo);
Response patchUser(
String id, ScimPatchOp request, UriInfo uriInfo, SecurityContext securityContext);
Response updateUser(String id, ScimUser user, UriInfo uriInfo);
Response deleteUser(String id, UriInfo uriInfo, SecurityContext securityContext);
Response listGroups(UriInfo uriInfo);
Response createGroup(ScimGroup group, UriInfo uriInfo, SecurityContext securityContext);
Response updateGroup(
String id, ScimGroup group, UriInfo uriInfo, SecurityContext securityContext);
Response deleteGroup(String id, UriInfo uriInfo, SecurityContext securityContext);
Response getGroup(String id, UriInfo uriInfo);
Response patchGroup(
String id, ScimPatchOp request, UriInfo uriInfo, SecurityContext securityContext);
Response getSchemas();
Response getServiceProviderConfig();
}

View File

@ -0,0 +1,91 @@
package org.openmetadata.service.scim.impl;
import jakarta.ws.rs.core.Response;
import jakarta.ws.rs.core.SecurityContext;
import jakarta.ws.rs.core.UriInfo;
import org.openmetadata.schema.api.scim.ScimGroup;
import org.openmetadata.schema.api.scim.ScimPatchOp;
import org.openmetadata.schema.api.scim.ScimUser;
import org.openmetadata.service.scim.ScimProvisioningService;
public class DefaultScimProvisioningService implements ScimProvisioningService {
private static final String MSG = "SCIM is not implemented in OpenMetadata.";
private Response notImplemented() {
return Response.status(Response.Status.NOT_IMPLEMENTED).entity(MSG).build();
}
@Override
public Response listUsers(UriInfo uriInfo) {
return notImplemented();
}
@Override
public Response createUser(ScimUser user, UriInfo uriInfo, SecurityContext securityContext) {
return notImplemented();
}
@Override
public Response getUser(String id, UriInfo uriInfo) {
return notImplemented();
}
@Override
public Response patchUser(
String id, ScimPatchOp request, UriInfo uriInfo, SecurityContext securityContext) {
return notImplemented();
}
@Override
public Response updateUser(String id, ScimUser user, UriInfo uriInfo) {
return notImplemented();
}
@Override
public Response deleteUser(String id, UriInfo uriInfo, SecurityContext securityContext) {
return notImplemented();
}
@Override
public Response listGroups(UriInfo uriInfo) {
return notImplemented();
}
@Override
public Response createGroup(ScimGroup group, UriInfo uriInfo, SecurityContext securityContext) {
return notImplemented();
}
@Override
public Response updateGroup(
String id, ScimGroup group, UriInfo uriInfo, SecurityContext securityContext) {
return notImplemented();
}
@Override
public Response deleteGroup(String id, UriInfo uriInfo, SecurityContext securityContext) {
return notImplemented();
}
@Override
public Response getGroup(String id, UriInfo uriInfo) {
return notImplemented();
}
@Override
public Response patchGroup(
String id, ScimPatchOp request, UriInfo uriInfo, SecurityContext securityContext) {
return notImplemented();
}
@Override
public Response getSchemas() {
return notImplemented();
}
@Override
public Response getServiceProviderConfig() {
return notImplemented();
}
}

View File

@ -100,7 +100,8 @@ public final class SecurityUtil {
List<String> jwtPrincipalClaimsOrder,
Map<String, ?> claims) {
String userName;
if (!nullOrEmpty(jwtPrincipalClaimsMapping)) {
if (!nullOrEmpty(jwtPrincipalClaimsMapping) && !isBotW(claims)) {
// We have a mapping available so we will use that
String usernameClaim = jwtPrincipalClaimsMapping.get(USERNAME_CLAIM_KEY);
String userNameClaimValue = getClaimOrObject(claims.get(usernameClaim));
@ -122,7 +123,8 @@ public final class SecurityUtil {
Map<String, ?> claims,
String defaulPrincipalClaim) {
String email;
if (!nullOrEmpty(jwtPrincipalClaimsMapping)) {
if (!nullOrEmpty(jwtPrincipalClaimsMapping) && !isBotW(claims)) {
// We have a mapping available so we will use that
String emailClaim = jwtPrincipalClaimsMapping.get(EMAIL_CLAIM_KEY);
String emailClaimValue = getClaimOrObject(claims.get(emailClaim));
@ -192,7 +194,8 @@ public final class SecurityUtil {
Set<String> allowedDomains,
boolean enforcePrincipalDomain) {
String domain = StringUtils.EMPTY;
if (!nullOrEmpty(jwtPrincipalClaimsMapping)) {
if (!nullOrEmpty(jwtPrincipalClaimsMapping) && !isBotW(claims)) {
// We have a mapping available so we will use that
String emailClaim = jwtPrincipalClaimsMapping.get(EMAIL_CLAIM_KEY);
String emailClaimValue = getClaimOrObject(claims.get(emailClaim));
@ -241,4 +244,9 @@ public final class SecurityUtil {
public static boolean isBot(Map<String, Claim> claims) {
return claims.containsKey(BOT_CLAIM) && Boolean.TRUE.equals(claims.get(BOT_CLAIM).asBoolean());
}
public static boolean isBotW(Map<String, ?> claims) {
Claim isBotClaim = (Claim) claims.get("isBot");
return isBotClaim != null && Boolean.TRUE.equals(isBotClaim.asBoolean());
}
}

View File

@ -355,6 +355,8 @@ public final class UserUtil {
.withUpdatedAt(System.currentTimeMillis())
.withTeams(EntityUtil.toEntityReferences(create.getTeams(), Entity.TEAM))
.withRoles(EntityUtil.toEntityReferences(create.getRoles(), Entity.ROLE))
.withDomains(EntityUtil.getEntityReferences(Entity.DOMAIN, create.getDomains()));
.withDomains(EntityUtil.getEntityReferences(Entity.DOMAIN, create.getDomains()))
.withExternalId(create.getExternalId())
.withScimUserName(create.getScimUserName());
}
}

View File

@ -0,0 +1,11 @@
{
"name": "scim-bot",
"displayName": "SCIM Bot",
"description": "Bot with SCIM-only access",
"fullyQualifiedName": "scim-bot",
"botUser": {
"name" : "scim-bot",
"type" : "user"
},
"provider": "system"
}

View File

@ -0,0 +1,9 @@
{
"name": "scim-bot",
"roles": [
{
"name": "ScimBotRole",
"type": "role"
}
]
}

View File

@ -0,0 +1,18 @@
{
"name": "ScimBotPolicy",
"displayName": "SCIM Bot Policy",
"fullyQualifiedName": "ScimBotPolicy",
"description": "Policy to allow SCIM bot access only to SCIM APIs and block all others.",
"enabled": true,
"allowDelete": false,
"provider": "system",
"rules": [
{
"name": "Allow-Scim-Endpoints",
"description": "Allow access to SCIM endpoints",
"resources": ["scim"],
"operations": ["CreateScim", "EditScim", "ViewScim", "DeleteScim"],
"effect": "allow"
}
]
}

View File

@ -0,0 +1,13 @@
{
"name": "ScimBotRole",
"displayName": "SCIM bot role",
"description": "Role corresponding to the SCIM bot, with SCIM-only access.",
"allowDelete": false,
"provider": "system",
"policies": [
{
"type": "policy",
"name": "ScimBotPolicy"
}
]
}

View File

@ -0,0 +1,78 @@
{
"$id": "https://open-metadata.org/schema/api/scimGroup.json",
"$schema": "http://json-schema.org/draft-07/schema#",
"title": "ScimGroup",
"description": "SCIM-compliant Group object",
"type": "object",
"javaType": "org.openmetadata.schema.api.scim.ScimGroup",
"properties": {
"schemas": {
"type": "array",
"description": "SCIM schemas used for this resource",
"items": {
"type": "string"
},
"default": ["urn:ietf:params:scim:schemas:core:2.0:Group"]
},
"id": {
"type": "string",
"description": "Unique identifier for the group"
},
"displayName": {
"type": "string",
"description": "Human-readable name of the group"
},
"externalId": {
"type": "string",
"description": "External system identifier"
},
"active": {
"type": "boolean",
"description": "Whether the group is active"
},
"members": {
"type": "array",
"description": "Members of the group",
"items": {
"type": "object",
"properties": {
"value": {
"type": "string",
"description": "ID of the member (user)"
},
"display": {
"type": "string",
"description": "Display name of the member"
},
"type": {
"type": "string",
"description": "Type of member - typically 'User'"
}
},
"required": ["value"]
}
},
"meta": {
"type": "object",
"description": "Metadata about the group",
"properties": {
"resourceType": {
"type": "string"
},
"created": {
"type": "string",
"format": "date-time"
},
"lastModified": {
"type": "string",
"format": "date-time"
},
"location": {
"type": "string"
}
}
}
},
"required": ["schemas", "displayName"],
"additionalProperties": true
}

View File

@ -0,0 +1,39 @@
{
"$id": "https://open-metadata.org/schema/api/scimPatchOp.json",
"$schema": "http://json-schema.org/draft-07/schema#",
"title": "ScimPatchOp",
"description": "SCIM PatchOp request as per RFC 7644",
"type": "object",
"javaType": "org.openmetadata.schema.api.scim.ScimPatchOp",
"properties": {
"schemas": {
"type": "array",
"items": {
"type": "string"
},
"default": ["urn:ietf:params:scim:api:messages:2.0:PatchOp"]
},
"Operations": {
"type": "array",
"items": {
"type": "object",
"properties": {
"op": {
"type": "string",
"enum": ["add", "replace", "remove"],
"javaType": "java.lang.String"
},
"path": {
"type": "string"
},
"value": {
"type": ["object", "array", "string", "boolean", "number"]
}
},
"required": ["op"]
}
}
},
"required": ["schemas", "Operations"],
"additionalProperties": false
}

View File

@ -0,0 +1,92 @@
{
"$id": "https://open-metadata.org/schema/api/scimUser.json",
"$schema": "http://json-schema.org/draft-07/schema#",
"title": "ScimUser",
"description": "SCIM-compliant User object",
"type": "object",
"javaType": "org.openmetadata.schema.api.scim.ScimUser",
"properties": {
"schemas": {
"type": "array",
"items": {
"type": "string"
}
},
"id": { "type": "string" },
"externalId": { "type": "string" },
"userName": { "type": "string" },
"displayName": { "type": "string" },
"active": { "type": "boolean", "default": true },
"title": { "type": "string" },
"preferredLanguage": { "type": "string" },
"emails": {
"type": "array",
"items": {
"type": "object",
"properties": {
"value": { "type": "string", "format": "email" },
"type": { "type": "string" },
"primary": { "type": "boolean" }
}
}
},
"phoneNumbers": {
"type": "array",
"items": {
"type": "object",
"properties": {
"value": { "type": "string" },
"type": { "type": "string" }
}
}
},
"addresses": {
"type": "array",
"items": {
"type": "object",
"properties": {
"type": { "type": "string" },
"formatted": { "type": "string" },
"streetAddress": { "type": "string" },
"locality": { "type": "string" },
"region": { "type": "string" },
"postalCode": { "type": "string" },
"country": { "type": "string" }
}
}
},
"name": {
"type": "object",
"properties": {
"givenName": { "type": "string" },
"familyName": { "type": "string" },
"formatted": { "type": "string" }
}
},
"meta": {
"type": "object",
"properties": {
"resourceType": { "type": "string" },
"created": { "type": "string", "format": "date-time" },
"lastModified": { "type": "string", "format": "date-time" },
"location": { "type": "string" }
},
"additionalProperties": true
},
"urn:ietf:params:scim:schemas:extension:enterprise:2.0:User": {
"type": "object",
"properties": {
"employeeId": { "type": "string" },
"department": { "type": "string" },
"manager": {
"type": "object",
"properties": {
"value": { "type": "string" },
"displayName": { "type": "string" }
}
}
}
}
},
"additionalProperties": true
}

View File

@ -23,6 +23,10 @@
"description": "Optional name used for display purposes. Example 'Marketing Team'.",
"type": "string"
},
"externalId": {
"description": "External identifier for the team from an external identity provider (e.g., Azure AD group ID).",
"type": "string"
},
"description": {
"description": "Optional description of the team.",
"$ref": "../../type/basic.json#/definitions/markdown"

View File

@ -18,6 +18,14 @@
"description": "Name used for display purposes. Example 'FirstName LastName'",
"type": "string"
},
"externalId": {
"description": "External identifier from identity provider (used for SCIM).",
"type": "string"
},
"scimUserName": {
"description": "Raw user name from SCIM.",
"type": "string"
},
"email": {
"$ref": "../../type/basic.json#/definitions/email"
},

View File

@ -53,7 +53,11 @@
"Deploy",
"Trigger",
"Kill",
"GenerateToken"
"GenerateToken",
"EditScim",
"CreateScim",
"DeleteScim",
"ViewScim"
]
}
},

View File

@ -44,6 +44,10 @@
"description": "Name used for display purposes. Example 'Data Science team'.",
"type": "string"
},
"externalId": {
"description": "External identifier for the team from an external identity provider (e.g., Azure AD group ID).",
"type": "string"
},
"description": {
"description": "Description of the team.",
"$ref": "../../type/basic.json#/definitions/markdown"

View File

@ -49,6 +49,14 @@
"description": "Used for user biography.",
"$ref": "../../type/basic.json#/definitions/markdown"
},
"externalId": {
"description": "External identifier from identity provider (used for SCIM).",
"type": "string"
},
"scimUserName": {
"description": "Raw user name from SCIM.",
"type": "string"
},
"displayName": {
"description": "Name used for display purposes. Example 'FirstName LastName'.",
"type": "string"

View File

@ -0,0 +1,23 @@
{
"$id": "https://open-metadata.org/schema/security/scim/scimConfiguration.json",
"$schema": "http://json-schema.org/draft-07/schema#",
"title": "SCIM Configuration",
"description": "SCIM configuration for automatic provisioning through identity providers like Azure AD or Okta.",
"type": "object",
"javaType": "org.openmetadata.schema.security.scim.ScimConfiguration",
"properties": {
"enabled": {
"title": "Enabled",
"description": "Whether SCIM provisioning is enabled.",
"type": "boolean",
"default": false
},
"identityProvider": {
"title": "Identity Provider",
"description": "The name of the identity provider for SCIM (e.g., azure, okta).",
"type": "string",
"default": "azure"
}
},
"additionalProperties": false
}

View File

@ -34,7 +34,8 @@
"searchSettings",
"assetCertificationSettings",
"lineageSettings",
"workflowSettings"
"workflowSettings",
"scimConfiguration"
]
}
},

View File

@ -0,0 +1,73 @@
/*
* Copyright 2025 Collate.
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* http://www.apache.org/licenses/LICENSE-2.0
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/**
* SCIM-compliant Group object
*/
export interface ScimGroup {
/**
* Whether the group is active
*/
active?: boolean;
/**
* Human-readable name of the group
*/
displayName: string;
/**
* External system identifier
*/
externalId?: string;
/**
* Unique identifier for the group
*/
id?: string;
/**
* Members of the group
*/
members?: Member[];
/**
* Metadata about the group
*/
meta?: Meta;
/**
* SCIM schemas used for this resource
*/
schemas: string[];
[property: string]: any;
}
export interface Member {
/**
* Display name of the member
*/
display?: string;
/**
* Type of member - typically 'User'
*/
type?: string;
/**
* ID of the member (user)
*/
value: string;
[property: string]: any;
}
/**
* Metadata about the group
*/
export interface Meta {
created?: Date;
lastModified?: Date;
location?: string;
resourceType?: string;
[property: string]: any;
}

View File

@ -0,0 +1,32 @@
/*
* Copyright 2025 Collate.
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* http://www.apache.org/licenses/LICENSE-2.0
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/**
* SCIM PatchOp request as per RFC 7644
*/
export interface ScimPatchOp {
Operations: Operation[];
schemas: string[];
}
export interface Operation {
op: Op;
path?: string;
value?: any[] | boolean | number | { [key: string]: any } | string;
[property: string]: any;
}
export enum Op {
Add = "add",
Remove = "remove",
Replace = "replace",
}

View File

@ -0,0 +1,84 @@
/*
* Copyright 2025 Collate.
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* http://www.apache.org/licenses/LICENSE-2.0
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/**
* SCIM-compliant User object
*/
export interface ScimUser {
active?: boolean;
addresses?: Address[];
displayName?: string;
emails?: Email[];
externalId?: string;
id?: string;
meta?: Meta;
name?: Name;
phoneNumbers?: PhoneNumber[];
preferredLanguage?: string;
schemas?: string[];
title?: string;
"urn:ietf:params:scim:schemas:extension:enterprise:2.0:User"?: UrnIETFParamsScimSchemasExtensionEnterprise20User;
userName?: string;
[property: string]: any;
}
export interface Address {
country?: string;
formatted?: string;
locality?: string;
postalCode?: string;
region?: string;
streetAddress?: string;
type?: string;
[property: string]: any;
}
export interface Email {
primary?: boolean;
type?: string;
value?: string;
[property: string]: any;
}
export interface Meta {
created?: Date;
lastModified?: Date;
location?: string;
resourceType?: string;
[property: string]: any;
}
export interface Name {
familyName?: string;
formatted?: string;
givenName?: string;
[property: string]: any;
}
export interface PhoneNumber {
type?: string;
value?: string;
[property: string]: any;
}
export interface UrnIETFParamsScimSchemasExtensionEnterprise20User {
department?: string;
employeeId?: string;
manager?: Manager;
[property: string]: any;
}
export interface Manager {
displayName?: string;
value?: string;
[property: string]: any;
}

View File

@ -41,6 +41,11 @@ export interface CreateTeam {
* Email address of the team.
*/
email?: string;
/**
* External identifier for the team from an external identity provider (e.g., Azure AD group
* ID).
*/
externalId?: string;
/**
* Can any user join this team during sign up? Value of true indicates yes, and false no.
*/

View File

@ -47,6 +47,10 @@ export interface CreateUser {
*/
domains?: string[];
email: string;
/**
* External identifier from identity provider (used for SCIM).
*/
externalId?: string;
/**
* When true indicates user is an administrator for the system with superuser privileges
*/
@ -72,6 +76,10 @@ export interface CreateUser {
* Roles that the user has been assigned
*/
roles?: string[];
/**
* Raw user name from SCIM.
*/
scimUserName?: string;
/**
* Teams that the user belongs to
*/

View File

@ -32,7 +32,9 @@ export enum Operation {
All = "All",
Create = "Create",
CreateIngestionPipelineAutomator = "CreateIngestionPipelineAutomator",
CreateScim = "CreateScim",
Delete = "Delete",
DeleteScim = "DeleteScim",
DeleteTestCaseFailedRowsSample = "DeleteTestCaseFailedRowsSample",
Deploy = "Deploy",
EditAll = "EditAll",
@ -53,6 +55,7 @@ export enum Operation {
EditReviewers = "EditReviewers",
EditRole = "EditRole",
EditSampleData = "EditSampleData",
EditScim = "EditScim",
EditStatus = "EditStatus",
EditTags = "EditTags",
EditTeams = "EditTeams",
@ -69,6 +72,7 @@ export enum Operation {
ViewProfilerGlobalConfiguration = "ViewProfilerGlobalConfiguration",
ViewQueries = "ViewQueries",
ViewSampleData = "ViewSampleData",
ViewScim = "ViewScim",
ViewTestCaseFailedRowsSample = "ViewTestCaseFailedRowsSample",
ViewTests = "ViewTests",
ViewUsage = "ViewUsage",

View File

@ -56,6 +56,11 @@ export interface Team {
* Email address of the team.
*/
email?: string;
/**
* External identifier for the team from an external identity provider (e.g., Azure AD group
* ID).
*/
externalId?: string;
/**
* FullyQualifiedName same as `name`.
*/

View File

@ -45,6 +45,10 @@ export interface User {
* Email address of the user.
*/
email: string;
/**
* External identifier from identity provider (used for SCIM).
*/
externalId?: string;
/**
* List of entities followed by the user.
*/
@ -107,6 +111,10 @@ export interface User {
* Roles that the user has been assigned.
*/
roles?: EntityReference[];
/**
* Raw user name from SCIM.
*/
scimUserName?: string;
/**
* Teams that the user belongs to.
*/

View File

@ -0,0 +1,26 @@
/*
* Copyright 2025 Collate.
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* http://www.apache.org/licenses/LICENSE-2.0
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/**
* SCIM configuration for automatic provisioning through identity providers like Azure AD or
* Okta.
*/
export interface ScimConfiguration {
/**
* Whether SCIM provisioning is enabled.
*/
enabled?: boolean;
/**
* The name of the identity provider for SCIM (e.g., azure, okta).
*/
identityProvider?: string;
}

View File

@ -43,6 +43,7 @@ export enum SettingType {
OpenMetadataBaseURLConfiguration = "openMetadataBaseUrlConfiguration",
ProfilerConfiguration = "profilerConfiguration",
SandboxModeEnabled = "sandboxModeEnabled",
ScimConfiguration = "scimConfiguration",
SearchSettings = "searchSettings",
SecretsManagerConfiguration = "secretsManagerConfiguration",
SlackAppConfiguration = "slackAppConfiguration",