[CRV-12] Implementation completed

parent c5bd5249
......@@ -74,7 +74,9 @@ limitations under the License.
<properties>
<connid.version>1.4.2.0</connid.version>
<cxf.version>3.1.4</cxf.version>
<jackson.version>2.6.4</jackson.version>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
......@@ -98,7 +100,25 @@ limitations under the License.
<version>${connid.version}</version>
</dependency>
<!-- TEST -->
<dependency>
<groupId>org.apache.cxf</groupId>
<artifactId>cxf-rt-rs-client</artifactId>
<version>${cxf.version}</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.jaxrs</groupId>
<artifactId>jackson-jaxrs-json-provider</artifactId>
<version>${jackson.version}</version>
</dependency>
<!-- TEST -->
<dependency>
<groupId>org.apache.cxf</groupId>
<artifactId>cxf-rt-transports-local</artifactId>
<version>${cxf.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>net.tirasa.connid</groupId>
<artifactId>connector-test-common</artifactId>
......
/*
/*
* Copyright 2015 The CHOReVOLUTION project
*
* Licensed under the Apache License, Version 2.0 (the "License");
......@@ -15,6 +15,7 @@
*/
package eu.chorevolution.idm.connid.federationserver;
import java.net.URI;
import org.identityconnectors.common.StringUtil;
import org.identityconnectors.framework.common.exceptions.ConfigurationException;
import org.identityconnectors.framework.spi.AbstractConfiguration;
......@@ -22,22 +23,39 @@ import org.identityconnectors.framework.spi.ConfigurationProperty;
public class FederationServerConfiguration extends AbstractConfiguration {
private String sampleProperty = "SAMPLE VALUE";
private String baseAddress;
@ConfigurationProperty(displayMessageKey = "sampleProperty.display",
helpMessageKey = "sampleProperty.help", order = 1)
public String getSampleProperty() {
return sampleProperty;
private String domain;
@ConfigurationProperty(displayMessageKey = "baseAddress.display", helpMessageKey = "baseAddress.help", order = 1)
public String getBaseAddress() {
return baseAddress;
}
public void setBaseAddress(final String baseAddress) {
this.baseAddress = baseAddress;
}
public void setSampleProperty(final String sampleProperty) {
this.sampleProperty = sampleProperty;
@ConfigurationProperty(displayMessageKey = "domain.display", helpMessageKey = "domain.help", order = 2)
public String getDomain() {
return domain;
}
public void setDomain(final String domain) {
this.domain = domain;
}
@Override
public void validate() {
if (StringUtil.isBlank(sampleProperty)) {
throw new ConfigurationException("sampleProperty must not be blank!");
if (StringUtil.isBlank(baseAddress)) {
throw new ConfigurationException("baseAddress must not be blank!");
} else {
// verify that baseAddress is a valid URI
URI.create(baseAddress);
}
if (StringUtil.isBlank(domain)) {
throw new ConfigurationException("domain must not be blank!");
}
}
......
/*
/*
* Copyright 2015 The CHOReVOLUTION project
*
* Licensed under the Apache License, Version 2.0 (the "License");
......@@ -15,46 +15,49 @@
*/
package eu.chorevolution.idm.connid.federationserver;
import java.util.Collections;
import com.fasterxml.jackson.jaxrs.json.JacksonJaxbJsonProvider;
import eu.chorevolution.idm.connid.federationserver.api.EndUser;
import eu.chorevolution.idm.connid.federationserver.api.FederationServerEndUserService;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.UUID;
import org.apache.cxf.jaxrs.client.JAXRSClientFactory;
import org.identityconnectors.common.StringUtil;
import org.identityconnectors.common.logging.Log;
import org.identityconnectors.common.security.GuardedString;
import org.identityconnectors.framework.api.operations.APIOperation;
import org.identityconnectors.framework.api.operations.ResolveUsernameApiOp;
import org.identityconnectors.framework.common.objects.Attribute;
import org.identityconnectors.framework.common.objects.AttributeBuilder;
import org.identityconnectors.framework.common.objects.AttributeInfo;
import org.identityconnectors.framework.common.objects.AttributeInfoBuilder;
import org.identityconnectors.framework.common.objects.AttributeUtil;
import org.identityconnectors.framework.common.objects.ConnectorObject;
import org.identityconnectors.framework.common.objects.Name;
import org.identityconnectors.framework.common.objects.ObjectClass;
import org.identityconnectors.framework.common.objects.ObjectClassInfo;
import org.identityconnectors.framework.common.objects.OperationOptionInfo;
import org.identityconnectors.framework.common.objects.OperationOptions;
import org.identityconnectors.framework.common.objects.OperationalAttributes;
import org.identityconnectors.framework.common.objects.ResultsHandler;
import org.identityconnectors.framework.common.objects.Schema;
import org.identityconnectors.framework.common.objects.SyncResultsHandler;
import org.identityconnectors.framework.common.objects.SyncToken;
import org.identityconnectors.framework.common.objects.SchemaBuilder;
import org.identityconnectors.framework.common.objects.Uid;
import org.identityconnectors.framework.common.objects.filter.AbstractFilterTranslator;
import org.identityconnectors.framework.common.objects.filter.EqualsFilter;
import org.identityconnectors.framework.common.objects.filter.FilterTranslator;
import org.identityconnectors.framework.spi.Configuration;
import org.identityconnectors.framework.spi.Connector;
import org.identityconnectors.framework.spi.ConnectorClass;
import org.identityconnectors.framework.spi.operations.AuthenticateOp;
import org.identityconnectors.framework.spi.operations.CreateOp;
import org.identityconnectors.framework.spi.operations.DeleteOp;
import org.identityconnectors.framework.spi.operations.SchemaOp;
import org.identityconnectors.framework.spi.operations.SearchOp;
import org.identityconnectors.framework.spi.operations.SyncOp;
import org.identityconnectors.framework.spi.operations.TestOp;
import org.identityconnectors.framework.spi.operations.UpdateAttributeValuesOp;
import org.identityconnectors.framework.spi.operations.UpdateOp;
/**
* This sample connector provides (empty) implementations for all ConnId operations, but this is not mandatory: any
* connector can choose which operations are actually to be implemented.
*/
@ConnectorClass(configurationClass = FederationServerConfiguration.class, displayNameKey = "sample.connector.display")
@ConnectorClass(
configurationClass = FederationServerConfiguration.class,
displayNameKey = "federationserver.connector.display")
public class FederationServerConnector implements Connector,
CreateOp, UpdateOp, UpdateAttributeValuesOp, DeleteOp,
AuthenticateOp, ResolveUsernameApiOp, SchemaOp, SyncOp, TestOp, SearchOp<FederationServerFilter> {
CreateOp, UpdateOp, DeleteOp,
SchemaOp, TestOp, SearchOp<FederationServerFilter> {
private static final Log LOG = Log.getLog(FederationServerConnector.class);
......@@ -76,95 +79,122 @@ public class FederationServerConnector implements Connector,
// dispose of any resources the this connector uses.
}
@Override
public Uid create(
final ObjectClass objectClass,
final Set<Attribute> createAttributes,
final OperationOptions options) {
private void checkObjectClass(final ObjectClass objectClass) {
if (!objectClass.equals(ObjectClass.ACCOUNT)) {
throw new IllegalArgumentException("Only " + ObjectClass.ACCOUNT.getObjectClassValue() + " is supported");
}
}
return new Uid(UUID.randomUUID().toString());
private FederationServerEndUserService getClient() {
List<Object> providers = new ArrayList<>();
providers.add(new JacksonJaxbJsonProvider());
return JAXRSClientFactory.create(
getConfiguration().getBaseAddress(), FederationServerEndUserService.class, providers);
}
@Override
public Uid update(
final ObjectClass objectClass,
final Uid uid,
final Set<Attribute> replaceAttributes,
final OperationOptions options) {
private EndUser getEndUser(final String username, final Set<Attribute> attrs) {
EndUser endUser = new EndUser();
endUser.setUsername(username);
return uid;
}
attrs.stream().forEach((attr) -> {
if (attr.getName().equals(OperationalAttributes.PASSWORD_NAME)) {
if (attr.getValue() != null && !attr.getValue().isEmpty()) {
StringBuilder clearPwd = new StringBuilder();
@Override
public Uid addAttributeValues(
final ObjectClass objclass,
final Uid uid,
final Set<Attribute> valuesToAdd,
final OperationOptions options) {
AttributeUtil.getGuardedStringValue(attr).access(clearPwd::append);
return uid;
endUser.setPassword(clearPwd.toString());
}
} else if (attr.getName().equals(OperationalAttributes.ENABLE_NAME)) {
if (attr.getValue() != null && !attr.getValue().isEmpty()) {
endUser.setActive(((Boolean) attr.getValue().get(0)));
}
} else if (attr.getName().equals(FederationServerConstants.GROUPS_NAME)) {
if (attr.getValue() != null) {
attr.getValue().stream().forEach((value) -> {
endUser.getGroups().add(value.toString());
});
}
} else if (!attr.getName().equals(Uid.NAME) && !attr.getName().equals(Name.NAME)) {
Set<String> values = new HashSet<>();
if (attr.getValue() != null) {
attr.getValue().stream().forEach((value) -> {
values.add(value.toString());
});
}
endUser.getAttributes().put(attr.getName(), values);
}
});
return endUser;
}
@Override
public Uid removeAttributeValues(
final ObjectClass objclass,
final Uid uid,
final Set<Attribute> valuesToRemove,
public Uid create(
final ObjectClass objectClass,
final Set<Attribute> attrs,
final OperationOptions options) {
return uid;
checkObjectClass(objectClass);
Name name = AttributeUtil.getNameFromAttributes(attrs);
if (name == null || name.getValue() == null || name.getValue().isEmpty()) {
throw new IllegalArgumentException("No " + Name.NAME + " provided");
}
getClient().create(getConfiguration().getDomain(), getEndUser(name.getNameValue(), attrs));
return new Uid(name.getNameValue());
}
@Override
public void delete(
public Uid update(
final ObjectClass objectClass,
final Uid uid,
final Set<Attribute> attrs,
final OperationOptions options) {
}
@Override
public Uid authenticate(
final ObjectClass objectClass,
final String username,
final GuardedString password,
final OperationOptions options) {
checkObjectClass(objectClass);
String updated = null;
Name name = AttributeUtil.getNameFromAttributes(attrs);
if (name != null) {
updated = name.getNameValue();
}
if (updated == null) {
updated = uid.getUidValue();
}
getClient().update(getConfiguration().getDomain(), uid.getUidValue(), getEndUser(updated, attrs));
return new Uid(username);
return new Uid(updated);
}
@Override
public Uid resolveUsername(
public void delete(
final ObjectClass objectClass,
final String username,
final Uid uid,
final OperationOptions options) {
return new Uid(username);
checkObjectClass(objectClass);
getClient().delete(getConfiguration().getDomain(), uid.getUidValue());
}
@Override
public Schema schema() {
return new Schema(
Collections.<ObjectClassInfo>emptySet(),
Collections.<OperationOptionInfo>emptySet(),
Collections.<Class<? extends APIOperation>, Set<ObjectClassInfo>>emptyMap(),
Collections.<Class<? extends APIOperation>, Set<OperationOptionInfo>>emptyMap());
}
Set<AttributeInfo> attrInfo = new HashSet<>();
attrInfo.add(AttributeInfoBuilder.build(FederationServerConstants.GROUPS_NAME, String.class));
@Override
public void sync(
final ObjectClass objectClass,
final SyncToken token,
final SyncResultsHandler handler,
final OperationOptions options) {
}
SchemaBuilder schemaBuilder = new SchemaBuilder(FederationServerConnector.class);
schemaBuilder.defineObjectClass(ObjectClass.ACCOUNT.getObjectClassValue(), attrInfo);
@Override
public SyncToken getLatestSyncToken(final ObjectClass objectClass) {
return new SyncToken(null);
return schemaBuilder.build();
}
@Override
public void test() {
getConfiguration().validate();
}
@Override
......@@ -173,14 +203,55 @@ public class FederationServerConnector implements Connector,
final OperationOptions options) {
return new AbstractFilterTranslator<FederationServerFilter>() {
@Override
protected FederationServerFilter createEqualsExpression(final EqualsFilter filter, final boolean not) {
return filter.getAttribute() == null || filter.getAttribute().getValue() == null
|| filter.getAttribute().getValue().isEmpty()
? super.createEqualsExpression(filter, not)
: new FederationServerFilter(filter.getAttribute().getValue().get(0).toString());
}
};
}
private ConnectorObject getConnectorObject(final EndUser endUser) {
Set<Attribute> attrs = new HashSet<>();
attrs.add(new Uid(endUser.getUsername()));
attrs.add(new Name(endUser.getUsername()));
if (endUser.getPassword() != null) {
attrs.add(AttributeBuilder.buildPassword(endUser.getPassword().toCharArray()));
}
attrs.add(AttributeBuilder.buildEnabled(endUser.isActive()));
attrs.add(AttributeBuilder.build(FederationServerConstants.GROUPS_NAME, endUser.getGroups()));
endUser.getAttributes().entrySet().stream().forEach((entry) -> {
attrs.add(AttributeBuilder.build(entry.getKey(), entry.getValue()));
});
return new ConnectorObject(ObjectClass.ACCOUNT, attrs);
}
@Override
public void executeQuery(
final ObjectClass objectClass,
final FederationServerFilter query,
final ResultsHandler handler,
final OperationOptions options) {
checkObjectClass(objectClass);
FederationServerEndUserService client = getClient();
if (query == null || StringUtil.isBlank(query.getUsername())) {
client.list(getConfiguration().getDomain()).stream().forEach((endUser) -> {
handler.handle(getConnectorObject(endUser));
});
} else {
EndUser found = client.read(getConfiguration().getDomain(), query.getUsername());
if (found != null) {
handler.handle(getConnectorObject(found));
}
}
}
}
/*
* Copyright 2015 The CHOReVOLUTION project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package eu.chorevolution.idm.connid.federationserver;
public final class FederationServerConstants {
public static final String GROUPS_NAME = "__GROUPS__";
private FederationServerConstants() {
// private constructor for static utility class
}
}
/*
/*
* Copyright 2015 The CHOReVOLUTION project
*
* Licensed under the Apache License, Version 2.0 (the "License");
......@@ -15,9 +15,16 @@
*/
package eu.chorevolution.idm.connid.federationserver;
/**
* This implementation depends on the logic that this connector follows.
*/
public class FederationServerFilter {
private final String username;
public FederationServerFilter(final String username) {
this.username = username;
}
public String getUsername() {
return username;
}
}
/*
* Copyright 2015 The CHOReVOLUTION project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package eu.chorevolution.idm.connid.federationserver.api;
import java.io.Serializable;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
public class EndUser implements Serializable {
private static final long serialVersionUID = -3901863997664130719L;
private String username;
private String password;
private boolean active = true;
private final Set<String> groups = new HashSet<>();
private final Map<String, Set<String>> attributes = new HashMap<>();
public String getUsername() {
return username;
}
public void setUsername(final String username) {
this.username = username;
}
public String getPassword() {
return password;
}
public boolean isActive() {
return active;
}
public void setActive(final boolean active) {
this.active = active;
}
public void setPassword(final String password) {
this.password = password;
}
public Set<String> getGroups() {
return groups;
}
public Map<String, Set<String>> getAttributes() {
return attributes;
}
}
/*
* Copyright 2015 The CHOReVOLUTION project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package eu.chorevolution.idm.connid.federationserver.api;
import java.util.List;
import javax.ws.rs.Consumes;
import javax.ws.rs.DELETE;
import javax.ws.rs.GET;
import javax.ws.rs.POST;
import javax.ws.rs.PUT;
import javax.ws.rs.Path;
import javax.ws.rs.PathParam;
import javax.ws.rs.Produces;
import javax.ws.rs.core.MediaType;
/**
* REST operations for end-user management.
*/
@Path("/domains/{domain}/endusers")
public interface FederationServerEndUserService {
@GET
@Produces({ MediaType.APPLICATION_JSON })
List<EndUser> list(@PathParam("domain") String domain);
@POST
@Consumes({ MediaType.APPLICATION_JSON })
void create(@PathParam("domain") String domain, EndUser enduser);
@GET
@Path("{username}")
@Produces({ MediaType.APPLICATION_JSON })
EndUser read(@PathParam("domain") String domain, @PathParam("username") String username);
@PUT
@Path("{username}")
@Consumes({ MediaType.APPLICATION_JSON })
void update(@PathParam("domain") String domain, @PathParam("username") String username, EndUser enduser);
@DELETE
@Path("{username}")
void delete(@PathParam("domain") String domain, @PathParam("username") String username);
}
......@@ -13,6 +13,8 @@
# See the License for the specific language governing permissions and
# limitations under the License.
#
sampleProperty.display=Sampe Property
sampleProperty.help=Only a sample property