diff options
| author | elijah <elijah@riseup.net> | 2015-03-11 01:12:23 -0700 | 
|---|---|---|
| committer | elijah <elijah@riseup.net> | 2015-03-17 22:36:27 -0700 | 
| commit | 9266c3ac58404894539e25e514d8d8a6775c701f (patch) | |
| tree | 3beaa846b37aecdf933763564710c2e1042c03a3 | |
| parent | a777c4c677e8cbd4f91c66a29ee1ecb347c5b8ab (diff) | |
add support for rotating tokens and sessions databases, and for a special tmp db for test users.
| -rw-r--r-- | DEPLOY.md | 5 | ||||
| -rw-r--r-- | Gemfile | 4 | ||||
| -rw-r--r-- | Gemfile.lock | 6 | ||||
| -rw-r--r-- | app/models/account.rb | 23 | ||||
| -rw-r--r-- | app/models/temporary_user.rb | 78 | ||||
| -rw-r--r-- | app/models/token.rb | 13 | ||||
| -rw-r--r-- | app/models/user.rb | 2 | ||||
| -rw-r--r-- | lib/tasks/leap_web_core_tasks.rake | 35 | ||||
| -rw-r--r-- | test/integration/api/srp_test.rb | 10 | ||||
| -rw-r--r-- | test/integration/api/tmp_user_test.rb | 19 | ||||
| -rw-r--r-- | test/test_helper.rb | 9 | ||||
| -rw-r--r-- | test/unit/tmp_user_test.rb | 29 | ||||
| -rw-r--r-- | test/unit/token_test.rb | 2 | 
13 files changed, 208 insertions, 27 deletions
| @@ -25,8 +25,9 @@ run `cap deploy` to deploy to the server.  Please make sure your deploy includes the following files: -* public/config/provider.json -* config/couchdb.yml +* `public/config/provider.json` -- provider bootstrap file. +* `config/couchdb.yml` -- normal webapp couchdb configuration. +* `config/couchdb.admin.yml` -- configuration used for rake tasks.  ## Couch Security ## @@ -6,8 +6,8 @@ require File.expand_path('../lib/gemfile_tools.rb', __FILE__)  gem "rails", "~> 3.2.21"  gem "couchrest", "~> 1.1.3"  gem "couchrest_model", "~> 2.0.0" -unless ARGV.grep(/assets:precompile/) -  gem "couchrest_session_store", "~> 0.2.4" +if ARGV.grep(/assets:precompile/).empty? +  gem "couchrest_session_store", "= 0.3.0"  end  ## AUTHENTICATION diff --git a/Gemfile.lock b/Gemfile.lock index 346155a..4de7e05 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -76,8 +76,8 @@ GEM        couchrest (~> 1.1.3)        mime-types (>= 1.15)        tzinfo (>= 0.3.22) -    couchrest_session_store (0.2.4) -      actionpack +    couchrest_session_store (0.3.0) +      actionpack (~> 3.0)        couchrest        couchrest_model      cucumber (1.3.17) @@ -255,7 +255,7 @@ DEPENDENCIES    client_side_validations-simple_form    couchrest (~> 1.1.3)    couchrest_model (~> 2.0.0) -  couchrest_session_store (~> 0.2.4) +  couchrest_session_store (= 0.3.0)    cucumber-rails    debugger    factory_girl_rails diff --git a/app/models/account.rb b/app/models/account.rb index e60a356..af470ed 100644 --- a/app/models/account.rb +++ b/app/models/account.rb @@ -20,7 +20,7 @@ class Account      user = nil      user = User.new(attrs)      user.save -    if user.persisted? +    if !user.tmp? && user.persisted?        identity = user.identity        identity.user_id = user.id        identity.save @@ -52,10 +52,12 @@ class Account    def destroy(destroy_identity=false)      return unless @user -    if destroy_identity == false -      Identity.disable_all_for(@user) -    else -      Identity.destroy_all_for(@user) +    if !user.tmp? +      if destroy_identity == false +        Identity.disable_all_for(@user) +      else +        Identity.destroy_all_for(@user) +      end      end      @user.destroy    end @@ -84,12 +86,11 @@ class Account    end    def self.creation_problem?(user, identity) -    user.nil? || -    !user.persisted? || -    identity.nil? || -    !identity.persisted? || -    user.errors.any? || -    identity.errors.any? +    return true if user.nil? || !user.persisted? || user.errors.any? +    if !user.tmp? +      return true if identity.nil? || !identity.persisted? || identity.errors.any? +    end +    return false    end    # You can hook into the account lifecycle from different engines using diff --git a/app/models/temporary_user.rb b/app/models/temporary_user.rb new file mode 100644 index 0000000..87800ed --- /dev/null +++ b/app/models/temporary_user.rb @@ -0,0 +1,78 @@ +# +# For users with login '*test_user*', we don't want to store these documents in +# the main users db. This is because we create and destroy a lot of test +# users. This weirdness of using a different db for some users breaks a lot of +# things, such as associations. However, this is OK for now since we don't need +# those for running the frequent nagios tests. +# +# This module is included in user.rb. This will only work if it is included +# after designs are defined, otherwise, the design definition will overwrite +# find_by_login(). +# + +module TemporaryUser +  extend ActiveSupport::Concern +  include CouchRest::Model::DatabaseMethod + +  USER_DB     = 'users' +  TMP_USER_DB = 'tmp_users' +  TMP_LOGIN   = 'test_user' + +  included do +    use_database_method :db_name + +    # since the original find_by_login is dynamically created with +    # instance_eval, it appears that we also need to use instance eval to +    # override it. +    instance_eval <<-EOS, __FILE__, __LINE__ + 1 +      def find_by_login(*args) +        if args.grep(/#{TMP_LOGIN}/).any? +          by_login.database(tmp_database).key(*args).first() +        else +          by_login.key(*args).first() +        end +      end +    EOS +  end + +  module ClassMethods +    def get(id, db = database) +      super(id, db) || super(id, tmp_database) +    end +    alias :find :get + +    # calls db_name(TMP_LOGIN), then creates a CouchRest::Database +    # from the name +    def tmp_database +      choose_database(TMP_LOGIN) +    end + +    def db_name(login=nil) +      if !login.nil? && login.include?(TMP_LOGIN) +        TMP_USER_DB +      else +        USER_DB +      end +    end + +    # create the tmp db if it doesn't exist. +    # requires admin access. +    def create_tmp_database! +      design_doc.sync!(tmp_database.tap{|db|db.create!}) +    end +  end + +  # +  # this gets called each and every time a User object needs to +  # access the database. +  # +  def db_name +    self.class.db_name(self.login) +  end + +  # returns true if this User instance is stored in tmp db. +  def tmp? +    !login.nil? && login.include?(TMP_LOGIN) +  end + +end diff --git a/app/models/token.rb b/app/models/token.rb index ff2ad12..4afd275 100644 --- a/app/models/token.rb +++ b/app/models/token.rb @@ -1,8 +1,15 @@  require 'digest/sha2'  class Token < CouchRest::Model::Base +  def self.expires_after +    APP_CONFIG[:auth] && APP_CONFIG[:auth][:token_expires_after] +  end -  use_database :tokens +  include CouchRest::Model::Rotation +  rotate_database :tokens, +    :every => 1.month, +    :timestamp_field => :last_seen_at, +    :timeout => self.expires_after.to_i # in minutes    belongs_to :user @@ -23,10 +30,6 @@ class Token < CouchRest::Model::Base      self.find Digest::SHA512.hexdigest(token)    end -  def self.expires_after -    APP_CONFIG[:auth] && APP_CONFIG[:auth][:token_expires_after] -  end -    def self.expired      return [] unless expires_after      by_last_seen_at.endkey(expires_after.minutes.ago) diff --git a/app/models/user.rb b/app/models/user.rb index 9ac7d3d..52e20dd 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -42,6 +42,8 @@ class User < CouchRest::Model::Base      view :by_created_at    end # end of design +  include TemporaryUser # MUST come after designs are defined. +    def self.login_starts_with(query)      self.by_login.startkey(query).endkey(query + "\ufff0")    end diff --git a/lib/tasks/leap_web_core_tasks.rake b/lib/tasks/leap_web_core_tasks.rake index ec6abac..e446f93 100644 --- a/lib/tasks/leap_web_core_tasks.rake +++ b/lib/tasks/leap_web_core_tasks.rake @@ -8,7 +8,6 @@ namespace :couchrest do  end  namespace :cleanup do -      desc "Cleanup all expired session documents"    task :sessions => :environment do      # make sure this is the same as in @@ -23,3 +22,37 @@ namespace :cleanup do    end  end +namespace :db do +  desc "Rotate the databases, as needed." +  task :rotate => :environment do +    # +    # db rotation must be performed by admin, and since +    # CouchRest::Session::Document is not a CouchRest::Model, we need to +    # override the default config twice. +    # + +    CouchRest::Model::Base.configure do |conf| +      conf.environment = Rails.env +      conf.connection_config_file = File.join(Rails.root, 'config', 'couchdb.admin.yml') +    end +    Token.rotate_database_now(:window => 1.day) + +    CouchRest::Session::Document.configure do |conf| +      conf.environment = Rails.env +      conf.connection_config_file = File.join(Rails.root, 'config', 'couchdb.admin.yml') +    end +    CouchRest::Session::Document.rotate_database_now(:window => 1.day) +  end + +  desc "Delete and recreate temporary databases." +  task :deletetmp => :environment do +    # db deletion and creation must be performed by admin +    CouchRest::Model::Base.configure do |conf| +      conf.environment = Rails.env +      conf.connection_config_file = File.join(Rails.root, 'config', 'couchdb.admin.yml') +    end +    User.tmp_database.recreate! +    User.design_doc.sync!(User.tmp_database) +  end + +end diff --git a/test/integration/api/srp_test.rb b/test/integration/api/srp_test.rb index 946450e..fbef47e 100644 --- a/test/integration/api/srp_test.rb +++ b/test/integration/api/srp_test.rb @@ -32,11 +32,11 @@ class SrpTest < RackTest    attr_reader :server_auth -  def register_user(login = "integration_test_user", password = 'srp, verify me!') +  def register_user(login = "integration_test", password = 'srp, verify me!')      cleanup_user(login)      post 'http://api.lvh.me:3000/1/users.json',        user_params(login: login, password: password) -    @user = User.find_by_login(login) +    assert(@user = User.find_by_login(login), 'user should have been created: %s' % last_response_errors)      @login = login      @password = password    end @@ -101,4 +101,10 @@ class SrpTest < RackTest        SRP::Client.new(params.delete(:login) || @login, params)      end    end + +  def last_response_errors +    JSON.parse(last_response.body)['errors'] +  rescue +    "" +  end  end diff --git a/test/integration/api/tmp_user_test.rb b/test/integration/api/tmp_user_test.rb new file mode 100644 index 0000000..4c1e659 --- /dev/null +++ b/test/integration/api/tmp_user_test.rb @@ -0,0 +1,19 @@ +require 'test_helper' +require_relative 'srp_test' + +class TmpUserTest < SrpTest + +  setup do +    register_user('test_user_'+SecureRandom.hex(5)) +  end + +  test "login with srp" do +    authenticate +    assert_nil server_auth["errors"] +    assert_nil server_auth["error"] +    assert_equal ["M2", "id", "token"], server_auth.keys +    assert last_response.successful? +    assert server_auth["M2"] +  end + +end diff --git a/test/test_helper.rb b/test/test_helper.rb index 7959ddb..dfc6627 100644 --- a/test/test_helper.rb +++ b/test/test_helper.rb @@ -30,3 +30,12 @@ class ActiveSupport::TestCase    require 'i18n/missing_translations'    at_exit { I18n.missing_translations.dump }  end + +# +# Create databases, since the temporary databases might not have been created +# when `rake couchrest:migrate` was run. +# + +Token.create_database! if Token.respond_to?(:create_database) +CouchRest::Session::Document.create_database! if CouchRest::Session::Document.respond_to?(:create_database) +User.create_tmp_database! if User.respond_to?(:create_tmp_database) diff --git a/test/unit/tmp_user_test.rb b/test/unit/tmp_user_test.rb new file mode 100644 index 0000000..55b117f --- /dev/null +++ b/test/unit/tmp_user_test.rb @@ -0,0 +1,29 @@ +require 'test_helper' + +class TmpUserTest < ActiveSupport::TestCase + +  test "test_user saved to tmp_users" do +    begin +      assert User.ancestors.include?(TemporaryUser) + +      assert_difference('User.database.info["doc_count"]') do +        normal_user = User.create!(:login => 'a'+SecureRandom.hex(5).downcase, +          :password_verifier => 'ABCDEF0010101', :password_salt => 'ABCDEF') +        refute normal_user.database.to_s.include?('tmp') +      end + +      assert_difference('User.tmp_database.info["doc_count"]') do +        tmp_user = User.create!(:login => 'test_user_'+SecureRandom.hex(5).downcase, +          :password_verifier => 'ABCDEF0010101', :password_salt => 'ABCDEF') +        assert tmp_user.database.to_s.include?('tmp') +      end +    ensure +      begin +        normal_user.destroy +        tmp_user.destroy +      rescue +      end +    end +  end + +end diff --git a/test/unit/token_test.rb b/test/unit/token_test.rb index b143345..5468650 100644 --- a/test/unit/token_test.rb +++ b/test/unit/token_test.rb @@ -1,6 +1,6 @@  require 'test_helper' -class ClientCertificateTest < ActiveSupport::TestCase +class TokenTest < ActiveSupport::TestCase    include StubRecordHelper    setup do | 
