passwd 0.1.5 → 0.2.0

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.
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
+