@@ -18,13 +18,15 @@ import (
18
18
"github.com/tabbed/pqtype"
19
19
"golang.org/x/exp/maps"
20
20
"golang.org/x/exp/slices"
21
+ "golang.org/x/oauth2"
21
22
"golang.org/x/xerrors"
22
23
protobuf "google.golang.org/protobuf/proto"
23
24
24
25
"cdr.dev/slog"
25
26
26
27
"github.com/coder/coder/coderd/audit"
27
28
"github.com/coder/coder/coderd/database"
29
+ "github.com/coder/coder/coderd/httpmw"
28
30
"github.com/coder/coder/coderd/parameter"
29
31
"github.com/coder/coder/coderd/telemetry"
30
32
"github.com/coder/coder/codersdk"
@@ -52,6 +54,7 @@ type Server struct {
52
54
Auditor * atomic.Pointer [audit.Auditor ]
53
55
54
56
AcquireJobDebounce time.Duration
57
+ OIDCConfig httpmw.OAuth2Config
55
58
}
56
59
57
60
// AcquireJob queries the database to lock a job.
@@ -155,6 +158,14 @@ func (server *Server) AcquireJob(ctx context.Context, _ *proto.Empty) (*proto.Ac
155
158
return nil , failJob (fmt .Sprintf ("publish workspace update: %s" , err ))
156
159
}
157
160
161
+ var workspaceOwnerOIDCAccessToken string
162
+ if server .OIDCConfig != nil {
163
+ workspaceOwnerOIDCAccessToken , err = obtainOIDCAccessToken (ctx , server .Database , server .OIDCConfig , owner .ID )
164
+ if err != nil {
165
+ return nil , failJob (fmt .Sprintf ("obtain OIDC access token: %s" , err ))
166
+ }
167
+ }
168
+
158
169
// Compute parameters for the workspace to consume.
159
170
parameters , err := parameter .Compute (ctx , server .Database , parameter.ComputeScope {
160
171
TemplateImportJobID : templateVersion .JobID ,
@@ -194,13 +205,14 @@ func (server *Server) AcquireJob(ctx context.Context, _ *proto.Empty) (*proto.Ac
194
205
ParameterValues : protoParameters ,
195
206
RichParameterValues : convertRichParameterValues (workspaceBuildParameters ),
196
207
Metadata : & sdkproto.Provision_Metadata {
197
- CoderUrl : server .AccessURL .String (),
198
- WorkspaceTransition : transition ,
199
- WorkspaceName : workspace .Name ,
200
- WorkspaceOwner : owner .Username ,
201
- WorkspaceOwnerEmail : owner .Email ,
202
- WorkspaceId : workspace .ID .String (),
203
- WorkspaceOwnerId : owner .ID .String (),
208
+ CoderUrl : server .AccessURL .String (),
209
+ WorkspaceTransition : transition ,
210
+ WorkspaceName : workspace .Name ,
211
+ WorkspaceOwner : owner .Username ,
212
+ WorkspaceOwnerEmail : owner .Email ,
213
+ WorkspaceOwnerOidcAccessToken : workspaceOwnerOIDCAccessToken ,
214
+ WorkspaceId : workspace .ID .String (),
215
+ WorkspaceOwnerId : owner .ID .String (),
204
216
},
205
217
},
206
218
}
@@ -1062,6 +1074,51 @@ func InsertWorkspaceResource(ctx context.Context, db database.Store, jobID uuid.
1062
1074
return nil
1063
1075
}
1064
1076
1077
+ // obtainOIDCAccessToken returns a valid OpenID Connect access token
1078
+ // for the user if it's able to obtain one, otherwise it returns an empty string.
1079
+ func obtainOIDCAccessToken (ctx context.Context , db database.Store , oidcConfig httpmw.OAuth2Config , userID uuid.UUID ) (string , error ) {
1080
+ link , err := db .GetUserLinkByUserIDLoginType (ctx , database.GetUserLinkByUserIDLoginTypeParams {
1081
+ UserID : userID ,
1082
+ LoginType : database .LoginTypeOIDC ,
1083
+ })
1084
+ if errors .Is (err , sql .ErrNoRows ) {
1085
+ err = nil
1086
+ }
1087
+ if err != nil {
1088
+ return "" , xerrors .Errorf ("get owner oidc link: %w" , err )
1089
+ }
1090
+
1091
+ if link .OAuthExpiry .Before (database .Now ()) && ! link .OAuthExpiry .IsZero () && link .OAuthRefreshToken != "" {
1092
+ token , err := oidcConfig .TokenSource (ctx , & oauth2.Token {
1093
+ AccessToken : link .OAuthAccessToken ,
1094
+ RefreshToken : link .OAuthRefreshToken ,
1095
+ Expiry : link .OAuthExpiry ,
1096
+ }).Token ()
1097
+ if err != nil {
1098
+ // If OIDC fails to refresh, we return an empty string and don't fail.
1099
+ // There isn't a way to hard-opt in to OIDC from a template, so we don't
1100
+ // want to fail builds if users haven't authenticated for a while or something.
1101
+ return "" , nil
1102
+ }
1103
+ link .OAuthAccessToken = token .AccessToken
1104
+ link .OAuthRefreshToken = token .RefreshToken
1105
+ link .OAuthExpiry = token .Expiry
1106
+
1107
+ link , err = db .UpdateUserLink (ctx , database.UpdateUserLinkParams {
1108
+ UserID : userID ,
1109
+ LoginType : database .LoginTypeOIDC ,
1110
+ OAuthAccessToken : link .OAuthAccessToken ,
1111
+ OAuthRefreshToken : link .OAuthRefreshToken ,
1112
+ OAuthExpiry : link .OAuthExpiry ,
1113
+ })
1114
+ if err != nil {
1115
+ return "" , xerrors .Errorf ("update user link: %w" , err )
1116
+ }
1117
+ }
1118
+
1119
+ return link .OAuthAccessToken , nil
1120
+ }
1121
+
1065
1122
func convertValidationTypeSystem (typeSystem sdkproto.ParameterSchema_TypeSystem ) (database.ParameterTypeSystem , error ) {
1066
1123
switch typeSystem {
1067
1124
case sdkproto .ParameterSchema_None :
0 commit comments