From 4500757acd3fb3c5f4fea94ee71247eedc4013d6 Mon Sep 17 00:00:00 2001 From: Maxim Slipenko Date: Mon, 9 Dec 2024 18:59:11 +0300 Subject: [PATCH 1/9] feat: add synchronization for SSH keys with OpenID Connect Co-authored-by: Kirill Kolmykov --- routers/web/admin/auths.go | 1 + services/auth/source/oauth2/providers_base.go | 4 + .../auth/source/oauth2/providers_openid.go | 4 + services/auth/source/oauth2/source.go | 26 ++++-- services/auth/source/oauth2/source_sync.go | 92 +++++++++++++++++++ services/forms/auth_form.go | 1 + templates/admin/auth/edit.tmpl | 25 +++-- templates/admin/auth/source/oauth.tmpl | 26 ++++-- web_src/js/features/admin/common.js | 6 +- 9 files changed, 158 insertions(+), 27 deletions(-) diff --git a/routers/web/admin/auths.go b/routers/web/admin/auths.go index 799b7e8a84..dcdc8e6a2a 100644 --- a/routers/web/admin/auths.go +++ b/routers/web/admin/auths.go @@ -197,6 +197,7 @@ func parseOAuth2Config(form forms.AuthenticationForm) *oauth2.Source { CustomURLMapping: customURLMapping, IconURL: form.Oauth2IconURL, Scopes: scopes, + AttributeSSHPublicKey: form.Oauth2AttributeSSHPublicKey, RequiredClaimName: form.Oauth2RequiredClaimName, RequiredClaimValue: form.Oauth2RequiredClaimValue, SkipLocalTwoFA: form.SkipLocalTwoFA, diff --git a/services/auth/source/oauth2/providers_base.go b/services/auth/source/oauth2/providers_base.go index 9d4ab106e5..63318b84ef 100644 --- a/services/auth/source/oauth2/providers_base.go +++ b/services/auth/source/oauth2/providers_base.go @@ -48,4 +48,8 @@ func (b *BaseProvider) CustomURLSettings() *CustomURLSettings { return nil } +func (b *BaseProvider) CanProvideSSHKeys() bool { + return false +} + var _ Provider = &BaseProvider{} diff --git a/services/auth/source/oauth2/providers_openid.go b/services/auth/source/oauth2/providers_openid.go index 285876d5ac..f606581271 100644 --- a/services/auth/source/oauth2/providers_openid.go +++ b/services/auth/source/oauth2/providers_openid.go @@ -51,6 +51,10 @@ func (o *OpenIDProvider) CustomURLSettings() *CustomURLSettings { return nil } +func (o *OpenIDProvider) CanProvideSSHKeys() bool { + return true +} + var _ GothProvider = &OpenIDProvider{} func init() { diff --git a/services/auth/source/oauth2/source.go b/services/auth/source/oauth2/source.go index 3454c9ad55..fe4823e778 100644 --- a/services/auth/source/oauth2/source.go +++ b/services/auth/source/oauth2/source.go @@ -4,6 +4,8 @@ package oauth2 import ( + "strings" + "code.gitea.io/gitea/models/auth" "code.gitea.io/gitea/modules/json" ) @@ -17,15 +19,16 @@ type Source struct { CustomURLMapping *CustomURLMapping IconURL string - Scopes []string - RequiredClaimName string - RequiredClaimValue string - GroupClaimName string - AdminGroup string - GroupTeamMap string - GroupTeamMapRemoval bool - RestrictedGroup string - SkipLocalTwoFA bool `json:",omitempty"` + Scopes []string + AttributeSSHPublicKey string + RequiredClaimName string + RequiredClaimValue string + GroupClaimName string + AdminGroup string + GroupTeamMap string + GroupTeamMapRemoval bool + RestrictedGroup string + SkipLocalTwoFA bool `json:",omitempty"` // reference to the authSource authSource *auth.Source @@ -41,6 +44,11 @@ func (source *Source) ToDB() ([]byte, error) { return json.Marshal(source) } +// ProvidesSSHKeys returns if this source provides SSH Keys +func (source *Source) ProvidesSSHKeys() bool { + return len(strings.TrimSpace(source.AttributeSSHPublicKey)) > 0 +} + // SetAuthSource sets the related AuthSource func (source *Source) SetAuthSource(authSource *auth.Source) { source.authSource = authSource diff --git a/services/auth/source/oauth2/source_sync.go b/services/auth/source/oauth2/source_sync.go index 5e30313c8f..667c0957fc 100644 --- a/services/auth/source/oauth2/source_sync.go +++ b/services/auth/source/oauth2/source_sync.go @@ -5,15 +5,20 @@ package oauth2 import ( "context" + "fmt" "time" "code.gitea.io/gitea/models/auth" "code.gitea.io/gitea/models/db" user_model "code.gitea.io/gitea/models/user" "code.gitea.io/gitea/modules/log" + "code.gitea.io/gitea/modules/util" "github.com/markbates/goth" + "github.com/markbates/goth/providers/openidConnect" "golang.org/x/oauth2" + + asymkey_model "code.gitea.io/gitea/models/asymkey" ) // Sync causes this OAuth2 source to synchronize its users with the db. @@ -108,7 +113,94 @@ func (source *Source) refresh(ctx context.Context, provider goth.Provider, u *us u.RefreshToken = token.RefreshToken } + needUserFetch := source.ProvidesSSHKeys() + + if needUserFetch { + fetchedUser, err := fetchUser(provider, token) + if err != nil { + log.Error("fetchUser: %v", err) + } else { + err = updateSSHKeys(ctx, source, user, &fetchedUser) + if err != nil { + log.Error("updateSshKeys: %v", err) + } + } + } + err = user_model.UpdateExternalUserByExternalID(ctx, u) return err } + +func fetchUser(provider goth.Provider, token *oauth2.Token) (goth.User, error) { + state, err := util.CryptoRandomString(40) + if err != nil { + return goth.User{}, err + } + + session, err := provider.BeginAuth(state) + if err != nil { + return goth.User{}, err + } + + if s, ok := session.(*openidConnect.Session); ok { + s.AccessToken = token.AccessToken + s.RefreshToken = token.RefreshToken + s.ExpiresAt = token.Expiry + s.IDToken = token.Extra("id_token").(string) + } + + gothUser, err := provider.FetchUser(session) + if err != nil { + return goth.User{}, err + } + + return gothUser, nil +} + +func updateSSHKeys( + ctx context.Context, + source *Source, + user *user_model.User, + fetchedUser *goth.User, +) error { + if source.ProvidesSSHKeys() { + sshKeys, err := getSSHKeys(source, fetchedUser) + if err != nil { + return err + } + + if asymkey_model.SynchronizePublicKeys(ctx, user, source.authSource, sshKeys) { + err = asymkey_model.RewriteAllPublicKeys(ctx) + if err != nil { + return err + } + } + } + + return nil +} + +func getSSHKeys(source *Source, gothUser *goth.User) ([]string, error) { + key := source.AttributeSSHPublicKey + value, exists := gothUser.RawData[key] + if !exists { + return nil, fmt.Errorf("attribute '%s' not found in user data", key) + } + + rawSlice, ok := value.([]interface{}) + if !ok { + return nil, fmt.Errorf("unexpected type for SSH public key, expected []interface{} but got %T", value) + } + + sshKeys := make([]string, 0, len(rawSlice)) + for i, v := range rawSlice { + str, ok := v.(string) + if !ok { + return nil, fmt.Errorf("unexpected element type at index %d in SSH public key array, expected string but got %T", i, v) + } + sshKeys = append(sshKeys, str) + } + + return sshKeys, nil +} diff --git a/services/forms/auth_form.go b/services/forms/auth_form.go index f0da63155a..39aae51756 100644 --- a/services/forms/auth_form.go +++ b/services/forms/auth_form.go @@ -75,6 +75,7 @@ type AuthenticationForm struct { Oauth2RestrictedGroup string Oauth2GroupTeamMap string `binding:"ValidGroupTeamMap"` Oauth2GroupTeamMapRemoval bool + Oauth2AttributeSSHPublicKey string SkipLocalTwoFA bool SSPIAutoCreateUsers bool SSPIAutoActivateUsers bool diff --git a/templates/admin/auth/edit.tmpl b/templates/admin/auth/edit.tmpl index a8b2049f92..84fefc0484 100644 --- a/templates/admin/auth/edit.tmpl +++ b/templates/admin/auth/edit.tmpl @@ -326,19 +326,28 @@ - {{range .OAuth2Providers}}{{if .CustomURLSettings}} - - - - - - - {{end}}{{end}} + {{range .OAuth2Providers}} + {{if .CustomURLSettings}} + + + + + + + {{end}} + {{if .CanProvideSSHKeys}} + + {{end}} + {{end}}
+
+ + +
diff --git a/templates/admin/auth/source/oauth.tmpl b/templates/admin/auth/source/oauth.tmpl index 0560cc8256..7d0a64d269 100644 --- a/templates/admin/auth/source/oauth.tmpl +++ b/templates/admin/auth/source/oauth.tmpl @@ -63,19 +63,27 @@
- {{range .OAuth2Providers}}{{if .CustomURLSettings}} - - - - - - - {{end}}{{end}} - + {{range .OAuth2Providers}} + {{if .CustomURLSettings}} + + + + + + + {{end}} + {{if .CanProvideSSHKeys}} + + {{end}} + {{end}}
+
+ + +
diff --git a/web_src/js/features/admin/common.js b/web_src/js/features/admin/common.js index 1a5bd6e490..a42d8261f1 100644 --- a/web_src/js/features/admin/common.js +++ b/web_src/js/features/admin/common.js @@ -62,7 +62,7 @@ export function initAdminCommon() { } function onOAuth2Change(applyDefaultValues) { - hideElem('.open_id_connect_auto_discovery_url, .oauth2_use_custom_url'); + hideElem('.open_id_connect_auto_discovery_url, .oauth2_use_custom_url, .oauth2_attribute_ssh_public_key'); for (const input of document.querySelectorAll('.open_id_connect_auto_discovery_url input[required]')) { input.removeAttribute('required'); } @@ -85,6 +85,10 @@ export function initAdminCommon() { } } } + const canProvideSSHKeys = document.getElementById(`${provider}_canProvideSSHKeys`); + if (canProvideSSHKeys) { + showElem('.oauth2_attribute_ssh_public_key'); + } onOAuth2UseCustomURLChange(applyDefaultValues); } From 47fd9a421fd9d3a8cba87c1574ea3ae3a60f4cdb Mon Sep 17 00:00:00 2001 From: Maxim Slipenko Date: Tue, 10 Dec 2024 07:41:04 +0000 Subject: [PATCH 2/9] style: run make fmt --- services/auth/source/oauth2/source_sync.go | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/services/auth/source/oauth2/source_sync.go b/services/auth/source/oauth2/source_sync.go index 667c0957fc..b0b08d1a50 100644 --- a/services/auth/source/oauth2/source_sync.go +++ b/services/auth/source/oauth2/source_sync.go @@ -8,6 +8,7 @@ import ( "fmt" "time" + asymkey_model "code.gitea.io/gitea/models/asymkey" "code.gitea.io/gitea/models/auth" "code.gitea.io/gitea/models/db" user_model "code.gitea.io/gitea/models/user" @@ -17,8 +18,6 @@ import ( "github.com/markbates/goth" "github.com/markbates/goth/providers/openidConnect" "golang.org/x/oauth2" - - asymkey_model "code.gitea.io/gitea/models/asymkey" ) // Sync causes this OAuth2 source to synchronize its users with the db. @@ -188,7 +187,7 @@ func getSSHKeys(source *Source, gothUser *goth.User) ([]string, error) { return nil, fmt.Errorf("attribute '%s' not found in user data", key) } - rawSlice, ok := value.([]interface{}) + rawSlice, ok := value.([]any) if !ok { return nil, fmt.Errorf("unexpected type for SSH public key, expected []interface{} but got %T", value) } From a87e0d0e46724e7c5fd06bdfc4a7413bc098461c Mon Sep 17 00:00:00 2001 From: Maxim Slipenko Date: Tue, 10 Dec 2024 14:01:02 +0000 Subject: [PATCH 3/9] fix: do not rewrite when there are no keys --- services/auth/source/oauth2/source_sync.go | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/services/auth/source/oauth2/source_sync.go b/services/auth/source/oauth2/source_sync.go index b0b08d1a50..59590a8bb3 100644 --- a/services/auth/source/oauth2/source_sync.go +++ b/services/auth/source/oauth2/source_sync.go @@ -169,6 +169,10 @@ func updateSSHKeys( return err } + if len(sshKeys) == 0 { + return nil + } + if asymkey_model.SynchronizePublicKeys(ctx, user, source.authSource, sshKeys) { err = asymkey_model.RewriteAllPublicKeys(ctx) if err != nil { From 8bd1c7ff319bc4cec9f0e979edd91e1002c0407d Mon Sep 17 00:00:00 2001 From: Maxim Slipenko Date: Thu, 19 Dec 2024 10:47:47 +0000 Subject: [PATCH 4/9] feat: add synchronization for SSH keys in handleOAuth2SignIn --- routers/web/auth/oauth.go | 56 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 56 insertions(+) diff --git a/routers/web/auth/oauth.go b/routers/web/auth/oauth.go index fbdd47479a..16c3c9f9d4 100644 --- a/routers/web/auth/oauth.go +++ b/routers/web/auth/oauth.go @@ -48,6 +48,8 @@ import ( "github.com/markbates/goth/providers/openidConnect" "github.com/markbates/goth/providers/zoom" go_oauth2 "golang.org/x/oauth2" + + asymkey_model "code.gitea.io/gitea/models/asymkey" ) const ( @@ -1183,8 +1185,62 @@ func updateAvatarIfNeed(ctx *context.Context, url string, u *user_model.User) { } } +func getSSHKeys(source *oauth2.Source, gothUser *goth.User) ([]string, error) { + key := source.AttributeSSHPublicKey + value, exists := gothUser.RawData[key] + if !exists { + return nil, fmt.Errorf("attribute '%s' not found in user data", key) + } + + rawSlice, ok := value.([]any) + if !ok { + return nil, fmt.Errorf("unexpected type for SSH public key, expected []interface{} but got %T", value) + } + + sshKeys := make([]string, 0, len(rawSlice)) + for i, v := range rawSlice { + str, ok := v.(string) + if !ok { + return nil, fmt.Errorf("unexpected element type at index %d in SSH public key array, expected string but got %T", i, v) + } + sshKeys = append(sshKeys, str) + } + + return sshKeys, nil +} + +func updateSshPubIfNeed( + ctx *context.Context, + authSource *auth.Source, + fetchedUser *goth.User, + user *user_model.User, +) error { + oauth2Source := authSource.Cfg.(*oauth2.Source) + + if oauth2Source.ProvidesSSHKeys() { + sshKeys, err := getSSHKeys(oauth2Source, fetchedUser) + if err != nil { + return err + } + + if len(sshKeys) == 0 { + return nil + } + + if asymkey_model.SynchronizePublicKeys(ctx, user, authSource, sshKeys) { + err = asymkey_model.RewriteAllPublicKeys(ctx) + if err != nil { + return err + } + } + } + + return nil +} + func handleOAuth2SignIn(ctx *context.Context, source *auth.Source, u *user_model.User, gothUser goth.User) { updateAvatarIfNeed(ctx, gothUser.AvatarURL, u) + updateSshPubIfNeed(ctx, source, &gothUser, u) needs2FA := false if !source.Cfg.(*oauth2.Source).SkipLocalTwoFA { From 32de0745e438a55bb4fc3cb10c29580af89f4618 Mon Sep 17 00:00:00 2001 From: Maxim Slipenko Date: Thu, 19 Dec 2024 11:17:28 +0000 Subject: [PATCH 5/9] style: fix fmt and lint --- routers/web/auth/oauth.go | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/routers/web/auth/oauth.go b/routers/web/auth/oauth.go index 16c3c9f9d4..48aaca17cb 100644 --- a/routers/web/auth/oauth.go +++ b/routers/web/auth/oauth.go @@ -17,6 +17,7 @@ import ( "sort" "strings" + asymkey_model "code.gitea.io/gitea/models/asymkey" "code.gitea.io/gitea/models/auth" org_model "code.gitea.io/gitea/models/organization" user_model "code.gitea.io/gitea/models/user" @@ -48,8 +49,6 @@ import ( "github.com/markbates/goth/providers/openidConnect" "github.com/markbates/goth/providers/zoom" go_oauth2 "golang.org/x/oauth2" - - asymkey_model "code.gitea.io/gitea/models/asymkey" ) const ( @@ -1209,7 +1208,7 @@ func getSSHKeys(source *oauth2.Source, gothUser *goth.User) ([]string, error) { return sshKeys, nil } -func updateSshPubIfNeed( +func updateSSHPubIfNeed( ctx *context.Context, authSource *auth.Source, fetchedUser *goth.User, @@ -1240,7 +1239,11 @@ func updateSshPubIfNeed( func handleOAuth2SignIn(ctx *context.Context, source *auth.Source, u *user_model.User, gothUser goth.User) { updateAvatarIfNeed(ctx, gothUser.AvatarURL, u) - updateSshPubIfNeed(ctx, source, &gothUser, u) + err := updateSSHPubIfNeed(ctx, source, &gothUser, u) + if err != nil { + ctx.ServerError("updateSSHPubIfNeed", err) + return + } needs2FA := false if !source.Cfg.(*oauth2.Source).SkipLocalTwoFA { From 515eec3d1a02603c07e2bc39bb351792d4ac95e8 Mon Sep 17 00:00:00 2001 From: Maxim Slipenko Date: Sat, 21 Dec 2024 21:25:38 +0000 Subject: [PATCH 6/9] remove `len(sshKeys) == 0` check --- routers/web/auth/oauth.go | 4 ---- 1 file changed, 4 deletions(-) diff --git a/routers/web/auth/oauth.go b/routers/web/auth/oauth.go index 48aaca17cb..4d8fbcb7a5 100644 --- a/routers/web/auth/oauth.go +++ b/routers/web/auth/oauth.go @@ -1222,10 +1222,6 @@ func updateSSHPubIfNeed( return err } - if len(sshKeys) == 0 { - return nil - } - if asymkey_model.SynchronizePublicKeys(ctx, user, authSource, sshKeys) { err = asymkey_model.RewriteAllPublicKeys(ctx) if err != nil { From 283d883e5ae01eef16e2c9f63929122cd816162e Mon Sep 17 00:00:00 2001 From: Maxim Slipenko Date: Sat, 21 Dec 2024 21:25:45 +0000 Subject: [PATCH 7/9] add integration tests for synchronization for SSH keys with OpenID Connect --- tests/integration/oauth_test.go | 111 ++++++++++++++++++++++++++++++++ 1 file changed, 111 insertions(+) diff --git a/tests/integration/oauth_test.go b/tests/integration/oauth_test.go index f385b99e46..675573d6cb 100644 --- a/tests/integration/oauth_test.go +++ b/tests/integration/oauth_test.go @@ -691,6 +691,117 @@ func TestSignInOAuthCallbackRedirectToEscaping(t *testing.T) { assert.Equal(t, "/login/oauth/authorize?redirect_uri=https://translate.example.org", test.RedirectURL(resp)) } +func setupMockOIDCServer() *httptest.Server { + var mockServer *httptest.Server + mockServer = httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + switch r.URL.Path { + case "/.well-known/openid-configuration": + w.WriteHeader(http.StatusOK) + w.Write([]byte(`{ + "issuer": "` + mockServer.URL + `", + "authorization_endpoint": "` + mockServer.URL + `/authorize", + "token_endpoint": "` + mockServer.URL + `/token", + "userinfo_endpoint": "` + mockServer.URL + `/userinfo" + }`)) + default: + http.NotFound(w, r) + } + })) + return mockServer +} + +func TestSignInOauthCallbackSyncSSHKeys(t *testing.T) { + defer tests.PrepareTestEnv(t)() + mockServer := setupMockOIDCServer() + defer mockServer.Close() + + sourceName := "oidc" + authPayload := authSourcePayloadOpenIDConnect(sourceName, mockServer.URL+"/") + authPayload["oauth2_attribute_ssh_public_key"] = "sshpubkey" + authSource := addAuthSource(t, authPayload) + + userID := "5678" + user := &user_model.User{ + Name: "oidc.user", + Email: "oidc.user@example.com", + Passwd: "oidc.userpassword", + Type: user_model.UserTypeIndividual, + LoginType: auth_model.OAuth2, + LoginSource: authSource.ID, + LoginName: userID, + IsActive: true, + } + defer createUser(context.Background(), t, user)() + + for _, tt := range []struct { + name string + rawData map[string]interface{} + parsedKeySets []string + }{ + { + name: "Add keys", + rawData: map[string]interface{}{ + "sshpubkey": []interface{}{ + "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAINDRDoephkaFELacrNNe2fqAwedhRB1MKOpLEHlPuczO nocomment", + }, + }, + parsedKeySets: []string{ + "SHA256:X/mW7JUQ8J8yhrKBbZ/pJni8qx7zPA1DTFsi8ftpDwg", + }, + }, + { + name: "Update keys", + rawData: map[string]interface{}{ + "sshpubkey": []interface{}{ + "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIMLLMOLFMouSJmzOASKKv178d+7op4utSxcugF9tVVch nocomment", + "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIGyDh9sg1IGQGa0U363wcGXrDlGBhZI3UHvS7we/0d+T nocomment", + }, + }, + parsedKeySets: []string{ + "SHA256:gsyG4JNmY5XoLBK5lSzuwD3EXcaDBiDKBkqDkpQTH6Q", + "SHA256:bbEKB1Qpumgk6QrgiN6t/kIvtUZvIQ8rqQBz8yYPzYw", + }, + }, + { + name: "Remove keys", + rawData: map[string]interface{}{ + "sshpubkey": []interface{}{}, + }, + parsedKeySets: []string{}, + }, + } { + t.Run(tt.name, func(t *testing.T) { + defer mockCompleteUserAuth(func(res http.ResponseWriter, req *http.Request) (goth.User, error) { + return goth.User{ + Provider: sourceName, + UserID: userID, + Email: user.Email, + RawData: tt.rawData, + }, nil + })() + + session := emptyTestSession(t) + + req := NewRequest(t, "GET", fmt.Sprintf("/user/oauth2/%s/callback?code=XYZ&state=XYZ", sourceName)) + resp := session.MakeRequest(t, req, http.StatusSeeOther) + assert.Equal(t, "/", test.RedirectURL(resp)) + + req = NewRequest(t, "GET", "/user/settings/keys") + resp = session.MakeRequest(t, req, http.StatusOK) + + htmlDoc := NewHTMLParser(t, resp.Body) + divs := htmlDoc.doc.Find("#keys-ssh .flex-item .flex-item-body:not(:last-child)") + + syncedKeys := make([]string, divs.Length()) + for i := 0; i < divs.Length(); i++ { + syncedKeys[i] = strings.TrimSpace(divs.Eq(i).Text()) + } + + assert.ElementsMatch(t, tt.parsedKeySets, syncedKeys, "Unequal number of keys") + }) + } +} + func TestSignUpViaOAuthWithMissingFields(t *testing.T) { defer tests.PrepareTestEnv(t)() // enable auto-creation of accounts via OAuth2 From 4f92b738b50da841a757c42bb34c565cde417615 Mon Sep 17 00:00:00 2001 From: Maxim Slipenko Date: Sat, 21 Dec 2024 21:44:05 +0000 Subject: [PATCH 8/9] style: make fmt --- tests/integration/oauth_test.go | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/tests/integration/oauth_test.go b/tests/integration/oauth_test.go index 675573d6cb..6b5a1feb16 100644 --- a/tests/integration/oauth_test.go +++ b/tests/integration/oauth_test.go @@ -735,13 +735,13 @@ func TestSignInOauthCallbackSyncSSHKeys(t *testing.T) { for _, tt := range []struct { name string - rawData map[string]interface{} + rawData map[string]any parsedKeySets []string }{ { name: "Add keys", - rawData: map[string]interface{}{ - "sshpubkey": []interface{}{ + rawData: map[string]any{ + "sshpubkey": []any{ "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAINDRDoephkaFELacrNNe2fqAwedhRB1MKOpLEHlPuczO nocomment", }, }, @@ -751,8 +751,8 @@ func TestSignInOauthCallbackSyncSSHKeys(t *testing.T) { }, { name: "Update keys", - rawData: map[string]interface{}{ - "sshpubkey": []interface{}{ + rawData: map[string]any{ + "sshpubkey": []any{ "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIMLLMOLFMouSJmzOASKKv178d+7op4utSxcugF9tVVch nocomment", "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIGyDh9sg1IGQGa0U363wcGXrDlGBhZI3UHvS7we/0d+T nocomment", }, @@ -764,8 +764,8 @@ func TestSignInOauthCallbackSyncSSHKeys(t *testing.T) { }, { name: "Remove keys", - rawData: map[string]interface{}{ - "sshpubkey": []interface{}{}, + rawData: map[string]any{ + "sshpubkey": []any{}, }, parsedKeySets: []string{}, }, From 666654a5efc42db67a836fa1c8833d944de49056 Mon Sep 17 00:00:00 2001 From: Maxim Slipenko Date: Fri, 27 Dec 2024 11:17:27 +0300 Subject: [PATCH 9/9] fix: return empty slice if AttributeSSHPublicKey is missing in RawData --- routers/web/auth/oauth.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/routers/web/auth/oauth.go b/routers/web/auth/oauth.go index 4d8fbcb7a5..62b7b0b6d3 100644 --- a/routers/web/auth/oauth.go +++ b/routers/web/auth/oauth.go @@ -1188,7 +1188,7 @@ func getSSHKeys(source *oauth2.Source, gothUser *goth.User) ([]string, error) { key := source.AttributeSSHPublicKey value, exists := gothUser.RawData[key] if !exists { - return nil, fmt.Errorf("attribute '%s' not found in user data", key) + return []string{}, nil } rawSlice, ok := value.([]any)