Commit ef6f093d authored by Francesco Chicchiricco's avatar Francesco Chicchiricco
Browse files

[CRV-19] Communication with Security Filter instances

parent 92c26bbe
......@@ -29,6 +29,11 @@ public class AuthenticationHandler implements ContainerRequestFilter {
@Override
public void filter(final ContainerRequestContext requestContext) throws IOException {
// Skip authentication for Security Filter
if (requestContext.getUriInfo().getRequestUri().toASCIIString().contains("/management/")) {
return;
}
String authorization = requestContext.getHeaderString("Authorization");
if (authorization == null) {
authorization = "";
......
......@@ -25,17 +25,27 @@ import org.springframework.stereotype.Service;
@Path(value = "/management")
public class SecurityFilterManagementImpl implements SecurityFilterManagement {
private static final RuntimeInfo SHARED_INSTANCE;
static {
SHARED_INSTANCE = new RuntimeInfo();
SHARED_INSTANCE.setStatus(Status.ENABLED);
SHARED_INSTANCE.setSecurityContext("A context");
}
@Override
public RuntimeInfo info() {
return null;
return SHARED_INSTANCE;
}
@Override
public void status(final Status status) {
SHARED_INSTANCE.setStatus(status);
}
@Override
public void securityContext(final String securityContext) {
SHARED_INSTANCE.setSecurityContext(securityContext);
}
}
......@@ -117,7 +117,7 @@ limitations under the License.
<packageType>WAR</packageType>
<packageUrl>http://localhost:8080/TrafficInformation/SF_TrafficInformation.war</packageUrl>
<serviceType>SECURITY_FILTER</serviceType>
<url>http://192.168.150.142/sftrafficinformation</url>
<url>http://localhost:9080/choremocks/rest/</url>
<deploymentInfo>
<endpoint>http://192.168.150.130:8080/sftrafficinformation</endpoint>
<node>
......@@ -199,7 +199,7 @@ limitations under the License.
<packageType>WAR</packageType>
<packageUrl>http://localhost:8080/TrafficInformation/SF_Poi.war</packageUrl>
<serviceType>SECURITY_FILTER</serviceType>
<url>http://192.168.150.142/sfpoi</url>
<url>http://localhost:9080/choremocks/rest</url>
<deploymentInfo>
<endpoint>http://192.168.150.130:8080/sfpoi</endpoint>
<node>
......
/*
* Copyright 2016 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.common.types;
import javax.xml.bind.annotation.XmlType;
import org.apache.syncope.common.lib.AbstractBaseBean;
@XmlType
public class SecurityFilterInfo extends AbstractBaseBean {
private static final long serialVersionUID = -2334732785143202012L;
private SecurityFilterStatus status;
private String securityContext;
public SecurityFilterStatus getStatus() {
return status;
}
public void setStatus(final SecurityFilterStatus status) {
this.status = status;
}
public String getSecurityContext() {
return securityContext;
}
public void setSecurityContext(final String securityContext) {
this.securityContext = securityContext;
}
}
/*
* Copyright 2016 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.common.types;
import javax.xml.bind.annotation.XmlEnum;
@XmlEnum
public enum SecurityFilterStatus {
ENABLED,
DISABLED;
}
......@@ -17,9 +17,17 @@ package eu.chorevolution.idm.common.types;
public enum ServiceAction {
/**
* Enforce security context if and only if Security Filter is available.
* Enable the corresponding Security Filter instance, if available.
*/
ENFORCE,
ENABLE_SECURITY_FILTER,
/**
* Disable the corresponding Security Filter instance, if available.
*/
DISABLE_SECURITY_FILTER,
/**
* Enforce security context if and only if a Security Filter instance is available.
*/
ENFORCE_SECURITY_CONTEXT,
/**
* Replace the service providing the name of the former and name and URL of the newer.
*/
......
......@@ -24,6 +24,8 @@ import eu.chorevolution.datamodel.ExistingService;
import eu.chorevolution.idm.common.ChorevolutionEntitlement;
import eu.chorevolution.idm.common.types.ChoreographyAction;
import eu.chorevolution.idm.common.types.ChoreographyOperation;
import eu.chorevolution.idm.common.types.SecurityFilterInfo;
import eu.chorevolution.idm.common.types.SecurityFilterStatus;
import eu.chorevolution.idm.common.types.ServiceAction;
import java.io.ByteArrayInputStream;
import java.io.IOException;
......@@ -55,6 +57,7 @@ import org.apache.syncope.common.lib.to.AnyObjectTO;
import org.apache.syncope.common.lib.to.AnyTypeClassTO;
import org.apache.syncope.common.lib.to.AttrTO;
import org.apache.syncope.common.lib.to.GroupTO;
import org.apache.syncope.common.lib.to.MembershipTO;
import org.apache.syncope.common.lib.to.PlainSchemaTO;
import org.apache.syncope.common.lib.to.TypeExtensionTO;
import org.apache.syncope.common.lib.types.AnyTypeKind;
......@@ -91,6 +94,8 @@ public class ChoreographyLogic extends AbstractLogic<AbstractBaseBean> {
private static final String CHOREOGRAPHY_ID_SCHEMA = "id";
private static final String SECURITY_FILTER_ENDPOINT_SCHEMA = "Endpoint";
private static String SECRET_KEY;
static {
......@@ -124,7 +129,7 @@ public class ChoreographyLogic extends AbstractLogic<AbstractBaseBean> {
@Autowired
private ChoreographyInstanceDAO choreographyInstanceDAO;
private WebClient getWebClient(final String enactmentEngineKey, final String endpoint) throws Exception {
private WebClient getEEWebClient(final String enactmentEngineKey, final String endpoint) throws Exception {
AnyObjectTO enactmentEngine = anyObjectLogic.read(enactmentEngineKey);
if (!ENACTMENT_ENGINE_TYPE.equals(enactmentEngine.getType())) {
throw new NotFoundException("Enactment Engine instance with key " + enactmentEngineKey);
......@@ -169,13 +174,48 @@ public class ChoreographyLogic extends AbstractLogic<AbstractBaseBean> {
accept(MediaType.APPLICATION_JSON_TYPE).type(MediaType.APPLICATION_XML_TYPE);
}
private WebClient getSFWebClient(final String baseURL, final String endpoint) {
List<Object> providers = new ArrayList<>();
providers.add(new JacksonJaxbJsonProvider());
return WebClient.create(
StringUtils.removeEndIgnoreCase(baseURL, "/") + "/management/" + endpoint, providers).
accept(MediaType.APPLICATION_JSON_TYPE).type(MediaType.APPLICATION_JSON_TYPE);
}
private GroupTO choreographyExists(final String id) {
AttributeCond idCond = new AttributeCond(AttributeCond.Type.EQ);
idCond.setSchema(CHOREOGRAPHY_ID_SCHEMA);
idCond.setExpression(id);
AttributeCond isChoreographyCond = new AttributeCond(AttributeCond.Type.EQ);
isChoreographyCond.setSchema("isChoreography");
isChoreographyCond.setExpression("true");
List<GroupTO> candidates = groupLogic.search(SearchCond.getAndCond(
SearchCond.getLeafCond(idCond), SearchCond.getLeafCond(isChoreographyCond)),
1, 1, Collections.<OrderByClause>emptyList(), SyncopeConstants.ROOT_REALM, false);
if (candidates.isEmpty()) {
throw new NotFoundException("Choreography " + id);
}
return candidates.get(0);
}
private AnyObjectTO anyObjectExists(final String id) {
AnyObjectTO serviceTO = anyObjectLogic.read(id);
if (!SERVICE_TYPE.equals(serviceTO.getType())) {
throw new NotFoundException(SERVICE_TYPE + " " + id);
}
return serviceTO;
}
@PreAuthorize("hasRole('" + ChorevolutionEntitlement.CHOREOGRAPHY_CREATE + "')")
public String enact(final String name, final String enactmentEngineKey, final InputStream chorSpec) {
String generatedChoreographyId = null;
Response response;
try {
WebClient webClient = getWebClient(enactmentEngineKey, "/?choreographyName=" + name);
WebClient webClient = getEEWebClient(enactmentEngineKey, "/?choreographyName=" + name);
response = webClient.post(chorSpec);
if (response.getStatus() != Response.Status.CREATED.getStatusCode()) {
throw new WebApplicationException(response);
......@@ -200,31 +240,12 @@ public class ChoreographyLogic extends AbstractLogic<AbstractBaseBean> {
return generatedChoreographyId;
}
private GroupTO checkChoreographyExists(final String id) {
AttributeCond idCond = new AttributeCond(AttributeCond.Type.EQ);
idCond.setSchema(CHOREOGRAPHY_ID_SCHEMA);
idCond.setExpression(id);
AttributeCond isChoreographyCond = new AttributeCond(AttributeCond.Type.EQ);
isChoreographyCond.setSchema("isChoreography");
isChoreographyCond.setExpression("true");
List<GroupTO> candidates = groupLogic.search(SearchCond.getAndCond(
SearchCond.getLeafCond(idCond), SearchCond.getLeafCond(isChoreographyCond)),
1, 1, Collections.<OrderByClause>emptyList(), SyncopeConstants.ROOT_REALM, false);
if (candidates.isEmpty()) {
throw new NotFoundException("Choreography " + id);
}
return candidates.get(0);
}
@PreAuthorize("hasRole('" + ChorevolutionEntitlement.CHOREOGRAPHY_UPDATE + "')")
public void enact(final String id, final String name, final String enactmentEngineKey, final InputStream chorSpec) {
checkChoreographyExists(id);
choreographyExists(id);
try {
WebClient webClient = getWebClient(enactmentEngineKey, "/" + id + "?choreographyName=" + name);
WebClient webClient = getEEWebClient(enactmentEngineKey, "/" + id + "?choreographyName=" + name);
Response response = webClient.put(chorSpec);
if (response.getStatus() != Response.Status.ACCEPTED.getStatusCode()) {
throw new WebApplicationException(response);
......@@ -236,10 +257,10 @@ public class ChoreographyLogic extends AbstractLogic<AbstractBaseBean> {
@PreAuthorize("hasRole('" + ChorevolutionEntitlement.CHOREOGRAPHY_DELETE + "')")
public void delete(final String id, final String enactmentEngineKey) {
checkChoreographyExists(id);
choreographyExists(id);
try {
WebClient webClient = getWebClient(enactmentEngineKey, "/" + id);
WebClient webClient = getEEWebClient(enactmentEngineKey, "/" + id);
Response response = webClient.delete();
if (response.getStatus() != Response.Status.ACCEPTED.getStatusCode()) {
throw new WebApplicationException(response);
......@@ -276,7 +297,7 @@ public class ChoreographyLogic extends AbstractLogic<AbstractBaseBean> {
if (securityFilterURL != null) {
membershipPatch.getPlainAttrs().add(new AttrPatch.Builder().
attrTO(new AttrTO.Builder().
schema("Endpoint").value(securityFilterURL).build()).
schema(SECURITY_FILTER_ENDPOINT_SCHEMA).value(securityFilterURL).build()).
build());
}
servicePatch.getMemberships().add(membershipPatch);
......@@ -391,7 +412,7 @@ public class ChoreographyLogic extends AbstractLogic<AbstractBaseBean> {
case UPDATE:
// 0. check that the choreography with the passed id exists
choreography = checkChoreographyExists(id);
choreography = choreographyExists(id);
String oldName = choreography.getName();
// 1. update name and chroSpec
......@@ -452,7 +473,7 @@ public class ChoreographyLogic extends AbstractLogic<AbstractBaseBean> {
case DELETE:
// 0. check that the choreography with the passed id exists
choreography = checkChoreographyExists(id);
choreography = choreographyExists(id);
// 1. remove the schemas and the USER type extension
try {
......@@ -510,8 +531,90 @@ public class ChoreographyLogic extends AbstractLogic<AbstractBaseBean> {
final String id,
final String serviceId,
final ServiceAction action,
final String newServiceURL) {
final String argument) {
GroupTO choreography = choreographyExists(id);
AnyObjectTO service = anyObjectExists(serviceId);
MembershipTO membership = service.getMembershipMap().get(choreography.getKey());
if (membership == null) {
throw new NotFoundException(
"Service " + service.getName() + " not involved in choreography " + choreography.getName());
}
if (action == ServiceAction.REPLACE) {
// ChoreographyApi#replaceService()
} else {
AttrTO securityFilterEndpoint = membership.getPlainAttrMap().get(SECURITY_FILTER_ENDPOINT_SCHEMA);
if (securityFilterEndpoint == null || securityFilterEndpoint.getValues().isEmpty()) {
throw new NotFoundException(
"No security filter for " + service.getName() + " in choreography " + choreography.getName());
}
WebClient webClient;
Response response = null;
try {
switch (action) {
case ENABLE_SECURITY_FILTER:
webClient = getSFWebClient(
securityFilterEndpoint.getValues().get(0), SecurityFilterStatus.ENABLED.name());
response = webClient.put(null);
break;
case DISABLE_SECURITY_FILTER:
webClient = getSFWebClient(
securityFilterEndpoint.getValues().get(0), SecurityFilterStatus.DISABLED.name());
response = webClient.put(null);
break;
case ENFORCE_SECURITY_CONTEXT:
webClient = getSFWebClient(
securityFilterEndpoint.getValues().get(0), "securityContext");
response = webClient.post(argument);
break;
default:
}
if (response != null && response.getStatus() != Response.Status.OK.getStatusCode()) {
throw new WebApplicationException(response);
}
} catch (Exception e) {
throw new RuntimeException(
"While requesting " + action + " on security filter for " + service.getName()
+ " in choreography " + choreography.getName(), e);
}
}
}
@PreAuthorize("hasRole('" + ChorevolutionEntitlement.ON_CHOREOGRAPHY_SERVICE + "')")
public SecurityFilterInfo readSecurityFilterInfo(final String id, final String serviceId) {
GroupTO choreography = choreographyExists(id);
AnyObjectTO service = anyObjectExists(serviceId);
MembershipTO membership = service.getMembershipMap().get(choreography.getKey());
if (membership == null) {
throw new NotFoundException(
"Service " + service.getName() + " not involved in choreography " + choreography.getName());
}
AttrTO securityFilterEndpoint = membership.getPlainAttrMap().get(SECURITY_FILTER_ENDPOINT_SCHEMA);
if (securityFilterEndpoint == null || securityFilterEndpoint.getValues().isEmpty()) {
throw new NotFoundException(
"No security filter for " + service.getName() + " in choreography " + choreography.getName());
}
try {
WebClient webClient = getSFWebClient(securityFilterEndpoint.getValues().get(0), "info");
Response response = webClient.get();
if (response.getStatus() != Response.Status.OK.getStatusCode()) {
throw new WebApplicationException(response);
}
return response.readEntity(SecurityFilterInfo.class);
} catch (Exception e) {
throw new RuntimeException("While reading security filter information for " + service.getName()
+ " in choreography " + choreography.getName(), e);
}
}
@Override
......
......@@ -17,15 +17,18 @@ package org.apache.syncope.common.rest.api.service;
import eu.chorevolution.idm.common.types.ChoreographyAction;
import eu.chorevolution.idm.common.types.ChoreographyOperation;
import eu.chorevolution.idm.common.types.SecurityFilterInfo;
import eu.chorevolution.idm.common.types.ServiceAction;
import java.io.InputStream;
import javax.validation.constraints.NotNull;
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.QueryParam;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;
......@@ -121,8 +124,9 @@ public interface ChoreographyService extends JAXRSService {
* @param id choreography id
* @param serviceId choreography service id
* @param action action to be performed
* @param argument Security context name if {@link ServiceAction#ENFORCE}; new service id if
* {@link ServiceAction#REPLACE}
* @param argument Security context name if {@link ServiceAction#ENFORCE_SECURITY_CONTEXT};
* new service id if {@link ServiceAction#REPLACE};
* nothing if {@link ServiceAction#ENABLE_SECURITY_FILTER} or {@link ServiceAction#DISABLE_SECURITY_FILTER}
* service URL to replace the current set for the given service id
*/
@POST
......@@ -133,4 +137,19 @@ public interface ChoreographyService extends JAXRSService {
@NotNull @QueryParam("action") ServiceAction action,
@QueryParam("argument") String argument);
/**
* Returns information about the current status of the security filter instance associated to the given service,
* in the given choreography (if available).
*
* @param id choreography id
* @param serviceId choreography service id
* @return information about the current status of the security filter instance associated to the given service,
* in the given choreography (if available)
*/
@GET
@Path("{id}/{serviceId}/securityFilterInfo")
@Produces({ MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML })
SecurityFilterInfo readSecurityFilterInfo(
@NotNull @PathParam("id") String id,
@NotNull @PathParam("serviceId") String serviceId);
}
......@@ -17,6 +17,7 @@ package org.apache.syncope.core.rest.cxf.service;
import eu.chorevolution.idm.common.types.ChoreographyAction;
import eu.chorevolution.idm.common.types.ChoreographyOperation;
import eu.chorevolution.idm.common.types.SecurityFilterInfo;
import eu.chorevolution.idm.common.types.ServiceAction;
import org.apache.syncope.core.logic.ChoreographyLogic;
import java.io.InputStream;
......@@ -74,4 +75,9 @@ public class ChoreographyServiceImpl extends AbstractServiceImpl implements Chor
logic.onChoreographyService(id, serviceId, action, newServiceURL);
}
@Override
public SecurityFilterInfo readSecurityFilterInfo(final String id, final String serviceId) {
return logic.readSecurityFilterInfo(id, serviceId);
}
}
Supports Markdown
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment