kraut 0.5.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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