@@ -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
}
@@ -1878,6 +1928,10 @@ func (q *querier) FindMatchingPresetID(ctx context.Context, arg database.FindMat
1878
1928
return q .db .FindMatchingPresetID (ctx ,arg )
1879
1929
}
1880
1930
1931
+ func (q * querier )GetAIBridgeInterceptionByID (ctx context.Context ,id uuid.UUID ) (database.AIBridgeInterception ,error ) {
1932
+ return fetch (q .log ,q .auth ,q .db .GetAIBridgeInterceptionByID )(ctx ,id )
1933
+ }
1934
+
1881
1935
func (q * querier )GetAPIKeyByID (ctx context.Context ,id string ) (database.APIKey ,error ) {
1882
1936
return fetch (q .log ,q .auth ,q .db .GetAPIKeyByID )(ctx ,id )
1883
1937
}
@@ -3757,6 +3811,34 @@ func (q *querier) GetWorkspacesEligibleForTransition(ctx context.Context, now ti
3757
3811
return q .db .GetWorkspacesEligibleForTransition (ctx ,now )
3758
3812
}
3759
3813
3814
+ func (q * querier )InsertAIBridgeInterception (ctx context.Context ,arg database.InsertAIBridgeInterceptionParams ) (database.AIBridgeInterception ,error ) {
3815
+ return insert (q .log ,q .auth ,rbac .ResourceAibridgeInterception .WithOwner (arg .InitiatorID .String ()),q .db .InsertAIBridgeInterception )(ctx ,arg )
3816
+ }
3817
+
3818
+ func (q * querier )InsertAIBridgeTokenUsage (ctx context.Context ,arg database.InsertAIBridgeTokenUsageParams )error {
3819
+ // All aibridge_token_usages records belong to the initiator of their associated session.
3820
+ if err := q .authorizeAIBridgeInterceptionUpdate (ctx ,arg .InterceptionID );err != nil {
3821
+ return err
3822
+ }
3823
+ return q .db .InsertAIBridgeTokenUsage (ctx ,arg )
3824
+ }
3825
+
3826
+ func (q * querier )InsertAIBridgeToolUsage (ctx context.Context ,arg database.InsertAIBridgeToolUsageParams )error {
3827
+ // All aibridge_tool_usages records belong to the initiator of their associated session.
3828
+ if err := q .authorizeAIBridgeInterceptionUpdate (ctx ,arg .InterceptionID );err != nil {
3829
+ return err
3830
+ }
3831
+ return q .db .InsertAIBridgeToolUsage (ctx ,arg )
3832
+ }
3833
+
3834
+ func (q * querier )InsertAIBridgeUserPrompt (ctx context.Context ,arg database.InsertAIBridgeUserPromptParams )error {
3835
+ // All aibridge_user_prompts records belong to the initiator of their associated session.
3836
+ if err := q .authorizeAIBridgeInterceptionUpdate (ctx ,arg .InterceptionID );err != nil {
3837
+ return err
3838
+ }
3839
+ return q .db .InsertAIBridgeUserPrompt (ctx ,arg )
3840
+ }
3841
+
3760
3842
func (q * querier )InsertAPIKey (ctx context.Context ,arg database.InsertAPIKeyParams ) (database.APIKey ,error ) {
3761
3843
// TODO(Cian): ideally this would be encoded in the policy, but system users are just members and we
3762
3844
// don't currently have a capability to conditionally deny creating resources by owner ID in a role.