passwd 0.1.5 → 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (98) hide show
  1. checksums.yaml +4 -4
  2. data/.travis.yml +4 -1
  3. data/CHANGELOG.md +30 -1
  4. data/Gemfile +0 -5
  5. data/LICENSE.txt +2 -1
  6. data/README.md +96 -156
  7. data/Rakefile +2 -1
  8. data/example/.gitignore +16 -0
  9. data/example/Gemfile +25 -0
  10. data/example/README.rdoc +28 -0
  11. data/example/Rakefile +6 -0
  12. data/example/app/assets/images/.keep +0 -0
  13. data/example/app/assets/javascripts/application.js +16 -0
  14. data/example/app/assets/stylesheets/application.css +16 -0
  15. data/example/app/controllers/application_controller.rb +10 -0
  16. data/example/app/controllers/concerns/.keep +0 -0
  17. data/example/app/controllers/profiles_controller.rb +28 -0
  18. data/example/app/controllers/root_controller.rb +5 -0
  19. data/example/app/controllers/sessions_controller.rb +29 -0
  20. data/example/app/helpers/application_helper.rb +2 -0
  21. data/example/app/mailers/.keep +0 -0
  22. data/example/app/models/.keep +0 -0
  23. data/example/app/models/concerns/.keep +0 -0
  24. data/example/app/models/user.rb +4 -0
  25. data/example/app/views/layouts/application.html.erb +15 -0
  26. data/example/app/views/profiles/edit.html.erb +14 -0
  27. data/example/app/views/profiles/show.html.erb +12 -0
  28. data/example/app/views/root/index.html.erb +5 -0
  29. data/example/app/views/sessions/new.html.erb +6 -0
  30. data/example/bin/bundle +3 -0
  31. data/example/bin/rails +4 -0
  32. data/example/bin/rake +4 -0
  33. data/example/config.ru +4 -0
  34. data/example/config/application.rb +40 -0
  35. data/example/config/boot.rb +4 -0
  36. data/example/config/database.yml +26 -0
  37. data/example/config/environment.rb +5 -0
  38. data/example/config/environments/development.rb +37 -0
  39. data/example/config/environments/production.rb +78 -0
  40. data/example/config/environments/test.rb +39 -0
  41. data/example/config/initializers/assets.rb +8 -0
  42. data/example/config/initializers/backtrace_silencers.rb +7 -0
  43. data/example/config/initializers/cookies_serializer.rb +3 -0
  44. data/example/config/initializers/filter_parameter_logging.rb +4 -0
  45. data/example/config/initializers/inflections.rb +16 -0
  46. data/example/config/initializers/mime_types.rb +4 -0
  47. data/example/config/initializers/passwd.rb +41 -0
  48. data/example/config/initializers/session_store.rb +3 -0
  49. data/example/config/initializers/wrap_parameters.rb +14 -0
  50. data/example/config/locales/en.yml +23 -0
  51. data/example/config/routes.rb +16 -0
  52. data/example/config/secrets.yml +22 -0
  53. data/example/db/migrate/20141122165914_create_users.rb +13 -0
  54. data/example/db/schema.rb +25 -0
  55. data/example/db/seeds.rb +7 -0
  56. data/example/lib/assets/.keep +0 -0
  57. data/example/lib/tasks/.keep +0 -0
  58. data/example/lib/tasks/user.rake +12 -0
  59. data/example/log/.keep +0 -0
  60. data/example/public/404.html +67 -0
  61. data/example/public/422.html +67 -0
  62. data/example/public/500.html +66 -0
  63. data/example/public/favicon.ico +0 -0
  64. data/example/public/robots.txt +5 -0
  65. data/example/vendor/assets/javascripts/.keep +0 -0
  66. data/example/vendor/assets/stylesheets/.keep +0 -0
  67. data/lib/generators/passwd/config_generator.rb +13 -0
  68. data/lib/generators/passwd/templates/passwd_config.rb +41 -0
  69. data/lib/passwd.rb +18 -3
  70. data/lib/passwd/action_controller_ext.rb +48 -0
  71. data/lib/passwd/active_record_ext.rb +65 -0
  72. data/lib/passwd/base.rb +17 -62
  73. data/lib/passwd/configuration.rb +82 -0
  74. data/lib/passwd/errors.rb +6 -13
  75. data/lib/passwd/password.rb +73 -25
  76. data/lib/passwd/policy.rb +28 -0
  77. data/lib/passwd/railtie.rb +19 -0
  78. data/lib/passwd/salt.rb +50 -0
  79. data/lib/passwd/version.rb +2 -1
  80. data/passwd.gemspec +8 -2
  81. data/spec/passwd/.keep +0 -0
  82. data/spec/passwd/active_record_ext_spec.rb +80 -0
  83. data/spec/passwd/base_spec.rb +55 -231
  84. data/spec/passwd/configuration_spec.rb +50 -0
  85. data/spec/passwd/password_spec.rb +129 -123
  86. data/spec/spec_helper.rb +14 -3
  87. data/spec/support/data_util.rb +11 -0
  88. data/spec/support/paths.rb +2 -0
  89. metadata +164 -30
  90. data/lib/passwd/active_record.rb +0 -62
  91. data/lib/passwd/configuration/abstract_config.rb +0 -37
  92. data/lib/passwd/configuration/config.rb +0 -24
  93. data/lib/passwd/configuration/policy.rb +0 -46
  94. data/lib/passwd/configuration/tmp_config.rb +0 -18
  95. data/spec/passwd/active_record_spec.rb +0 -163
  96. data/spec/passwd/configuration/config_spec.rb +0 -250
  97. data/spec/passwd/configuration/policy_spec.rb +0 -133
  98. data/spec/passwd/configuration/tmp_config_spec.rb +0 -265
File without changes
@@ -0,0 +1,5 @@
1
+ # See http://www.robotstxt.org/robotstxt.html for documentation on how to use the robots.txt file
2
+ #
3
+ # To ban all spiders from the entire site uncomment the next two lines:
4
+ # User-agent: *
5
+ # Disallow: /
@@ -0,0 +1,13 @@
1
+ module Passwd
2
+ module Generators
3
+ class ConfigGenerator < ::Rails::Generators::Base
4
+ source_root File.expand_path(File.join(File.dirname(__FILE__), "templates"))
5
+
6
+ desc "Create Passwd configuration file"
7
+ def create_configuration_file
8
+ template "passwd_config.rb", "config/initializers/passwd.rb"
9
+ end
10
+ end
11
+ end
12
+ end
13
+
@@ -0,0 +1,41 @@
1
+ Passwd.configure do |c|
2
+ # Password settings
3
+ # The following settings are all default values.
4
+
5
+ # Hashing algorithm
6
+ # Supported algorithm is :md5, :rmd160, :sha1, :sha256, :sha384 and :sha512
7
+ # c.algorithm = :sha512
8
+
9
+ # Random generate password length
10
+ # c.length = 8
11
+
12
+ # Number of hashed by stretching
13
+ # Not stretching if specified nil.
14
+ # c.stretching = nil
15
+
16
+ # Character type that is used for password
17
+ # c.lower = true
18
+ # c.upper = true
19
+ # c.number = true
20
+ end
21
+
22
+ Passwd.policy_configure do |c|
23
+ # Minimum password length
24
+ # c.min_length = 8
25
+
26
+ # Character types to force the use
27
+ # c.require_lower = true
28
+ # c.require_upper = false
29
+ # c.require_number = true
30
+ end
31
+
32
+ # Session key for authentication
33
+ Rails.application.config.passwd.session_key = :user_id
34
+
35
+ # Authentication Model Class
36
+ Rails.application.config.passwd.authenticate_class = :User
37
+
38
+ # Redirect path when not signin
39
+ # E.G. :signin_path # Do not specify ***_url
40
+ Rails.application.config.passwd.redirect_to = nil
41
+
@@ -1,10 +1,25 @@
1
- # coding: utf-8
2
-
3
1
  require "digest/sha1"
4
2
  require "digest/sha2"
5
3
 
6
4
  require "passwd/version"
7
5
  require "passwd/errors"
6
+ require "passwd/policy"
7
+ require "passwd/configuration"
8
8
  require "passwd/base"
9
+ require "passwd/salt"
9
10
  require "passwd/password"
10
- require "passwd/active_record"
11
+ require "passwd/railtie" if defined?(Rails)
12
+
13
+ module Passwd
14
+ extend Base
15
+ extend Configuration::Writable
16
+
17
+ def self.policy_check(plain)
18
+ Password.from_plain(plain).valid?
19
+ end
20
+
21
+ def self.match?(plain, salt_hash, hash)
22
+ Password.from_hash(hash, salt_hash).match?(plain)
23
+ end
24
+ end
25
+
@@ -0,0 +1,48 @@
1
+ module Passwd
2
+ module ActionControllerExt
3
+ extend ActiveSupport::Concern
4
+
5
+ included do
6
+ helper_method :current_user
7
+ end
8
+
9
+ def current_user
10
+ @current_user ||= auth_class.find_by(id: session[auth_key])
11
+ end
12
+
13
+ def signin!(user)
14
+ @current_user = user
15
+ session[auth_key] = user.id
16
+ end
17
+
18
+ def signout!
19
+ @current_user = session[auth_key] = nil
20
+ end
21
+
22
+ private
23
+ def auth_key
24
+ Rails.application.config.passwd.session_key || :user_id
25
+ end
26
+
27
+ def auth_class
28
+ @_auth_class ||=
29
+ (Rails.application.config.passwd.authenticate_class || :User).to_s.constantize
30
+ end
31
+
32
+ def _redirect_path
33
+ _to = Rails.application.config.passwd.redirect_to
34
+ _to ? Rails.application.routes.url_helpers.send(_to) : nil
35
+ end
36
+
37
+ def require_signin
38
+ unless current_user
39
+ if _redirect_path
40
+ redirect_to _redirect_path
41
+ else
42
+ raise UnauthorizedAccess
43
+ end
44
+ end
45
+ end
46
+ end
47
+ end
48
+
@@ -0,0 +1,65 @@
1
+ module Passwd
2
+ module ActiveRecordExt
3
+ def with_authenticate(options = {})
4
+ _id_key = options.fetch(:id, :email)
5
+ _salt_key = options.fetch(:salt, :salt)
6
+ _pass_key = options.fetch(:password, :password)
7
+
8
+ _define_passwd(_salt_key, _pass_key)
9
+ _define_singleton_auth(_id_key)
10
+ _define_instance_auth
11
+ _define_set_password(_salt_key, _pass_key)
12
+ _define_update_password(_salt_key, _pass_key)
13
+ end
14
+
15
+ private
16
+ def _define_passwd(_salt_key, _pass_key)
17
+ define_method :passwd do |cache = true|
18
+ return @_passwd if cache && @_passwd
19
+ self.reload unless self.new_record?
20
+ _salt, _pass = self.send(_salt_key), self.send(_pass_key)
21
+ if _salt.present? && _pass.present?
22
+ @_passwd = Passwd::Password.from_hash(_pass, _salt)
23
+ else
24
+ self.set_password
25
+ end
26
+ end
27
+ end
28
+
29
+ def _define_singleton_auth(_id_key)
30
+ define_singleton_method :authenticate do |_id, _pass|
31
+ _condition = Array(_id_key).map {|k| "#{k} = :id"}.join(" OR ")
32
+ _user = self.find_by(_condition, id: _id)
33
+ _user if _user && _user.passwd.match?(_pass)
34
+ end
35
+ end
36
+
37
+ def _define_instance_auth
38
+ define_method :authenticate do |_pass|
39
+ self.passwd.match?(_pass)
40
+ end
41
+ end
42
+
43
+ def _define_set_password(_salt_key, _pass_key)
44
+ define_method :set_password do |_pass = nil|
45
+ _options = _pass ? {plain: _pass} : {}
46
+ _passwd = Passwd::Password.new(_options)
47
+ self.send("#{_salt_key}=", _passwd.salt.hash)
48
+ self.send("#{_pass_key}=", _passwd.hash)
49
+ self.instance_variable_set(:@_passwd, _passwd)
50
+ end
51
+ end
52
+
53
+ def _define_update_password(_salt_key, _pass_key)
54
+ define_method :update_password do |_old, _new, _policy = false|
55
+ raise PolicyNotMatch if _policy && !Passwd.policy_check(_new)
56
+ if self.passwd.match?(_old)
57
+ self.set_password(_new)
58
+ else
59
+ raise AuthenticationFails
60
+ end
61
+ end
62
+ end
63
+ end
64
+ end
65
+
@@ -1,76 +1,31 @@
1
- # coding: utf-8
2
-
3
- require "singleton"
4
- require "passwd/configuration/config"
5
- require "passwd/configuration/tmp_config"
6
- require "passwd/configuration/policy"
7
-
8
1
  module Passwd
9
- @config = Config.instance
10
- @policy = Policy.instance
11
-
12
2
  module Base
13
- def create(options={})
14
- if options.empty?
15
- config = @config
16
- else
17
- config = TmpConfig.new(@config, options)
18
- end
19
- Array.new(config.length){config.letters[rand(config.letters.size)]}.join
20
- end
21
-
22
- def auth(password_text, salt_hash, password_hash)
23
- enc_pass = Passwd.hashing("#{salt_hash}#{password_text}")
24
- password_hash == enc_pass
25
- end
26
-
27
- def hashing(plain, algorithm=nil)
28
- if algorithm.nil?
29
- eval "Digest::#{@config.algorithm.to_s.upcase}.hexdigest \"#{plain}\""
30
- else
31
- eval "Digest::#{algorithm.to_s.upcase}.hexdigest \"#{plain}\""
32
- end
3
+ def random(options = {})
4
+ c = PwConfig.merge(options)
5
+ Array.new(c.length){c.letters[rand(c.letters.size)]}.join
33
6
  end
34
7
 
35
- def confirm_check(password, confirm, with_policy=false)
36
- raise PasswordNotMatch, "Password not match" if password != confirm
37
- return true unless with_policy
38
- Passwd.policy_check(password)
39
- end
8
+ def digest(plain, _algorithm = nil)
9
+ _algorithm ||= PwConfig.algorithm
40
10
 
41
- def configure(options={}, &block)
42
- if block_given?
43
- @config.configure &block
44
- else
45
- if options.empty?
46
- @config
47
- else
48
- @config.merge options
11
+ if PwConfig.stretching
12
+ _pass = plain
13
+ PwConfig.stretching.times do
14
+ _pass = digest_without_stretching(_pass, _algorithm)
49
15
  end
50
- end
51
- end
52
- alias :config :configure
53
-
54
- def policy_configure(&block)
55
- if block_given?
56
- @policy.configure &block
57
16
  else
58
- @policy
17
+ digest_without_stretching(plain, _algorithm)
59
18
  end
60
19
  end
61
20
 
62
- def policy_check(password)
63
- @policy.valid?(password, @config)
21
+ def digest_without_stretching(plain, _algorithm = nil)
22
+ algorithm(_algorithm || PwConfig.algorithm).hexdigest(plain)
64
23
  end
65
24
 
66
- def reset_config
67
- @config.reset
68
- end
69
-
70
- def reset_policy
71
- @policy.reset
72
- end
25
+ private
26
+ def algorithm(_algorithm)
27
+ Digest.const_get(_algorithm.upcase, false)
28
+ end
73
29
  end
74
-
75
- extend Base
76
30
  end
31
+
@@ -0,0 +1,82 @@
1
+ module Passwd
2
+ class Configuration
3
+ KINDS = %i(lower upper number).freeze
4
+ LETTERS = KINDS.map {|k| "letters_#{k}".to_sym}.freeze
5
+
6
+ VALID_OPTIONS = [
7
+ :algorithm,
8
+ :length,
9
+ :policy,
10
+ :stretching,
11
+ ].concat(KINDS).concat(LETTERS).freeze
12
+
13
+ attr_accessor *VALID_OPTIONS
14
+
15
+ def initialize
16
+ reset
17
+ end
18
+
19
+ def configure
20
+ yield self
21
+ end
22
+
23
+ def merge(params)
24
+ self.dup.merge!(params)
25
+ end
26
+
27
+ def merge!(params)
28
+ params.keys.each do |key|
29
+ self.send("#{key}=", params[key])
30
+ end
31
+ self
32
+ end
33
+
34
+ KINDS.each do |kind|
35
+ define_method "#{kind}_chars" do
36
+ self.send("letters_#{kind}") || []
37
+ end
38
+ end
39
+
40
+ def letters
41
+ KINDS.detect {|k| self.send(k)} || (raise ConfigError, "letters is empry.")
42
+ LETTERS.zip(KINDS).map {|l, k|
43
+ self.send(l) if self.send(k)
44
+ }.compact.flatten
45
+ end
46
+
47
+ def reset
48
+ self.algorithm = :sha512
49
+ self.length = 8
50
+ self.policy = Policy.new
51
+ self.stretching = nil
52
+ self.lower = true
53
+ self.upper = true
54
+ self.number = true
55
+ self.letters_lower = ("a".."z").to_a
56
+ self.letters_upper = ("A".."Z").to_a
57
+ self.letters_number = ("0".."9").to_a
58
+ end
59
+
60
+ module Writable
61
+ def self.extended(base)
62
+ base.send(:include, Accessible)
63
+ end
64
+
65
+ def configure(options = {}, &block)
66
+ PwConfig.merge!(options) unless options.empty?
67
+ PwConfig.configure(&block) if block_given?
68
+ end
69
+
70
+ def policy_configure(&block)
71
+ PwConfig.policy.configure(&block) if block_given?
72
+ end
73
+ end
74
+
75
+ module Accessible
76
+ def self.included(base)
77
+ base.const_set(:PwConfig, Configuration.new)
78
+ end
79
+ end
80
+ end
81
+ end
82
+
@@ -1,15 +1,8 @@
1
- # coding: utf-8
2
-
3
1
  module Passwd
4
- class PasswdError < StandardError
5
- end
6
-
7
- class AuthError < PasswdError
8
- end
9
-
10
- class PasswordNotMatch < PasswdError
11
- end
12
-
13
- class PolicyNotMatch < PasswdError
14
- end
2
+ class PasswdError < StandardError; end
3
+ class UnauthorizedAccess < PasswdError; end
4
+ class PolicyNotMatch < PasswdError; end
5
+ class AuthenticationFails < PasswdError; end
6
+ class ConfigError < PasswdError; end
15
7
  end
8
+
@@ -1,41 +1,89 @@
1
- # coding: utf-8
2
-
3
1
  module Passwd
4
2
  class Password
5
- attr_reader :text, :hash, :salt_text, :salt_hash
3
+ include Base
4
+
5
+ attr_reader :plain, :hash, :salt
6
+
7
+ def initialize(options = {})
8
+ options = default_options.merge(options)
9
+
10
+ if options.has_key?(:hash)
11
+ raise ArgumentError unless options.has_key?(:salt_hash)
12
+ @salt = Salt.from_hash(options[:salt_hash], self)
13
+ @hash = options[:hash]
14
+ else
15
+ @salt =
16
+ case
17
+ when options.has_key?(:salt_hash)
18
+ Salt.from_hash(options[:salt_hash], self)
19
+ when options.has_key?(:salt_plain)
20
+ Salt.from_plain(options[:salt_plain], self)
21
+ else
22
+ Salt.new(password: self)
23
+ end
24
+ self.update_plain(options[:plain])
25
+ end
26
+ end
27
+
28
+ def update_plain(value)
29
+ @plain = value
30
+ rehash
31
+ end
32
+
33
+ def update_hash(value, salt_hash)
34
+ @plain = nil
35
+ @hash = value
36
+ self.salt.update_hash(salt_hash)
37
+ end
38
+
39
+ def rehash
40
+ raise PasswdError unless self.plain
41
+ @hash = digest([self.salt.hash, self.plain].join)
42
+ end
6
43
 
7
- def initialize(options={})
8
- @text = options.fetch(:password, Passwd.create)
9
- @salt_text = options.fetch(:salt_text, Time.now.to_s)
10
- @salt_hash = Passwd.hashing(@salt_text)
11
- @hash = Passwd.hashing("#{@salt_hash}#{@text}")
44
+ def match?(value)
45
+ self.hash == digest([self.salt.hash, value].join)
12
46
  end
13
47
 
14
- def text=(password)
15
- @hash = Passwd.hashing("#{@salt_hash}#{password}")
16
- @text = password
48
+ def ==(other)
49
+ match?(other)
17
50
  end
18
51
 
19
- def hash=(password_hash)
20
- @text = nil
21
- @hash = password_hash
52
+ def to_s
53
+ self.plain.to_s
22
54
  end
23
55
 
24
- def salt_text=(salt_text)
25
- @salt_hash = Passwd.hashing(salt_text)
26
- @hash = Passwd.hashing("#{@salt_hash}#{@text}")
27
- @salt_text = salt_text
56
+ def valid?
57
+ raise PasswdError unless self.plain
58
+
59
+ return false if PwConfig.policy.min_length > self.plain.size
60
+
61
+ Configuration::KINDS.each do |key|
62
+ if PwConfig.policy.send("require_#{key}")
63
+ return false unless include_char?(PwConfig.send("letters_#{key}"))
64
+ end
65
+ end
66
+ true
28
67
  end
29
68
 
30
- def salt_hash=(salt_hash)
31
- @salt_text = nil
32
- @hash = Passwd.hashing("#{salt_hash}#{@text}")
33
- @salt_hash = salt_hash
69
+ def self.from_plain(value, options = {})
70
+ new(options.merge(plain: value))
34
71
  end
35
72
 
36
- def ==(password)
37
- enc_pass = Passwd.hashing("#{@salt_hash}#{password}")
38
- @hash == enc_pass
73
+ def self.from_hash(value, salt_hash)
74
+ new(hash: value, salt_hash: salt_hash)
39
75
  end
76
+
77
+ private
78
+ def default_options
79
+ {plain: random}
80
+ end
81
+
82
+ def include_char?(letters)
83
+ raise PasswdError unless self.plain
84
+ self.plain.chars.uniq.each {|c| return true if letters.include?(c)}
85
+ false
86
+ end
40
87
  end
41
88
  end
89
+