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