@@ -19,6 +19,7 @@ import (
19
19
"github.com/tabbed/pqtype"
20
20
"golang.org/x/exp/maps"
21
21
"golang.org/x/exp/slices"
22
+ "golang.org/x/oauth2"
22
23
"golang.org/x/xerrors"
23
24
protobuf "google.golang.org/protobuf/proto"
24
25
@@ -27,6 +28,7 @@ import (
27
28
"github.com/coder/coder/coderd/audit"
28
29
"github.com/coder/coder/coderd/database"
29
30
"github.com/coder/coder/coderd/database/dbauthz"
31
+ "github.com/coder/coder/coderd/httpmw"
30
32
"github.com/coder/coder/coderd/parameter"
31
33
"github.com/coder/coder/coderd/schedule"
32
34
"github.com/coder/coder/coderd/telemetry"
@@ -58,6 +60,7 @@ type Server struct {
58
60
TemplateScheduleStore * atomic.Pointer [schedule.TemplateScheduleStore ]
59
61
60
62
AcquireJobDebounce time.Duration
63
+ OIDCConfig httpmw.OAuth2Config
61
64
}
62
65
63
66
// AcquireJob queries the database to lock a job.
@@ -168,6 +171,14 @@ func (server *Server) AcquireJob(ctx context.Context, _ *proto.Empty) (*proto.Ac
168
171
return nil , failJob (fmt .Sprintf ("publish workspace update: %s" , err ))
169
172
}
170
173
174
+ var workspaceOwnerOIDCAccessToken string
175
+ if server .OIDCConfig != nil {
176
+ workspaceOwnerOIDCAccessToken , err = obtainOIDCAccessToken (ctx , server .Database , server .OIDCConfig , owner .ID )
177
+ if err != nil {
178
+ return nil , failJob (fmt .Sprintf ("obtain OIDC access token: %s" , err ))
179
+ }
180
+ }
181
+
171
182
// Compute parameters for the workspace to consume.
172
183
parameters , err := parameter .Compute (ctx , server .Database , parameter.ComputeScope {
173
184
TemplateImportJobID : templateVersion .JobID ,
@@ -208,15 +219,16 @@ func (server *Server) AcquireJob(ctx context.Context, _ *proto.Empty) (*proto.Ac
208
219
RichParameterValues : convertRichParameterValues (workspaceBuildParameters ),
209
220
VariableValues : asVariableValues (templateVariables ),
210
221
Metadata : & sdkproto.Provision_Metadata {
211
- CoderUrl : server .AccessURL .String (),
212
- WorkspaceTransition : transition ,
213
- WorkspaceName : workspace .Name ,
214
- WorkspaceOwner : owner .Username ,
215
- WorkspaceOwnerEmail : owner .Email ,
216
- WorkspaceId : workspace .ID .String (),
217
- WorkspaceOwnerId : owner .ID .String (),
218
- TemplateName : template .Name ,
219
- TemplateVersion : templateVersion .Name ,
222
+ CoderUrl : server .AccessURL .String (),
223
+ WorkspaceTransition : transition ,
224
+ WorkspaceName : workspace .Name ,
225
+ WorkspaceOwner : owner .Username ,
226
+ WorkspaceOwnerEmail : owner .Email ,
227
+ WorkspaceOwnerOidcAccessToken : workspaceOwnerOIDCAccessToken ,
228
+ WorkspaceId : workspace .ID .String (),
229
+ WorkspaceOwnerId : owner .ID .String (),
230
+ TemplateName : template .Name ,
231
+ TemplateVersion : templateVersion .Name ,
220
232
},
221
233
},
222
234
}
@@ -1295,6 +1307,51 @@ func InsertWorkspaceResource(ctx context.Context, db database.Store, jobID uuid.
1295
1307
return nil
1296
1308
}
1297
1309
1310
+ // obtainOIDCAccessToken returns a valid OpenID Connect access token
1311
+ // for the user if it's able to obtain one, otherwise it returns an empty string.
1312
+ func obtainOIDCAccessToken (ctx context.Context , db database.Store , oidcConfig httpmw.OAuth2Config , userID uuid.UUID ) (string , error ) {
1313
+ link , err := db .GetUserLinkByUserIDLoginType (ctx , database.GetUserLinkByUserIDLoginTypeParams {
1314
+ UserID : userID ,
1315
+ LoginType : database .LoginTypeOIDC ,
1316
+ })
1317
+ if errors .Is (err , sql .ErrNoRows ) {
1318
+ err = nil
1319
+ }
1320
+ if err != nil {
1321
+ return "" , xerrors .Errorf ("get owner oidc link: %w" , err )
1322
+ }
1323
+
1324
+ if link .OAuthExpiry .Before (database .Now ()) && ! link .OAuthExpiry .IsZero () && link .OAuthRefreshToken != "" {
1325
+ token , err := oidcConfig .TokenSource (ctx , & oauth2.Token {
1326
+ AccessToken : link .OAuthAccessToken ,
1327
+ RefreshToken : link .OAuthRefreshToken ,
1328
+ Expiry : link .OAuthExpiry ,
1329
+ }).Token ()
1330
+ if err != nil {
1331
+ // If OIDC fails to refresh, we return an empty string and don't fail.
1332
+ // There isn't a way to hard-opt in to OIDC from a template, so we don't
1333
+ // want to fail builds if users haven't authenticated for a while or something.
1334
+ return "" , nil
1335
+ }
1336
+ link .OAuthAccessToken = token .AccessToken
1337
+ link .OAuthRefreshToken = token .RefreshToken
1338
+ link .OAuthExpiry = token .Expiry
1339
+
1340
+ link , err = db .UpdateUserLink (ctx , database.UpdateUserLinkParams {
1341
+ UserID : userID ,
1342
+ LoginType : database .LoginTypeOIDC ,
1343
+ OAuthAccessToken : link .OAuthAccessToken ,
1344
+ OAuthRefreshToken : link .OAuthRefreshToken ,
1345
+ OAuthExpiry : link .OAuthExpiry ,
1346
+ })
1347
+ if err != nil {
1348
+ return "" , xerrors .Errorf ("update user link: %w" , err )
1349
+ }
1350
+ }
1351
+
1352
+ return link .OAuthAccessToken , nil
1353
+ }
1354
+
1298
1355
func convertValidationTypeSystem (typeSystem sdkproto.ParameterSchema_TypeSystem ) (database.ParameterTypeSystem , error ) {
1299
1356
switch typeSystem {
1300
1357
case sdkproto .ParameterSchema_None :
0 commit comments