|
|
@@ -17,8 +17,17 @@ import (
|
|
17
|
17
|
"strconv"
|
|
18
|
18
|
"sync"
|
|
19
|
19
|
"time"
|
|
|
20
|
+ "strings"
|
|
20
|
21
|
)
|
|
21
|
22
|
|
|
|
23
|
+// Define a struct to parse the .well-known/openid-configuration response
|
|
|
24
|
+type OidcEndpoint struct {
|
|
|
25
|
+ Issuer string `json:"issuer"`
|
|
|
26
|
+ AuthURL string `json:"authorization_endpoint"`
|
|
|
27
|
+ TokenURL string `json:"token_endpoint"`
|
|
|
28
|
+ UserInfo string `json:"userinfo_endpoint"`
|
|
|
29
|
+}
|
|
|
30
|
+
|
|
22
|
31
|
type OauthService struct {
|
|
23
|
32
|
}
|
|
24
|
33
|
|
|
|
@@ -78,6 +87,14 @@ type GoogleUserdata struct {
|
|
78
|
87
|
Picture string `json:"picture"`
|
|
79
|
88
|
VerifiedEmail bool `json:"verified_email"`
|
|
80
|
89
|
}
|
|
|
90
|
+type OidcUserdata struct {
|
|
|
91
|
+ Sub string `json:"sub"`
|
|
|
92
|
+ Email string `json:"email"`
|
|
|
93
|
+ VerifiedEmail bool `json:"email_verified"`
|
|
|
94
|
+ Name string `json:"name"`
|
|
|
95
|
+ PreferredUsername string `json:"preferred_username"`
|
|
|
96
|
+}
|
|
|
97
|
+
|
|
81
|
98
|
type OauthCacheItem struct {
|
|
82
|
99
|
UserId uint `json:"user_id"`
|
|
83
|
100
|
Id string `json:"id"` //rustdesk的设备ID
|
|
|
@@ -137,35 +154,102 @@ func (os *OauthService) BeginAuth(op string) (error error, code, url string) {
|
|
137
|
154
|
return err, code, ""
|
|
138
|
155
|
}
|
|
139
|
156
|
|
|
140
|
|
-// GetOauthConfig 获取配置
|
|
|
157
|
+// Method to fetch OIDC configuration dynamically
|
|
|
158
|
+func FetchOidcConfig(issuer string) (error, OidcEndpoint) {
|
|
|
159
|
+ configURL := strings.TrimSuffix(issuer, "/") + "/.well-known/openid-configuration"
|
|
|
160
|
+
|
|
|
161
|
+ // Get the HTTP client (with or without proxy based on configuration)
|
|
|
162
|
+ client := getHTTPClientWithProxy()
|
|
|
163
|
+
|
|
|
164
|
+ resp, err := client.Get(configURL)
|
|
|
165
|
+ if err != nil {
|
|
|
166
|
+ return errors.New("failed to fetch OIDC configuration"), OidcEndpoint{}
|
|
|
167
|
+ }
|
|
|
168
|
+ defer resp.Body.Close()
|
|
|
169
|
+
|
|
|
170
|
+ if resp.StatusCode != http.StatusOK {
|
|
|
171
|
+ return errors.New("OIDC configuration not found, status code: %d"), OidcEndpoint{}
|
|
|
172
|
+ }
|
|
|
173
|
+
|
|
|
174
|
+ var endpoint OidcEndpoint
|
|
|
175
|
+ if err := json.NewDecoder(resp.Body).Decode(&endpoint); err != nil {
|
|
|
176
|
+ return errors.New("failed to parse OIDC configuration"), OidcEndpoint{}
|
|
|
177
|
+ }
|
|
|
178
|
+
|
|
|
179
|
+ return nil, endpoint
|
|
|
180
|
+}
|
|
|
181
|
+
|
|
|
182
|
+// GetOauthConfig retrieves the OAuth2 configuration based on the provider type
|
|
141
|
183
|
func (os *OauthService) GetOauthConfig(op string) (error, *oauth2.Config) {
|
|
142
|
|
- if op == model.OauthTypeGithub {
|
|
143
|
|
- g := os.InfoByOp(model.OauthTypeGithub)
|
|
144
|
|
- if g.Id == 0 || g.ClientId == "" || g.ClientSecret == "" || g.RedirectUrl == "" {
|
|
145
|
|
- return errors.New("ConfigNotFound"), nil
|
|
146
|
|
- }
|
|
147
|
|
- return nil, &oauth2.Config{
|
|
148
|
|
- ClientID: g.ClientId,
|
|
149
|
|
- ClientSecret: g.ClientSecret,
|
|
150
|
|
- RedirectURL: g.RedirectUrl,
|
|
151
|
|
- Endpoint: github.Endpoint,
|
|
152
|
|
- Scopes: []string{"read:user", "user:email"},
|
|
153
|
|
- }
|
|
|
184
|
+ switch op {
|
|
|
185
|
+ case model.OauthTypeGithub:
|
|
|
186
|
+ return os.getGithubConfig()
|
|
|
187
|
+ case model.OauthTypeGoogle:
|
|
|
188
|
+ return os.getGoogleConfig()
|
|
|
189
|
+ case model.OauthTypeOidc:
|
|
|
190
|
+ return os.getOidcConfig()
|
|
|
191
|
+ default:
|
|
|
192
|
+ return errors.New("unsupported OAuth type"), nil
|
|
154
|
193
|
}
|
|
155
|
|
- if op == model.OauthTypeGoogle {
|
|
156
|
|
- g := os.InfoByOp(model.OauthTypeGoogle)
|
|
157
|
|
- if g.Id == 0 || g.ClientId == "" || g.ClientSecret == "" || g.RedirectUrl == "" {
|
|
158
|
|
- return errors.New("ConfigNotFound"), nil
|
|
159
|
|
- }
|
|
160
|
|
- return nil, &oauth2.Config{
|
|
161
|
|
- ClientID: g.ClientId,
|
|
162
|
|
- ClientSecret: g.ClientSecret,
|
|
163
|
|
- RedirectURL: g.RedirectUrl,
|
|
164
|
|
- Endpoint: google.Endpoint,
|
|
165
|
|
- Scopes: []string{"https://www.googleapis.com/auth/userinfo.profile", "https://www.googleapis.com/auth/userinfo.email"},
|
|
166
|
|
- }
|
|
|
194
|
+}
|
|
|
195
|
+
|
|
|
196
|
+// Helper function to get GitHub OAuth2 configuration
|
|
|
197
|
+func (os *OauthService) getGithubConfig() (error, *oauth2.Config) {
|
|
|
198
|
+ g := os.InfoByOp(model.OauthTypeGithub)
|
|
|
199
|
+ if g.Id == 0 || g.ClientId == "" || g.ClientSecret == "" || g.RedirectUrl == "" {
|
|
|
200
|
+ return errors.New("ConfigNotFound"), nil
|
|
|
201
|
+ }
|
|
|
202
|
+ return nil, &oauth2.Config{
|
|
|
203
|
+ ClientID: g.ClientId,
|
|
|
204
|
+ ClientSecret: g.ClientSecret,
|
|
|
205
|
+ RedirectURL: g.RedirectUrl,
|
|
|
206
|
+ Endpoint: github.Endpoint,
|
|
|
207
|
+ Scopes: []string{"read:user", "user:email"},
|
|
|
208
|
+ }
|
|
|
209
|
+}
|
|
|
210
|
+
|
|
|
211
|
+// Helper function to get Google OAuth2 configuration
|
|
|
212
|
+func (os *OauthService) getGoogleConfig() (error, *oauth2.Config) {
|
|
|
213
|
+ g := os.InfoByOp(model.OauthTypeGoogle)
|
|
|
214
|
+ if g.Id == 0 || g.ClientId == "" || g.ClientSecret == "" || g.RedirectUrl == "" {
|
|
|
215
|
+ return errors.New("ConfigNotFound"), nil
|
|
|
216
|
+ }
|
|
|
217
|
+ return nil, &oauth2.Config{
|
|
|
218
|
+ ClientID: g.ClientId,
|
|
|
219
|
+ ClientSecret: g.ClientSecret,
|
|
|
220
|
+ RedirectURL: g.RedirectUrl,
|
|
|
221
|
+ Endpoint: google.Endpoint,
|
|
|
222
|
+ Scopes: []string{"https://www.googleapis.com/auth/userinfo.profile", "https://www.googleapis.com/auth/userinfo.email"},
|
|
|
223
|
+ }
|
|
|
224
|
+}
|
|
|
225
|
+
|
|
|
226
|
+// Helper function to get OIDC OAuth2 configuration
|
|
|
227
|
+func (os *OauthService) getOidcConfig() (error, *oauth2.Config) {
|
|
|
228
|
+ g := os.InfoByOp(model.OauthTypeOidc)
|
|
|
229
|
+ if g.Id == 0 || g.ClientId == "" || g.ClientSecret == "" || g.RedirectUrl == "" || g.Issuer == "" {
|
|
|
230
|
+ return errors.New("ConfigNotFound"), nil
|
|
|
231
|
+ }
|
|
|
232
|
+
|
|
|
233
|
+ // Set scopes
|
|
|
234
|
+ scopes := strings.TrimSpace(g.Scopes)
|
|
|
235
|
+ if scopes == "" {
|
|
|
236
|
+ scopes = "openid,profile,email"
|
|
|
237
|
+ }
|
|
|
238
|
+ scopeList := strings.Split(scopes, ",")
|
|
|
239
|
+ err, endpoint := FetchOidcConfig(g.Issuer)
|
|
|
240
|
+ if err != nil {
|
|
|
241
|
+ return err, nil
|
|
|
242
|
+ }
|
|
|
243
|
+ return nil, &oauth2.Config{
|
|
|
244
|
+ ClientID: g.ClientId,
|
|
|
245
|
+ ClientSecret: g.ClientSecret,
|
|
|
246
|
+ RedirectURL: g.RedirectUrl,
|
|
|
247
|
+ Endpoint: oauth2.Endpoint{
|
|
|
248
|
+ AuthURL: endpoint.AuthURL,
|
|
|
249
|
+ TokenURL: endpoint.TokenURL,
|
|
|
250
|
+ },
|
|
|
251
|
+ Scopes: scopeList,
|
|
167
|
252
|
}
|
|
168
|
|
- return errors.New("ConfigNotFound"), nil
|
|
169
|
253
|
}
|
|
170
|
254
|
|
|
171
|
255
|
func getHTTPClientWithProxy() *http.Client {
|
|
|
@@ -269,6 +353,53 @@ func (os *OauthService) GoogleCallback(code string) (error error, userData *Goog
|
|
269
|
353
|
return
|
|
270
|
354
|
}
|
|
271
|
355
|
|
|
|
356
|
+func (os *OauthService) OidcCallback(code string) (error error, userData *OidcUserdata) {
|
|
|
357
|
+ err, oauthConfig := os.GetOauthConfig(model.OauthTypeOidc)
|
|
|
358
|
+ if err != nil {
|
|
|
359
|
+ return err, nil
|
|
|
360
|
+ }
|
|
|
361
|
+ // 使用代理配置创建 HTTP 客户端
|
|
|
362
|
+ httpClient := getHTTPClientWithProxy()
|
|
|
363
|
+ ctx := context.WithValue(context.Background(), oauth2.HTTPClient, httpClient)
|
|
|
364
|
+
|
|
|
365
|
+ token, err := oauthConfig.Exchange(ctx, code)
|
|
|
366
|
+ if err != nil {
|
|
|
367
|
+ global.Logger.Warn("oauthConfig.Exchange() failed: ", err)
|
|
|
368
|
+ error = errors.New("GetOauthTokenError")
|
|
|
369
|
+ return
|
|
|
370
|
+ }
|
|
|
371
|
+
|
|
|
372
|
+ // 使用带有代理的 HTTP 客户端获取用户信息
|
|
|
373
|
+ client := oauthConfig.Client(ctx, token)
|
|
|
374
|
+ g := os.InfoByOp(model.OauthTypeOidc)
|
|
|
375
|
+ err, endpoint := FetchOidcConfig(g.Issuer)
|
|
|
376
|
+ if err != nil {
|
|
|
377
|
+ global.Logger.Warn("failed fetching OIDC configuration: ", err)
|
|
|
378
|
+ error = errors.New("FetchOidcConfigError")
|
|
|
379
|
+ return
|
|
|
380
|
+ }
|
|
|
381
|
+ resp, err := client.Get(endpoint.UserInfo)
|
|
|
382
|
+ if err != nil {
|
|
|
383
|
+ global.Logger.Warn("failed getting user info: ", err)
|
|
|
384
|
+ error = errors.New("GetOauthUserInfoError")
|
|
|
385
|
+ return
|
|
|
386
|
+ }
|
|
|
387
|
+ defer func(Body io.ReadCloser) {
|
|
|
388
|
+ err := Body.Close()
|
|
|
389
|
+ if err != nil {
|
|
|
390
|
+ global.Logger.Warn("failed closing response body: ", err)
|
|
|
391
|
+ }
|
|
|
392
|
+ }(resp.Body)
|
|
|
393
|
+
|
|
|
394
|
+ // 解析用户信息
|
|
|
395
|
+ if err = json.NewDecoder(resp.Body).Decode(&userData); err != nil {
|
|
|
396
|
+ global.Logger.Warn("failed decoding user info: ", err)
|
|
|
397
|
+ error = errors.New("DecodeOauthUserInfoError")
|
|
|
398
|
+ return
|
|
|
399
|
+ }
|
|
|
400
|
+ return
|
|
|
401
|
+}
|
|
|
402
|
+
|
|
272
|
403
|
func (os *OauthService) UserThirdInfo(op, openid string) *model.UserThird {
|
|
273
|
404
|
ut := &model.UserThird{}
|
|
274
|
405
|
global.DB.Where("open_id = ? and third_type = ?", openid, op).First(ut)
|
|
|
@@ -282,6 +413,11 @@ func (os *OauthService) BindGithubUser(openid, username string, userId uint) err
|
|
282
|
413
|
func (os *OauthService) BindGoogleUser(email, username string, userId uint) error {
|
|
283
|
414
|
return os.BindOauthUser(model.OauthTypeGoogle, email, username, userId)
|
|
284
|
415
|
}
|
|
|
416
|
+
|
|
|
417
|
+func (os *OauthService) BindOidcUser(sub, username string, userId uint) error {
|
|
|
418
|
+ return os.BindOauthUser(model.OauthTypeOidc, sub, username, userId)
|
|
|
419
|
+}
|
|
|
420
|
+
|
|
285
|
421
|
func (os *OauthService) BindOauthUser(thirdType, openid, username string, userId uint) error {
|
|
286
|
422
|
utr := &model.UserThird{
|
|
287
|
423
|
OpenId: openid,
|
|
|
@@ -298,6 +434,9 @@ func (os *OauthService) UnBindGithubUser(userid uint) error {
|
|
298
|
434
|
func (os *OauthService) UnBindGoogleUser(userid uint) error {
|
|
299
|
435
|
return os.UnBindThird(model.OauthTypeGoogle, userid)
|
|
300
|
436
|
}
|
|
|
437
|
+func (os *OauthService) UnBindOidcUser(userid uint) error {
|
|
|
438
|
+ return os.UnBindThird(model.OauthTypeOidc, userid)
|
|
|
439
|
+}
|
|
301
|
440
|
func (os *OauthService) UnBindThird(thirdType string, userid uint) error {
|
|
302
|
441
|
return global.DB.Where("user_id = ? and third_type = ?", userid, thirdType).Delete(&model.UserThird{}).Error
|
|
303
|
442
|
}
|