caleb-restful-authentication 1.1.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (54) hide show
  1. data/CHANGELOG +68 -0
  2. data/README.textile +240 -0
  3. data/Rakefile +32 -0
  4. data/TODO +15 -0
  5. data/generators/authenticated/USAGE +1 -0
  6. data/generators/authenticated/authenticated_generator.rb +508 -0
  7. data/generators/authenticated/lib/insert_routes.rb +54 -0
  8. data/generators/authenticated/templates/_model_partial.html.erb +8 -0
  9. data/generators/authenticated/templates/activation.erb +3 -0
  10. data/generators/authenticated/templates/authenticated_system.rb +189 -0
  11. data/generators/authenticated/templates/authenticated_test_helper.rb +22 -0
  12. data/generators/authenticated/templates/controller.rb +43 -0
  13. data/generators/authenticated/templates/helper.rb +2 -0
  14. data/generators/authenticated/templates/login.html.erb +21 -0
  15. data/generators/authenticated/templates/mailer.rb +33 -0
  16. data/generators/authenticated/templates/migration.rb +29 -0
  17. data/generators/authenticated/templates/model.rb +101 -0
  18. data/generators/authenticated/templates/model_controller.rb +117 -0
  19. data/generators/authenticated/templates/model_helper.rb +93 -0
  20. data/generators/authenticated/templates/model_helper_spec.rb +158 -0
  21. data/generators/authenticated/templates/observer.rb +14 -0
  22. data/generators/authenticated/templates/signup.html.erb +21 -0
  23. data/generators/authenticated/templates/signup_notification.erb +8 -0
  24. data/generators/authenticated/templates/site_keys.rb +38 -0
  25. data/generators/authenticated/templates/spec/controllers/access_control_spec.rb +90 -0
  26. data/generators/authenticated/templates/spec/controllers/authenticated_system_spec.rb +102 -0
  27. data/generators/authenticated/templates/spec/controllers/sessions_controller_spec.rb +139 -0
  28. data/generators/authenticated/templates/spec/controllers/users_controller_spec.rb +200 -0
  29. data/generators/authenticated/templates/spec/fixtures/users.yml +66 -0
  30. data/generators/authenticated/templates/spec/helpers/users_helper_spec.rb +141 -0
  31. data/generators/authenticated/templates/spec/models/user_spec.rb +295 -0
  32. data/generators/authenticated/templates/stories/rest_auth_stories.rb +22 -0
  33. data/generators/authenticated/templates/stories/rest_auth_stories_helper.rb +81 -0
  34. data/generators/authenticated/templates/stories/steps/ra_navigation_steps.rb +49 -0
  35. data/generators/authenticated/templates/stories/steps/ra_resource_steps.rb +179 -0
  36. data/generators/authenticated/templates/stories/steps/ra_response_steps.rb +171 -0
  37. data/generators/authenticated/templates/stories/steps/user_steps.rb +153 -0
  38. data/generators/authenticated/templates/stories/users/accounts.story +194 -0
  39. data/generators/authenticated/templates/stories/users/sessions.story +134 -0
  40. data/generators/authenticated/templates/test/functional_test.rb +82 -0
  41. data/generators/authenticated/templates/test/mailer_test.rb +31 -0
  42. data/generators/authenticated/templates/test/model_functional_test.rb +95 -0
  43. data/generators/authenticated/templates/test/unit_test.rb +166 -0
  44. data/init.rb +1 -0
  45. data/lib/authentication.rb +40 -0
  46. data/lib/authentication/by_cookie_token.rb +82 -0
  47. data/lib/authentication/by_password.rb +64 -0
  48. data/lib/authorization.rb +14 -0
  49. data/lib/authorization/aasm_roles.rb +64 -0
  50. data/lib/authorization/stateful_roles.rb +63 -0
  51. data/lib/trustification.rb +14 -0
  52. data/lib/trustification/email_validation.rb +20 -0
  53. data/rails/init.rb +6 -0
  54. metadata +115 -0
@@ -0,0 +1,22 @@
1
+ #!/usr/bin/env ruby
2
+ ENV["RAILS_ENV"] = "test"
3
+ require File.expand_path(File.dirname(__FILE__) + "/../config/environment")
4
+ require 'spec/rails/story_adapter'
5
+ require 'spec/story'
6
+ require File.expand_path(File.dirname(__FILE__) + "/rest_auth_stories_helper.rb")
7
+
8
+ # Make visible for testing
9
+ ApplicationController.send(:public, :logged_in?, :current_user, :authorized?)
10
+
11
+ this_dir = File.dirname(__FILE__)
12
+ Dir[File.join(this_dir, "steps/*.rb")].each do |file|
13
+ puts file.to_s
14
+ require file
15
+ end
16
+
17
+ with_steps_for :ra_navigation, :ra_response, :ra_resource, :<%= file_name %> do
18
+ story_files = Dir[File.join(this_dir, "<%= table_name %>", '*.story')]
19
+ story_files.each do |file|
20
+ run file, :type => RailsStory
21
+ end
22
+ end
@@ -0,0 +1,81 @@
1
+ # If you have a global stories helper, move this line there:
2
+ include AuthenticatedTestHelper
3
+
4
+ # Most of the below came out of code from Ben Mabey
5
+ # http://www.benmabey.com/2008/02/04/rspec-plain-text-stories-webrat-chunky-bacon/
6
+
7
+ # These allow exceptions to come through as opposed to being caught and having non-helpful responses returned.
8
+ ActionController::Base.class_eval do
9
+ def perform_action
10
+ perform_action_without_rescue
11
+ end
12
+ end
13
+ Dispatcher.class_eval do
14
+ def self.failsafe_response(output, status, exception = nil)
15
+ raise exception
16
+ end
17
+ end
18
+
19
+ #
20
+ # Sugar for turning a story's attribute list into list, array, etc.
21
+ #
22
+ module ToFooFromStory
23
+ def ToFooFromStory.fix_key key
24
+ key.downcase.gsub(/\s+/, '_')
25
+ end
26
+ def ToFooFromStory.fix_value value
27
+ return '' if !value
28
+ value.strip!
29
+ case
30
+ when value =~ /^'(.*)'$/ then value = $1
31
+ when value =~ /^"(.*)"$/ then value = $1
32
+ when value == 'nil!' then value = nil
33
+ when value == 'non-nil!' then value = be_nil
34
+ when value =~ /^#\{(.*)\}$/ then value = eval($1)
35
+ end
36
+ value
37
+ end
38
+ # Converts a key: value list found in the steps into a hash.
39
+ # Example:
40
+ # ISBN: '0967539854' and comment: 'I love this book' and Quality rating: '4'
41
+ # # => {"quality_rating"=>"4", "isbn"=>"0967539854", "comment"=>"I love this book"}
42
+ def to_hash_from_story
43
+ hsh = self.split(/,? and |, /).inject({}) do |hash_so_far, key_value|
44
+ key, value = key_value.split(":")
45
+ if !value then warn "Couldn't understand story '#{self}': only understood up to the part '#{hash_so_far.to_yaml}'" end
46
+ hash_so_far.merge(ToFooFromStory::fix_key(key) => ToFooFromStory::fix_value(value))
47
+ end
48
+ end
49
+ # Coverts an attribute list found in the steps into an array
50
+ # Example:
51
+ # login, email, updated_at, and gravatar
52
+ # # => ['login', 'email', 'updated_at', 'gravatar']
53
+ def to_array_from_story
54
+ self.split(/,? and |, /).map do |value|
55
+ ToFooFromStory::fix_value(value)
56
+ end
57
+ end
58
+ end
59
+ class String
60
+ include ToFooFromStory
61
+ end
62
+
63
+ def instantize(string)
64
+ instance_variable_get("@#{string}")
65
+ end
66
+
67
+ #
68
+ # Spew response onto screen -- painful but scrolling >> debugger
69
+ #
70
+ def dump_response
71
+ # note that @request and @template won't to_yaml and that @session includes @cgi
72
+ response_methods = response.instance_variables - ['@request', '@template', '@cgi']
73
+ request_methods = response.request.instance_variables - ['@session_options_with_string_keys', '@cgi', '@session']
74
+ response_methods.map!{|attr| attr.gsub(/^@/,'')}.sort!
75
+ request_methods.map!{ |attr| attr.gsub(/^@/,'')}.sort!
76
+ puts '', '*' * 75,
77
+ response.instance_values.slice(*response_methods).to_yaml,
78
+ "*" * 75, '',
79
+ response.request.instance_values.slice(*request_methods).to_yaml,
80
+ "*" * 75, ''
81
+ end
@@ -0,0 +1,49 @@
1
+ #
2
+ # Where to go
3
+ #
4
+ steps_for(:ra_navigation) do
5
+ #
6
+ # GET
7
+ # Go to a given page.
8
+ When "$actor goes to $path" do |actor, path|
9
+ case path
10
+ when 'the home page' then get '/'
11
+ else get path
12
+ end
13
+ end
14
+
15
+ # POST -- Ex:
16
+ # When she creates a book with ISBN: '0967539854' and comment: 'I love this book' and rating: '4'
17
+ # When she creates a singular session with login: 'reggie' and password: 'i_haxxor_joo'
18
+ # Since I'm not smrt enough to do it right, explicitly specify singular resources
19
+ When %r{$actor creates an? $resource with $attributes} do |actor, resource, attributes|
20
+ attributes = attributes.to_hash_from_story
21
+ if resource =~ %r{singular ([\w/]+)}
22
+ resource = $1.downcase.singularize
23
+ post "/#{resource}", attributes
24
+ else
25
+ post "/#{resource.downcase.pluralize}", { resource.downcase.singularize => attributes }
26
+ end
27
+ end
28
+
29
+ # PUT
30
+ When %r{$actor asks to update '$resource' with $attributes} do |_, resource, attributes|
31
+ attributes = attributes.to_hash_from_story
32
+ put "#{resource}", attributes
33
+ dump_response
34
+ end
35
+
36
+ # DELETE -- Slap together the POST-form-as-fake-HTTP-DELETE submission
37
+ When %r{$actor asks to delete '$resource'} do |_, resource|
38
+ post "/#{resource.downcase.pluralize}", { :_method => :delete }
39
+ dump_response
40
+ end
41
+
42
+
43
+ # Redirect --
44
+ # Rather than coding in get/get_via_redirect's and past/p_v_r's,
45
+ # let's just demand that in the story itself.
46
+ When "$actor follows that redirect!" do |actor|
47
+ follow_redirect!
48
+ end
49
+ end
@@ -0,0 +1,179 @@
1
+ # The flexible code for resource testing came out of code from Ben Mabey
2
+ # http://www.benmabey.com/2008/02/04/rspec-plain-text-stories-webrat-chunky-bacon/
3
+ steps_for(:ra_resource) do
4
+ #
5
+ # Construct resources
6
+ #
7
+
8
+ #
9
+ # Build a resource as described, store it as an @instance variable. Ex:
10
+ # "Given a <%= file_name %> with login: 'mojojojo'"
11
+ # produces a <%= class_name %> instance stored in @<%= file_name %> with 'mojojojo' as its login
12
+ # attribute.
13
+ #
14
+ Given "a $resource instance with $attributes" do |resource, attributes|
15
+ klass, instance, attributes = parse_resource_args resource, attributes
16
+ instance = klass.new(attributes)
17
+ instance.save!
18
+ find_resource(resource, attributes).should_not be_nil
19
+ keep_instance! resource, instance
20
+ end
21
+
22
+ #
23
+ # Stuff attributes into a preexisting @resource
24
+ # "And the <%= file_name %> has thac0: 3"
25
+ # takes the earlier-defined @<%= file_name %> instance and sets its thac0 to '3'.
26
+ #
27
+ Given "the $resource has $attributes" do |resource, attributes|
28
+ klass, instance, attributes = parse_resource_args resource, attributes
29
+ attributes.each do |attr, val|
30
+ instance.send("#{attr}=", val)
31
+ end
32
+ instance.save!
33
+ find_resource(resource, attributes).should_not be_nil
34
+ keep_instance! resource, instance
35
+ end
36
+
37
+ #
38
+ # Destroy all for this resource
39
+ #
40
+ Given "no $resource with $attr: '$val' exists" do |resource, attr, val|
41
+ klass, instance = parse_resource_args resource
42
+ klass.destroy_all(attr.to_sym => val)
43
+ instance = find_resource resource, attr.to_sym => val
44
+ instance.should be_nil
45
+ keep_instance! resource, instance
46
+ end
47
+
48
+ #
49
+ # Then's for resources
50
+ #
51
+
52
+ # Resource like this DOES exist
53
+ Then %r{an? $resource with $attributes should exist} do |resource, attributes|
54
+ instance = find_resource resource, attributes
55
+ instance.should_not be_nil
56
+ keep_instance! resource, instance
57
+ end
58
+ # Resource like this DOES NOT exist
59
+ Then %r{no $resource with $attributes should exist} do |resource, attributes|
60
+ instance = find_resource resource, attributes
61
+ instance.should be_nil
62
+ end
63
+
64
+ # Resource has attributes with given values
65
+ Then "the $resource should have $attributes" do |resource, attributes|
66
+ klass, instance, attributes = parse_resource_args resource, attributes
67
+ attributes.each do |attr, val|
68
+ instance.send(attr).should == val
69
+ end
70
+ end
71
+ # Resource attributes should / should not be nil
72
+ Then "the $resource's $attr should be nil" do |resource, attr|
73
+ klass, instance = parse_resource_args resource
74
+ instance.send(attr).should be_nil
75
+ end
76
+ Then "the $resource's $attr should not be nil" do |resource, attr|
77
+ klass, instance = parse_resource_args resource
78
+ instance.send(attr).should_not be_nil
79
+ end
80
+
81
+ #
82
+ # Bank each of the @resource's listed attributes for later.
83
+ #
84
+ Given "we try hard to remember the $resource's $attributes" do |resource, attributes|
85
+ attributes = attributes.to_array_from_story
86
+ attributes.each do |attr|
87
+ memorize_resource_value resource, attr
88
+ end
89
+ end
90
+ #
91
+ # Bank each of the @resource's listed attributes for later.
92
+ #
93
+ Given "we don't remember anything about the past" do
94
+ memorize_forget_all!
95
+ end
96
+
97
+ #
98
+ # Compare @resource.attr to its earlier-memorized value.
99
+ # Specify ' using method_name' (abs, to_s, &c) to coerce before comparing.
100
+ # For important and mysterious reasons, timestamps want to_i or to_s.
101
+ #
102
+ Then %r{the $resource\'s $attribute should stay the same(?: under $func)?} do |resource, attr, func|
103
+ klass, instance = parse_resource_args resource
104
+ # Get the values
105
+ old_value = recall_resource_value(resource, attr)
106
+ new_value = instance.send(attr)
107
+ # Transform each value, maybe, using value.func
108
+ if func then new_value = new_value.send(func); old_value = old_value.send(func) end
109
+ # Compare
110
+ old_value.should eql(new_value)
111
+ end
112
+
113
+ #
114
+ # Look for each for the given attributes in the page's text
115
+ #
116
+ Then "page should have the $resource's $attributes" do |resource, attributes|
117
+ actual_resource = instantize(resource)
118
+ attributes.split(/, and |, /).each do |attribute|
119
+ response.should have_text(/#{actual_resource.send(attribute.strip.gsub(" ","_"))}/)
120
+ end
121
+ end
122
+
123
+ end
124
+
125
+ #
126
+ # Turn a resource name and a to_hash_from_story string like
127
+ # "attr: 'value', attr2: 'value2', ... , and attrN: 'valueN'"
128
+ # into
129
+ # * klass -- the class matching that Resource
130
+ # * instance -- the possibly-preexisting local instance value @resource
131
+ # * attributes -- a hash matching the given attribute-list string
132
+ #
133
+ def parse_resource_args resource, attributes=nil
134
+ instance = instantize resource
135
+ klass = resource.classify.constantize
136
+ attributes = attributes.to_hash_from_story if attributes
137
+ [klass, instance, attributes]
138
+ end
139
+
140
+ #
141
+ # Given a class name 'resource' and a hash of conditsion, find a model
142
+ #
143
+ def find_resource resource, conditions
144
+ klass, instance = parse_resource_args resource
145
+ conditions = conditions.to_hash_from_story unless (conditions.is_a? Hash)
146
+ klass.find(:first, :conditions => conditions)
147
+ end
148
+
149
+ #
150
+ # Simple, brittle, useful: store the given resource's attribute
151
+ # so we can compare it later.
152
+ #
153
+ def memorize_resource_value resource, attr
154
+ klass, instance = parse_resource_args resource
155
+ value = instance.send(attr)
156
+ @_memorized ||= {}
157
+ @_memorized[resource] ||= {}
158
+ @_memorized[resource][attr] = value
159
+ value
160
+ end
161
+ def recall_resource_value resource, attr
162
+ @_memorized[resource][attr]
163
+ end
164
+ def memorize_forget_all!
165
+ @_memorized = {}
166
+ end
167
+
168
+ #
169
+ # Keep the object around in a local instance variable @resource.
170
+ #
171
+ # So, for instance,
172
+ # klass, instance = parse_resource_args '<%= file_name %>'
173
+ # instance = klass.new({login => 'me', password => 'monkey', ...})
174
+ # keep_instance! resource, instance
175
+ # keeps the just-constructed <%= class_name %> model in the @<%= file_name %> instance variable.
176
+ #
177
+ def keep_instance! resource, object
178
+ instance_variable_set("@#{resource}", object)
179
+ end
@@ -0,0 +1,171 @@
1
+ #
2
+ # What you should see when you get there
3
+ #
4
+
5
+ steps_for(:ra_response) do
6
+ #
7
+ # Destinations. Ex:
8
+ # She should be at the new kids page
9
+ # Tarkin should be at the destroy alderaan page
10
+ # The visitor should be at the '/lolcats/download' form
11
+ # The visitor should be redirected to '/hi/mom'
12
+ #
13
+ # It doesn't know anything about actual routes -- it just
14
+ # feeds its output to render_template or redirect_to
15
+ #
16
+ Then "$actor should be at $path" do |_, path|
17
+ response.should render_template(grok_path(path))
18
+ end
19
+
20
+ Then "$actor should be redirected to $path" do |_, path|
21
+ response.should redirect_to(grok_path(path))
22
+ end
23
+
24
+ Then "the page should look AWESOME" do
25
+ response.should have_tag('head>title')
26
+ response.should have_tag('h1')
27
+ # response.should be_valid_xhtml
28
+ end
29
+
30
+ #
31
+ # Tags
32
+ #
33
+
34
+ Then "the page should contain '$text'" do |_, text|
35
+ response.should have_text(/#{text}/)
36
+ end
37
+
38
+ # please note: this enforces the use of a <label> field
39
+ Then "$actor should see a <$container> containing a $attributes" do |_, container, attributes|
40
+ attributes = attributes.to_hash_from_story
41
+ response.should have_tag(container) do
42
+ attributes.each do |tag, label|
43
+ case tag
44
+ when "textfield" then with_tag "input[type='text']"; with_tag("label", label)
45
+ when "password" then with_tag "input[type='password']"; with_tag("label", label)
46
+ when "submit" then with_tag "input[type='submit'][value='#{label}']"
47
+ else with_tag tag, label
48
+ end
49
+ end
50
+ end
51
+ end
52
+
53
+ #
54
+ # Session, cookie variables
55
+ #
56
+ Then "$actor $token cookie should include $attrlist" do |_, token, attrlist|
57
+ attrlist = attrlist.to_array_from_story
58
+ cookies.include?(token).should be_true
59
+ attrlist.each do |val|
60
+ cookies[token].include?(val).should be_true
61
+ end
62
+ end
63
+
64
+ Then "$actor $token cookie should exist but not include $attrlist" do |_, token, attrlist|
65
+ attrlist = attrlist.to_array_from_story
66
+ cookies.include?(token).should be_true
67
+ puts [cookies, attrlist, token].to_yaml
68
+ attrlist.each do |val|
69
+ cookies[token].include?(val).should_not be_true
70
+ end
71
+ end
72
+
73
+ Then "$actor should have $an $token cookie" do |_, _, token|
74
+ cookies[token].should_not be_blank
75
+ end
76
+ Then "$actor should not have $an $token cookie" do |_, _, token|
77
+ cookies[token].should be_blank
78
+ end
79
+
80
+ Given "$actor has $an cookie jar with $attributes" do |_, _, attributes|
81
+ attributes = attributes.to_hash_from_story
82
+ attributes.each do |attr, val|
83
+ cookies[attr] = val
84
+ end
85
+ end
86
+ Given "$actor session store has no $attrlist" do |_, attrlist|
87
+ attrlist = attrlist.to_array_from_story
88
+ attrlist.each do |attr|
89
+ # Note that the comparison passes through 'to_s'
90
+ session[attr.to_sym] = nil
91
+ end
92
+ end
93
+
94
+ Then "$actor session store should have $attributes" do |_, attributes|
95
+ attributes = attributes.to_hash_from_story
96
+ attributes.each do |attr, val|
97
+ # Note that the comparison passes through 'to_s'
98
+ session[attr.to_sym].to_s.should eql(val)
99
+ end
100
+ end
101
+
102
+ Then "$actor session store should not have $attrlist" do |_, attrlist|
103
+ attrlist = attrlist.to_array_from_story
104
+ attrlist.each do |attr|
105
+ session[attr.to_sym].blank?.should be_true
106
+ end
107
+ end
108
+
109
+ #
110
+ # Flash messages
111
+ #
112
+
113
+ Then "$actor should see $an $notice message '$message'" do |_, _, notice, message|
114
+ response.should have_flash(notice, %r{#{message}})
115
+ end
116
+
117
+ Then "$actor should not see $an $notice message '$message'" do |_, _, notice, message|
118
+ response.should_not have_flash(notice, %r{#{message}})
119
+ end
120
+
121
+ Then "$actor should see no messages" do |_|
122
+ ['error', 'warning', 'notice'].each do |notice|
123
+ response.should_not have_flash(notice)
124
+ end
125
+ end
126
+
127
+ RE_POLITENESS = /(?:please|sorry|thank(?:s| you))/i
128
+ Then %r{we should be polite about it} do
129
+ response.should have_tag("div.error,div.notice", RE_POLITENESS)
130
+ end
131
+ Then %r{we should not even be polite about it} do
132
+ response.should_not have_tag("div.error,div.notice", RE_POLITENESS)
133
+ end
134
+
135
+ #
136
+ # Resource's attributes
137
+ #
138
+ # "Then page should have the $resource's $attributes" is in resource_steps
139
+
140
+ # helpful debug step
141
+ Then "we dump the response" do
142
+ dump_response
143
+ end
144
+ end
145
+
146
+
147
+ def have_flash notice, *args
148
+ have_tag("div.#{notice}", *args)
149
+ end
150
+
151
+ RE_PRETTY_RESOURCE = /the (index|show|new|create|edit|update|destroy) (\w+) (page|form)/i
152
+ RE_THE_FOO_PAGE = /the '?([^']*)'? (page|form)/i
153
+ RE_QUOTED_PATH = /^'([^']*)'$/i
154
+ def grok_path path
155
+ path.gsub(/\s+again$/,'') # strip trailing ' again'
156
+ case
157
+ when path == 'the home page' then dest = '/'
158
+ when path =~ RE_PRETTY_RESOURCE then dest = template_for $1, $2
159
+ when path =~ RE_THE_FOO_PAGE then dest = $1
160
+ when path =~ RE_QUOTED_PATH then dest = $1
161
+ else dest = path
162
+ end
163
+ dest
164
+ end
165
+
166
+ # turns 'new', 'road bikes' into 'road_bikes/new'
167
+ # note that it's "action resource"
168
+ def template_for(action, resource)
169
+ "#{resource.gsub(" ","_")}/#{action}"
170
+ end
171
+