lorca 0.3.0

Sign up to get free protection for your applications and to get access to all the features.
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