capsium 0.1.2 → 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.
Files changed (111) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +1 -0
  3. data/.rubocop.yml +46 -7
  4. data/CHANGELOG.md +183 -0
  5. data/Gemfile +6 -0
  6. data/LICENSE +21 -0
  7. data/README.adoc +496 -127
  8. data/Rakefile +8 -1
  9. data/bin/console +1 -1
  10. data/capsium.gemspec +12 -19
  11. data/exe/capsium +1 -1
  12. data/lib/capsium/cli/convert.rb +18 -0
  13. data/lib/capsium/cli/formatting.rb +27 -0
  14. data/lib/capsium/cli/package.rb +148 -0
  15. data/lib/capsium/cli/reactor.rb +33 -0
  16. data/lib/capsium/cli.rb +4 -96
  17. data/lib/capsium/converters/jekyll.rb +5 -7
  18. data/lib/capsium/converters.rb +7 -0
  19. data/lib/capsium/package/authentication.rb +38 -0
  20. data/lib/capsium/package/authentication_config.rb +78 -0
  21. data/lib/capsium/package/cipher.rb +197 -0
  22. data/lib/capsium/package/composition.rb +77 -0
  23. data/lib/capsium/package/dataset.rb +67 -34
  24. data/lib/capsium/package/dependency_resolver.rb +61 -0
  25. data/lib/capsium/package/encryption_config.rb +38 -0
  26. data/lib/capsium/package/manifest.rb +33 -22
  27. data/lib/capsium/package/manifest_config.rb +52 -7
  28. data/lib/capsium/package/merged_view.rb +168 -0
  29. data/lib/capsium/package/metadata.rb +4 -8
  30. data/lib/capsium/package/metadata_config.rb +97 -9
  31. data/lib/capsium/package/preparation.rb +81 -0
  32. data/lib/capsium/package/routes.rb +39 -41
  33. data/lib/capsium/package/routes_config.rb +174 -44
  34. data/lib/capsium/package/security.rb +140 -0
  35. data/lib/capsium/package/security_config.rb +60 -0
  36. data/lib/capsium/package/signer.rb +201 -0
  37. data/lib/capsium/package/storage.rb +20 -45
  38. data/lib/capsium/package/storage_config.rb +128 -3
  39. data/lib/capsium/package/store.rb +109 -0
  40. data/lib/capsium/package/testing/config_test.rb +70 -0
  41. data/lib/capsium/package/testing/context.rb +11 -0
  42. data/lib/capsium/package/testing/data_validation_test.rb +63 -0
  43. data/lib/capsium/package/testing/file_test.rb +45 -0
  44. data/lib/capsium/package/testing/report.rb +33 -0
  45. data/lib/capsium/package/testing/route_test.rb +64 -0
  46. data/lib/capsium/package/testing/test_case.rb +60 -0
  47. data/lib/capsium/package/testing/test_suite.rb +86 -0
  48. data/lib/capsium/package/testing.rb +20 -0
  49. data/lib/capsium/package/validator.rb +160 -0
  50. data/lib/capsium/package/verification.rb +32 -0
  51. data/lib/capsium/package/version.rb +204 -0
  52. data/lib/capsium/package.rb +92 -106
  53. data/lib/capsium/packager.rb +75 -6
  54. data/lib/capsium/reactor/authenticator.rb +180 -0
  55. data/lib/capsium/reactor/deploy.rb +71 -0
  56. data/lib/capsium/reactor/htpasswd.rb +154 -0
  57. data/lib/capsium/reactor/introspection.rb +91 -0
  58. data/lib/capsium/reactor/oauth2.rb +106 -0
  59. data/lib/capsium/reactor/serving.rb +75 -0
  60. data/lib/capsium/reactor/session.rb +82 -0
  61. data/lib/capsium/reactor.rb +93 -43
  62. data/lib/capsium/thor_ext.rb +1 -1
  63. data/lib/capsium/version.rb +1 -1
  64. data/lib/capsium.rb +7 -9
  65. data/sig/capsium/package/authentication.rbs +23 -0
  66. data/sig/capsium/package/authentication_config.rbs +71 -0
  67. data/sig/capsium/package/cipher.rbs +51 -0
  68. data/sig/capsium/package/composition.rbs +20 -0
  69. data/sig/capsium/package/dataset.rbs +28 -0
  70. data/sig/capsium/package/dependency_resolver.rbs +49 -0
  71. data/sig/capsium/package/encryption_config.rbs +35 -0
  72. data/sig/capsium/package/manifest.rbs +34 -0
  73. data/sig/capsium/package/manifest_config.rbs +38 -0
  74. data/sig/capsium/package/merged_view.rbs +46 -0
  75. data/sig/capsium/package/metadata.rbs +26 -0
  76. data/sig/capsium/package/metadata_config.rbs +49 -0
  77. data/sig/capsium/package/preparation.rbs +23 -0
  78. data/sig/capsium/package/routes.rbs +37 -0
  79. data/sig/capsium/package/routes_config.rbs +107 -0
  80. data/sig/capsium/package/security.rbs +72 -0
  81. data/sig/capsium/package/security_config.rbs +57 -0
  82. data/sig/capsium/package/signer.rbs +59 -0
  83. data/sig/capsium/package/storage.rbs +28 -0
  84. data/sig/capsium/package/storage_config.rbs +85 -0
  85. data/sig/capsium/package/store.rbs +37 -0
  86. data/sig/capsium/package/testing/config_test.rbs +21 -0
  87. data/sig/capsium/package/testing/context.rbs +14 -0
  88. data/sig/capsium/package/testing/data_validation_test.rbs +22 -0
  89. data/sig/capsium/package/testing/file_test.rbs +17 -0
  90. data/sig/capsium/package/testing/report.rbs +20 -0
  91. data/sig/capsium/package/testing/route_test.rbs +22 -0
  92. data/sig/capsium/package/testing/test_case.rbs +41 -0
  93. data/sig/capsium/package/testing/test_suite.rbs +20 -0
  94. data/sig/capsium/package/testing.rbs +7 -0
  95. data/sig/capsium/package/validator.rbs +29 -0
  96. data/sig/capsium/package/verification.rbs +24 -0
  97. data/sig/capsium/package/version.rbs +39 -0
  98. data/sig/capsium/package.rbs +61 -0
  99. data/sig/capsium/packager.rbs +32 -0
  100. data/sig/capsium/reactor/authenticator.rbs +40 -0
  101. data/sig/capsium/reactor/deploy.rbs +34 -0
  102. data/sig/capsium/reactor/htpasswd.rbs +36 -0
  103. data/sig/capsium/reactor/introspection.rbs +31 -0
  104. data/sig/capsium/reactor/oauth2.rbs +31 -0
  105. data/sig/capsium/reactor/serving.rbs +22 -0
  106. data/sig/capsium/reactor/session.rbs +36 -0
  107. data/sig/capsium/reactor.rbs +35 -0
  108. data/sig/capsium.rbs +3 -1
  109. metadata +108 -170
  110. data/lib/capsium/package/dataset_config.rb +0 -28
  111. data/lib/capsium/protector.rb +0 -103
data/Rakefile CHANGED
@@ -9,4 +9,11 @@ require "rubocop/rake_task"
9
9
 
10
10
  RuboCop::RakeTask.new
11
11
 
12
- task default: %i[spec rubocop]
12
+ namespace :rbs do
13
+ desc "Validate the RBS signatures (sig/)"
14
+ task :validate do
15
+ sh "bundle exec rbs -I sig validate"
16
+ end
17
+ end
18
+
19
+ task default: %i[spec rubocop rbs:validate]
data/bin/console CHANGED
@@ -2,7 +2,7 @@
2
2
  # frozen_string_literal: true
3
3
 
4
4
  require "bundler/setup"
5
- require "capsium"
5
+ require File.expand_path("../lib/capsium", __dir__)
6
6
 
7
7
  # You can add fixtures and/or initialization code here to make experimenting
8
8
  # with your gem easier. You can also use a different console, if you like.
data/capsium.gemspec CHANGED
@@ -1,6 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require_relative "lib/capsium/version"
3
+ require File.expand_path("lib/capsium/version", __dir__)
4
4
 
5
5
  Gem::Specification.new do |spec|
6
6
  spec.name = "capsium"
@@ -8,14 +8,19 @@ Gem::Specification.new do |spec|
8
8
  spec.authors = ["Ribose Inc."]
9
9
  spec.email = ["open.source@ribose.com"]
10
10
 
11
- spec.summary = "Capsium"
12
- spec.description = "Capsium"
11
+ spec.summary = "Packager and reactor for Capsium packages (.cap)"
12
+ spec.description = "Capsium facilitates the creation, management and " \
13
+ "deployment of content packages with ease. This gem " \
14
+ "provides a structured way to handle content, data, " \
15
+ "and metadata for various applications: it packages " \
16
+ "them into .cap files and serves them over HTTP."
13
17
  spec.homepage = "https://github.com/capsiums/capsium"
14
18
  spec.license = "MIT"
15
19
 
16
20
  spec.metadata["homepage_uri"] = spec.homepage
17
21
  spec.metadata["source_code_uri"] = spec.homepage
18
22
  spec.metadata["changelog_uri"] = "https://github.com/capsiums/capsium/releases"
23
+ spec.metadata["rubygems_mfa_required"] = "true"
19
24
 
20
25
  spec.files = Dir.chdir(File.expand_path(__dir__)) do
21
26
  `git ls-files -z`
@@ -26,30 +31,18 @@ Gem::Specification.new do |spec|
26
31
  spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
27
32
  spec.require_paths = ["lib"]
28
33
 
29
- spec.required_ruby_version = ">= 2.7.0"
34
+ spec.required_ruby_version = ">= 3.2.0"
30
35
 
31
- spec.add_dependency "cssminify"
36
+ spec.add_dependency "bcrypt"
32
37
  spec.add_dependency "csv"
33
- spec.add_dependency "htmlcompressor"
34
- spec.add_dependency "html-proofer"
35
- spec.add_dependency "jekyll"
36
38
  spec.add_dependency "json"
37
39
  spec.add_dependency "json-schema"
38
40
  spec.add_dependency "listen"
41
+ spec.add_dependency "lutaml-model", ">= 0.8", "< 1.0"
39
42
  spec.add_dependency "marcel"
40
- spec.add_dependency "rubyzip", "~> 2.3.2"
41
- spec.add_dependency "scss_lint"
42
- spec.add_dependency "shale"
43
+ spec.add_dependency "rubyzip", ">= 2.3.2", "< 4.0"
43
44
  spec.add_dependency "sqlite3"
44
45
  spec.add_dependency "thor"
45
- spec.add_dependency "uglifier"
46
46
  spec.add_dependency "webrick"
47
47
  spec.add_dependency "yaml"
48
-
49
- spec.add_development_dependency "pry"
50
- spec.add_development_dependency "rack-test"
51
- spec.add_development_dependency "rake", "~> 13.0"
52
- spec.add_development_dependency "rspec", "~> 3.11"
53
- spec.add_development_dependency "rubocop", "~> 1.58"
54
- spec.add_development_dependency "rubocop-performance"
55
48
  end
data/exe/capsium CHANGED
@@ -1,6 +1,6 @@
1
1
  #!/usr/bin/env ruby
2
2
  # frozen_string_literal: true
3
3
 
4
- require "capsium/cli"
4
+ require File.expand_path("../lib/capsium", __dir__)
5
5
 
6
6
  Capsium::Cli.start(ARGV)
@@ -0,0 +1,18 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Capsium
4
+ class Cli
5
+ class Convert < Thor
6
+ extend ThorExt::Start
7
+
8
+ desc "jekyll PACKAGE_FILE OUTPUT_DIRECTORY",
9
+ "Convert a Capsium package to a Jekyll site"
10
+
11
+ def jekyll(package_file, output_directory)
12
+ converter = Capsium::Converters::Jekyll.new(package_file,
13
+ output_directory)
14
+ converter.convert
15
+ end
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,27 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Capsium
4
+ class Cli < Thor
5
+ # Output formatting helpers for CLI subcommands.
6
+ module Formatting
7
+ private
8
+
9
+ def format_result(result)
10
+ line = "#{result.ok? ? 'PASS' : 'FAIL'} #{result.name}"
11
+ return line if result.messages.empty?
12
+
13
+ "#{line}: #{result.messages.join('; ')}"
14
+ end
15
+
16
+ # The resolved dependency tree (ARCHITECTURE.md section 4a): one
17
+ # line per dependency showing the declared GUID and range, the
18
+ # resolved version and the store .cap it resolved to, nested.
19
+ def print_dependency_tree(package, indent = "")
20
+ package.resolved_dependencies.each do |dep|
21
+ puts "#{indent}- #{dep.guid} (#{dep.range}) => #{dep.version} [#{dep.path}]"
22
+ print_dependency_tree(dep.package, "#{indent} ")
23
+ end
24
+ end
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,148 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "json"
4
+
5
+ module Capsium
6
+ class Cli
7
+ class Package < Thor
8
+ extend ThorExt::Start
9
+ include Formatting
10
+
11
+ desc "info PACKAGE_PATH", "Display information about the package"
12
+ option :store, type: :string, desc: "Package store directory (default: CAPSIUM_STORE)"
13
+
14
+ def info(path_to_package)
15
+ package = Capsium::Package.new(path_to_package, store: options[:store])
16
+ puts "Package Path: #{package.path}"
17
+ puts "Routes: #{package.routes.to_hash}"
18
+ puts "Manifest: #{package.manifest.to_hash}"
19
+ print_dependency_tree(package)
20
+ end
21
+
22
+ desc "manifest PATH_TO_PACKAGE",
23
+ "Show the manifest content of the package"
24
+
25
+ def manifest(path_to_package)
26
+ package = Capsium::Package.new(path_to_package)
27
+ puts JSON.pretty_generate(package.manifest.to_hash)
28
+ end
29
+
30
+ desc "storage PATH_TO_PACKAGE", "Show the storage datasets of the package"
31
+
32
+ def storage(path_to_package)
33
+ package = Capsium::Package.new(path_to_package)
34
+ puts JSON.pretty_generate(package.storage.to_hash)
35
+ end
36
+
37
+ desc "routes PATH_TO_PACKAGE", "Show the routes of the package"
38
+
39
+ def routes(path_to_package)
40
+ package = Capsium::Package.new(path_to_package)
41
+ puts JSON.pretty_generate(package.routes.to_hash)
42
+ end
43
+
44
+ desc "metadata PATH_TO_PACKAGE", "Show the metadata of the package"
45
+
46
+ def metadata(path_to_package)
47
+ package = Capsium::Package.new(path_to_package)
48
+ puts JSON.pretty_generate(package.metadata.to_hash)
49
+ end
50
+
51
+ desc "pack PATH_TO_PACKAGE_FOLDER", "Package the files into the package"
52
+ option :force, type: :boolean, default: false, aliases: "-f"
53
+
54
+ def pack(path_to_package)
55
+ package = Capsium::Package.new(path_to_package)
56
+ packager = Capsium::Packager.new
57
+ packager.pack(package, options)
58
+ end
59
+
60
+ desc "unpack PACKAGE_FILE", "Unpack a .cap package into a directory"
61
+ option :output, type: :string, aliases: "-o", desc: "Output directory"
62
+
63
+ def unpack(path_to_package)
64
+ destination = options[:output] || File.basename(path_to_package, ".cap")
65
+ Capsium::Packager.new.unpack(path_to_package, destination)
66
+ puts "Package unpacked to: #{destination}"
67
+ end
68
+
69
+ desc "validate PACKAGE_PATH", "Validate a package directory or .cap file"
70
+
71
+ def validate(path_to_package)
72
+ results = Capsium::Package::Validator.new(path_to_package).run
73
+ results.each { |result| puts format_result(result) }
74
+ return if results.all?(&:ok?)
75
+
76
+ raise Thor::Error, "Package validation failed"
77
+ end
78
+
79
+ desc "sign PACKAGE_PATH --key KEY.pem [--cert CERT.pem]",
80
+ "Sign the package (RSA-SHA256) and record the signature in security.json"
81
+ option :key, type: :string, required: true,
82
+ desc: "Path to the RSA private key PEM (min 2048 bits)"
83
+ option :cert, type: :string,
84
+ desc: "Path to the X.509 certificate PEM (must match the key)"
85
+
86
+ def sign(path_to_package)
87
+ Capsium::Package::Signer.sign_package(path_to_package, options[:key], options[:cert])
88
+ puts "Package signed: #{path_to_package}"
89
+ end
90
+
91
+ desc "verify-signature PACKAGE_PATH [--cert CERT.pem]",
92
+ "Verify the package digital signature declared in security.json"
93
+ option :cert, type: :string,
94
+ desc: "Path to the X.509 certificate or public key PEM " \
95
+ "(defaults to the key embedded in the package)"
96
+
97
+ def verify_signature(path_to_package)
98
+ unless Capsium::Package::Signer.verify_package(path_to_package, options[:cert])
99
+ raise Thor::Error, "Signature verification failed: #{path_to_package}"
100
+ end
101
+
102
+ puts "Signature valid: #{path_to_package}"
103
+ rescue Capsium::Package::Signer::SignatureError => e
104
+ raise Thor::Error, e.message
105
+ end
106
+
107
+ desc "encrypt PACKAGE_PATH --public-key PUB.pem -o OUT.cap",
108
+ "Encrypt a package (AES-256-GCM, RSA-OAEP-SHA256 wrapped DEK)"
109
+ option :public_key, type: :string, required: true,
110
+ desc: "Path to the recipient RSA public key or X.509 certificate PEM"
111
+ option :output, type: :string, required: true, aliases: "-o",
112
+ desc: "Output path for the encrypted .cap"
113
+
114
+ def encrypt(path_to_package)
115
+ Capsium::Package::Cipher.new.encrypt(
116
+ path_to_package, options[:public_key], options[:output]
117
+ )
118
+ puts "Package encrypted: #{options[:output]}"
119
+ end
120
+
121
+ desc "decrypt PACKAGE_PATH --private-key PRIV.pem [-o OUT.cap]",
122
+ "Decrypt an encrypted package"
123
+ option :private_key, type: :string, required: true,
124
+ desc: "Path to the recipient RSA private key PEM"
125
+ option :output, type: :string, aliases: "-o",
126
+ desc: "Output path (default: <name>-decrypted.cap)"
127
+
128
+ def decrypt(path_to_package)
129
+ output = options[:output] || "#{File.basename(path_to_package, '.cap')}-decrypted.cap"
130
+ Capsium::Package::Cipher.new.decrypt(path_to_package, options[:private_key], output)
131
+ puts "Package decrypted: #{output}"
132
+ end
133
+
134
+ desc "test PACKAGE_PATH", "Run the package's tests/*.yaml suite (05x-testing DSL)"
135
+
136
+ def test(path_to_package)
137
+ package = Capsium::Package.new(path_to_package)
138
+ report = Capsium::Package::Testing::TestSuite.new(package).run
139
+ report.results.each { |result| puts format_result(result) }
140
+ if report.results.empty?
141
+ puts "No tests found (#{Capsium::Package::Testing::TestSuite::TESTS_DIR}/*.yaml)"
142
+ end
143
+ puts report.summary
144
+ raise Thor::Error, "Package tests failed" unless report.ok?
145
+ end
146
+ end
147
+ end
148
+ end
@@ -0,0 +1,33 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Capsium
4
+ class Cli
5
+ class Reactor < Thor
6
+ extend ThorExt::Start
7
+
8
+ desc "serve PACKAGE_PATH",
9
+ "Start the Capsium reactor to serve the package"
10
+ option :port, type: :numeric, default: Capsium::Reactor::DEFAULT_PORT
11
+ option :do_not_listen, type: :boolean, default: false
12
+ option :store, type: :string,
13
+ desc: "Package store directory for dependency " \
14
+ "resolution (default: CAPSIUM_STORE)"
15
+ option :deploy, type: :string,
16
+ desc: "deploy.json with reactor-side secrets " \
17
+ "(default: CAPSIUM_DEPLOY)"
18
+
19
+ def serve(path_to_package)
20
+ reactor = Capsium::Reactor.new(
21
+ package: path_to_package,
22
+ port: options[:port],
23
+ do_not_listen: options[:do_not_listen],
24
+ store: options[:store],
25
+ deploy: options[:deploy]
26
+ )
27
+ reactor.serve
28
+ ensure
29
+ reactor&.package&.cleanup
30
+ end
31
+ end
32
+ end
33
+ end
data/lib/capsium/cli.rb CHANGED
@@ -1,107 +1,15 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- # lib/capsium/cli.rb
4
3
  require "thor"
5
- require_relative "package"
6
- require_relative "reactor"
7
- require_relative "thor_ext"
8
- require_relative "converters/jekyll"
9
4
 
10
5
  module Capsium
11
6
  class Cli < Thor
12
7
  extend ThorExt::Start
13
8
 
14
- class Package < Thor
15
- extend ThorExt::Start
16
-
17
- desc "info PACKAGE_PATH", "Display information about the package"
18
-
19
- def info(path_to_package)
20
- package = Capsium::Package.new(path_to_package)
21
- puts "Package Path: #{package.path}"
22
- puts "Routes: #{package.routes.as_json}"
23
- puts "Manifest: #{package.manifest.as_json}"
24
- end
25
-
26
- desc "manifest PATH_TO_PACKAGE",
27
- "Show the manifest content of the package"
28
-
29
- def manifest(path_to_package)
30
- package = Capsium::Package.new(path_to_package)
31
- puts JSON.pretty_generate(package.manifest.as_json)
32
- end
33
-
34
- desc "storage PATH_TO_PACKAGE", "Show the storage datasets of the package"
35
-
36
- def storage(path_to_package)
37
- package = Capsium::Package.new(path_to_package)
38
- puts JSON.pretty_generate(package.storage.as_json)
39
- end
40
-
41
- desc "routes PATH_TO_PACKAGE", "Show the routes of the package"
42
-
43
- def routes(path_to_package)
44
- package = Capsium::Package.new(path_to_package)
45
- puts JSON.pretty_generate(package.routes.as_json)
46
- end
47
-
48
- desc "metadata PATH_TO_PACKAGE", "Show the metadata of the package"
49
-
50
- def metadata(path_to_package)
51
- package = Capsium::Package.new(path_to_package)
52
- puts JSON.pretty_generate(package.metadata.as_json)
53
- end
54
-
55
- desc "pack PATH_TO_PACKAGE_FOLDER", "Package the files into the package"
56
- option :force, type: :boolean, default: false, aliases: "-f"
57
-
58
- def pack(path_to_package)
59
- package = Capsium::Package.new(path_to_package)
60
- packager = Capsium::Packager.new
61
- packager.pack(package, options)
62
- rescue StandardError => e
63
- puts e
64
- puts e.inspect
65
- puts e.backtrace
66
- end
67
- end
68
-
69
- class Reactor < Thor
70
- extend ThorExt::Start
71
-
72
- desc "serve PACKAGE_PATH",
73
- "Start the Capsium reactor to serve the package"
74
- option :port, type: :numeric, default: Capsium::Reactor::DEFAULT_PORT
75
- option :do_not_listen, type: :boolean, default: false
76
-
77
- def serve(path_to_package)
78
- reactor = Capsium::Reactor.new(
79
- package: path_to_package,
80
- port: options[:port],
81
- do_not_listen: options[:do_not_listen],
82
- )
83
- reactor.serve
84
- rescue StandardError => e
85
- puts e
86
- puts e.inspect
87
- puts e.backtrace
88
- ensure
89
- reactor.package.cleanup
90
- end
91
- end
92
-
93
- class Convert < Thor
94
- extend ThorExt::Start
95
-
96
- desc "jekyll PACKAGE_FILE OUTPUT_DIRECTORY",
97
- "Convert a Capsium package to a Jekyll site"
98
-
99
- def jekyll(package_file, output_directory)
100
- converter = Capsium::Converters::Jekyll.new(package_file,
101
- output_directory)
102
- converter.convert
103
- end
104
- end
9
+ autoload :Convert, "capsium/cli/convert"
10
+ autoload :Formatting, "capsium/cli/formatting"
11
+ autoload :Package, "capsium/cli/package"
12
+ autoload :Reactor, "capsium/cli/reactor"
105
13
 
106
14
  desc "package SUBCOMMAND ...ARGS", "Manage packages"
107
15
  subcommand "package", Capsium::Cli::Package
@@ -1,9 +1,8 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- # lib/capsium/converters/jekyll.rb
4
3
  require "fileutils"
5
- require "json"
6
- require "capsium/package"
4
+ require "pathname"
5
+ require "yaml"
7
6
 
8
7
  module Capsium
9
8
  module Converters
@@ -38,7 +37,7 @@ module Capsium
38
37
  "baseurl" => "",
39
38
  "url" => "",
40
39
  "markdown" => "kramdown",
41
- "theme" => "minima",
40
+ "theme" => "minima"
42
41
  }
43
42
  write_file("_config.yml", config.to_yaml)
44
43
  end
@@ -51,7 +50,7 @@ module Capsium
51
50
 
52
51
  def copy_content_files(package)
53
52
  package.content_files.each do |file_path|
54
- relative_path = Pathname.new(file_path).relative_path_from(Pathname.new(package.instance_variable_get(:@path)))
53
+ relative_path = Pathname.new(file_path).relative_path_from(Pathname.new(package.path))
55
54
  output_path = File.join(@output_directory, relative_path)
56
55
  FileUtils.mkdir_p(File.dirname(output_path))
57
56
  FileUtils.cp(file_path, output_path)
@@ -63,8 +62,7 @@ module Capsium
63
62
  route.path == "/"
64
63
  end
65
64
  if root_route
66
- index_path = File.join(package.instance_variable_get(:@path),
67
- root_route.target.file)
65
+ index_path = File.join(package.path, root_route.resource)
68
66
  index_content = File.read(index_path)
69
67
  write_file("index.html", index_content)
70
68
  else
@@ -0,0 +1,7 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Capsium
4
+ module Converters
5
+ autoload :Jekyll, "capsium/converters/jekyll"
6
+ end
7
+ end
@@ -0,0 +1,38 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "forwardable"
4
+
5
+ module Capsium
6
+ class Package
7
+ # Loads a package's authentication.json (ARCHITECTURE.md section 4b).
8
+ class Authentication
9
+ extend Forwardable
10
+
11
+ attr_reader :path, :config
12
+
13
+ def_delegators :@config, :to_json, :to_hash
14
+
15
+ def initialize(path)
16
+ @path = path
17
+ @config = File.exist?(path) ? AuthenticationConfig.from_json(File.read(path)) : nil
18
+ end
19
+
20
+ def present?
21
+ !@config.nil?
22
+ end
23
+
24
+ def basic_auth
25
+ @config&.authentication&.basic_auth
26
+ end
27
+
28
+ def oauth2
29
+ @config&.authentication&.oauth2
30
+ end
31
+
32
+ # Whether any authentication method is enabled.
33
+ def enabled?
34
+ !!(basic_auth&.enabled? || oauth2&.enabled?)
35
+ end
36
+ end
37
+ end
38
+ end
@@ -0,0 +1,78 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "json"
4
+ require "lutaml/model"
5
+
6
+ module Capsium
7
+ class Package
8
+ # The "basicAuth" object of authentication.json (ARCHITECTURE.md
9
+ # section 4b): Apache-style Basic authentication verified against an
10
+ # htpasswd file. "passwdFile" is a package-relative path (password
11
+ # hashes, not plaintext secrets, so it may ship in the package);
12
+ # deploy.json may override it with a reactor-side file.
13
+ class BasicAuthConfig < Lutaml::Model::Serializable
14
+ attribute :enabled, :boolean, default: false
15
+ attribute :passwd_file, :string
16
+ attribute :realm, :string, default: "capsium"
17
+
18
+ json do
19
+ map :enabled, to: :enabled
20
+ map "passwdFile", to: :passwd_file
21
+ map :realm, to: :realm
22
+ end
23
+
24
+ def enabled?
25
+ !!enabled
26
+ end
27
+ end
28
+
29
+ # The "oauth2" object of authentication.json: an OAuth2
30
+ # authorization-code flow. The client secret is NEVER read from the
31
+ # package — it comes from deploy.json or reactor configuration.
32
+ class OAuth2Config < Lutaml::Model::Serializable
33
+ attribute :enabled, :boolean, default: false
34
+ attribute :provider, :string
35
+ attribute :client_id, :string
36
+ attribute :authorization_url, :string
37
+ attribute :token_url, :string
38
+ attribute :userinfo_url, :string
39
+ attribute :redirect_path, :string, default: "/auth/callback"
40
+ attribute :scopes, :string, collection: true, default: []
41
+
42
+ json do
43
+ map :enabled, to: :enabled
44
+ map :provider, to: :provider
45
+ map "clientId", to: :client_id
46
+ map "authorizationUrl", to: :authorization_url
47
+ map "tokenUrl", to: :token_url
48
+ map "userinfoUrl", to: :userinfo_url
49
+ map "redirectPath", to: :redirect_path
50
+ map :scopes, to: :scopes
51
+ end
52
+
53
+ def enabled?
54
+ !!enabled
55
+ end
56
+ end
57
+
58
+ # The "authentication" object of authentication.json.
59
+ class AuthenticationData < Lutaml::Model::Serializable
60
+ attribute :basic_auth, BasicAuthConfig
61
+ attribute :oauth2, OAuth2Config
62
+
63
+ json do
64
+ map "basicAuth", to: :basic_auth
65
+ map :oauth2, to: :oauth2
66
+ end
67
+ end
68
+
69
+ # Canonical authentication.json model (ARCHITECTURE.md section 4b).
70
+ class AuthenticationConfig < Lutaml::Model::Serializable
71
+ attribute :authentication, AuthenticationData
72
+
73
+ json do
74
+ map :authentication, to: :authentication
75
+ end
76
+ end
77
+ end
78
+ end