CVE-2017-4974
Description
An issue was discovered in Cloud Foundry Foundation cf-release versions prior to v258; UAA release 2.x versions prior to v2.7.4.15, 3.6.x versions prior to v3.6.9, 3.9.x versions prior to v3.9.11, and other versions prior to v3.16.0; and UAA bosh release (uaa-release) 13.x versions prior to v13.13, 24.x versions prior to v24.8, and other versions prior to v30.1. An authorized user can use a blind SQL injection attack to query the contents of the UAA database, aka "Blind SQL Injection with privileged UAA endpoints."
Affected packages
Versions sourced from the GitHub Security Advisory.
| Package | Affected versions | Patched versions |
|---|---|---|
org.cloudfoundry.identity:cloudfoundry-identity-serverMaven | >= 2.0.0, < 2.7.4.15 | 2.7.4.15 |
org.cloudfoundry.identity:cloudfoundry-identity-serverMaven | >= 3.0.0, < 3.6.9 | 3.6.9 |
org.cloudfoundry.identity:cloudfoundry-identity-serverMaven | >= 3.7.0, < 3.9.11 | 3.9.11 |
org.cloudfoundry.identity:cloudfoundry-identity-serverMaven | >= 3.10.0, < 3.16.0 | 3.16.0 |
Affected products
65cpe:2.3:a:cloudfoundry:cloud_foundry_uaa_bosh:*:*:*:*:*:*:*:*+ 23 more
- cpe:2.3:a:cloudfoundry:cloud_foundry_uaa_bosh:*:*:*:*:*:*:*:*range: <=30
- cpe:2.3:a:cloudfoundry:cloud_foundry_uaa_bosh:13.1:*:*:*:*:*:*:*
- cpe:2.3:a:cloudfoundry:cloud_foundry_uaa_bosh:13.10:*:*:*:*:*:*:*
- cpe:2.3:a:cloudfoundry:cloud_foundry_uaa_bosh:13.11:*:*:*:*:*:*:*
- cpe:2.3:a:cloudfoundry:cloud_foundry_uaa_bosh:13.12:*:*:*:*:*:*:*
- cpe:2.3:a:cloudfoundry:cloud_foundry_uaa_bosh:13.2:*:*:*:*:*:*:*
- cpe:2.3:a:cloudfoundry:cloud_foundry_uaa_bosh:13.3:*:*:*:*:*:*:*
- cpe:2.3:a:cloudfoundry:cloud_foundry_uaa_bosh:13.4:*:*:*:*:*:*:*
- cpe:2.3:a:cloudfoundry:cloud_foundry_uaa_bosh:13.5:*:*:*:*:*:*:*
- cpe:2.3:a:cloudfoundry:cloud_foundry_uaa_bosh:13.6:*:*:*:*:*:*:*
- cpe:2.3:a:cloudfoundry:cloud_foundry_uaa_bosh:13.7:*:*:*:*:*:*:*
- cpe:2.3:a:cloudfoundry:cloud_foundry_uaa_bosh:13.8:*:*:*:*:*:*:*
- cpe:2.3:a:cloudfoundry:cloud_foundry_uaa_bosh:13.9:*:*:*:*:*:*:*
- cpe:2.3:a:cloudfoundry:cloud_foundry_uaa_bosh:24:*:*:*:*:*:*:*
- cpe:2.3:a:cloudfoundry:cloud_foundry_uaa_bosh:24.1:*:*:*:*:*:*:*
- cpe:2.3:a:cloudfoundry:cloud_foundry_uaa_bosh:24.2:*:*:*:*:*:*:*
- cpe:2.3:a:cloudfoundry:cloud_foundry_uaa_bosh:24.3:*:*:*:*:*:*:*
- cpe:2.3:a:cloudfoundry:cloud_foundry_uaa_bosh:24.4:*:*:*:*:*:*:*
- cpe:2.3:a:cloudfoundry:cloud_foundry_uaa_bosh:24.5:*:*:*:*:*:*:*
- cpe:2.3:a:cloudfoundry:cloud_foundry_uaa_bosh:24.6:*:*:*:*:*:*:*
- cpe:2.3:a:cloudfoundry:cloud_foundry_uaa_bosh:24.7:*:*:*:*:*:*:*
- cpe:2.3:a:cloudfoundry:cloud_foundry_uaa_bosh:30.1:*:*:*:*:*:*:*
- cpe:2.3:a:cloudfoundry:cloud_foundry_uaa_bosh:30.2:*:*:*:*:*:*:*
- cpe:2.3:a:cloudfoundry:cloud_foundry_uaa_bosh:30.3:*:*:*:*:*:*:*
cpe:2.3:a:pivotal_software:cloud_foundry_uaa:*:*:*:*:*:*:*:*+ 38 more
- cpe:2.3:a:pivotal_software:cloud_foundry_uaa:*:*:*:*:*:*:*:*range: <=4.2.0
- cpe:2.3:a:pivotal_software:cloud_foundry_uaa:2.2.5.4:*:*:*:*:*:*:*
- cpe:2.3:a:pivotal_software:cloud_foundry_uaa:2.7.1:*:*:*:*:*:*:*
- cpe:2.3:a:pivotal_software:cloud_foundry_uaa:2.7.2:*:*:*:*:*:*:*
- cpe:2.3:a:pivotal_software:cloud_foundry_uaa:2.7.3:*:*:*:*:*:*:*
- cpe:2.3:a:pivotal_software:cloud_foundry_uaa:2.7.4:*:*:*:*:*:*:*
- cpe:2.3:a:pivotal_software:cloud_foundry_uaa:2.7.4.1:*:*:*:*:*:*:*
- cpe:2.3:a:pivotal_software:cloud_foundry_uaa:2.7.4.11:*:*:*:*:*:*:*
- cpe:2.3:a:pivotal_software:cloud_foundry_uaa:2.7.4.12:*:*:*:*:*:*:*
- cpe:2.3:a:pivotal_software:cloud_foundry_uaa:2.7.4.13:*:*:*:*:*:*:*
- cpe:2.3:a:pivotal_software:cloud_foundry_uaa:2.7.4.14:*:*:*:*:*:*:*
- cpe:2.3:a:pivotal_software:cloud_foundry_uaa:2.7.4.2:*:*:*:*:*:*:*
- cpe:2.3:a:pivotal_software:cloud_foundry_uaa:2.7.4.3:*:*:*:*:*:*:*
- cpe:2.3:a:pivotal_software:cloud_foundry_uaa:2.7.4.4:*:*:*:*:*:*:*
- cpe:2.3:a:pivotal_software:cloud_foundry_uaa:2.7.4.5:*:*:*:*:*:*:*
- cpe:2.3:a:pivotal_software:cloud_foundry_uaa:2.7.4.6:*:*:*:*:*:*:*
- cpe:2.3:a:pivotal_software:cloud_foundry_uaa:2.7.4.7:*:*:*:*:*:*:*
- cpe:2.3:a:pivotal_software:cloud_foundry_uaa:2.7.4.8:*:*:*:*:*:*:*
- cpe:2.3:a:pivotal_software:cloud_foundry_uaa:2.7.4.9:*:*:*:*:*:*:*
- cpe:2.3:a:pivotal_software:cloud_foundry_uaa:3.6.1:*:*:*:*:*:*:*
- cpe:2.3:a:pivotal_software:cloud_foundry_uaa:3.6.2:*:*:*:*:*:*:*
- cpe:2.3:a:pivotal_software:cloud_foundry_uaa:3.6.3:*:*:*:*:*:*:*
- cpe:2.3:a:pivotal_software:cloud_foundry_uaa:3.6.4:*:*:*:*:*:*:*
- cpe:2.3:a:pivotal_software:cloud_foundry_uaa:3.6.5:*:*:*:*:*:*:*
- cpe:2.3:a:pivotal_software:cloud_foundry_uaa:3.6.6:*:*:*:*:*:*:*
- cpe:2.3:a:pivotal_software:cloud_foundry_uaa:3.6.7:*:*:*:*:*:*:*
- cpe:2.3:a:pivotal_software:cloud_foundry_uaa:3.6.8:*:*:*:*:*:*:*
- cpe:2.3:a:pivotal_software:cloud_foundry_uaa:3.9.1:*:*:*:*:*:*:*
- cpe:2.3:a:pivotal_software:cloud_foundry_uaa:3.9.10:*:*:*:*:*:*:*
- cpe:2.3:a:pivotal_software:cloud_foundry_uaa:3.9.12:*:*:*:*:*:*:*
- cpe:2.3:a:pivotal_software:cloud_foundry_uaa:3.9.13:*:*:*:*:*:*:*
- cpe:2.3:a:pivotal_software:cloud_foundry_uaa:3.9.2:*:*:*:*:*:*:*
- cpe:2.3:a:pivotal_software:cloud_foundry_uaa:3.9.3:*:*:*:*:*:*:*
- cpe:2.3:a:pivotal_software:cloud_foundry_uaa:3.9.4:*:*:*:*:*:*:*
- cpe:2.3:a:pivotal_software:cloud_foundry_uaa:3.9.5:*:*:*:*:*:*:*
- cpe:2.3:a:pivotal_software:cloud_foundry_uaa:3.9.6:*:*:*:*:*:*:*
- cpe:2.3:a:pivotal_software:cloud_foundry_uaa:3.9.7:*:*:*:*:*:*:*
- cpe:2.3:a:pivotal_software:cloud_foundry_uaa:3.9.8:*:*:*:*:*:*:*
- cpe:2.3:a:pivotal_software:cloud_foundry_uaa:3.9.9:*:*:*:*:*:*:*
- Range: Cloud Foundry UAA
Patches
52dbeb9e93e07better parsing of attributes
3 files changed · +188 −5
server/src/main/java/org/cloudfoundry/identity/uaa/resources/jdbc/SimpleSearchQueryConverter.java+89 −1 modified@@ -13,6 +13,7 @@ package org.cloudfoundry.identity.uaa.resources.jdbc; +import com.unboundid.scim.sdk.InvalidResourceException; import com.unboundid.scim.sdk.SCIMException; import com.unboundid.scim.sdk.SCIMFilter; import org.apache.commons.logging.Log; @@ -25,19 +26,82 @@ import java.text.DateFormat; import java.text.ParseException; import java.text.SimpleDateFormat; +import java.util.Arrays; import java.util.Collections; import java.util.HashMap; +import java.util.LinkedList; +import java.util.List; import java.util.Map; +import static java.util.Collections.emptyList; +import static java.util.Optional.ofNullable; import static org.cloudfoundry.identity.uaa.resources.jdbc.SearchQueryConverter.ProcessedFilter.ORDER_BY; public class SimpleSearchQueryConverter implements SearchQueryConverter { + //LOWER + public static final List<String> VALID_ATTRIBUTE_NAMES = Collections.unmodifiableList( + Arrays.asList( + "id", + "created", + "lastmodified", + "version", + "username", + "password", + "email", + "givenname", + "familyname", + "name.familyname", + "name.givenname", + "active", + "phonenumber", + "verified", + "origin", + "identity_zone_id", + "passwd_lastmodified", + "passwd_change_required", + "last_logon_success_time", + "previous_logon_success_time", + "displayname", + "scope", + "group_id", + "member_id", + "member_type", + "description", + "client_id", + "authorized_grant_types", + "web_server_redirect_uri", + "redirect_uri", + "access_token_validity", + "refresh_token_validity", + "autoapprove", + "show_on_home_page", + "created_by", + "required_user_groups", + "user_id", + "meta.lastmodified", + "meta.created", + "meta.location", + "meta.resourcetype", + "meta.version", + "emails.value", + "groups.display", + "phonenumbers.value", + "gm.external_group", + "gm.origin", + "g.displayname", + "g.id" + ) + ); + private static Log logger = LogFactory.getLog(SimpleSearchQueryConverter.class); private AttributeNameMapper mapper = new SimpleAttributeNameMapper(Collections.<String, String> emptyMap()); private boolean dbCaseInsensitive = false; + public SimpleSearchQueryConverter() { + } + public boolean isDbCaseInsensitive() { return dbCaseInsensitive; } @@ -92,7 +156,7 @@ private String getWhereClause(String filter, String sortBy, boolean ascending, M } } - private SCIMFilter scimFilter(String filter) throws SCIMException { + protected SCIMFilter scimFilter(String filter) throws SCIMException { SCIMFilter scimFilter; try { scimFilter = SCIMFilter.parse(filter); @@ -101,9 +165,33 @@ private SCIMFilter scimFilter(String filter) throws SCIMException { filter = filter.replaceAll("'","\""); scimFilter = SCIMFilter.parse(filter); } + validateFilterAttributes(scimFilter); return scimFilter; } + private void validateFilterAttributes(SCIMFilter filter) throws SCIMException { + List<String> invalidAttributes = new LinkedList<>(); + validateFilterAttributes(filter, invalidAttributes); + if (!invalidAttributes.isEmpty()) { + throw new InvalidResourceException("Invalid filter attributes:"+StringUtils.collectionToCommaDelimitedString(invalidAttributes)); + } + } + + private void validateFilterAttributes(SCIMFilter filter, List<String> invalidAttribues) { + if (filter.getFilterAttribute()!=null && filter.getFilterAttribute().getAttributeName()!=null) { + String name = filter.getFilterAttribute().getAttributeName(); + if (filter.getFilterAttribute().getSubAttributeName()!=null) { + name = name + "." + filter.getFilterAttribute().getSubAttributeName(); + } + if (!VALID_ATTRIBUTE_NAMES.contains(name.toLowerCase())) { + invalidAttribues.add(name); + } + } + for (SCIMFilter subfilter : ofNullable(filter.getFilterComponents()).orElse(emptyList())) { + validateFilterAttributes(subfilter, invalidAttribues); + } + } + private String createFilter(SCIMFilter filter, Map<String,Object> values, AttributeNameMapper mapper, String paramPrefix) { switch (filter.getFilterType()) { case AND:
server/src/test/java/org/cloudfoundry/identity/uaa/resources/jdbc/SimpleSearchQueryConverterTests.java+95 −0 added@@ -0,0 +1,95 @@ +/* + * **************************************************************************** + * Cloud Foundry + * Copyright (c) [2009-2017] Pivotal Software, Inc. All Rights Reserved. + * + * This product is licensed to you under the Apache License, Version 2.0 (the "License"). + * You may not use this product except in compliance with the License. + * + * This product includes a number of subcomponents with + * separate copyright notices and license terms. Your use of these + * subcomponents is subject to the terms and conditions of the + * subcomponent's license, as noted in the LICENSE file. + * **************************************************************************** + */ + +package org.cloudfoundry.identity.uaa.resources.jdbc; + +import com.unboundid.scim.sdk.InvalidResourceException; +import com.unboundid.scim.sdk.SCIMFilter; +import org.cloudfoundry.identity.uaa.scim.jdbc.ScimSearchQueryConverter; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.ExpectedException; + +import java.util.concurrent.atomic.AtomicInteger; + +import static java.util.Collections.emptyList; +import static java.util.Optional.ofNullable; +import static org.hamcrest.Matchers.containsString; +import static org.hamcrest.Matchers.startsWith; + +public class SimpleSearchQueryConverterTests { + + SimpleSearchQueryConverter converter; + + String query = "user_id eq \"7e2345e8-8bbf-4eaa-9bc3-ae1ba610f890\"" + + "and " + + "client_id eq \"app\"" + + "and " + + "meta.lastmodified gt \"some-value\"" + + "and " + + "(an/**/invalid/**/attribute/**/and/**/1" + //invalid attribute name + " pr " + //operator (present) + "and " + + "1 eq 1)" + //invalid attribute name 1 + " and " + + "\"1\" eq \"1\""; + + String validQuery = "user_id eq \"7e2345e8-8bbf-4eaa-9bc3-ae1ba610f890\"" + + "and " + + "client_id eq \"app\"" + + "and " + + "meta.lastmodified gt \"some-value\"" + + "and " + + "meta.created pr"; + + @Rule + public ExpectedException exception = ExpectedException.none(); + + @Before + public void setup() { + converter = new ScimSearchQueryConverter(); + } + + @Test + public void test_query() throws Exception { + exception.expect(InvalidResourceException.class); + exception.expectMessage(startsWith("Invalid filter attributes")); + exception.expectMessage(containsString("an/**/invalid/**/attribute/**/and/**/1")); + exception.expectMessage(containsString("1")); + exception.expectMessage(containsString("\"1\"")); + SCIMFilter filter = converter.scimFilter(query); + } + + @Test + public void print_query() throws Exception { + SCIMFilter filter = converter.scimFilter(validQuery); + printFilterAttributes(filter, new AtomicInteger(0)); + } + + public void printFilterAttributes(SCIMFilter filter, AtomicInteger pos) { + if (filter.getFilterAttribute() != null) { + String name = filter.getFilterAttribute().getAttributeName(); + if (filter.getFilterAttribute().getSubAttributeName() != null) { + name = name + "." + filter.getFilterAttribute().getSubAttributeName(); + } + System.out.println((pos.incrementAndGet()) + ". Attribute name:" + name); + } + for (SCIMFilter subfilter : ofNullable(filter.getFilterComponents()).orElse(emptyList())) { + printFilterAttributes(subfilter, pos); + } + } + +} \ No newline at end of file
server/src/test/java/org/cloudfoundry/identity/uaa/scim/jdbc/ScimSearchQueryConverterTests.java+4 −4 modified@@ -58,8 +58,8 @@ public void canConvertValidFilters() throws Exception { validate(filterProcessor.convert("username pr and emails.value co \".com\"", null, false),"(username IS NOT NULL AND LOWER(email) LIKE LOWER(:__value_0))", 1); validate(filterProcessor.convert("username eq \"joe\" or emails.value co \".com\"", null, false),"(LOWER(username) = LOWER(:__value_0) OR LOWER(email) LIKE LOWER(:__value_1))", 2); validate(filterProcessor.convert("active eq true", null, false),"active = :__value_0", 1, Boolean.class); - validate(filterProcessor.convert("test eq 1000000.45", null, false),"test = :__value_0", 1, Double.class); - validate(filterProcessor.convert("test eq 1000000", null, false),"test = :__value_0", 1, Double.class); + validate(filterProcessor.convert("Version eq 1000000.45", null, false),"Version = :__value_0", 1, Double.class); + validate(filterProcessor.convert("meta.VerSion eq 1000000", null, false),"VerSion = :__value_0", 1, Double.class); } @Test @@ -84,8 +84,8 @@ public void caseInsensitiveDbDoesNotInjectLower() throws Exception { validate(filterProcessor.convert("username pr and emails.value co \".com\"", null, false),"(username IS NOT NULL AND email LIKE :__value_0)", 1); validate(filterProcessor.convert("username eq \"joe\" or emails.value co \".com\"", null, false),"(username = :__value_0 OR email LIKE :__value_1)", 2); validate(filterProcessor.convert("active eq true", null, false),"active = :__value_0", 1, Boolean.class); - validate(filterProcessor.convert("test eq 1000000.45", null, false),"test = :__value_0", 1, Double.class); - validate(filterProcessor.convert("test eq 1000000", null, false),"test = :__value_0", 1, Double.class); + validate(filterProcessor.convert("Version eq 1000000.45", null, false),"Version = :__value_0", 1, Double.class); + validate(filterProcessor.convert("meta.VerSion eq 1000000", null, false),"VerSion = :__value_0", 1, Double.class); } @Test
01edea6337c8better parsing of attributes
4 files changed · +296 −14
common/src/main/java/org/cloudfoundry/identity/uaa/rest/jdbc/SimpleSearchQueryConverter.java+103 −10 modified@@ -1,6 +1,6 @@ /******************************************************************************* * Cloud Foundry - * Copyright (c) [2009-2014] Pivotal Software, Inc. All Rights Reserved. + * Copyright (c) [2009-2016] Pivotal Software, Inc. All Rights Reserved. * * This product is licensed to you under the Apache License, Version 2.0 (the "License"). * You may not use this product except in compliance with the License. @@ -13,13 +13,7 @@ package org.cloudfoundry.identity.uaa.rest.jdbc; -import java.text.DateFormat; -import java.text.ParseException; -import java.text.SimpleDateFormat; -import java.util.Collections; -import java.util.HashMap; -import java.util.Map; - +import com.unboundid.scim.sdk.InvalidResourceException; import com.unboundid.scim.sdk.SCIMException; import com.unboundid.scim.sdk.SCIMFilter; import org.apache.commons.logging.Log; @@ -29,15 +23,85 @@ import org.springframework.security.oauth2.common.util.RandomValueStringGenerator; import org.springframework.util.StringUtils; +import java.text.DateFormat; +import java.text.ParseException; +import java.text.SimpleDateFormat; +import java.util.Arrays; +import java.util.Collections; +import java.util.HashMap; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; + +import static java.util.Collections.emptyList; +import static java.util.Optional.ofNullable; import static org.cloudfoundry.identity.uaa.rest.jdbc.SearchQueryConverter.ProcessedFilter.ORDER_BY; public class SimpleSearchQueryConverter implements SearchQueryConverter { + //LOWER + public static final List<String> VALID_ATTRIBUTE_NAMES = Collections.unmodifiableList( + Arrays.asList( + "id", + "created", + "lastmodified", + "version", + "username", + "password", + "email", + "givenname", + "familyname", + "name.familyname", + "name.givenname", + "active", + "phonenumber", + "verified", + "origin", + "identity_zone_id", + "passwd_lastmodified", + "passwd_change_required", + "last_logon_success_time", + "previous_logon_success_time", + "displayname", + "scope", + "group_id", + "member_id", + "member_type", + "description", + "client_id", + "authorized_grant_types", + "web_server_redirect_uri", + "redirect_uri", + "access_token_validity", + "refresh_token_validity", + "autoapprove", + "show_on_home_page", + "created_by", + "required_user_groups", + "user_id", + "meta.lastmodified", + "meta.created", + "meta.location", + "meta.resourcetype", + "meta.version", + "emails.value", + "groups.display", + "phonenumbers.value", + "gm.external_group", + "gm.origin", + "g.displayname", + "g.id" + ) + ); + private static Log logger = LogFactory.getLog(SimpleSearchQueryConverter.class); private AttributeNameMapper mapper = new SimpleAttributeNameMapper(Collections.<String, String> emptyMap()); private boolean dbCaseInsensitive = false; + public SimpleSearchQueryConverter() { + } + public boolean isDbCaseInsensitive() { return dbCaseInsensitive; } @@ -92,7 +156,7 @@ private String getWhereClause(String filter, String sortBy, boolean ascending, M } } - private SCIMFilter scimFilter(String filter) throws SCIMException { + protected SCIMFilter scimFilter(String filter) throws SCIMException { SCIMFilter scimFilter; try { scimFilter = SCIMFilter.parse(filter); @@ -101,9 +165,33 @@ private SCIMFilter scimFilter(String filter) throws SCIMException { filter = filter.replaceAll("'","\""); scimFilter = SCIMFilter.parse(filter); } + validateFilterAttributes(scimFilter); return scimFilter; } + private void validateFilterAttributes(SCIMFilter filter) throws SCIMException { + List<String> invalidAttributes = new LinkedList<>(); + validateFilterAttributes(filter, invalidAttributes); + if (!invalidAttributes.isEmpty()) { + throw new InvalidResourceException("Invalid filter attributes:"+StringUtils.collectionToCommaDelimitedString(invalidAttributes)); + } + } + + private void validateFilterAttributes(SCIMFilter filter, List<String> invalidAttribues) { + if (filter.getFilterAttribute()!=null && filter.getFilterAttribute().getAttributeName()!=null) { + String name = filter.getFilterAttribute().getAttributeName(); + if (filter.getFilterAttribute().getSubAttributeName()!=null) { + name = name + "." + filter.getFilterAttribute().getSubAttributeName(); + } + if (!VALID_ATTRIBUTE_NAMES.contains(name.toLowerCase())) { + invalidAttribues.add(name); + } + } + for (SCIMFilter subfilter : ofNullable(filter.getFilterComponents()).orElse(emptyList())) { + validateFilterAttributes(subfilter, invalidAttribues); + } + } + private String createFilter(SCIMFilter filter, Map<String,Object> values, AttributeNameMapper mapper, String paramPrefix) { switch (filter.getFilterType()) { case AND: @@ -130,7 +218,12 @@ private String createFilter(SCIMFilter filter, Map<String,Object> values, Attrib return null; } - protected String comparisonClause(SCIMFilter filter, String comparator, Map<String, Object> values, String valuePrefix, String valueSuffix, String paramPrefix) { + protected String comparisonClause(SCIMFilter filter, + String comparator, + Map<String, Object> values, + String valuePrefix, + String valueSuffix, + String paramPrefix) { String pName = getParamName(values, paramPrefix); String paramName = ":"+pName; if (filter.getFilterValue() == null) {
common/src/test/java/org/cloudfoundry/identity/uaa/rest/jdbc/SimpleSearchQueryConverterTests.java+94 −0 added@@ -0,0 +1,94 @@ +/* + * **************************************************************************** + * Cloud Foundry + * Copyright (c) [2009-2017] Pivotal Software, Inc. All Rights Reserved. + * + * This product is licensed to you under the Apache License, Version 2.0 (the "License"). + * You may not use this product except in compliance with the License. + * + * This product includes a number of subcomponents with + * separate copyright notices and license terms. Your use of these + * subcomponents is subject to the terms and conditions of the + * subcomponent's license, as noted in the LICENSE file. + * **************************************************************************** + */ + +package org.cloudfoundry.identity.uaa.rest.jdbc; + +import com.unboundid.scim.sdk.InvalidResourceException; +import com.unboundid.scim.sdk.SCIMFilter; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.ExpectedException; + +import java.util.concurrent.atomic.AtomicInteger; + +import static java.util.Collections.emptyList; +import static java.util.Optional.ofNullable; +import static org.hamcrest.Matchers.containsString; +import static org.hamcrest.Matchers.startsWith; + +public class SimpleSearchQueryConverterTests { + + SimpleSearchQueryConverter converter; + + String query = "user_id eq \"7e2345e8-8bbf-4eaa-9bc3-ae1ba610f890\"" + + "and " + + "client_id eq \"app\"" + + "and " + + "meta.lastmodified gt \"some-value\"" + + "and " + + "(an/**/invalid/**/attribute/**/and/**/1" + //invalid attribute name + " pr " + //operator (present) + "and " + + "1 eq 1)" + //invalid attribute name 1 + " and " + + "\"1\" eq \"1\""; + + String validQuery = "user_id eq \"7e2345e8-8bbf-4eaa-9bc3-ae1ba610f890\"" + + "and " + + "client_id eq \"app\"" + + "and " + + "meta.lastmodified gt \"some-value\"" + + "and " + + "meta.created pr"; + + @Rule + public ExpectedException exception = ExpectedException.none(); + + @Before + public void setup() { + converter = new SimpleSearchQueryConverter(); + } + + @Test + public void test_query() throws Exception { + exception.expect(InvalidResourceException.class); + exception.expectMessage(startsWith("Invalid filter attributes")); + exception.expectMessage(containsString("an/**/invalid/**/attribute/**/and/**/1")); + exception.expectMessage(containsString("1")); + exception.expectMessage(containsString("\"1\"")); + SCIMFilter filter = converter.scimFilter(query); + } + + @Test + public void print_query() throws Exception { + SCIMFilter filter = converter.scimFilter(validQuery); + printFilterAttributes(filter, new AtomicInteger(0)); + } + + public void printFilterAttributes(SCIMFilter filter, AtomicInteger pos) { + if (filter.getFilterAttribute() != null) { + String name = filter.getFilterAttribute().getAttributeName(); + if (filter.getFilterAttribute().getSubAttributeName() != null) { + name = name + "." + filter.getFilterAttribute().getSubAttributeName(); + } + System.out.println((pos.incrementAndGet()) + ". Attribute name:" + name); + } + for (SCIMFilter subfilter : ofNullable(filter.getFilterComponents()).orElse(emptyList())) { + printFilterAttributes(subfilter, pos); + } + } + +} \ No newline at end of file
scim/src/test/java/org/cloudfoundry/identity/uaa/scim/jdbc/ScimSearchQueryConverterTests.java+4 −4 modified@@ -58,8 +58,8 @@ public void canConvertValidFilters() throws Exception { validate(filterProcessor.convert("username pr and emails.value co \".com\"", null, false),"(username IS NOT NULL AND LOWER(email) LIKE LOWER(:__value_0))", 1); validate(filterProcessor.convert("username eq \"joe\" or emails.value co \".com\"", null, false),"(LOWER(username) = LOWER(:__value_0) OR LOWER(email) LIKE LOWER(:__value_1))", 2); validate(filterProcessor.convert("active eq true", null, false),"active = :__value_0", 1, Boolean.class); - validate(filterProcessor.convert("test eq 1000000.45", null, false),"test = :__value_0", 1, Double.class); - validate(filterProcessor.convert("test eq 1000000", null, false),"test = :__value_0", 1, Double.class); + validate(filterProcessor.convert("Version eq 1000000.45", null, false),"Version = :__value_0", 1, Double.class); + validate(filterProcessor.convert("meta.VerSion eq 1000000", null, false),"VerSion = :__value_0", 1, Double.class); } @Test @@ -84,8 +84,8 @@ public void caseInsensitiveDbDoesNotInjectLower() throws Exception { validate(filterProcessor.convert("username pr and emails.value co \".com\"", null, false),"(username IS NOT NULL AND email LIKE :__value_0)", 1); validate(filterProcessor.convert("username eq \"joe\" or emails.value co \".com\"", null, false),"(username = :__value_0 OR email LIKE :__value_1)", 2); validate(filterProcessor.convert("active eq true", null, false),"active = :__value_0", 1, Boolean.class); - validate(filterProcessor.convert("test eq 1000000.45", null, false),"test = :__value_0", 1, Double.class); - validate(filterProcessor.convert("test eq 1000000", null, false),"test = :__value_0", 1, Double.class); + validate(filterProcessor.convert("Version eq 1000000.45", null, false),"Version = :__value_0", 1, Double.class); + validate(filterProcessor.convert("meta.VerSion eq 1000000", null, false),"VerSion = :__value_0", 1, Double.class); } @Test
server/src/test/java/org/cloudfoundry/identity/uaa/resources/jdbc/SimpleSearchQueryConverterTests.java+95 −0 added@@ -0,0 +1,95 @@ +/* + * **************************************************************************** + * Cloud Foundry + * Copyright (c) [2009-2017] Pivotal Software, Inc. All Rights Reserved. + * + * This product is licensed to you under the Apache License, Version 2.0 (the "License"). + * You may not use this product except in compliance with the License. + * + * This product includes a number of subcomponents with + * separate copyright notices and license terms. Your use of these + * subcomponents is subject to the terms and conditions of the + * subcomponent's license, as noted in the LICENSE file. + * **************************************************************************** + */ + +package org.cloudfoundry.identity.uaa.resources.jdbc; + +import com.unboundid.scim.sdk.InvalidResourceException; +import com.unboundid.scim.sdk.SCIMFilter; +import org.cloudfoundry.identity.uaa.scim.jdbc.ScimSearchQueryConverter; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.ExpectedException; + +import java.util.concurrent.atomic.AtomicInteger; + +import static java.util.Collections.emptyList; +import static java.util.Optional.ofNullable; +import static org.hamcrest.Matchers.containsString; +import static org.hamcrest.Matchers.startsWith; + +public class SimpleSearchQueryConverterTests { + + SimpleSearchQueryConverter converter; + + String query = "user_id eq \"7e2345e8-8bbf-4eaa-9bc3-ae1ba610f890\"" + + "and " + + "client_id eq \"app\"" + + "and " + + "meta.lastmodified gt \"some-value\"" + + "and " + + "(an/**/invalid/**/attribute/**/and/**/1" + //invalid attribute name + " pr " + //operator (present) + "and " + + "1 eq 1)" + //invalid attribute name 1 + " and " + + "\"1\" eq \"1\""; + + String validQuery = "user_id eq \"7e2345e8-8bbf-4eaa-9bc3-ae1ba610f890\"" + + "and " + + "client_id eq \"app\"" + + "and " + + "meta.lastmodified gt \"some-value\"" + + "and " + + "meta.created pr"; + + @Rule + public ExpectedException exception = ExpectedException.none(); + + @Before + public void setup() { + converter = new ScimSearchQueryConverter(); + } + + @Test + public void test_query() throws Exception { + exception.expect(InvalidResourceException.class); + exception.expectMessage(startsWith("Invalid filter attributes")); + exception.expectMessage(containsString("an/**/invalid/**/attribute/**/and/**/1")); + exception.expectMessage(containsString("1")); + exception.expectMessage(containsString("\"1\"")); + SCIMFilter filter = converter.scimFilter(query); + } + + @Test + public void print_query() throws Exception { + SCIMFilter filter = converter.scimFilter(validQuery); + printFilterAttributes(filter, new AtomicInteger(0)); + } + + public void printFilterAttributes(SCIMFilter filter, AtomicInteger pos) { + if (filter.getFilterAttribute() != null) { + String name = filter.getFilterAttribute().getAttributeName(); + if (filter.getFilterAttribute().getSubAttributeName() != null) { + name = name + "." + filter.getFilterAttribute().getSubAttributeName(); + } + System.out.println((pos.incrementAndGet()) + ". Attribute name:" + name); + } + for (SCIMFilter subfilter : ofNullable(filter.getFilterComponents()).orElse(emptyList())) { + printFilterAttributes(subfilter, pos); + } + } + +} \ No newline at end of file
5dc5ca9176edbetter parsing of attributes
3 files changed · +201 −13
server/src/main/java/org/cloudfoundry/identity/uaa/resources/jdbc/SimpleSearchQueryConverter.java+102 −9 modified@@ -13,13 +13,7 @@ package org.cloudfoundry.identity.uaa.resources.jdbc; -import java.text.DateFormat; -import java.text.ParseException; -import java.text.SimpleDateFormat; -import java.util.Collections; -import java.util.HashMap; -import java.util.Map; - +import com.unboundid.scim.sdk.InvalidResourceException; import com.unboundid.scim.sdk.SCIMException; import com.unboundid.scim.sdk.SCIMFilter; import org.apache.commons.logging.Log; @@ -29,15 +23,85 @@ import org.springframework.security.oauth2.common.util.RandomValueStringGenerator; import org.springframework.util.StringUtils; +import java.text.DateFormat; +import java.text.ParseException; +import java.text.SimpleDateFormat; +import java.util.Arrays; +import java.util.Collections; +import java.util.HashMap; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; + +import static java.util.Collections.emptyList; +import static java.util.Optional.ofNullable; import static org.cloudfoundry.identity.uaa.resources.jdbc.SearchQueryConverter.ProcessedFilter.ORDER_BY; public class SimpleSearchQueryConverter implements SearchQueryConverter { + //LOWER + public static final List<String> VALID_ATTRIBUTE_NAMES = Collections.unmodifiableList( + Arrays.asList( + "id", + "created", + "lastmodified", + "version", + "username", + "password", + "email", + "givenname", + "familyname", + "name.familyname", + "name.givenname", + "active", + "phonenumber", + "verified", + "origin", + "identity_zone_id", + "passwd_lastmodified", + "passwd_change_required", + "last_logon_success_time", + "previous_logon_success_time", + "displayname", + "scope", + "group_id", + "member_id", + "member_type", + "description", + "client_id", + "authorized_grant_types", + "web_server_redirect_uri", + "redirect_uri", + "access_token_validity", + "refresh_token_validity", + "autoapprove", + "show_on_home_page", + "created_by", + "required_user_groups", + "user_id", + "meta.lastmodified", + "meta.created", + "meta.location", + "meta.resourcetype", + "meta.version", + "emails.value", + "groups.display", + "phonenumbers.value", + "gm.external_group", + "gm.origin", + "g.displayname", + "g.id" + ) + ); + private static Log logger = LogFactory.getLog(SimpleSearchQueryConverter.class); private AttributeNameMapper mapper = new SimpleAttributeNameMapper(Collections.<String, String> emptyMap()); private boolean dbCaseInsensitive = false; + public SimpleSearchQueryConverter() { + } + public boolean isDbCaseInsensitive() { return dbCaseInsensitive; } @@ -92,7 +156,7 @@ private String getWhereClause(String filter, String sortBy, boolean ascending, M } } - private SCIMFilter scimFilter(String filter) throws SCIMException { + protected SCIMFilter scimFilter(String filter) throws SCIMException { SCIMFilter scimFilter; try { scimFilter = SCIMFilter.parse(filter); @@ -101,9 +165,33 @@ private SCIMFilter scimFilter(String filter) throws SCIMException { filter = filter.replaceAll("'","\""); scimFilter = SCIMFilter.parse(filter); } + validateFilterAttributes(scimFilter); return scimFilter; } + private void validateFilterAttributes(SCIMFilter filter) throws SCIMException { + List<String> invalidAttributes = new LinkedList<>(); + validateFilterAttributes(filter, invalidAttributes); + if (!invalidAttributes.isEmpty()) { + throw new InvalidResourceException("Invalid filter attributes:"+StringUtils.collectionToCommaDelimitedString(invalidAttributes)); + } + } + + private void validateFilterAttributes(SCIMFilter filter, List<String> invalidAttribues) { + if (filter.getFilterAttribute()!=null && filter.getFilterAttribute().getAttributeName()!=null) { + String name = filter.getFilterAttribute().getAttributeName(); + if (filter.getFilterAttribute().getSubAttributeName()!=null) { + name = name + "." + filter.getFilterAttribute().getSubAttributeName(); + } + if (!VALID_ATTRIBUTE_NAMES.contains(name.toLowerCase())) { + invalidAttribues.add(name); + } + } + for (SCIMFilter subfilter : ofNullable(filter.getFilterComponents()).orElse(emptyList())) { + validateFilterAttributes(subfilter, invalidAttribues); + } + } + private String createFilter(SCIMFilter filter, Map<String,Object> values, AttributeNameMapper mapper, String paramPrefix) { switch (filter.getFilterType()) { case AND: @@ -130,7 +218,12 @@ private String createFilter(SCIMFilter filter, Map<String,Object> values, Attrib return null; } - protected String comparisonClause(SCIMFilter filter, String comparator, Map<String, Object> values, String valuePrefix, String valueSuffix, String paramPrefix) { + protected String comparisonClause(SCIMFilter filter, + String comparator, + Map<String, Object> values, + String valuePrefix, + String valueSuffix, + String paramPrefix) { String pName = getParamName(values, paramPrefix); String paramName = ":"+pName; if (filter.getFilterValue() == null) {
server/src/test/java/org/cloudfoundry/identity/uaa/resources/jdbc/SimpleSearchQueryConverterTests.java+95 −0 added@@ -0,0 +1,95 @@ +/* + * **************************************************************************** + * Cloud Foundry + * Copyright (c) [2009-2017] Pivotal Software, Inc. All Rights Reserved. + * + * This product is licensed to you under the Apache License, Version 2.0 (the "License"). + * You may not use this product except in compliance with the License. + * + * This product includes a number of subcomponents with + * separate copyright notices and license terms. Your use of these + * subcomponents is subject to the terms and conditions of the + * subcomponent's license, as noted in the LICENSE file. + * **************************************************************************** + */ + +package org.cloudfoundry.identity.uaa.resources.jdbc; + +import com.unboundid.scim.sdk.InvalidResourceException; +import com.unboundid.scim.sdk.SCIMFilter; +import org.cloudfoundry.identity.uaa.scim.jdbc.ScimSearchQueryConverter; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.ExpectedException; + +import java.util.concurrent.atomic.AtomicInteger; + +import static java.util.Collections.emptyList; +import static java.util.Optional.ofNullable; +import static org.hamcrest.Matchers.containsString; +import static org.hamcrest.Matchers.startsWith; + +public class SimpleSearchQueryConverterTests { + + SimpleSearchQueryConverter converter; + + String query = "user_id eq \"7e2345e8-8bbf-4eaa-9bc3-ae1ba610f890\"" + + "and " + + "client_id eq \"app\"" + + "and " + + "meta.lastmodified gt \"some-value\"" + + "and " + + "(an/**/invalid/**/attribute/**/and/**/1" + //invalid attribute name + " pr " + //operator (present) + "and " + + "1 eq 1)" + //invalid attribute name 1 + " and " + + "\"1\" eq \"1\""; + + String validQuery = "user_id eq \"7e2345e8-8bbf-4eaa-9bc3-ae1ba610f890\"" + + "and " + + "client_id eq \"app\"" + + "and " + + "meta.lastmodified gt \"some-value\"" + + "and " + + "meta.created pr"; + + @Rule + public ExpectedException exception = ExpectedException.none(); + + @Before + public void setup() { + converter = new ScimSearchQueryConverter(); + } + + @Test + public void test_query() throws Exception { + exception.expect(InvalidResourceException.class); + exception.expectMessage(startsWith("Invalid filter attributes")); + exception.expectMessage(containsString("an/**/invalid/**/attribute/**/and/**/1")); + exception.expectMessage(containsString("1")); + exception.expectMessage(containsString("\"1\"")); + SCIMFilter filter = converter.scimFilter(query); + } + + @Test + public void print_query() throws Exception { + SCIMFilter filter = converter.scimFilter(validQuery); + printFilterAttributes(filter, new AtomicInteger(0)); + } + + public void printFilterAttributes(SCIMFilter filter, AtomicInteger pos) { + if (filter.getFilterAttribute() != null) { + String name = filter.getFilterAttribute().getAttributeName(); + if (filter.getFilterAttribute().getSubAttributeName() != null) { + name = name + "." + filter.getFilterAttribute().getSubAttributeName(); + } + System.out.println((pos.incrementAndGet()) + ". Attribute name:" + name); + } + for (SCIMFilter subfilter : ofNullable(filter.getFilterComponents()).orElse(emptyList())) { + printFilterAttributes(subfilter, pos); + } + } + +} \ No newline at end of file
server/src/test/java/org/cloudfoundry/identity/uaa/scim/jdbc/ScimSearchQueryConverterTests.java+4 −4 modified@@ -58,8 +58,8 @@ public void canConvertValidFilters() throws Exception { validate(filterProcessor.convert("username pr and emails.value co \".com\"", null, false),"(username IS NOT NULL AND LOWER(email) LIKE LOWER(:__value_0))", 1); validate(filterProcessor.convert("username eq \"joe\" or emails.value co \".com\"", null, false),"(LOWER(username) = LOWER(:__value_0) OR LOWER(email) LIKE LOWER(:__value_1))", 2); validate(filterProcessor.convert("active eq true", null, false),"active = :__value_0", 1, Boolean.class); - validate(filterProcessor.convert("test eq 1000000.45", null, false),"test = :__value_0", 1, Double.class); - validate(filterProcessor.convert("test eq 1000000", null, false),"test = :__value_0", 1, Double.class); + validate(filterProcessor.convert("Version eq 1000000.45", null, false),"Version = :__value_0", 1, Double.class); + validate(filterProcessor.convert("meta.VerSion eq 1000000", null, false),"VerSion = :__value_0", 1, Double.class); } @Test @@ -84,8 +84,8 @@ public void caseInsensitiveDbDoesNotInjectLower() throws Exception { validate(filterProcessor.convert("username pr and emails.value co \".com\"", null, false),"(username IS NOT NULL AND email LIKE :__value_0)", 1); validate(filterProcessor.convert("username eq \"joe\" or emails.value co \".com\"", null, false),"(username = :__value_0 OR email LIKE :__value_1)", 2); validate(filterProcessor.convert("active eq true", null, false),"active = :__value_0", 1, Boolean.class); - validate(filterProcessor.convert("test eq 1000000.45", null, false),"test = :__value_0", 1, Double.class); - validate(filterProcessor.convert("test eq 1000000", null, false),"test = :__value_0", 1, Double.class); + validate(filterProcessor.convert("Version eq 1000000.45", null, false),"Version = :__value_0", 1, Double.class); + validate(filterProcessor.convert("meta.VerSion eq 1000000", null, false),"VerSion = :__value_0", 1, Double.class); } @Test
74b9b270787abetter parsing of attributes
3 files changed · +201 −13
server/src/main/java/org/cloudfoundry/identity/uaa/resources/jdbc/SimpleSearchQueryConverter.java+102 −9 modified@@ -13,13 +13,7 @@ package org.cloudfoundry.identity.uaa.resources.jdbc; -import java.text.DateFormat; -import java.text.ParseException; -import java.text.SimpleDateFormat; -import java.util.Collections; -import java.util.HashMap; -import java.util.Map; - +import com.unboundid.scim.sdk.InvalidResourceException; import com.unboundid.scim.sdk.SCIMException; import com.unboundid.scim.sdk.SCIMFilter; import org.apache.commons.logging.Log; @@ -29,15 +23,85 @@ import org.springframework.security.oauth2.common.util.RandomValueStringGenerator; import org.springframework.util.StringUtils; +import java.text.DateFormat; +import java.text.ParseException; +import java.text.SimpleDateFormat; +import java.util.Arrays; +import java.util.Collections; +import java.util.HashMap; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; + +import static java.util.Collections.emptyList; +import static java.util.Optional.ofNullable; import static org.cloudfoundry.identity.uaa.resources.jdbc.SearchQueryConverter.ProcessedFilter.ORDER_BY; public class SimpleSearchQueryConverter implements SearchQueryConverter { + //LOWER + public static final List<String> VALID_ATTRIBUTE_NAMES = Collections.unmodifiableList( + Arrays.asList( + "id", + "created", + "lastmodified", + "version", + "username", + "password", + "email", + "givenname", + "familyname", + "name.familyname", + "name.givenname", + "active", + "phonenumber", + "verified", + "origin", + "identity_zone_id", + "passwd_lastmodified", + "passwd_change_required", + "last_logon_success_time", + "previous_logon_success_time", + "displayname", + "scope", + "group_id", + "member_id", + "member_type", + "description", + "client_id", + "authorized_grant_types", + "web_server_redirect_uri", + "redirect_uri", + "access_token_validity", + "refresh_token_validity", + "autoapprove", + "show_on_home_page", + "created_by", + "required_user_groups", + "user_id", + "meta.lastmodified", + "meta.created", + "meta.location", + "meta.resourcetype", + "meta.version", + "emails.value", + "groups.display", + "phonenumbers.value", + "gm.external_group", + "gm.origin", + "g.displayname", + "g.id" + ) + ); + private static Log logger = LogFactory.getLog(SimpleSearchQueryConverter.class); private AttributeNameMapper mapper = new SimpleAttributeNameMapper(Collections.<String, String> emptyMap()); private boolean dbCaseInsensitive = false; + public SimpleSearchQueryConverter() { + } + public boolean isDbCaseInsensitive() { return dbCaseInsensitive; } @@ -92,7 +156,7 @@ private String getWhereClause(String filter, String sortBy, boolean ascending, M } } - private SCIMFilter scimFilter(String filter) throws SCIMException { + protected SCIMFilter scimFilter(String filter) throws SCIMException { SCIMFilter scimFilter; try { scimFilter = SCIMFilter.parse(filter); @@ -101,9 +165,33 @@ private SCIMFilter scimFilter(String filter) throws SCIMException { filter = filter.replaceAll("'","\""); scimFilter = SCIMFilter.parse(filter); } + validateFilterAttributes(scimFilter); return scimFilter; } + private void validateFilterAttributes(SCIMFilter filter) throws SCIMException { + List<String> invalidAttributes = new LinkedList<>(); + validateFilterAttributes(filter, invalidAttributes); + if (!invalidAttributes.isEmpty()) { + throw new InvalidResourceException("Invalid filter attributes:"+StringUtils.collectionToCommaDelimitedString(invalidAttributes)); + } + } + + private void validateFilterAttributes(SCIMFilter filter, List<String> invalidAttribues) { + if (filter.getFilterAttribute()!=null && filter.getFilterAttribute().getAttributeName()!=null) { + String name = filter.getFilterAttribute().getAttributeName(); + if (filter.getFilterAttribute().getSubAttributeName()!=null) { + name = name + "." + filter.getFilterAttribute().getSubAttributeName(); + } + if (!VALID_ATTRIBUTE_NAMES.contains(name.toLowerCase())) { + invalidAttribues.add(name); + } + } + for (SCIMFilter subfilter : ofNullable(filter.getFilterComponents()).orElse(emptyList())) { + validateFilterAttributes(subfilter, invalidAttribues); + } + } + private String createFilter(SCIMFilter filter, Map<String,Object> values, AttributeNameMapper mapper, String paramPrefix) { switch (filter.getFilterType()) { case AND: @@ -130,7 +218,12 @@ private String createFilter(SCIMFilter filter, Map<String,Object> values, Attrib return null; } - protected String comparisonClause(SCIMFilter filter, String comparator, Map<String, Object> values, String valuePrefix, String valueSuffix, String paramPrefix) { + protected String comparisonClause(SCIMFilter filter, + String comparator, + Map<String, Object> values, + String valuePrefix, + String valueSuffix, + String paramPrefix) { String pName = getParamName(values, paramPrefix); String paramName = ":"+pName; if (filter.getFilterValue() == null) {
server/src/test/java/org/cloudfoundry/identity/uaa/resources/jdbc/SimpleSearchQueryConverterTests.java+95 −0 added@@ -0,0 +1,95 @@ +/* + * **************************************************************************** + * Cloud Foundry + * Copyright (c) [2009-2017] Pivotal Software, Inc. All Rights Reserved. + * + * This product is licensed to you under the Apache License, Version 2.0 (the "License"). + * You may not use this product except in compliance with the License. + * + * This product includes a number of subcomponents with + * separate copyright notices and license terms. Your use of these + * subcomponents is subject to the terms and conditions of the + * subcomponent's license, as noted in the LICENSE file. + * **************************************************************************** + */ + +package org.cloudfoundry.identity.uaa.resources.jdbc; + +import com.unboundid.scim.sdk.InvalidResourceException; +import com.unboundid.scim.sdk.SCIMFilter; +import org.cloudfoundry.identity.uaa.scim.jdbc.ScimSearchQueryConverter; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.ExpectedException; + +import java.util.concurrent.atomic.AtomicInteger; + +import static java.util.Collections.emptyList; +import static java.util.Optional.ofNullable; +import static org.hamcrest.Matchers.containsString; +import static org.hamcrest.Matchers.startsWith; + +public class SimpleSearchQueryConverterTests { + + SimpleSearchQueryConverter converter; + + String query = "user_id eq \"7e2345e8-8bbf-4eaa-9bc3-ae1ba610f890\"" + + "and " + + "client_id eq \"app\"" + + "and " + + "meta.lastmodified gt \"some-value\"" + + "and " + + "(an/**/invalid/**/attribute/**/and/**/1" + //invalid attribute name + " pr " + //operator (present) + "and " + + "1 eq 1)" + //invalid attribute name 1 + " and " + + "\"1\" eq \"1\""; + + String validQuery = "user_id eq \"7e2345e8-8bbf-4eaa-9bc3-ae1ba610f890\"" + + "and " + + "client_id eq \"app\"" + + "and " + + "meta.lastmodified gt \"some-value\"" + + "and " + + "meta.created pr"; + + @Rule + public ExpectedException exception = ExpectedException.none(); + + @Before + public void setup() { + converter = new ScimSearchQueryConverter(); + } + + @Test + public void test_query() throws Exception { + exception.expect(InvalidResourceException.class); + exception.expectMessage(startsWith("Invalid filter attributes")); + exception.expectMessage(containsString("an/**/invalid/**/attribute/**/and/**/1")); + exception.expectMessage(containsString("1")); + exception.expectMessage(containsString("\"1\"")); + SCIMFilter filter = converter.scimFilter(query); + } + + @Test + public void print_query() throws Exception { + SCIMFilter filter = converter.scimFilter(validQuery); + printFilterAttributes(filter, new AtomicInteger(0)); + } + + public void printFilterAttributes(SCIMFilter filter, AtomicInteger pos) { + if (filter.getFilterAttribute() != null) { + String name = filter.getFilterAttribute().getAttributeName(); + if (filter.getFilterAttribute().getSubAttributeName() != null) { + name = name + "." + filter.getFilterAttribute().getSubAttributeName(); + } + System.out.println((pos.incrementAndGet()) + ". Attribute name:" + name); + } + for (SCIMFilter subfilter : ofNullable(filter.getFilterComponents()).orElse(emptyList())) { + printFilterAttributes(subfilter, pos); + } + } + +} \ No newline at end of file
server/src/test/java/org/cloudfoundry/identity/uaa/scim/jdbc/ScimSearchQueryConverterTests.java+4 −4 modified@@ -58,8 +58,8 @@ public void canConvertValidFilters() throws Exception { validate(filterProcessor.convert("username pr and emails.value co \".com\"", null, false),"(username IS NOT NULL AND LOWER(email) LIKE LOWER(:__value_0))", 1); validate(filterProcessor.convert("username eq \"joe\" or emails.value co \".com\"", null, false),"(LOWER(username) = LOWER(:__value_0) OR LOWER(email) LIKE LOWER(:__value_1))", 2); validate(filterProcessor.convert("active eq true", null, false),"active = :__value_0", 1, Boolean.class); - validate(filterProcessor.convert("test eq 1000000.45", null, false),"test = :__value_0", 1, Double.class); - validate(filterProcessor.convert("test eq 1000000", null, false),"test = :__value_0", 1, Double.class); + validate(filterProcessor.convert("Version eq 1000000.45", null, false),"Version = :__value_0", 1, Double.class); + validate(filterProcessor.convert("meta.VerSion eq 1000000", null, false),"VerSion = :__value_0", 1, Double.class); } @Test @@ -84,8 +84,8 @@ public void caseInsensitiveDbDoesNotInjectLower() throws Exception { validate(filterProcessor.convert("username pr and emails.value co \".com\"", null, false),"(username IS NOT NULL AND email LIKE :__value_0)", 1); validate(filterProcessor.convert("username eq \"joe\" or emails.value co \".com\"", null, false),"(username = :__value_0 OR email LIKE :__value_1)", 2); validate(filterProcessor.convert("active eq true", null, false),"active = :__value_0", 1, Boolean.class); - validate(filterProcessor.convert("test eq 1000000.45", null, false),"test = :__value_0", 1, Double.class); - validate(filterProcessor.convert("test eq 1000000", null, false),"test = :__value_0", 1, Double.class); + validate(filterProcessor.convert("Version eq 1000000.45", null, false),"Version = :__value_0", 1, Double.class); + validate(filterProcessor.convert("meta.VerSion eq 1000000", null, false),"VerSion = :__value_0", 1, Double.class); } @Test
b6d6526cb891better parsing of attributes
3 files changed · +201 −13
server/src/main/java/org/cloudfoundry/identity/uaa/resources/jdbc/SimpleSearchQueryConverter.java+102 −9 modified@@ -13,13 +13,7 @@ package org.cloudfoundry.identity.uaa.resources.jdbc; -import java.text.DateFormat; -import java.text.ParseException; -import java.text.SimpleDateFormat; -import java.util.Collections; -import java.util.HashMap; -import java.util.Map; - +import com.unboundid.scim.sdk.InvalidResourceException; import com.unboundid.scim.sdk.SCIMException; import com.unboundid.scim.sdk.SCIMFilter; import org.apache.commons.logging.Log; @@ -29,15 +23,85 @@ import org.springframework.security.oauth2.common.util.RandomValueStringGenerator; import org.springframework.util.StringUtils; +import java.text.DateFormat; +import java.text.ParseException; +import java.text.SimpleDateFormat; +import java.util.Arrays; +import java.util.Collections; +import java.util.HashMap; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; + +import static java.util.Collections.emptyList; +import static java.util.Optional.ofNullable; import static org.cloudfoundry.identity.uaa.resources.jdbc.SearchQueryConverter.ProcessedFilter.ORDER_BY; public class SimpleSearchQueryConverter implements SearchQueryConverter { + //LOWER + public static final List<String> VALID_ATTRIBUTE_NAMES = Collections.unmodifiableList( + Arrays.asList( + "id", + "created", + "lastmodified", + "version", + "username", + "password", + "email", + "givenname", + "familyname", + "name.familyname", + "name.givenname", + "active", + "phonenumber", + "verified", + "origin", + "identity_zone_id", + "passwd_lastmodified", + "passwd_change_required", + "last_logon_success_time", + "previous_logon_success_time", + "displayname", + "scope", + "group_id", + "member_id", + "member_type", + "description", + "client_id", + "authorized_grant_types", + "web_server_redirect_uri", + "redirect_uri", + "access_token_validity", + "refresh_token_validity", + "autoapprove", + "show_on_home_page", + "created_by", + "required_user_groups", + "user_id", + "meta.lastmodified", + "meta.created", + "meta.location", + "meta.resourcetype", + "meta.version", + "emails.value", + "groups.display", + "phonenumbers.value", + "gm.external_group", + "gm.origin", + "g.displayname", + "g.id" + ) + ); + private static Log logger = LogFactory.getLog(SimpleSearchQueryConverter.class); private AttributeNameMapper mapper = new SimpleAttributeNameMapper(Collections.<String, String> emptyMap()); private boolean dbCaseInsensitive = false; + public SimpleSearchQueryConverter() { + } + public boolean isDbCaseInsensitive() { return dbCaseInsensitive; } @@ -92,7 +156,7 @@ private String getWhereClause(String filter, String sortBy, boolean ascending, M } } - private SCIMFilter scimFilter(String filter) throws SCIMException { + protected SCIMFilter scimFilter(String filter) throws SCIMException { SCIMFilter scimFilter; try { scimFilter = SCIMFilter.parse(filter); @@ -101,9 +165,33 @@ private SCIMFilter scimFilter(String filter) throws SCIMException { filter = filter.replaceAll("'","\""); scimFilter = SCIMFilter.parse(filter); } + validateFilterAttributes(scimFilter); return scimFilter; } + private void validateFilterAttributes(SCIMFilter filter) throws SCIMException { + List<String> invalidAttributes = new LinkedList<>(); + validateFilterAttributes(filter, invalidAttributes); + if (!invalidAttributes.isEmpty()) { + throw new InvalidResourceException("Invalid filter attributes:"+StringUtils.collectionToCommaDelimitedString(invalidAttributes)); + } + } + + private void validateFilterAttributes(SCIMFilter filter, List<String> invalidAttribues) { + if (filter.getFilterAttribute()!=null && filter.getFilterAttribute().getAttributeName()!=null) { + String name = filter.getFilterAttribute().getAttributeName(); + if (filter.getFilterAttribute().getSubAttributeName()!=null) { + name = name + "." + filter.getFilterAttribute().getSubAttributeName(); + } + if (!VALID_ATTRIBUTE_NAMES.contains(name.toLowerCase())) { + invalidAttribues.add(name); + } + } + for (SCIMFilter subfilter : ofNullable(filter.getFilterComponents()).orElse(emptyList())) { + validateFilterAttributes(subfilter, invalidAttribues); + } + } + private String createFilter(SCIMFilter filter, Map<String,Object> values, AttributeNameMapper mapper, String paramPrefix) { switch (filter.getFilterType()) { case AND: @@ -130,7 +218,12 @@ private String createFilter(SCIMFilter filter, Map<String,Object> values, Attrib return null; } - protected String comparisonClause(SCIMFilter filter, String comparator, Map<String, Object> values, String valuePrefix, String valueSuffix, String paramPrefix) { + protected String comparisonClause(SCIMFilter filter, + String comparator, + Map<String, Object> values, + String valuePrefix, + String valueSuffix, + String paramPrefix) { String pName = getParamName(values, paramPrefix); String paramName = ":"+pName; if (filter.getFilterValue() == null) {
server/src/test/java/org/cloudfoundry/identity/uaa/resources/jdbc/SimpleSearchQueryConverterTests.java+95 −0 added@@ -0,0 +1,95 @@ +/* + * **************************************************************************** + * Cloud Foundry + * Copyright (c) [2009-2017] Pivotal Software, Inc. All Rights Reserved. + * + * This product is licensed to you under the Apache License, Version 2.0 (the "License"). + * You may not use this product except in compliance with the License. + * + * This product includes a number of subcomponents with + * separate copyright notices and license terms. Your use of these + * subcomponents is subject to the terms and conditions of the + * subcomponent's license, as noted in the LICENSE file. + * **************************************************************************** + */ + +package org.cloudfoundry.identity.uaa.resources.jdbc; + +import com.unboundid.scim.sdk.InvalidResourceException; +import com.unboundid.scim.sdk.SCIMFilter; +import org.cloudfoundry.identity.uaa.scim.jdbc.ScimSearchQueryConverter; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.ExpectedException; + +import java.util.concurrent.atomic.AtomicInteger; + +import static java.util.Collections.emptyList; +import static java.util.Optional.ofNullable; +import static org.hamcrest.Matchers.containsString; +import static org.hamcrest.Matchers.startsWith; + +public class SimpleSearchQueryConverterTests { + + SimpleSearchQueryConverter converter; + + String query = "user_id eq \"7e2345e8-8bbf-4eaa-9bc3-ae1ba610f890\"" + + "and " + + "client_id eq \"app\"" + + "and " + + "meta.lastmodified gt \"some-value\"" + + "and " + + "(an/**/invalid/**/attribute/**/and/**/1" + //invalid attribute name + " pr " + //operator (present) + "and " + + "1 eq 1)" + //invalid attribute name 1 + " and " + + "\"1\" eq \"1\""; + + String validQuery = "user_id eq \"7e2345e8-8bbf-4eaa-9bc3-ae1ba610f890\"" + + "and " + + "client_id eq \"app\"" + + "and " + + "meta.lastmodified gt \"some-value\"" + + "and " + + "meta.created pr"; + + @Rule + public ExpectedException exception = ExpectedException.none(); + + @Before + public void setup() { + converter = new ScimSearchQueryConverter(); + } + + @Test + public void test_query() throws Exception { + exception.expect(InvalidResourceException.class); + exception.expectMessage(startsWith("Invalid filter attributes")); + exception.expectMessage(containsString("an/**/invalid/**/attribute/**/and/**/1")); + exception.expectMessage(containsString("1")); + exception.expectMessage(containsString("\"1\"")); + SCIMFilter filter = converter.scimFilter(query); + } + + @Test + public void print_query() throws Exception { + SCIMFilter filter = converter.scimFilter(validQuery); + printFilterAttributes(filter, new AtomicInteger(0)); + } + + public void printFilterAttributes(SCIMFilter filter, AtomicInteger pos) { + if (filter.getFilterAttribute() != null) { + String name = filter.getFilterAttribute().getAttributeName(); + if (filter.getFilterAttribute().getSubAttributeName() != null) { + name = name + "." + filter.getFilterAttribute().getSubAttributeName(); + } + System.out.println((pos.incrementAndGet()) + ". Attribute name:" + name); + } + for (SCIMFilter subfilter : ofNullable(filter.getFilterComponents()).orElse(emptyList())) { + printFilterAttributes(subfilter, pos); + } + } + +} \ No newline at end of file
server/src/test/java/org/cloudfoundry/identity/uaa/scim/jdbc/ScimSearchQueryConverterTests.java+4 −4 modified@@ -58,8 +58,8 @@ public void canConvertValidFilters() throws Exception { validate(filterProcessor.convert("username pr and emails.value co \".com\"", null, false),"(username IS NOT NULL AND LOWER(email) LIKE LOWER(:__value_0))", 1); validate(filterProcessor.convert("username eq \"joe\" or emails.value co \".com\"", null, false),"(LOWER(username) = LOWER(:__value_0) OR LOWER(email) LIKE LOWER(:__value_1))", 2); validate(filterProcessor.convert("active eq true", null, false),"active = :__value_0", 1, Boolean.class); - validate(filterProcessor.convert("test eq 1000000.45", null, false),"test = :__value_0", 1, Double.class); - validate(filterProcessor.convert("test eq 1000000", null, false),"test = :__value_0", 1, Double.class); + validate(filterProcessor.convert("Version eq 1000000.45", null, false),"Version = :__value_0", 1, Double.class); + validate(filterProcessor.convert("meta.VerSion eq 1000000", null, false),"VerSion = :__value_0", 1, Double.class); } @Test @@ -84,8 +84,8 @@ public void caseInsensitiveDbDoesNotInjectLower() throws Exception { validate(filterProcessor.convert("username pr and emails.value co \".com\"", null, false),"(username IS NOT NULL AND email LIKE :__value_0)", 1); validate(filterProcessor.convert("username eq \"joe\" or emails.value co \".com\"", null, false),"(username = :__value_0 OR email LIKE :__value_1)", 2); validate(filterProcessor.convert("active eq true", null, false),"active = :__value_0", 1, Boolean.class); - validate(filterProcessor.convert("test eq 1000000.45", null, false),"test = :__value_0", 1, Double.class); - validate(filterProcessor.convert("test eq 1000000", null, false),"test = :__value_0", 1, Double.class); + validate(filterProcessor.convert("Version eq 1000000.45", null, false),"Version = :__value_0", 1, Double.class); + validate(filterProcessor.convert("meta.VerSion eq 1000000", null, false),"VerSion = :__value_0", 1, Double.class); } @Test
Vulnerability mechanics
Generated by null/stub on May 9, 2026. Inputs: CWE entries + fix-commit diffs from this CVE's patches. Citations validated against bundle.
References
11- www.securityfocus.com/bid/99254nvdBroken LinkThird Party AdvisoryVDB Entry
- github.com/advisories/GHSA-cw9c-v3v2-99hmghsaADVISORY
- nvd.nist.gov/vuln/detail/CVE-2017-4974ghsaADVISORY
- www.cloudfoundry.org/cve-2017-4974/nvdVendor Advisory
- github.com/cloudfoundry/uaa/commit/01edea6337c8ddb2ab80906aa1254d3c1dc02fbghsaWEB
- github.com/cloudfoundry/uaa/commit/2dbeb9e93e076d71d7f0886dea9f77f23e0b8f3cghsaWEB
- github.com/cloudfoundry/uaa/commit/5dc5ca9176ed5baa870680d99f37e7e559dddc5ghsaWEB
- github.com/cloudfoundry/uaa/commit/74b9b270787aa602196d59d58893c3a6e09816f9ghsaWEB
- github.com/cloudfoundry/uaa/commit/b6d6526cb89120043d390bf0274cd062e9fc452ghsaWEB
- web.archive.org/web/20200227163823/http://www.securityfocus.com/bid/99254ghsaWEB
- www.cloudfoundry.org/cve-2017-4974ghsaWEB
News mentions
0No linked articles in our index yet.