@@ -175,6 +175,27 @@ func (q *querier) authorizePrebuiltWorkspace(ctx context.Context, action policy.
175
175
return xerrors .Errorf ("authorize context: %w" , workspaceErr )
176
176
}
177
177
178
+ // authorizeAIBridgeInterceptionUpdate validates that the context's actor matches the initiator of the AIBridgeInterception.
179
+ // This is used by all of the sub-resources which fall under the [ResourceAibridgeInterception] umbrella.
180
+ func (q * querier ) authorizeAIBridgeInterceptionUpdate (ctx context.Context , sessID uuid.UUID ) error {
181
+ act , ok := ActorFromContext (ctx )
182
+ if ! ok {
183
+ return ErrNoActor
184
+ }
185
+
186
+ sess , err := q .db .GetAIBridgeInterceptionByID (ctx , sessID )
187
+ if err != nil {
188
+ return xerrors .Errorf ("fetch aibridge session %q: %w" , sessID , err )
189
+ }
190
+
191
+ err = q .auth .Authorize (ctx , act , policy .ActionUpdate , sess .RBACObject ())
192
+ if err != nil {
193
+ return logNotAuthorizedError (ctx , q .log , err )
194
+ }
195
+
196
+ return nil
197
+ }
198
+
178
199
type authContextKey struct {}
179
200
180
201
// ActorFromContext returns the authorization subject from the context.
@@ -542,6 +563,29 @@ var (
542
563
}),
543
564
Scope : rbac .ScopeAll ,
544
565
}.WithCachedASTValue ()
566
+
567
+ // See aibridged package.
568
+ subjectAibridged = rbac.Subject {
569
+ Type : rbac .SubjectAibridged ,
570
+ FriendlyName : "AIBridge Daemon" ,
571
+ ID : uuid .Nil .String (),
572
+ Roles : rbac .Roles ([]rbac.Role {
573
+ {
574
+ Identifier : rbac.RoleIdentifier {Name : "aibridged" },
575
+ DisplayName : "AIBridge Daemon" ,
576
+ Site : rbac .Permissions (map [string ][]policy.Action {
577
+ rbac .ResourceUser .Type : {
578
+ policy .ActionReadPersonal , // Required to read users' external auth links. // TODO: this is too broad; reduce scope to just external_auth_links by creating separate resource.
579
+ },
580
+ rbac .ResourceApiKey .Type : {policy .ActionRead }, // Validate API keys.
581
+ rbac .ResourceAibridgeInterception .Type : {policy .ActionCreate , policy .ActionRead , policy .ActionUpdate },
582
+ }),
583
+ Org : map [string ][]rbac.Permission {},
584
+ User : []rbac.Permission {},
585
+ },
586
+ }),
587
+ Scope : rbac .ScopeAll ,
588
+ }.WithCachedASTValue ()
545
589
)
546
590
547
591
// AsProvisionerd returns a context with an actor that has permissions required
@@ -624,6 +668,12 @@ func AsUsagePublisher(ctx context.Context) context.Context {
624
668
return As (ctx , subjectUsagePublisher )
625
669
}
626
670
671
+ // AsAIBridged returns a context with an actor that has permissions
672
+ // required for creating, reading, and updating aibridge-related resources.
673
+ func AsAIBridged (ctx context.Context ) context.Context {
674
+ return As (ctx , subjectAibridged )
675
+ }
676
+
627
677
var AsRemoveActor = rbac.Subject {
628
678
ID : "remove-actor" ,
629
679
}
@@ -1866,6 +1916,10 @@ func (q *querier) FindMatchingPresetID(ctx context.Context, arg database.FindMat
1866
1916
return q .db .FindMatchingPresetID (ctx , arg )
1867
1917
}
1868
1918
1919
+ func (q * querier ) GetAIBridgeInterceptionByID (ctx context.Context , id uuid.UUID ) (database.AIBridgeInterception , error ) {
1920
+ return fetch (q .log , q .auth , q .db .GetAIBridgeInterceptionByID )(ctx , id )
1921
+ }
1922
+
1869
1923
func (q * querier ) GetAPIKeyByID (ctx context.Context , id string ) (database.APIKey , error ) {
1870
1924
return fetch (q .log , q .auth , q .db .GetAPIKeyByID )(ctx , id )
1871
1925
}
@@ -3745,6 +3799,34 @@ func (q *querier) GetWorkspacesEligibleForTransition(ctx context.Context, now ti
3745
3799
return q .db .GetWorkspacesEligibleForTransition (ctx , now )
3746
3800
}
3747
3801
3802
+ func (q * querier ) InsertAIBridgeInterception (ctx context.Context , arg database.InsertAIBridgeInterceptionParams ) (database.AIBridgeInterception , error ) {
3803
+ return insert (q .log , q .auth , rbac .ResourceAibridgeInterception .WithOwner (arg .InitiatorID .String ()), q .db .InsertAIBridgeInterception )(ctx , arg )
3804
+ }
3805
+
3806
+ func (q * querier ) InsertAIBridgeTokenUsage (ctx context.Context , arg database.InsertAIBridgeTokenUsageParams ) error {
3807
+ // All aibridge_token_usages records belong to the initiator of their associated session.
3808
+ if err := q .authorizeAIBridgeInterceptionUpdate (ctx , arg .InterceptionID ); err != nil {
3809
+ return err
3810
+ }
3811
+ return q .db .InsertAIBridgeTokenUsage (ctx , arg )
3812
+ }
3813
+
3814
+ func (q * querier ) InsertAIBridgeToolUsage (ctx context.Context , arg database.InsertAIBridgeToolUsageParams ) error {
3815
+ // All aibridge_tool_usages records belong to the initiator of their associated session.
3816
+ if err := q .authorizeAIBridgeInterceptionUpdate (ctx , arg .InterceptionID ); err != nil {
3817
+ return err
3818
+ }
3819
+ return q .db .InsertAIBridgeToolUsage (ctx , arg )
3820
+ }
3821
+
3822
+ func (q * querier ) InsertAIBridgeUserPrompt (ctx context.Context , arg database.InsertAIBridgeUserPromptParams ) error {
3823
+ // All aibridge_user_prompts records belong to the initiator of their associated session.
3824
+ if err := q .authorizeAIBridgeInterceptionUpdate (ctx , arg .InterceptionID ); err != nil {
3825
+ return err
3826
+ }
3827
+ return q .db .InsertAIBridgeUserPrompt (ctx , arg )
3828
+ }
3829
+
3748
3830
func (q * querier ) InsertAPIKey (ctx context.Context , arg database.InsertAPIKeyParams ) (database.APIKey , error ) {
3749
3831
// TODO(Cian): ideally this would be encoded in the policy, but system users are just members and we
3750
3832
// don't currently have a capability to conditionally deny creating resources by owner ID in a role.
0 commit comments