gsasl 0.1.0 → 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
data/.rvmrc ADDED
@@ -0,0 +1 @@
1
+ rvm use ruby-1.9.3-p194
data/.travis.yml ADDED
@@ -0,0 +1,2 @@
1
+ before_script:
2
+ - sudo apt-get install libgsasl
data/LICENCE ADDED
@@ -0,0 +1,19 @@
1
+ Copyright (c) 2012 Vincent Landgraf
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining a copy of
4
+ this software and associated documentation files (the "Software"), to deal in
5
+ the Software without restriction, including without limitation the rights to
6
+ use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
7
+ the Software, and to permit persons to whom the Software is furnished to do so,
8
+ subject to the following conditions:
9
+
10
+ The above copyright notice and this permission notice shall be included in all
11
+ copies or substantial portions of the Software.
12
+
13
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
19
+ SOFTWARE.
data/README.md CHANGED
@@ -9,33 +9,82 @@ This libaray is a lib ffi based wrapper for the [GNU SASL](http://www.gnu.org/so
9
9
  * **CRAM-MD5**: Challenge-Response Authentication Mechanism.
10
10
  * **DIGEST-MD5**: Digest Authentication.
11
11
  * **SCRAM-SHA-1**: SCRAM-SHA-1 authentication.
12
- * **NTLM**: Microsoft NTLM authentication.
13
12
  * **SECURID**: Authentication using tokens.
13
+
14
+ Platfrom and compile flags dependend mechanisms:
15
+
16
+ * **NTLM**: Microsoft NTLM authentication.
14
17
  * **GSSAPI**: GSSAPI (Kerberos 5) authentication.
15
18
  * **GS2-KRB5**: Improved GSSAPI (Kerberos 5) authentication.
16
19
  * **KERBEROS\_V5**: Experimental KERBEROS\_V5 authentication.
17
20
  * **SAML20**: Experimental SAML20 authentication.
18
21
  * **OPENID20**: Experimental OPENID20 authentication.
19
22
 
23
+ # Install libgsasl
24
+
25
+ To use the library the libgsasl must be installed on the system. The gem uses libffi to access the library so no further comilation needed. It also should work with all important versions of ruby.
26
+
27
+ ## Mac OS X
28
+
29
+ Install the library using homebrew:
30
+
31
+ brew install libgsasl
32
+
33
+ ## Debian & Ubuntu
34
+
35
+ Install the library using apt-get:
36
+
37
+ sudo apt-get install libgsasl7
38
+
39
+ ## FreeBSD
40
+
41
+ Install the library using ports (as root):
42
+
43
+ cd /usr/ports/security/gsasl/
44
+ make install clean
45
+
20
46
  # Use in Ruby
21
47
 
48
+ ## To authenticate against a server
49
+
50
+ In this example a client authenticates against an IMAP4 server. The methode `#authenticate_with` is used to setup all the data and callbacks neccessary to perform the authentication.
51
+
52
+ # connect to an imap server
53
+ require 'socket'
54
+ socket = TCPSocket.new('imap.example.com', 143)
55
+ puts socket.gets
56
+
57
+ # issue an authenticate command
58
+ socket.print "a1 AUTHENTICATE LOGIN\r\n"
59
+
60
+ # authenticate using the imap4 protocol specifics
61
+ context = Gsasl::Context.new
62
+ context.authenticate_with("LOGIN", "user@example.com", "secret") do |remote|
63
+ remote.receive { socket.gets.gsub!("\r\n|+\s", "") }
64
+ remote.send { |data| socket.print "#{data}\r\n" }
65
+ end
66
+
67
+ # logout
68
+ puts socket.gets
69
+ socket.print "a2 LOGOUT\r\n"
70
+
71
+ # close connection
72
+ puts socket.gets
73
+ socket.close
74
+
75
+ ## Advanced
76
+
22
77
  In the following example the server and the client are on the same machine. If the server is on the remote site, one has to implement a server that will return the next challenge on `server#read` and implements a `server#send` to send the challenge to the server. Also it is possible to not use the `#authenticate` function but to implement the processing individually.
23
78
 
24
79
  session = Gsasl::Context.new
25
80
  client = session.create_client("CRAM-MD5")
26
- server = session.create_server("CRAM-MD5")
27
-
28
- server.set_callback do |property|
29
- if property == Gsasl::GSASL_PASSWORD
30
- if server[Gsasl::GSASL_AUTHID] == "joe"
31
- server[Gsasl::GSASL_PASSWORD] = "secret"
32
- end
33
- Gsasl::GSASL_OK
34
- end
81
+ server = session.create_server("CRAM-MD5") do |type, authid|
82
+ "secret" if type = :password && authid == "joe"
35
83
  end
36
84
 
37
- client[Gsasl::GSASL_AUTHID] = "joe"
38
- client[Gsasl::GSASL_PASSWORD] = "secret"
39
-
40
- client.authenticate(server).should be_true
41
-
85
+ @client.credentials!("joe", "secret")
86
+ @client.authenticate(@server).should be_true
87
+
88
+ # Copyright Licence
89
+
90
+ Copyright (c) 2012 Vincent Landgraf All Rights Reserved. Released under a MIT License.
data/lib/gsasl/context.rb CHANGED
@@ -60,9 +60,15 @@ module Gsasl
60
60
  # @return [Gsasl::Peer] the server peer
61
61
  # @example
62
62
  # peer = @session.create_server("CRAM-MD5")
63
- def create_server(mechanism_name)
63
+ # @example Server with password database attached directly
64
+ # peer = @session.create_server("CRAM-MD5") do |type, authid|
65
+ # DB.find_password_for_user(auth_id) if type == :password
66
+ # end
67
+ def create_server(mechanism_name, realm = "gsasl", &block)
64
68
  peer = Peer.new(@context, mechanism_name, :server)
65
69
  @peers[peer.session.address] = peer
70
+ peer.realm = realm
71
+ peer.authentication_callback = block if block_given?
66
72
  peer
67
73
  end
68
74
 
@@ -76,6 +82,48 @@ module Gsasl
76
82
  @peers[peer.session.address] = peer
77
83
  peer
78
84
  end
85
+
86
+ # Authenticate against a remote peer using a socket like authenication
87
+ # scheme.
88
+ # @param [String] mechanism the SASL mechanism to use
89
+ # @param [String] authid the username auth id of the user to use for auth.
90
+ # @param [String] password the password of the specified user
91
+ # @return [Boolean] true if the authentication was successful, false
92
+ # otherwise
93
+ # @yield [remote] the block that defines how to interact with the remote
94
+ # site
95
+ # @yieldparam [Gsasl::RemoteAuthenticator] remote the remote authenticator
96
+ # that needs to be defined in order for gsasl to receive and set data.
97
+ # @example Authenticate against an imap server with PLAIN authentication
98
+ # # connect to an imap server
99
+ # require 'socket'
100
+ # socket = TCPSocket.new('imap.example.com', 143)
101
+ # puts socket.gets
102
+ #
103
+ # # issue an authenticate command
104
+ # socket.print "a1 AUTHENTICATE PLAIN\r\n"
105
+ #
106
+ # # authenticate using the imap4 protocol specifics
107
+ # context = Gsasl::Context.new
108
+ # context.authenticate_with("PLAIN", "user@example.com", "pass") do |remote|
109
+ # remote.receive { socket.gets.gsub!("\r\n|+\s", "") }
110
+ # remote.send { |data| socket.print "#{data}\r\n" }
111
+ # end
112
+ # puts socket.gets # => capabilities after authentication
113
+ #
114
+ # # logout
115
+ # socket.print "a2 LOGOUT\r\n"
116
+ # puts socket.gets
117
+ #
118
+ # # close connection
119
+ # socket.close
120
+ # context.close
121
+ def authenticate_with(mechanism, authid, password, &block)
122
+ client = create_client(mechanism)
123
+ client.credentials!(authid, password)
124
+ client.authenticate_with(&block)
125
+ client.close
126
+ end
79
127
 
80
128
  private
81
129
 
data/lib/gsasl/peer.rb CHANGED
@@ -1,3 +1,5 @@
1
+ require 'digest/md5'
2
+
1
3
  module Gsasl
2
4
  # A peer is a client or server side that processes data to do an
3
5
  # authentication.
@@ -25,6 +27,46 @@ module Gsasl
25
27
  @session = peer.get_pointer(0)
26
28
  end
27
29
 
30
+ # Updates the peer with credential information
31
+ # @param [String] authid the username auth id of the user to use for auth.
32
+ # @param [String] password the password of the specified user
33
+ # @example
34
+ # peer.credentials! "username", "secret"
35
+ def credentials!(authid, password)
36
+ self[Gsasl::GSASL_AUTHID] = authid
37
+ self[Gsasl::GSASL_PASSWORD] = password
38
+ end
39
+
40
+ # Updates the peer with secure id information
41
+ # @param [String] authid the username auth id of the user to use for auth.
42
+ # @param [String] passcode the passcode of the specified id
43
+ # @example
44
+ # peer.credentials! "username", "12312312331"
45
+ def secureid!(authid, passcode)
46
+ self[Gsasl::GSASL_AUTHID] = authid
47
+ self[Gsasl::GSASL_PASSCODE] = passcode
48
+ end
49
+
50
+ # Updates the peer with a service definition
51
+ # @param [String] name the name of the service. a list of service names
52
+ # can be found here: http://www.iana.org/assignments/gssapi-service-names/gssapi-service-names.xml
53
+ # @param [String] hostname the name of the host the service is on
54
+ # @example
55
+ # peer.service! "smtp", "localhost"
56
+ def service!(name, hostname)
57
+ self[Gsasl::GSASL_SERVICE] = name
58
+ self[Gsasl::GSASL_HOSTNAME] = hostname
59
+ end
60
+
61
+ # Update the anoynmous token that the client peer used to authenticate with
62
+ # the server
63
+ # @param [String] token the token that will be send to the server
64
+ # @example
65
+ # peer.anonymous! "some-token"
66
+ def anonymous!(token)
67
+ self[Gsasl::GSASL_ANONYMOUS_TOKEN] = token
68
+ end
69
+
28
70
  # Sets a property for this peer.
29
71
  # @param [Fixnum] property on of the Gsasl API Keys
30
72
  # @param [String] value the value tho set for the api key
@@ -43,6 +85,53 @@ module Gsasl
43
85
  Gsasl.gsasl_property_get(@session, property)
44
86
  end
45
87
 
88
+ # Update the realm of the peer
89
+ # @param [String] val the realm that should be set on the peer
90
+ # @example
91
+ # peer.realm = "Awesome SMTPD"
92
+ def realm=(val)
93
+ self[Gsasl::GSASL_REALM] = val
94
+ end
95
+
96
+ # Returns the realm that is set for the peer
97
+ # @return [String, nil] the realm if one was set or nil
98
+ def realm
99
+ self[Gsasl::GSASL_REALM]
100
+ end
101
+
102
+ # Update authzid for external authentication
103
+ # @param [String] val the authzid that should be set on the peer
104
+ # @example
105
+ # peer.realm = "Awesome SMTPD"
106
+ def authzid=(val)
107
+ self[Gsasl::GSASL_AUTHZID] = val
108
+ end
109
+
110
+ # Returns the authzid for external authentication
111
+ # @return [String, nil] the authzid if one was set or nil
112
+ def authzid
113
+ self[Gsasl::GSASL_AUTHZID]
114
+ end
115
+
116
+ # Returns the authid for authentication
117
+ # @return [String, nil] the authid if one was set or nil
118
+ def authid
119
+ self[Gsasl::GSASL_AUTHID]
120
+ end
121
+
122
+ # Generate a digest md5 hased password that can be stored in the database
123
+ # to authenticate the user without saving a plaintext password.
124
+ # @note if the realm of the peer is set, it will be used to generate the
125
+ # hash. Therefore it has tp be set later on the peer also to match the
126
+ # password again. Alternatively the realm can be passed directly.
127
+ # @param [String] authid the username or id to generate the password for
128
+ # @param [String] password the password to hash
129
+ # @param [String] realm the realm if not passed the peer realm will be used
130
+ # if no realm is set to an empty string ("")
131
+ def digest_md5_hashed_password(authid, password, realm = nil)
132
+ Digest::MD5.hexdigest("#{authid}:#{realm || self.realm}:#{password}")
133
+ end
134
+
46
135
  # Registers a callback for the peer. In case a variable is not provided.
47
136
  # @yield [property] The callback that will be calles during the processing.
48
137
  # @yieldparam [Fixnum] property a property for the
@@ -51,6 +140,98 @@ module Gsasl
51
140
  @callback = block
52
141
  end
53
142
 
143
+ # Sets an authentication callback on the peer. The passed block will be
144
+ # called to determine the authentication password, secureid, password hash
145
+ # etc.. Depending on what mechanisms one supports different types must be
146
+ # handled.
147
+ # @yieldparam [Symbol] type is that should be handled in this case
148
+ # @yieldparam [String] authid the authenticated id
149
+ # @yieldreturn [String, true, nil] a mechanism specific value or no value
150
+ # to indicate a failed authentication
151
+ def authentication_callback=(block)
152
+ self.callback do |property|
153
+ case property
154
+ when Gsasl::GSASL_PASSWORD
155
+ handle_password_authentication(&block)
156
+ when Gsasl::GSASL_VALIDATE_SECURID
157
+ handle_secureid_authentication(&block)
158
+ when Gsasl::GSASL_DIGEST_MD5_HASHED_PASSWORD
159
+ handle_digest_md5_authentication(&block)
160
+ when Gsasl::GSASL_VALIDATE_ANONYMOUS
161
+ handle_anonymous_authentication(&block)
162
+ when Gsasl::GSASL_VALIDATE_EXTERNAL
163
+ handle_external_authentication(&block)
164
+ end
165
+ end
166
+ end
167
+
168
+ # Handles the password authentication with the passed block. Therefor the
169
+ # block has to return the password for the passed user. No return value means
170
+ # that the user is unknown and the authentication fails.
171
+ # @yield [type, authid] the block that handles password gathering.
172
+ # @yieldparam [Symbol] type is allways :password for password auth
173
+ # @yieldparam [String] authid the authenticated id
174
+ # @yieldreturn [String, nil] the password or nil if the user wasn't found
175
+ def handle_password_authentication
176
+ if password = yield(:password, authid)
177
+ self[Gsasl::GSASL_PASSWORD] = password
178
+ Gsasl::GSASL_OK
179
+ end
180
+ end
181
+
182
+ # Handles the digest md5 based password hash authentication with the passed
183
+ # block. Therefor theblock has to return the hashed password for the passed
184
+ # user. No return value means that the user is unknown and the
185
+ # authentication fails.
186
+ # @yield [type, authid] the block that handles password hash gathering.
187
+ # @yieldparam [Symbol] type is allways :digest_md5_hashed_password for
188
+ # digest md5 based password hash auth
189
+ # @yieldparam [String] authid the authenticated id
190
+ # @yieldreturn [String, nil] the password hash or nil if the user wasn't
191
+ # found
192
+ def handle_digest_md5_authentication
193
+ if hash = yield(:digest_md5_hashed_password, authid)
194
+ self[Gsasl::GSASL_DIGEST_MD5_HASHED_PASSWORD] = hash
195
+ Gsasl::GSASL_OK
196
+ end
197
+ end
198
+
199
+ # Handles the secureid authentication with the passed block. Therefor the
200
+ # block has to return the secureid for the passed user. No return value means
201
+ # that the user is unknown and the authentication fails.
202
+ # @yield [type, authid] the block that handles secureid gathering.
203
+ # @yieldparam [Symbol] type is allways :passcode for secureid auth
204
+ # @yieldparam [String] authid the authenticated id
205
+ # @yieldreturn [String, nil] the secureid or nil if the user wasn't found
206
+ def handle_secureid_authentication
207
+ if secureid = yield(:passcode, authid)
208
+ self[Gsasl::GSASL_PASSCODE] = secureid
209
+ Gsasl::GSASL_OK
210
+ end
211
+ end
212
+
213
+ # Handles the anonymous authentication with the passed block. Therefor the
214
+ # block has to return the success for the anonymous user. No return value
215
+ # means that the user is not allowed and the authentication fails.
216
+ # @yield [type, authid] the block that handles anonymous authentication
217
+ # @yieldparam [Symbol] type is allways :anonymous for external auth
218
+ # @yieldparam [String] authid the authenticated id
219
+ # @yieldreturn [Boolean, nil] true or nil if the anonymous isn't allowed
220
+ def handle_anonymous_authentication
221
+ Gsasl::GSASL_OK if yield(:anonymous, self[Gsasl::GSASL_ANONYMOUS_TOKEN])
222
+ end
223
+
224
+ # Handles the external authentication with the passed block. Therefor the
225
+ # block has to return the success for the passed user. No return value means
226
+ # that the user is unknown and the authentication fails.
227
+ # @yield [type, authid] the block that handles external authentication
228
+ # @yieldparam [Symbol] type is allways :external for external auth
229
+ # @yieldparam [String] authid the authenticated id
230
+ # @yieldreturn [Boolean, nil] true or nil if the user wasn't authenticated
231
+ def handle_external_authentication
232
+ Gsasl::GSASL_OK if yield(:external, authzid)
233
+ end
234
+
54
235
  # Used as a server hook in the local test environment.
55
236
  # @return [Array<Fixnum, String>] Result code and base64 encoded challenge
56
237
  def read #b64_str
@@ -69,18 +250,44 @@ module Gsasl
69
250
  # @return [Boolean] true if the authentication was successfull,
70
251
  # false otherwise
71
252
  def authenticate(server)
72
- result = -1
253
+ result = GSASL_NEEDS_MORE
254
+ input = nil
73
255
 
74
- begin
75
- result, input = server.read
76
- result, output = process input
77
-
78
- if (result == GSASL_NEEDS_MORE || result == GSASL_OK)
79
- result, output = server.send(output)
80
- end
81
- end while result == GSASL_NEEDS_MORE
256
+ while result == GSASL_NEEDS_MORE
257
+ result, output = server.send(input)
258
+ break if result != Gsasl::GSASL_NEEDS_MORE
259
+ _, input = process output
260
+ end
261
+
262
+ result == Gsasl::GSASL_OK
263
+ end
264
+
265
+ # Authenticate against a remote peer using a socket like authenication
266
+ # scheme.
267
+ # @return [Boolean] true if the authentication was successful, false
268
+ # otherwise
269
+ # @yield [remote] the block that defines how to interact with the remote
270
+ # site
271
+ # @yieldparam [Gsasl::RemoteAuthenticator] remote the remote authenticator
272
+ # that needs to be defined in order for gsasl to receive and set data.
273
+ # @example Authenticate with a ruby socket against an imap server
274
+ # client.authenticate_with do |remote|
275
+ # remote.receive { socket.gets.gsub!("\r\n|+\s", "") }
276
+ # remote.send { |data| socket.print "#{data}\r\n" }
277
+ # end
278
+ def authenticate_with(&block)
279
+ result = GSASL_NEEDS_MORE
280
+
281
+ # create a new authenticator and define its behaviour
282
+ remote = RemoteAuthenticator.new
283
+ block.call(remote)
284
+
285
+ while result == GSASL_NEEDS_MORE
286
+ challenge = remote.receive
287
+ result, response = process challenge
288
+ remote.send(response)
289
+ end
82
290
 
83
- Gsasl.raise_error!(result) unless Gsasl::GSASL_AUTHENTICATION_ERROR
84
291
  result == Gsasl::GSASL_OK
85
292
  end
86
293
 
@@ -0,0 +1,44 @@
1
+ module Gsasl
2
+ # This class handles remote authentication sessions that are based on a socket
3
+ # like interaction mechanism. This class will most of the time not be used
4
+ # directly but through helper methods (See Peer#authenticate_with).
5
+ class RemoteAuthenticator
6
+ def initialize
7
+ @receive_callback = nil
8
+ @send_callback = nil
9
+ end
10
+
11
+ # This defines or calls the recieve callback. It will be defined, if a
12
+ # block is given, otherwise the callback is going to be called.
13
+ # @yield the block that is going to be called if data need to be read from
14
+ # the remote site.
15
+ # @yieldreturn [String] the callback should return a string that includes
16
+ # a challenge
17
+ def receive(&block)
18
+ if block_given?
19
+ # define the callback
20
+ @receive_callback = block
21
+ elsif @receive_callback
22
+ @receive_callback.call
23
+ else
24
+ raise GsaslError, "The receive callback is not defined!"
25
+ end
26
+ end
27
+
28
+ # This defines or calls the send callback. It will be defined, if a
29
+ # block is given, otherwise the callback is going to be called.
30
+ # @yield [data] the block that is going to be called if data need to be
31
+ # send to the remote site.
32
+ # @yieldparam [String] data that should be send to a remote site.
33
+ def send(data = nil, &block)
34
+ if block_given?
35
+ # define the callback
36
+ @send_callback = block
37
+ elsif @send_callback
38
+ @send_callback.call(data)
39
+ else
40
+ raise GsaslError, "The send callback is not defined!"
41
+ end
42
+ end
43
+ end
44
+ end
data/lib/gsasl/version.rb CHANGED
@@ -1,3 +1,3 @@
1
1
  module Gsasl
2
- VERSION = "0.1.0"
2
+ VERSION = "0.2.0"
3
3
  end
data/lib/gsasl.rb CHANGED
@@ -1,6 +1,7 @@
1
1
  require "gsasl/version"
2
2
  require "gsasl/native"
3
3
  require "gsasl/context"
4
+ require "gsasl/remote_authenticator"
4
5
  require "gsasl/peer"
5
6
 
6
7
  module Gsasl
@@ -0,0 +1,212 @@
1
+ require 'spec_helper'
2
+ require 'securerandom'
3
+
4
+ describe "Abstraction layer for the authentication callback" do
5
+ before(:each) do
6
+ @session = Gsasl::Context.new
7
+ end
8
+
9
+ context "ANONYMOUS" do
10
+ before(:each) do
11
+ @client = @session.create_client("ANONYMOUS")
12
+ @server = @session.create_server("ANONYMOUS") do |type, authid|
13
+ type == :anonymous and authid == "joe"
14
+ end
15
+ end
16
+
17
+ it "should be able to authenticate with correct credentials" do
18
+ @client.anonymous! "joe"
19
+ @client.authenticate(@server).should be_true
20
+ end
21
+
22
+ it "should not be able to authenticate with wrong credentials" do
23
+ @client.anonymous! "joe1"
24
+ @client.authenticate(@server).should be_false
25
+ end
26
+
27
+ after(:each) do
28
+ @client.close
29
+ @server.close
30
+ end
31
+ end
32
+
33
+ context "EXTERNAL" do
34
+ before(:each) do
35
+ @client = @session.create_client("EXTERNAL")
36
+ @server = @session.create_server("EXTERNAL") do |type, authid|
37
+ type == :external and authid == "joe"
38
+ end
39
+ end
40
+
41
+ it "should be able to authenticate with correct credentials" do
42
+ @client.authzid = "joe"
43
+ @client.authenticate(@server).should be_true
44
+ end
45
+
46
+ it "should not be able to authenticate with wrong credentials" do
47
+ @client.authzid = "joe1"
48
+ @client.authenticate(@server).should be_false
49
+ end
50
+
51
+ after(:each) do
52
+ @client.close
53
+ @server.close
54
+ end
55
+ end
56
+
57
+ context "PLAIN" do
58
+ before(:each) do
59
+ @client = @session.create_client("PLAIN")
60
+ @server = @session.create_server("PLAIN") do |type, authid|
61
+ "secret" if type = :password && authid == "joe"
62
+ end
63
+ end
64
+
65
+ it "should be able to authenticate with correct credentials" do
66
+ @client.credentials!("joe", "secret")
67
+ @client.authenticate(@server).should be_true
68
+ end
69
+
70
+ it "should not be able to authenticate with wrong credentials" do
71
+ @client.credentials!("joe1", "secret")
72
+ @client.authenticate(@server).should be_false
73
+ end
74
+
75
+ after(:each) do
76
+ @client.close
77
+ @server.close
78
+ end
79
+ end
80
+
81
+ context "LOGIN" do
82
+ before(:each) do
83
+ @client = @session.create_client("LOGIN")
84
+ @server = @session.create_server("LOGIN") do |type, authid|
85
+ "secret" if type = :password && authid == "joe"
86
+ end
87
+ end
88
+
89
+ it "should be able to authenticate with correct credentials" do
90
+ @client.credentials!("joe", "secret")
91
+ @client.authenticate(@server).should be_true
92
+ end
93
+
94
+ it "should not be able to authenticate with wrong credentials" do
95
+ @client.credentials!("joe1", "secret")
96
+ @client.authenticate(@server).should be_false
97
+ end
98
+
99
+ after(:each) do
100
+ @client.close
101
+ @server.close
102
+ end
103
+ end
104
+
105
+ context "SECURID" do
106
+ before(:each) do
107
+ @client = @session.create_client("SECURID")
108
+ @server = @session.create_server("SECURID") do |type, authid|
109
+ "579a0eaa23c2c60a1bc5" if type = :passcode && authid == "joe"
110
+ end
111
+ end
112
+
113
+ it "should be able to authenticate with correct credentials" do
114
+ @client.secureid!("joe", "579a0eaa23c2c60a1bc5")
115
+ @client.authenticate(@server).should be_true
116
+ end
117
+
118
+ it "should not be able to authenticate with wrong credentials" do
119
+ @client.secureid!("joe1", "579a0eaa23c2c60a1bc5")
120
+ @client.authenticate(@server).should be_false
121
+ end
122
+
123
+ after(:each) do
124
+ @client.close
125
+ @server.close
126
+ end
127
+ end
128
+
129
+ context "CRAM-MD5" do
130
+ before(:each) do
131
+ @client = @session.create_client("CRAM-MD5")
132
+ @server = @session.create_server("CRAM-MD5") do |type, authid|
133
+ "secret" if type = :password && authid == "joe"
134
+ end
135
+ end
136
+
137
+ it "should be able to authenticate with correct credentials" do
138
+ @client.credentials!("joe", "secret")
139
+ @client.authenticate(@server).should be_true
140
+ end
141
+
142
+ it "should not be able to authenticate with wrong credentials" do
143
+ @client.credentials!("joe1", "secret")
144
+ @client.authenticate(@server).should be_false
145
+ end
146
+
147
+ after(:each) do
148
+ @client.close
149
+ @server.close
150
+ end
151
+ end
152
+
153
+ context "DIGEST-MD5" do
154
+ before(:each) do
155
+ @client = @session.create_client("DIGEST-MD5")
156
+ @server = @session.create_server("DIGEST-MD5") do |type, authid|
157
+ # emulate a stored md5 hased password a cleartext password could be used
158
+ # instead. The format is: <authid:realm:password>
159
+ if type = :digest_md5_hashed_password && authid == "joe"
160
+ @server.digest_md5_hashed_password(authid, "secret")
161
+ end
162
+ end
163
+ end
164
+
165
+ it "should be able to authenticate with correct credentials" do
166
+ @client.credentials!("joe", "secret")
167
+ @client.service!("smtp", "localhost")
168
+ @client.authenticate(@server).should be_true
169
+ end
170
+
171
+ it "should not be able to authenticate with wrong credentials" do
172
+ @client.credentials!("joe1", "secret")
173
+ @client.service!("smtp", "localhost")
174
+ @client.authenticate(@server).should be_false
175
+ end
176
+
177
+ after(:each) do
178
+ @client.close
179
+ @server.close
180
+ end
181
+ end
182
+
183
+ context "SCRAM-SHA-1" do
184
+ before(:each) do
185
+ @client = @session.create_client("SCRAM-SHA-1")
186
+ @server = @session.create_server("SCRAM-SHA-1") do |type, authid|
187
+ "secret" if type = :password && authid == "joe"
188
+ end
189
+ end
190
+
191
+ it "should be able to authenticate with correct credentials" do
192
+ @client.credentials!("joe", "secret")
193
+ @client.service!("smtp", "localhost")
194
+ @client.authenticate(@server).should be_true
195
+ end
196
+
197
+ it "should not be able to authenticate with wrong credentials" do
198
+ @client.credentials!("joe1", "secret")
199
+ @client.service!("smtp", "localhost")
200
+ @client.authenticate(@server).should be_false
201
+ end
202
+
203
+ after(:each) do
204
+ @client.close
205
+ @server.close
206
+ end
207
+ end
208
+
209
+ after(:each) do
210
+ @session.close
211
+ end
212
+ end
@@ -1,4 +1,6 @@
1
1
  require 'spec_helper'
2
+ require 'digest/md5'
3
+ require 'securerandom'
2
4
 
3
5
  describe "Authentications" do
4
6
  before(:each) do
@@ -41,13 +43,270 @@ describe "Authentications" do
41
43
  @client[Gsasl::GSASL_PASSWORD] = "secret"
42
44
 
43
45
  @client.authenticate(@server).should be_false
44
- end
46
+ end
47
+
48
+ after(:each) do
49
+ @client.close
50
+ @server.close
51
+ end
52
+ end
53
+
54
+ describe "DIGEST-MD5" do
55
+ before(:each) do
56
+ @client = @session.create_client("DIGEST-MD5")
57
+ @server = @session.create_server("DIGEST-MD5")
58
+ end
59
+
60
+ it "should be able to authenticate correctly" do
61
+ @server.callback do |property|
62
+ if property == Gsasl::GSASL_PASSWORD
63
+ if @server[Gsasl::GSASL_AUTHID] == "joe"
64
+ @server[Gsasl::GSASL_PASSWORD] = "secret"
65
+ end
66
+ Gsasl::GSASL_OK
67
+ end
68
+ end
69
+
70
+ @client[Gsasl::GSASL_SERVICE] = "imap"
71
+ @client[Gsasl::GSASL_HOSTNAME] = "localhost"
72
+ @client[Gsasl::GSASL_AUTHID] = "joe"
73
+ @client[Gsasl::GSASL_PASSWORD] = "secret"
74
+
75
+ @client.authenticate(@server).should be_true
76
+ end
77
+
78
+ it "should be possible to not authenticate correctly" do
79
+ @server.callback do |property|
80
+ if property == Gsasl::GSASL_PASSWORD
81
+ if @server[Gsasl::GSASL_AUTHID] == "joe"
82
+ @server[Gsasl::GSASL_PASSWORD] = "test"
83
+ end
84
+ Gsasl::GSASL_OK
85
+ end
86
+ end
87
+
88
+ @client[Gsasl::GSASL_SERVICE] = "imap"
89
+ @client[Gsasl::GSASL_HOSTNAME] = "localhost"
90
+ @client[Gsasl::GSASL_AUTHID] = "joe"
91
+ @client[Gsasl::GSASL_PASSWORD] = "secret"
92
+
93
+ @client.authenticate(@server).should be_false
94
+ end
95
+
96
+ after(:each) do
97
+ @client.close
98
+ @server.close
99
+ end
100
+ end
101
+
102
+ describe "SCRAM-SHA-1" do
103
+ before(:each) do
104
+ @client = @session.create_client("SCRAM-SHA-1")
105
+ @server = @session.create_server("SCRAM-SHA-1")
106
+ end
107
+
108
+ it "should be able to authenticate correctly" do
109
+ @server.callback do |property|
110
+ if property == Gsasl::GSASL_PASSWORD
111
+ if @server[Gsasl::GSASL_AUTHID] == "joe"
112
+ @server[Gsasl::GSASL_PASSWORD] = "secret"
113
+ Gsasl::GSASL_OK
114
+ end
115
+ end
116
+ end
117
+
118
+ @client[Gsasl::GSASL_AUTHID] = "joe"
119
+ @client[Gsasl::GSASL_PASSWORD] = "secret"
120
+
121
+ @client.authenticate(@server).should be_true
122
+ end
123
+
124
+ it "should be possible to not authenticate correctly" do
125
+ @server.callback do |property|
126
+ if property == Gsasl::GSASL_PASSWORD
127
+ if @server[Gsasl::GSASL_AUTHID] == "joe"
128
+ @server[Gsasl::GSASL_PASSWORD] = "secret"
129
+ Gsasl::GSASL_OK
130
+ end
131
+ end
132
+ end
133
+
134
+ @client[Gsasl::GSASL_AUTHID] = "joe1"
135
+ @client[Gsasl::GSASL_PASSWORD] = "secret"
136
+
137
+ @client.authenticate(@server).should be_false
138
+ end
45
139
 
46
140
  after(:each) do
47
141
  @client.close
48
142
  @server.close
49
143
  end
50
- end
144
+ end
145
+
146
+ describe "PLAIN" do
147
+ before(:each) do
148
+ @client = @session.create_client("PLAIN")
149
+ @server = @session.create_server("PLAIN")
150
+ end
151
+
152
+ it "should be able to authenticate correctly" do
153
+ @server.callback do |property|
154
+ if property == Gsasl::GSASL_PASSWORD
155
+ if @server[Gsasl::GSASL_AUTHID] == "joe"
156
+ @server[Gsasl::GSASL_PASSWORD] = "secret"
157
+ end
158
+ Gsasl::GSASL_OK
159
+ end
160
+ end
161
+
162
+ @client[Gsasl::GSASL_AUTHID] = "joe"
163
+ @client[Gsasl::GSASL_PASSWORD] = "secret"
164
+
165
+ @client.authenticate(@server).should be_true
166
+ end
167
+
168
+ it "should be possible to not authenticate correctly" do
169
+ @server.callback do |property|
170
+ if property == Gsasl::GSASL_PASSWORD
171
+ if @server[Gsasl::GSASL_AUTHID] == "joe"
172
+ @server[Gsasl::GSASL_PASSWORD] = "test"
173
+ end
174
+ Gsasl::GSASL_OK
175
+ end
176
+ end
177
+
178
+ @client[Gsasl::GSASL_AUTHID] = "joe"
179
+ @client[Gsasl::GSASL_PASSWORD] = "secret"
180
+
181
+ @client.authenticate(@server).should be_false
182
+ end
183
+
184
+ after(:each) do
185
+ @client.close
186
+ @server.close
187
+ end
188
+ end
189
+
190
+ describe "LOGIN" do
191
+ before(:each) do
192
+ @client = @session.create_client("LOGIN")
193
+ @server = @session.create_server("LOGIN")
194
+ end
195
+
196
+ it "should be able to authenticate correctly" do
197
+ @server.callback do |property|
198
+ if property == Gsasl::GSASL_PASSWORD
199
+ if @server[Gsasl::GSASL_AUTHID] == "joe"
200
+ @server[Gsasl::GSASL_PASSWORD] = "secret"
201
+ end
202
+ Gsasl::GSASL_OK
203
+ end
204
+ end
205
+
206
+ @client[Gsasl::GSASL_AUTHID] = "joe"
207
+ @client[Gsasl::GSASL_PASSWORD] = "secret"
208
+
209
+ @client.authenticate(@server).should be_true
210
+ end
211
+
212
+ it "should be possible to not authenticate correctly" do
213
+ @server.callback do |property|
214
+ if property == Gsasl::GSASL_PASSWORD
215
+ if @server[Gsasl::GSASL_AUTHID] == "joe"
216
+ @server[Gsasl::GSASL_PASSWORD] = "test"
217
+ end
218
+ Gsasl::GSASL_OK
219
+ end
220
+ end
221
+
222
+ @client[Gsasl::GSASL_AUTHID] = "joe"
223
+ @client[Gsasl::GSASL_PASSWORD] = "secret"
224
+
225
+ @client.authenticate(@server).should be_false
226
+ end
227
+
228
+ after(:each) do
229
+ @client.close
230
+ @server.close
231
+ end
232
+ end
233
+
234
+ describe "SECURID" do
235
+ before(:each) do
236
+ @client = @session.create_client("SECURID")
237
+ @server = @session.create_server("SECURID")
238
+ end
239
+
240
+ it "should be able to authenticate correctly" do
241
+ @server.callback do |property|
242
+ if property == Gsasl::GSASL_VALIDATE_SECURID
243
+ if @server[Gsasl::GSASL_AUTHID] == "joe" &&
244
+ @server[Gsasl::GSASL_PASSCODE] == "579a0eaa23c2c60a1bc5"
245
+ Gsasl::GSASL_OK
246
+ end
247
+ end
248
+ end
249
+
250
+ @client[Gsasl::GSASL_AUTHID] = "joe"
251
+ @client[Gsasl::GSASL_PASSCODE] = "579a0eaa23c2c60a1bc5"
252
+
253
+ @client.authenticate(@server).should be_true
254
+ end
255
+
256
+ it "should be possible to not authenticate correctly" do
257
+ @server.callback do |property|
258
+ if property == Gsasl::GSASL_PASSCODE
259
+ if @server[Gsasl::GSASL_AUTHID] == "joe"
260
+ @server[Gsasl::GSASL_PASSCODE] = "579a0eaa23c2c60a1bc5"
261
+ end
262
+ Gsasl::GSASL_OK
263
+ end
264
+ end
265
+
266
+ @client[Gsasl::GSASL_AUTHID] = "joe"
267
+ @client[Gsasl::GSASL_PASSCODE] = "sekshfkjhcret"
268
+
269
+ @client.authenticate(@server).should be_false
270
+ end
271
+
272
+ after(:each) do
273
+ @client.close
274
+ @server.close
275
+ end
276
+ end
277
+
278
+ describe "DIGEST-MD5 with digest md5 based password" do
279
+ before(:each) do
280
+ @client = @session.create_client("DIGEST-MD5")
281
+ @server = @session.create_server("DIGEST-MD5")
282
+ @server[Gsasl::GSASL_REALM] = "test"
283
+ end
284
+
285
+ it "should be able to authenticate correctly" do
286
+ @server.callback do |property|
287
+ if property == Gsasl::GSASL_DIGEST_MD5_HASHED_PASSWORD
288
+ if @server[Gsasl::GSASL_AUTHID] == "joe"
289
+ passwd_hash = Digest::MD5.hexdigest("joe:test:secret")
290
+ @server[Gsasl::GSASL_DIGEST_MD5_HASHED_PASSWORD] = passwd_hash
291
+ end
292
+ Gsasl::GSASL_OK
293
+ end
294
+ end
295
+
296
+ @client[Gsasl::GSASL_SERVICE] = "imap"
297
+ @client[Gsasl::GSASL_HOSTNAME] = "localhost"
298
+ @client[Gsasl::GSASL_AUTHID] = "joe"
299
+ @client[Gsasl::GSASL_REALM] = "test"
300
+ @client[Gsasl::GSASL_PASSWORD] = "secret"
301
+
302
+ @client.authenticate(@server).should be_true
303
+ end
304
+
305
+ after(:each) do
306
+ @client.close
307
+ @server.close
308
+ end
309
+ end
51
310
 
52
311
  after(:each) do
53
312
  @session.close
data/spec/context_spec.rb CHANGED
@@ -35,6 +35,22 @@ describe Gsasl::Context do
35
35
  @session.client_mechanisms.should include("PLAIN")
36
36
  end
37
37
 
38
+ context "realm" do
39
+ it "should initialize a server with a default realm" do
40
+ @server = @session.create_server("PLAIN")
41
+ @server.realm.should == "gsasl"
42
+ end
43
+
44
+ it "should initialize the server with a diffeent realm" do
45
+ @server = @session.create_server("PLAIN", "test")
46
+ @server.realm.should == "test"
47
+ end
48
+
49
+ after(:each) do
50
+ @server.close
51
+ end
52
+ end
53
+
38
54
  after(:each) do
39
55
  @session.close
40
56
  end
data/spec/peer_spec.rb CHANGED
@@ -37,6 +37,47 @@ describe Gsasl::Context do
37
37
  @peer.call(10)
38
38
  test_property.should == 10
39
39
  end
40
+
41
+ it "should to set the credentails" do
42
+ @peer.credentials!("joe", "secret")
43
+ @peer[Gsasl::GSASL_AUTHID].should == "joe"
44
+ @peer[Gsasl::GSASL_PASSWORD].should == "secret"
45
+ end
46
+
47
+ it "should to set the secureid" do
48
+ @peer.secureid!("joe", "123123123123")
49
+ @peer[Gsasl::GSASL_AUTHID].should == "joe"
50
+ @peer[Gsasl::GSASL_PASSCODE].should == "123123123123"
51
+ end
52
+
53
+ it "should update and return the realm" do
54
+ @peer.realm.should == nil
55
+ @peer.realm = "Awesome SMTPD"
56
+ @peer.realm.should == "Awesome SMTPD"
57
+ end
58
+
59
+ it "should be possible to set the service details" do
60
+ @peer.service!("smtp", "localhost")
61
+ @peer[Gsasl::GSASL_SERVICE].should == "smtp"
62
+ @peer[Gsasl::GSASL_HOSTNAME].should == "localhost"
63
+ end
64
+
65
+ it "should set the anonymous token for a peer" do
66
+ @peer.anonymous! "some-token"
67
+ @peer[Gsasl::GSASL_ANONYMOUS_TOKEN].should == "some-token"
68
+ end
69
+
70
+ it "should set and get the authzid fir external authentications" do
71
+ @peer.authzid.should == nil
72
+ @peer.authzid = "someid"
73
+ @peer[Gsasl::GSASL_AUTHZID].should == "someid"
74
+ @peer.authzid.should == "someid"
75
+ end
76
+
77
+ it "should be possible to generate a pre generated md5 hash" do
78
+ @peer.digest_md5_hashed_password("joe", "secret").should == \
79
+ "fb2441a715a5484c6fa16147c4a6b7a8"
80
+ end
40
81
  end
41
82
 
42
83
  after(:each) do
@@ -0,0 +1,39 @@
1
+ require 'spec_helper'
2
+
3
+ describe Gsasl::RemoteAuthenticator do
4
+ it "should be possible to create an remote authenticator" do
5
+ Gsasl::RemoteAuthenticator.new
6
+ end
7
+
8
+ context "with authenticator" do
9
+ before(:each) do
10
+ @authenticator = Gsasl::RemoteAuthenticator.new
11
+ end
12
+
13
+ it "should assign and call the recieve method hock" do
14
+ a = 0
15
+ @authenticator.receive { a += 1 }
16
+ @authenticator.receive.should == 1
17
+ a.should == 1
18
+ end
19
+
20
+ it "should assign and call the send method hock" do
21
+ a = nil
22
+ @authenticator.send { |data| a = data }
23
+ @authenticator.send("asd").should == "asd"
24
+ a.should == "asd"
25
+ end
26
+
27
+ it "should raise an error if receive is called without being defined" do
28
+ lambda do
29
+ @authenticator.receive
30
+ end.should raise_error(Gsasl::GsaslError)
31
+ end
32
+
33
+ it "should raise an error if send is called without being defined" do
34
+ lambda do
35
+ @authenticator.send
36
+ end.should raise_error(Gsasl::GsaslError)
37
+ end
38
+ end
39
+ end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: gsasl
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.0
4
+ version: 0.2.0
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
@@ -9,7 +9,7 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2012-03-25 00:00:00.000000000Z
12
+ date: 2012-05-01 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: rspec
@@ -68,7 +68,10 @@ extra_rdoc_files: []
68
68
  files:
69
69
  - .gitignore
70
70
  - .rspec
71
+ - .rvmrc
72
+ - .travis.yml
71
73
  - Gemfile
74
+ - LICENCE
72
75
  - README.md
73
76
  - Rakefile
74
77
  - gsasl.gemspec
@@ -76,10 +79,13 @@ files:
76
79
  - lib/gsasl/context.rb
77
80
  - lib/gsasl/native.rb
78
81
  - lib/gsasl/peer.rb
82
+ - lib/gsasl/remote_authenticator.rb
79
83
  - lib/gsasl/version.rb
84
+ - spec/abstraction_spec.rb
80
85
  - spec/authentication_spec.rb
81
86
  - spec/context_spec.rb
82
87
  - spec/peer_spec.rb
88
+ - spec/remote_authenticator_spec.rb
83
89
  - spec/spec_helper.rb
84
90
  homepage: ''
85
91
  licenses: []
@@ -101,12 +107,15 @@ required_rubygems_version: !ruby/object:Gem::Requirement
101
107
  version: '0'
102
108
  requirements: []
103
109
  rubyforge_project: gsasl
104
- rubygems_version: 1.8.19
110
+ rubygems_version: 1.8.24
105
111
  signing_key:
106
112
  specification_version: 3
107
113
  summary: A lib ffi based wrapper for lib GNU SASL
108
114
  test_files:
115
+ - spec/abstraction_spec.rb
109
116
  - spec/authentication_spec.rb
110
117
  - spec/context_spec.rb
111
118
  - spec/peer_spec.rb
119
+ - spec/remote_authenticator_spec.rb
112
120
  - spec/spec_helper.rb
121
+ has_rdoc: