Skip to content
Browse files

Add credentials plugin

Its purpose is to avoid storing cleartext passwords in .couchapprc.

The plugin replaces specific URI userinfo strings (e.g. KEYCHAIN_CREDENTIALS)
in the .couchapprc with username and password retrieved from a credentials
store like Keychain Services in Mac OS X.

The plugin is designed to be extensible to support different
credentials stores and takes into account that not all stores
are available on every platform.
  • Loading branch information...
1 parent f804548 commit 3ac653f5a317c91a3055b4e27f31cad215b9d988 @afh afh committed Jan 28, 2012
Showing with 128 additions and 0 deletions.
  1. +4 −0 Gemfile
  2. +4 −0 lib/soca/plugin.rb
  3. +75 −0 lib/soca/plugins/credentials.rb
  4. +1 −0 lib/soca/pusher.rb
  5. +1 −0 test/helper.rb
  6. +40 −0 test/test_credentials_plugin.rb
  7. +3 −0 test/testapp/.couchapprc
View
4 Gemfile
@@ -18,3 +18,7 @@ end
group :test do
gem "shoulda", ">= 0"
end
+
+group :darwin do
+ gem 'keychain_services', '~>0.1.1'
+end
View
4 lib/soca/plugin.rb
@@ -25,5 +25,9 @@ def app_dir
pusher.app_dir
end
+ def config
+ pusher.config
+ end
+
end
end
View
75 lib/soca/plugins/credentials.rb
@@ -0,0 +1,75 @@
+module Soca
+ module Plugins
+ class Credentials < Soca::Plugin
+
+ name 'credentials'
+
+ # Credentials plugin
+ # This plugin is run after the couchapprc is loaded,
+ # it checks the db field in every environment,
+ # searches for strings ending with '_CREDENTIALS' in the URI userinfo field,
+ # passes the URI host to a method handling the requested credentials,
+ # and replaces the userinfo with the username and password from the credentials method.
+ #
+ # When adding a new credentials method, please make sure the platform
+ # specific requirements are met (e.g. external tools or gems)
+ # and configure its platform availability in the credentials_supported? method
+
+ def after_load_couchapprc
+ config['couchapprc']['env'].each do |env, cfg|
+ next unless cfg['db'] =~ /^(https?:\/\/)([^@]+_CREDENTIALS)@(.*)$/i
+ scheme = $1
+ userinfo = $2
+ host = $3
+
+ unless credentials_supported?(userinfo)
+ Soca.logger.error "#{userinfo} are not supported on the #{RUBY_PLATFORM} platform"
+ puts 'skip'
+ next
+ end
+
+ (username, password) = send(userinfo.downcase, host)
+ unless username and password
+ Soca.logger.warn "#{userinfo} returned empty credentials for #{host}"
+ else
+ credentials = "#{username}:#{password}@"
+ config['couchapprc']['env'][env]['db'] = "#{scheme}#{credentials}#{host}"
+ Soca.logger.debug "Replacing #{userinfo} with #{credentials} in #{cfg['db']}"
+ end
+ end
+ end
+
+ private
+ def credentials_supported?(type)
+ Soca.logger.debug "Checking support for #{type} on #{RUBY_PLATFORM}"
+ available_credentials = {
+ 'darwin' => %w[keychain_credentials],
+ #'linux' => %w[]
+ }
+ supported_credentials = available_credentials.map { |os, list| list if RUBY_PLATFORM =~ /#{os}/i }
+ supported_credentials.flatten.compact.include?(type.downcase)
+ end
+
+ # This methods retrieves the user credentials from the Mac OS X Keychain Services
+ def keychain_credentials(host)
+ begin
+ require 'keychain'
+ rescue LoadError
+ warn "Please install the keychain_services gem, in order to retrieve user credentials from your keychain"
+ return
+ end
+
+ Soca.logger.debug "Searching for #{host} in keychain"
+ item = Keychain.items.find { |item| item if item.label == host }
+ unless item
+ # strip url-path from host url and search again
+ (host, db) = host.split('/', 2)
+ Soca.logger.debug "Searching for #{host} in keychain"
+ item = Keychain.items.find { |item| item if item.label == host }
+ end
+ [item.account, item.password] if item
+ end
+
+ end
+ end
+end
View
1 lib/soca/pusher.rb
@@ -26,6 +26,7 @@ def load_config
def load_couchapprc
@config ||= {}
@config['couchapprc'] = JSON.parse(File.read(File.join(app_dir, '.couchapprc')))
+ run_hooks!(:after_load_couchapprc)
end
def build
View
1 test/helper.rb
@@ -8,6 +8,7 @@
$LOAD_PATH.unshift(File.dirname(__FILE__))
require 'soca'
require 'soca/plugins/compass'
+require 'soca/plugins/credentials'
class Test::Unit::TestCase
View
40 test/test_credentials_plugin.rb
@@ -0,0 +1,40 @@
+require 'helper'
+
+class TestCredentialsPlugin < Test::Unit::TestCase
+ def app_path(relative='')
+ File.expand_path(@test_app_dir + '/' + relative)
+ end
+
+ context 'credentials plugin' do
+ setup do
+ @pusher = Soca::Pusher.new(app_path)
+ @plugin = Soca::Plugins::Credentials.new(@pusher)
+ end
+
+ if RUBY_PLATFORM =~ /darwin/i
+ require 'keychain'
+ context 'given keychain credentials' do
+ should 'check for platform availability' do
+ @plugin.expects(:credentials_supported?).
+ with('KEYCHAIN_CREDENTIALS').
+ returns(true)
+ @plugin.after_load_couchapprc
+ end
+
+ # Note this test requires you have
+ # an application password item
+ # in your keychain with the
+ # name localhost:5894/testapp
+ # username admin
+ # password admin
+ should 'return username and password for host from keychain' do
+ @plugin.expects(:keychain_credentials).
+ with('localhost:5984/testapp').
+ returns(%w[admin admin])
+ @plugin.after_load_couchapprc
+ end
+ end
+ end
+
+ end
+end
View
3 test/testapp/.couchapprc
@@ -5,6 +5,9 @@
},
"production": {
"db": "http://admin:admin@c.ixxr.net/testapp"
+ },
+ "keychain_credentials": {
+ "db": "http://KEYCHAIN_CREDENTIALS@localhost:5984/testapp"
}
}
}

0 comments on commit 3ac653f

Please sign in to comment.
Something went wrong with that request. Please try again.