lorca 0.3.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 ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: ab5b9ef8040a51c71e968398dc37c1e656cfb50246eb5d576b3565959f5a2f1c
4
+ data.tar.gz: 86fd64feae9c99878f0d1053f78a1ee2dd399831946f2436b9d4d29f8c590a0a
5
+ SHA512:
6
+ metadata.gz: ef2adcc3d3fc5c3506e31132aaefe545bd79f4f504be6bab1434d5b15ad73a253fdc3caf37b8697d1a563b520a703a1245467078f5bb70ad673d18ffc78bbc63
7
+ data.tar.gz: df0ff349f468f3a310842f5ed37546d5c366138b68c43aafecd2c3ed4010fa22a9d76be5f40023a98270f76625d544f97b01ca0906fbe3a4bef50a30520821dd
data/ChangeLog.md ADDED
@@ -0,0 +1,38 @@
1
+ # ChangeLog
2
+
3
+ ## [Unreleased]
4
+
5
+ ## [0.3.0]
6
+
7
+ ### Security
8
+
9
+ - Use `Sysrandom` gem as pseudo-random generator for Ruby versions below 2.5.
10
+
11
+ ### Added
12
+
13
+ - Lorca expansion system.
14
+ - LorcaCore expansion with base instance, and class, methods.
15
+ - Phrases expansion to generate passphrases. Based on EFF's diceware.
16
+ - CLI expansion to use Lorca from the terminal.
17
+
18
+ ## [0.0.1]
19
+
20
+ ### Security
21
+
22
+ - Added Manifest for everyone to keep track of gem files. Manually edited.
23
+
24
+ ### Changed
25
+
26
+ - Extracted gemspec info to added modules.
27
+
28
+ ### Added
29
+
30
+ - Lorca::LorcaDoc, and Lorca::LorcaManifest modules.
31
+ - License.
32
+ - ChangeLog for users to keep track of relevant gem changes.
33
+
34
+ ## [0.0.0]
35
+
36
+ ### Added
37
+
38
+ - Lorca gemspec with bare minimum of files, and settings.
data/License.md ADDED
@@ -0,0 +1,13 @@
1
+ Copyright (c) 2018, René Maya.
2
+
3
+ Permission to use, copy, modify, and distribute this software for any
4
+ purpose with or without fee is hereby granted, provided that the above
5
+ copyright notice and this permission notice appear in all copies.
6
+
7
+ THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
8
+ WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
9
+ MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
10
+ ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
11
+ WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
12
+ ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
13
+ OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
data/Manifest.md ADDED
@@ -0,0 +1,45 @@
1
+ ChangeLog.md
2
+
3
+ License.md
4
+
5
+ Manifest.md
6
+
7
+ ReadMe.md
8
+
9
+ _expand_lib_path.rb
10
+
11
+ bin/lorca
12
+
13
+ bin/lorca-alpine
14
+
15
+ lib/lorca/doc.rb
16
+
17
+ lib/lorca/manifest.rb
18
+
19
+ lib/lorca/expansions/cli.rb
20
+
21
+ lib/lorca/expansions/phrases/eff_large_wordlist.dir
22
+
23
+ lib/lorca/expansions/phrases/eff_large_wordlist.pag
24
+
25
+ lib/lorca/expansions/phrases.rb
26
+
27
+ lib/lorca/version.rb
28
+
29
+ lib/lorca.rb
30
+
31
+ test/_config/minitest.rb
32
+
33
+ test/bin/test_lorca.rb
34
+
35
+ test/lib/test_lorca.rb
36
+
37
+ test/lib/lorca/expansions/test_cli.rb
38
+
39
+ test/lib/lorca/expansions/test_phrases.rb
40
+
41
+ test/lib/lorca/test_doc.rb
42
+
43
+ test/lib/lorca/test_manifest.rb
44
+
45
+ test/lib/lorca/test_version.rb
data/ReadMe.md ADDED
@@ -0,0 +1,41 @@
1
+ # Lorca
2
+
3
+ ## Details
4
+
5
+ Lorca generates passphrases based on the algorithm originally described in
6
+ [EFF Dice-Generated Passphrases](https://www.eff.org/dice).
7
+ It uses [EFF's long word list](https://www.eff.org/files/2016/07/18/eff_large_wordlist.txt).
8
+
9
+ ## System Requirements
10
+
11
+ - *nix OS.
12
+ - Ruby 2.4+
13
+
14
+ ## Installation
15
+
16
+ Add to a project's Gemfile:
17
+
18
+ ```ruby
19
+ gem "lorca"
20
+ ```
21
+
22
+ And then run
23
+
24
+ ```bash
25
+ $ bundle
26
+ ```
27
+
28
+ Or to install the command line interface locally:
29
+
30
+ ```bash
31
+ $ gem install lorca
32
+ ```
33
+
34
+ ## Quick Start
35
+
36
+ :TODO: Add usage examples here.
37
+
38
+ ## License
39
+
40
+ Copyright (c) 2018, René Maya. Licensed ISC. Read attached `License.md` file
41
+ for details.
@@ -0,0 +1,5 @@
1
+ # -*- coding: utf-8 -*-
2
+ # -*- frozen_string_literal: true -*-
3
+
4
+ lib = File.expand_path "./../lib", __FILE__
5
+ $LOAD_PATH.unshift lib unless $LOAD_PATH.include? lib
data/bin/lorca ADDED
@@ -0,0 +1,9 @@
1
+ #!/usr/bin/env ruby -E UTF-8:UTF-8
2
+
3
+ require_relative "./../_expand_lib_path"
4
+ require "lorca"
5
+ require "lorca/expansions/cli"
6
+
7
+ Lorca.add Lorca::CLI
8
+ lorca = Lorca.new
9
+ lorca.run
data/bin/lorca-alpine ADDED
@@ -0,0 +1,9 @@
1
+ #!/usr/bin/ruby --enable-frozen-string-literal
2
+
3
+ require_relative "./../_expand_lib_path"
4
+ require "lorca"
5
+ require "lorca/expansions/cli"
6
+
7
+ Lorca.add Lorca::CLI
8
+ lorca = Lorca.new
9
+ lorca.run
data/lib/lorca/doc.rb ADDED
@@ -0,0 +1,42 @@
1
+ # -*- coding: utf-8 -*-
2
+ # -*- frozen_string_literal: true -*-
3
+ require "lorca/manifest"
4
+
5
+ class Lorca
6
+ # Aimed at gemspec, and supported doc generators.
7
+ module LorcaDoc
8
+ # One line summary of what Lorca is.
9
+ def self.summary
10
+ "Passphrase, and password generator"
11
+ end
12
+
13
+ # Short description of what Lorca does, and how does it do it.
14
+ def self.description
15
+ <<~EOH
16
+ Lorca is a passphrase generator based on diceware made available by the EFF.
17
+ Passwords, and pins are handle by the system's pseudo-random generator.
18
+ EOH
19
+ end
20
+
21
+ # Array of options used for building the documentation.
22
+ def self.build_options
23
+ [
24
+ "--line-numbers",
25
+ "--title", "#{LorcaManifest.official_name}: #{self.summary}",
26
+ "--main", "ReadMe.md"
27
+ ]
28
+ end
29
+
30
+ # Array of documented files. These should be listed in Manifest.md.
31
+ def self.files
32
+ Lorca::LorcaManifest.files.select { |f|
33
+ Lorca::LorcaManifest.codebase.any? { |dir| f.start_with? dir }
34
+ }
35
+ end
36
+
37
+ # Array of extra doc files. These should be listed in Manifest.md.
38
+ def self.appendices
39
+ Lorca::LorcaManifest.files.select { |f| f.end_with? ".md" }
40
+ end
41
+ end
42
+ end
@@ -0,0 +1,66 @@
1
+ # -*- coding: utf-8 -*-
2
+ # -*- frozen_string_literal: true -*-
3
+
4
+ require "optparse"
5
+ require "clipboard"
6
+ require "lorca/expansions/phrases"
7
+
8
+ class Lorca
9
+ module CLI # :nodoc:
10
+
11
+ # The CLI plugin enables Lorca use through the command line. It autoloads
12
+ # included plugins.
13
+ module LorcaPlugin
14
+ # Run Lorca with +options+ enabled.
15
+ # lorca = Lorca.plugin(Lorca::CLI).new
16
+ # lorca.run
17
+ def run options=$argv
18
+ cli_parse options
19
+ end
20
+
21
+ private
22
+
23
+ def cli_parse options
24
+ Object.new.extend(CLI).parse options
25
+ end
26
+ end
27
+
28
+ def self.load_dependencies expansion, **_
29
+ expansion.add Lorca::Phrases
30
+ end
31
+
32
+ def parse argv=$argv, writer:Lorca.new, pad: Clipboard, notice: $stdout
33
+ cli = OptionParser.new do |parser|
34
+ write_phrase parser, notice, writer, pad
35
+ display_help parser, notice
36
+ end
37
+
38
+ cli.parse!(*argv)
39
+
40
+ notice.puts cli
41
+ end
42
+
43
+ private
44
+
45
+ def write_phrase cli, notice, writer, pad
46
+ description = "Generate a passphrase with optional number of WORDS"
47
+ cli.on("-p", "--phrase [WORDS]", Integer, description) { |nw|
48
+ phrase = nw ? writer.phrase(words: nw) : writer.phrase
49
+ pad.copy phrase
50
+ notice.puts "passphrase copied to clipboard"
51
+ exit_cli
52
+ }
53
+ end
54
+
55
+ def display_help cli, notice
56
+ cli.on_tail("-h", "--help", "Show this message") {
57
+ notice.puts cli
58
+ exit_cli
59
+ }
60
+ end
61
+
62
+ def exit_cli status: 0
63
+ exit status unless ENV["LORCA_ENV"] == "test"
64
+ end
65
+ end
66
+ end
@@ -0,0 +1,64 @@
1
+ # -*- coding: utf-8 -*-
2
+ # -*- frozen_string_literal: true -*-
3
+ require "sdbm"
4
+
5
+ class Lorca
6
+ # Built-in Phrases expansion. Adds all necessary dependencies to enable secure
7
+ # random phrases. Intended as a passphrase generator.
8
+ #
9
+ # It exposes the following instance methods:
10
+ #
11
+ # phrase :: A phrase randomly generated suitable as a passphrase.
12
+ module Phrases
13
+ # Default minimum number of words a user can request per phrase.
14
+ MIN_WORDS = 8.freeze
15
+ # Default maximum number of words a user can request per phrase.
16
+ MAX_WORDS = 53.freeze
17
+ # Default number of dice used for rolling a word id. Meant to fit the included
18
+ # vocabulary.
19
+ DICE = 5.freeze
20
+ # The default vocabulary's database path.
21
+ VOCABULARY = "./lib/lorca/expansions/phrases/eff_large_wordlist"
22
+
23
+ # Configure the Lorca::Phrases expansion upon loading.
24
+ #
25
+ # The following settings are configurable.
26
+ #
27
+ # min_words :: The minimum number of words a user can request per phrase.
28
+ # max_words :: The maximum number of words a user can request per phrase.
29
+ # dice_to_toss :: The number of dice to toss to generate a word id. It should
30
+ # only be changed when switching vocabulary.
31
+ # vocabulary :: The path to the SDBM compatible vocabulary database.
32
+ def self.configure expansion, min_words: MIN_WORDS, max_words: MAX_WORDS,
33
+ dice_to_toss: DICE, vocabulary: VOCABULARY
34
+ config = expansion.settings[:phrases] = {}
35
+ config[:min_words] = min_words
36
+ config[:max_words] = max_words
37
+ config[:dice_to_toss] = dice_to_toss
38
+ config[:vocabulary] = vocabulary
39
+ end
40
+
41
+ module LorcaPlugin
42
+ # The phrase written given a number of +words+.
43
+ def phrase words: settings[:phrases][:min_words]
44
+ conf = settings[:phrases]
45
+ words = validate_counter words, range: conf[:min_words]..conf[:max_words]
46
+ phrase_ids = []
47
+ words.times { phrase_ids.push(roll_word_id dice: conf[:dice_to_toss]) }
48
+ phrase_ids.map(&method(:word)).join " "
49
+ end
50
+
51
+ # :nodoc:
52
+ private
53
+
54
+ WordIdError = Class.new StandardError
55
+
56
+ def word id, wordlist: settings[:phrases][:vocabulary], manager: SDBM
57
+ read_only = 0444
58
+ manager.open(wordlist, read_only) { |db| db.fetch id }
59
+ rescue => e
60
+ raise WordIdError, e.message
61
+ end
62
+ end
63
+ end
64
+ end
@@ -0,0 +1,72 @@
1
+ # -*- coding: utf-8 -*-
2
+ # -*- frozen_string_literal: true -*-
3
+
4
+ class Lorca
5
+ # Aimed at basic security auditing tools.
6
+ module LorcaManifest
7
+ # Name as used in `require` statements.
8
+ def self.gem_name
9
+ official_name.downcase
10
+ end
11
+
12
+ # Name as it's meant to be spelled.
13
+ def self.official_name
14
+ "Lorca"
15
+ end
16
+
17
+ # Array of authors' string names
18
+ def self.authors
19
+ ["René Maya"].freeze
20
+ end
21
+
22
+ # Array of maintainers' email strings.
23
+ def self.contact_emails
24
+ %w[rem@disroot.org].freeze
25
+ end
26
+
27
+ # Main repo url.
28
+ def self.repo
29
+ "https://notabug.org/rem/lorca"
30
+ end
31
+
32
+ # License type. Full text in License.md.
33
+ def self.license
34
+ "ISC"
35
+ end
36
+
37
+ # Array of official gem files. Manually maintained at Manifest.md.
38
+ #--
39
+ # Not frozen, raises warnings building gem.
40
+ #++
41
+ def self.files
42
+ File.readlines("./Manifest.md").map(&:chomp).reject(&:empty?)
43
+ end
44
+
45
+ # Array of directories housing the codebase.
46
+ def self.codebase
47
+ src = "lib"
48
+ [src].freeze if files.any? { |f| f.start_with? "#{src}/" }
49
+ end
50
+
51
+ # Directory housing the test suite.
52
+ def self.test_suite
53
+ "test"
54
+ end
55
+
56
+ # Array of test files.
57
+ def self.tests
58
+ tests = Dir["test/**/test_*.rb"] - Dir["test/dev/**/*.rb"]
59
+ tests.freeze
60
+ end
61
+
62
+ # Directory housing binstubs.
63
+ def self.bindir
64
+ "bin"
65
+ end
66
+
67
+ # Array of binstubs.
68
+ def self.binstubs
69
+ %w[lorca].freeze
70
+ end
71
+ end
72
+ end
@@ -0,0 +1,16 @@
1
+ # -*- coding: utf-8 -*-
2
+ # -*- frozen_string_literal: true -*-
3
+
4
+ class Lorca
5
+ # Major version. Updated for changes that may require users to modify their code.
6
+ LorcaMajorVersion = 0
7
+
8
+ # Minor version. Updated for new feature releases.
9
+ LorcaMinorVersion = 3
10
+
11
+ # Patch version. Updated only for bug fixes from the last feature release.
12
+ LorcaPatchVersion = 0
13
+
14
+ # Full version as a string.
15
+ LorcaVersion = "#{LorcaMajorVersion}.#{LorcaMinorVersion}.#{LorcaPatchVersion}"
16
+ end
data/lib/lorca.rb ADDED
@@ -0,0 +1,113 @@
1
+ # -*- coding: utf-8 -*-
2
+ # -*- frozen_string_literal: true -*-
3
+ require "lorca/version"
4
+
5
+ begin
6
+ gem "sysrandom"
7
+ require "sysrandom/securerandom"
8
+ rescue Gem::LoadError
9
+ require "securerandom"
10
+ end
11
+
12
+ # Lorca main class.
13
+ class Lorca
14
+ # Exception raised by an invalid number of dice
15
+ LorcaDiceError = Class.new StandardError
16
+
17
+ # Exception raised by an invalid range
18
+ LorcaRangeError = Class.new StandardError
19
+
20
+ # Exception raised by an invalid counter
21
+ LorcaCounterError = Class.new StandardError
22
+
23
+ @settings = {}
24
+
25
+ # Lorca core methods.
26
+ module LorcaCore
27
+ # LorcaCore class methods.
28
+ module LorcaExtension
29
+ class << self
30
+ # Expose warn as it is used for deprecation warnings.
31
+ # Lorca::LorcaCore::LorcaExtension.warn can be overridden for custom
32
+ # extension warnings.
33
+ public :warn
34
+ end
35
+
36
+ # Lorca class settings/configuration hash.
37
+ attr_reader :settings
38
+
39
+ # Adds a Lorca Expansion. An expansion is simply a module which changes
40
+ # Lorca. Methods defined under the +ExpansionName::LorcaPlugin+ namespace
41
+ # get included. The ones defined under +ExpansionName::LorcaExtension+,
42
+ # extended.
43
+ #
44
+ # Expansions can define +ExpansionName.load_dependencies+ to load, and
45
+ # configure their dependencies. They can also define +ExpansionName.configure+
46
+ # so users can modify its settings.
47
+ #--
48
+ # don't load from load_path; security over convenience
49
+ # rename add
50
+ #++
51
+ def add expansion, **stns, &block
52
+ expansion.load_dependencies(self, **stns, &block) if expansion.respond_to?(:load_dependencies)
53
+ include expansion::LorcaPlugin if defined? expansion::LorcaPlugin
54
+ extend expansion::LorcaExtension if defined? expansion::LorcaExtension
55
+ expansion.configure(self, **stns, &block) if expansion.respond_to?(:configure)
56
+ self
57
+ end
58
+ end
59
+
60
+ # LorcaCore instance methods.
61
+ module LorcaPlugin
62
+ # Lorca instance settings hash. Frozen duplicate of class settings meant
63
+ # for reading expansion configurations.
64
+ def settings
65
+ self.class.settings.dup.freeze
66
+ end
67
+
68
+ # Roll a word id using given number of +dice+.
69
+ def roll_word_id(dice:)
70
+ dice = validate_counter dice, range: dice..dice
71
+ id = ""
72
+ dice.times { id += roll_dice.to_s }
73
+ id
74
+ rescue Lorca::LorcaCounterError => e
75
+ raise Lorca::LorcaDiceError, e.message
76
+ end
77
+
78
+ # Roll a six-sided dice.
79
+ def roll_dice
80
+ (SecureRandom.random_number(5) + 1)
81
+ end
82
+
83
+ # Validate a given counter +number+ is within the given +range+.
84
+ def validate_counter(number, range:)
85
+ num = validate_positive_integer number
86
+ return num if num == range.min && num == range.max
87
+
88
+ range = validate_range range
89
+ range.cover?(num) ? num : fail(LorcaCounterError, "Counter out of range")
90
+ end
91
+
92
+ # Validate the +range+ given is end-inclusive, and only positive numbers.
93
+ def validate_range range
94
+ validate_positive_integer range.min
95
+ validate_positive_integer range.max
96
+ range
97
+ rescue Lorca::LorcaCounterError
98
+ raise LorcaRangeError, "Should be an end-inclusive range of positive integers"
99
+ end
100
+
101
+ # Validate +counter+ is a number greater than zero.
102
+ def validate_positive_integer counter
103
+ unless counter.is_a?(Integer) && counter > 0
104
+ fail LorcaCounterError, "Should be a positive integer"
105
+ end
106
+ counter
107
+ end
108
+ end
109
+ end
110
+
111
+ extend LorcaCore::LorcaExtension
112
+ add LorcaCore
113
+ end
@@ -0,0 +1,19 @@
1
+ # -*- coding: utf-8 -*-
2
+ # -*- frozen_string_literal: true -*-
3
+
4
+ require "minitest/autorun"
5
+ require "minitest/pride"
6
+ require "minitest/proveit"
7
+ require "minitest/unordered"
8
+ require_relative "./../../_expand_lib_path"
9
+
10
+ class Minitest::Test
11
+ make_my_diffs_pretty!
12
+ prove_it!
13
+ end
14
+
15
+ module Minitest::Assertions
16
+ def assert_frozen obj
17
+ assert obj.frozen?, "Expected #{obj.inspect} to be frozen"
18
+ end
19
+ end
@@ -0,0 +1,25 @@
1
+ # -*- coding: utf-8 -*-
2
+ # -*- frozen_string_literal: true -*-
3
+
4
+ require "_config/minitest"
5
+ require "rbconfig"
6
+
7
+ class TestLorcaBin < Minitest::Test
8
+ def test_binstubs_return_usage_by_default
9
+ skip "slow CLI Integration" unless ENV["LORCA_ALL_TESTS"]
10
+
11
+ host = RbConfig::CONFIG["host_vendor"]
12
+ lorca = "lorca"
13
+ lorca += %r"alpine".match?(host) ? "-#{host}" : ""
14
+
15
+ out, _ = capture_subprocess_io { system lorca }
16
+ assert_match %r"usage:"i, out
17
+ end
18
+
19
+ def test_binstubs_permissions
20
+ lorca = "bin/lorca"
21
+
22
+ assert File.executable_real? lorca
23
+ assert File.executable_real? "#{lorca}-alpine"
24
+ end
25
+ end
@@ -0,0 +1,59 @@
1
+ # -*- coding: utf-8 -*-
2
+ # -*- frozen_string_literal: true -*-
3
+ require_relative "./../../../_config/minitest"
4
+ require "lorca/expansions/cli"
5
+
6
+ class TestLorcaCLIExpansion < Minitest::Test
7
+
8
+ # test dependencies, useful for testing Lorca::CLI but should be replaceable
9
+ require "lorca"
10
+ require "clipboard" # Lorca can't paste, nor clear, clipboard contents
11
+
12
+ def setup
13
+ @argv = -> options { Array[StringIO.new(options).read.split] }
14
+ @cli = Object.new.extend Lorca::CLI
15
+ end
16
+
17
+ def test_display_help_when_no_options_given
18
+ assert_output(%r"\Ausage"i) { @cli.parse @argv.("") }
19
+ end
20
+
21
+ def test_h__help_shows_usage_info
22
+ assert_output(%r"\Ausage"i) { @cli.parse @argv.("-h") }
23
+ assert_output(%r"\Ausage"i) { @cli.parse @argv.("--help") }
24
+ end
25
+
26
+ def test_p__phrase_copies_passphrase_to_clipboard
27
+ writer = Lorca.add(Lorca::Phrases).new
28
+ message = %r"\Apassphrase copied to clipboard\n"i # no `\z` due to `exit_cli`
29
+ copied = "ah"
30
+
31
+ writer.stub :phrase, copied do
32
+ assert_output(message) { @cli.parse @argv.("--phrase"), writer: writer }
33
+ assert_output { @cli.parse @argv.("-p"), writer: writer }
34
+ end
35
+
36
+ assert_equal copied, Clipboard.paste
37
+ Clipboard.clear
38
+ end
39
+
40
+ def test_phrase_is_composed_of_as_many_words_as_the_given_number
41
+ writer = Lorca.add(Lorca::Phrases).new
42
+ copied = "ah"
43
+
44
+ writer.stub :phrase, copied do
45
+ _ = capture_io { @cli.parse @argv.("--phrase 9"), writer: writer }
46
+ assert_equal copied, Clipboard.paste
47
+ end
48
+ Clipboard.clear
49
+ end
50
+ end
51
+
52
+ class TestCLILorcaPlugin < Minitest::Test
53
+ def test_lorca_cli_plugin
54
+ argv = -> options { Array[StringIO.new(options).read.split] }
55
+ lorca = Lorca.add(Lorca::CLI).new
56
+
57
+ assert_output(%r"\Ausage"i) { lorca.run argv.("") }
58
+ end
59
+ end
@@ -0,0 +1,66 @@
1
+ # -*- coding: utf-8 -*-
2
+ # -*- frozen_string_literal: true -*-
3
+ require_relative "./../../../_config/minitest"
4
+ require "lorca"
5
+ require "lorca/expansions/phrases"
6
+
7
+ # Use stubs for built-in expansions. External ones should instanciate Lorca.
8
+ class TestLorcaPhrasesExpansion < Minitest::Test
9
+ def setup
10
+ Lorca.add Lorca::Phrases
11
+ @lorca = Lorca.new
12
+ end
13
+
14
+ def test_returns_a_phrase_with_a_pre_set_length
15
+ @lorca.stub(:roll_word_id, "11111") do
16
+ assert_match %r"\A(abacus *){8}\z", @lorca.phrase
17
+ end
18
+ end
19
+
20
+ def test_writes_a_phrase_which_words_matches_the_given_number
21
+ num = 10
22
+
23
+ @lorca.stub(:roll_word_id, "11111") do
24
+ assert_match %r/([a-z]+ *){#{num}}/, @lorca.phrase(words: num)
25
+ end
26
+ end
27
+
28
+ def test_fails_to_return_a_phrase_given_less_words_than_default
29
+ assert_raises(Lorca::LorcaCounterError) { @lorca.phrase words: 7 }
30
+ end
31
+
32
+ def test_fails_to_return_a_phrase_given_an_invalid_amount_of_words
33
+ assert_raises(Lorca::LorcaCounterError) { @lorca.phrase words: Object.new }
34
+ end
35
+
36
+ def test_configures_min_words
37
+ Lorca.add Lorca::Phrases, min_words: 9
38
+
39
+ assert_equal 9, Lorca.settings[:phrases][:min_words]
40
+
41
+ @lorca.stub(:roll_word_id, "11111") do
42
+ assert_match %r"\A(abacus *){9}\z", @lorca.phrase
43
+ end
44
+ end
45
+
46
+ def test_configures_max_words
47
+ Lorca.add Lorca::Phrases, max_words: 12
48
+
49
+ assert_equal 12, Lorca.settings[:phrases][:max_words]
50
+ assert_raises { @lorca.phrase words: 13 }
51
+ end
52
+
53
+ def test_configures_dice_to_toss_per_word_id
54
+ Lorca.add Lorca::Phrases, dice_to_toss: 3
55
+
56
+ assert_equal 3, Lorca.settings[:phrases][:dice_to_toss]
57
+ assert_raises { @lorca.phrase }
58
+ end
59
+
60
+ def test_configures_vocabulary
61
+ path = "path/to/non/existent/vocab"
62
+ Lorca.add Lorca::Phrases, vocabulary: path
63
+ assert_equal path, Lorca.settings[:phrases][:vocabulary]
64
+ assert_raises { @lorca.phrase }
65
+ end
66
+ end
@@ -0,0 +1,32 @@
1
+ # -*- coding: utf-8 -*-
2
+ # -*- frozen_string_literal: true -*-
3
+
4
+ require "_config/minitest"
5
+ require "lorca/doc"
6
+
7
+ # Tests w/ assert_respond_to show we care about the method existing not what's
8
+ # returned, nor it's type. The type is implicitly check by building a gem w/o
9
+ # warnings. There's a test for that.
10
+ class TestLorcaDoc < Minitest::Test
11
+ def test_has_summary
12
+ assert_respond_to Lorca::LorcaDoc, :summary
13
+ end
14
+
15
+ def test_has_description
16
+ assert_respond_to Lorca::LorcaDoc, :description
17
+ end
18
+
19
+ def test_has_build_options
20
+ assert_respond_to Lorca::LorcaDoc, :build_options
21
+ end
22
+
23
+ def test_documented_files_are_listed_in_the_manifest
24
+ manifest = Lorca::LorcaManifest.files
25
+ Lorca::LorcaDoc.files.each { |f| assert_includes manifest, f }
26
+ end
27
+
28
+ def test_appendices_are_among_files_listed_in_the_manifest
29
+ manifest = Lorca::LorcaManifest.files
30
+ Lorca::LorcaDoc.appendices.each { |a| assert_includes manifest, a }
31
+ end
32
+ end
@@ -0,0 +1,86 @@
1
+ # -*- coding: utf-8 -*-
2
+ # -*- frozen_string_literal: true -*-
3
+ require_relative "./../../_config/minitest"
4
+ require "lorca/manifest"
5
+
6
+ class TestLorcaManifest < Minitest::Test
7
+ def test_has_gem_name
8
+ assert_equal "lorca", Lorca::LorcaManifest.gem_name
9
+ end
10
+
11
+ def test_has_official_name
12
+ assert_equal "Lorca", Lorca::LorcaManifest.official_name
13
+ end
14
+
15
+ def test_has_authors
16
+ expected_authors = ["René Maya"]
17
+ authors = Lorca::LorcaManifest.authors
18
+
19
+ assert_equal expected_authors, authors
20
+ assert_frozen authors
21
+ end
22
+
23
+ def test_has_contact_emails
24
+ expected_emails = %w[rem@disroot.org]
25
+ emails = Lorca::LorcaManifest.contact_emails
26
+ assert_equal expected_emails, emails
27
+ assert_frozen emails
28
+ end
29
+
30
+ def test_has_repo
31
+ assert_equal "https://notabug.org/rem/lorca", Lorca::LorcaManifest.repo
32
+ end
33
+
34
+ def test_has_license
35
+ assert_equal "ISC", Lorca::LorcaManifest.license
36
+ end
37
+
38
+ def test_has_files
39
+ # test/**/dev/**/*.* files are excluded because they are development helpers
40
+ expected_files =
41
+ %w[ChangeLog.md License.md Manifest.md ReadMe.md _expand_lib_path.rb] +
42
+ Dir["bin/*"] +
43
+ Dir["{lib,test}/**/*.{rb,txt,dir,pag}"] -
44
+ Dir["test/**/dev/**/*.{rb,txt}"]
45
+
46
+ # assert_frozen missing. Apparently Gem::Specification mutates it.
47
+ # must be unordered because CI
48
+ assert_equal_unordered expected_files, Lorca::LorcaManifest.files
49
+ end
50
+
51
+ def test_codebase_returns_required_paths_basenames
52
+ lib = "lib"
53
+ codebase = Lorca::LorcaManifest.codebase
54
+
55
+ assert Lorca::LorcaManifest.files.any? { |f| %r[\A#{lib}/(.*)].match? f }
56
+ assert_equal [lib], codebase
57
+ assert_frozen codebase
58
+ end
59
+
60
+ def test__test_suite_returns_test_dir_basename
61
+ suite = "test"
62
+ assert_equal suite, Lorca::LorcaManifest.test_suite
63
+ assert Lorca::LorcaManifest.files.any? { |f| %r[\A#{suite}/(.*)].match? f }
64
+ end
65
+
66
+ def test__tests_are_included_in_the_manifest
67
+ tests = Lorca::LorcaManifest.tests
68
+ files = Lorca::LorcaManifest.files
69
+
70
+ tests.each { |t| assert_includes files, t }
71
+ assert_frozen tests
72
+ end
73
+
74
+ def test_has_bindir
75
+ assert_equal "bin", Lorca::LorcaManifest.bindir
76
+ end
77
+
78
+ def test_binstubs_are_among_files_in_the_manifest
79
+ binstubs = Lorca::LorcaManifest.binstubs
80
+ files = Lorca::LorcaManifest.files
81
+ bindir = Lorca::LorcaManifest.bindir
82
+
83
+ binstubs.each { |b| assert_includes files, "#{bindir}/#{b}" }
84
+ assert_frozen binstubs
85
+ end
86
+ end
@@ -0,0 +1,11 @@
1
+ # -*- coding: utf-8 -*-
2
+ # -*- frozen_string_literal: true -*-
3
+
4
+ require "_config/minitest"
5
+ require "lorca/version"
6
+
7
+ class TestLorcaVersion < Minitest::Test
8
+ def test_lorca_has_a_version
9
+ assert_match %r{\d+.\d+.\d+}, Lorca::LorcaVersion
10
+ end
11
+ end
@@ -0,0 +1,157 @@
1
+ # -*- coding: utf-8 -*-
2
+ # -*- frozen_string_literal: true -*-
3
+ require_relative "./../_config/minitest"
4
+ require "lorca"
5
+
6
+ module FakeExpansion
7
+ module LorcaPlugin
8
+ def fake
9
+ end
10
+ end
11
+
12
+ module LorcaExtension
13
+ def warn msg="extension deprecation"
14
+ super msg
15
+ end
16
+ end
17
+ end
18
+
19
+ module NoPluginExpansion
20
+ LorcaExtension = Module.new
21
+ end
22
+
23
+ module NoExtensionExpansion
24
+ LorcaPlugin = Module.new
25
+ end
26
+
27
+ module LoadsDependenciesExpansion
28
+ def self.load_dependencies mod, **_
29
+ mod.send(:include, Module.new {
30
+ def whatever
31
+ true
32
+ end
33
+ }
34
+ )
35
+ end
36
+ end
37
+
38
+ module ConfigurableExpansion
39
+ def self.configure mod, **stns#, &block
40
+ mod.settings[:something] = stns[:something]
41
+ end
42
+ end
43
+
44
+ class TestLorcaCoreExtension < Minitest::Test
45
+ def test_lorca_can_add_expansions
46
+ Lorca.add FakeExpansion
47
+ lorca = Lorca.new
48
+
49
+ assert_respond_to lorca, :fake
50
+ end
51
+
52
+ def test_extensions_can_override_warn_for_custom_warnings
53
+ Lorca.add FakeExpansion
54
+ _, warning = capture_io { Lorca.warn }
55
+
56
+ assert_match %r/extension deprecation/, warning
57
+ end
58
+
59
+ def test_add_expansion_with_no_plugin
60
+ assert Lorca.add(NoPluginExpansion)
61
+ end
62
+
63
+ def test_add_expansion_with_no_extension
64
+ assert Lorca.add(NoExtensionExpansion)
65
+ end
66
+
67
+ def test_add_expansions_in_succession
68
+ assert Lorca.add(Module.new).add(Module.new)
69
+ end
70
+
71
+ def test_expansion_can_load_dependencies_when_needed
72
+ Lorca.add LoadsDependenciesExpansion
73
+ assert Lorca.new.whatever
74
+ end
75
+
76
+ def test_can_configure_expansion_when_added
77
+ Lorca.add ConfigurableExpansion, something: "configured"
78
+ assert_equal "configured", Lorca.settings[:something]
79
+ end
80
+ end
81
+
82
+ class TestLorcaCorePlugin < Minitest::Test
83
+ def setup
84
+ @lorca = Lorca.new
85
+ end
86
+
87
+ def test_has_immutable_settings
88
+ assert_frozen @lorca.settings
89
+ end
90
+
91
+ def test_rolls_dice
92
+ assert_includes 1..6, @lorca.roll_dice
93
+ end
94
+
95
+ def test_rolls_a_word_id_given_n_dice
96
+ n_dice = 5
97
+ assert_match %r/[1-6]{#{n_dice}}/, @lorca.roll_word_id(dice: n_dice)
98
+ end
99
+
100
+ def test_fails_to_roll_word_id_given_invalid_number_of_dice
101
+ [0, -1, nil, Object.new].map do |invalid_dice|
102
+ e = assert_raises(Lorca::LorcaDiceError) {
103
+ @lorca.roll_word_id dice: invalid_dice
104
+ }
105
+ assert_match %r"should be a positive integer"i, e.message
106
+ end
107
+ end
108
+
109
+ def test_validates_counter_is_within_range
110
+ num = 8
111
+ assert_equal num, @lorca.validate_counter(num, range: 1..10)
112
+ end
113
+
114
+ def test_fails_to_validate_counter_out_of_range
115
+ err_min = assert_raises(Lorca::LorcaCounterError) do
116
+ @lorca.validate_counter 3, range: 4..7
117
+ end
118
+
119
+ err_max = assert_raises(Lorca::LorcaCounterError) do
120
+ @lorca.validate_counter 8, range: 4..7
121
+ end
122
+
123
+ message = %r"\Acounter out of range\z"i
124
+ assert_match message, err_min.message
125
+ assert_match message, err_max.message
126
+ end
127
+
128
+ def test_validates_a_range
129
+ range = 3..6
130
+ assert_equal range, @lorca.validate_range(range)
131
+ end
132
+
133
+ def test_fails_to_validate_an_invalid_range
134
+ [3..2, 4..0, -1..4, "a".."z"].map do |invalid_range|
135
+ e = assert_raises(Lorca::LorcaRangeError) {
136
+ @lorca.validate_range invalid_range
137
+ }
138
+ assert_match %r"\Ashould be an end-inclusive range of positive integers\z"i,
139
+ e.message
140
+ end
141
+ end
142
+
143
+ def test_validates_positive_integer
144
+ num = 8
145
+ assert_equal num, @lorca.validate_positive_integer(num)
146
+ end
147
+
148
+ def test_fails_to_validate_positive_integer_given_invalid_input
149
+ [-1, 0, 2.1, Object.new, nil].map do |invalid_counter|
150
+ e = assert_raises(Lorca::LorcaCounterError) {
151
+ @lorca.validate_positive_integer invalid_counter
152
+ }
153
+
154
+ assert_match %r"\Ashould be a positive integer\z"i, e.message
155
+ end
156
+ end
157
+ end
metadata ADDED
@@ -0,0 +1,246 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: lorca
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.3.0
5
+ platform: ruby
6
+ authors:
7
+ - René Maya
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2018-05-04 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: clipboard
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '1.1'
20
+ - - ">="
21
+ - !ruby/object:Gem::Version
22
+ version: 1.1.1
23
+ type: :runtime
24
+ prerelease: false
25
+ version_requirements: !ruby/object:Gem::Requirement
26
+ requirements:
27
+ - - "~>"
28
+ - !ruby/object:Gem::Version
29
+ version: '1.1'
30
+ - - ">="
31
+ - !ruby/object:Gem::Version
32
+ version: 1.1.1
33
+ - !ruby/object:Gem::Dependency
34
+ name: bundler
35
+ requirement: !ruby/object:Gem::Requirement
36
+ requirements:
37
+ - - "~>"
38
+ - !ruby/object:Gem::Version
39
+ version: '1.16'
40
+ - - ">="
41
+ - !ruby/object:Gem::Version
42
+ version: 1.16.1
43
+ type: :development
44
+ prerelease: false
45
+ version_requirements: !ruby/object:Gem::Requirement
46
+ requirements:
47
+ - - "~>"
48
+ - !ruby/object:Gem::Version
49
+ version: '1.16'
50
+ - - ">="
51
+ - !ruby/object:Gem::Version
52
+ version: 1.16.1
53
+ - !ruby/object:Gem::Dependency
54
+ name: rake
55
+ requirement: !ruby/object:Gem::Requirement
56
+ requirements:
57
+ - - "~>"
58
+ - !ruby/object:Gem::Version
59
+ version: '12.3'
60
+ - - ">="
61
+ - !ruby/object:Gem::Version
62
+ version: 12.3.0
63
+ type: :development
64
+ prerelease: false
65
+ version_requirements: !ruby/object:Gem::Requirement
66
+ requirements:
67
+ - - "~>"
68
+ - !ruby/object:Gem::Version
69
+ version: '12.3'
70
+ - - ">="
71
+ - !ruby/object:Gem::Version
72
+ version: 12.3.0
73
+ - !ruby/object:Gem::Dependency
74
+ name: hanna-nouveau
75
+ requirement: !ruby/object:Gem::Requirement
76
+ requirements:
77
+ - - "~>"
78
+ - !ruby/object:Gem::Version
79
+ version: '1.0'
80
+ - - ">="
81
+ - !ruby/object:Gem::Version
82
+ version: 1.0.3
83
+ type: :development
84
+ prerelease: false
85
+ version_requirements: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - "~>"
88
+ - !ruby/object:Gem::Version
89
+ version: '1.0'
90
+ - - ">="
91
+ - !ruby/object:Gem::Version
92
+ version: 1.0.3
93
+ - !ruby/object:Gem::Dependency
94
+ name: minitest
95
+ requirement: !ruby/object:Gem::Requirement
96
+ requirements:
97
+ - - "~>"
98
+ - !ruby/object:Gem::Version
99
+ version: '5.11'
100
+ - - ">="
101
+ - !ruby/object:Gem::Version
102
+ version: 5.11.3
103
+ type: :development
104
+ prerelease: false
105
+ version_requirements: !ruby/object:Gem::Requirement
106
+ requirements:
107
+ - - "~>"
108
+ - !ruby/object:Gem::Version
109
+ version: '5.11'
110
+ - - ">="
111
+ - !ruby/object:Gem::Version
112
+ version: 5.11.3
113
+ - !ruby/object:Gem::Dependency
114
+ name: minitest-unordered
115
+ requirement: !ruby/object:Gem::Requirement
116
+ requirements:
117
+ - - "~>"
118
+ - !ruby/object:Gem::Version
119
+ version: '1.0'
120
+ - - ">="
121
+ - !ruby/object:Gem::Version
122
+ version: 1.0.2
123
+ type: :development
124
+ prerelease: false
125
+ version_requirements: !ruby/object:Gem::Requirement
126
+ requirements:
127
+ - - "~>"
128
+ - !ruby/object:Gem::Version
129
+ version: '1.0'
130
+ - - ">="
131
+ - !ruby/object:Gem::Version
132
+ version: 1.0.2
133
+ - !ruby/object:Gem::Dependency
134
+ name: minitest-proveit
135
+ requirement: !ruby/object:Gem::Requirement
136
+ requirements:
137
+ - - "~>"
138
+ - !ruby/object:Gem::Version
139
+ version: '1.0'
140
+ - - ">="
141
+ - !ruby/object:Gem::Version
142
+ version: 1.0.0
143
+ type: :development
144
+ prerelease: false
145
+ version_requirements: !ruby/object:Gem::Requirement
146
+ requirements:
147
+ - - "~>"
148
+ - !ruby/object:Gem::Version
149
+ version: '1.0'
150
+ - - ">="
151
+ - !ruby/object:Gem::Version
152
+ version: 1.0.0
153
+ - !ruby/object:Gem::Dependency
154
+ name: minitest-autotest
155
+ requirement: !ruby/object:Gem::Requirement
156
+ requirements:
157
+ - - "~>"
158
+ - !ruby/object:Gem::Version
159
+ version: '1.0'
160
+ - - ">="
161
+ - !ruby/object:Gem::Version
162
+ version: 1.0.3
163
+ type: :development
164
+ prerelease: false
165
+ version_requirements: !ruby/object:Gem::Requirement
166
+ requirements:
167
+ - - "~>"
168
+ - !ruby/object:Gem::Version
169
+ version: '1.0'
170
+ - - ">="
171
+ - !ruby/object:Gem::Version
172
+ version: 1.0.3
173
+ description: |
174
+ Lorca is a passphrase generator based on diceware made available by the EFF.
175
+ Passwords, and pins are handle by the system's pseudo-random generator.
176
+ email:
177
+ - rem@disroot.org
178
+ executables:
179
+ - lorca
180
+ extensions: []
181
+ extra_rdoc_files:
182
+ - ChangeLog.md
183
+ - License.md
184
+ - Manifest.md
185
+ - ReadMe.md
186
+ files:
187
+ - ChangeLog.md
188
+ - License.md
189
+ - Manifest.md
190
+ - ReadMe.md
191
+ - _expand_lib_path.rb
192
+ - bin/lorca
193
+ - bin/lorca-alpine
194
+ - lib/lorca.rb
195
+ - lib/lorca/doc.rb
196
+ - lib/lorca/expansions/cli.rb
197
+ - lib/lorca/expansions/phrases.rb
198
+ - lib/lorca/expansions/phrases/eff_large_wordlist.dir
199
+ - lib/lorca/expansions/phrases/eff_large_wordlist.pag
200
+ - lib/lorca/manifest.rb
201
+ - lib/lorca/version.rb
202
+ - test/_config/minitest.rb
203
+ - test/bin/test_lorca.rb
204
+ - test/lib/lorca/expansions/test_cli.rb
205
+ - test/lib/lorca/expansions/test_phrases.rb
206
+ - test/lib/lorca/test_doc.rb
207
+ - test/lib/lorca/test_manifest.rb
208
+ - test/lib/lorca/test_version.rb
209
+ - test/lib/test_lorca.rb
210
+ homepage: https://notabug.org/rem/lorca
211
+ licenses:
212
+ - ISC
213
+ metadata: {}
214
+ post_install_message:
215
+ rdoc_options:
216
+ - "--line-numbers"
217
+ - "--title"
218
+ - 'Lorca: Passphrase, and password generator'
219
+ - "--main"
220
+ - ReadMe.md
221
+ require_paths:
222
+ - lib
223
+ required_ruby_version: !ruby/object:Gem::Requirement
224
+ requirements:
225
+ - - ">="
226
+ - !ruby/object:Gem::Version
227
+ version: '2.4'
228
+ required_rubygems_version: !ruby/object:Gem::Requirement
229
+ requirements:
230
+ - - ">="
231
+ - !ruby/object:Gem::Version
232
+ version: '0'
233
+ requirements: []
234
+ rubyforge_project:
235
+ rubygems_version: 2.7.6
236
+ signing_key:
237
+ specification_version: 4
238
+ summary: Passphrase, and password generator
239
+ test_files:
240
+ - test/bin/test_lorca.rb
241
+ - test/lib/lorca/expansions/test_cli.rb
242
+ - test/lib/lorca/expansions/test_phrases.rb
243
+ - test/lib/lorca/test_doc.rb
244
+ - test/lib/lorca/test_manifest.rb
245
+ - test/lib/lorca/test_version.rb
246
+ - test/lib/test_lorca.rb