zas-service 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- data/LICENSE +7 -0
- data/README.md +60 -0
- data/lib/zas.rb +3 -0
- data/lib/zas/authenticators.rb +6 -0
- data/lib/zas/authenticators/filtered_authenticator.rb +35 -0
- data/lib/zas/authenticators/sequel_password_authenticator.rb +69 -0
- data/lib/zas/crypto_providers.rb +5 -0
- data/lib/zas/crypto_providers/sha512.rb +28 -0
- data/lib/zas/filters.rb +5 -0
- data/lib/zas/filters/http_basic_auth.rb +20 -0
- data/lib/zas/service.rb +78 -0
- data/lib/zas/service_configuration.rb +23 -0
- metadata +74 -0
data/LICENSE
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
Copyright (c) 2012 Anthony Eden
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
4
|
+
|
5
|
+
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
6
|
+
|
7
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,60 @@
|
|
1
|
+
# Zero Authentication Service
|
2
|
+
|
3
|
+
Zas is an authentication service build on 0mq. It is designed to accept credentials and verify them against a data store. The purpose of Zas is for simple and fast authentication in Service Oriented Architectures.
|
4
|
+
|
5
|
+
## Installing the service
|
6
|
+
|
7
|
+
gem install bundler
|
8
|
+
bundle install
|
9
|
+
|
10
|
+
## Running the Service
|
11
|
+
|
12
|
+
General usage. This example assumes an in-memory database and uses Sequel to connect to that database.
|
13
|
+
|
14
|
+
# connect to the database
|
15
|
+
require 'sequel'
|
16
|
+
db = Sequel.connect(db_url)
|
17
|
+
|
18
|
+
# set up the authenticator
|
19
|
+
authenticator = Zas::Authenticators::SequelPasswordAuthenticator.new(db)
|
20
|
+
|
21
|
+
# construct the service and add the authenticator
|
22
|
+
service = Zas::Service.new
|
23
|
+
service.authenticators['http_basic_auth'] = Zas::Authenticators::FilteredAuthenticator.new(
|
24
|
+
sequel_authenticator, [Zas::Filters::HttpBasicAuth]
|
25
|
+
)
|
26
|
+
|
27
|
+
# run the service
|
28
|
+
service.run
|
29
|
+
|
30
|
+
Running from irb:
|
31
|
+
|
32
|
+
bundle exec irb -Ilib -rzas
|
33
|
+
=> # as above
|
34
|
+
|
35
|
+
## Configuration
|
36
|
+
|
37
|
+
You may provide a service configuration to the service initializer:
|
38
|
+
|
39
|
+
config = Zas::ServiceConfiguration.new(:host => '127.0.0.1', :port => '6000', :name => 'my-service')
|
40
|
+
Zas::Service.new(config).run
|
41
|
+
|
42
|
+
The following options are available:
|
43
|
+
|
44
|
+
* host - The host to listen to. Either * or an IP address. Defaults to *
|
45
|
+
* port - The port to listen to. Defaults to 5555
|
46
|
+
* name - A name to use in log messages. Defaults to zas-service
|
47
|
+
|
48
|
+
## Logging
|
49
|
+
|
50
|
+
All logging will be sent to syslog.
|
51
|
+
|
52
|
+
## Notes
|
53
|
+
|
54
|
+
This library is not thread-safe.
|
55
|
+
|
56
|
+
## Integration Specs
|
57
|
+
|
58
|
+
To run the integration specs:
|
59
|
+
|
60
|
+
rake -Ilib integrations
|
data/lib/zas.rb
ADDED
@@ -0,0 +1,35 @@
|
|
1
|
+
module Zas
|
2
|
+
module Authenticators
|
3
|
+
# An authenticator that filters the credentials through 1 or more filters that can transform
|
4
|
+
# the credentials before passing them to another authenticator.
|
5
|
+
#
|
6
|
+
# Example:
|
7
|
+
#
|
8
|
+
# authenticator = FilteredAuthenticator.new(MyRealAuthenticator.new, [SomeFilter, SomeOtherFilter])
|
9
|
+
# authenticator.authenticate(credentials)
|
10
|
+
class FilteredAuthenticator
|
11
|
+
# Initialize the filtered authenticator with the given delegate
|
12
|
+
# and filters. Filters will be applied when the authentication
|
13
|
+
# occurs.
|
14
|
+
#
|
15
|
+
# delegate - The implementation to delegate to after filtering is complete
|
16
|
+
# filters - The filters to apply
|
17
|
+
def initialize(delegate, *filters)
|
18
|
+
self.delegate = delegate
|
19
|
+
self.filters = [filters].compact.flatten
|
20
|
+
end
|
21
|
+
|
22
|
+
# Authenticate the given credentials.
|
23
|
+
#
|
24
|
+
# credentials - The credentials
|
25
|
+
def authenticate(credentials)
|
26
|
+
credentials = filters.reduce(credentials) { |credentials, filter| filter.authenticate(credentials) }
|
27
|
+
delegate.authenticate(*credentials)
|
28
|
+
end
|
29
|
+
|
30
|
+
private
|
31
|
+
attr_accessor :delegate
|
32
|
+
attr_accessor :filters
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
@@ -0,0 +1,69 @@
|
|
1
|
+
module Zas
|
2
|
+
module Authenticators
|
3
|
+
# Public: Authenticate a username/password combination against a database using Sequel.
|
4
|
+
class SequelPasswordAuthenticator
|
5
|
+
# Public: Configuration for the authenticator
|
6
|
+
#
|
7
|
+
# Configuration attributes include:
|
8
|
+
#
|
9
|
+
# table_name - The table name to query to authenticate. Defaults to 'users'
|
10
|
+
# username_field - The name of the field used to store the user identifier, defaults to 'username'
|
11
|
+
# password_field - The name of the field used to store the encrypted password, 'defaults to 'crypted_password'
|
12
|
+
# salt_field - The name of the field used to store a salt, defaults to 'password_salt'
|
13
|
+
class Configuration < Struct.new(:table_name, :username_field, :password_field, :salt_field)
|
14
|
+
def initialize(attributes={})
|
15
|
+
attributes.each do |k, v|
|
16
|
+
self[k] = v
|
17
|
+
end
|
18
|
+
end
|
19
|
+
def table_name
|
20
|
+
self[:table_name] || :users
|
21
|
+
end
|
22
|
+
def username_field
|
23
|
+
self[:username_field] || :username
|
24
|
+
end
|
25
|
+
def password_field
|
26
|
+
self[:password_field] || :crypted_password
|
27
|
+
end
|
28
|
+
def salt_field
|
29
|
+
self[:salt_field] || :password_salt
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
# Initialize the autenticator with the given sequel DB. Optionally pass in additional
|
34
|
+
# configuration for the authenticator.
|
35
|
+
#
|
36
|
+
# sequel_db - The Sequel Datbase instance
|
37
|
+
# config - The configuration instance
|
38
|
+
def initialize(sequel_db, config=Configuration.new)
|
39
|
+
self.db = sequel_db
|
40
|
+
self.table_name = config.table_name.to_sym
|
41
|
+
self.username_field = config.username_field.to_sym
|
42
|
+
self.password_field = config.password_field.to_sym
|
43
|
+
self.salt_field = config.salt_field.to_sym
|
44
|
+
end
|
45
|
+
|
46
|
+
# Authenticate the given username/password pair
|
47
|
+
def authenticate(username, password)
|
48
|
+
record = db[table_name].filter(username_field => username).first
|
49
|
+
tokens = [password, record[salt_field]].compact
|
50
|
+
matches?(record[password_field], *tokens) if record
|
51
|
+
end
|
52
|
+
|
53
|
+
private
|
54
|
+
attr_accessor :db
|
55
|
+
attr_accessor :table_name
|
56
|
+
attr_accessor :username_field
|
57
|
+
attr_accessor :password_field
|
58
|
+
attr_accessor :salt_field
|
59
|
+
|
60
|
+
def encrypt(*password)
|
61
|
+
Zas::CryptoProviders::Sha512.new.encrypt(*password)
|
62
|
+
end
|
63
|
+
|
64
|
+
def matches?(crypted, *tokens)
|
65
|
+
Zas::CryptoProviders::Sha512.new.matches?(crypted, *tokens)
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
module Zas
|
2
|
+
module CryptoProviders
|
3
|
+
# = Sha512
|
4
|
+
#
|
5
|
+
# Uses the Sha512 hash algorithm to encrypt passwords.
|
6
|
+
class Sha512
|
7
|
+
require 'digest/sha2'
|
8
|
+
|
9
|
+
# The number of times to loop through the encryption. This is twenty because that is what restful_authentication defaults to.
|
10
|
+
def stretches
|
11
|
+
@stretches ||= 20
|
12
|
+
end
|
13
|
+
attr_writer :stretches
|
14
|
+
|
15
|
+
# Turns your raw password into a Sha512 hash.
|
16
|
+
def encrypt(*tokens)
|
17
|
+
digest = tokens.flatten.join(nil)
|
18
|
+
stretches.times { digest = Digest::SHA512.hexdigest(digest) }
|
19
|
+
digest
|
20
|
+
end
|
21
|
+
|
22
|
+
# Does the crypted password match the tokens? Uses the same tokens that were used to encrypt.
|
23
|
+
def matches?(crypted, *tokens)
|
24
|
+
encrypt(*tokens) == crypted
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
data/lib/zas/filters.rb
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
require 'base64'
|
2
|
+
module Zas
|
3
|
+
module Filters
|
4
|
+
# Filter that base64 decodes the credentials and splits them
|
5
|
+
# on a colon (based on the HTTP Basic Authentication
|
6
|
+
# speciications).
|
7
|
+
module HttpBasicAuth
|
8
|
+
module_function
|
9
|
+
# Filter the authentication credentials.
|
10
|
+
#
|
11
|
+
# credentials - The credentials
|
12
|
+
#
|
13
|
+
# Returns a pair of [username, password]
|
14
|
+
def authenticate(credentials)
|
15
|
+
decoded_credentials = Base64.decode64(credentials)
|
16
|
+
decoded_credentials.split(":")
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
data/lib/zas/service.rb
ADDED
@@ -0,0 +1,78 @@
|
|
1
|
+
require 'zmq'
|
2
|
+
require 'syslogger'
|
3
|
+
require 'yajl'
|
4
|
+
require 'hashie'
|
5
|
+
|
6
|
+
module Zas
|
7
|
+
# Authentication service class. Construct a new instance of this
|
8
|
+
# class and call the instance method #run to start it.
|
9
|
+
class Service
|
10
|
+
require 'zas/service_configuration'
|
11
|
+
require 'zas/filters'
|
12
|
+
require 'zas/authenticators'
|
13
|
+
require 'zas/crypto_providers'
|
14
|
+
|
15
|
+
# A collection authenticators mapped to keys.
|
16
|
+
def authenticators
|
17
|
+
@authenticators ||= {}
|
18
|
+
end
|
19
|
+
|
20
|
+
# Initialize the service. The service will not be running yet. Invoke the #run method on the service
|
21
|
+
# instantce to run the service.
|
22
|
+
#
|
23
|
+
# config - Configuration spec for the service.
|
24
|
+
def initialize(config=ServiceConfiguration.new)
|
25
|
+
self.host = config.host
|
26
|
+
self.port = config.port
|
27
|
+
self.context = ZMQ::Context.new
|
28
|
+
self.logger = Syslogger.new(config.name, Syslog::LOG_PID, Syslog::LOG_LOCAL0)
|
29
|
+
end
|
30
|
+
|
31
|
+
# Run the service.
|
32
|
+
def run
|
33
|
+
socket = context.socket ZMQ::REP
|
34
|
+
socket.bind "tcp://#{host}:#{port}"
|
35
|
+
|
36
|
+
trap("INT") { socket.close }
|
37
|
+
|
38
|
+
begin
|
39
|
+
while req = socket.recv
|
40
|
+
logger.debug req
|
41
|
+
socket.send(encode(authenticate(req)))
|
42
|
+
end
|
43
|
+
rescue ZMQ::Error => e
|
44
|
+
logger.error "Shutting down: #{e.message}"
|
45
|
+
e
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
private
|
50
|
+
attr_accessor :host
|
51
|
+
attr_accessor :port
|
52
|
+
attr_accessor :context
|
53
|
+
attr_accessor :logger
|
54
|
+
|
55
|
+
# Handle the authentication.
|
56
|
+
#
|
57
|
+
# req_string - A JSON string
|
58
|
+
#
|
59
|
+
# Returns something useful
|
60
|
+
def authenticate(req_string)
|
61
|
+
req = Hashie::Mash.new(Yajl::Parser.parse(req_string))
|
62
|
+
authenticator = authenticators[req.strategy]
|
63
|
+
raise "No authenticator found for #{req.strategy}" unless authenticator
|
64
|
+
{:authenticated? => authenticator.authenticate(req.credentials)}
|
65
|
+
rescue => e
|
66
|
+
{:error => e.message}
|
67
|
+
end
|
68
|
+
|
69
|
+
# Encode the response object
|
70
|
+
#
|
71
|
+
# data - The object
|
72
|
+
#
|
73
|
+
# Returns the JSON encoded object
|
74
|
+
def encode(data)
|
75
|
+
Yajl::Encoder.encode(data)
|
76
|
+
end
|
77
|
+
end
|
78
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
module Zas
|
2
|
+
# Struct used to hold configuration information for the service
|
3
|
+
#
|
4
|
+
# host - The host to listen to requests on. May be "*" or an IP address.
|
5
|
+
# port - The port number to listen to requests on.
|
6
|
+
# name - The name of the service for logging.
|
7
|
+
class ServiceConfiguration < Struct.new(:host, :port, :name)
|
8
|
+
def initialize(attributes={})
|
9
|
+
attributes.each do |k, v|
|
10
|
+
self[k] = v
|
11
|
+
end
|
12
|
+
end
|
13
|
+
def host
|
14
|
+
self[:host] || '*'
|
15
|
+
end
|
16
|
+
def port
|
17
|
+
self[:port] || '5555'
|
18
|
+
end
|
19
|
+
def name
|
20
|
+
self[:name] || 'zas-service'
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
metadata
ADDED
@@ -0,0 +1,74 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: zas-service
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.1
|
5
|
+
prerelease:
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- Anthony Eden
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
date: 2012-04-22 00:00:00.000000000 Z
|
13
|
+
dependencies:
|
14
|
+
- !ruby/object:Gem::Dependency
|
15
|
+
name: rspec
|
16
|
+
requirement: !ruby/object:Gem::Requirement
|
17
|
+
none: false
|
18
|
+
requirements:
|
19
|
+
- - ! '>='
|
20
|
+
- !ruby/object:Gem::Version
|
21
|
+
version: '0'
|
22
|
+
type: :development
|
23
|
+
prerelease: false
|
24
|
+
version_requirements: !ruby/object:Gem::Requirement
|
25
|
+
none: false
|
26
|
+
requirements:
|
27
|
+
- - ! '>='
|
28
|
+
- !ruby/object:Gem::Version
|
29
|
+
version: '0'
|
30
|
+
description: ZeroAS provides an authentication service that uses 0mq as its transport
|
31
|
+
protocol
|
32
|
+
email:
|
33
|
+
- anthonyeden+zero-as@gmail.com
|
34
|
+
executables: []
|
35
|
+
extensions: []
|
36
|
+
extra_rdoc_files: []
|
37
|
+
files:
|
38
|
+
- lib/zas/authenticators/filtered_authenticator.rb
|
39
|
+
- lib/zas/authenticators/sequel_password_authenticator.rb
|
40
|
+
- lib/zas/authenticators.rb
|
41
|
+
- lib/zas/crypto_providers/sha512.rb
|
42
|
+
- lib/zas/crypto_providers.rb
|
43
|
+
- lib/zas/filters/http_basic_auth.rb
|
44
|
+
- lib/zas/filters.rb
|
45
|
+
- lib/zas/service.rb
|
46
|
+
- lib/zas/service_configuration.rb
|
47
|
+
- lib/zas.rb
|
48
|
+
- LICENSE
|
49
|
+
- README.md
|
50
|
+
homepage: http://github.com/zero-as/zas-service
|
51
|
+
licenses: []
|
52
|
+
post_install_message:
|
53
|
+
rdoc_options: []
|
54
|
+
require_paths:
|
55
|
+
- lib
|
56
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
57
|
+
none: false
|
58
|
+
requirements:
|
59
|
+
- - ! '>='
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '0'
|
62
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
63
|
+
none: false
|
64
|
+
requirements:
|
65
|
+
- - ! '>='
|
66
|
+
- !ruby/object:Gem::Version
|
67
|
+
version: 1.3.6
|
68
|
+
requirements: []
|
69
|
+
rubyforge_project:
|
70
|
+
rubygems_version: 1.8.21
|
71
|
+
signing_key:
|
72
|
+
specification_version: 3
|
73
|
+
summary: Simple, fast authentication service for Service Oriented Architectures
|
74
|
+
test_files: []
|