rad_users 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
data/Rakefile ADDED
@@ -0,0 +1,24 @@
1
+ require 'rake_ext'
2
+ require 'ruby_ext'
3
+
4
+ # Paths
5
+ tasks_dir = "#{__FILE__.dirname}/tasks"
6
+ $LOAD_PATH << tasks_dir unless $LOAD_PATH.include? tasks_dir
7
+
8
+ #
9
+ # Project
10
+ #
11
+ project(
12
+ name: "users",
13
+ official_name: 'rad_users',
14
+ summary: "User Management for RadKit Framework",
15
+ gem: true,
16
+
17
+ author: "Alexey Petrushin",
18
+ homepage: "http://github.com/alexeypetrushin/rad_users"
19
+ )
20
+
21
+ #
22
+ # Other
23
+ #
24
+ require 'users/tasks'
@@ -0,0 +1,142 @@
1
+ module Rad::Controller::Authenticated
2
+ module RoutingHelper
3
+ %w(login logout signup).each do |path|
4
+ define_method "#{path}_path" do |*args|
5
+ options = parse_routing_arguments *args
6
+
7
+ options = {
8
+ host: rad.users.host,
9
+ port: rad.users.port,
10
+ url_root: rad.users.url_root,
11
+
12
+ l: I18n.locale,
13
+ _return_to: (workspace.params[:_return_to] || workspace.request.url)
14
+ }.merge(options)
15
+
16
+ url_for_path "/#{path}", options
17
+ end
18
+ end
19
+
20
+ def user_path *args
21
+ options = parse_routing_arguments *args
22
+
23
+ options = {
24
+ # host: rad.users.host,
25
+ # port: rad.users.port,
26
+ url_root: rad.users.url_root,
27
+
28
+ l: I18n.locale
29
+ }.merge(options)
30
+
31
+ name = options.delete(:id)
32
+ # options[:url_root] = rad.config.users!.url_root! unless options.include? :url_root
33
+
34
+ url_for_path "/profiles/#{name}/show", options
35
+ end
36
+ end
37
+
38
+ protected
39
+ rad.extension :prepare_current_user, self do
40
+ define_method :prepare_current_user do
41
+ user = login_from_basic_auth || login_from_session || login_from_cookie || login_as_anonymous
42
+ raise "You probably don't create Anonymous User!" if user.nil?
43
+ Models::User.current = user
44
+ end
45
+ end
46
+
47
+
48
+ #
49
+ # Authentication Methods
50
+ #
51
+ def login_from_basic_auth
52
+ # TODO3 basic auth
53
+ # authenticate_with_http_controller_basic do |login, password|
54
+ # User.authenticate_by_password login, password unless login.blank? or password.blank?
55
+ # end
56
+ # username, password = request.credentials
57
+ # User.authenticate_by_password username, password unless username.blank? or password.blank?
58
+ end
59
+
60
+ def login_from_cookie
61
+ token = !request.cookies['auth_token'].blank? && Models::SecureToken.by_token(request.cookies['auth_token'])
62
+ if token and !token[:user_id].blank?
63
+ id = BSON::ObjectId.from_string token[:user_id]
64
+ if user = Models::User.first(_id: id, state: 'active')
65
+ request.session['user_id'] = user._id.to_s
66
+ user
67
+ end
68
+ end
69
+ end
70
+
71
+ def login_from_session
72
+ id = request.session['user_id']
73
+ Models::User.by_id BSON::ObjectId.from_string(id) unless id.blank?
74
+ end
75
+
76
+ def login_as_anonymous
77
+ request.session['user_id'] = Models::User.anonymous._id.to_s
78
+ Models::User.anonymous
79
+ end
80
+
81
+ def return_to_path_for_login
82
+ return_to_path
83
+ end
84
+
85
+ def return_to_path_for_logout
86
+ return_to_path
87
+ end
88
+
89
+ def set_current_user_with_updating_session user
90
+ current_user = Models::User.current
91
+ user.must_not == current_user
92
+
93
+ # Clear
94
+ clear_session!
95
+ unless current_user.anonymous?
96
+ Models::SecureToken.delete_all user_id: current_user._id.to_s
97
+ response.delete_cookie 'auth_token'
98
+ end
99
+
100
+ # Set session and cookie token
101
+ request.session['user_id'] = user._id.to_s
102
+ unless user.anonymous?
103
+ token = Models::SecureToken.new
104
+ token[:user_id] = user._id.to_s
105
+ token[:type] = 'cookie_auth'
106
+ token.expires_at = 2.weeks.from_now
107
+ token.save!
108
+
109
+ response.set_cookie 'auth_token', value: token.token, expires: token.expires_at
110
+ end
111
+
112
+ Models::User.current = user
113
+ end
114
+
115
+
116
+ #
117
+ # Special
118
+ #
119
+ PRESERVE_SESSION_KEYS = %w{authenticity_token}
120
+ rad.after :http, bang: false do
121
+ if rad.http.session
122
+ session_id = rad.http.session.stringify_keys['key'] || raise("session key not defined!")
123
+ PRESERVE_SESSION_KEYS << session_id unless PRESERVE_SESSION_KEYS.include? session_id
124
+ end
125
+ end
126
+
127
+
128
+ def clear_session!
129
+ session = request.session
130
+
131
+ session['dumb_key'] # hack, need this to initialize session, othervise it's empty
132
+ to_delete = session.keys.select{|key| !PRESERVE_SESSION_KEYS.include?(key.to_s)}
133
+ to_delete.each{|key| session.delete key}
134
+ end
135
+
136
+ end
137
+
138
+ Rad::Controller::Http.inherit Rad::Controller::Authenticated
139
+
140
+ [Rad::Controller::Abstract, Rad::Controller::Context].each do |klass|
141
+ klass.inherit Rad::Controller::Authenticated::RoutingHelper
142
+ end
@@ -0,0 +1,19 @@
1
+ module Models::OpenIdAuthentication
2
+ attr_writer :open_ids
3
+ def open_ids; @open_ids ||= [] end
4
+
5
+ inherited do
6
+ validates_uniqueness_of :open_ids, allow_blank: true
7
+ end
8
+
9
+ def authenticated_by_open_id? open_id
10
+ self.open_id == open_id
11
+ end
12
+
13
+ module ClassMethods
14
+ def authenticate_by_open_id open_id
15
+ return nil if open_id.blank?
16
+ Models::User.first state: 'active', open_ids: open_id
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,88 @@
1
+ module Models::PasswordAuthentication
2
+ # TODO1 remove this key
3
+ SITE_KEY = '3eed5a60c1bf8d43de5d0560e9fc2442fe74fdad'
4
+ DIGEST_STRETCHES = 10
5
+ PASSWORD_LENGTH = 3..40
6
+
7
+ attr_accessor :crypted_password, :salt
8
+
9
+ attr_reader :password
10
+ def password= password
11
+ @password = password
12
+ encrypt_password!
13
+ end
14
+
15
+ inherited do
16
+ validates_confirmation_of :password, if: :validate_password?
17
+ validates_length_of :password, in: PASSWORD_LENGTH, if: :validate_password?
18
+ end
19
+
20
+ def authenticated_by_password? password
21
+ return false if crypted_password.blank? or password.blank?
22
+ self.crypted_password == self.class.encrypt_password(password, salt)
23
+ end
24
+
25
+ def update_password password, password_confirmation, old_password
26
+ if crypted_password.blank?
27
+ self.password, self.password_confirmation = password, password_confirmation
28
+ elsif authenticated_by_password? old_password
29
+ self.password, self.password_confirmation = password, password_confirmation
30
+ true
31
+ else
32
+ errors.add :base, t(:invalid_old_password)
33
+ false
34
+ end
35
+ end
36
+
37
+ protected
38
+ def encrypt_password!
39
+ if password.blank?
40
+ self.crypted_password = ""
41
+ else
42
+ self.salt ||= self.class.generate_token
43
+ self.crypted_password = self.class.encrypt_password password, salt
44
+ end
45
+ end
46
+
47
+ def validate_password?
48
+ !password.nil?
49
+ # crypted_password.blank? or !password.blank?
50
+ end
51
+
52
+ module ClassMethods
53
+ def authenticate_by_password name, password
54
+ return nil if name.blank? or password.blank?
55
+ u = Models::User.first state: 'active', name: name
56
+ u && u.authenticated_by_password?(password) ? u : nil
57
+ end
58
+
59
+ # def by_secure_token token
60
+ # first conditions: {
61
+ # secure_token: token,
62
+ # secure_token_expires_at: {:$gt => Time.now.utc}
63
+ # }
64
+ # end
65
+ #
66
+ # def by_open_id id
67
+ # return nil if id.blank?
68
+ # first open_ids: id
69
+ # end
70
+
71
+ def encrypt_password password, salt
72
+ digest = SITE_KEY
73
+ DIGEST_STRETCHES.times do
74
+ digest = secure_digest(digest, salt, password, SITE_KEY)
75
+ end
76
+ digest
77
+ end
78
+
79
+ def generate_token
80
+ secure_digest Time.now, (1..10).map{ rand.to_s }
81
+ end
82
+
83
+ protected
84
+ def secure_digest *args
85
+ Digest::SHA1.hexdigest(args.flatten.join('--'))
86
+ end
87
+ end
88
+ end
@@ -0,0 +1,128 @@
1
+ require 'uri'
2
+ require 'openid'
3
+ require 'rack/openid'
4
+
5
+ module OpenIdAuthentication
6
+ def self.new(app)
7
+ store = OpenIdAuthentication.store
8
+ if store.nil?
9
+ rad.logger.warn "OpenIdAuthentication.store is nil. Using in-memory store."
10
+ end
11
+
12
+ ::Rack::OpenID.new(app, OpenIdAuthentication.store)
13
+ end
14
+
15
+ def self.store
16
+ @@store
17
+ end
18
+
19
+ def self.store=(*store_option)
20
+ store, *parameters = *([ store_option ].flatten)
21
+
22
+ @@store = case store
23
+ when :memory
24
+ require 'openid/store/memory'
25
+ OpenID::Store::Memory.new
26
+ when :file
27
+ require 'openid/store/filesystem'
28
+ OpenID::Store::Filesystem.new("#{rad.runtime_path}/tmp/openids")
29
+ when :memcache
30
+ require 'memcache'
31
+ require 'openid/store/memcache'
32
+ OpenID::Store::Memcache.new(MemCache.new(parameters))
33
+ else
34
+ raise "Unknown store!"
35
+ end
36
+ end
37
+
38
+ # self.store = nil
39
+
40
+ class Result
41
+ ERROR_MESSAGES = {
42
+ :missing => "Sorry, the OpenID server couldn't be found",
43
+ :invalid => "Sorry, but this does not appear to be a valid OpenID",
44
+ :canceled => "OpenID verification was canceled",
45
+ :failed => "OpenID verification failed",
46
+ :setup_needed => "OpenID verification needs setup"
47
+ }
48
+
49
+ def self.[](code)
50
+ new(code)
51
+ end
52
+
53
+ def initialize(code)
54
+ @code = code
55
+ end
56
+
57
+ def status
58
+ @code
59
+ end
60
+
61
+ ERROR_MESSAGES.keys.each { |state| define_method("#{state}?") { @code == state } }
62
+
63
+ def successful?
64
+ @code == :successful
65
+ end
66
+
67
+ def unsuccessful?
68
+ ERROR_MESSAGES.keys.include?(@code)
69
+ end
70
+
71
+ def message
72
+ ERROR_MESSAGES[@code]
73
+ end
74
+ end
75
+
76
+ protected
77
+ # The parameter name of "openid_identifier" is used rather than
78
+ # the Rails convention "open_id_identifier" because that's what
79
+ # the specification dictates in order to get browser auto-complete
80
+ # working across sites
81
+ def using_open_id?(identifier = nil) #:doc:
82
+ identifier ||= open_id_identifier
83
+ !identifier.blank? || workspace.env[Rack::OpenID::RESPONSE]
84
+ end
85
+
86
+ def authenticate_with_open_id(identifier = nil, options = {}, &block) #:doc:
87
+ identifier ||= open_id_identifier
88
+
89
+ if workspace.env[Rack::OpenID::RESPONSE]
90
+ complete_open_id_authentication(&block)
91
+ else
92
+ begin_open_id_authentication(identifier, options, &block)
93
+ end
94
+ end
95
+
96
+ private
97
+ def open_id_identifier
98
+ params[:openid_identifier] || params[:openid_url]
99
+ end
100
+
101
+ def begin_open_id_authentication(identifier, options = {})
102
+ options[:identifier] = identifier
103
+ value = Rack::OpenID.build_header(options)
104
+ response.headers[Rack::OpenID::AUTHENTICATE_HEADER] = value
105
+ head :unauthorized
106
+ end
107
+
108
+ def complete_open_id_authentication
109
+ response = workspace.env[Rack::OpenID::RESPONSE]
110
+ identifier = response.display_identifier
111
+
112
+ case response.status
113
+ when OpenID::Consumer::SUCCESS
114
+ yield Result[:successful], identifier,
115
+ OpenID::SReg::Response.from_success_response(response)
116
+ when :missing
117
+ yield Result[:missing], identifier, nil
118
+ when :invalid
119
+ yield Result[:invalid], identifier, nil
120
+ when OpenID::Consumer::CANCEL
121
+ yield Result[:canceled], identifier, nil
122
+ when OpenID::Consumer::FAILURE
123
+ yield Result[:failed], identifier, nil
124
+ when OpenID::Consumer::SETUP_NEEDED
125
+ yield Result[:setup_needed], response.setup_url, nil
126
+ end
127
+ end
128
+ end
@@ -0,0 +1,3 @@
1
+ rad.register :user, scope: :cycle do
2
+ Models::User.new
3
+ end
@@ -0,0 +1,21 @@
1
+ users_dir = "#{__FILE__}/../../.."
2
+
3
+ rad.register :users, depends_on: :kit do
4
+ require 'users/_require'
5
+
6
+ rad.configure :web, users_dir do |c|
7
+ # c.config blank: true, override: false
8
+ c.locales
9
+ c.asset_paths 'app/static'
10
+ c.template_paths 'app/views'
11
+ c.autoload_paths %w(lib app)
12
+ end
13
+ Rad::Users.new
14
+ end
15
+ rad.after :users do
16
+ OpenIdAuthentication.store = rad.users.open_id_store
17
+
18
+ rad.configure :web, users_dir do |c|
19
+ c.routes
20
+ end
21
+ end
@@ -0,0 +1,5 @@
1
+ open_id_store: :file
2
+ url_root: /users
3
+ email: admin@localhost
4
+ host: localhost
5
+ open_id_store: :file
@@ -0,0 +1,35 @@
1
+ require 'users/gems'
2
+
3
+ #
4
+ # Libraries
5
+ #
6
+ %w(
7
+ open_id_authentication
8
+ password_authentication
9
+ ).each{|f| require "_models/#{f}"}
10
+
11
+ %w(
12
+ authenticated
13
+ ).each{|f| require "_http_controller/#{f}"}
14
+
15
+
16
+ class Rad::Users
17
+ attr_accessor :open_id_store
18
+ attr_writer :email, :host, :url_root, :port
19
+ attr_required :email, :open_id_store
20
+ def host; @host || rad.http.host end
21
+ def url_root; @url_root || rad.http.url_root end
22
+ def port; @port || rad.http.port end
23
+
24
+ attr_accessor :avatars_path
25
+ end
26
+
27
+
28
+ #
29
+ # Open ID
30
+ #
31
+ require '_open_id_authentication'
32
+
33
+ rad.http.stack.push(-> builder {
34
+ builder.use OpenIdAuthentication
35
+ })
@@ -0,0 +1,29 @@
1
+ module ControllerHelper
2
+ module ClassMethods
3
+ #
4
+ # Navigation
5
+ #
6
+ def logo opt = {}, &block
7
+ before opt do |controller|
8
+ controller.instance_variable_set "@logo", controller.instance_eval(&block)
9
+ end
10
+ end
11
+
12
+ def breadcrumb opt = {}, &block
13
+ before opt do |controller|
14
+ controller.instance_variable_set "@breadcrumb", controller.instance_eval(&block)
15
+ end
16
+ end
17
+
18
+ def active_menu opt = {}, &block
19
+ before opt do |controller|
20
+ controller.instance_variable_set "@active_menu", controller.instance_eval(&block)
21
+ end
22
+ end
23
+ end
24
+
25
+ protected
26
+ def set_theme
27
+ @theme = "simplicity"
28
+ end
29
+ end
@@ -0,0 +1,48 @@
1
+ require 'factory_girl'
2
+
3
+ #
4
+ # User
5
+ #
6
+ Factory.define :blank_user, class: 'Models::User' do |u|
7
+ u.sequence(:name){|i| "user#{i}"}
8
+ end
9
+
10
+ Factory.define :new_user, class: 'Models::User' do |u|
11
+ u.sequence(:name){|i| "user#{i}"}
12
+ u.sequence(:email){|i| "user#{i}@email.com"}
13
+ u.sequence(:password){|i| "user#{i}"}
14
+ u.password_confirmation{|_self| _self.password}
15
+ end
16
+
17
+ Factory.define :user, parent: :new_user do |u|
18
+ u.state 'active'
19
+ end
20
+
21
+ Factory.define :open_id_user, class: 'Models::User' do |u|
22
+ u.sequence(:name){|i| "user#{i}"}
23
+ u.sequence(:open_ids){|i| ["open_id_#{i}"]}
24
+ u.state 'active'
25
+ end
26
+
27
+ Factory.define :anonymous, parent: :new_user do |u|
28
+ u.name 'anonymous'
29
+ u.email "anonymous@mail.com"
30
+ u.password "anonymous_password"
31
+ u.password_confirmation{|_self| _self.password}
32
+ end
33
+
34
+ Factory.define :admin, parent: :new_user do |u|
35
+ u.admin true
36
+ end
37
+
38
+ Factory.define :member, parent: :new_user do |u|
39
+ u.roles{%w{member}}
40
+ end
41
+
42
+ Factory.define :manager, parent: :member do |u|
43
+ u.roles{%w{manager}}
44
+ end
45
+
46
+ Factory.define :global_admin, parent: :new_user do |u|
47
+ u.global_admin true
48
+ end
data/lib/users/gems.rb ADDED
@@ -0,0 +1,9 @@
1
+ # core gem dependencies
2
+ gem 'ruby-openid', '2.1.8'
3
+
4
+ # core gems
5
+ gem 'rack-openid', '1.3.1'
6
+
7
+ if respond_to? :fake_gem
8
+ fake_gem 'rad_kit'
9
+ end
@@ -0,0 +1,8 @@
1
+ Models::User.collection.instance_eval do
2
+ create_index [[:open_ids, 1]]
3
+ create_index [[:name, 1]], unique: true
4
+ create_index [[:email, 1]], unique: true
5
+ create_index [[:state, 1]]
6
+ create_index [[:created_at]]
7
+ create_index [[:updated_at]]
8
+ end
data/lib/users/spec.rb ADDED
@@ -0,0 +1,60 @@
1
+ require 'kit/spec'
2
+
3
+
4
+ #
5
+ # Factories
6
+ #
7
+ rad.users
8
+ require 'users/factories'
9
+
10
+
11
+ #
12
+ # Routing helpers
13
+ #
14
+ rspec.include Rad::Controller::Authenticated::RoutingHelper
15
+
16
+
17
+ #
18
+ # User helpers
19
+ #
20
+ Models::User.class_eval do
21
+ def self.anonymous
22
+ @anonymous ||= Factory.build :anonymous
23
+ end
24
+ end
25
+
26
+
27
+ #
28
+ # Authorization stub
29
+ #
30
+ AUTHENTICATION_CONTROLLERS = rad.extension :authentication_controllers do
31
+ [::Rad::Controller::Authenticated]
32
+ end
33
+
34
+ AUTHENTICATION_CONTROLLERS.each do |auth|
35
+ auth.class_eval do
36
+ alias_method :prepare_current_user_without_test, :prepare_current_user
37
+ def prepare_current_user_with_test; end
38
+ alias_method :prepare_current_user, :prepare_current_user_with_test
39
+ end
40
+ end
41
+
42
+ rspec do
43
+ def self.with_auth
44
+ before :all do
45
+ AUTHENTICATION_CONTROLLERS.each do |auth|
46
+ auth.class_eval do
47
+ alias_method :prepare_current_user, :prepare_current_user_without_test
48
+ end
49
+ end
50
+ end
51
+
52
+ after :all do
53
+ AUTHENTICATION_CONTROLLERS.each do |auth|
54
+ auth.class_eval do
55
+ alias_method :prepare_current_user, :prepare_current_user_with_test
56
+ end
57
+ end
58
+ end
59
+ end
60
+ end
@@ -0,0 +1,33 @@
1
+ require 'rad/tasks'
2
+
3
+ namespace :users do
4
+ desc "Creates :anonymous and :admin (name: admin, password: admin) users"
5
+ task initialize: :environment do
6
+ # Anonymous User
7
+ Models::User.destroy_all({name: 'anonymous'}, validate: false)
8
+ Models::User.new.set!(
9
+ name: 'anonymous',
10
+ email: "anonymous@mail.com",
11
+ password: "anonymous_password",
12
+ password_confirmation: "anonymous_password",
13
+ state: 'active'
14
+ ).save! validate: false
15
+
16
+ # Admin User
17
+ Models::User.destroy_all({name: 'admin'}, validate: false)
18
+ admin = Models::User.new
19
+ admin.set!(
20
+ name: 'admin',
21
+ email: "admin@mail.com",
22
+ password: 'admin',
23
+ password_confirmation: 'admin',
24
+ state: 'active'
25
+ )
26
+ if admin.respond_to? :global_admin=
27
+ admin.global_admin = true
28
+ else
29
+ admin.admin = true
30
+ end
31
+ admin.save! validate: false
32
+ end
33
+ end
data/readme.md ADDED
@@ -0,0 +1,3 @@
1
+ # User Management module for Rad Kit Framework
2
+
3
+ Copyright (c) Alexey Petrushin http://petrush.in, released under the MIT license.
@@ -0,0 +1,163 @@
1
+ require 'controllers/spec_helper'
2
+
3
+ describe "Identities" do
4
+ with_controllers
5
+ set_controller Controllers::Identities
6
+
7
+ describe "Signup using Mail and Password" do
8
+ before{login_as Models::User.anonymous}
9
+
10
+ it "enter_email_form" do
11
+ call :enter_email_form
12
+ response.should be_ok
13
+ end
14
+
15
+ it "enter_email" do
16
+ pcall :enter_email, token: {email: "some@mail.com"}, l: 'ru', _return_to: 'http://town.com'
17
+ response.should redirect_to(follow_email_link_identities_path(l: 'ru', _return_to: 'http://town.com'))
18
+
19
+ Models::User::EmailVerificationToken.count.should == 1
20
+ token = Models::User::EmailVerificationToken.first
21
+
22
+ token.email.should == "some@mail.com"
23
+
24
+ sent_letters.size.should == 1
25
+ mail = sent_letters.first
26
+
27
+ mail.body.should include(finish_email_registration_form_identities_path(token: token.token, l: 'ru', _return_to: 'http://town.com'))
28
+ end
29
+
30
+ it "finish_email_registration_form" do
31
+ token = Models::User::EmailVerificationToken.create! email: "some@mail.com"
32
+ call :finish_email_registration_form, token: token.token
33
+ response.should be_ok
34
+ end
35
+
36
+ it "finish_email_registration" do
37
+ return_to = 'http://town.com'
38
+ token = Models::User::EmailVerificationToken.create! email: "some@mail.com"
39
+ user_attrs = {name: "user1", password: "user1", password_confirmation: "user1"}.stringify_keys
40
+ pcall :finish_email_registration, token: token.token, user: user_attrs, l: 'ru', _return_to: return_to
41
+ response.should redirect_to(login_path(_return_to: return_to))
42
+ end
43
+ end
44
+
45
+ describe "Registered Users should be able to reset Password" do
46
+ before do
47
+ @user = Factory.create :user
48
+ end
49
+
50
+ it "forgot_password_form" do
51
+ login_as Models::User.anonymous
52
+ call :forgot_password_form
53
+ response.should be_ok
54
+ end
55
+
56
+ it "forgot_password" do
57
+ login_as Models::User.anonymous
58
+ pcall :forgot_password, email: @user.email
59
+
60
+ Models::User::ForgotPasswordToken.count.should == 1
61
+ token = Models::User::ForgotPasswordToken.first
62
+
63
+ sent_letters.size.should == 1
64
+ mail = sent_letters.last
65
+ mail.body.should include(reset_password_form_identities_path(token: token.token))
66
+
67
+ response.should redirect_to(default_path)
68
+ end
69
+
70
+ it "reset_password_form" do
71
+ token = Models::User::ForgotPasswordToken.create! user: @user
72
+
73
+ login_as Models::User.anonymous
74
+ call :reset_password_form, token: token.token
75
+ response.should be_ok
76
+ end
77
+
78
+ it "reset_password" do
79
+ token = Models::User::ForgotPasswordToken.create! user: @user
80
+
81
+ login_as Models::User.anonymous
82
+ pcall :reset_password, user: {password: "new password", password_confirmation: "new password"}.stringify_keys, token: token.token
83
+ response.should redirect_to(login_path(_return_to: nil))
84
+
85
+ @user.reload
86
+ @user.should be_authenticated_by_password("new password")
87
+ end
88
+
89
+ it "reset_password shouldn't reset password if token is invalid" do
90
+ token = Models::User::ForgotPasswordToken.create! user: @user
91
+
92
+ login_as Models::User.anonymous
93
+ pcall :reset_password, user: {password: "new password", password_confirmation: "new password"}.stringify_keys, token: 'invalid token'
94
+ response.should redirect_to(default_path)
95
+
96
+ @user.reload
97
+ @user.should_not be_authenticated_by_password("new password")
98
+ end
99
+ end
100
+
101
+ describe "Registered Users should be able to change Password" do
102
+ login_as :user
103
+
104
+ it "update_password_form" do
105
+ call :update_password_form
106
+ end
107
+
108
+ it "update_password" do
109
+ pcall :update_password, old_password: @user.password, user: {password: "new password", password_confirmation: "new password"}.stringify_keys
110
+ response.should redirect_to(default_path)
111
+ @user.should be_authenticated_by_password("new password")
112
+ end
113
+
114
+ it "Should't allow to change Password if Old Password is Invalid" do
115
+ pcall :update_password, old_password: 'invalid password', user: {password: "new password", password_confirmation: "new password"}.stringify_keys
116
+ @user.should_not be_authenticated_by_password("new password")
117
+ end
118
+ end
119
+
120
+ describe "Signup using OpenId" do
121
+ before do
122
+ @token = Models::SecureToken.new
123
+ @token[:open_id] = "some_id"
124
+ @token.save!
125
+
126
+ @return_to = "http://some.com/some".freeze
127
+ @escaped_return_to = "http%3A%2F%2Fsome.com%2Fsome".freeze
128
+
129
+ login_as Models::User.anonymous
130
+ end
131
+
132
+ def form_action_should_include_return_to
133
+ Nokogiri::XML(response.body).css("form").first[:action].should include(@escaped_return_to)
134
+ end
135
+
136
+ it "finish_open_id_registration_form" do
137
+ call :finish_open_id_registration_form, token: @token.token, _return_to: @return_to
138
+ response.should be_ok
139
+ form_action_should_include_return_to
140
+ end
141
+
142
+ it "finish_open_id_registration" do
143
+ pcall :finish_open_id_registration, user: {name: 'user1'}.stringify_keys, token: @token.token, _return_to: @return_to
144
+
145
+ # cas_token = Models::SecureToken.first type: 'cas'
146
+ # cas_token.should_not be_nil
147
+ # response.should redirect_to(@return_to + "?cas_token=#{cas_token.token}")
148
+
149
+ response.should redirect_to(@return_to)
150
+
151
+ user = Models::User.find_by_name 'user1'
152
+ user.should be_active
153
+ user.should_not be_nil
154
+ Models::User.current.should == user
155
+ end
156
+
157
+ it "should preserve _return_to if invalid form submited (from error)" do
158
+ pcall :finish_open_id_registration, user: {name: 'invalid name'}.stringify_keys, token: @token.token, _return_to: @return_to
159
+ response.should be_ok
160
+ form_action_should_include_return_to
161
+ end
162
+ end
163
+ end
@@ -0,0 +1,7 @@
1
+ require 'controllers/spec_helper'
2
+
3
+ describe "Profiles" do
4
+ with_controllers
5
+ set_controller Controllers::Profiles
6
+ login_as :admin
7
+ end
@@ -0,0 +1,154 @@
1
+ require 'controllers/spec_helper'
2
+
3
+ describe "Authentication" do
4
+ with_controllers
5
+ with_auth
6
+
7
+ def form_action_should_include_return_to
8
+ Nokogiri::XML(response.body_as_string).css("form").first[:action].should include(@escaped_return_to)
9
+ end
10
+
11
+ before :all do
12
+ class SomeDomain < Controllers::UserManagement
13
+ def all; end
14
+ end
15
+ end
16
+
17
+ after :all do
18
+ remove_constants :SomeDomain
19
+ end
20
+
21
+ before do
22
+ Models::User.current = NotDefined
23
+ @return_to = "http://some.com/some".freeze
24
+ @escaped_return_to = "http%3A%2F%2Fsome.com%2Fsome".freeze
25
+ end
26
+
27
+ describe "By defautl should logged in as Anonymous" do
28
+ it do
29
+ pcall SomeDomain, :all
30
+ response.should be_ok
31
+ Models::User.current.should == Models::User.anonymous
32
+ end
33
+ end
34
+
35
+ describe "Login by Password" do
36
+ it "Should display Log In Form" do
37
+ pcall Controllers::Sessions, :login, _return_to: @return_to
38
+ response.should be_ok
39
+ form_action_should_include_return_to
40
+ end
41
+
42
+ it "Registered Users should be able to Log In" do
43
+ user = Factory.create :user
44
+ pcall Controllers::Sessions, :login, name: user.name, password: user.password, _return_to: @return_to
45
+ response.location.start_with?(@return_to).should be_true
46
+ Models::User.current.should == user
47
+ end
48
+
49
+ it "Users shouldn't be able to login with invalid password" do
50
+ user = Factory.create :user
51
+ pcall Controllers::Sessions, :login, name: user.name, password: 'invalid', _return_to: @return_to
52
+ response.should be_ok
53
+ form_action_should_include_return_to
54
+ Models::User.current.should == Models::User.anonymous
55
+ end
56
+
57
+ it "Not activated users should'not be able to login" do
58
+ user = Factory.create :new_user
59
+ pcall Controllers::Sessions, :login, name: user.name, password: user.password, _return_to: @return_to
60
+ response.should be_ok
61
+ form_action_should_include_return_to
62
+ Models::User.current.should == Models::User.anonymous
63
+ end
64
+ end
65
+
66
+ describe "Login by OpenID" do
67
+ it "if user doesn't exists redirect to registration" do
68
+ pcall Controllers::Sessions, :login, openid_identifier: "http://some_id.com", _return_to: @return_to
69
+
70
+ token = Models::SecureToken.first
71
+ token.should_not be_nil
72
+
73
+ response.should redirect_to(finish_open_id_registration_form_identities_path(token: token.token, _return_to: @return_to))
74
+ end
75
+
76
+ it "if user exists login" do
77
+ open_id = "http://some_id.com"
78
+
79
+ user = Factory.build :user
80
+ user.open_ids << open_id
81
+ user.save!
82
+
83
+ pcall Controllers::Sessions, :login, openid_identifier: open_id, _return_to: @return_to
84
+
85
+ # token = Models::SecureToken.first type: 'cas'
86
+ # token.should_not be_nil
87
+ # response.should redirect_to(@return_to + "?cas_token=#{token.token}")
88
+
89
+ response.should redirect_to(@return_to)
90
+
91
+ Models::User.current.should == user
92
+ end
93
+ end
94
+
95
+ describe "Log Out" do
96
+ it "Registered Users should be able to Log Out" do
97
+ user = Factory.create :user
98
+ Models::User.current = user
99
+ call Controllers::Sessions, :logout, _return_to: @return_to
100
+ response.should redirect_to(@return_to)
101
+
102
+ Models::User.current.should == Models::User.anonymous
103
+ end
104
+
105
+ it "Should not loose session variables (from error)" do
106
+ pcall Controllers::Sessions, :login do |c|
107
+ request.session[:variable] = true
108
+ c.call
109
+ request.session[:variable].should be_true
110
+ end
111
+ response.should be_ok
112
+ end
113
+ end
114
+
115
+ describe "Set Cookie Token" do
116
+ it "should set remember me token" do
117
+ user = Factory.create :user
118
+ pcall Controllers::Sessions, :login, name: user.name, password: user.password do |c|
119
+ c.call
120
+
121
+ Models::SecureToken.count.should == 1
122
+ token = Models::SecureToken.first
123
+ token[:user_id].should == user._id.to_s
124
+
125
+ response.cookies.should =~ /auth_token=#{token.token}/
126
+ end
127
+ end
128
+ end
129
+
130
+ describe "Restore user from Cookie Token" do
131
+ it "any action in domain controller" do
132
+ user = Factory.create :user
133
+
134
+ token = Models::SecureToken.new
135
+ token[:user_id] = user._id.to_s
136
+ token.expires_at = 2.weeks.from_now
137
+ token.save!
138
+
139
+ pcall SomeDomain, :all do |c|
140
+ request.cookies['auth_token'] = token.token
141
+ c.call
142
+ end
143
+
144
+ Models::User.current.name.should == user.name
145
+ end
146
+ end
147
+
148
+ describe "Miscellaneous" do
149
+ it "should show status" do
150
+ call Controllers::Sessions, :status
151
+ response.should be_ok
152
+ end
153
+ end
154
+ end
@@ -0,0 +1,13 @@
1
+ require 'spec_helper'
2
+
3
+ OpenIdAuthentication.class_eval do
4
+ def authenticate_with_open_id identifier = nil, options = {}, &block
5
+ openid_identifier = params[:openid_identifier]
6
+ raise 'invalid usage' if openid_identifier.nil?
7
+ block.call(
8
+ {successful: true}.to_openobject,
9
+ openid_identifier,
10
+ "some not used parameters"
11
+ )
12
+ end
13
+ end
@@ -0,0 +1,80 @@
1
+ require 'spec_helper'
2
+
3
+ describe "User" do
4
+ with_models
5
+
6
+ it "blank user" do
7
+ user = Factory.build(:blank_user)
8
+ user.should_not be_valid
9
+ user.should be_inactive
10
+ end
11
+
12
+ describe "Authentication by Password" do
13
+ it "registering user" do
14
+ user = Factory.build(:new_user)
15
+ user.crypted_password.should_not be_blank
16
+ user.should be_valid
17
+ user.should be_inactive
18
+ user.should be_authenticated_by_password(user.password)
19
+ end
20
+
21
+ it "authentication" do
22
+ user = Factory.create :user
23
+ Models::User.authenticate_by_password(user.name, user.password).should == user
24
+ end
25
+
26
+ it "email uniquiness" do
27
+ user = Factory.build :user
28
+ user.email = "some@email.com"
29
+ user.save.should be_true
30
+
31
+ user = Factory.build :user
32
+ user.email = "some@email.com"
33
+ user.save.should be_false
34
+ user.errors[:email].should_not be_blank
35
+ end
36
+
37
+ it "update_password" do
38
+ user = Factory.create :user
39
+ user.update_password('new_password', 'new_password', 'invalid').should be_false
40
+ user.update_password('new_password', 'new_password', user.password).should be_true
41
+ end
42
+ end
43
+
44
+ describe "Authentication by OpenID" do
45
+ it "registering user" do
46
+ user = Factory.build(:blank_user)
47
+ user.open_ids << "open_id"
48
+ user.crypted_password.should be_blank
49
+ user.should be_valid
50
+ user.should be_inactive
51
+
52
+ user.save.should be_true
53
+ user.reload
54
+ user.crypted_password.should be_blank
55
+ end
56
+
57
+ it "authentication" do
58
+ user = Factory.create :open_id_user
59
+ Models::User.authenticate_by_open_id(user.open_ids.first).should == user
60
+ end
61
+
62
+ # it "open_id uniquiness" do
63
+ # user = Factory.build :open_id_user
64
+ # user.open_ids = ['some_id']
65
+ # user.save.should be_true
66
+ #
67
+ # user = Factory.build :open_id_user
68
+ # user.open_ids = ['some_id']
69
+ # user.save.should be_false
70
+ # user.errors[:open_ids].should_not be_blank
71
+ # end
72
+ end
73
+
74
+ it "add password to OpenID" do
75
+ user = Factory.create :open_id_user
76
+ user.update_password('new_password', 'new_password', '').should be_true
77
+ user.save.should be_true
78
+ Models::User.authenticate_by_password(user.name, user.password).should == user
79
+ end
80
+ end
@@ -0,0 +1,9 @@
1
+ require 'rspec_ext'
2
+
3
+ require 'rad'
4
+ require 'rad/spec'
5
+
6
+ rad.kit
7
+
8
+ require 'kit/spec'
9
+ require 'users/spec'
metadata ADDED
@@ -0,0 +1,99 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: rad_users
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ prerelease:
6
+ platform: ruby
7
+ authors:
8
+ - Alexey Petrushin
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2011-09-22 00:00:00.000000000Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: ruby-openid
16
+ requirement: &2833150 !ruby/object:Gem::Requirement
17
+ none: false
18
+ requirements:
19
+ - - =
20
+ - !ruby/object:Gem::Version
21
+ version: 2.1.8
22
+ type: :runtime
23
+ prerelease: false
24
+ version_requirements: *2833150
25
+ - !ruby/object:Gem::Dependency
26
+ name: rack-openid
27
+ requirement: &2832910 !ruby/object:Gem::Requirement
28
+ none: false
29
+ requirements:
30
+ - - =
31
+ - !ruby/object:Gem::Version
32
+ version: 1.3.1
33
+ type: :runtime
34
+ prerelease: false
35
+ version_requirements: *2832910
36
+ - !ruby/object:Gem::Dependency
37
+ name: rad_kit
38
+ requirement: &2832670 !ruby/object:Gem::Requirement
39
+ none: false
40
+ requirements:
41
+ - - ! '>='
42
+ - !ruby/object:Gem::Version
43
+ version: '0'
44
+ type: :runtime
45
+ prerelease: false
46
+ version_requirements: *2832670
47
+ description:
48
+ email:
49
+ executables: []
50
+ extensions: []
51
+ extra_rdoc_files: []
52
+ files:
53
+ - Rakefile
54
+ - readme.md
55
+ - lib/_http_controller/authenticated.rb
56
+ - lib/_models/open_id_authentication.rb
57
+ - lib/_models/password_authentication.rb
58
+ - lib/_open_id_authentication.rb
59
+ - lib/components/user.rb
60
+ - lib/components/users.rb
61
+ - lib/components/users.yml
62
+ - lib/users/_require.rb
63
+ - lib/users/controller_helper.rb
64
+ - lib/users/factories.rb
65
+ - lib/users/gems.rb
66
+ - lib/users/indexes.rb
67
+ - lib/users/spec.rb
68
+ - lib/users/tasks.rb
69
+ - spec/controllers/identities_spec.rb
70
+ - spec/controllers/profiles_spec.rb
71
+ - spec/controllers/sessions_spec.rb
72
+ - spec/controllers/spec_helper.rb
73
+ - spec/models/user_spec.rb
74
+ - spec/spec_helper.rb
75
+ homepage: http://github.com/alexeypetrushin/rad_users
76
+ licenses: []
77
+ post_install_message:
78
+ rdoc_options: []
79
+ require_paths:
80
+ - lib
81
+ required_ruby_version: !ruby/object:Gem::Requirement
82
+ none: false
83
+ requirements:
84
+ - - ! '>='
85
+ - !ruby/object:Gem::Version
86
+ version: '0'
87
+ required_rubygems_version: !ruby/object:Gem::Requirement
88
+ none: false
89
+ requirements:
90
+ - - ! '>='
91
+ - !ruby/object:Gem::Version
92
+ version: '0'
93
+ requirements: []
94
+ rubyforge_project:
95
+ rubygems_version: 1.8.6
96
+ signing_key:
97
+ specification_version: 3
98
+ summary: User Management for RadKit Framework
99
+ test_files: []