package com.nateyolles.sling.publick.services.impl; import java.util.Map; import org.apache.commons.lang.StringUtils; import org.apache.felix.scr.annotations.Activate; import org.apache.felix.scr.annotations.Component; import org.apache.felix.scr.annotations.Deactivate; import org.apache.felix.scr.annotations.Reference; import org.apache.felix.scr.annotations.Service; import org.apache.felix.scr.annotations.Properties; import org.apache.felix.scr.annotations.Property; import org.apache.http.impl.client.HttpClients; import org.apache.sling.api.SlingHttpServletRequest; import org.apache.sling.api.resource.ModifiableValueMap; import org.apache.sling.api.resource.PersistenceException; import org.apache.sling.api.resource.Resource; import org.apache.sling.api.resource.ValueMap; import org.osgi.service.component.ComponentContext; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import ac.simons.akismet.Akismet; import ac.simons.akismet.AkismetComment; import ac.simons.akismet.AkismetException; import com.nateyolles.sling.publick.PublickConstants; import com.nateyolles.sling.publick.services.HttpService; import com.nateyolles.sling.publick.services.LinkRewriterService; import com.nateyolles.sling.publick.services.OsgiConfigurationService; import com.nateyolles.sling.publick.services.AkismetService; import org.osgi.framework.Constants; /** * Akismet service to get and set settings and communicate with * www.akismet.com. Verify your key, validate comments, submit * spam and ham. * * Checking comments returns whether the comment is spam. That * means a false return means the comment is valid while a true * return means the comment is spam. To test, send * "viagra-test-123" as the author and it will always trigger a * true response. * * For false positives, a comment determined to be spam that isn't, * correct the situation by submitting ham. * * To identify a comment as spam that wasn't caught before, submit * the comment as spam. */ @Service(value = AkismetService.class) @Component(metatype = true, immediate = true, name = "Publick Akismet settings", description = "Akismet settings for www.akismet.com's service.") @Properties({ @Property(name = AkismetServiceImpl.AKISMET_API_KEY, label = "API Key", description = "The Akismet API key."), @Property(name = AkismetServiceImpl.AKISMET_DOMAIN_NAME, label = "Domain Name", description = "The domain name of the blog starting with http:// or https://"), @Property(name = AkismetServiceImpl.AKISMET_ENABLED, boolValue = AkismetServiceImpl.ENABLED_DEFAULT_VALUE, label = "Enabled", description = "Enable Akismet."), @Property(name = Constants.SERVICE_DESCRIPTION, value = "Akismet settings for www.akismet.com's service."), @Property(name = Constants.SERVICE_VENDOR, value = "Publick") }) public class AkismetServiceImpl implements AkismetService { /** Service to get and set OSGi properties. */ @Reference private OsgiConfigurationService osgiService; /** HTTP helpers */ @Reference private HttpService httpService; /** Rewrite links to get permanent path to comment */ @Reference private LinkRewriterService linkRewriter; /** PID of the current OSGi component */ private static final String COMPONENT_PID = "Publick Akismet settings"; /** Default value for enabled */ public static final boolean ENABLED_DEFAULT_VALUE = false; /** Type of Akismet submission */ public static final String AKISMET_COMMENT_TYPE = "comment"; /** The logger. */ private static final Logger LOGGER = LoggerFactory.getLogger(AkismetServiceImpl.class); /** * Set multiple properties for the Akismet Settings service. * * This is useful for setting multiple properties as the same * time in that the OSGi component will only be updated once * and thus reset only once. * * @param properties A map of properties to set. * @return true if save was successful. */ public boolean setProperties(final Map<String, Object> properties) { return osgiService.setProperties(COMPONENT_PID, properties); } /** * Is the Akismet service enabled. * * @return True if the Akismet service is enabled. */ public boolean getEnabled() { return osgiService.getBooleanProperty(COMPONENT_PID, AKISMET_ENABLED, ENABLED_DEFAULT_VALUE); } /** * Set whether the Akismet service is enabled. * * @param enabled The enabled property to set. * @return true if the save was successful. */ public boolean setEnabled(final boolean enabled) { return osgiService.setProperty(COMPONENT_PID, AKISMET_ENABLED, enabled); } /** * Get the Akismet API key. * * @return The Akismet API key. */ public String getApiKey() { return osgiService.getStringProperty(COMPONENT_PID, AKISMET_API_KEY, null); } /** * Set the Akismet API key. * * @param apiKey The API key property to set. * @return true if the save was successful. */ public boolean setApiKey(final String apiKey) { return osgiService.setProperty(COMPONENT_PID, AKISMET_API_KEY, apiKey); } /** * Get the Akismet domain name. * * @return The Akismet domain name. */ public String getDomainName() { return osgiService.getStringProperty(COMPONENT_PID, AKISMET_DOMAIN_NAME, null); } /** * Set the Akismet domain name. * * @param domainName The domain nameproperty to set. * @return true if the save was successful. */ public boolean setDomainName(final String domainName) { return osgiService.setProperty(COMPONENT_PID, AKISMET_DOMAIN_NAME, domainName); } /** * Verify that Akismet is configured correctly using the saved * API key and domain name settings. * * Sends a request to the Akismet servers with the API key and * domain name. * * @return true if Akismet is configured correctly. */ public boolean verifyKey() { return verifyKey(getApiKey(), getDomainName()); } /** * Verify that Akismet is configured correctly using the given * API key and domain name. * * Sends a request to the Akismet servers with the API key and * domain name. * * @param apiKey The Akismet API key to test. * @param domainName The Akismet domain name to test. * @return true if Akismet is configured correctly. */ public boolean verifyKey(final String apiKey, final String domainName) { final Akismet akismet = new Akismet(HttpClients.createDefault()); boolean result = false; akismet.setApiKey(apiKey); akismet.setApiConsumer(domainName); try { result = akismet.verifyKey(); } catch (AkismetException e) { LOGGER.error("Could not verify Akismet Key", e); } return result; } /** * Check comment against Akismet servers. * * Be aware that this method returns whether the submission is spam or not. * A false response means that the submission was successful and that the * comment is not spam. This behavior is inline with Akismet's behavior. * * @param commentResource The publick:comment resource to act upon. * @return true if comment is spam, false if comment is valid. */ public boolean isSpam(final Resource commentResource) { final boolean result = doAkismet(AkismetAction.CHECK_COMMENT, commentResource); if (result) { try { final ModifiableValueMap properties = commentResource.adaptTo(ModifiableValueMap.class); properties.put(PublickConstants.COMMENT_PROPERTY_SPAM, true); commentResource.getResourceResolver().commit(); } catch (PersistenceException e) { LOGGER.error("Could not save spam properties", e); } } return result; } /** * Submit comment as spam to the Akismet servers. * * If a comment gets checked and incorrectly is reported as ham, this will * submit it back to the servers as spam to help make the world a better * place. * * @param commentResource The publick:comment resource to act upon. * @return true if submission was successful. */ public boolean submitSpam(final Resource commentResource) { final boolean result = doAkismet(AkismetAction.SUBMIT_SPAM, commentResource); if (result) { try { final ModifiableValueMap properties = commentResource.adaptTo(ModifiableValueMap.class); properties.put(PublickConstants.COMMENT_PROPERTY_SPAM, true); properties.put(PublickConstants.COMMENT_PROPERTY_DISPLAY, false); commentResource.getResourceResolver().commit(); } catch (PersistenceException e) { LOGGER.error("Could not save spam properties", e); } } return result; } /** * Submit comment as ham to the Akismet servers. * * If a comment gets checked and incorrectly is reported as spam, this will * submit it back to the servers as ham to correct a false positive. * * @param commentResource The publick:comment resource to act upon. * @return true if submission was successful. */ public boolean submitHam(final Resource commentResource) { final boolean result = doAkismet(AkismetAction.SUBMIT_HAM, commentResource); if (result) { try { final ModifiableValueMap properties = commentResource.adaptTo(ModifiableValueMap.class); properties.put(PublickConstants.COMMENT_PROPERTY_SPAM, false); properties.put(PublickConstants.COMMENT_PROPERTY_DISPLAY, true); commentResource.getResourceResolver().commit(); } catch (PersistenceException e) { LOGGER.error("Could not save spam properties", e); } } return result; } /** * Perform Akismet actions * * @param action The Akismet action to perform * @param commentResource The publick:comment resource to act upon. * @return True True if Spam or Ham submission was successful. */ private boolean doAkismet(final AkismetAction action, final Resource commentResource) { final Akismet akismet = new Akismet(HttpClients.createDefault()); final AkismetComment comment = getAkismetComment(commentResource); akismet.setApiKey(getApiKey()); akismet.setApiConsumer(getDomainName()); boolean result = false; try { if (action == AkismetAction.CHECK_COMMENT) { result = akismet.commentCheck(comment); } else if (action == AkismetAction.SUBMIT_SPAM) { result = akismet.submitSpam(comment); } else if (action == AkismetAction.SUBMIT_HAM) { result = akismet.submitHam(comment); } } catch (AkismetException e) { LOGGER.error("Could not communication with Akismet", e); } return result; } /** * Get an AkismetComment from a publick:comment resource. * * @param commentResource The publick:comment resource. * @return Akismet Comment created from the properties of the comment resource. */ private AkismetComment getAkismetComment(final Resource commentResource) { final AkismetComment akismetComment = new AkismetComment(); if (commentResource != null) { final ValueMap properties = commentResource.getValueMap(); // Get external link taking extensionless URLs into account String externalLink = StringUtils.removeEnd(getDomainName(), "/"); externalLink = externalLink.concat(linkRewriter.rewriteLink(commentResource.getPath())); akismetComment.setUserIp(properties.get(PublickConstants.COMMENT_PROPERTY_USER_IP, String.class)); akismetComment.setUserAgent(properties.get(PublickConstants.COMMENT_PROPERTY_USER_AGENT, String.class)); akismetComment.setReferrer(properties.get(PublickConstants.COMMENT_PROPERTY_REFERRER, String.class)); akismetComment.setPermalink(externalLink); akismetComment.setCommentType(AKISMET_COMMENT_TYPE); akismetComment.setCommentAuthor(properties.get(PublickConstants.COMMENT_PROPERTY_AUTHOR, String.class)); akismetComment.setCommentContent(properties.get(PublickConstants.COMMENT_PROPERTY_COMMENT, String.class)); } return akismetComment; } /** * Service activation. */ @Activate protected void activate(Map<String, Object> properties) { } /** * Service Deactivation. * * @param ctx The current component context. */ @Deactivate protected void deactivate(ComponentContext ctx) { } /** * Akismet actions */ private enum AkismetAction { CHECK_COMMENT, SUBMIT_SPAM, SUBMIT_HAM } }