rad_users 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- data/Rakefile +24 -0
- data/lib/_http_controller/authenticated.rb +142 -0
- data/lib/_models/open_id_authentication.rb +19 -0
- data/lib/_models/password_authentication.rb +88 -0
- data/lib/_open_id_authentication.rb +128 -0
- data/lib/components/user.rb +3 -0
- data/lib/components/users.rb +21 -0
- data/lib/components/users.yml +5 -0
- data/lib/users/_require.rb +35 -0
- data/lib/users/controller_helper.rb +29 -0
- data/lib/users/factories.rb +48 -0
- data/lib/users/gems.rb +9 -0
- data/lib/users/indexes.rb +8 -0
- data/lib/users/spec.rb +60 -0
- data/lib/users/tasks.rb +33 -0
- data/readme.md +3 -0
- data/spec/controllers/identities_spec.rb +163 -0
- data/spec/controllers/profiles_spec.rb +7 -0
- data/spec/controllers/sessions_spec.rb +154 -0
- data/spec/controllers/spec_helper.rb +13 -0
- data/spec/models/user_spec.rb +80 -0
- data/spec/spec_helper.rb +9 -0
- metadata +99 -0
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,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,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
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
|
data/lib/users/tasks.rb
ADDED
@@ -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,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,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
|
data/spec/spec_helper.rb
ADDED
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: []
|