Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
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
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,9 @@ was missing, ensuring that a `PatchRequest` is always available at runtime when

Corrected an annotation in `PatchRequest` which did not mark `Operations` with a multiValueClass.

Fixed an issue where Jackson `JsonReadFeature` properties were not used despite the property being
set on the MapperFactory.

## 6.0.0 - 2026-May-11
The UnboundID SCIM SDK has been updated to use version 3 of the Jackson library (this release ships
with v3.1.3). This change aligns the SCIM SDK with HTTP libraries such as Spring Framework 7/Spring
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,6 @@
import tools.jackson.core.ObjectReadContext;
import tools.jackson.core.io.IOContext;
import tools.jackson.core.json.JsonReadContext;
import tools.jackson.core.json.JsonReadFeature;
import tools.jackson.core.json.ReaderBasedJsonParser;
import tools.jackson.core.sym.CharsToNameCanonicalizer;

Expand All @@ -52,41 +51,27 @@ public class ScimFilterJsonParser extends ReaderBasedJsonParser
/**
* Creates a Jackson-based object for parsing SCIM filters.
*
* @param readContext The object read context.
* @param ctxt The I/O context to use.
* @param streamFeatures The standard stream read features.
* @param formatFeatures The format stream read features.
* @param r Reader used for reading actual content.
* @param st The name canonicalizer to use.
*/
public ScimFilterJsonParser(
@NotNull final ObjectReadContext readContext,
@NotNull final IOContext ctxt,
final int streamFeatures,
final int formatFeatures,
@NotNull final Reader r,
@NotNull final CharsToNameCanonicalizer st)
{
super(getReadContext(r), ctxt, JsonReadFeature.collectDefaults(), 0, r, st);
super(readContext, ctxt, streamFeatures, formatFeatures, r, st);

// By default, the JSON read context is set to StreamContext.TYPE_ROOT,
// which will require whitespace after any unquoted token (e.g., a number).
// We don't want this restriction when parsing a SCIM filter, so set the
// context type to -1, which is effectively "none".
this._streamReadContext = new JsonReadContext(null, 0, null, -1, 1, 0);
}

/**
* Provides a Jackson reader context based on the settings established in the
* SCIM SDK's JsonMapper.
*/
@NotNull
private static ObjectReadContext getReadContext(@NotNull final Reader r)
{
// Create a parser to obtain access to an ObjectReadContext, then close the
// parser to avoid leaking an object on every invocation. When Jackson
// closes a parser, the underlying Reader, r, is not closed when the caller
// owns the resource, so this is safe. An alternative to this approach is to
// use SDK_OBJECT_MAPPER._deserializationContext() as the ObjectReadContext.
// However, this method is labelled for unit test usage only, so it is not
// guaranteed to be a stable API in future versions of Jackson.
var parser = JsonUtils.getObjectReader().createParser(r);
ObjectReadContext context = parser.objectReadContext();
parser.close();
return context;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@
import com.unboundid.scim2.common.annotations.NotNull;
import tools.jackson.core.ErrorReportConfiguration;
import tools.jackson.core.JsonParser;
import tools.jackson.core.ObjectReadContext;
import tools.jackson.core.io.ContentReference;
import tools.jackson.core.io.IOContext;
import tools.jackson.core.json.JsonFactory;
Expand Down Expand Up @@ -80,7 +81,14 @@ JsonParser createScimFilterParser(@NotNull final Reader r)
ContentReference reference = ContentReference.construct(true, r, config);
IOContext ctxt = _createContext(reference, false);

return new ScimFilterJsonParser(ctxt, r, _rootCharSymbols.makeChild());
final ObjectReadContext readCtxt = getReadContext(r);
return new ScimFilterJsonParser(readCtxt,
ctxt,
readCtxt.getStreamReadFeatures(0),
readCtxt.getFormatReadFeatures(0),
r,
_rootCharSymbols.makeChild()
);
}

/**
Expand All @@ -94,4 +102,24 @@ public ScimJsonFactory copy()
{
return new ScimJsonFactory(this);
}

/**
* Provides a Jackson reader context based on the settings established in the
* SCIM SDK's JsonMapper.
*/
@NotNull
private static ObjectReadContext getReadContext(@NotNull final Reader r)
{
// Create a parser to obtain access to an ObjectReadContext, then close the
// parser to avoid leaking an object on every invocation. When Jackson
// closes a parser, the underlying Reader, r, is not closed when the caller
// owns the resource, so this is safe. An alternative to this approach is to
// use SDK_OBJECT_MAPPER._deserializationContext() as the ObjectReadContext.
// However, that method is labelled for unit test usage only, so it is not
// guaranteed to be a stable API in future versions of Jackson.
var parser = JsonUtils.getObjectReader().createParser(r);
ObjectReadContext context = parser.objectReadContext();
parser.close();
return context;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -33,16 +33,20 @@
package com.unboundid.scim2.common;

import com.unboundid.scim2.common.annotations.NotNull;
import com.unboundid.scim2.common.exceptions.BadRequestException;
import com.unboundid.scim2.common.filters.Filter;
import com.unboundid.scim2.common.types.Email;
import com.unboundid.scim2.common.types.UserResource;
import com.unboundid.scim2.common.utils.JsonUtils;
import com.unboundid.scim2.common.utils.MapperFactory;
import org.testng.annotations.AfterMethod;
import org.testng.annotations.Test;
import tools.jackson.core.json.JsonReadFeature;
import tools.jackson.databind.json.JsonMapper;
import tools.jackson.databind.node.ObjectNode;

import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatThrownBy;
import static tools.jackson.databind.MapperFeature.SORT_PROPERTIES_ALPHABETICALLY;


Expand All @@ -66,7 +70,7 @@ public void tearDown()
* the builder provided in {@link MapperFactory#createBuilder()}.
*/
@Test
public void testCustomJsonMapperBuilder()
public void testCustomJsonMapperBuilder() throws Exception
{
// A SCIM resource with the attributes (except 'schema') sorted
// alphabetically.
Expand Down Expand Up @@ -102,6 +106,21 @@ public void testCustomJsonMapperBuilder()
// sort the fields alphabetically.
userJSON = JsonUtils.getObjectWriter().writeValueAsString(user);
assertThat(userJSON).isEqualTo(expectedJSON);

// Test the use of single quotes in filters, which should not be permitted
// by default.
assertThatThrownBy(() -> Filter.fromString("userName eq 'drank'"))
.isInstanceOf(BadRequestException.class)
.hasMessageContaining("Unexpected character");

JsonMapper.Builder quoteCfg = JsonUtils.getInitialMapperConfig()
.enable(JsonReadFeature.ALLOW_SINGLE_QUOTES);
JsonUtils.setCustomMapperFactory(new MapperFactory().setConfig(quoteCfg));

// This time, the filter should be permitted.
Filter f = Filter.fromString("userName eq 'drank'");
assertThat(f.getComparisonValue()).isNotNull();
assertThat(f.getComparisonValue().asString()).isEqualTo("drank");
}

/**
Expand Down
Loading