You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@directory.apache.org by bd...@apache.org on 2022/08/17 21:39:22 UTC
[directory-scimple] branch runscope-tests created (now a025b5e)
This is an automated email from the ASF dual-hosted git repository.
bdemers pushed a change to branch runscope-tests
in repository https://gitbox.apache.org/repos/asf/directory-scimple.git
at a025b5e Minor changes to Spring example to support support resource filtering
This branch includes the following new commits:
new a025b5e Minor changes to Spring example to support support resource filtering
The 1 revisions listed above as "new" are entirely new to this
repository and will be described in separate emails. The revisions
listed as "add" were already present in the repository and have only
been added to this reference.
[directory-scimple] 01/01: Minor changes to Spring example to support support resource filtering
Posted by bd...@apache.org.
This is an automated email from the ASF dual-hosted git repository.
bdemers pushed a commit to branch runscope-tests
in repository https://gitbox.apache.org/repos/asf/directory-scimple.git
commit a025b5e589b86bd83befba2019eb54499ad42b59
Author: Brian Demers <bd...@apache.org>
AuthorDate: Wed Aug 17 17:39:16 2022 -0400
Minor changes to Spring example to support support resource filtering
And other tweaks needed to pass Okta's public Runscope tests:
https://developer.okta.com/docs/guides/scim-provisioning-integration-test/main/
NOTE: These test should be turned into an integration suite as to NOT need any 3rd party tools to run them
---
.../scim-server-spring-boot/README.md | 4 +-
...tion.java => ScimpleSpringBootApplication.java} | 19 +++-
.../example/spring/ScimpleSpringConfiguration.java | 9 ++
.../spring/service/InMemoryScimFilterMatcher.java | 110 +++++++++++++++++++++
.../spring/service/InMemoryUserService.java | 46 ++++++---
.../src/main/resources/application.properties | 1 +
.../server/configuration/ServerConfiguration.java | 3 +-
.../spec/schema/ServiceProviderConfiguration.java | 11 +--
8 files changed, 178 insertions(+), 25 deletions(-)
diff --git a/scim-server-examples/scim-server-spring-boot/README.md b/scim-server-examples/scim-server-spring-boot/README.md
index 8ba5ccf..bea3262 100644
--- a/scim-server-examples/scim-server-spring-boot/README.md
+++ b/scim-server-examples/scim-server-spring-boot/README.md
@@ -12,9 +12,9 @@ Run: `mvn spring-boot:run` and then access one of the endpoints:
```bash
# httpie
-http :8080/Users
+http :8080/v2/Users
# curl
-curl localhost:8080/Users
+curl localhost:8080/v2/Users
```
n
diff --git a/scim-server-examples/scim-server-spring-boot/src/main/java/org/apache/directory/scim/example/spring/SpringBootApplication.java b/scim-server-examples/scim-server-spring-boot/src/main/java/org/apache/directory/scim/example/spring/ScimpleSpringBootApplication.java
similarity index 53%
rename from scim-server-examples/scim-server-spring-boot/src/main/java/org/apache/directory/scim/example/spring/SpringBootApplication.java
rename to scim-server-examples/scim-server-spring-boot/src/main/java/org/apache/directory/scim/example/spring/ScimpleSpringBootApplication.java
index 1c8c1af..a4a65e4 100644
--- a/scim-server-examples/scim-server-spring-boot/src/main/java/org/apache/directory/scim/example/spring/SpringBootApplication.java
+++ b/scim-server-examples/scim-server-spring-boot/src/main/java/org/apache/directory/scim/example/spring/ScimpleSpringBootApplication.java
@@ -19,12 +19,25 @@
package org.apache.directory.scim.example.spring;
+import org.apache.directory.scim.server.configuration.ServerConfiguration;
+import org.apache.directory.scim.spec.schema.ServiceProviderConfiguration;
import org.springframework.boot.SpringApplication;
+import org.springframework.boot.autoconfigure.SpringBootApplication;
+import org.springframework.context.annotation.Bean;
-@org.springframework.boot.autoconfigure.SpringBootApplication
-public class SpringBootApplication {
+@SpringBootApplication
+public class ScimpleSpringBootApplication {
public static void main(String[] args) {
- SpringApplication.run(SpringBootApplication.class, args);
+ SpringApplication.run(ScimpleSpringBootApplication.class, args);
+ }
+
+ @Bean
+ ServerConfiguration serverConfiguration() {
+ return new ServerConfiguration().addAuthenticationSchema(
+ new ServiceProviderConfiguration.AuthenticationSchema()
+ .setType(ServiceProviderConfiguration.AuthenticationSchema.Type.OAUTH_BEARER)
+ .setName(ServiceProviderConfiguration.AuthenticationSchema.Type.OAUTH_BEARER.name())
+ .setDescription("OAuth2 Bearer Token"));
}
}
diff --git a/scim-server-examples/scim-server-spring-boot/src/main/java/org/apache/directory/scim/example/spring/ScimpleSpringConfiguration.java b/scim-server-examples/scim-server-spring-boot/src/main/java/org/apache/directory/scim/example/spring/ScimpleSpringConfiguration.java
index 13bc401..9b37dcb 100644
--- a/scim-server-examples/scim-server-spring-boot/src/main/java/org/apache/directory/scim/example/spring/ScimpleSpringConfiguration.java
+++ b/scim-server-examples/scim-server-spring-boot/src/main/java/org/apache/directory/scim/example/spring/ScimpleSpringConfiguration.java
@@ -50,6 +50,14 @@ import java.util.*;
@Configuration
public class ScimpleSpringConfiguration {
+// TODO: ServerConfiguration should be configured based on application.properties?
+// TODO: commented out for now, @ConditionalOnMissingBean would only work if this were an AutoConfig from another module
+// @Bean
+// @ConditionalOnMissingBean
+// ServerConfiguration serverConfiguration() {
+// return new ServerConfiguration();
+// }
+
@Bean
@ConditionalOnMissingBean
Registry registry() {
@@ -96,6 +104,7 @@ public class ScimpleSpringConfiguration {
// basic beans, this could also be defined as @Beans above too
bind(EtagGenerator.class).to(EtagGenerator.class);
bind(AttributeUtil.class).to(AttributeUtil.class);
+// bind(ServerConfiguration.class).to(ServerConfiguration.class);
}
});
return config;
diff --git a/scim-server-examples/scim-server-spring-boot/src/main/java/org/apache/directory/scim/example/spring/service/InMemoryScimFilterMatcher.java b/scim-server-examples/scim-server-spring-boot/src/main/java/org/apache/directory/scim/example/spring/service/InMemoryScimFilterMatcher.java
new file mode 100644
index 0000000..3865e53
--- /dev/null
+++ b/scim-server-examples/scim-server-spring-boot/src/main/java/org/apache/directory/scim/example/spring/service/InMemoryScimFilterMatcher.java
@@ -0,0 +1,110 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF 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.apache.directory.scim.example.spring.service;
+
+import org.apache.directory.scim.spec.exception.ScimResourceInvalidException;
+import org.apache.directory.scim.spec.protocol.filter.AttributeComparisonExpression;
+import org.apache.directory.scim.spec.protocol.filter.CompareOperator;
+import org.apache.directory.scim.spec.protocol.filter.FilterExpression;
+import org.apache.directory.scim.spec.protocol.search.Filter;
+import org.apache.directory.scim.spec.resources.ScimResource;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.util.ReflectionUtils;
+
+import java.lang.reflect.Field;
+
+final class InMemoryScimFilterMatcher {
+
+ private static final Logger log = LoggerFactory.getLogger(InMemoryScimFilterMatcher.class);
+
+ private InMemoryScimFilterMatcher(){}
+
+ /**
+ * Basic FilterExpression support for in-memory objects, actual implementations should translate the Filter into
+ * the appropriate query language.
+ */
+ public static boolean matches(ScimResource resource, Filter filter) {
+
+ // no filter defined, return everything
+ if (filter == null) {
+ return true;
+ }
+
+ // FIXME: This example ONLY supports simple Filter arguments, Filters can be more complex, you would likely translate
+ // them into a query instead of manually filtering anyway
+
+ if (filter.getExpression() instanceof AttributeComparisonExpression expression) {
+ String attribute = expression.getAttributePath().getAttributeName();
+ CompareOperator op = expression.getOperation();
+ Object compareValue = expression.getCompareValue();
+
+
+ Field field = ReflectionUtils.findField(resource.getClass(), attribute);
+ if (field == null) {
+ return false;
+ }
+ field.setAccessible(true);
+
+ Object actualValue = ReflectionUtils.getField(field, resource);
+ switch (op) {
+ case EQ -> {
+ return compareValue.equals(actualValue);
+ }
+ case NE -> {
+ return !compareValue.equals(actualValue);
+ }
+ case SW -> {
+ return isStringExpression(field, compareValue, expression)
+ && actualValue != null
+ && actualValue.toString().startsWith(compareValue.toString());
+ }
+ case EW -> {
+ return isStringExpression(field, compareValue, expression)
+ && actualValue != null
+ && actualValue.toString().endsWith(compareValue.toString());
+ }
+ case CO -> {
+ return isStringExpression(field, compareValue, expression)
+ && actualValue != null
+ && actualValue.toString().contains(compareValue.toString());
+ }
+ default -> throw new ScimResourceInvalidException("This example only supports basic string filters");
+ }
+ } else{
+ log.warn("Unsupported Filter expression of type: " + filter.getExpression().getClass());
+ return false;
+ }
+ }
+
+ private static boolean isStringExpression(Field field, Object compareValue, FilterExpression expression) {
+ if (!field.getType().isAssignableFrom(String.class)) {
+ // TODO: maybe this should be a 400?
+ log.warn("Non String value for expression : " + expression.toFilter());
+ return false;
+ }
+ if (compareValue == null) {
+ // TODO: maybe this should be a 400?
+ log.warn("Empty value for expression : " + expression.toFilter());
+ return false;
+ }
+ return true;
+ }
+}
diff --git a/scim-server-examples/scim-server-spring-boot/src/main/java/org/apache/directory/scim/example/spring/service/InMemoryUserService.java b/scim-server-examples/scim-server-spring-boot/src/main/java/org/apache/directory/scim/example/spring/service/InMemoryUserService.java
index f4ead5f..a9855e1 100644
--- a/scim-server-examples/scim-server-spring-boot/src/main/java/org/apache/directory/scim/example/spring/service/InMemoryUserService.java
+++ b/scim-server-examples/scim-server-spring-boot/src/main/java/org/apache/directory/scim/example/spring/service/InMemoryUserService.java
@@ -19,14 +19,15 @@
package org.apache.directory.scim.example.spring.service;
-import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import jakarta.annotation.PostConstruct;
+import jakarta.ws.rs.core.Response;
import org.apache.directory.scim.example.spring.extensions.LuckyNumberExtension;
+import org.apache.directory.scim.server.exception.UnableToCreateResourceException;
import org.apache.directory.scim.server.exception.UnableToUpdateResourceException;
import org.apache.directory.scim.server.provider.Provider;
import org.apache.directory.scim.server.provider.UpdateRequest;
@@ -34,10 +35,7 @@ import org.apache.directory.scim.spec.protocol.filter.FilterResponse;
import org.apache.directory.scim.spec.protocol.search.Filter;
import org.apache.directory.scim.spec.protocol.search.PageRequest;
import org.apache.directory.scim.spec.protocol.search.SortRequest;
-import org.apache.directory.scim.spec.resources.Email;
-import org.apache.directory.scim.spec.resources.ScimExtension;
-import org.apache.directory.scim.spec.resources.ScimResource;
-import org.apache.directory.scim.spec.resources.ScimUser;
+import org.apache.directory.scim.spec.resources.*;
import org.springframework.stereotype.Service;
/**
@@ -48,7 +46,7 @@ import org.springframework.stereotype.Service;
*/
@Service
public class InMemoryUserService implements Provider<ScimUser> {
-
+
static final String DEFAULT_USER_ID = "1";
static final String DEFAULT_USER_EXTERNAL_ID = "e" + DEFAULT_USER_ID;
static final String DEFAULT_USER_DISPLAY_NAME = "User " + DEFAULT_USER_ID;
@@ -56,7 +54,7 @@ public class InMemoryUserService implements Provider<ScimUser> {
static final String DEFAULT_USER_EMAIL_TYPE = "work";
static final int DEFAULT_USER_LUCKY_NUMBER = 7;
- private Map<String, ScimUser> users = new HashMap<>();
+ private final Map<String, ScimUser> users = new HashMap<>();
@PostConstruct
public void init() {
@@ -65,12 +63,15 @@ public class InMemoryUserService implements Provider<ScimUser> {
user.setExternalId(DEFAULT_USER_EXTERNAL_ID);
user.setUserName(DEFAULT_USER_EXTERNAL_ID);
user.setDisplayName(DEFAULT_USER_DISPLAY_NAME);
+ user.setName(new Name()
+ .setGivenName("Tester")
+ .setFamilyName("McTest"));
Email email = new Email();
email.setDisplay(DEFAULT_USER_EMAIL_VALUE);
email.setValue(DEFAULT_USER_EMAIL_VALUE);
email.setType(DEFAULT_USER_EMAIL_TYPE);
email.setPrimary(true);
- user.setEmails(Arrays.asList(email));
+ user.setEmails(List.of(email));
LuckyNumberExtension luckyNumberExtension = new LuckyNumberExtension();
luckyNumberExtension.setLuckyNumber(DEFAULT_USER_LUCKY_NUMBER);
@@ -89,7 +90,7 @@ public class InMemoryUserService implements Provider<ScimUser> {
* @see Provider#create(ScimResource)
*/
@Override
- public ScimUser create(ScimUser resource) {
+ public ScimUser create(ScimUser resource) throws UnableToCreateResourceException {
String resourceId = resource.getId();
int idCandidate = resource.hashCode();
String id = resourceId != null ? resourceId : Integer.toString(idCandidate);
@@ -98,8 +99,16 @@ public class InMemoryUserService implements Provider<ScimUser> {
id = Integer.toString(idCandidate);
++idCandidate;
}
- users.put(id, resource);
+
+ // check to make sure the user doesn't already exist
+ boolean existingUserFound = users.values().stream()
+ .anyMatch(user -> user.getUserName().equals(resource.getUserName()));
+ if (existingUserFound) {
+ throw new UnableToCreateResourceException(Response.Status.CONFLICT, "User '" + resource.getUserName() + "' already exists.");
+ }
+
resource.setId(id);
+ users.put(id, resource);
return resource;
}
@@ -135,7 +144,19 @@ public class InMemoryUserService implements Provider<ScimUser> {
*/
@Override
public FilterResponse<ScimUser> find(Filter filter, PageRequest pageRequest, SortRequest sortRequest) {
- return new FilterResponse<>(users.values(), pageRequest, users.size());
+
+ long count = pageRequest.getCount() != null ? pageRequest.getCount() : users.size();
+ long startIndex = pageRequest.getStartIndex() != null
+ ? pageRequest.getStartIndex() - 1 // SCIM is 1-based indexed
+ : 0;
+
+ List<ScimUser> result = users.values().stream()
+ .skip(startIndex)
+ .limit(count)
+ .filter(user -> InMemoryScimFilterMatcher.matches(user, filter))
+ .toList();
+
+ return new FilterResponse<>(result, pageRequest, result.size());
}
/**
@@ -143,7 +164,6 @@ public class InMemoryUserService implements Provider<ScimUser> {
*/
@Override
public List<Class<? extends ScimExtension>> getExtensionList() {
- return Arrays.asList(LuckyNumberExtension.class);
+ return List.of(LuckyNumberExtension.class);
}
-
}
diff --git a/scim-server-examples/scim-server-spring-boot/src/main/resources/application.properties b/scim-server-examples/scim-server-spring-boot/src/main/resources/application.properties
index e69de29..2987eae 100644
--- a/scim-server-examples/scim-server-spring-boot/src/main/resources/application.properties
+++ b/scim-server-examples/scim-server-spring-boot/src/main/resources/application.properties
@@ -0,0 +1 @@
+server.servlet.context-path = /v2
diff --git a/scim-server/src/main/java/org/apache/directory/scim/server/configuration/ServerConfiguration.java b/scim-server/src/main/java/org/apache/directory/scim/server/configuration/ServerConfiguration.java
index 41445f1..65c7224 100644
--- a/scim-server/src/main/java/org/apache/directory/scim/server/configuration/ServerConfiguration.java
+++ b/scim-server/src/main/java/org/apache/directory/scim/server/configuration/ServerConfiguration.java
@@ -79,8 +79,9 @@ public class ServerConfiguration {
return Collections.unmodifiableList(authenticationSchemas);
}
- public void addAuthenticationSchema(AuthenticationSchema authenticationSchema) {
+ public ServerConfiguration addAuthenticationSchema(AuthenticationSchema authenticationSchema) {
authenticationSchemas.add(authenticationSchema);
+ return this;
}
public SupportedConfiguration getChangePasswordConfiguration() {
diff --git a/scim-spec/scim-spec-schema/src/main/java/org/apache/directory/scim/spec/schema/ServiceProviderConfiguration.java b/scim-spec/scim-spec-schema/src/main/java/org/apache/directory/scim/spec/schema/ServiceProviderConfiguration.java
index e73307b..594ea0e 100644
--- a/scim-spec/scim-spec-schema/src/main/java/org/apache/directory/scim/spec/schema/ServiceProviderConfiguration.java
+++ b/scim-spec/scim-spec-schema/src/main/java/org/apache/directory/scim/spec/schema/ServiceProviderConfiguration.java
@@ -47,12 +47,11 @@ public class ServiceProviderConfiguration extends ScimResourceWithOptionalId {
public static class AuthenticationSchema {
public enum Type {
- @XmlEnumValue("oauth")
- OAUTH, @XmlEnumValue("oauth2")
- OAUTH2, @XmlEnumValue("oauthbearertoken")
- OAUTH_BEARER, @XmlEnumValue("httpbasic")
- HTTP_BASIC, @XmlEnumValue("httpdigest")
- HTTP_DIGEST;
+ @XmlEnumValue("oauth") OAUTH,
+ @XmlEnumValue("oauth2") OAUTH2,
+ @XmlEnumValue("oauthbearertoken") OAUTH_BEARER,
+ @XmlEnumValue("httpbasic") HTTP_BASIC,
+ @XmlEnumValue("httpdigest") HTTP_DIGEST;
}
@XmlElement