angus-authentication 0.0.2

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,4 @@
1
+ require_relative 'angus/authentication/exceptions'
2
+ require_relative 'angus/authentication/provider'
3
+
4
+ require_relative 'rack/middleware/angus_authentication'
@@ -0,0 +1,24 @@
1
+ module Angus
2
+ module Authentication
3
+ class DefaultAuthenticator
4
+
5
+ def initialize(private_key)
6
+ @private_key = private_key
7
+ end
8
+
9
+ def call(session_id, auth_data, auth_token)
10
+ if Digest::SHA1.hexdigest("#@private_key\n#{auth_data}") == auth_token
11
+ private_session_key_seed = BCrypt::Engine.generate_salt
12
+ private_session_key = Digest::SHA1.hexdigest(
13
+ "#@private_key\n#{private_session_key_seed}"
14
+ )
15
+
16
+ return private_session_key, private_session_key_seed
17
+ else
18
+ return nil, nil
19
+ end
20
+ end
21
+
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,17 @@
1
+ module Angus
2
+ module Authentication
3
+
4
+ class MissingAuthorizationData < StandardError
5
+
6
+ end
7
+
8
+ class InvalidAuthorizationData < StandardError
9
+
10
+ end
11
+
12
+ class AuthorizationTimeout < StandardError
13
+
14
+ end
15
+
16
+ end
17
+ end
@@ -0,0 +1,154 @@
1
+ require 'digest'
2
+ require 'bcrypt'
3
+
4
+ require_relative 'redis_store'
5
+ require_relative 'default_authenticator'
6
+
7
+ module Angus
8
+ module Authentication
9
+
10
+ class Provider
11
+
12
+ DEFAULT_ID_TTL = 60 * 60
13
+ DEFAULT_SESSION_TTL = 60 * 60
14
+ DEFAULT_PRIVATE_KEY = 'CHANGE ME!!'
15
+
16
+ AUTHENTICATION_HEADER = 'HTTP_AUTHORIZATION'
17
+ BAAS_AUTHENTICATION_HEADER = 'HTTP_X_BAAS_AUTH'
18
+ BAAS_SESSION_HEADER = 'X-Baas-Session-Seed'
19
+ DATE_HEADER = 'HTTP_DATE'
20
+ REQUEST_HEADER = 'REQUEST_METHOD'
21
+ PATH_HEADER = 'PATH_INFO'
22
+
23
+ def initialize(settings, env)
24
+ @session_id_ttl = settings[:session_id_ttl] || DEFAULT_ID_TTL
25
+ @session_ttl = settings[:session_ttl] || DEFAULT_SESSION_TTL
26
+ @private_key = settings[:private_key] || DEFAULT_PRIVATE_KEY
27
+ @authenticator = settings[:authenticator] || DefaultAuthenticator.new(@private_key)
28
+ @store = RedisStore.new(settings[:store] || {})
29
+ @excluded_regexps = settings[:excluded_regexps] || []
30
+ @env = env
31
+ end
32
+
33
+ def authenticate!
34
+ return unless should_authenticate?
35
+
36
+ if has_session?
37
+ authenticate_session
38
+ else
39
+ start_session
40
+ end
41
+ end
42
+
43
+ def update_response_header(response)
44
+ return unless should_authenticate?
45
+
46
+ headers = response[1]
47
+
48
+ session_data = @store.get_session_data(session_id)
49
+
50
+ headers[BAAS_SESSION_HEADER] = session_data['key_seed']
51
+ end
52
+
53
+ private
54
+
55
+ def should_authenticate?
56
+ return true if @excluded_regexps.empty?
57
+
58
+ @excluded_regexps.none? { |regexp| request_path.match(regexp) }
59
+ end
60
+
61
+ def request_path
62
+ @env[PATH_HEADER]
63
+ end
64
+
65
+ def has_session?
66
+ @store.has_key?(session_id)
67
+ end
68
+
69
+ def start_session
70
+ raise MissingAuthorizationData unless authorization_data_present?
71
+
72
+ private_session_key, private_session_key_seed = @authenticator.call(session_id, auth_data,
73
+ auth_token)
74
+
75
+ raise InvalidAuthorizationData unless private_session_key
76
+
77
+ session_data = {
78
+ 'private_key' => private_session_key,
79
+ 'key_seed' => private_session_key_seed,
80
+ 'created_at' => Time.now.iso8601
81
+ }
82
+
83
+ @store.save_session_data(session_id, session_data, @session_id_ttl + @session_ttl)
84
+ end
85
+
86
+ def authenticate_session
87
+ raise MissingAuthorizationData unless session_data_present?
88
+
89
+ if session_expired? && authorization_data_present?
90
+ start_session && return
91
+ elsif session_expired?
92
+ raise AuthorizationTimeout
93
+ end
94
+ session_data = @store.get_session_data(session_id)
95
+
96
+ if authenticate_session_token(session_data['private_key'])
97
+ raise InvalidAuthorizationData
98
+ end
99
+ end
100
+
101
+ def authenticate_session_token(private_key)
102
+ Digest::SHA1.hexdigest("#{private_key}\n#{auth_data}") != session_auth_token
103
+ end
104
+
105
+ def authorization_data_present?
106
+ @env[DATE_HEADER] != nil && @env[AUTHENTICATION_HEADER] != nil &&
107
+ extract_session_id(@env[AUTHENTICATION_HEADER]) != nil
108
+ end
109
+
110
+ def session_data_present?
111
+ @env[DATE_HEADER] != nil && @env[BAAS_AUTHENTICATION_HEADER] != nil &&
112
+ extract_session_id(@env[BAAS_AUTHENTICATION_HEADER]) != nil
113
+ end
114
+
115
+ def session_expired?
116
+ session_data = @store.get_session_data(session_id)
117
+
118
+ created_at = Time.iso8601(session_data['created_at'])
119
+
120
+ (created_at + @session_ttl) < Time.now
121
+ rescue Exception
122
+ true
123
+ end
124
+
125
+ def auth_data
126
+ "#{@env[DATE_HEADER]}\n" +
127
+ "#{@env[REQUEST_HEADER]}\n" +
128
+ "#{@env[PATH_HEADER]}"
129
+ end
130
+
131
+ def auth_token
132
+ (@env[AUTHENTICATION_HEADER] || '').match(/.*:([a-zA-Z0-9]*)$/)
133
+ $1
134
+ end
135
+
136
+ def session_id
137
+ extract_session_id(@env[BAAS_AUTHENTICATION_HEADER]) ||
138
+ extract_session_id(@env[AUTHENTICATION_HEADER])
139
+ end
140
+
141
+ def session_auth_token
142
+ (@env[BAAS_AUTHENTICATION_HEADER] || '').match(/.*:([a-zA-Z0-9]*)$/)
143
+ $1
144
+ end
145
+
146
+ def extract_session_id(data)
147
+ (data || '').match(/^([a-zA-Z0-9]*):.*/)
148
+ $1
149
+ end
150
+
151
+ end
152
+
153
+ end
154
+ end
@@ -0,0 +1,40 @@
1
+ require 'json'
2
+ require 'redis'
3
+
4
+ module Angus
5
+ module Authentication
6
+
7
+ class RedisStore
8
+
9
+ DEFAULT_NAMESPACE = ''
10
+
11
+ def initialize(settings)
12
+ @namespace = settings.delete(:namespace) || DEFAULT_NAMESPACE
13
+ @settings = settings
14
+ end
15
+
16
+ def has_key?(key)
17
+ redis.exists(add_namespace(key))
18
+ end
19
+
20
+ def save_session_data(key, data, ttl)
21
+ redis.set(add_namespace(key), JSON(data))
22
+ redis.expire(add_namespace(key), ttl)
23
+ end
24
+
25
+ def get_session_data(key)
26
+ JSON(redis.get(add_namespace(key)) || {})
27
+ end
28
+
29
+ def redis
30
+ @redis ||= Redis.new(@settings)
31
+ end
32
+
33
+ def add_namespace(key)
34
+ "#@namespace.angus-authentication-provider.#{key}"
35
+ end
36
+
37
+ end
38
+
39
+ end
40
+ end
@@ -0,0 +1,5 @@
1
+ module Angus
2
+ module Authentication
3
+ VERSION = '0.0.2'
4
+ end
5
+ end
@@ -0,0 +1,41 @@
1
+ require 'angus/authentication/provider'
2
+
3
+ module Rack
4
+ module Middleware
5
+
6
+ class AngusAuthentication
7
+
8
+ UNAUTHORIZED_MESSAGE = 'Unauthorized'
9
+ TIMEOUT_MESSAGE = 'Authentication Timeout'
10
+
11
+ def initialize(app, settings = {})
12
+ @app = app
13
+
14
+ @authentication_settings = settings
15
+ end
16
+
17
+ def call(env)
18
+ dup.call!(env)
19
+ end
20
+
21
+ def call!(env)
22
+ authentication_provider = Angus::Authentication::Provider.new(@authentication_settings, env)
23
+
24
+ authentication_provider.authenticate!
25
+
26
+ response = @app.call(env)
27
+
28
+ authentication_provider.update_response_header(response)
29
+
30
+ response
31
+ rescue Angus::Authentication::MissingAuthorizationData,
32
+ Angus::Authentication::InvalidAuthorizationData
33
+ [401, {}, [UNAUTHORIZED_MESSAGE]]
34
+ rescue Angus::Authentication::AuthorizationTimeout
35
+ [419, {}, [TIMEOUT_MESSAGE]]
36
+ end
37
+
38
+ end
39
+
40
+ end
41
+ end
metadata ADDED
@@ -0,0 +1,245 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: angus-authentication
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.2
5
+ prerelease:
6
+ platform: ruby
7
+ authors:
8
+ - Adrian Gomez
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2013-11-26 00:00:00.000000000 Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: rack
16
+ requirement: !ruby/object:Gem::Requirement
17
+ none: false
18
+ requirements:
19
+ - - ~>
20
+ - !ruby/object:Gem::Version
21
+ version: '1.5'
22
+ type: :runtime
23
+ prerelease: false
24
+ version_requirements: !ruby/object:Gem::Requirement
25
+ none: false
26
+ requirements:
27
+ - - ~>
28
+ - !ruby/object:Gem::Version
29
+ version: '1.5'
30
+ - !ruby/object:Gem::Dependency
31
+ name: redis
32
+ requirement: !ruby/object:Gem::Requirement
33
+ none: false
34
+ requirements:
35
+ - - ! '>='
36
+ - !ruby/object:Gem::Version
37
+ version: '0'
38
+ type: :runtime
39
+ prerelease: false
40
+ version_requirements: !ruby/object:Gem::Requirement
41
+ none: false
42
+ requirements:
43
+ - - ! '>='
44
+ - !ruby/object:Gem::Version
45
+ version: '0'
46
+ - !ruby/object:Gem::Dependency
47
+ name: bcrypt-ruby
48
+ requirement: !ruby/object:Gem::Requirement
49
+ none: false
50
+ requirements:
51
+ - - ~>
52
+ - !ruby/object:Gem::Version
53
+ version: '3'
54
+ type: :runtime
55
+ prerelease: false
56
+ version_requirements: !ruby/object:Gem::Requirement
57
+ none: false
58
+ requirements:
59
+ - - ~>
60
+ - !ruby/object:Gem::Version
61
+ version: '3'
62
+ - !ruby/object:Gem::Dependency
63
+ name: rake
64
+ requirement: !ruby/object:Gem::Requirement
65
+ none: false
66
+ requirements:
67
+ - - ~>
68
+ - !ruby/object:Gem::Version
69
+ version: '10.1'
70
+ type: :development
71
+ prerelease: false
72
+ version_requirements: !ruby/object:Gem::Requirement
73
+ none: false
74
+ requirements:
75
+ - - ~>
76
+ - !ruby/object:Gem::Version
77
+ version: '10.1'
78
+ - !ruby/object:Gem::Dependency
79
+ name: rspec
80
+ requirement: !ruby/object:Gem::Requirement
81
+ none: false
82
+ requirements:
83
+ - - ~>
84
+ - !ruby/object:Gem::Version
85
+ version: '2.14'
86
+ type: :development
87
+ prerelease: false
88
+ version_requirements: !ruby/object:Gem::Requirement
89
+ none: false
90
+ requirements:
91
+ - - ~>
92
+ - !ruby/object:Gem::Version
93
+ version: '2.14'
94
+ - !ruby/object:Gem::Dependency
95
+ name: rack-test
96
+ requirement: !ruby/object:Gem::Requirement
97
+ none: false
98
+ requirements:
99
+ - - ~>
100
+ - !ruby/object:Gem::Version
101
+ version: '0.6'
102
+ type: :development
103
+ prerelease: false
104
+ version_requirements: !ruby/object:Gem::Requirement
105
+ none: false
106
+ requirements:
107
+ - - ~>
108
+ - !ruby/object:Gem::Version
109
+ version: '0.6'
110
+ - !ruby/object:Gem::Dependency
111
+ name: mock_redis
112
+ requirement: !ruby/object:Gem::Requirement
113
+ none: false
114
+ requirements:
115
+ - - ! '>='
116
+ - !ruby/object:Gem::Version
117
+ version: '0'
118
+ type: :development
119
+ prerelease: false
120
+ version_requirements: !ruby/object:Gem::Requirement
121
+ none: false
122
+ requirements:
123
+ - - ! '>='
124
+ - !ruby/object:Gem::Version
125
+ version: '0'
126
+ - !ruby/object:Gem::Dependency
127
+ name: timecop
128
+ requirement: !ruby/object:Gem::Requirement
129
+ none: false
130
+ requirements:
131
+ - - ! '>='
132
+ - !ruby/object:Gem::Version
133
+ version: '0'
134
+ type: :development
135
+ prerelease: false
136
+ version_requirements: !ruby/object:Gem::Requirement
137
+ none: false
138
+ requirements:
139
+ - - ! '>='
140
+ - !ruby/object:Gem::Version
141
+ version: '0'
142
+ - !ruby/object:Gem::Dependency
143
+ name: simplecov
144
+ requirement: !ruby/object:Gem::Requirement
145
+ none: false
146
+ requirements:
147
+ - - ~>
148
+ - !ruby/object:Gem::Version
149
+ version: '0.7'
150
+ type: :development
151
+ prerelease: false
152
+ version_requirements: !ruby/object:Gem::Requirement
153
+ none: false
154
+ requirements:
155
+ - - ~>
156
+ - !ruby/object:Gem::Version
157
+ version: '0.7'
158
+ - !ruby/object:Gem::Dependency
159
+ name: simplecov-rcov
160
+ requirement: !ruby/object:Gem::Requirement
161
+ none: false
162
+ requirements:
163
+ - - ! '>='
164
+ - !ruby/object:Gem::Version
165
+ version: '0'
166
+ type: :development
167
+ prerelease: false
168
+ version_requirements: !ruby/object:Gem::Requirement
169
+ none: false
170
+ requirements:
171
+ - - ! '>='
172
+ - !ruby/object:Gem::Version
173
+ version: '0'
174
+ - !ruby/object:Gem::Dependency
175
+ name: simplecov-rcov-text
176
+ requirement: !ruby/object:Gem::Requirement
177
+ none: false
178
+ requirements:
179
+ - - ! '>='
180
+ - !ruby/object:Gem::Version
181
+ version: '0'
182
+ type: :development
183
+ prerelease: false
184
+ version_requirements: !ruby/object:Gem::Requirement
185
+ none: false
186
+ requirements:
187
+ - - ! '>='
188
+ - !ruby/object:Gem::Version
189
+ version: '0'
190
+ - !ruby/object:Gem::Dependency
191
+ name: ci_reporter
192
+ requirement: !ruby/object:Gem::Requirement
193
+ none: false
194
+ requirements:
195
+ - - ! '>='
196
+ - !ruby/object:Gem::Version
197
+ version: '0'
198
+ type: :development
199
+ prerelease: false
200
+ version_requirements: !ruby/object:Gem::Requirement
201
+ none: false
202
+ requirements:
203
+ - - ! '>='
204
+ - !ruby/object:Gem::Version
205
+ version: '0'
206
+ description:
207
+ email:
208
+ - angus@moove-it.com
209
+ executables: []
210
+ extensions: []
211
+ extra_rdoc_files: []
212
+ files:
213
+ - lib/angus/authentication/default_authenticator.rb
214
+ - lib/angus/authentication/version.rb
215
+ - lib/angus/authentication/exceptions.rb
216
+ - lib/angus/authentication/redis_store.rb
217
+ - lib/angus/authentication/provider.rb
218
+ - lib/angus-authentication.rb
219
+ - lib/rack/middleware/angus_authentication.rb
220
+ homepage: http://mooveit.github.io/angus-authentication
221
+ licenses:
222
+ - MIT
223
+ post_install_message:
224
+ rdoc_options: []
225
+ require_paths:
226
+ - lib
227
+ required_ruby_version: !ruby/object:Gem::Requirement
228
+ none: false
229
+ requirements:
230
+ - - ! '>='
231
+ - !ruby/object:Gem::Version
232
+ version: '0'
233
+ required_rubygems_version: !ruby/object:Gem::Requirement
234
+ none: false
235
+ requirements:
236
+ - - ! '>='
237
+ - !ruby/object:Gem::Version
238
+ version: '0'
239
+ requirements: []
240
+ rubyforge_project:
241
+ rubygems_version: 1.8.25
242
+ signing_key:
243
+ specification_version: 3
244
+ summary: Offers authentication for rack applications.
245
+ test_files: []