zas-service 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
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,3 @@
1
+ module Zas
2
+ require 'zas/service'
3
+ end
@@ -0,0 +1,6 @@
1
+ module Zas
2
+ module Authenticators
3
+ require 'zas/authenticators/filtered_authenticator'
4
+ require 'zas/authenticators/sequel_password_authenticator'
5
+ end
6
+ end
@@ -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,5 @@
1
+ module Zas
2
+ module CryptoProviders
3
+ require 'zas/crypto_providers/sha512'
4
+ end
5
+ 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
@@ -0,0 +1,5 @@
1
+ module Zas
2
+ module Filters
3
+ require 'zas/filters/http_basic_auth'
4
+ end
5
+ end
@@ -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
@@ -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: []