auth-slice 0.1.0

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.
data/LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2008 PragmaQuest Inc
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README ADDED
@@ -0,0 +1,65 @@
1
+ AuthSlice
2
+ =========
3
+
4
+ An user authentication slice for the Merb framework. See http://github.com/ctran/merb-skel for an example application that uses this slice
5
+
6
+ == Installation:
7
+
8
+ > gem install merb
9
+ > gem install merb-slices
10
+
11
+ To use activerecord adapter
12
+ > gem install activerecord
13
+ > gem merb_activerecord
14
+
15
+ To use datamapper adapter
16
+ > gem install datamapper
17
+ > gem install merb_datamapper
18
+
19
+ === config/init.rb
20
+
21
+ # add the slice as a regular dependency
22
+ dependency 'auth-slice'
23
+
24
+ === config/router.rb
25
+
26
+ # This will add the following routes /login, /logout and /signup
27
+ r.slice(:AuthSlice)
28
+
29
+ # This will add the following routes /auth-slice/login, /auth-slice/logout and /auth-slice/signup
30
+ r.add_slice(:AuthSlice)
31
+
32
+ # This will add the following routes /user/login, /user/logout and /user/signup
33
+ r.add_slice(:AuthSlice, 'user') # same as :path => 'user'
34
+
35
+ === Normally you should also run the following rake task:
36
+ > rake slices:auth_slice:install
37
+
38
+ == Customization/Overrides
39
+
40
+ By default, the user model class is in AuthSlice::User. To use shorter name, add this to your init.rb
41
+
42
+ Merb::Slices.config[:auth_slice] = { :user_model_class => "User" }
43
+
44
+ You can also put your application-level overrides in:
45
+
46
+ host-app/slices/auth-slice/app - controllers, models, views ...
47
+
48
+ Templates are located in this order:
49
+
50
+ 1. host-app/slices/auth-slice/app/views/*
51
+ 2. gems/auth-slice/app/views/*
52
+ 3. host-app/app/views/*
53
+
54
+ You can use the host application's layout by configuring the
55
+ auth-slice slice in a before_app_loads block:
56
+
57
+ Merb::Slices.config[:auth_slice] = { :layout => :application }
58
+
59
+ By default :auth_slice is used. If you need to override
60
+ stylesheets or javascripts, just specify your own files in your layout
61
+ instead/in addition to the ones supplied (if any) in
62
+ host-app/public/slices/auth-slice.
63
+
64
+ In any case don't edit those files directly as they may be clobbered any time
65
+ rake auth_slice:install is run.
@@ -0,0 +1,47 @@
1
+ require 'rubygems'
2
+ require 'rake/gempackagetask'
3
+ require 'spec/rake/spectask'
4
+ require 'merb-core/version'
5
+ require 'merb-core/test/tasks/spectasks'
6
+
7
+ PLUGIN = "auth-slice"
8
+ NAME = "auth-slice"
9
+ AUTHOR = "Cuong Tran"
10
+ EMAIL = "ctran@pragmaquest.com"
11
+ HOMEPAGE = "http://merb-slices.rubyforge.org/auth-slice/"
12
+ SUMMARY = "Merb Slice that provides user authentication"
13
+ SLICE_VERSION = "0.1.0"
14
+
15
+ spec = Gem::Specification.new do |s|
16
+ s.name = NAME
17
+ s.version = SLICE_VERSION
18
+ s.platform = Gem::Platform::RUBY
19
+ s.has_rdoc = true
20
+ s.extra_rdoc_files = ["README", "LICENSE"]
21
+ s.summary = SUMMARY
22
+ s.description = s.summary
23
+ s.author = AUTHOR
24
+ s.email = EMAIL
25
+ s.homepage = HOMEPAGE
26
+ s.add_dependency('merb-slices', '>= 0.9.4')
27
+ s.require_path = 'lib'
28
+ s.files = %w(LICENSE README Rakefile) + Dir.glob("{lib,spec,app,public}/**/*")
29
+ end
30
+
31
+ Rake::GemPackageTask.new(spec) do |pkg|
32
+ pkg.gem_spec = spec
33
+ end
34
+
35
+ desc "Install Foo as a gem"
36
+ task :install => [:package] do
37
+ sh %{sudo gem install pkg/#{NAME}-#{SLICE_VERSION} --no-update-sources --local}
38
+ end
39
+
40
+ namespace :jruby do
41
+
42
+ desc "Run :package and install the resulting .gem with jruby"
43
+ task :install => :package do
44
+ sh %{#{SUDO} jruby -S gem install pkg/#{NAME}-#{VERSION}.gem --no-rdoc --no-ri}
45
+ end
46
+
47
+ end
@@ -0,0 +1,4 @@
1
+ class AuthSlice::Application < Merb::Controller
2
+ controller_for_slice
3
+ include AuthSlice::ControllerMixin
4
+ end
@@ -0,0 +1,150 @@
1
+ module AuthSlice
2
+ module ControllerMixin
3
+ protected
4
+ # Returns true or false if the user is logged in.
5
+ # Preloads @current_user with the user model if they're logged in.
6
+ def logged_in?
7
+ current_user != :false
8
+ end
9
+
10
+ # Accesses the current user from the session. Set it to :false if login fails
11
+ # so that future calls do not hit the database.
12
+ def current_user
13
+ @current_user ||= (login_from_session || login_from_basic_auth || login_from_cookie || :false)
14
+ end
15
+
16
+ # Store the given user in the session.
17
+ def current_user=(new_user)
18
+ session[:user] = (new_user.nil? || new_user.is_a?(Symbol)) ? nil : new_user.id
19
+ @current_user = new_user
20
+ end
21
+
22
+ # Check if the user is authorized
23
+ #
24
+ # Override this method in your controllers if you want to restrict access
25
+ # to only a few actions or if you want to check if the user
26
+ # has the correct rights.
27
+ #
28
+ # Example:
29
+ #
30
+ # # only allow nonbobs
31
+ # def authorized?
32
+ # current_user.username != "bob"
33
+ # end
34
+ def authorized?
35
+ logged_in?
36
+ end
37
+
38
+ # Filter method to enforce a login requirement.
39
+ #
40
+ # To require logins for all actions, use this in your controllers:
41
+ #
42
+ # before_filter :login_required
43
+ #
44
+ # To require logins for specific actions, use this in your controllers:
45
+ #
46
+ # before_filter :login_required, :only => [ :edit, :update ]
47
+ #
48
+ # To skip this in a subclassed controller:
49
+ #
50
+ # skip_before_filter :login_required
51
+ #
52
+ def login_required
53
+ authorized? || throw(:halt, :access_denied)
54
+ end
55
+
56
+ # Redirect as appropriate when an access request fails.
57
+ #
58
+ # The default HTML action is to redirect to the login screen.
59
+ #
60
+ # The default XML action is to render the text Couldn't authenticate you.
61
+ # To provide this response wrapped in XML, make sure to specify an
62
+ # XML layout, such as /app/views/layouts/application.xml.builder.
63
+ #
64
+ # Override this method in your controllers if you want to have special
65
+ # behavior in case the user is not authorized
66
+ # to access the requested action. For example, a popup window might
67
+ # simply close itself.
68
+ def access_denied
69
+ case content_type
70
+ when :html
71
+ store_location
72
+ redirect url(:login)
73
+ when :xml
74
+ headers["Status"] = "Unauthorized"
75
+ headers["WWW-Authenticate"] = %(Basic realm="Web Password")
76
+ self.status = 401
77
+ render "Couldn't authenticate you"
78
+ end
79
+ end
80
+
81
+ # Store the URI of the current request in the session.
82
+ #
83
+ # We can return to this location by calling #redirect_back_or_default.
84
+ def store_location
85
+ session[:return_to] = request.uri
86
+ end
87
+
88
+ # Redirect to the URI stored by the most recent store_location call or
89
+ # to the passed default.
90
+ def redirect_back_or_default(default)
91
+ loc = session[:return_to] || default
92
+ session[:return_to] = nil
93
+ redirect loc
94
+ end
95
+
96
+ # Inclusion hook to make #current_user and #logged_in?
97
+ # available as ActionView helper methods.
98
+ # def self.included(base)
99
+ # base.send :helper_method, :current_user, :logged_in?
100
+ # end
101
+
102
+ # Called from #current_user. First attempt to login by the user id stored in the session.
103
+ def login_from_session
104
+ self.current_user = find_user_by_id(session[:user]) if session[:user]
105
+ end
106
+
107
+ # Called from #current_user. Now, attempt to login by basic authentication information.
108
+ def login_from_basic_auth
109
+ username, passwd = get_auth_data
110
+ self.current_user = verify_login(username, passwd) if username && passwd
111
+ end
112
+
113
+ # Called from #current_user. Finaly, attempt to login by an expiring token in the cookie.
114
+ def login_from_cookie
115
+ user = cookies[:auth_token] && find_user_by_remember_token(cookies[:auth_token])
116
+ if user && user.remember_token?
117
+ user.remember_me
118
+ cookies[:auth_token] = { :value => user.remember_token, :expires => Time.parse(user.remember_token_expires_at.strftime) }
119
+ self.current_user = user
120
+ end
121
+ end
122
+
123
+ def reset_session
124
+ session.data.each{|k,v| session.data.delete(k)}
125
+ end
126
+
127
+ protected
128
+ def verify_login(username, password)
129
+ AuthSlice::User.authenticate(username, password)
130
+ end
131
+
132
+ def find_user_by_id(user_id)
133
+ AuthSlice::User.find_by_id(user_id)
134
+ end
135
+
136
+ def find_user_by_remember_token(token)
137
+ AuthSlice::User.find_by_remember_token(token)
138
+ end
139
+
140
+ private
141
+ @@http_auth_headers = %w(Authorization HTTP_AUTHORIZATION X-HTTP_AUTHORIZATION X_HTTP_AUTHORIZATION REDIRECT_X_HTTP_AUTHORIZATION)
142
+
143
+ # gets BASIC auth info
144
+ def get_auth_data
145
+ auth_key = @@http_auth_headers.detect { |h| request.env.has_key?(h) }
146
+ auth_data = request.env[auth_key].to_s.split unless auth_key.blank?
147
+ return auth_data && auth_data[0] == 'Basic' ? Base64.decode64(auth_data[1]).split(':')[0..1] : [nil, nil]
148
+ end
149
+ end
150
+ end
@@ -0,0 +1,41 @@
1
+ class AuthSlice::Users < AuthSlice::Application
2
+ provides :html
3
+
4
+ skip_before :login_required
5
+
6
+ def login
7
+ if request.post?
8
+ self.current_user = verify_login(params[:username], params[:password])
9
+ if logged_in?
10
+ if params[:remember_me] == "1"
11
+ self.current_user.remember_me
12
+ cookies[:auth_token] = {
13
+ :value => self.current_user.remember_token,
14
+ :expires => Time.parse(current_user.remember_token_expires_at.strftime)
15
+ }
16
+ end
17
+ return redirect_back_or_default('/')
18
+ end
19
+ end
20
+
21
+ render
22
+ end
23
+
24
+ def logout
25
+ self.current_user.forget_me if logged_in?
26
+ cookies.delete :auth_token
27
+ reset_session
28
+ redirect_back_or_default('/')
29
+ end
30
+
31
+ def signup
32
+ cookies.delete :auth_token
33
+ @user = AuthSlice::User.new(params['auth_slice::user'] || {})
34
+
35
+ if request.post? && @user.save
36
+ return redirect_back_or_default('/')
37
+ end
38
+
39
+ render
40
+ end
41
+ end
@@ -0,0 +1,64 @@
1
+ module Merb
2
+ module AuthSlice
3
+ module ApplicationHelper
4
+
5
+ # @param *segments<Array[#to_s]> Path segments to append.
6
+ #
7
+ # @return <String>
8
+ # A path relative to the public directory, with added segments.
9
+ def image_path(*segments)
10
+ public_path_for(:image, *segments)
11
+ end
12
+
13
+ # @param *segments<Array[#to_s]> Path segments to append.
14
+ #
15
+ # @return <String>
16
+ # A path relative to the public directory, with added segments.
17
+ def javascript_path(*segments)
18
+ public_path_for(:javascript, *segments)
19
+ end
20
+
21
+ # @param *segments<Array[#to_s]> Path segments to append.
22
+ #
23
+ # @return <String>
24
+ # A path relative to the public directory, with added segments.
25
+ def stylesheet_path(*segments)
26
+ public_path_for(:stylesheet, *segments)
27
+ end
28
+
29
+ # Construct a path relative to the public directory
30
+ #
31
+ # @param <Symbol> The type of component.
32
+ # @param *segments<Array[#to_s]> Path segments to append.
33
+ #
34
+ # @return <String>
35
+ # A path relative to the public directory, with added segments.
36
+ def public_path_for(type, *segments)
37
+ File.join(::AuthSlice.public_dir_for(type), *segments)
38
+ end
39
+
40
+ # Construct an app-level path.
41
+ #
42
+ # @param <Symbol> The type of component.
43
+ # @param *segments<Array[#to_s]> Path segments to append.
44
+ #
45
+ # @return <String>
46
+ # A path within the host application, with added segments.
47
+ def app_path_for(type, *segments)
48
+ File.join(::AuthSlice.app_dir_for(type), *segments)
49
+ end
50
+
51
+ # Construct a slice-level path.
52
+ #
53
+ # @param <Symbol> The type of component.
54
+ # @param *segments<Array[#to_s]> Path segments to append.
55
+ #
56
+ # @return <String>
57
+ # A path within the slice source (Gem), with added segments.
58
+ def slice_path_for(type, *segments)
59
+ File.join(::AuthSlice.dir_for(type), *segments)
60
+ end
61
+
62
+ end
63
+ end
64
+ end
@@ -0,0 +1,55 @@
1
+ module AuthSlice
2
+ module Adapter
3
+ module Activerecord
4
+ def self.create_user_model
5
+ Object.class_eval <<-end_eval
6
+ class ::AuthSlice::User < ActiveRecord::Base
7
+ include AuthSlice::Adapter::Activerecord
8
+ end
9
+ end_eval
10
+ end
11
+
12
+ def self.included(base)
13
+ base.class_eval do
14
+ include AuthSlice::BaseModel
15
+ extend ClassMethods
16
+
17
+ validates_presence_of :name
18
+ validates_presence_of :username
19
+ validates_presence_of :email
20
+ validates_length_of :username, :within => 3..40
21
+ validates_length_of :password, :within => 4..40, :if => :password_required?
22
+ validates_presence_of :password_confirmation, :if => :password_required?
23
+ validates_confirmation_of :password, :if => :password_required?
24
+ validates_uniqueness_of :username, :case_sensitive => false, :if => lambda { |u| !u.username.blank? }
25
+ validates_uniqueness_of :email, :case_sensitive => false, :if => lambda { |u| !u.email.blank? }
26
+
27
+ before_save :encrypt_password
28
+
29
+ def username=(value)
30
+ self[:username] = value.downcase if value
31
+ end
32
+ end
33
+ end
34
+
35
+ module ClassMethods
36
+ def drop_db_table
37
+ self.connection.drop_table("users")
38
+ end
39
+
40
+ def create_db_table
41
+ self.connection.create_table("users") do |t|
42
+ t.string :name, :limit => 40, :null => false
43
+ t.string :username, :limit => 40, :null => false
44
+ t.string :email, :null => false
45
+ t.string :crypted_password, :limit => 40
46
+ t.string :salt, :limit => 40, :null => false
47
+ t.string :remember_token
48
+ t.date :remember_token_expires_at
49
+ t.timestamps
50
+ end
51
+ end
52
+ end
53
+ end
54
+ end
55
+ end