kraut 0.5.6

Sign up to get free protection for your applications and to get access to all the features.
Files changed (50) hide show
  1. data/.gitignore +11 -0
  2. data/.rspec +1 -0
  3. data/Gemfile +3 -0
  4. data/README.md +175 -0
  5. data/Rakefile +10 -0
  6. data/app/controllers/kraut/sessions_controller.rb +30 -0
  7. data/app/models/kraut/session.rb +67 -0
  8. data/app/views/kraut/sessions/new.html.haml +15 -0
  9. data/autotest/discover.rb +1 -0
  10. data/config/initializers/savon.rb +12 -0
  11. data/config/locales/kraut.yml +14 -0
  12. data/config/routes.rb +5 -0
  13. data/kraut.gemspec +43 -0
  14. data/lib/kraut.rb +3 -0
  15. data/lib/kraut/application.rb +31 -0
  16. data/lib/kraut/client.rb +63 -0
  17. data/lib/kraut/kraut.rb +21 -0
  18. data/lib/kraut/mapper.rb +20 -0
  19. data/lib/kraut/principal.rb +85 -0
  20. data/lib/kraut/rails/authentication.rb +80 -0
  21. data/lib/kraut/rails/engine.rb +29 -0
  22. data/lib/kraut/rails/spec/login_helper.rb +28 -0
  23. data/lib/kraut/rails/spec/protected_action.rb +68 -0
  24. data/lib/kraut/rails/spec/user_helper.rb +27 -0
  25. data/lib/kraut/rails/spec_helper.rb +15 -0
  26. data/lib/kraut/version.rb +6 -0
  27. data/spec/controllers/application_controller_spec.rb +219 -0
  28. data/spec/controllers/sessions_controller_spec.rb +106 -0
  29. data/spec/fixtures/authenticate_application/invalid_app.xml +11 -0
  30. data/spec/fixtures/authenticate_application/invalid_password.xml +11 -0
  31. data/spec/fixtures/authenticate_application/success.xml +10 -0
  32. data/spec/fixtures/authenticate_principal/application_access_denied.xml +11 -0
  33. data/spec/fixtures/authenticate_principal/invalid_password.xml +11 -0
  34. data/spec/fixtures/authenticate_principal/invalid_user.xml +11 -0
  35. data/spec/fixtures/authenticate_principal/success.xml +7 -0
  36. data/spec/fixtures/find_principal_by_token/invalid_token.xml +11 -0
  37. data/spec/fixtures/find_principal_by_token/success.xml +39 -0
  38. data/spec/fixtures/find_principal_with_attributes_by_name/invalid_user.xml +11 -0
  39. data/spec/fixtures/find_principal_with_attributes_by_name/success.xml +69 -0
  40. data/spec/fixtures/is_group_member/not_in_group.xml +8 -0
  41. data/spec/fixtures/is_group_member/success.xml +8 -0
  42. data/spec/kraut/application_spec.rb +99 -0
  43. data/spec/kraut/client_spec.rb +101 -0
  44. data/spec/kraut/mapper_spec.rb +48 -0
  45. data/spec/kraut/principal_spec.rb +142 -0
  46. data/spec/models/session_spec.rb +148 -0
  47. data/spec/rails/engine_spec.rb +24 -0
  48. data/spec/spec_helper.rb +33 -0
  49. data/spec/views/sessions/new.html.haml_spec.rb +11 -0
  50. metadata +237 -0
@@ -0,0 +1,21 @@
1
+ module Kraut
2
+
3
+ class Error < RuntimeError; end
4
+ class InvalidAuthorization < Error; end
5
+ class InvalidAuthentication < Error; end
6
+ class ApplicationAccessDenied < Error; end
7
+ class InvalidPrincipalToken < Error; end
8
+ class UnknownError < Error; end
9
+
10
+ class << self
11
+
12
+ attr_accessor :endpoint
13
+
14
+ def namespace
15
+ @namespace ||= "http://authentication.integration.crowd.atlassian.com"
16
+ end
17
+
18
+ attr_writer :namespace
19
+
20
+ end
21
+ end
@@ -0,0 +1,20 @@
1
+ module Kraut
2
+
3
+ # = Kraut::Mapper
4
+ #
5
+ # Contains methods for mapping attributes.
6
+ module Mapper
7
+
8
+ # Accepts a Hash of +attributes+ and assigns them via writer methods.
9
+ # Calls an <tt>after_initialize</tt> method if available.
10
+ def initialize(attributes = nil)
11
+ mass_assign! attributes
12
+ end
13
+
14
+ # Expects a Hash of +attributes+ and assigns them via attribute writers.
15
+ def mass_assign!(attributes)
16
+ attributes.each { |key, value| send "#{key}=", value } if attributes
17
+ end
18
+
19
+ end
20
+ end
@@ -0,0 +1,85 @@
1
+ # -*- encoding : utf-8 -*-
2
+ require "kraut/client"
3
+ require "kraut/mapper"
4
+ require "kraut/application"
5
+
6
+ module Kraut
7
+
8
+ # = Kraut::Principal
9
+ #
10
+ # Represents a principal registered with Crowd.
11
+ class Principal
12
+ include Mapper
13
+
14
+ # Expects a +name+ and +password+ and returns a new authenticated <tt>Kraut::Principal</tt>.
15
+ def self.authenticate(name, password)
16
+ response = Client.auth_request :authenticate_principal, :in1 => {
17
+ "aut:application" => Application.name,
18
+ "aut:credential" => { "aut:credential" => password }, "aut:name" => name
19
+ }
20
+
21
+ new :name => name, :password => password, :token => response[:out].to_s
22
+ end
23
+
24
+ def self.find_by_token(token)
25
+ response = Client.auth_request :find_principal_by_token, :in1 => token.to_s
26
+
27
+ # assumption: this works without failure since the auth_request raises an error if the request was not successful!
28
+ new :name => response[:out][:name].to_s, :token => token.to_s
29
+ end
30
+
31
+ attr_accessor :name, :password, :token
32
+
33
+ # Returns the principal name to display.
34
+ def display_name
35
+ attributes[:display_name]
36
+ end
37
+
38
+ # Returns the principal's email address.
39
+ def email
40
+ attributes[:mail]
41
+ end
42
+
43
+ # Returns whether the principal's password is expired. Principals with an expired password
44
+ # are still able to authenticate and access your application if you do not use this method.
45
+ def requires_password_change?
46
+ attributes[:requires_password_change]
47
+ end
48
+
49
+ # Returns a Hash of attributes for the principal.
50
+ def attributes
51
+ @attributes ||= find_attributes
52
+ end
53
+
54
+ attr_writer :attributes
55
+
56
+ # Returns whether the principal is a member of a given +group+.
57
+ def member_of?(group)
58
+ return groups[group] unless groups[group].nil?
59
+ groups[group] = Client.auth_request(:is_group_member, :in1 => group, :in2 => name)[:out]
60
+ end
61
+
62
+ def groups
63
+ @groups ||= {}
64
+ end
65
+
66
+ attr_writer :groups
67
+
68
+ private
69
+
70
+ # Retrieves attributes for the current principal.
71
+ def find_attributes
72
+ response = Client.auth_request(:find_principal_with_attributes_by_name, :in1 => name)[:out]
73
+
74
+ response[:attributes][:soap_attribute].inject({}) do |memo, entry|
75
+ # next two lines: prevent Nori::StringWithAttributes to bubble up
76
+ # use plain strings instead (for serializability) [thomas, 2011-06-07]
77
+ value = entry[:values][:string]
78
+ value = value.to_s if value.is_a?(String)
79
+ memo[entry[:name].snakecase.to_sym] = value
80
+ memo
81
+ end
82
+ end
83
+
84
+ end
85
+ end
@@ -0,0 +1,80 @@
1
+ module Kraut
2
+
3
+ module Rails
4
+
5
+ module Authentication
6
+
7
+ def self.included(base)
8
+ base.helper_method :user, :logged_in?, :allowed_to?
9
+ base.rescue_from SecurityError do |e|
10
+ reset_session
11
+ redirect_to new_kraut_sessions_path, :alert => I18n.t("errors.kraut.session_expired")
12
+ end
13
+ end
14
+
15
+ # The timeout for a Crowd session in minutes.
16
+ CROWD_SESSION_TIMEOUT_MINUTES = 25
17
+
18
+ def switch_user(user)
19
+ session[:user] = user
20
+ end
21
+
22
+ def user
23
+ session[:user]
24
+ end
25
+
26
+ def logged_in?
27
+ !user.nil?
28
+ end
29
+
30
+ def allowed_to?(action)
31
+ authenticate_application
32
+ !!user && user.allowed_to?(action)
33
+ end
34
+
35
+ def check_for_crowd_token
36
+ if params[:crowd_token]
37
+ begin
38
+ authenticate_application
39
+ switch_user(Session.find_by_token(params[:crowd_token]))
40
+ rescue Kraut::InvalidPrincipalToken
41
+ reset_session
42
+ redirect_to new_kraut_sessions_path, :alert => I18n.t("errors.kraut.token_not_found")
43
+ end
44
+ end
45
+ end
46
+
47
+ def verify_login
48
+ unless logged_in?
49
+ store_current_location
50
+ redirect_to new_kraut_sessions_path
51
+ end
52
+ end
53
+
54
+ def verify_access
55
+ authenticate_application
56
+ unless logged_in? && user.allowed_to?("#{params[:controller]}_#{params[:action]}")
57
+ store_current_location
58
+ redirect_to new_kraut_sessions_path, :alert => I18n.t("errors.kraut.access_denied")
59
+ end
60
+ end
61
+
62
+ def authenticate_application
63
+ if Kraut::Application.authentication_required? CROWD_SESSION_TIMEOUT_MINUTES
64
+ Kraut::Application.authenticate Kraut::Rails::Engine.config.webservice[:user], Kraut::Rails::Engine.config.webservice[:password]
65
+ end
66
+ end
67
+
68
+ def store_current_location
69
+ session[:stored_location] = request.fullpath if request.get?
70
+ end
71
+
72
+ def stored_location!
73
+ session.delete(:stored_location)
74
+ end
75
+
76
+ end
77
+
78
+ end
79
+
80
+ end
@@ -0,0 +1,29 @@
1
+ require 'rails'
2
+
3
+ require 'kraut'
4
+ require 'kraut/rails/authentication'
5
+
6
+ module Kraut
7
+
8
+ module Rails
9
+
10
+ class Engine < ::Rails::Engine
11
+
12
+ config.after_initialize do
13
+ ActionController::Base.class_eval do
14
+ include Kraut::Rails::Authentication
15
+ end
16
+ end
17
+
18
+ def self.resolve_authorization_aliases(authorizations, aliases)
19
+ authorizations.inject({}) do |resolved, (action, groups)|
20
+ resolved[action] = groups.map { |group| aliases[group] }
21
+ resolved
22
+ end
23
+ end
24
+
25
+ end
26
+
27
+ end
28
+
29
+ end
@@ -0,0 +1,28 @@
1
+ module Kraut
2
+
3
+ module Rails
4
+
5
+ module Spec
6
+
7
+ module LoginHelper
8
+
9
+ def login!
10
+ Kraut::Application.stubs(:authentication_required?).returns(false)
11
+ session[:user] = create_user
12
+ end
13
+
14
+ def logout!
15
+ session[:user] = nil
16
+ end
17
+
18
+ def user
19
+ session[:user]
20
+ end
21
+
22
+ end
23
+
24
+ end
25
+
26
+ end
27
+
28
+ end
@@ -0,0 +1,68 @@
1
+ module Kraut
2
+
3
+ module Rails
4
+
5
+ module Spec
6
+
7
+ module ProtectedAction
8
+
9
+ # This shouldn't be called outside of describe_authorized_action,
10
+ # since it'll just be ignored - better situated somewhere else? [thomas, 2011-06-07]
11
+ # used to test unauthenticated/unauthorized access
12
+ # keep in mind that the before/after hooks specified in describe_protected_action
13
+ # don't apply to this request!
14
+ def unauthorized_request(&block)
15
+ @@unauthorized_request = block
16
+ end
17
+
18
+ # describes an action protected by kraut:
19
+ # tests if the specs in the block pass when authenticated (and authorized in case +action+ is specified)
20
+ # tests if the action recirects to the login page when not authorized (in case +unauthorized_access+ is called within the block and +action+ is specified)
21
+ # tests if the action recirects to the login page when not authenticated (in case +unauthorized_access+ is called within the block)
22
+ def describe_protected_action(message, action = nil, &block)
23
+
24
+ @@unauthorized_request = nil
25
+
26
+ describe message do
27
+
28
+ describe "authenticated#{" and authorized to do #{action}" if action}" do
29
+ before do
30
+ login! if user.nil?
31
+ user.expects(:allowed_to?).with(action.to_s).at_least_once.returns(true) if action
32
+ end
33
+ module_eval &block
34
+ end
35
+
36
+ unauthorized_request = @@unauthorized_request
37
+
38
+ describe "authenticated but unauthorized to do #{action}" do
39
+ before do
40
+ login! if user.nil?
41
+ user.expects(:allowed_to?).with(action.to_s).at_least_once.returns(false)
42
+ end
43
+ it "redirects to login page with an alert" do
44
+ instance_eval &unauthorized_request
45
+ response.should redirect_to("/sessions/new")
46
+ flash[:alert].should_not be_nil
47
+ end
48
+ end if action && unauthorized_request
49
+
50
+ describe "unauthenticated" do
51
+ before { logout! }
52
+ it "redirects to login page" do
53
+ instance_eval &unauthorized_request
54
+ response.should redirect_to("/sessions/new")
55
+ end
56
+ end if unauthorized_request
57
+
58
+ end
59
+
60
+ end
61
+
62
+ end
63
+
64
+ end
65
+
66
+ end
67
+
68
+ end
@@ -0,0 +1,27 @@
1
+ module Kraut
2
+
3
+ module Rails
4
+
5
+ module Spec
6
+
7
+ module UserHelper
8
+
9
+ def create_user
10
+ Kraut::Session.new(
11
+ :username => "user",
12
+ :password => "secret",
13
+ :principal => Kraut::Principal.new(
14
+ :name => "user",
15
+ :password => "secret",
16
+ :token => "token"
17
+ )
18
+ )
19
+ end
20
+
21
+ end
22
+
23
+ end
24
+
25
+ end
26
+
27
+ end
@@ -0,0 +1,15 @@
1
+ require "kraut/rails/spec/login_helper"
2
+ require "kraut/rails/spec/protected_action"
3
+ require "kraut/rails/spec/user_helper"
4
+
5
+ Kraut::Application.stubs(:authenticate).returns(["name", "password", "token"])
6
+
7
+ RSpec.configure do |config|
8
+ config.include Kraut::Rails::Spec::LoginHelper, :example_group => {
9
+ :file_path => /spec\/(controllers|views|helpers)/
10
+ }
11
+ config.extend Kraut::Rails::Spec::ProtectedAction, :example_group => {
12
+ :file_path => /spec\/controllers/
13
+ }
14
+ config.include Kraut::Rails::Spec::UserHelper
15
+ end
@@ -0,0 +1,6 @@
1
+ # -*- encoding : utf-8 -*-
2
+ module Kraut
3
+
4
+ VERSION = "0.5.6"
5
+
6
+ end
@@ -0,0 +1,219 @@
1
+ require "spec_helper"
2
+
3
+ describe ApplicationController do
4
+
5
+ context "when a SecurityError was raised" do
6
+ controller do
7
+ def index
8
+ raise SecurityError
9
+ end
10
+ end
11
+
12
+ it "swallows error, sets an alert and redirects to the login page" do
13
+ lambda { get :index }.should_not raise_error(Kraut::InvalidPrincipalToken)
14
+ flash[:alert].should == I18n.t("errors.kraut.session_expired")
15
+ response.should redirect_to("/sessions/new")
16
+ end
17
+ end
18
+
19
+ describe "#switch_user" do
20
+ it "stores the user in the session" do
21
+ session[:user].should be_nil
22
+ controller.switch_user(user = Kraut::Session.new)
23
+ session[:user].should == user
24
+ end
25
+ end
26
+
27
+ describe "#user" do
28
+ it "retrieves the user from the session" do
29
+ controller.user.should be_nil
30
+ session[:user] = user = Kraut::Session.new
31
+ controller.user.should == user
32
+ end
33
+ end
34
+
35
+ describe "#logged_in?" do
36
+ it "returns false when not logged in" do
37
+ controller.logged_in?.should == false
38
+ end
39
+ it "returns true when logged in" do
40
+ controller.switch_user(Kraut::Session.new)
41
+ controller.logged_in?.should == true
42
+ end
43
+ end
44
+
45
+ context "#allowed_to?" do
46
+ before { controller.expects(:authenticate_application) }
47
+
48
+ it "should return false when not logged in" do
49
+ controller.allowed_to?(:act).should == false
50
+ end
51
+
52
+ it "should return false when logged in user is not allowed to perform action" do
53
+ controller.switch_user(user = Kraut::Session.new)
54
+ user.expects(:allowed_to?).with(:act).returns(false)
55
+ controller.allowed_to?(:act).should == false
56
+ end
57
+
58
+ it "should return true when logged in user is allowed to perform action" do
59
+ controller.switch_user(user = Kraut::Session.new)
60
+ user.expects(:allowed_to?).with(:act).returns(true)
61
+ controller.allowed_to?(:act).should == true
62
+ end
63
+ end
64
+
65
+ context "before_filters" do
66
+
67
+ describe ":check_for_crowd_token" do
68
+ controller do
69
+ before_filter :check_for_crowd_token
70
+ def index
71
+ render :text => 'no fail'
72
+ end
73
+ end
74
+
75
+ it "raises no error, resets the session, sets an alert and redirects to the login page if no principal is found" do
76
+ controller.expects(:authenticate_application)
77
+ Kraut::Session.expects(:find_by_token).with('abcd').raises(Kraut::InvalidPrincipalToken)
78
+ lambda { get :index, :crowd_token => 'abcd' }.should_not raise_error(Kraut::InvalidPrincipalToken)
79
+ session.should be_empty
80
+ flash[:alert].should == I18n.t("errors.kraut.token_not_found")
81
+ response.should redirect_to("/sessions/new")
82
+ end
83
+
84
+ it "sets the user session to the principal" do
85
+ controller.expects(:authenticate_application)
86
+ Kraut::Session.expects(:find_by_token).with('abcd').returns(user = Kraut::Session.new)
87
+ user.stubs(:allowed_to?).returns(true)
88
+ get :index, :crowd_token => 'abcd'
89
+ response.should be_success
90
+ response.body.should == 'no fail'
91
+ end
92
+
93
+ it "only takes action if the crowd_token param is set" do
94
+ Kraut::Session.expects(:find_by_token).never
95
+ get :index
96
+ response.should be_success
97
+ response.body.should == 'no fail'
98
+ end
99
+ end
100
+
101
+ describe ":verify_login" do
102
+ controller do
103
+ before_filter :verify_login
104
+ def index
105
+ render :text => 'no fail'
106
+ end
107
+ end
108
+
109
+ it "does nothing when logged in" do
110
+ controller.switch_user(Kraut::Session.new)
111
+ get :index
112
+ controller.stored_location!.should be_nil
113
+ response.should be_success
114
+ response.body.should == 'no fail'
115
+ end
116
+
117
+ it "stores the current location and redirects the the login page when not logged in" do
118
+ get :index
119
+ controller.stored_location!.should_not be_nil
120
+ response.should redirect_to("/sessions/new")
121
+ end
122
+ end
123
+
124
+ describe ":verify_access" do
125
+ before { controller.expects(:authenticate_application) }
126
+
127
+ controller do
128
+ before_filter :set_params, :verify_access
129
+ def index
130
+ render :text => 'no fail'
131
+ end
132
+ def set_params
133
+ params[:controller] = 'cont'
134
+ params[:action] = 'act'
135
+ end
136
+ end
137
+
138
+ it "does nothing when logged in and authorized" do
139
+ controller.switch_user(user = Kraut::Session.new)
140
+ user.expects(:allowed_to?).with('cont_act').returns(true)
141
+ get :index
142
+ controller.stored_location!.should be_nil
143
+ response.should be_success
144
+ response.body.should == 'no fail'
145
+ end
146
+
147
+ it "stores the current location, sets and alert and redirects the the login page when logged in but unauthorized" do
148
+ controller.switch_user(user = Kraut::Session.new)
149
+ user.expects(:allowed_to?).with('cont_act').returns(false)
150
+ controller.expects(:store_current_location)
151
+ get :index
152
+ flash[:alert].should == I18n.t("errors.kraut.access_denied")
153
+ response.should redirect_to("/sessions/new")
154
+ end
155
+
156
+ it "stores the current location, sets and alert and redirects the the login page when not logged in" do
157
+ controller.expects(:store_current_location)
158
+ get :index
159
+ flash[:alert].should == I18n.t("errors.kraut.access_denied")
160
+ response.should redirect_to("/sessions/new")
161
+ end
162
+ end
163
+
164
+ end
165
+
166
+ context "internal methods" do
167
+
168
+ describe "#authenticate_application" do
169
+ it "authenticates the application when required" do
170
+ Kraut::Rails::Engine.config.webservice = { :user => "u", :password => "p" }
171
+ Kraut::Application.expects(:authentication_required?).with(25).returns(true)
172
+ Kraut::Application.expects(:authenticate).with("u", "p")
173
+ controller.authenticate_application
174
+ end
175
+
176
+ it "doesn't authenticate the application when not required" do
177
+ Kraut::Application.stubs(:authentication_required?).returns(false)
178
+ Kraut::Application.expects(:authenticate).never
179
+ controller.authenticate_application
180
+ end
181
+ end
182
+
183
+ describe "#store_current_location" do
184
+ controller do
185
+ def index
186
+ store_current_location
187
+ render :text => 'no fail'
188
+ end
189
+ end
190
+
191
+ it "stores request's full path on a GET" do
192
+ get :index
193
+ response.should be_success
194
+ response.body.should == 'no fail'
195
+ controller.stored_location!.should == "/stub_resources"
196
+ end
197
+
198
+ %w(delete head post put).each do |method|
199
+ it "doesn't store request's full path on a #{method.upcase}" do
200
+ send method, :index
201
+ response.should be_success
202
+ response.body.should == 'no fail'
203
+ controller.stored_location!.should be_nil
204
+ end
205
+ end
206
+ end
207
+
208
+ describe "#stored_location!" do
209
+ it "retrieves the location stored in the session, then delete it" do
210
+ controller.stored_location!.should be_nil
211
+ session[:stored_location] = "loc"
212
+ controller.stored_location!.should == "loc"
213
+ controller.stored_location!.should be_nil
214
+ end
215
+ end
216
+
217
+ end
218
+
219
+ end