htauth 2.3.0 → 3.0.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.
- checksums.yaml +4 -4
- data/HISTORY.md +14 -0
- data/Manifest.txt +4 -28
- data/exe/htdigest-ruby +14 -0
- data/exe/htpasswd-ruby +14 -0
- data/htauth.gemspec +33 -0
- data/lib/htauth/algorithm.rb +30 -29
- data/lib/htauth/argon2.rb +45 -36
- data/lib/htauth/bcrypt.rb +12 -11
- data/lib/htauth/cli/digest.rb +42 -46
- data/lib/htauth/cli/passwd.rb +126 -115
- data/lib/htauth/cli.rb +5 -3
- data/lib/htauth/console.rb +9 -6
- data/lib/htauth/crypt.rb +11 -9
- data/lib/htauth/descendant_tracker.rb +11 -9
- data/lib/htauth/digest_entry.rb +22 -20
- data/lib/htauth/digest_file.rb +25 -18
- data/lib/htauth/entry.rb +3 -1
- data/lib/htauth/error.rb +6 -5
- data/lib/htauth/file.rb +35 -39
- data/lib/htauth/md5.rb +35 -34
- data/lib/htauth/passwd_entry.rb +26 -24
- data/lib/htauth/passwd_file.rb +26 -21
- data/lib/htauth/plaintext.rb +7 -5
- data/lib/htauth/sha1.rb +9 -7
- data/lib/htauth/version.rb +3 -1
- data/lib/htauth.rb +29 -28
- metadata +15 -133
- data/Rakefile +0 -29
- data/bin/htdigest-ruby +0 -12
- data/bin/htpasswd-ruby +0 -12
- data/spec/algorithm_spec.rb +0 -7
- data/spec/argon2_spec.rb +0 -28
- data/spec/bcrypt_spec.rb +0 -32
- data/spec/cli/digest_spec.rb +0 -149
- data/spec/cli/passwd_spec.rb +0 -346
- data/spec/crypt_spec.rb +0 -11
- data/spec/digest_entry_spec.rb +0 -59
- data/spec/digest_file_spec.rb +0 -64
- data/spec/md5_spec.rb +0 -11
- data/spec/passwd_entry_spec.rb +0 -172
- data/spec/passwd_file_spec.rb +0 -84
- data/spec/plaintext_spec.rb +0 -11
- data/spec/sha1_spec.rb +0 -10
- data/spec/spec_helper.rb +0 -25
- data/spec/test.add.digest +0 -3
- data/spec/test.add.passwd +0 -3
- data/spec/test.delete.digest +0 -1
- data/spec/test.delete.passwd +0 -1
- data/spec/test.original.digest +0 -2
- data/spec/test.original.passwd +0 -2
- data/spec/test.update.digest +0 -2
- data/spec/test.update.passwd +0 -2
- data/tasks/default.rake +0 -250
- data/tasks/this.rb +0 -208
- /data/{LICENSE → LICENSE.txt} +0 -0
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: bf3dca478538f6a659cfe1cbe39b0688782e34f606cba0b116a63a87c42f022e
|
|
4
|
+
data.tar.gz: 83508be980a643ec2a481582ecc8ad02f3f8271b2d34359ab26bad2369d4eddd
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 48c8f83ca6e2c893731098c076680c37eeb6ff8fe1bde0589957c56239f8239cdb2e8fad01ea2278c6c10f25879e9adc4ba43385f6250435c08207d7b9381a87
|
|
7
|
+
data.tar.gz: a059b27747b6ca12201171500c8f08672b2b378d4e80c83ed5caa9725e009f0366470607e756c02905c253f9c2c4dd173aa58760c7978238a3862c85b038b431
|
data/HISTORY.md
CHANGED
|
@@ -1,4 +1,18 @@
|
|
|
1
1
|
# Changelog
|
|
2
|
+
## Version 3.0.0 - 2026-05-23
|
|
3
|
+
|
|
4
|
+
* Modernize project structure
|
|
5
|
+
* Bump minimum Ruby version from 2.3.0 to 3.0.0
|
|
6
|
+
* Add rubocop for code linting with multiple plugins
|
|
7
|
+
* Rename `is_entry?`/`is_entry_for?` methods to `entry?`/`entry_for?` (Ruby naming conventions)
|
|
8
|
+
* Update CI build matrix: Ruby 3.3, 3.4, 4.0, JRuby 10.1, TruffleRuby 34.0
|
|
9
|
+
* Remove Ruby 3.2 from build matrix
|
|
10
|
+
* Move development dependencies from gemspec to Gemfile
|
|
11
|
+
* Add `ostruct` as a runtime dependency
|
|
12
|
+
* Remove test files from gem package
|
|
13
|
+
* Rename `LICENSE` to `LICENSE.txt`
|
|
14
|
+
* Apply rubocop autocorrections across all source and spec files
|
|
15
|
+
|
|
2
16
|
## Version 2.3.0 - 2024-02-03
|
|
3
17
|
|
|
4
18
|
* Add support for argon2 encryption [#18](https://github.com/copiousfreetime/htauth/pull/18)
|
data/Manifest.txt
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
CONTRIBUTING.md
|
|
2
2
|
HISTORY.md
|
|
3
|
-
LICENSE
|
|
3
|
+
LICENSE.txt
|
|
4
4
|
Manifest.txt
|
|
5
5
|
README.md
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
6
|
+
exe/htdigest-ruby
|
|
7
|
+
exe/htpasswd-ruby
|
|
8
|
+
htauth.gemspec
|
|
9
9
|
lib/htauth.rb
|
|
10
10
|
lib/htauth/algorithm.rb
|
|
11
11
|
lib/htauth/argon2.rb
|
|
@@ -27,27 +27,3 @@ lib/htauth/passwd_file.rb
|
|
|
27
27
|
lib/htauth/plaintext.rb
|
|
28
28
|
lib/htauth/sha1.rb
|
|
29
29
|
lib/htauth/version.rb
|
|
30
|
-
spec/algorithm_spec.rb
|
|
31
|
-
spec/argon2_spec.rb
|
|
32
|
-
spec/bcrypt_spec.rb
|
|
33
|
-
spec/cli/digest_spec.rb
|
|
34
|
-
spec/cli/passwd_spec.rb
|
|
35
|
-
spec/crypt_spec.rb
|
|
36
|
-
spec/digest_entry_spec.rb
|
|
37
|
-
spec/digest_file_spec.rb
|
|
38
|
-
spec/md5_spec.rb
|
|
39
|
-
spec/passwd_entry_spec.rb
|
|
40
|
-
spec/passwd_file_spec.rb
|
|
41
|
-
spec/plaintext_spec.rb
|
|
42
|
-
spec/sha1_spec.rb
|
|
43
|
-
spec/spec_helper.rb
|
|
44
|
-
spec/test.add.digest
|
|
45
|
-
spec/test.add.passwd
|
|
46
|
-
spec/test.delete.digest
|
|
47
|
-
spec/test.delete.passwd
|
|
48
|
-
spec/test.original.digest
|
|
49
|
-
spec/test.original.passwd
|
|
50
|
-
spec/test.update.digest
|
|
51
|
-
spec/test.update.passwd
|
|
52
|
-
tasks/default.rake
|
|
53
|
-
tasks/this.rb
|
data/exe/htdigest-ruby
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
#!/usr/bin/env ruby
|
|
2
|
+
# frozen_string_literal: true
|
|
3
|
+
|
|
4
|
+
begin
|
|
5
|
+
require "htauth/cli/digest"
|
|
6
|
+
rescue LoadError
|
|
7
|
+
path = File.expand_path(File.join(File.dirname(__FILE__), "..", "lib"))
|
|
8
|
+
raise if $LOAD_PATH.include?(path)
|
|
9
|
+
|
|
10
|
+
$LOAD_PATH << path
|
|
11
|
+
retry
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
HTAuth::CLI::Digest.new.run(ARGV, ENV)
|
data/exe/htpasswd-ruby
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
#!/usr/bin/env ruby
|
|
2
|
+
# frozen_string_literal: true
|
|
3
|
+
|
|
4
|
+
begin
|
|
5
|
+
require "htauth/cli/passwd"
|
|
6
|
+
rescue LoadError
|
|
7
|
+
path = File.expand_path(File.join(File.dirname(__FILE__), "..", "lib"))
|
|
8
|
+
raise if $LOAD_PATH.include?(path)
|
|
9
|
+
|
|
10
|
+
$LOAD_PATH << path
|
|
11
|
+
retry
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
HTAuth::CLI::Passwd.new.run(ARGV, ENV)
|
data/htauth.gemspec
ADDED
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
# DO NOT EDIT - This file is automatically generated
|
|
2
|
+
# Make changes to Manifest.txt and/or Rakefile and regenerate
|
|
3
|
+
# -*- encoding: utf-8 -*-
|
|
4
|
+
# stub: htauth 3.0.0 ruby lib
|
|
5
|
+
|
|
6
|
+
Gem::Specification.new do |s|
|
|
7
|
+
s.name = "htauth".freeze
|
|
8
|
+
s.version = "3.0.0".freeze
|
|
9
|
+
|
|
10
|
+
s.required_rubygems_version = Gem::Requirement.new(">= 0".freeze) if s.respond_to? :required_rubygems_version=
|
|
11
|
+
s.metadata = { "bug_tracker_uri" => "https://github.com/copiousfreetime/htauth/issues", "changelog_uri" => "https://github.com/copiousfreetime/htauth/blob/master/HISTORY.md", "homepage_uri" => "https://github.com/copiousfreetime/htauth", "source_code_uri" => "https://github.com/copiousfreetime/htauth" } if s.respond_to? :metadata=
|
|
12
|
+
s.require_paths = ["lib".freeze]
|
|
13
|
+
s.authors = ["Jeremy Hinegardner".freeze]
|
|
14
|
+
s.bindir = "exe".freeze
|
|
15
|
+
s.date = "2026-05-23"
|
|
16
|
+
s.description = "HTAuth provides an API and commandline tools for managing Apache/httpd style htpasswd and htdigest files.".freeze
|
|
17
|
+
s.email = "jeremy@copiousfreetime.org".freeze
|
|
18
|
+
s.executables = ["htdigest-ruby".freeze, "htpasswd-ruby".freeze]
|
|
19
|
+
s.extra_rdoc_files = ["CONTRIBUTING.md".freeze, "HISTORY.md".freeze, "LICENSE.txt".freeze, "Manifest.txt".freeze, "README.md".freeze]
|
|
20
|
+
s.files = ["CONTRIBUTING.md".freeze, "HISTORY.md".freeze, "LICENSE.txt".freeze, "Manifest.txt".freeze, "README.md".freeze, "exe/htdigest-ruby".freeze, "exe/htpasswd-ruby".freeze, "htauth.gemspec".freeze, "lib/htauth.rb".freeze, "lib/htauth/algorithm.rb".freeze, "lib/htauth/argon2.rb".freeze, "lib/htauth/bcrypt.rb".freeze, "lib/htauth/cli.rb".freeze, "lib/htauth/cli/digest.rb".freeze, "lib/htauth/cli/passwd.rb".freeze, "lib/htauth/console.rb".freeze, "lib/htauth/crypt.rb".freeze, "lib/htauth/descendant_tracker.rb".freeze, "lib/htauth/digest_entry.rb".freeze, "lib/htauth/digest_file.rb".freeze, "lib/htauth/entry.rb".freeze, "lib/htauth/error.rb".freeze, "lib/htauth/file.rb".freeze, "lib/htauth/md5.rb".freeze, "lib/htauth/passwd_entry.rb".freeze, "lib/htauth/passwd_file.rb".freeze, "lib/htauth/plaintext.rb".freeze, "lib/htauth/sha1.rb".freeze, "lib/htauth/version.rb".freeze]
|
|
21
|
+
s.homepage = "http://github.com/copiousfreetime/htauth".freeze
|
|
22
|
+
s.licenses = ["MIT".freeze]
|
|
23
|
+
s.rdoc_options = ["--main".freeze, "README.md".freeze, "--markup".freeze, "tomdoc".freeze]
|
|
24
|
+
s.required_ruby_version = Gem::Requirement.new(">= 3.0.0".freeze)
|
|
25
|
+
s.rubygems_version = "4.0.10".freeze
|
|
26
|
+
s.summary = "HTAuth provides an API and commandline tools for managing Apache/httpd style htpasswd and htdigest files.".freeze
|
|
27
|
+
|
|
28
|
+
s.specification_version = 4
|
|
29
|
+
|
|
30
|
+
s.add_runtime_dependency(%q<bcrypt>.freeze, ["~> 3.1".freeze])
|
|
31
|
+
s.add_runtime_dependency(%q<base64>.freeze, ["~> 0.2".freeze])
|
|
32
|
+
s.add_runtime_dependency(%q<ostruct>.freeze, ["~> 0.6".freeze])
|
|
33
|
+
end
|
data/lib/htauth/algorithm.rb
CHANGED
|
@@ -1,50 +1,50 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
require
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "htauth/error"
|
|
4
|
+
require "htauth/descendant_tracker"
|
|
5
|
+
require "securerandom"
|
|
4
6
|
module HTAuth
|
|
5
7
|
class InvalidAlgorithmError < Error; end
|
|
6
8
|
|
|
7
9
|
# Internal: Base class all the password algorithms derive from
|
|
8
10
|
#
|
|
9
11
|
class Algorithm
|
|
10
|
-
|
|
11
12
|
extend DescendantTracker
|
|
12
13
|
|
|
13
|
-
SALT_CHARS = (%w[
|
|
14
|
+
SALT_CHARS = (%w[. /] + ("0".."9").to_a + ("A".."Z").to_a + ("a".."z").to_a).freeze
|
|
14
15
|
SALT_LENGTH = 8
|
|
15
16
|
|
|
16
17
|
# Public: flag for the argon2 algorithm
|
|
17
|
-
ARGON2 = "argon2"
|
|
18
|
+
ARGON2 = "argon2"
|
|
18
19
|
# Public: flag for the bcrypt algorithm
|
|
19
|
-
BCRYPT = "bcrypt"
|
|
20
|
+
BCRYPT = "bcrypt"
|
|
20
21
|
# Public: flag for the md5 algorithm
|
|
21
|
-
MD5 = "md5"
|
|
22
|
+
MD5 = "md5"
|
|
22
23
|
# Public: flag for the sha1 algorithm
|
|
23
|
-
SHA1 = "sha1"
|
|
24
|
+
SHA1 = "sha1"
|
|
24
25
|
# Public: flag for the plaintext algorithm
|
|
25
|
-
PLAINTEXT = "plaintext"
|
|
26
|
+
PLAINTEXT = "plaintext"
|
|
26
27
|
# Public: flag for the crypt algorithm
|
|
27
|
-
CRYPT = "crypt"
|
|
28
|
+
CRYPT = "crypt"
|
|
28
29
|
|
|
29
30
|
# Public: flag for the default algorithm
|
|
30
31
|
DEFAULT = MD5
|
|
31
32
|
|
|
32
33
|
# Public: flag to indicate using the existing algorithm of the entry
|
|
33
|
-
EXISTING = "existing"
|
|
34
|
-
|
|
34
|
+
EXISTING = "existing"
|
|
35
35
|
|
|
36
36
|
class << self
|
|
37
37
|
def algorithm_name
|
|
38
|
-
|
|
38
|
+
name.split("::").last.downcase
|
|
39
39
|
end
|
|
40
40
|
|
|
41
41
|
def algorithm_from_name(a_name, params = {})
|
|
42
42
|
found = children.find { |c| c.algorithm_name == a_name }
|
|
43
|
-
|
|
44
|
-
names = children.map
|
|
43
|
+
unless found
|
|
44
|
+
names = children.map(&:algorithm_name)
|
|
45
45
|
raise InvalidAlgorithmError, "`#{a_name}' is an unknown encryption algorithm, use one of #{names.join(', ')}"
|
|
46
46
|
end
|
|
47
|
-
|
|
47
|
+
found.new(params)
|
|
48
48
|
end
|
|
49
49
|
|
|
50
50
|
# NOTE: if it is plaintext, and the length is 13 - it may matched crypt
|
|
@@ -57,13 +57,13 @@ module HTAuth
|
|
|
57
57
|
|
|
58
58
|
raise InvalidAlgorithmError, "unknown encryption algorithm used for `#{password_field}`" if match.nil?
|
|
59
59
|
|
|
60
|
-
|
|
60
|
+
match.new(existing: password_field)
|
|
61
61
|
end
|
|
62
62
|
|
|
63
63
|
# Internal: Does this class handle this type of password entry
|
|
64
64
|
#
|
|
65
65
|
def handles?(password_entry)
|
|
66
|
-
raise NotImplementedError, "#{
|
|
66
|
+
raise NotImplementedError, "#{name} must implement #{name}.handles?(password_entry)"
|
|
67
67
|
end
|
|
68
68
|
|
|
69
69
|
# Internal: Constant time string comparison.
|
|
@@ -74,14 +74,15 @@ module HTAuth
|
|
|
74
74
|
# that have already been processed by HMAC. This should not be used
|
|
75
75
|
# on variable length plaintext strings because it could leak length info
|
|
76
76
|
# via timing attacks.
|
|
77
|
-
def secure_compare(
|
|
78
|
-
return false unless
|
|
77
|
+
def secure_compare(lhs, rhs)
|
|
78
|
+
return false unless lhs.bytesize == rhs.bytesize
|
|
79
79
|
|
|
80
|
-
l =
|
|
80
|
+
l = lhs.unpack("C*")
|
|
81
81
|
|
|
82
|
-
r
|
|
83
|
-
|
|
84
|
-
r
|
|
82
|
+
r = 0
|
|
83
|
+
i = -1
|
|
84
|
+
rhs.each_byte { |v| r |= v ^ l[i += 1] }
|
|
85
|
+
r.zero?
|
|
85
86
|
end
|
|
86
87
|
end
|
|
87
88
|
|
|
@@ -100,19 +101,19 @@ module HTAuth
|
|
|
100
101
|
|
|
101
102
|
# Internal: 8 bytes of random items from SALT_CHARS
|
|
102
103
|
def gen_salt(length = SALT_LENGTH)
|
|
103
|
-
Array.new(length) { SALT_CHARS.sample }.join
|
|
104
|
+
Array.new(length) { SALT_CHARS.sample }.join
|
|
104
105
|
end
|
|
105
106
|
|
|
106
107
|
# Internal: this is not the Base64 encoding, this is the to64()
|
|
107
108
|
# method from the Apache Portable Runtime (APR) library
|
|
108
109
|
# https://github.com/apache/apr/blob/trunk/crypto/apr_md5.c#L493-L502
|
|
109
|
-
def
|
|
110
|
+
def to64(number, rounds)
|
|
110
111
|
r = StringIO.new
|
|
111
|
-
rounds.times do |
|
|
112
|
+
rounds.times do |_x|
|
|
112
113
|
r.print(SALT_CHARS[number % 64])
|
|
113
114
|
number >>= 6
|
|
114
115
|
end
|
|
115
|
-
|
|
116
|
+
r.string
|
|
116
117
|
end
|
|
117
118
|
end
|
|
118
119
|
end
|
data/lib/htauth/argon2.rb
CHANGED
|
@@ -1,18 +1,25 @@
|
|
|
1
|
-
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "htauth/algorithm"
|
|
2
4
|
begin
|
|
3
|
-
require
|
|
5
|
+
require "argon2"
|
|
4
6
|
rescue LoadError
|
|
5
7
|
end
|
|
6
8
|
|
|
7
9
|
module HTAuth
|
|
8
10
|
# Internal: Support of the argon2id algorithm and password format.
|
|
9
|
-
|
|
10
11
|
class Argon2 < Algorithm
|
|
12
|
+
# Internal: Error class when argon2 is specified on windows
|
|
11
13
|
class NotSupportedError < ::HTAuth::InvalidAlgorithmError
|
|
12
14
|
def message
|
|
13
|
-
|
|
15
|
+
[
|
|
16
|
+
"Unfortunately Argon2 passwords are not supported on `#{RUBY_PLATFORM} at this time.",
|
|
17
|
+
"This because the upstream argon2 gem does not support windows.",
|
|
18
|
+
].join(" ")
|
|
14
19
|
end
|
|
15
20
|
end
|
|
21
|
+
|
|
22
|
+
# Internal: Error class when argon2 is not installed
|
|
16
23
|
class NotInstalledError < ::HTAuth::InvalidAlgorithmError
|
|
17
24
|
def message
|
|
18
25
|
"Argon2 passwords are supported if the `argon2' gem is installed. Add `gem 'argon2', '~> 2.3'` to your Gemfile"
|
|
@@ -21,7 +28,7 @@ module HTAuth
|
|
|
21
28
|
|
|
22
29
|
# from upstream, used to help make a nice error message if its not installed
|
|
23
30
|
# https://github.com/technion/ruby-argon2/blob/3388d7e05e8b486ea4ba8bd2aeb1e9988f025f13/lib/argon2/hash_format.rb#L45
|
|
24
|
-
PREFIX = /^\$argon2(id?|d).{,113}
|
|
31
|
+
PREFIX = /^\$argon2(id?|d).{,113}/
|
|
25
32
|
ARGON2_GEM_INSTALLED = defined?(::Argon2)
|
|
26
33
|
|
|
27
34
|
def self.supported?
|
|
@@ -35,40 +42,42 @@ module HTAuth
|
|
|
35
42
|
|
|
36
43
|
attr_accessor :options
|
|
37
44
|
|
|
38
|
-
|
|
45
|
+
def self.handles?(password_entry)
|
|
39
46
|
return false unless PREFIX.match?(password_entry)
|
|
47
|
+
|
|
40
48
|
ensure_available!
|
|
41
49
|
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
50
|
+
::Argon2::Password.valid_hash?(password_entry)
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
def self.extract_options_from_existing_password_field(existing)
|
|
54
|
+
hash_format = ::Argon2::HashFormat.new(existing)
|
|
55
|
+
|
|
56
|
+
# m_cost on the input is the 2**m_cost, but in the hash its the number of
|
|
57
|
+
# bytes, so need to convert it back to a power of 2, which is the
|
|
58
|
+
# log2(m_cost)
|
|
59
|
+
|
|
60
|
+
{
|
|
61
|
+
t_cost: hash_format.t_cost,
|
|
62
|
+
m_cost: ::Math.log2(hash_format.m_cost).floor,
|
|
63
|
+
p_cost: hash_format.p_cost,
|
|
64
|
+
}
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
def initialize(params = { profile: :rfc_9106_low_memory })
|
|
68
|
+
super()
|
|
69
|
+
self.class.ensure_available!
|
|
70
|
+
@options = if (existing = params["existing"] || params[:existing])
|
|
71
|
+
self.class.extract_options_from_existing_password_field(existing)
|
|
72
|
+
else
|
|
73
|
+
params
|
|
74
|
+
end
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
def encode(password)
|
|
78
|
+
argon2 = ::Argon2::Password.new(options)
|
|
79
|
+
argon2.create(password)
|
|
80
|
+
end
|
|
72
81
|
|
|
73
82
|
def verify_password?(password, digest)
|
|
74
83
|
::Argon2::Password.verify_password(password, digest)
|
data/lib/htauth/bcrypt.rb
CHANGED
|
@@ -1,18 +1,18 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "htauth/algorithm"
|
|
4
|
+
require "bcrypt"
|
|
3
5
|
|
|
4
6
|
module HTAuth
|
|
5
7
|
# Internal: an implementation of the Bcrypt based encoding algorithm
|
|
6
8
|
# as used in the apache htpasswd -B option
|
|
7
|
-
|
|
8
9
|
class Bcrypt < Algorithm
|
|
9
|
-
|
|
10
10
|
attr_accessor :cost
|
|
11
11
|
|
|
12
12
|
DEFAULT_APACHE_COST = 5 # this is the default cost from htpasswd
|
|
13
13
|
|
|
14
14
|
def self.handles?(password_entry)
|
|
15
|
-
|
|
15
|
+
::BCrypt::Password.valid_hash?(password_entry)
|
|
16
16
|
end
|
|
17
17
|
|
|
18
18
|
def self.extract_cost_from_existing_password_field(existing)
|
|
@@ -21,15 +21,16 @@ module HTAuth
|
|
|
21
21
|
end
|
|
22
22
|
|
|
23
23
|
def initialize(params = {})
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
24
|
+
super()
|
|
25
|
+
@cost = if (existing = params["existing"] || params[:existing])
|
|
26
|
+
self.class.extract_cost_from_existing_password_field(existing)
|
|
27
|
+
else
|
|
28
|
+
params["cost"] || params[:cost] || DEFAULT_APACHE_COST
|
|
29
|
+
end
|
|
29
30
|
end
|
|
30
31
|
|
|
31
32
|
def encode(password)
|
|
32
|
-
::BCrypt::Password.create(password, :
|
|
33
|
+
::BCrypt::Password.create(password, cost: cost)
|
|
33
34
|
end
|
|
34
35
|
|
|
35
36
|
def verify_password?(password, digest)
|
data/lib/htauth/cli/digest.rb
CHANGED
|
@@ -1,13 +1,14 @@
|
|
|
1
|
-
|
|
1
|
+
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
require
|
|
4
|
-
|
|
3
|
+
require "htauth/cli"
|
|
4
|
+
|
|
5
|
+
require "ostruct"
|
|
6
|
+
require "optparse"
|
|
5
7
|
|
|
6
8
|
module HTAuth
|
|
7
9
|
module CLI
|
|
8
10
|
# Internal: Implemenation of the commandline htdigest-ruby
|
|
9
11
|
class Digest
|
|
10
|
-
|
|
11
12
|
MAX_PASSWD_LENGTH = 255
|
|
12
13
|
|
|
13
14
|
attr_accessor :digest_file
|
|
@@ -19,7 +20,7 @@ module HTAuth
|
|
|
19
20
|
end
|
|
20
21
|
|
|
21
22
|
def options
|
|
22
|
-
if @options.nil?
|
|
23
|
+
if @options.nil?
|
|
23
24
|
@options = ::OpenStruct.new
|
|
24
25
|
@options.show_version = false
|
|
25
26
|
@options.show_help = false
|
|
@@ -33,24 +34,22 @@ module HTAuth
|
|
|
33
34
|
end
|
|
34
35
|
|
|
35
36
|
def option_parser
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
options.show_version = v
|
|
53
|
-
end
|
|
37
|
+
@option_parser ||= OptionParser.new(nil, 14) do |op|
|
|
38
|
+
op.banner = "Usage: #{op.program_name} [options] passwordfile realm username"
|
|
39
|
+
op.on("-c", "--create", "Create a new digest password file; this overwrites an existing file.") do |_c|
|
|
40
|
+
options.file_mode = DigestFile::CREATE
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
op.on("-D", "--delete", "Delete the specified user.") do |d|
|
|
44
|
+
options.delete_entry = d
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
op.on("-h", "--help", "Display this help.") do |h|
|
|
48
|
+
options.show_help = h
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
op.on("-v", "--version", "Show version info.") do |v|
|
|
52
|
+
options.show_version = v
|
|
54
53
|
end
|
|
55
54
|
end
|
|
56
55
|
@option_parser
|
|
@@ -67,27 +66,25 @@ module HTAuth
|
|
|
67
66
|
end
|
|
68
67
|
|
|
69
68
|
def parse_options(argv)
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
exit 1
|
|
82
|
-
end
|
|
69
|
+
option_parser.parse!(argv)
|
|
70
|
+
show_version if options.show_version
|
|
71
|
+
show_help if options.show_help || (argv.size < 3)
|
|
72
|
+
|
|
73
|
+
options.passwdfile = argv.shift
|
|
74
|
+
options.realm = argv.shift
|
|
75
|
+
options.username = argv.shift
|
|
76
|
+
rescue ::OptionParser::ParseError => e
|
|
77
|
+
warn "ERROR: #{option_parser.program_name} - #{e}"
|
|
78
|
+
warn "Try `#{option_parser.program_name} --help` for more information"
|
|
79
|
+
exit 1
|
|
83
80
|
end
|
|
84
81
|
|
|
85
|
-
def run(argv,
|
|
82
|
+
def run(argv, _env = ENV)
|
|
86
83
|
begin
|
|
87
84
|
parse_options(argv)
|
|
88
85
|
digest_file = DigestFile.new(options.passwdfile, options.file_mode)
|
|
89
86
|
|
|
90
|
-
if options.delete_entry
|
|
87
|
+
if options.delete_entry
|
|
91
88
|
digest_file.delete(options.username, options.realm)
|
|
92
89
|
else
|
|
93
90
|
console = Console.new
|
|
@@ -106,18 +103,17 @@ module HTAuth
|
|
|
106
103
|
end
|
|
107
104
|
|
|
108
105
|
digest_file.save!
|
|
109
|
-
|
|
110
|
-
rescue HTAuth::FileAccessError => fae
|
|
106
|
+
rescue HTAuth::FileAccessError => e
|
|
111
107
|
msg = "Could not open password file #{options.passwdfile} "
|
|
112
|
-
|
|
113
|
-
|
|
108
|
+
warn "#{msg}: #{e.message}"
|
|
109
|
+
warn e.backtrace.join("\n")
|
|
114
110
|
exit 1
|
|
115
|
-
rescue HTAuth::Error =>
|
|
116
|
-
|
|
111
|
+
rescue HTAuth::Error => e
|
|
112
|
+
warn e.message
|
|
117
113
|
exit 1
|
|
118
|
-
rescue SignalException =>
|
|
114
|
+
rescue SignalException => e
|
|
119
115
|
$stderr.puts
|
|
120
|
-
|
|
116
|
+
warn "Interrupted #{e}"
|
|
121
117
|
exit 1
|
|
122
118
|
end
|
|
123
119
|
exit 0
|