gitlab-glaz 0.0.3-x86_64-linux-gnu → 1.0.0-x86_64-linux-gnu

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 6fe05b2e696ed9f97735807520cf2603ca73f9d8ec916830673dca78989245e5
4
- data.tar.gz: 4bcc357bab65524bd9e498540bcbb1c7e34bf1470573fb3b20a1ed84c95fbad9
3
+ metadata.gz: dfcae08928853d5919f115175a543775243e32ff7e37a46e2ac0ddb1d6bd1795
4
+ data.tar.gz: 1632f9fcfa5cd0334e3686b5b61dcb808b080545cceeb6986f47fcd8ad903fec
5
5
  SHA512:
6
- metadata.gz: bd87e4f8e02706c8b0b75b9057650218ac65e06dd0512ff881a7f7d8772b633061069f5769d1ecec03312b72468060df3a2124b44c978ec48fcdcf2bdf7d4fbd
7
- data.tar.gz: cd81e1212e718df87d768aa804547ac7fdc3685cb900a71a0a187e88a703f729ac21b22e5e6c4f12ecd99b229e61094a0b163bfbdc85d45ba1948e09541e2bdb
6
+ metadata.gz: 7ebe80491b18e91888fa8c4264dd38b51dba991b526161c7d615539d6bf65ae67414add414d09f910fdddeee4bec5100beaa0d5ac1075a04bef2af3dffb8de1b
7
+ data.tar.gz: ba98dd5097bf70272893f203665095f3590e5913fe65139be3f293c2e7b7fe0c6a2267f45aaa0424ac9496df88a8958b5d9d5604b11090b387aa2f4e5f724d67
data/Cargo.lock CHANGED
@@ -244,10 +244,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
244
244
  checksum = "c673075a2e0e5f4a1dde27ce9dee1ea4558c7ffe648f576438a20ca1d2acc4b0"
245
245
  dependencies = [
246
246
  "iana-time-zone",
247
- "js-sys",
248
247
  "num-traits",
249
248
  "serde",
250
- "wasm-bindgen",
251
249
  "windows-link",
252
250
  ]
253
251
 
@@ -465,47 +463,21 @@ dependencies = [
465
463
 
466
464
  [[package]]
467
465
  name = "glaz"
468
- version = "0.0.3"
466
+ version = "1.0.0"
469
467
  dependencies = [
470
468
  "glaz-module",
471
- "magnus",
472
- ]
473
-
474
- [[package]]
475
- name = "glaz-engine"
476
- version = "0.1.0"
477
- source = "git+https://gitlab.com/gitlab-org/auth/glaz.git?rev=e1100ed8d93b3db58627229b7c4e16e29905a3d0#e1100ed8d93b3db58627229b7c4e16e29905a3d0"
478
- dependencies = [
479
- "async-trait",
480
- "glaz-graph",
481
- "glaz-policy",
482
469
  "glaz-roles",
483
- "glaz-storage",
484
- "serde_json",
485
- ]
486
-
487
- [[package]]
488
- name = "glaz-graph"
489
- version = "0.1.0"
490
- source = "git+https://gitlab.com/gitlab-org/auth/glaz.git?rev=e1100ed8d93b3db58627229b7c4e16e29905a3d0#e1100ed8d93b3db58627229b7c4e16e29905a3d0"
491
- dependencies = [
492
- "anyhow",
493
- "glaz-storage",
494
- "uuid",
470
+ "magnus",
495
471
  ]
496
472
 
497
473
  [[package]]
498
474
  name = "glaz-module"
499
475
  version = "0.1.0"
500
- source = "git+https://gitlab.com/gitlab-org/auth/glaz.git?rev=e1100ed8d93b3db58627229b7c4e16e29905a3d0#e1100ed8d93b3db58627229b7c4e16e29905a3d0"
476
+ source = "git+https://gitlab.com/gitlab-org/auth/glaz.git?rev=13a32ad3f0e6490384afb6ba6a3c3e2a6439901a#13a32ad3f0e6490384afb6ba6a3c3e2a6439901a"
501
477
  dependencies = [
502
- "glaz-engine",
503
478
  "glaz-policy",
504
479
  "glaz-proto",
505
- "glaz-roles",
506
- "glaz-storage",
507
480
  "prost",
508
- "serde_json",
509
481
  "thiserror",
510
482
  "tokio",
511
483
  "uuid",
@@ -514,12 +486,11 @@ dependencies = [
514
486
  [[package]]
515
487
  name = "glaz-policy"
516
488
  version = "0.1.0"
517
- source = "git+https://gitlab.com/gitlab-org/auth/glaz.git?rev=e1100ed8d93b3db58627229b7c4e16e29905a3d0#e1100ed8d93b3db58627229b7c4e16e29905a3d0"
489
+ source = "git+https://gitlab.com/gitlab-org/auth/glaz.git?rev=13a32ad3f0e6490384afb6ba6a3c3e2a6439901a#13a32ad3f0e6490384afb6ba6a3c3e2a6439901a"
518
490
  dependencies = [
519
491
  "async-trait",
520
492
  "cedar-policy",
521
- "glaz-roles",
522
- "glaz-storage",
493
+ "glaz-proto",
523
494
  "serde_json",
524
495
  "thiserror",
525
496
  ]
@@ -527,28 +498,21 @@ dependencies = [
527
498
  [[package]]
528
499
  name = "glaz-proto"
529
500
  version = "0.1.0"
530
- source = "git+https://gitlab.com/gitlab-org/auth/glaz.git?rev=e1100ed8d93b3db58627229b7c4e16e29905a3d0#e1100ed8d93b3db58627229b7c4e16e29905a3d0"
501
+ source = "git+https://gitlab.com/gitlab-org/auth/glaz.git?rev=13a32ad3f0e6490384afb6ba6a3c3e2a6439901a#13a32ad3f0e6490384afb6ba6a3c3e2a6439901a"
531
502
  dependencies = [
532
503
  "prost",
533
504
  "prost-build",
505
+ "prost-types",
534
506
  "protoc-bin-vendored",
535
507
  ]
536
508
 
537
509
  [[package]]
538
510
  name = "glaz-roles"
539
511
  version = "0.1.0"
540
- source = "git+https://gitlab.com/gitlab-org/auth/glaz.git?rev=e1100ed8d93b3db58627229b7c4e16e29905a3d0#e1100ed8d93b3db58627229b7c4e16e29905a3d0"
541
-
542
- [[package]]
543
- name = "glaz-storage"
544
- version = "0.1.0"
545
- source = "git+https://gitlab.com/gitlab-org/auth/glaz.git?rev=e1100ed8d93b3db58627229b7c4e16e29905a3d0#e1100ed8d93b3db58627229b7c4e16e29905a3d0"
512
+ source = "git+https://gitlab.com/gitlab-org/auth/glaz.git?rev=13a32ad3f0e6490384afb6ba6a3c3e2a6439901a#13a32ad3f0e6490384afb6ba6a3c3e2a6439901a"
546
513
  dependencies = [
547
- "async-trait",
548
- "chrono",
549
- "glaz-roles",
550
514
  "serde",
551
- "serde_json",
515
+ "serde_yaml",
552
516
  "thiserror",
553
517
  "uuid",
554
518
  ]
@@ -705,7 +669,7 @@ dependencies = [
705
669
  "ena",
706
670
  "itertools 0.14.0",
707
671
  "lalrpop-util",
708
- "petgraph",
672
+ "petgraph 0.7.1",
709
673
  "pico-args",
710
674
  "regex",
711
675
  "regex-syntax",
@@ -988,6 +952,17 @@ dependencies = [
988
952
  "indexmap 2.13.1",
989
953
  ]
990
954
 
955
+ [[package]]
956
+ name = "petgraph"
957
+ version = "0.8.3"
958
+ source = "registry+https://github.com/rust-lang/crates.io-index"
959
+ checksum = "8701b58ea97060d5e5b155d383a69952a60943f0e6dfe30b04c287beb0b27455"
960
+ dependencies = [
961
+ "fixedbitset",
962
+ "hashbrown 0.15.5",
963
+ "indexmap 2.13.1",
964
+ ]
965
+
991
966
  [[package]]
992
967
  name = "phf_shared"
993
968
  version = "0.11.3"
@@ -1053,9 +1028,9 @@ dependencies = [
1053
1028
 
1054
1029
  [[package]]
1055
1030
  name = "prost"
1056
- version = "0.13.5"
1031
+ version = "0.14.4"
1057
1032
  source = "registry+https://github.com/rust-lang/crates.io-index"
1058
- checksum = "2796faa41db3ec313a31f7624d9286acf277b52de526150b7e69f3debf891ee5"
1033
+ checksum = "528ac67416ff8646872a3c02cad9cc4ee5dc9f9540c9b10771855c95cb2e5ae1"
1059
1034
  dependencies = [
1060
1035
  "bytes",
1061
1036
  "prost-derive",
@@ -1063,16 +1038,15 @@ dependencies = [
1063
1038
 
1064
1039
  [[package]]
1065
1040
  name = "prost-build"
1066
- version = "0.13.5"
1041
+ version = "0.14.4"
1067
1042
  source = "registry+https://github.com/rust-lang/crates.io-index"
1068
- checksum = "be769465445e8c1474e9c5dac2018218498557af32d9ed057325ec9a41ae81bf"
1043
+ checksum = "03da047801ff44bb6a4d407d4860c05fd70bb81714e6b2f3812603d5b145b042"
1069
1044
  dependencies = [
1070
1045
  "heck",
1071
- "itertools 0.14.0",
1046
+ "itertools 0.13.0",
1072
1047
  "log",
1073
1048
  "multimap",
1074
- "once_cell",
1075
- "petgraph",
1049
+ "petgraph 0.8.3",
1076
1050
  "prettyplease",
1077
1051
  "prost",
1078
1052
  "prost-types",
@@ -1083,12 +1057,12 @@ dependencies = [
1083
1057
 
1084
1058
  [[package]]
1085
1059
  name = "prost-derive"
1086
- version = "0.13.5"
1060
+ version = "0.14.4"
1087
1061
  source = "registry+https://github.com/rust-lang/crates.io-index"
1088
- checksum = "8a56d757972c98b346a9b766e3f02746cde6dd1cd1d1d563472929fdd74bec4d"
1062
+ checksum = "b570b25f7617e43d59005d0990ccb79e950a423952cea19671b7a876da390adf"
1089
1063
  dependencies = [
1090
1064
  "anyhow",
1091
- "itertools 0.14.0",
1065
+ "itertools 0.13.0",
1092
1066
  "proc-macro2",
1093
1067
  "quote",
1094
1068
  "syn",
@@ -1096,9 +1070,9 @@ dependencies = [
1096
1070
 
1097
1071
  [[package]]
1098
1072
  name = "prost-types"
1099
- version = "0.13.5"
1073
+ version = "0.14.4"
1100
1074
  source = "registry+https://github.com/rust-lang/crates.io-index"
1101
- checksum = "52c2c1bf36ddb1a1c396b3601a3cec27c2462e45f07c386894ec3ccf5332bd16"
1075
+ checksum = "f94967dc7688f3054c7fac87473ffae4cc4c3904800e2d9f5b857246d8963b0a"
1102
1076
  dependencies = [
1103
1077
  "prost",
1104
1078
  ]
@@ -1311,6 +1285,12 @@ version = "1.0.22"
1311
1285
  source = "registry+https://github.com/rust-lang/crates.io-index"
1312
1286
  checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d"
1313
1287
 
1288
+ [[package]]
1289
+ name = "ryu"
1290
+ version = "1.0.23"
1291
+ source = "registry+https://github.com/rust-lang/crates.io-index"
1292
+ checksum = "9774ba4a74de5f7b1c1451ed6cd5285a32eddb5cccb8cc655a4e50009e06477f"
1293
+
1314
1294
  [[package]]
1315
1295
  name = "same-file"
1316
1296
  version = "1.0.6"
@@ -1437,6 +1417,19 @@ dependencies = [
1437
1417
  "syn",
1438
1418
  ]
1439
1419
 
1420
+ [[package]]
1421
+ name = "serde_yaml"
1422
+ version = "0.9.34+deprecated"
1423
+ source = "registry+https://github.com/rust-lang/crates.io-index"
1424
+ checksum = "6a8b1a1a2ebf674015cc02edccce75287f1a0130d394307b36743c2f5d504b47"
1425
+ dependencies = [
1426
+ "indexmap 2.13.1",
1427
+ "itoa",
1428
+ "ryu",
1429
+ "serde",
1430
+ "unsafe-libyaml",
1431
+ ]
1432
+
1440
1433
  [[package]]
1441
1434
  name = "sha3"
1442
1435
  version = "0.10.8"
@@ -1550,7 +1543,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
1550
1543
  checksum = "32497e9a4c7b38532efcdebeef879707aa9f794296a4f0244f6f69e9bc8574bd"
1551
1544
  dependencies = [
1552
1545
  "fastrand",
1553
- "getrandom",
1554
1546
  "once_cell",
1555
1547
  "rustix",
1556
1548
  "windows-sys 0.61.2",
@@ -1720,6 +1712,12 @@ version = "0.2.6"
1720
1712
  source = "registry+https://github.com/rust-lang/crates.io-index"
1721
1713
  checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853"
1722
1714
 
1715
+ [[package]]
1716
+ name = "unsafe-libyaml"
1717
+ version = "0.2.11"
1718
+ source = "registry+https://github.com/rust-lang/crates.io-index"
1719
+ checksum = "673aac59facbab8a9007c7f6108d11f63b603f7cabff99fabf650fea5c32b861"
1720
+
1723
1721
  [[package]]
1724
1722
  name = "uuid"
1725
1723
  version = "1.23.0"
data/Cargo.toml CHANGED
@@ -4,11 +4,12 @@ members = ["ext/*"]
4
4
  resolver = "2"
5
5
 
6
6
  [workspace.package]
7
- version = "0.0.3"
7
+ version = "1.0.0"
8
8
  edition = "2024"
9
9
 
10
10
  # Shared dependencies for all crates
11
11
  [workspace.dependencies]
12
12
  magnus = { version = "0.8.2", features = ["rb-sys"] }
13
13
  rb-sys = "0.9.128"
14
- glaz-module = { git = "https://gitlab.com/gitlab-org/auth/glaz.git", rev = "e1100ed8d93b3db58627229b7c4e16e29905a3d0" }
14
+ glaz-module = { git = "https://gitlab.com/gitlab-org/auth/glaz.git", rev = "13a32ad3f0e6490384afb6ba6a3c3e2a6439901a" }
15
+ glaz-roles = { git = "https://gitlab.com/gitlab-org/auth/glaz.git", rev = "13a32ad3f0e6490384afb6ba6a3c3e2a6439901a" }
data/README.md CHANGED
@@ -3,68 +3,52 @@
3
3
  > [!WARNING]
4
4
  > This gem is currently designed entirely for internal use at GitLab.
5
5
 
6
- Ruby bindings for the [Glaz](https://gitlab.com/gitlab-org/auth/glaz) authorization engine. It wraps the Rust-backed `glaz-ruby` crate via [Magnus](https://github.com/matsadler/magnus) FFI, exposing the Glaz `CheckEngine` to Ruby without leaving the Ruby process.
7
-
8
- ## Requirements
9
-
10
- - Ruby >= 3.1
11
- - Rust toolchain (for building the native extension)
6
+ Ruby bindings for the [Glaz](https://gitlab.com/gitlab-org/auth/glaz) authorization engine. A thin [Magnus](https://github.com/matsadler/magnus) FFI extension exposes the Rust `glaz-module` engine to Ruby, so Cedar policy checks run in-process.
12
7
 
13
8
  ## Installation
14
9
 
15
- Add to your Gemfile:
10
+ Requires Ruby >= 3.1 and a Rust toolchain. Add to your Gemfile:
16
11
 
17
12
  ```ruby
18
13
  gem 'gitlab-glaz', path: 'gems/gitlab-glaz'
19
14
  ```
20
15
 
21
- ## Building
22
-
23
- The gem contains a Rust extension that must be compiled before use:
24
-
25
- ```shell
26
- bundle exec rake compile
27
- ```
28
-
29
- This compiles the Rust crate in `ext/glaz/` and places the resulting native library under `lib/glaz/`.
30
-
31
16
  ## Usage
32
17
 
33
18
  ```ruby
34
19
  require 'gitlab-glaz'
35
20
 
36
- # Use the Glaz CheckEngine via the compiled native extension
37
- ```
21
+ engine = Gitlab::Glaz::Engine.new(schema: cedar_schema, policies: cedar_policies)
38
22
 
39
- ## Architecture
23
+ engine.check_action(
24
+ subject_uuid: user_uuid,
25
+ object_uuid: project_uuid,
26
+ action: 'read_docs',
27
+ context: {}
28
+ )
29
+ # => { allowed: true, reason: 'permit_granted' }
40
30
 
41
- | Layer | Technology | Purpose |
42
- | ------- | ----------- | --------- |
43
- | Ruby API | `lib/gitlab-glaz.rb` | Entry point, loads the native extension |
44
- | FFI bridge | [Magnus](https://github.com/matsadler/magnus) + [rb-sys](https://github.com/oxidize-rb/rb-sys) | Bridges Ruby ↔ Rust |
45
- | Rust extension | `ext/glaz/` | Thin `#[magnus::init]` shim that delegates to `glaz-ruby` |
46
- | Core engine | [`glaz-ruby`](https://gitlab.com/gitlab-org/auth/glaz) | Rust crate implementing the Glaz authorization engine |
31
+ # Swap the policy set at runtime; the previous set stays active if the
32
+ # new source fails to parse or validate against the schema:
33
+ engine.load_policies(new_cedar_policies)
34
+ ```
47
35
 
48
- ## Development
36
+ The schema is loaded once at construction and cannot change; build a new engine to use a new schema.
49
37
 
50
- The workspace root [`Cargo.toml`](Cargo.toml) declares all shared Rust dependencies. The extension crate lives in [`ext/glaz/`](ext/glaz/).
38
+ Schemas must declare `User` and `Resource` entities (checks build `User::"<subject_uuid>"` and `Resource::"<object_uuid>"` UIDs), and every action's context type must include `user_permissions: Set<Action>`, which the engine injects with the checked action.
51
39
 
52
- To build only the Rust extension:
40
+ ## Development
53
41
 
54
42
  ```shell
55
- cargo build --release
43
+ bundle exec rake compile # build the native extension (required before rspec)
44
+ bundle exec rspec
56
45
  ```
57
46
 
58
- To run the full compile via Rake (used during gem installation):
59
-
60
- ```shell
61
- bundle exec rake
62
- ```
47
+ The extension crate lives in [`ext/glaz/`](ext/glaz/); shared Rust dependencies are declared in the root [`Cargo.toml`](Cargo.toml).
63
48
 
64
49
  ## Releasing a new version
65
50
 
66
- To release a new version, create a merge request and use the `Release` template, following its instructions.
67
- Once merged, the new version with precompiled, native gems will automatically be published to RubyGems.
51
+ Create a merge request using the `Release` template and follow its instructions. Once merged, precompiled native gems are published to RubyGems automatically.
68
52
 
69
53
  ## License
70
54
 
data/ext/glaz/Cargo.toml CHANGED
@@ -9,4 +9,5 @@ crate-type = ["cdylib"]
9
9
 
10
10
  [dependencies]
11
11
  glaz-module = { workspace = true }
12
+ glaz-roles = { workspace = true }
12
13
  magnus = { workspace = true }
data/ext/glaz/src/lib.rs CHANGED
@@ -1,26 +1,48 @@
1
1
  use magnus::{Error, Ruby, prelude::*};
2
2
 
3
- #[magnus::wrap(class = "Glaz::Engine::CheckPermission")]
4
- struct CheckPermission {
3
+ /// Thin 1:1 wrapper around `glaz_module::PermissionCheckEngine`. The engine
4
+ /// starts empty; `load_schema` must be called before `load_policies`, and
5
+ /// `check_action` fails with a RuntimeError until both are loaded.
6
+ #[magnus::wrap(class = "Glaz::PermissionCheckEngine")]
7
+ struct PermissionCheckEngine {
5
8
  engine: glaz_module::PermissionCheckEngine,
6
9
  }
7
10
 
8
- impl CheckPermission {
11
+ impl PermissionCheckEngine {
9
12
  fn initialize(ruby: &Ruby) -> Result<Self, Error> {
10
- glaz_module::PermissionCheckEngine::new()
11
- .map(|engine| Self { engine })
12
- .map_err(|e| Error::new(ruby.exception_runtime_error(), format!("{e}")))
13
+ let engine = glaz_module::PermissionCheckEngine::new().map_err(|e| map_error(ruby, e))?;
14
+
15
+ Ok(Self { engine })
16
+ }
17
+
18
+ /// Load the Cedar schema. Re-loading a schema drops the active policies,
19
+ /// so callers should load the schema exactly once, before any policies.
20
+ fn load_schema(ruby: &Ruby, rb_self: &Self, schema: String) -> Result<(), Error> {
21
+ rb_self
22
+ .engine
23
+ .load_schema(&schema)
24
+ .map_err(|e| map_error(ruby, e))
13
25
  }
14
26
 
15
- fn check_permission(
27
+ /// Replace the active policy set with the given Cedar policy source,
28
+ /// validated against the loaded schema. On error the previous policies
29
+ /// remain active.
30
+ fn load_policies(ruby: &Ruby, rb_self: &Self, policies: String) -> Result<(), Error> {
31
+ rb_self
32
+ .engine
33
+ .load_policies(&policies)
34
+ .map_err(|e| map_error(ruby, e))
35
+ }
36
+
37
+ fn check_action(
16
38
  ruby: &Ruby,
17
- rb_self: &CheckPermission,
39
+ rb_self: &Self,
18
40
  proto_bytes: magnus::RString,
19
41
  ) -> Result<magnus::RHash, Error> {
20
42
  let bytes = unsafe { proto_bytes.as_slice() };
21
43
  let result = rb_self
22
44
  .engine
23
- .check_permission(bytes)
45
+ .check(bytes)
24
46
  .map_err(|e| map_error(ruby, e))?;
25
47
  let hash = ruby.hash_new();
26
48
  hash.aset(ruby.sym_new("allowed"), result.allowed)?;
@@ -29,10 +51,30 @@ impl CheckPermission {
29
51
  }
30
52
  }
31
53
 
54
+ /// Return the default roles embedded in the extension at build time (parsed
55
+ /// from glaz-roles' roles/**/*.yml) as an array of hashes shaped like
56
+ /// `{ id: String, name: String, permissions: [String, ...] }`.
57
+ fn roles(ruby: &Ruby) -> Result<magnus::RArray, Error> {
58
+ let array = ruby.ary_new();
59
+ for role in glaz_roles::default_roles() {
60
+ let hash = ruby.hash_new();
61
+ hash.aset(ruby.sym_new("id"), role.id.to_string())?;
62
+ hash.aset(ruby.sym_new("name"), role.name)?;
63
+ let permissions = ruby.ary_new();
64
+ for permission in &role.permissions {
65
+ permissions.push(permission.as_str())?;
66
+ }
67
+ hash.aset(ruby.sym_new("permissions"), permissions)?;
68
+ array.push(hash)?;
69
+ }
70
+ Ok(array)
71
+ }
72
+
32
73
  fn map_error(ruby: &Ruby, e: glaz_module::GlazError) -> Error {
33
74
  use glaz_module::GlazError::*;
34
75
  match e {
35
76
  InvalidArgument(_) => Error::new(ruby.exception_arg_error(), format!("{e}")),
77
+ NotInitialized(_) => Error::new(ruby.exception_runtime_error(), format!("{e}")),
36
78
  Runtime(_) => Error::new(ruby.exception_runtime_error(), format!("{e}")),
37
79
  }
38
80
  }
@@ -40,12 +82,23 @@ fn map_error(ruby: &Ruby, e: glaz_module::GlazError) -> Error {
40
82
  #[magnus::init]
41
83
  fn init(ruby: &Ruby) -> Result<(), Error> {
42
84
  let glaz = ruby.define_module("Glaz")?;
43
- let engine = glaz.define_module("Engine")?;
44
- let class = engine.define_class("CheckPermission", ruby.class_object())?;
45
- class.define_singleton_method("new", magnus::function!(CheckPermission::initialize, 0))?;
85
+ glaz.define_singleton_method("roles", magnus::function!(roles, 0))?;
86
+ let class = glaz.define_class("PermissionCheckEngine", ruby.class_object())?;
87
+ class.define_singleton_method(
88
+ "new",
89
+ magnus::function!(PermissionCheckEngine::initialize, 0),
90
+ )?;
91
+ class.define_method(
92
+ "load_schema",
93
+ magnus::method!(PermissionCheckEngine::load_schema, 1),
94
+ )?;
95
+ class.define_method(
96
+ "load_policies",
97
+ magnus::method!(PermissionCheckEngine::load_policies, 1),
98
+ )?;
46
99
  class.define_method(
47
- "check_permission",
48
- magnus::method!(CheckPermission::check_permission, 1),
100
+ "check_action",
101
+ magnus::method!(PermissionCheckEngine::check_action, 1),
49
102
  )?;
50
103
  Ok(())
51
104
  }
Binary file
Binary file
Binary file
Binary file
Binary file
@@ -0,0 +1,60 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "json"
4
+
5
+ module Gitlab
6
+ module Glaz
7
+ # Wraps a native Glaz::PermissionCheckEngine instance.
8
+ #
9
+ # The Cedar schema is fixed for the lifetime of the engine: it is loaded
10
+ # exactly once at construction and cannot be re-loaded; build a new Engine
11
+ # to change it. The policy set can be replaced at any time with
12
+ # #load_policies.
13
+ #
14
+ # Schemas must declare `User` and `Resource` entity types and declare
15
+ # each checkable action with `appliesTo { principal: [User], resource:
16
+ # [Resource], ... }`, because the engine builds `User::"<subject_uuid>"` and
17
+ # `Resource::"<object_uuid>"` entity UIDs for every check. The engine also
18
+ # injects `user_permissions` (a Set<Action> containing the checked action)
19
+ # into the Cedar context, so an action's declared context type must include
20
+ # `user_permissions: Set<Action>` alongside any custom attributes.
21
+ class Engine
22
+ # @param schema [String] Cedar schema source (`.cedarschema` DSL).
23
+ # @param policies [String] Cedar policy source, validated against the
24
+ # schema; ArgumentError is raised when either does not parse or the
25
+ # policies do not validate.
26
+ def initialize(schema:, policies:)
27
+ @native = ::Glaz::PermissionCheckEngine.new
28
+ @native.load_schema(schema)
29
+ @native.load_policies(policies)
30
+ end
31
+
32
+ # Replace the active policy set with the given Cedar policy source,
33
+ # validated against the engine's schema. Raises ArgumentError when the
34
+ # source does not parse or validate; the previous policies then remain
35
+ # active. The swap is atomic with respect to concurrent checks.
36
+ def load_policies(source)
37
+ @native.load_policies(source)
38
+ end
39
+
40
+ # Evaluate whether the subject may perform +action+ on the object.
41
+ #
42
+ # @param context [Hash] ABAC attributes made available to policies as the
43
+ # Cedar context; must match the context type the schema declares for
44
+ # the checked action.
45
+ # @return [Hash] { allowed: Boolean, reason: String }
46
+ def check_action(subject_uuid:, object_uuid:, action:, context: {})
47
+ request = ::Glaz::CheckRequest.encode(
48
+ ::Glaz::CheckRequest.new(
49
+ subject: ::Relationships::V1::Principal.new(id: subject_uuid),
50
+ action: action,
51
+ object: ::Relationships::V1::Object.new(id: object_uuid),
52
+ context: context.to_json
53
+ )
54
+ )
55
+
56
+ @native.check_action(request)
57
+ end
58
+ end
59
+ end
60
+ end
@@ -2,6 +2,6 @@
2
2
 
3
3
  module Gitlab
4
4
  module Glaz
5
- VERSION = "0.0.3"
5
+ VERSION = "1.0.0"
6
6
  end
7
7
  end
data/lib/gitlab/glaz.rb CHANGED
@@ -6,21 +6,20 @@ require "proto/service_pb"
6
6
 
7
7
  load_rust_extension
8
8
 
9
+ require_relative "glaz/engine"
10
+
9
11
  module Gitlab
10
12
  module Glaz
11
13
  module_function
12
14
 
13
- def check_permission(subject_uuid:, object_uuid:, permission:, context: {})
14
- request = ::Glaz::CheckPermissionRequest.encode(
15
- ::Glaz::CheckPermissionRequest.new(
16
- subject: subject_uuid,
17
- object: object_uuid,
18
- permission: permission,
19
- context: context.to_json
20
- )
21
- )
22
-
23
- ::Glaz::Engine::CheckPermission.new.check_permission(request)
15
+ # Returns the default roles embedded in the native extension at build time,
16
+ # sourced from the glaz-roles crate's roles/**/*.yml definitions. Each entry
17
+ # is a hash: { id: String, name: String, permissions: Array<String> }.
18
+ def roles
19
+ @roles ||= ::Glaz.roles.each do |role|
20
+ role[:permissions].freeze
21
+ role.freeze
22
+ end.freeze
24
23
  end
25
24
  end
26
25
  end
@@ -0,0 +1,28 @@
1
+ # frozen_string_literal: true
2
+ # Generated by the protocol buffer compiler. DO NOT EDIT!
3
+ # source: proto/relationships/relationships.proto
4
+
5
+ require 'google/protobuf'
6
+
7
+ require 'google/protobuf/timestamp_pb'
8
+
9
+
10
+ descriptor_data = "\n\'proto/relationships/relationships.proto\x12\x10relationships.v1\x1a\x1fgoogle/protobuf/timestamp.proto\"\x14\n\x06Object\x12\n\n\x02id\x18\x01 \x01(\t\"\x17\n\tPrincipal\x12\n\n\x02id\x18\x01 \x01(\t\"\x12\n\x04Role\x12\n\n\x02id\x18\x01 \x01(\t\"Y\n\x08Identity\x12(\n\x06origin\x18\x01 \x01(\x0e\x32\x18.relationships.v1.Origin\x12\x11\n\torigin_id\x18\x02 \x01(\t\x12\x10\n\x08local_id\x18\x03 \x01(\t\"q\n\x07Subject\x12.\n\x08identity\x18\x01 \x01(\x0b\x32\x1a.relationships.v1.IdentityH\x00\x12\x30\n\tprincipal\x18\x02 \x01(\x0b\x32\x1b.relationships.v1.PrincipalH\x00\x42\x04\n\x02id\"\xdf\x01\n\x0cRelationship\x12*\n\x07subject\x18\x01 \x01(\x0b\x32\x19.relationships.v1.Subject\x12(\n\x06object\x18\x02 \x01(\x0b\x32\x18.relationships.v1.Object\x12$\n\x04kind\x18\x03 \x01(\x0e\x32\x16.relationships.v1.Kind\x12$\n\x04role\x18\x04 \x01(\x0b\x32\x16.relationships.v1.Role\x12-\n\ttimestamp\x18\x05 \x01(\x0b\x32\x1a.google.protobuf.Timestamp\"\xb5\x01\n\x11RelationshipInput\x12*\n\x07subject\x18\x01 \x01(\x0b\x32\x19.relationships.v1.Subject\x12(\n\x06object\x18\x02 \x01(\x0b\x32\x18.relationships.v1.Object\x12$\n\x04kind\x18\x03 \x01(\x0e\x32\x16.relationships.v1.Kind\x12$\n\x04role\x18\x04 \x01(\x0b\x32\x16.relationships.v1.Role\"\x8d\x01\n\x0fRelationshipKey\x12*\n\x07subject\x18\x01 \x01(\x0b\x32\x19.relationships.v1.Subject\x12(\n\x06object\x18\x02 \x01(\x0b\x32\x18.relationships.v1.Object\x12$\n\x04kind\x18\x03 \x01(\x0e\x32\x16.relationships.v1.Kind*1\n\x04Kind\x12\x14\n\x10KIND_UNSPECIFIED\x10\x00\x12\x13\n\x0fKIND_ASSIGNMENT\x10\x01*N\n\x06Origin\x12\x16\n\x12ORIGIN_UNSPECIFIED\x10\x00\x12\x0f\n\x0bORIGIN_SELF\x10\x01\x12\x1b\n\x17ORIGIN_GITLAB_FEDERATED\x10\x02\x42\x34Z2gitlab.com/gitlab-org/auth/iam/proto/relationshipsb\x06proto3"
11
+
12
+ pool = ::Google::Protobuf::DescriptorPool.generated_pool
13
+ pool.add_serialized_file(descriptor_data)
14
+
15
+ module Relationships
16
+ module V1
17
+ Object = ::Google::Protobuf::DescriptorPool.generated_pool.lookup("relationships.v1.Object").msgclass
18
+ Principal = ::Google::Protobuf::DescriptorPool.generated_pool.lookup("relationships.v1.Principal").msgclass
19
+ Role = ::Google::Protobuf::DescriptorPool.generated_pool.lookup("relationships.v1.Role").msgclass
20
+ Identity = ::Google::Protobuf::DescriptorPool.generated_pool.lookup("relationships.v1.Identity").msgclass
21
+ Subject = ::Google::Protobuf::DescriptorPool.generated_pool.lookup("relationships.v1.Subject").msgclass
22
+ Relationship = ::Google::Protobuf::DescriptorPool.generated_pool.lookup("relationships.v1.Relationship").msgclass
23
+ RelationshipInput = ::Google::Protobuf::DescriptorPool.generated_pool.lookup("relationships.v1.RelationshipInput").msgclass
24
+ RelationshipKey = ::Google::Protobuf::DescriptorPool.generated_pool.lookup("relationships.v1.RelationshipKey").msgclass
25
+ Kind = ::Google::Protobuf::DescriptorPool.generated_pool.lookup("relationships.v1.Kind").enummodule
26
+ Origin = ::Google::Protobuf::DescriptorPool.generated_pool.lookup("relationships.v1.Origin").enummodule
27
+ end
28
+ end
@@ -1,11 +1,13 @@
1
1
  # frozen_string_literal: true
2
-
3
2
  # Generated by the protocol buffer compiler. DO NOT EDIT!
4
- # source: service.proto
3
+ # source: proto/service.proto
5
4
 
6
5
  require 'google/protobuf'
7
6
 
8
- descriptor_data = "\n\rservice.proto\x12\x04glaz\"?\n\x0e\x45xplainRequest\x12\x0f\n\x07subject\x18\x01 \x01(\t\x12\x0e\n\x06object\x18\x02 \x01(\t\x12\x0c\n\x04role\x18\x03 \x01(\x05\"2\n\x0f\x45xplainResponse\x12\x0f\n\x07\x61llowed\x18\x01 \x01(\x08\x12\x0e\n\x06reason\x18\x02 \x01(\t\"2\n\x0f\x45valuateRequest\x12\x0f\n\x07subject\x18\x01 \x01(\t\x12\x0e\n\x06object\x18\x02 \x01(\t\"3\n\x10\x45valuateResponse\x12\x0f\n\x07\x61llowed\x18\x01 \x01(\x08\x12\x0e\n\x06reason\x18\x02 \x01(\t\"^\n\x16\x43heckPermissionRequest\x12\x0f\n\x07subject\x18\x01 \x01(\t\x12\x0e\n\x06object\x18\x02 \x01(\t\x12\x12\n\npermission\x18\x03 \x01(\t\x12\x0f\n\x07\x63ontext\x18\x04 \x01(\t\":\n\x17\x43heckPermissionResponse\x12\x0f\n\x07\x61llowed\x18\x01 \x01(\x08\x12\x0e\n\x06reason\x18\x02 \x01(\t2\xd9\x01\n\x14\x41uthorizationService\x12\x36\n\x07\x45xplain\x12\x14.glaz.ExplainRequest\x1a\x15.glaz.ExplainResponse\x12\x39\n\x08\x45valuate\x12\x15.glaz.EvaluateRequest\x1a\x16.glaz.EvaluateResponse\x12N\n\x0f\x43heckPermission\x12\x1c.glaz.CheckPermissionRequest\x1a\x1d.glaz.CheckPermissionResponseb\x06proto3"
7
+ require 'proto/relationships/relationships_pb'
8
+
9
+
10
+ descriptor_data = "\n\x13proto/service.proto\x12\x04glaz\x1a\'proto/relationships/relationships.proto\"?\n\x0e\x45xplainRequest\x12\x0f\n\x07subject\x18\x01 \x01(\t\x12\x0e\n\x06object\x18\x02 \x01(\t\x12\x0c\n\x04role\x18\x03 \x01(\x05\"2\n\x0f\x45xplainResponse\x12\x0f\n\x07\x61llowed\x18\x01 \x01(\x08\x12\x0e\n\x06reason\x18\x02 \x01(\t\"2\n\x0f\x45valuateRequest\x12\x0f\n\x07subject\x18\x01 \x01(\t\x12\x0e\n\x06object\x18\x02 \x01(\t\"3\n\x10\x45valuateResponse\x12\x0f\n\x07\x61llowed\x18\x01 \x01(\x08\x12\x0e\n\x06reason\x18\x02 \x01(\t\"\xbe\x01\n\x0c\x43heckRequest\x12,\n\x07subject\x18\x01 \x01(\x0b\x32\x1b.relationships.v1.Principal\x12\x0e\n\x06\x61\x63tion\x18\x02 \x01(\t\x12(\n\x06object\x18\x03 \x01(\x0b\x32\x18.relationships.v1.Object\x12\x35\n\rrelationships\x18\x04 \x03(\x0b\x32\x1e.relationships.v1.Relationship\x12\x0f\n\x07\x63ontext\x18\x05 \x01(\t\"0\n\rCheckResponse\x12\x0f\n\x07\x61llowed\x18\x01 \x01(\x08\x12\x0e\n\x06reason\x18\x02 \x01(\t2\xbb\x01\n\x14\x41uthorizationService\x12\x36\n\x07\x45xplain\x12\x14.glaz.ExplainRequest\x1a\x15.glaz.ExplainResponse\x12\x39\n\x08\x45valuate\x12\x15.glaz.EvaluateRequest\x1a\x16.glaz.EvaluateResponse\x12\x30\n\x05\x43heck\x12\x12.glaz.CheckRequest\x1a\x13.glaz.CheckResponseb\x06proto3"
9
11
 
10
12
  pool = ::Google::Protobuf::DescriptorPool.generated_pool
11
13
  pool.add_serialized_file(descriptor_data)
@@ -15,6 +17,6 @@ module Glaz
15
17
  ExplainResponse = ::Google::Protobuf::DescriptorPool.generated_pool.lookup("glaz.ExplainResponse").msgclass
16
18
  EvaluateRequest = ::Google::Protobuf::DescriptorPool.generated_pool.lookup("glaz.EvaluateRequest").msgclass
17
19
  EvaluateResponse = ::Google::Protobuf::DescriptorPool.generated_pool.lookup("glaz.EvaluateResponse").msgclass
18
- CheckPermissionRequest = ::Google::Protobuf::DescriptorPool.generated_pool.lookup("glaz.CheckPermissionRequest").msgclass
19
- CheckPermissionResponse = ::Google::Protobuf::DescriptorPool.generated_pool.lookup("glaz.CheckPermissionResponse").msgclass
20
+ CheckRequest = ::Google::Protobuf::DescriptorPool.generated_pool.lookup("glaz.CheckRequest").msgclass
21
+ CheckResponse = ::Google::Protobuf::DescriptorPool.generated_pool.lookup("glaz.CheckResponse").msgclass
20
22
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: gitlab-glaz
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.3
4
+ version: 1.0.0
5
5
  platform: x86_64-linux-gnu
6
6
  authors:
7
7
  - group::authorization
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2026-06-10 00:00:00.000000000 Z
11
+ date: 2026-07-07 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: google-protobuf
@@ -101,8 +101,10 @@ files:
101
101
  - lib/gitlab/glaz/3.3/glaz.so
102
102
  - lib/gitlab/glaz/3.4/glaz.so
103
103
  - lib/gitlab/glaz/4.0/glaz.so
104
+ - lib/gitlab/glaz/engine.rb
104
105
  - lib/gitlab/glaz/loader.rb
105
106
  - lib/gitlab/glaz/version.rb
107
+ - lib/proto/relationships/relationships_pb.rb
106
108
  - lib/proto/service_pb.rb
107
109
  homepage: https://gitlab.com/gitlab-org/ruby/gems/gitlab-glaz
108
110
  licenses: