Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add SCIM System Schema #418

Merged
merged 5 commits into from
Jan 23, 2025
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,163 @@
/*
* Copyright (c) 2025, WSO2 LLC. (http://www.wso2.com).
*
* WSO2 LLC. licenses this file to you 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.
*/

package org.wso2.charon3.core.config;

import org.apache.commons.lang.StringUtils;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
import org.wso2.charon3.core.exceptions.CharonException;
import org.wso2.charon3.core.exceptions.InternalErrorException;
import org.wso2.charon3.core.schema.AttributeSchema;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.util.HashMap;
import java.util.Map;
import java.util.Scanner;

import static org.wso2.charon3.core.schema.SCIMConstants.SYSTEM_USER_SCHEMA_URI;

/**
* This class is to build the extension system schema though the config file.
amanda-ariyaratne marked this conversation as resolved.
Show resolved Hide resolved
*/
public class SCIMSystemSchemaExtensionBuilder extends ExtensionBuilder {

private static final SCIMSystemSchemaExtensionBuilder instance = new SCIMSystemSchemaExtensionBuilder();
private static final Map<String, ExtensionAttributeSchemaConfig> extensionConfig = new HashMap<>();
private static final Map<String, AttributeSchema> attributeSchemas = new HashMap<>();
private AttributeSchema extensionSchema = null;
String extensionRootAttributeName = null;
String extensionRootAttributeURI;
amanda-ariyaratne marked this conversation as resolved.
Show resolved Hide resolved

/**
* Get the instance of the SCIMSystemSchemaExtensionBuilder.
*
* @return The instance of the SCIMSystemSchemaExtensionBuilder.
*/
public static SCIMSystemSchemaExtensionBuilder getInstance() {

return instance;
}

/**
* Constructor of the SCIMSystemSchemaExtensionBuilder.
*/
private SCIMSystemSchemaExtensionBuilder() {

extensionRootAttributeURI = SYSTEM_USER_SCHEMA_URI;
sadilchamishka marked this conversation as resolved.
Show resolved Hide resolved
}

/**
* Get the extension schema.
*
* @return The extension schema.
*/
public AttributeSchema getExtensionSchema() {

return extensionSchema;
}

/**
* Build the system schema extension from the config file.
*
* @param configFilePath Path to the config file.
* @throws CharonException If an error occurred while reading the config file.
* @throws InternalErrorException If an error occurred while building the schema.
*/
public void buildSystemSchemaExtension(String configFilePath) throws CharonException, InternalErrorException {

File provisioningConfig = new File(configFilePath);
try (InputStream configFilePathInputStream = new FileInputStream(provisioningConfig)) {
buildSystemSchemaExtension(configFilePathInputStream);
} catch (FileNotFoundException e) {
throw new CharonException(configFilePath + " file not found!", e);
} catch (JSONException e) {
throw new CharonException("Error while parsing " + configFilePath + " file!", e);
} catch (IOException e) {
throw new CharonException("Error while closing " + configFilePath + " file!", e);
}
}

/**
* Build the system schema extension from the input stream.
*
* @param inputStream The input stream.
* @throws CharonException If an error occurred while reading the configuration.
* @throws InternalErrorException If an error occurred while building the schema.
*/
public void buildSystemSchemaExtension(InputStream inputStream) throws CharonException, InternalErrorException {

readConfiguration(inputStream);
for (Map.Entry<String, ExtensionAttributeSchemaConfig> attributeSchemaConfig : extensionConfig.entrySet()) {
// If there are no children it is a simple attribute, build it.
if (!attributeSchemaConfig.getValue().hasChildren()) {
buildSimpleAttributeSchema(attributeSchemaConfig.getValue(), attributeSchemas);
} else {
// Need to build child schemas first.
buildComplexAttributeSchema(attributeSchemaConfig.getValue(), attributeSchemas, extensionConfig);
}
}

extensionSchema = attributeSchemas.get(extensionRootAttributeURI);
}

/**
* Read the configuration from the input stream.
*
* @param inputStream The input stream.
* @throws CharonException If an error occurred while reading the configuration.
*/
public void readConfiguration(InputStream inputStream) throws CharonException {

amanda-ariyaratne marked this conversation as resolved.
Show resolved Hide resolved
Scanner scanner = new Scanner(inputStream, "utf-8").useDelimiter("\\A");
sadilchamishka marked this conversation as resolved.
Show resolved Hide resolved
String jsonString = scanner.hasNext() ? scanner.next() : "";
sadilchamishka marked this conversation as resolved.
Show resolved Hide resolved

JSONArray attributeConfigArray = new JSONArray(jsonString);

for (int index = 0; index < attributeConfigArray.length(); ++index) {
JSONObject rawAttributeConfig = attributeConfigArray.getJSONObject(index);
ExtensionAttributeSchemaConfig schemaAttributeConfig =
new ExtensionAttributeSchemaConfig(rawAttributeConfig);
if (schemaAttributeConfig.getURI().startsWith(extensionRootAttributeURI)) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can the schemaAttributeConfig.getURI() be null?

Copy link
Contributor Author

@amanda-ariyaratne amanda-ariyaratne Jan 23, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No. getJSONObject always returns a JSON object. If there are parsing errors, then an exception is thrown.

extensionConfig.put(schemaAttributeConfig.getURI(), schemaAttributeConfig);
}

if (extensionRootAttributeURI.equals(schemaAttributeConfig.getURI())) {
extensionRootAttributeName = schemaAttributeConfig.getName();
}
}
}

@Override
public String getURI() {

return extensionRootAttributeURI;
}

@Override
protected boolean isRootConfig(ExtensionAttributeSchemaConfig config) {

return StringUtils.isNotBlank(extensionRootAttributeName)
sadilchamishka marked this conversation as resolved.
Show resolved Hide resolved
&& extensionRootAttributeName.equals(config.getName());
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,8 @@
import java.util.Map;
import java.util.Scanner;

import static org.wso2.charon3.core.schema.SCIMConstants.ENTERPRISE_USER_SCHEMA_URI;

/**
* This class is to build the extension user schema though the config file.
*/
Expand All @@ -42,16 +44,24 @@ public class SCIMUserSchemaExtensionBuilder extends ExtensionBuilder {
private static Map<String, ExtensionAttributeSchemaConfig> extensionConfig = new HashMap<>();
// Extension root attribute name.
String extensionRootAttributeName = null;
String extensionRootAttributeURI = null;
String extensionRootAttributeURI;
// built schema map
private static Map<String, AttributeSchema> attributeSchemas = new HashMap<String, AttributeSchema>();
private static Map<String, AttributeSchema> attributeSchemas = new HashMap<>();
// extension root attribute schema
private AttributeSchema extensionSchema = null;

public static SCIMUserSchemaExtensionBuilder getInstance() {
return configReader;
}

/**
* Constructor to initialize the SCIMUserSchemaExtensionBuilder.
*/
public SCIMUserSchemaExtensionBuilder() {

this.extensionRootAttributeURI = ENTERPRISE_USER_SCHEMA_URI;
}

public AttributeSchema getExtensionSchema() {
return extensionSchema;
}
Expand Down Expand Up @@ -80,24 +90,20 @@ public void buildUserSchemaExtension(InputStream inputStream) throws CharonExcep

readConfiguration(inputStream);
for (Map.Entry<String, ExtensionAttributeSchemaConfig> attributeSchemaConfig : extensionConfig.entrySet()) {
// if there are no children its a simple attribute, build it
// If there are no children it's a simple attribute, build it.
if (!attributeSchemaConfig.getValue().hasChildren()) {
buildSimpleAttributeSchema(attributeSchemaConfig.getValue(), attributeSchemas);
} else {
// need to build child schemas first
// Need to build child schemas first.
buildComplexAttributeSchema(attributeSchemaConfig.getValue(), attributeSchemas, extensionConfig);
}
}
// now get the extension schema
/*
* Assumption : Final config in the configuration file is the extension
* root attribute
*/

extensionSchema = attributeSchemas.get(extensionRootAttributeURI);
}

public void readConfiguration(InputStream inputStream) throws CharonException {
//Scanner scanner = new Scanner(new FileInputStream(provisioningConfig));

Scanner scanner = new Scanner(inputStream, "utf-8").useDelimiter("\\A");
String jsonString = scanner.hasNext() ? scanner.next() : "";

Expand All @@ -107,13 +113,11 @@ public void readConfiguration(InputStream inputStream) throws CharonException {
JSONObject rawAttributeConfig = attributeConfigArray.getJSONObject(index);
ExtensionAttributeSchemaConfig schemaAttributeConfig =
new ExtensionAttributeSchemaConfig(rawAttributeConfig);
extensionConfig.put(schemaAttributeConfig.getURI(), schemaAttributeConfig);
if (schemaAttributeConfig.getURI().startsWith(extensionRootAttributeURI)) {
extensionConfig.put(schemaAttributeConfig.getURI(), schemaAttributeConfig);
}

/**
* NOTE: Assume last config is the root config
*/
if (index == attributeConfigArray.length() - 1) {
extensionRootAttributeURI = schemaAttributeConfig.getURI();
if (extensionRootAttributeURI.equals(schemaAttributeConfig.getURI())) {
extensionRootAttributeName = schemaAttributeConfig.getName();
}
}
Expand All @@ -126,6 +130,7 @@ public String getURI() {
return extensionRootAttributeURI;
}

@Override
protected boolean isRootConfig(ExtensionAttributeSchemaConfig config) {

return StringUtils.isNotBlank(extensionRootAttributeName) &&
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -536,11 +536,12 @@ public ComplexAttribute buildComplexAttribute(AttributeSchema complexAttributeSc
List<AttributeSchema> subAttributeSchemas =
((AttributeSchema) complexAttributeSchema).getSubAttributeSchemas();
String userExtensionName = SCIMResourceSchemaManager.getInstance().getExtensionName();
String systemExtensionName = SCIMResourceSchemaManager.getInstance().getSystemSchemaExtensionName();
String customExtensionName = SCIMResourceSchemaManager.getInstance().getCustomSchemaExtensionURI();

//iterate through the complex attribute schema and extract the sub attributes.
for (AttributeSchema subAttributeSchema : subAttributeSchemas) {
//obtain the user defined value for given key- attribute schema name
//obtain the user defined value for given key-attribute schema name
Object attributeValObj = jsonObject.opt(subAttributeSchema.getName());
SCIMDefinitions.DataType subAttributeSchemaType = subAttributeSchema.getType();
if (subAttributeSchemaType.equals(STRING) || subAttributeSchemaType.equals(BINARY) ||
Expand Down Expand Up @@ -579,8 +580,9 @@ public ComplexAttribute buildComplexAttribute(AttributeSchema complexAttributeSc
//this case is only valid for the extension schema
//As according to the spec we have complex attribute inside complex attribute only for extension,
//we need to treat it separately
} else if ((complexAttributeSchema.getName().equals(userExtensionName)) ||
(complexAttributeSchema.getName().equals(customExtensionName))) {
} else if ((complexAttributeSchema.getName().equals(userExtensionName))
|| (complexAttributeSchema.getName().equals(customExtensionName))
|| (complexAttributeSchema.getName().equals(systemExtensionName))) {
if (subAttributeSchemaType.equals(COMPLEX)) {
//check for user defined extension's schema violation
List<AttributeSchema> subList = subAttributeSchema.getSubAttributeSchemas();
Expand Down Expand Up @@ -656,7 +658,6 @@ public ComplexAttribute buildComplexAttribute(AttributeSchema complexAttributeSc
return (ComplexAttribute) DefaultAttributeFactory.createAttribute(complexAttributeSchema, complexAttribute);
}


/*
* To build a complex type value of a Multi Valued Attribute. (eg. Email with value,type,primary as sub attributes
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -467,13 +467,13 @@ public String buildServiceProviderConfigJsonBody(HashMap<String, Object> config)

}

/*
* Build the user resource type json representation.
* @return
/**
* Build the user resource type json representation.
* @return json representation of user resource type.
*/
public String buildUserResourceTypeJsonBody() throws JSONException {
JSONObject userResourceTypeObject = new JSONObject();

JSONObject userResourceTypeObject = new JSONObject();
userResourceTypeObject.put(
SCIMConstants.CommonSchemaConstants.SCHEMAS, SCIMConstants.RESOURCE_TYPE_SCHEMA_URI);
userResourceTypeObject.put(
Expand All @@ -499,6 +499,15 @@ public String buildUserResourceTypeJsonBody() throws JSONException {
SCIMResourceSchemaManager.getInstance().getExtensionRequired());
schemaExtensions.put(extensionSchemaObject);

JSONObject systemSchemaObject = new JSONObject();
systemSchemaObject.put(
SCIMConstants.ResourceTypeSchemaConstants.SCHEMA_EXTENSIONS_SCHEMA,
SCIMResourceSchemaManager.getInstance().getSystemSchemaExtensionURI());
systemSchemaObject.put(
SCIMConstants.ResourceTypeSchemaConstants.SCHEMA_EXTENSIONS_REQUIRED,
SCIMResourceSchemaManager.getInstance().getSystemSchemaExtensionRequired());
schemaExtensions.put(systemSchemaObject);

// Add custom user schema as a schema extension.
if (StringUtils.isNotBlank(SCIMResourceSchemaManager.getInstance().getCustomSchemaExtensionURI())) {
JSONObject customSchemaObject = new JSONObject();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,8 +35,8 @@
import java.util.Map;

/**
* This is the interface for usermanager extension.
* An implementation can plugin their own user manager-(either LDAP based, DB based etc)
* This is the interface for user manager extension.
* An implementation can plug in their own user manager-(either LDAP based, DB based etc.)
* by implementing this interface and mentioning it in configuration.
*/
public interface UserManager {
Expand Down Expand Up @@ -265,6 +265,20 @@ default List<Attribute> getEnterpriseUserSchema() throws CharonException, NotImp
throw new NotImplementedException();
}

/**
* Retrieve schema of the system user.
*
* @return List of attributes of system user schema.
* @throws CharonException Charon exception.
* @throws NotImplementedException Functionality no implemented exception.
* @throws BadRequestException Bad request exception.
*/
default List<Attribute> getSystemUserSchema() throws CharonException, NotImplementedException,
BadRequestException {

throw new NotImplementedException();
}

/**
* Return Custom schema.
* @return Custom schema.
Expand Down
Loading
Loading