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
@@ -0,0 +1,24 @@
1
+ module Capsium
2
+ class Package
3
+ # Integrity and digital-signature verification of a loaded package
4
+ # (ARCHITECTURE.md section 6), mixed into Package.
5
+ module Verification
6
+ # Verifies the package against security.json. Returns a list of
7
+ # typed errors; empty when no security.json is present or all
8
+ # checksums match.
9
+ def verify_integrity: () -> Array[Security::ChecksumMismatch | Security::MissingFile | Security::UncheckedFile]
10
+
11
+ def verify_integrity!: () -> void
12
+
13
+ # Whether security.json declares a digital signature for this package.
14
+ def signed?: () -> bool
15
+
16
+ # Verifies the declared digital signature (RSA-SHA256) against the
17
+ # checksum-covered payload. True when the package is unsigned or the
18
+ # signature verifies; false on mismatch.
19
+ def verify_signature: () -> bool
20
+
21
+ def verify_signature!: () -> void
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,39 @@
1
+ module Capsium
2
+ class Package
3
+ # A semantic version (MAJOR.MINOR.PATCH[-prerelease][+build]) with
4
+ # semver precedence ordering.
5
+ class Version
6
+ include Comparable
7
+
8
+ PATTERN: Regexp
9
+
10
+ attr_reader major: Integer
11
+ attr_reader minor: Integer
12
+ attr_reader patch: Integer
13
+ attr_reader prerelease: String?
14
+
15
+ def self.parse: (String string) -> Version
16
+
17
+ def initialize: (Integer major, Integer minor, Integer patch,
18
+ ?String? prerelease) -> void
19
+
20
+ def <=>: (Version other) -> Integer
21
+
22
+ def to_s: () -> String
23
+ end
24
+
25
+ # A semver range for metadata.dependencies (ARCHITECTURE.md section
26
+ # 4a): "*", exact versions, wildcards/partials, caret, tilde,
27
+ # comparison operators and comma/space conjunctions.
28
+ class VersionRange
29
+ NUMERIC_PART: Regexp
30
+ WILDCARD_PART: Regexp
31
+
32
+ def self.parse: (String string) -> VersionRange
33
+
34
+ def initialize: (Array[[String, Version]] bounds) -> void
35
+
36
+ def satisfied_by?: (String | Version version) -> bool
37
+ end
38
+ end
39
+ end
@@ -0,0 +1,61 @@
1
+ module Capsium
2
+ # A Capsium package loaded from a directory or a .cap file
3
+ # (ARCHITECTURE.md section 1).
4
+ class Package
5
+ include Verification
6
+ include Composition
7
+ include Preparation
8
+
9
+ MANIFEST_FILE: String
10
+ METADATA_FILE: String
11
+ STORAGE_FILE: String
12
+ ROUTES_FILE: String
13
+ SECURITY_FILE: String
14
+ SIGNATURE_FILE: String
15
+ AUTHENTICATION_FILE: String
16
+ CONTENT_DIR: String
17
+ DATA_DIR: String
18
+
19
+ attr_reader name: String
20
+ attr_reader path: String
21
+ attr_reader manifest: Manifest
22
+ attr_reader metadata: Metadata
23
+ attr_reader routes: Routes
24
+ attr_reader storage: Storage
25
+ attr_reader security: Security
26
+ attr_reader load_type: Symbol
27
+ attr_reader resolved_dependencies: Array[ResolvedDependency]
28
+ attr_reader authentication: Authentication
29
+
30
+ # Loads a package directory or .cap file. Composite packages
31
+ # (metadata.dependencies, ARCHITECTURE.md section 4a) resolve against
32
+ # the package store given as `store` (directory path or Store) or via
33
+ # CAPSIUM_STORE. `dependency_chain` is internal: the ancestor GUIDs
34
+ # used for circular-dependency detection during recursive resolution.
35
+ def initialize: (String | Pathname path, ?load_type: Symbol? load_type,
36
+ ?decryption_key: String? decryption_key,
37
+ ?store: (Store | String)? store,
38
+ ?dependency_chain: Array[String] dependency_chain) -> void
39
+
40
+ def solidify: () -> void
41
+
42
+ def load_package: () -> void
43
+
44
+ # Removes the temporary extraction directories of the package and its
45
+ # resolved dependencies; a no-op for directory loads.
46
+ def cleanup: () -> void
47
+
48
+ def datasets: () -> Array[Dataset]
49
+
50
+ # The merged content view across the storage layers (ARCHITECTURE.md
51
+ # section 5a); exported_only gives the dependent-package view (section 4a).
52
+ # Resolved dependencies act as read-only layers below all own layers.
53
+ def merged_view: (?exported_only: bool exported_only) -> MergedView
54
+
55
+ # The .cap file this package was loaded from, or nil when loaded
56
+ # from a directory.
57
+ def cap_file_path: () -> String?
58
+
59
+ def content_files: () -> Array[String]
60
+ end
61
+ end
@@ -0,0 +1,32 @@
1
+ module Capsium
2
+ # Packs a package directory into a .cap file (generating
3
+ # security.json) and unpacks .cap files with zip-slip protection.
4
+ class Packager
5
+ class FileAlreadyExistsError < StandardError
6
+ end
7
+
8
+ # Raised when a .cap archive contains an entry whose name would be
9
+ # written outside the destination directory (zip-slip).
10
+ class UnsafeEntryError < Capsium::Error
11
+ end
12
+
13
+ DRIVE_LETTER_PATTERN: Regexp
14
+
15
+ def pack: (Package package, ?Hash[Symbol, untyped] options) -> String
16
+
17
+ def unpack: (String cap_file_path, String destination) -> void
18
+
19
+ def compress_package: (Package package, String cap_file) -> void
20
+
21
+ # Unpacks a .cap into a temporary directory, yields it for read-only
22
+ # inspection and returns the block's value.
23
+ def with_unpacked_cap: (String cap_path) { (String dir) -> untyped } -> untyped
24
+
25
+ # Unpacks a .cap into a temporary directory, yields it for
26
+ # modification, then recompresses the result back over cap_path.
27
+ # Returns cap_path.
28
+ def transform_cap: (String cap_path) { (String dir) -> untyped } -> String
29
+
30
+ def relative_path_current: (String absolute_path) -> String
31
+ end
32
+ end
@@ -0,0 +1,40 @@
1
+ module Capsium
2
+ class Reactor
3
+ # Request authentication and route authorization for the reactor
4
+ # (05x-authentication, ARCHITECTURE.md section 4b).
5
+ class Authenticator
6
+ LOGIN_PATH: String
7
+
8
+ attr_reader authentication: Package::Authentication
9
+ attr_reader deploy: Deploy
10
+
11
+ def initialize: (Package::Authentication authentication,
12
+ deploy: Deploy deploy, package_path: String package_path,
13
+ ?base_url: String? base_url,
14
+ ?state_file: String? state_file) -> void
15
+
16
+ def enabled?: () -> bool
17
+
18
+ # The paths this authenticator answers itself (OAuth2 login and
19
+ # callback), mounted by the reactor.
20
+ def endpoints: () -> Array[String]
21
+
22
+ def endpoint?: (String path) -> bool
23
+
24
+ def serve_endpoint: (untyped request, untyped response) -> untyped
25
+
26
+ # The identity for a request — from the session cookie or Basic
27
+ # credentials — or nil. An identity always carries "roles"
28
+ # (possibly empty).
29
+ def authenticate: (untyped request) -> Hash[String, untyped]?
30
+
31
+ # 401, with a Basic challenge when basicAuth is enabled.
32
+ def challenge: (untyped response) -> untyped
33
+
34
+ # :ok, :unauthenticated or :forbidden for a route's accessControl
35
+ # ({"roles": [...], "authenticationRequired": bool}).
36
+ def authorize: (Hash[String, untyped]? identity,
37
+ Hash[String, untyped]? access_control) -> (:ok | :unauthenticated | :forbidden)
38
+ end
39
+ end
40
+ end
@@ -0,0 +1,34 @@
1
+ module Capsium
2
+ class Reactor
3
+ # Reactor-side deployment configuration (deploy.json): everything
4
+ # that must NOT ship in the package — OAuth2 client secrets, the
5
+ # session signing secret, role assignments, an alternate htpasswd
6
+ # file, the public base URL.
7
+ class Deploy
8
+ FILE: String
9
+ ENV_VAR: String
10
+
11
+ attr_reader config: Hash[String, untyped]
12
+
13
+ # Loads from an explicit path, an already-parsed Hash, or the
14
+ # CAPSIUM_DEPLOY environment variable; empty when unconfigured.
15
+ def self.load: (Hash[String, untyped] | String? source) -> Deploy
16
+
17
+ def initialize: (Hash[String, untyped] config) -> void
18
+
19
+ def authentication: () -> Hash[String, untyped]
20
+
21
+ def base_url: () -> String?
22
+
23
+ def client_secret: () -> String?
24
+
25
+ def session_secret: () -> String?
26
+
27
+ def passwd_file: () -> String?
28
+
29
+ # Role assignments keyed by identity name (basic-auth username,
30
+ # OAuth2 email or subject): {"alice": ["admin", "user"]}.
31
+ def roles: () -> Hash[String, Array[String]]
32
+ end
33
+ end
34
+ end
@@ -0,0 +1,36 @@
1
+ module Capsium
2
+ class Reactor
3
+ # Apache htpasswd verification (05x-authentication). Supported hash
4
+ # formats: bcrypt ($2a$/$2b$/$2y$), Apache apr1 MD5 and md5-crypt
5
+ # ($apr1$/$1$), unsalted SHA-1 ({SHA}), and a crypt(3) fallback for
6
+ # the rest (platform-dependent).
7
+ class Htpasswd
8
+ # md5-crypt as deployed by Apache htpasswd, OpenSSL and glibc.
9
+ module Md5Crypt
10
+ ITOA64: String
11
+ DIGEST_GROUPS: Array[Array[Integer]]
12
+
13
+ def self.verify: (String hash, String password) -> bool
14
+
15
+ def self.digest: (String password, String salt, String magic) -> String
16
+
17
+ def self.initial_digest: (String password, String salt, String magic) -> String
18
+
19
+ def self.stretch_digest: (Integer iteration, String password, String salt, String final) -> String
20
+
21
+ def self.to64: (String final) -> String
22
+ end
23
+
24
+ attr_reader path: String
25
+
26
+ def initialize: (String path) -> void
27
+
28
+ def usernames: () -> Array[String]
29
+
30
+ def verify?: (String username, String password) -> bool
31
+ end
32
+
33
+ # Constant-time string comparison (length-check first).
34
+ def self.secure_compare: (String own, String theirs) -> bool
35
+ end
36
+ end
@@ -0,0 +1,31 @@
1
+ module Capsium
2
+ class Reactor
3
+ # Monitoring HTTP API reports for the package this reactor serves
4
+ # (ARCHITECTURE.md section 7).
5
+ class Introspection
6
+ METADATA_PATH: String
7
+ ROUTES_PATH: String
8
+ CONTENT_HASHES_PATH: String
9
+ CONTENT_VALIDITY_PATH: String
10
+ PATHS: Array[String]
11
+
12
+ attr_reader package: Package
13
+
14
+ def initialize: (Package package) -> void
15
+
16
+ def endpoint?: (String path) -> bool
17
+
18
+ # The report body for an introspection endpoint, or nil when the
19
+ # path is not an introspection endpoint.
20
+ def report_for: (String path) -> Hash[Symbol, untyped]?
21
+
22
+ def metadata_report: () -> Hash[Symbol, untyped]
23
+
24
+ def routes_report: () -> Hash[Symbol, untyped]
25
+
26
+ def content_hashes_report: () -> Hash[Symbol, untyped]
27
+
28
+ def content_validity_report: () -> Hash[Symbol, untyped]
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,31 @@
1
+ module Capsium
2
+ class Reactor
3
+ # OAuth2 authorization-code flow client (05x-authentication), used by
4
+ # the Authenticator. Talks to the provider over net/http; the client
5
+ # secret never comes from the package.
6
+ class OAuth2
7
+ # The token exchange or userinfo fetch failed at the provider.
8
+ class FlowError < Capsium::Error
9
+ end
10
+
11
+ GRANT_TYPE: String
12
+
13
+ attr_reader config: Package::OAuth2Config
14
+
15
+ def initialize: (Package::OAuth2Config config, client_secret: String client_secret,
16
+ session: Session session, ?base_url: String? base_url) -> void
17
+
18
+ # The provider URL the login endpoint redirects the browser to.
19
+ def authorization_url: (untyped request) -> String
20
+
21
+ def valid_state?: (String? state) -> bool
22
+
23
+ # Exchanges the authorization code and fetches the userinfo
24
+ # claims. Returns the raw userinfo hash; raises FlowError on
25
+ # provider errors.
26
+ def complete: (String? code, untyped request) -> Hash[String, untyped]
27
+
28
+ def callback_uri: (untyped request) -> String
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,22 @@
1
+ module Capsium
2
+ class Reactor
3
+ # Serving of resolved routes (static files through the merged view,
4
+ # datasets, handler-route 501s), mixed into Reactor. Includes route
5
+ # inheritance processing per 05x-routing.
6
+ module Serving
7
+ private
8
+
9
+ def serve_route: (Package::Route route, untyped response) -> untyped
10
+
11
+ def serve_file: (Package::Route route, untyped response) -> untyped
12
+
13
+ def inherited_processing: (Package::Route route, String body, Hash[String, String] headers) -> [String, Hash[String, String]]
14
+
15
+ def content_type_for: (Package::Route route, String content_path) -> String
16
+
17
+ def headers_for: (Package::Route route) -> Hash[String, String]
18
+
19
+ def serve_dataset: (String dataset_name, untyped response) -> untyped
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,36 @@
1
+ module Capsium
2
+ class Reactor
3
+ # HMAC-SHA256 signed session cookies (05x-authentication). The
4
+ # signing secret comes from deploy.json (sessionSecret) or is
5
+ # generated and persisted reactor-side (never in the package).
6
+ class Session
7
+ COOKIE_NAME: String
8
+ SECRET_BYTES: Integer
9
+
10
+ def self.hmac: (String secret, String data) -> String
11
+
12
+ # The persisted reactor-side secret: loaded from state_file when
13
+ # present, generated and written (mode 0600) otherwise.
14
+ def self.load_or_generate_secret: (String? state_file) -> String
15
+
16
+ attr_reader secret: String
17
+
18
+ def initialize: (?secret: String? secret, ?state_file: String? state_file) -> void
19
+
20
+ # HMAC-SHA256 of data with the session secret (hex).
21
+ def sign: (String data) -> String
22
+
23
+ def encode: (Hash[String, untyped] payload) -> String
24
+
25
+ # The verified payload, or nil when the cookie is missing or the
26
+ # signature does not match.
27
+ def decode: (String? cookie_value) -> Hash[String, untyped]?
28
+
29
+ # The identity for a request's Cookie header, or nil.
30
+ def identity_from: (untyped request) -> Hash[String, untyped]?
31
+
32
+ # A Set-Cookie value establishing the identity session.
33
+ def cookie_for: (Hash[String, untyped] identity) -> String
34
+ end
35
+ end
36
+ end
@@ -0,0 +1,35 @@
1
+ module Capsium
2
+ # Rack-free WEBrick reactor: serves the package routes plus the
3
+ # Monitoring HTTP API (ARCHITECTURE.md section 7) and reloads the
4
+ # package on filesystem changes.
5
+ class Reactor
6
+ include Serving
7
+
8
+ DEFAULT_PORT: Integer
9
+ DEFAULT_CACHE_CONTROL: String
10
+
11
+ attr_reader package: Package
12
+ attr_reader package_path: String
13
+ attr_reader routes: Package::Routes
14
+ attr_reader port: Integer
15
+ attr_reader cache_control: String?
16
+ attr_reader server: untyped
17
+ attr_reader server_thread: Thread?
18
+ attr_reader introspection: Introspection
19
+ attr_reader authenticator: Authenticator
20
+
21
+ def initialize: (package: Package | String package, ?port: Integer port,
22
+ ?cache_control: String? cache_control,
23
+ ?do_not_listen: bool do_not_listen,
24
+ ?store: (Package::Store | String)? store,
25
+ ?deploy: (Hash[String, untyped] | String)? deploy) -> void
26
+
27
+ def serve: () -> untyped
28
+
29
+ def handle_request: (untyped request, untyped response) -> untyped
30
+
31
+ def mount_routes: () -> void
32
+
33
+ def restart_server: () -> Thread
34
+ end
35
+ end
data/sig/capsium.rbs CHANGED
@@ -1,4 +1,6 @@
1
1
  module Capsium
2
+ class Error < StandardError
3
+ end
4
+
2
5
  VERSION: String
3
- # See the writing guide of rbs: https://github.com/ruby/rbs#guides
4
6
  end