├── .gitignore ├── CHANGELOG ├── Gemfile ├── MIT-LICENSE ├── README.md ├── Rakefile ├── lib ├── rakismet.rb └── rakismet │ ├── middleware.rb │ ├── model.rb │ ├── railtie.rb │ └── version.rb ├── rakismet.gemspec └── spec ├── .rspec ├── models ├── block_params_spec.rb ├── custom_params_spec.rb ├── extended_params_spec.rb ├── rakismet_model_spec.rb ├── request_params_spec.rb └── subclass_spec.rb ├── rakismet_middleware_spec.rb ├── rakismet_spec.rb └── spec_helper.rb /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | *.tmproj 3 | tmtags 4 | *.swp 5 | *.swo 6 | *.un~ 7 | coverage 8 | rdoc 9 | pkg 10 | Gemfile.lock 11 | .bundle -------------------------------------------------------------------------------- /CHANGELOG: -------------------------------------------------------------------------------- 1 | = 1.5.4 2 | * Don't prefix permalink param with comment_ 3 | = 1.5.3 4 | * Use HTTPS to connect to Akismet [Christian Wesselhoeft] 5 | = 1.5.2 6 | * Don't try to send Rails.version unless it's actually defined [Brett Bender] 7 | = 1.5.1 8 | * Send user_role attribute to Akismet without comment prefix [Hugo Bauer] 9 | = 1.5.0 10 | * Send HTTP_ env varialbes to Akismet [Eric Hochberger] 11 | = 1.4.0 12 | * Allow endpoint to be specified with a proc for multitenant applications [Bradly Feeley] 13 | * Add Akistmet permalink attribute [Eric Hochberger] 14 | = 1.3.0 15 | * Clean up gemspec and load paths [Steven Harman] 16 | * Add Akismet is_test param [Steven Harman] 17 | * Add Akismet user_role attribute [Steven Harman] 18 | = 1.2.1 19 | * Fix deprecated usage of HTTPResponse for Ruby 1.9.3 [Leonid Shevtsov] 20 | = 1.2.0 21 | * Rakismet attribute mappings are now inheritable 22 | = 1.1.2 23 | * Explicitly load version 24 | = 1.1.1 25 | * Fix SafeBuffer error under Rails 3.0.8 and 3.0.9 [Brandon Ferguson] 26 | * Readme cleanup [Zeke Sikelianos] 27 | * Drop Jeweler in favor of Bundler's gem tasks 28 | = 1.1.0 29 | * Add HTTP Proxy support [Francisco Trindade] 30 | = 1.0.1 31 | * Fix hash access for Ruby 1.9 [Alex Crichton] 32 | = 1.0.0 33 | * Update for Rails 3 34 | * Remove filters and replace with middleware 35 | * Remove initializers and replace with Railtie 36 | = 0.4.0 37 | * Rakismet is no longer injected into ActiveRecord or ActionController 38 | * API changes to support newly decoupled modules 39 | * Use Jeweler to manage gemspec 40 | = 0.3.6 41 | * Allow attributes to fall through to methods or AR attributes 42 | = 0.3.5 43 | * Added gemspec and rails/init.rb so rakismet can work as a gem [Michael Air] 44 | * Added generator template and manifest [Michael Air] 45 | = 0.3.0 46 | * Abstract out Rakismet version string 47 | * Set default Akismet Host 48 | * Abstract out the Akismet host [Mike Burns] 49 | * Started keeping a changelog :P 50 | -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | source "http://rubygems.org" 2 | 3 | gemspec 4 | -------------------------------------------------------------------------------- /MIT-LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2008 Josh French 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining 4 | a copy of this software and associated documentation files (the 5 | "Software"), to deal in the Software without restriction, including 6 | without limitation the rights to use, copy, modify, merge, publish, 7 | distribute, sublicense, and/or sell copies of the Software, and to 8 | permit persons to whom the Software is furnished to do so, subject to 9 | the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be 12 | included in all copies or substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 15 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 16 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 17 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 18 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 19 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 20 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Rakismet 2 | ======== 3 | 4 | **Akismet** () is a collaborative spam filtering service. 5 | **Rakismet** is easy Akismet integration with Rails and rack apps. TypePad's 6 | AntiSpam service and generic Akismet endpoints are supported. 7 | 8 | Compatibility 9 | ============= 10 | 11 | **Rakismet >= 1.0.0** work with Rails 3 and other Rack-based frameworks. 12 | 13 | **Rakismet <= 0.4.2** is compatible with Rails 2. 14 | 15 | Getting Started 16 | =============== 17 | 18 | Add the Rakismet gem to your Gemfile 19 | 20 | ```ruby 21 | gem 'rakismet' 22 | ``` 23 | 24 | Once you've added the Rakismet gem to your Gemfile and installed it with ``bundle install``, 25 | you'll need an API key. Head on over to http://akismet.com/signup/ and sign up 26 | for a new username. 27 | 28 | Configure the Rakismet key and the URL of your application by setting the following 29 | in application.rb: 30 | 31 | ```ruby 32 | config.rakismet.key = 'your wordpress key' 33 | config.rakismet.url = 'http://yourdomain.com/' 34 | ``` 35 | 36 | or an initializer, for example `config/initializers/rakismet.rb`: 37 | 38 | ```ruby 39 | YourApp::Application.config.rakismet.key = 'your wordpress key' 40 | YourApp::Application.config.rakismet.url = 'http://yourdomain.com/' 41 | ``` 42 | 43 | If you wish to use another Akismet-compatible API provider such as TypePad's 44 | antispam service, you'll also need to set `config.rakismet.host` to your service 45 | provider's endpoint. 46 | 47 | If you want to use a proxy to access akismet (i.e. your application is behind a 48 | firewall), set the proxy_host and proxy_port option. 49 | 50 | ```ruby 51 | config.rakismet.proxy_host = 'http://yourdomain.com/' 52 | config.rakismet.proxy_port = '8080' 53 | ``` 54 | 55 | If your Rails app is a multitenant application, you can specify your Rakismet url as a proc: 56 | 57 | ```ruby 58 | config.rakismet.url = Proc.new { ApplicationController.current_tenant.url } 59 | ``` 60 | 61 | Checking For Spam 62 | ----------------- 63 | 64 | First, introduce Rakismet to your model: 65 | 66 | ```ruby 67 | class Comment 68 | include Rakismet::Model 69 | end 70 | ``` 71 | 72 | With Rakismet mixed in to your model, you'll get three instance methods for interacting with 73 | Akismet: 74 | 75 | * `spam?` submits the comment to Akismet and returns true if Akismet thinks the comment is spam, false if not. 76 | * `ham!` resubmits a valid comment that Akismet erroneously marked as spam (marks it as a false positive.) 77 | * `spam!` resubmits a spammy comment that Akismet missed (marks it as a false negative.) 78 | 79 | The `ham!` and `spam!` methods will change the value of `spam?` but their 80 | primary purpose is to send feedback to Akismet. The service works best when you 81 | help correct the rare mistake; please consider using these methods if you're 82 | moderating comments or otherwise reviewing the Akismet responses. 83 | 84 | Configuring Your Model 85 | ---------------------- 86 | 87 | Rakismet sends the following information to the spam-hungry robots at Akismet: 88 | 89 | author : name submitted with the comment 90 | author_url : URL submitted with the comment 91 | author_email : email submitted with the comment 92 | comment_type : Defaults to comment but you can set it to trackback, pingback, or something more appropriate 93 | content : the content submitted 94 | permalink : the permanent URL for the entry the comment belongs to 95 | user_ip : IP address used to submit this comment 96 | user_agent : user agent string 97 | referrer : referring URL (note the spelling) 98 | 99 | By default, Rakismet just looks for attributes or methods on your class that 100 | match these names. You don't have to have accessors that match these exactly, 101 | however. If yours differ, just tell Rakismet what to call them: 102 | 103 | ```ruby 104 | class Comment 105 | include Rakismet::Model 106 | attr_accessor :commenter_name, :commenter_email 107 | rakismet_attrs :author => :commenter_name, :author_email => :commenter_email 108 | end 109 | ``` 110 | 111 | Or you can pass in a proc, to access associations: 112 | 113 | ```ruby 114 | class Comment < ActiveRecord::Base 115 | include Rakismet::Model 116 | belongs_to :author 117 | rakismet_attrs :author => proc { author.name }, 118 | :author_email => proc { author.email } 119 | end 120 | ``` 121 | 122 | You can even hard-code specific fields: 123 | 124 | ```ruby 125 | class Trackback 126 | include Rakismet::Model 127 | rakismet_attrs :comment_type => "trackback" 128 | end 129 | ``` 130 | 131 | Optional Request Variables 132 | -------------------------- 133 | 134 | Akismet wants certain information about the request environment: remote IP, the 135 | user agent string, and the HTTP referer when available. Normally, Rakismet 136 | asks your model for these. Storing this information on your model allows you to 137 | call the `spam?` method at a later time. For instance, maybe you're storing your 138 | comments in an administrative queue or processing them with a background job. 139 | 140 | You don't need to have these three attributes on your model, however. If you 141 | choose to omit them, Rakismet will instead look at the current request (if one 142 | exists) and take the values from the request object instead. 143 | 144 | This means that if you are **not storing the request variables**, you must call 145 | `spam?` from within the controller action that handles comment submissions. That 146 | way the IP, user agent, and referer will belong to the person submitting the 147 | comment. If you're not storing the request variables and you call `spam?` at a 148 | later time, the request information will be missing or invalid and Akismet won't 149 | be able to do its job properly. 150 | 151 | If you've decided to handle the request variables yourself, you can disable the 152 | middleware responsible for tracking the request information by adding this to 153 | your app initialization: 154 | 155 | ```ruby 156 | config.rakismet.use_middleware = false 157 | ``` 158 | 159 | Additionally, the middleware will send along additional env variables starting with 160 | HTTP_ to Akismet. If you wish to block any sensitive user information, use: 161 | 162 | ```ruby 163 | config.rakismet.excluded_headers = ['HTTP_COOKIE','HTTP_SENSITIVE'] 164 | ``` 165 | 166 | excluded_headers will default to ['HTTP_COOKIE'] 167 | 168 | Testing 169 | ------- 170 | 171 | Rakismet can be configued to tell Akismet that it should operate in test mode - 172 | so Akismet will not change its behavior based on any test API calls, meaning 173 | they will have no training effect. That means your tests can be somewhat 174 | repeatable in the sense that one test won't influence subsequent calls. 175 | 176 | You can configure Rakismet for test mode via application.rb: 177 | 178 | ```ruby 179 | config.rakismet.test = false # <- default 180 | config.rakismet.test = true 181 | ``` 182 | 183 | Or via an initializer: 184 | 185 | ```ruby 186 | YourApp::Application.config.rakismet.test = false # <- default 187 | YourApp::Application.config.rakismet.test = true 188 | ``` 189 | 190 | **NOTE**: When running in Rails, Rakismet will run in test mode when your Rails 191 | environment is `test` or `development`, unless explictly configured otherwise. 192 | Outside of Rails Rakismet defaults to test mode turned **off**. 193 | 194 | 195 | Verifying Responses 196 | ------------------- 197 | 198 | If you want to see what's happening behind the scenes, after you call one of 199 | `@comment.spam?`, `@comment.spam!` or `@comment.ham!` you can check 200 | `@comment.akismet_response`. 201 | 202 | This will contain the last response from the Akismet server. In the case of 203 | `spam?` it should be `true` or `false.` For `spam!` and `ham!` it should be 204 | `Feedback received.` If Akismet returned an error instead (e.g. if you left out 205 | some required information) this will contain the error message. 206 | 207 | FAQ 208 | === 209 | 210 | Why does Akismet think all of my test data is spam? 211 | --------------------------------------------------- 212 | 213 | Akismet needs enough information to decide if your test data is spam or not. 214 | Try to supply as much as possible, especially the author name and request 215 | variables. 216 | 217 | How can I simulate a spam submission? 218 | ------------------------------------- 219 | 220 | Most people have the opposite problem, where Akismet doesn't think anything is 221 | spam. The only guaranteed way to trigger a positive spam response is to set the 222 | comment author to "viagra-test-123". 223 | To simulate a negative (not spam) result, set user_role to administrator, and all 224 | other required fields populated with typical values. The Akismet API will always 225 | return a false response. 226 | 227 | If you've done this and `spam?` is still returning false, you're probably 228 | missing the user IP or one of the key/url config variables. One way to check is 229 | to call `@comment.akismet_response`. If you are missing a required field or 230 | there was another error, this will hold the Akismet error message. If your comment 231 | was processed normally, this value will simply be `true` or `false`. 232 | 233 | Can I use Rakismet with a different ORM or framework? 234 | ----------------------------------------------------- 235 | 236 | Sure. Rakismet doesn't care what your persistence layer is. It will work with 237 | Datamapper, a NoSQL store, or whatever next month's DB flavor is. 238 | 239 | Rakismet also has no dependencies on Rails or any of its components, and only 240 | uses a small Rack middleware object to do some of its magic. Depending on your 241 | framework, you may have to modify this slightly and/or manually place it in your 242 | stack. 243 | 244 | You'll also need to set a few config variables by hand. Instead of 245 | `config.rakismet.key`, `config.rakismet.url`, and `config.rakismet.host`, set 246 | these values directly with `Rakismet.key`, `Rakismet.url`, and `Rakismet.host`. 247 | 248 | --------------------------------------------------------------------------- 249 | 250 | If you have any implementation or usage questions, don't hesitate to get in 251 | touch: josh@vitamin-j.com. 252 | 253 | Copyright (c) 2008 Josh French, released under the MIT license 254 | -------------------------------------------------------------------------------- /Rakefile: -------------------------------------------------------------------------------- 1 | require 'bundler' 2 | Bundler::GemHelper.install_tasks 3 | 4 | require 'rspec/core/rake_task' 5 | RSpec::Core::RakeTask.new do |spec| 6 | spec.rspec_opts = ["--color", "--format progress"] 7 | end 8 | 9 | task :default => :spec -------------------------------------------------------------------------------- /lib/rakismet.rb: -------------------------------------------------------------------------------- 1 | require 'net/http' 2 | require 'uri' 3 | require 'cgi' 4 | require 'yaml' 5 | 6 | require 'rakismet/model' 7 | require 'rakismet/middleware' 8 | require 'rakismet/version' 9 | require 'rakismet/railtie.rb' if defined?(Rails) 10 | 11 | module Rakismet 12 | Request = Struct.new(:user_ip, :user_agent, :referrer, :http_headers) 13 | Undefined = Class.new(NameError) 14 | 15 | class << self 16 | attr_accessor :key, :url, :host, :proxy_host, :proxy_port, :test, :excluded_headers 17 | 18 | def excluded_headers 19 | @excluded_headers || ['HTTP_COOKIE'] 20 | end 21 | 22 | def request 23 | @request ||= Request.new 24 | end 25 | 26 | def url 27 | @url.is_a?(Proc) ? @url.call : @url 28 | end 29 | 30 | def set_request_vars(env) 31 | request.user_ip, request.user_agent, request.referrer = 32 | env['REMOTE_ADDR'], env['HTTP_USER_AGENT'], env['HTTP_REFERER'] 33 | 34 | # Collect all CGI-style HTTP_ headers except cookies for privacy.. 35 | request.http_headers = env.select { |k,v| k =~ /^HTTP_/ }.reject { |k,v| excluded_headers.include? k } 36 | end 37 | 38 | def clear_request 39 | @request = Request.new 40 | end 41 | 42 | def headers 43 | @headers ||= begin 44 | user_agent = "Rakismet/#{Rakismet::VERSION}" 45 | 46 | if defined?(Rails) && Rails.respond_to?(:version) 47 | user_agent = "Rails/#{Rails.version} | " + user_agent 48 | end 49 | 50 | { 'User-Agent' => user_agent, 'Content-Type' => 'application/x-www-form-urlencoded' } 51 | end 52 | end 53 | 54 | def validate_key 55 | validate_config 56 | akismet = URI.parse(verify_url) 57 | response = Net::HTTP.start(akismet.host, use_ssl: true, p_addr: proxy_host, p_port: proxy_port) do |http| 58 | data = "key=#{Rakismet.key}&blog=#{Rakismet.url}" 59 | http.post(akismet.path, data, Rakismet.headers) 60 | end 61 | @valid_key = (response.body == 'valid') 62 | end 63 | 64 | def valid_key? 65 | @valid_key == true 66 | end 67 | 68 | def akismet_call(function, args={}) 69 | validate_config 70 | args.merge!(:blog => Rakismet.url, :is_test => Rakismet.test_mode) 71 | akismet = URI.parse(call_url(function)) 72 | response = Net::HTTP.start(akismet.host, use_ssl: true, p_addr: proxy_host, p_port: proxy_port) do |http| 73 | params = args.map do |k,v| 74 | param = v.class < String ? v.to_str : v.to_s # for ActiveSupport::SafeBuffer and Nil, respectively 75 | "#{k}=#{CGI.escape(param)}" 76 | end 77 | http.post(akismet.path, params.join('&'), Rakismet.headers) 78 | end 79 | response.body 80 | end 81 | 82 | protected 83 | 84 | def verify_url 85 | "https://#{Rakismet.host}/1.1/verify-key" 86 | end 87 | 88 | def call_url(function) 89 | "https://#{Rakismet.key}.#{Rakismet.host}/1.1/#{function}" 90 | end 91 | 92 | def validate_config 93 | raise Undefined, "Rakismet.key is not defined" if Rakismet.key.nil? || Rakismet.key.empty? 94 | raise Undefined, "Rakismet.url is not defined" if Rakismet.url.nil? || Rakismet.url.empty? 95 | raise Undefined, "Rakismet.host is not defined" if Rakismet.host.nil? || Rakismet.host.empty? 96 | end 97 | 98 | def test_mode 99 | test ? 1 : 0 100 | end 101 | end 102 | 103 | end 104 | -------------------------------------------------------------------------------- /lib/rakismet/middleware.rb: -------------------------------------------------------------------------------- 1 | module Rakismet 2 | class Middleware 3 | 4 | def initialize(app) 5 | @app = app 6 | end 7 | 8 | def call(env) 9 | Rakismet.set_request_vars(env) 10 | response = @app.call(env) 11 | Rakismet.clear_request 12 | response 13 | end 14 | 15 | end 16 | end 17 | -------------------------------------------------------------------------------- /lib/rakismet/model.rb: -------------------------------------------------------------------------------- 1 | module Rakismet 2 | module Model 3 | 4 | def self.included(base) 5 | base.class_eval do 6 | attr_accessor :akismet_response 7 | class << self; attr_accessor :akismet_attrs; end 8 | extend ClassMethods 9 | include InstanceMethods 10 | self.rakismet_attrs 11 | end 12 | end 13 | 14 | module ClassMethods 15 | def rakismet_attrs(args={}) 16 | self.akismet_attrs ||= {} 17 | [:author, :author_url, :author_email, :content].each do |field| 18 | fieldname = "comment_#{field}".intern 19 | self.akismet_attrs[fieldname] = args.delete(field) || field 20 | end 21 | [:comment_type, :user_ip, :user_agent, :referrer, :user_role, :permalink].each do |field| 22 | self.akismet_attrs[field] = args.delete(field) || field 23 | end 24 | args.each_pair do |f,v| 25 | self.akismet_attrs[f] = v 26 | end 27 | end 28 | 29 | def inherited(subclass) 30 | super 31 | subclass.rakismet_attrs akismet_attrs.dup 32 | end 33 | end 34 | 35 | module InstanceMethods 36 | def spam? 37 | if instance_variable_defined? :@_spam 38 | @_spam 39 | else 40 | data = akismet_data(true) # Only spam? check should include http_headers 41 | self.akismet_response = Rakismet.akismet_call('comment-check', data) 42 | @_spam = self.akismet_response == 'true' 43 | end 44 | end 45 | 46 | def spam! 47 | Rakismet.akismet_call('submit-spam', akismet_data) 48 | @_spam = true 49 | end 50 | 51 | def ham! 52 | Rakismet.akismet_call('submit-ham', akismet_data) 53 | @_spam = false 54 | end 55 | 56 | private 57 | 58 | def akismet_data(include_http_headers = false) 59 | akismet = self.class.akismet_attrs.keys.inject({}) do |data,attr| 60 | mapped_field = self.class.akismet_attrs[attr] 61 | data.merge attr => if mapped_field.is_a?(Proc) 62 | instance_eval(&mapped_field) 63 | elsif !mapped_field.nil? && respond_to?(mapped_field) 64 | send(mapped_field) 65 | elsif not [:comment_type, :author, :author_email, 66 | :author_url, :content, :user_role, :permalink, 67 | :user_ip, :referrer, 68 | :user_agent].include?(mapped_field) 69 | # we've excluded any fields that appear to 70 | # have their default unmapped values 71 | mapped_field 72 | elsif respond_to?(attr) 73 | send(attr) 74 | elsif Rakismet.request.respond_to?(attr) 75 | Rakismet.request.send(attr) 76 | end 77 | end 78 | akismet.merge! Rakismet.request.http_headers if include_http_headers and Rakismet.request.http_headers 79 | akismet.delete_if { |k,v| v.nil? || v.empty? } 80 | akismet[:comment_type] ||= 'comment' 81 | akismet 82 | end 83 | end 84 | 85 | end 86 | end 87 | -------------------------------------------------------------------------------- /lib/rakismet/railtie.rb: -------------------------------------------------------------------------------- 1 | require 'rails' 2 | require 'rakismet' 3 | 4 | module Rakismet 5 | class Railtie < Rails::Railtie 6 | 7 | config.rakismet = ActiveSupport::OrderedOptions.new 8 | config.rakismet.host = 'rest.akismet.com' 9 | config.rakismet.use_middleware = true 10 | 11 | initializer 'rakismet.setup', :after => :load_config_initializers do |app| 12 | Rakismet.key = app.config.rakismet[:key] 13 | Rakismet.url = app.config.rakismet[:url] 14 | Rakismet.host = app.config.rakismet[:host] 15 | Rakismet.proxy_host = app.config.rakismet[:proxy_host] 16 | Rakismet.proxy_port = app.config.rakismet[:proxy_port] 17 | Rakismet.test = app.config.rakismet.fetch(:test) { Rails.env.test? || Rails.env.development? } 18 | app.middleware.use Rakismet::Middleware if app.config.rakismet.use_middleware 19 | end 20 | 21 | end 22 | end 23 | -------------------------------------------------------------------------------- /lib/rakismet/version.rb: -------------------------------------------------------------------------------- 1 | module Rakismet 2 | VERSION = "1.5.4" 3 | end 4 | -------------------------------------------------------------------------------- /rakismet.gemspec: -------------------------------------------------------------------------------- 1 | # -*- encoding: utf-8 -*- 2 | require File.expand_path('../lib/rakismet/version', __FILE__) 3 | 4 | Gem::Specification.new do |s| 5 | s.name = "rakismet" 6 | s.version = Rakismet::VERSION 7 | s.platform = Gem::Platform::RUBY 8 | s.authors = ["Josh French"] 9 | s.email = "josh@vitamin-j.com" 10 | s.homepage = "http://github.com/joshfrench/rakismet" 11 | s.summary = "Akismet and TypePad AntiSpam integration for Rails." 12 | s.description = "Rakismet is the easiest way to integrate Akismet or TypePad's AntiSpam into your Rails app." 13 | s.date = "2017-07-14" 14 | 15 | s.rubyforge_project = "rakismet" 16 | s.add_development_dependency "rake" 17 | s.add_development_dependency "rspec", "~> 2.11" 18 | 19 | s.files = `git ls-files`.split("\n") 20 | s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n") 21 | s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) } 22 | s.require_paths = ["lib"] 23 | s.extra_rdoc_files = ["README.md"] 24 | end 25 | 26 | -------------------------------------------------------------------------------- /spec/.rspec: -------------------------------------------------------------------------------- 1 | --color 2 | -------------------------------------------------------------------------------- /spec/models/block_params_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | PROC = proc { author.reverse } 4 | 5 | class BlockAkismetModel 6 | include Rakismet::Model 7 | rakismet_attrs :author => PROC 8 | end 9 | 10 | describe BlockAkismetModel do 11 | 12 | before do 13 | @block = BlockAkismetModel.new 14 | comment_attrs.each_pair { |k,v| @block.stub(k).and_return(v) } 15 | end 16 | 17 | it "should accept a block" do 18 | BlockAkismetModel.akismet_attrs[:comment_author].should eql(PROC) 19 | end 20 | 21 | it "should eval block with self = instance" do 22 | data = @block.send(:akismet_data) 23 | data[:comment_author].should eql(comment_attrs[:author].reverse) 24 | end 25 | end 26 | -------------------------------------------------------------------------------- /spec/models/custom_params_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | MAPPED_PARAMS = { :comment_type => :type2, :author => :author2, :content => :content2, 4 | :author_email => :author_email2, :author_url => :author_url2, 5 | :permalink => :permalink2 } 6 | 7 | class CustomAkismetModel 8 | include Rakismet::Model 9 | rakismet_attrs MAPPED_PARAMS.dup 10 | end 11 | 12 | 13 | describe CustomAkismetModel do 14 | it "should override default mappings" do 15 | CustomAkismetModel.akismet_attrs[:comment_type].should eql(:type2) 16 | CustomAkismetModel.akismet_attrs[:comment_author].should eql(:author2) 17 | CustomAkismetModel.akismet_attrs[:comment_content].should eql(:content2) 18 | CustomAkismetModel.akismet_attrs[:comment_author_email].should eql(:author_email2) 19 | CustomAkismetModel.akismet_attrs[:comment_author_url].should eql(:author_url2) 20 | CustomAkismetModel.akismet_attrs[:permalink].should eql(:permalink2) 21 | end 22 | end 23 | -------------------------------------------------------------------------------- /spec/models/extended_params_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | EXTRA = { :extra => :extra, :another => lambda { } } 4 | 5 | class ExtendedAkismetModel 6 | include Rakismet::Model 7 | rakismet_attrs EXTRA.dup 8 | end 9 | 10 | describe ExtendedAkismetModel do 11 | it "should include additional attributes" do 12 | [:extra, :another].each do |field| 13 | ExtendedAkismetModel.akismet_attrs[field].should eql(EXTRA[field]) 14 | end 15 | end 16 | end 17 | -------------------------------------------------------------------------------- /spec/models/rakismet_model_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | describe AkismetModel do 4 | 5 | before do 6 | @model = AkismetModel.new 7 | comment_attrs.each_pair { |k,v| @model.stub(k).and_return(v) } 8 | end 9 | 10 | it "should map internal params to Akismet params" do 11 | AkismetModel::akismet_attrs[:comment_author].should eql(:author) 12 | AkismetModel::akismet_attrs[:comment_author_email].should eql(:author_email) 13 | AkismetModel::akismet_attrs[:comment_author_url].should eql(:author_url) 14 | AkismetModel::akismet_attrs[:comment_content].should eql(:content) 15 | AkismetModel::akismet_attrs[:comment_type].should eql(:comment_type) 16 | AkismetModel::akismet_attrs[:permalink].should eql(:permalink) 17 | AkismetModel::akismet_attrs[:user_role].should eql(:user_role) 18 | end 19 | 20 | it "should have request mappings" do 21 | [:user_ip, :user_agent, :referrer].each do |field| 22 | AkismetModel.akismet_attrs[field].should eql(field) 23 | end 24 | end 25 | 26 | it "should populate comment type" do 27 | @model.send(:akismet_data)[:comment_type].should == comment_attrs[:comment_type] 28 | end 29 | 30 | describe ".spam?" do 31 | 32 | it "should use request variables from Rakismet.request if absent in model" do 33 | [:user_ip, :user_agent, :referrer].each do |field| 34 | @model.should_not respond_to(:field) 35 | end 36 | Rakismet.stub(:request).and_return(request) 37 | Rakismet.should_receive(:akismet_call). 38 | with('comment-check', akismet_attrs.merge(:user_ip => '127.0.0.1', 39 | :user_agent => 'RSpec', 40 | :referrer => 'http://test.host/referrer')) 41 | @model.spam? 42 | end 43 | 44 | it "should send http_headers from Rakismet.request if present" do 45 | Rakismet.stub(:request).and_return(request_with_headers) 46 | Rakismet.should_receive(:akismet_call). 47 | with('comment-check', akismet_attrs.merge(:user_ip => '127.0.0.1', 48 | :user_agent => 'RSpec', 49 | :referrer => 'http://test.host/referrer', 50 | 'HTTP_USER_AGENT' => 'RSpec', 51 | 'HTTP_REFERER' => 'http://test.host/referrer')) 52 | @model.spam? 53 | end 54 | 55 | it "should cache result of #spam?" do 56 | Rakismet.should_receive(:akismet_call).once 57 | @model.spam? 58 | @model.spam? 59 | end 60 | 61 | it "should be true if comment is spam" do 62 | Rakismet.stub(:akismet_call).and_return('true') 63 | @model.should be_spam 64 | end 65 | 66 | it "should be false if comment is not spam" do 67 | Rakismet.stub(:akismet_call).and_return('false') 68 | @model.should_not be_spam 69 | end 70 | 71 | it "should set akismet_response" do 72 | Rakismet.stub(:akismet_call).and_return('response') 73 | @model.spam? 74 | @model.akismet_response.should eql('response') 75 | end 76 | 77 | it "should not throw an error if request vars are missing" do 78 | Rakismet.stub(:request).and_return(empty_request) 79 | Rakismet.stub(:akismet_call).and_return('false') 80 | lambda { @model.spam? }.should_not raise_error 81 | end 82 | end 83 | 84 | 85 | describe ".spam!" do 86 | it "should call Base.akismet_call with submit-spam" do 87 | Rakismet.should_receive(:akismet_call).with('submit-spam', akismet_attrs) 88 | @model.spam! 89 | end 90 | 91 | it "should mutate #spam?" do 92 | Rakismet.stub(:akismet_call) 93 | @model.instance_variable_set(:@_spam, false) 94 | @model.spam! 95 | @model.should be_spam 96 | end 97 | end 98 | 99 | describe ".ham!" do 100 | it "should call Base.akismet_call with submit-ham" do 101 | Rakismet.should_receive(:akismet_call).with('submit-ham', akismet_attrs) 102 | @model.ham! 103 | end 104 | 105 | it "should mutate #spam?" do 106 | Rakismet.stub(:akismet_call) 107 | @model.instance_variable_set(:@_spam, true) 108 | @model.ham! 109 | @model.should_not be_spam 110 | end 111 | end 112 | 113 | end 114 | -------------------------------------------------------------------------------- /spec/models/request_params_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | class RequestParams 4 | include Rakismet::Model 5 | attr_accessor :user_ip, :user_agent, :referrer 6 | end 7 | 8 | describe RequestParams do 9 | before do 10 | @model = RequestParams.new 11 | attrs = comment_attrs(:user_ip => '192.168.0.1', :user_agent => 'Rakismet', :referrer => 'http://localhost/referrer') 12 | attrs.each_pair { |k,v| @model.stub(k).and_return(v) } 13 | end 14 | 15 | it "should use local values even if Rakismet.request is populated" do 16 | Rakismet.stub(:request).and_return(request) 17 | Rakismet.should_receive(:akismet_call). 18 | with('comment-check', akismet_attrs.merge(:user_ip => '192.168.0.1', 19 | :user_agent => 'Rakismet', 20 | :referrer => 'http://localhost/referrer')) 21 | @model.spam? 22 | end 23 | end 24 | -------------------------------------------------------------------------------- /spec/models/subclass_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | class Subclass < AkismetModel 4 | end 5 | 6 | describe Subclass do 7 | it "should inherit parent's rakismet attrs" do 8 | Subclass.akismet_attrs.should eql AkismetModel.akismet_attrs # key/value equality 9 | end 10 | 11 | it "should get a new copy of parent's rakismet attrs" do 12 | Subclass.akismet_attrs.should_not equal AkismetModel.akismet_attrs # object equality 13 | end 14 | end 15 | -------------------------------------------------------------------------------- /spec/rakismet_middleware_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | describe Rakismet::Middleware do 4 | 5 | let(:env) { { 'REMOTE_ADDR' => '127.0.0.1', 'HTTP_USER_AGENT' => 'RSpec', 'HTTP_REFERER' => 'http://test.host/referrer', 'HTTP_COOKIE' => "Don't violate my privacy" } } 6 | let(:app) { double(:app, :call => nil) } 7 | let(:request) { double(:request).as_null_object } 8 | 9 | before do 10 | @middleware = Rakismet::Middleware.new(app) 11 | end 12 | 13 | it "should set set Rakismet.request variables" do 14 | Rakismet.stub(:request).and_return(request) 15 | request.should_receive(:user_ip=).with('127.0.0.1') 16 | request.should_receive(:user_agent=).with('RSpec') 17 | request.should_receive(:referrer=).with('http://test.host/referrer') 18 | @middleware.call(env) 19 | end 20 | 21 | it "should set set Rakismet.request http_headers" do 22 | Rakismet.stub(:request).and_return(request) 23 | request.should_receive(:http_headers=).with({ 'HTTP_USER_AGENT' => 'RSpec', 'HTTP_REFERER' => 'http://test.host/referrer' }) 24 | @middleware.call(env) 25 | end 26 | 27 | it "should clear Rakismet.request after request is complete" do 28 | @middleware.call(env) 29 | Rakismet.request.user_ip.should be_nil 30 | Rakismet.request.user_agent.should be_nil 31 | Rakismet.request.referrer.should be_nil 32 | Rakismet.request.http_headers.should be_nil 33 | end 34 | 35 | end 36 | -------------------------------------------------------------------------------- /spec/rakismet_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | describe Rakismet do 4 | 5 | def mock_response(body) 6 | double(:response, :body => body) 7 | end 8 | let(:http) { double(:http, :post => mock_response('akismet response')) } 9 | 10 | before do 11 | Rakismet.key = 'dummy-key' 12 | Rakismet.url = 'test.localhost' 13 | Rakismet.host = 'endpoint.localhost' 14 | Rakismet.proxy_host = nil 15 | Rakismet.proxy_port = nil 16 | 17 | @test_url = "#{Rakismet.key}.#{Rakismet.host}" 18 | end 19 | 20 | describe "proxy host" do 21 | it "should have proxy host and port as nil by default" do 22 | Rakismet.proxy_host.should be_nil 23 | Rakismet.proxy_port.should be_nil 24 | end 25 | end 26 | 27 | describe "url" do 28 | it "should allow url to be a string" do 29 | Rakismet.url = "string.example.com" 30 | Rakismet.url.should eql("string.example.com") 31 | end 32 | 33 | it "should allow url to be a proc" do 34 | Rakismet.url = Proc.new { "proc.example.com" } 35 | Rakismet.url.should eql("proc.example.com") 36 | end 37 | end 38 | 39 | describe ".validate_config" do 40 | it "should raise an error if key is not found" do 41 | Rakismet.key = '' 42 | lambda { Rakismet.send(:validate_config) }.should raise_error(Rakismet::Undefined) 43 | end 44 | 45 | it "should raise an error if url is not found" do 46 | Rakismet.url = '' 47 | lambda { Rakismet.send(:validate_config) }.should raise_error(Rakismet::Undefined) 48 | end 49 | 50 | it "should raise an error if host is not found" do 51 | Rakismet.host = '' 52 | lambda { Rakismet.send(:validate_config) }.should raise_error(Rakismet::Undefined) 53 | end 54 | end 55 | 56 | describe ".validate_key" do 57 | it "should use proxy host and port" do 58 | Rakismet.proxy_host = 'proxy_host' 59 | Rakismet.proxy_port = 'proxy_port' 60 | 61 | Net::HTTP.should_receive(:start).with(Rakismet.host, use_ssl: true, p_addr: 'proxy_host', p_port: 'proxy_port') 62 | .and_return(mock_response('valid')) 63 | 64 | Rakismet.validate_key 65 | end 66 | 67 | it "should set @@valid_key = true if key is valid" do 68 | Net::HTTP.stub(:start).and_return(mock_response('valid')) 69 | Rakismet.validate_key 70 | Rakismet.valid_key?.should be_truthy 71 | end 72 | 73 | it "should set @@valid_key = false if key is invalid" do 74 | Net::HTTP.stub(:start).and_return(mock_response('invalid')) 75 | Rakismet.validate_key 76 | Rakismet.valid_key?.should be_falsey 77 | end 78 | 79 | it "should build url with host" do 80 | host = "api.antispam.typepad.com" 81 | Rakismet.host = host 82 | Net::HTTP.should_receive(:start).with(host, use_ssl: true, p_addr: nil, p_port: nil).and_yield(http) 83 | Rakismet.validate_key 84 | end 85 | end 86 | 87 | describe '.excluded_headers' do 88 | it "should default to ['HTTP_COOKIE']" do 89 | Rakismet.excluded_headers.should eq ['HTTP_COOKIE'] 90 | end 91 | end 92 | 93 | describe ".akismet_call" do 94 | before do 95 | Net::HTTP.stub(:start).and_yield(http) 96 | end 97 | 98 | it "should use proxy host and port" do 99 | Rakismet.proxy_host = 'proxy_host' 100 | Rakismet.proxy_port = 'proxy_port' 101 | 102 | Net::HTTP.should_receive(:start).with(@test_url, use_ssl: true, p_addr: 'proxy_host', p_port: 'proxy_port') 103 | .and_return(mock_response('valid')) 104 | 105 | Rakismet.send(:akismet_call, 'bogus-function') 106 | end 107 | 108 | it "should build url with API key for the correct host" do 109 | host = 'api.antispam.typepad.com' 110 | Rakismet.host = host 111 | Net::HTTP.should_receive(:start).with("#{Rakismet.key}.#{host}", use_ssl: true, p_addr: nil, p_port: nil) 112 | Rakismet.send(:akismet_call, 'bogus-function') 113 | end 114 | 115 | it "should post data to named function" do 116 | http.should_receive(:post).with('/1.1/bogus-function', %r(foo=#{CGI.escape 'escape//this'}), Rakismet.headers) 117 | Rakismet.send(:akismet_call, 'bogus-function', { :foo => 'escape//this' }) 118 | end 119 | 120 | it "should default to not being in test mode" do 121 | http.should_receive(:post).with(anything, %r(is_test=0), anything) 122 | Rakismet.send(:akismet_call, 'bogus-function') 123 | end 124 | 125 | it "should be in test mode when configured" do 126 | Rakismet.test = true 127 | http.should_receive(:post).with(anything, %r(is_test=1), anything) 128 | Rakismet.send(:akismet_call, 'bogus-function') 129 | end 130 | 131 | it "should return response.body" do 132 | Rakismet.send(:akismet_call, 'bogus-function').should eql('akismet response') 133 | end 134 | 135 | it "should build query string when params are nil" do 136 | lambda { 137 | Rakismet.send(:akismet_call, 'bogus-function', { :nil_param => nil }) 138 | }.should_not raise_error 139 | end 140 | end 141 | 142 | end 143 | -------------------------------------------------------------------------------- /spec/spec_helper.rb: -------------------------------------------------------------------------------- 1 | require File.expand_path "lib/rakismet" 2 | require 'ostruct' 3 | 4 | RSpec.configure do |config| 5 | config.mock_with(:rspec) { |c| c.syntax = :should } 6 | config.expect_with(:rspec) { |c| c.syntax = :should } 7 | end 8 | 9 | class AkismetModel 10 | include Rakismet::Model 11 | end 12 | 13 | def comment_attrs(attrs={}) 14 | { :comment_type => 'test', :author => 'Rails test', 15 | :author_email => 'test@test.host', :author_url => 'test.host', 16 | :content => 'comment content', :blog => Rakismet.url }.merge(attrs) 17 | end 18 | 19 | def akismet_attrs(attrs={}) 20 | { :comment_type => 'test', :comment_author_email => 'test@test.host', 21 | :comment_author => 'Rails test', :comment_author_url => 'test.host', 22 | :comment_content => 'comment content' }.merge(attrs) 23 | end 24 | 25 | def request 26 | OpenStruct.new(:user_ip => '127.0.0.1', 27 | :user_agent => 'RSpec', 28 | :referrer => 'http://test.host/referrer') 29 | end 30 | 31 | def request_with_headers 32 | OpenStruct.new(:user_ip => '127.0.0.1', 33 | :user_agent => 'RSpec', 34 | :referrer => 'http://test.host/referrer', 35 | :http_headers => { 'HTTP_USER_AGENT' => 'RSpec', 'HTTP_REFERER' => 'http://test.host/referrer' } ) 36 | end 37 | 38 | def empty_request 39 | OpenStruct.new(:user_ip => nil, 40 | :user_agent => nil, 41 | :referrer => nil, 42 | :http_headers => nil) 43 | end --------------------------------------------------------------------------------