rad_users 0.0.1

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/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: []