package com.threatconnect.sdk.parser.service.writer;
import com.google.gson.Gson;
import com.threatconnect.sdk.client.reader.AbstractGroupReaderAdapter;
import com.threatconnect.sdk.client.reader.ReaderAdapterFactory;
import com.threatconnect.sdk.client.response.IterableResponse;
import com.threatconnect.sdk.client.writer.AbstractGroupWriterAdapter;
import com.threatconnect.sdk.client.writer.WriterAdapterFactory;
import com.threatconnect.sdk.conn.Connection;
import com.threatconnect.sdk.exception.FailedResponseException;
import com.threatconnect.sdk.parser.model.Address;
import com.threatconnect.sdk.parser.model.Attribute;
import com.threatconnect.sdk.parser.model.EmailAddress;
import com.threatconnect.sdk.parser.model.File;
import com.threatconnect.sdk.parser.model.Group;
import com.threatconnect.sdk.parser.model.GroupType;
import com.threatconnect.sdk.parser.model.Host;
import com.threatconnect.sdk.parser.model.Indicator;
import com.threatconnect.sdk.parser.model.Url;
import com.threatconnect.sdk.parser.service.save.AssociateFailedException;
import com.threatconnect.sdk.parser.service.save.DeleteItemFailedException;
import com.threatconnect.sdk.parser.service.save.SaveItemFailedException;
import com.threatconnect.sdk.server.entity.Group.Type;
import com.threatconnect.sdk.server.response.entity.ApiEntitySingleResponse;
import com.threatconnect.sdk.util.ApiFilterType;
import java.io.IOException;
import java.util.Arrays;
import java.util.List;
public abstract class GroupWriter<E extends Group, T extends com.threatconnect.sdk.server.entity.Group>
extends Writer
{
protected final E groupSource;
private final Class<T> tcModelClass;
private final Type tcGroupType;
private T savedGroup;
public GroupWriter(final Connection connection, final E source, final Class<T> tcModelClass,
final Type tcGroupType)
{
super(connection);
this.groupSource = source;
this.tcModelClass = tcModelClass;
this.tcGroupType = tcGroupType;
}
/**
* Saves the group with the associated owner
*
* @param ownerName
* the owner name of the group
* @return the saved group
* @throws SaveItemFailedException
* if there was an issue saving this item
* @throws IOException
* if there was an exception communicating with the server
*/
public T saveGroup(final String ownerName)
throws SaveItemFailedException, IOException
{
try
{
// create the writer
AbstractGroupWriterAdapter<T> writer = createWriterAdapter();
// map the object
T group = mapper.map(groupSource, tcModelClass);
// attempt to lookup the indicator by the id
T readGroup = lookupGroup(groupSource.getName(), ownerName);
// check to see if the indicator was found on the server
if (null != readGroup)
{
// use this group as the saved group
savedGroup = readGroup;
return savedGroup;
}
if (logger.isDebugEnabled())
{
Gson gson = new Gson();
logger.info("Saving group: {}", gson.toJson(group));
}
// save the object
ApiEntitySingleResponse<T, ?> response = writer.create(group);
// check to see if this call was successful
if (response.isSuccess())
{
savedGroup = response.getItem();
// make sure the list of attributes is not empty
if (!groupSource.getAttributes().isEmpty())
{
// for each of the attributes of this group
for (Attribute attribute : groupSource.getAttributes())
{
// map the attribute to a server entity
com.threatconnect.sdk.server.entity.Attribute attr =
mapper.map(attribute, com.threatconnect.sdk.server.entity.Attribute.class);
// save the attributes for this group
ApiEntitySingleResponse<?, ?> attrResponse = writer.addAttribute(getSavedGroupID(), attr);
// check to see if this was not successful
if (!attrResponse.isSuccess())
{
logger.warn("Failed to save attribute \"{}\" for group id: {}", attribute.getType(),
getSavedGroupID());
logger.warn(attrResponse.getMessage());
logger.warn(attribute.getValue());
}
}
}
// make sure the list of tags is not empty
if (!groupSource.getTags().isEmpty())
{
// for each of the tags
for (String tag : groupSource.getTags())
{
// make sure this tag is not empty
if (null != tag && !tag.isEmpty())
{
// save the tag for this group
ApiEntitySingleResponse<?, ?> tagResponse = writer.associateTag(getSavedGroupID(), tag);
// check to see if this was not successful
if (!tagResponse.isSuccess())
{
logger.warn("Failed to save tag \"{}\" for group id: {}", tag, getSavedGroupID());
logger.warn(tagResponse.getMessage());
}
}
else
{
logger.warn("Skipping blank tag for: {}", getSavedGroupID());
}
}
}
return getSavedGroup();
}
else
{
throw new SaveItemFailedException(groupSource, response.getMessage());
}
}
catch (FailedResponseException e)
{
throw new SaveItemFailedException(groupSource, e);
}
}
/**
* Associates an indicator with the saved group object of this writer class
*
* @param indicator
* the indicator to associate to this group
* @throws AssociateFailedException
* if there as an issue associating the indicator to this group
* @throws IOException
* if there was an exception communicating with the server
*/
public void associateIndicator(final Indicator indicator)
throws AssociateFailedException, IOException
{
try
{
// create a new group writer to do the association
AbstractGroupWriterAdapter<T> writer = createWriterAdapter();
ApiEntitySingleResponse<?, ?> response = null;
String indicatorID = indicator.getIdentifier();
// switch based on the indicator type
switch (indicator.getIndicatorType())
{
case Address.INDICATOR_TYPE:
response = writer.associateIndicatorAddress(getSavedGroupID(), indicatorID);
break;
case EmailAddress.INDICATOR_TYPE:
response = writer.associateIndicatorEmailAddress(getSavedGroupID(), indicatorID);
break;
case File.INDICATOR_TYPE:
File file = (File) indicator;
List<String> hashes = Arrays.asList(file.getMd5(), file.getSha1(), file.getSha256());
// :TODO: the plural version of this method throws an internal sdk error so we
// have to use the single method and loop through the hashes for each of the
// hashes
for (String hash : hashes)
{
// make sure the hash is not null or empty
if (null != hash && !hash.isEmpty())
{
indicatorID = hash;
response = writer.associateIndicatorFile(getSavedGroupID(), indicatorID);
}
}
break;
case Host.INDICATOR_TYPE:
response = writer.associateIndicatorHost(getSavedGroupID(), indicatorID);
break;
case Url.INDICATOR_TYPE:
response = writer.associateIndicatorUrl(getSavedGroupID(), indicatorID);
break;
default:
response = null;
//:FIXME: associate custom indicator to group? This method does not yet exist
//response = writer.associateIndicatorCustom(getSavedGroupID(), indicatorID);
break;
}
// check to see if this was not successful
if (null != response && !response.isSuccess())
{
logger.warn("Failed to associate indicator \"{}\" with group id: {}", indicatorID, getSavedGroupID());
logger.warn(response.getMessage());
}
}
catch (FailedResponseException e)
{
throw new AssociateFailedException(e);
}
}
public void associateGroup(final GroupType groupType, final Integer savedID)
throws AssociateFailedException, IOException
{
try
{
// create a new indicator writer to do the association
AbstractGroupWriterAdapter<T> writer = createWriterAdapter();
ApiEntitySingleResponse<?, ?> response = null;
// switch based on the group type
switch (groupType)
{
case ADVERSARY:
response = writer.associateGroupAdversary(getSavedGroupID(), savedID);
break;
case DOCUMENT:
response = writer.associateGroupDocument(getSavedGroupID(), savedID);
break;
case EMAIL:
response = writer.associateGroupEmail(getSavedGroupID(), savedID);
break;
case INCIDENT:
response = writer.associateGroupIncident(getSavedGroupID(), savedID);
break;
case SIGNATURE:
response = writer.associateGroupSignature(getSavedGroupID(), savedID);
break;
case THREAT:
response = writer.associateGroupThreat(getSavedGroupID(), savedID);
break;
default:
response = null;
break;
}
// check to see if this was not successful
if (null != response && !response.isSuccess())
{
logger.warn("Failed to associate group id \"{}\" with group: {}", savedID, getSavedGroupID());
logger.warn(response.getMessage());
}
}
catch (FailedResponseException e)
{
throw new AssociateFailedException(e);
}
}
/**
* Deletes the group from the server if it exists
*
* @param ownerName
* the owner name of the group
* @throws DeleteItemFailedException
* if there was any reason the group could not be deleted
*/
public void deleteGroup(final String ownerName) throws DeleteItemFailedException
{
try
{
// create the writer
AbstractGroupWriterAdapter<T> writer = createWriterAdapter();
// look up the group from the server
T readGroup = lookupGroup(groupSource.getName(), ownerName);
// make sure the group is not null
if (null != readGroup)
{
// lookup the indicator from the server
ApiEntitySingleResponse<?, ?> response = writer.delete(readGroup.getId(), ownerName);
// check to see if this was not successful
if (!response.isSuccess())
{
throw new DeleteItemFailedException(response.getMessage());
}
}
}
catch (IOException e)
{
throw new DeleteItemFailedException(e);
}
}
/**
* Looks up a group by the group name
*
* @param groupName
* the name of the group to look up
* @return the existing indicator
* @throws FailedResponseException
* if the server returned an invalid response
* @throws IOException
* if there was an exception communicating with the server
*/
protected T lookupGroup(final String groupName, final String ownerName)
{
AbstractGroupReaderAdapter<T> reader = createReaderAdapter();
// make sure the group name is not null
if (null != groupName)
{
try
{
// lookup the group by the group name
IterableResponse<T> readGroups =
reader.getForFilters(ownerName, false, ApiFilterType.filterName().equal(groupName));
// check to see if the read groups is not null
if (null != readGroups)
{
// for each of the indicators
for (T group : readGroups)
{
// need to perform another lookup on the server to get all of the
// information for this group and not just the summary
return reader.getById(group.getId());
}
}
}
catch (FailedResponseException | IOException e)
{
logger.trace("Failed to lookup group name \"{}\"", groupName);
logger.trace(e.getMessage(), e);
return null;
}
}
return null;
}
/**
* Retrieves the id of the saved group
*
* @return the id of the saved group
*/
protected Integer getSavedGroupID()
{
com.threatconnect.sdk.server.entity.Group savedGroup = getSavedGroup();
// make sure the saved group is not null
if (null != savedGroup)
{
return savedGroup.getId();
}
throw new IllegalStateException("group is not saved");
}
/**
* Creates a reader adapter for this class
*
* @return the reader adapter for this indicator
*/
protected AbstractGroupReaderAdapter<T> createReaderAdapter()
{
return ReaderAdapterFactory.createGroupReader(tcGroupType, connection);
}
/**
* A convenience method for creating a writer adapter for this class
*
* @return the group writer adapter
*/
protected AbstractGroupWriterAdapter<T> createWriterAdapter()
{
return WriterAdapterFactory.createGroupWriter(tcGroupType, connection);
}
public T getSavedGroup()
{
return savedGroup;
}
}