@@ -175,6 +175,22 @@ 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 ,interceptionID uuid.UUID )error {
181
+ inter ,err := q .db .GetAIBridgeInterceptionByID (ctx ,interceptionID )
182
+ if err != nil {
183
+ return xerrors .Errorf ("fetch aibridge interception %q: %w" ,interceptionID ,err )
184
+ }
185
+
186
+ err = q .authorizeContext (ctx ,policy .ActionUpdate ,inter .RBACObject ())
187
+ if err != nil {
188
+ return err
189
+ }
190
+
191
+ return nil
192
+ }
193
+
178
194
type authContextKey struct {}
179
195
180
196
// ActorFromContext returns the authorization subject from the context.
@@ -542,6 +558,29 @@ var (
542
558
}),
543
559
Scope :rbac .ScopeAll ,
544
560
}.WithCachedASTValue ()
561
+
562
+ // See aibridged package.
563
+ subjectAibridged = rbac.Subject {
564
+ Type :rbac .SubjectAibridged ,
565
+ FriendlyName :"AIBridge Daemon" ,
566
+ ID :uuid .Nil .String (),
567
+ Roles :rbac .Roles ([]rbac.Role {
568
+ {
569
+ Identifier : rbac.RoleIdentifier {Name :"aibridged" },
570
+ DisplayName :"AIBridge Daemon" ,
571
+ Site :rbac .Permissions (map [string ][]policy.Action {
572
+ rbac .ResourceUser .Type : {
573
+ 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.
574
+ },
575
+ rbac .ResourceApiKey .Type : {policy .ActionRead },// Validate API keys.
576
+ rbac .ResourceAibridgeInterception .Type : {policy .ActionCreate ,policy .ActionRead ,policy .ActionUpdate },
577
+ }),
578
+ Org :map [string ][]rbac.Permission {},
579
+ User : []rbac.Permission {},
580
+ },
581
+ }),
582
+ Scope :rbac .ScopeAll ,
583
+ }.WithCachedASTValue ()
545
584
)
546
585
547
586
// AsProvisionerd returns a context with an actor that has permissions required
@@ -624,6 +663,12 @@ func AsUsagePublisher(ctx context.Context) context.Context {
624
663
return As (ctx ,subjectUsagePublisher )
625
664
}
626
665
666
+ // AsAIBridged returns a context with an actor that has permissions
667
+ // required for creating, reading, and updating aibridge-related resources.
668
+ func AsAIBridged (ctx context.Context ) context.Context {
669
+ return As (ctx ,subjectAibridged )
670
+ }
671
+
627
672
var AsRemoveActor = rbac.Subject {
628
673
ID :"remove-actor" ,
629
674
}
@@ -1878,6 +1923,10 @@ func (q *querier) FindMatchingPresetID(ctx context.Context, arg database.FindMat
1878
1923
return q .db .FindMatchingPresetID (ctx ,arg )
1879
1924
}
1880
1925
1926
+ func (q * querier )GetAIBridgeInterceptionByID (ctx context.Context ,id uuid.UUID ) (database.AIBridgeInterception ,error ) {
1927
+ return fetch (q .log ,q .auth ,q .db .GetAIBridgeInterceptionByID )(ctx ,id )
1928
+ }
1929
+
1881
1930
func (q * querier )GetAPIKeyByID (ctx context.Context ,id string ) (database.APIKey ,error ) {
1882
1931
return fetch (q .log ,q .auth ,q .db .GetAPIKeyByID )(ctx ,id )
1883
1932
}
@@ -3757,6 +3806,34 @@ func (q *querier) GetWorkspacesEligibleForTransition(ctx context.Context, now ti
3757
3806
return q .db .GetWorkspacesEligibleForTransition (ctx ,now )
3758
3807
}
3759
3808
3809
+ func (q * querier )InsertAIBridgeInterception (ctx context.Context ,arg database.InsertAIBridgeInterceptionParams ) (database.AIBridgeInterception ,error ) {
3810
+ return insert (q .log ,q .auth ,rbac .ResourceAibridgeInterception .WithOwner (arg .InitiatorID .String ()),q .db .InsertAIBridgeInterception )(ctx ,arg )
3811
+ }
3812
+
3813
+ func (q * querier )InsertAIBridgeTokenUsage (ctx context.Context ,arg database.InsertAIBridgeTokenUsageParams )error {
3814
+ // All aibridge_token_usages records belong to the initiator of their associated interception.
3815
+ if err := q .authorizeAIBridgeInterceptionUpdate (ctx ,arg .InterceptionID );err != nil {
3816
+ return err
3817
+ }
3818
+ return q .db .InsertAIBridgeTokenUsage (ctx ,arg )
3819
+ }
3820
+
3821
+ func (q * querier )InsertAIBridgeToolUsage (ctx context.Context ,arg database.InsertAIBridgeToolUsageParams )error {
3822
+ // All aibridge_tool_usages records belong to the initiator of their associated interception.
3823
+ if err := q .authorizeAIBridgeInterceptionUpdate (ctx ,arg .InterceptionID );err != nil {
3824
+ return err
3825
+ }
3826
+ return q .db .InsertAIBridgeToolUsage (ctx ,arg )
3827
+ }
3828
+
3829
+ func (q * querier )InsertAIBridgeUserPrompt (ctx context.Context ,arg database.InsertAIBridgeUserPromptParams )error {
3830
+ // All aibridge_user_prompts records belong to the initiator of their associated interception.
3831
+ if err := q .authorizeAIBridgeInterceptionUpdate (ctx ,arg .InterceptionID );err != nil {
3832
+ return err
3833
+ }
3834
+ return q .db .InsertAIBridgeUserPrompt (ctx ,arg )
3835
+ }
3836
+
3760
3837
func (q * querier )InsertAPIKey (ctx context.Context ,arg database.InsertAPIKeyParams ) (database.APIKey ,error ) {
3761
3838
// TODO(Cian): ideally this would be encoded in the policy, but system users are just members and we
3762
3839
// don't currently have a capability to conditionally deny creating resources by owner ID in a role.