mirror of
https://codeberg.org/forgejo/forgejo.git
synced 2025-01-25 14:53:15 +01:00
Merge pull request 'feat: add synchronization for SSH keys for OpenID Connect' (#6232) from Maks1mS/forgejo:feat/add-oidc-ssh-keys into forgejo
Some checks are pending
/ release (push) Waiting to run
testing / backend-checks (push) Waiting to run
testing / frontend-checks (push) Waiting to run
testing / test-unit (push) Blocked by required conditions
testing / test-e2e (push) Blocked by required conditions
testing / test-remote-cacher (redis) (push) Blocked by required conditions
testing / test-remote-cacher (valkey) (push) Blocked by required conditions
testing / test-remote-cacher (garnet) (push) Blocked by required conditions
testing / test-remote-cacher (redict) (push) Blocked by required conditions
testing / test-mysql (push) Blocked by required conditions
testing / test-pgsql (push) Blocked by required conditions
testing / test-sqlite (push) Blocked by required conditions
testing / security-check (push) Blocked by required conditions
Some checks are pending
/ release (push) Waiting to run
testing / backend-checks (push) Waiting to run
testing / frontend-checks (push) Waiting to run
testing / test-unit (push) Blocked by required conditions
testing / test-e2e (push) Blocked by required conditions
testing / test-remote-cacher (redis) (push) Blocked by required conditions
testing / test-remote-cacher (valkey) (push) Blocked by required conditions
testing / test-remote-cacher (garnet) (push) Blocked by required conditions
testing / test-remote-cacher (redict) (push) Blocked by required conditions
testing / test-mysql (push) Blocked by required conditions
testing / test-pgsql (push) Blocked by required conditions
testing / test-sqlite (push) Blocked by required conditions
testing / security-check (push) Blocked by required conditions
Reviewed-on: https://codeberg.org/forgejo/forgejo/pulls/6232 Reviewed-by: Gusted <gusted@noreply.codeberg.org>
This commit is contained in:
commit
db7be1a1db
10 changed files with 232 additions and 27 deletions
|
@ -197,6 +197,7 @@ func parseOAuth2Config(form forms.AuthenticationForm) *oauth2.Source {
|
||||||
CustomURLMapping: customURLMapping,
|
CustomURLMapping: customURLMapping,
|
||||||
IconURL: form.Oauth2IconURL,
|
IconURL: form.Oauth2IconURL,
|
||||||
Scopes: scopes,
|
Scopes: scopes,
|
||||||
|
AttributeSSHPublicKey: form.Oauth2AttributeSSHPublicKey,
|
||||||
RequiredClaimName: form.Oauth2RequiredClaimName,
|
RequiredClaimName: form.Oauth2RequiredClaimName,
|
||||||
RequiredClaimValue: form.Oauth2RequiredClaimValue,
|
RequiredClaimValue: form.Oauth2RequiredClaimValue,
|
||||||
SkipLocalTwoFA: form.SkipLocalTwoFA,
|
SkipLocalTwoFA: form.SkipLocalTwoFA,
|
||||||
|
|
|
@ -17,6 +17,7 @@ import (
|
||||||
"sort"
|
"sort"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
|
asymkey_model "code.gitea.io/gitea/models/asymkey"
|
||||||
"code.gitea.io/gitea/models/auth"
|
"code.gitea.io/gitea/models/auth"
|
||||||
org_model "code.gitea.io/gitea/models/organization"
|
org_model "code.gitea.io/gitea/models/organization"
|
||||||
user_model "code.gitea.io/gitea/models/user"
|
user_model "code.gitea.io/gitea/models/user"
|
||||||
|
@ -1183,8 +1184,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 []string{}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
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 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) {
|
func handleOAuth2SignIn(ctx *context.Context, source *auth.Source, u *user_model.User, gothUser goth.User) {
|
||||||
updateAvatarIfNeed(ctx, gothUser.AvatarURL, u)
|
updateAvatarIfNeed(ctx, gothUser.AvatarURL, u)
|
||||||
|
err := updateSSHPubIfNeed(ctx, source, &gothUser, u)
|
||||||
|
if err != nil {
|
||||||
|
ctx.ServerError("updateSSHPubIfNeed", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
needs2FA := false
|
needs2FA := false
|
||||||
if !source.Cfg.(*oauth2.Source).SkipLocalTwoFA {
|
if !source.Cfg.(*oauth2.Source).SkipLocalTwoFA {
|
||||||
|
|
|
@ -48,4 +48,8 @@ func (b *BaseProvider) CustomURLSettings() *CustomURLSettings {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (b *BaseProvider) CanProvideSSHKeys() bool {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
var _ Provider = &BaseProvider{}
|
var _ Provider = &BaseProvider{}
|
||||||
|
|
|
@ -51,6 +51,10 @@ func (o *OpenIDProvider) CustomURLSettings() *CustomURLSettings {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (o *OpenIDProvider) CanProvideSSHKeys() bool {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
var _ GothProvider = &OpenIDProvider{}
|
var _ GothProvider = &OpenIDProvider{}
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
|
|
|
@ -4,6 +4,8 @@
|
||||||
package oauth2
|
package oauth2
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"strings"
|
||||||
|
|
||||||
"code.gitea.io/gitea/models/auth"
|
"code.gitea.io/gitea/models/auth"
|
||||||
"code.gitea.io/gitea/modules/json"
|
"code.gitea.io/gitea/modules/json"
|
||||||
)
|
)
|
||||||
|
@ -17,15 +19,16 @@ type Source struct {
|
||||||
CustomURLMapping *CustomURLMapping
|
CustomURLMapping *CustomURLMapping
|
||||||
IconURL string
|
IconURL string
|
||||||
|
|
||||||
Scopes []string
|
Scopes []string
|
||||||
RequiredClaimName string
|
AttributeSSHPublicKey string
|
||||||
RequiredClaimValue string
|
RequiredClaimName string
|
||||||
GroupClaimName string
|
RequiredClaimValue string
|
||||||
AdminGroup string
|
GroupClaimName string
|
||||||
GroupTeamMap string
|
AdminGroup string
|
||||||
GroupTeamMapRemoval bool
|
GroupTeamMap string
|
||||||
RestrictedGroup string
|
GroupTeamMapRemoval bool
|
||||||
SkipLocalTwoFA bool `json:",omitempty"`
|
RestrictedGroup string
|
||||||
|
SkipLocalTwoFA bool `json:",omitempty"`
|
||||||
|
|
||||||
// reference to the authSource
|
// reference to the authSource
|
||||||
authSource *auth.Source
|
authSource *auth.Source
|
||||||
|
@ -41,6 +44,11 @@ func (source *Source) ToDB() ([]byte, error) {
|
||||||
return json.Marshal(source)
|
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
|
// SetAuthSource sets the related AuthSource
|
||||||
func (source *Source) SetAuthSource(authSource *auth.Source) {
|
func (source *Source) SetAuthSource(authSource *auth.Source) {
|
||||||
source.authSource = authSource
|
source.authSource = authSource
|
||||||
|
|
|
@ -75,6 +75,7 @@ type AuthenticationForm struct {
|
||||||
Oauth2RestrictedGroup string
|
Oauth2RestrictedGroup string
|
||||||
Oauth2GroupTeamMap string `binding:"ValidGroupTeamMap"`
|
Oauth2GroupTeamMap string `binding:"ValidGroupTeamMap"`
|
||||||
Oauth2GroupTeamMapRemoval bool
|
Oauth2GroupTeamMapRemoval bool
|
||||||
|
Oauth2AttributeSSHPublicKey string
|
||||||
SkipLocalTwoFA bool
|
SkipLocalTwoFA bool
|
||||||
SSPIAutoCreateUsers bool
|
SSPIAutoCreateUsers bool
|
||||||
SSPIAutoActivateUsers bool
|
SSPIAutoActivateUsers bool
|
||||||
|
|
|
@ -326,19 +326,28 @@
|
||||||
<input id="oauth2_tenant" name="oauth2_tenant" value="{{if $cfg.CustomURLMapping}}{{$cfg.CustomURLMapping.Tenant}}{{end}}">
|
<input id="oauth2_tenant" name="oauth2_tenant" value="{{if $cfg.CustomURLMapping}}{{$cfg.CustomURLMapping.Tenant}}{{end}}">
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{{range .OAuth2Providers}}{{if .CustomURLSettings}}
|
{{range .OAuth2Providers}}
|
||||||
<input id="{{.Name}}_customURLSettings" type="hidden" data-required="{{.CustomURLSettings.Required}}" data-available="true">
|
{{if .CustomURLSettings}}
|
||||||
<input id="{{.Name}}_token_url" value="{{.CustomURLSettings.TokenURL.Value}}" data-available="{{.CustomURLSettings.TokenURL.Available}}" data-required="{{.CustomURLSettings.TokenURL.Required}}" type="hidden">
|
<input id="{{.Name}}_customURLSettings" type="hidden" data-required="{{.CustomURLSettings.Required}}" data-available="true">
|
||||||
<input id="{{.Name}}_auth_url" value="{{.CustomURLSettings.AuthURL.Value}}" data-available="{{.CustomURLSettings.AuthURL.Available}}" data-required="{{.CustomURLSettings.AuthURL.Required}}" type="hidden">
|
<input id="{{.Name}}_token_url" value="{{.CustomURLSettings.TokenURL.Value}}" data-available="{{.CustomURLSettings.TokenURL.Available}}" data-required="{{.CustomURLSettings.TokenURL.Required}}" type="hidden">
|
||||||
<input id="{{.Name}}_profile_url" value="{{.CustomURLSettings.ProfileURL.Value}}" data-available="{{.CustomURLSettings.ProfileURL.Available}}" data-required="{{.CustomURLSettings.ProfileURL.Required}}" type="hidden">
|
<input id="{{.Name}}_auth_url" value="{{.CustomURLSettings.AuthURL.Value}}" data-available="{{.CustomURLSettings.AuthURL.Available}}" data-required="{{.CustomURLSettings.AuthURL.Required}}" type="hidden">
|
||||||
<input id="{{.Name}}_email_url" value="{{.CustomURLSettings.EmailURL.Value}}" data-available="{{.CustomURLSettings.EmailURL.Available}}" data-required="{{.CustomURLSettings.EmailURL.Required}}" type="hidden">
|
<input id="{{.Name}}_profile_url" value="{{.CustomURLSettings.ProfileURL.Value}}" data-available="{{.CustomURLSettings.ProfileURL.Available}}" data-required="{{.CustomURLSettings.ProfileURL.Required}}" type="hidden">
|
||||||
<input id="{{.Name}}_tenant" value="{{.CustomURLSettings.Tenant.Value}}" data-available="{{.CustomURLSettings.Tenant.Available}}" data-required="{{.CustomURLSettings.Tenant.Required}}" type="hidden">
|
<input id="{{.Name}}_email_url" value="{{.CustomURLSettings.EmailURL.Value}}" data-available="{{.CustomURLSettings.EmailURL.Available}}" data-required="{{.CustomURLSettings.EmailURL.Required}}" type="hidden">
|
||||||
{{end}}{{end}}
|
<input id="{{.Name}}_tenant" value="{{.CustomURLSettings.Tenant.Value}}" data-available="{{.CustomURLSettings.Tenant.Available}}" data-required="{{.CustomURLSettings.Tenant.Required}}" type="hidden">
|
||||||
|
{{end}}
|
||||||
|
{{if .CanProvideSSHKeys}}
|
||||||
|
<input id="{{.Name}}_canProvideSSHKeys" type="hidden">
|
||||||
|
{{end}}
|
||||||
|
{{end}}
|
||||||
|
|
||||||
<div class="field">
|
<div class="field">
|
||||||
<label for="oauth2_scopes">{{ctx.Locale.Tr "admin.auths.oauth2_scopes"}}</label>
|
<label for="oauth2_scopes">{{ctx.Locale.Tr "admin.auths.oauth2_scopes"}}</label>
|
||||||
<input id="oauth2_scopes" name="oauth2_scopes" value="{{if $cfg.Scopes}}{{StringUtils.Join $cfg.Scopes ","}}{{end}}">
|
<input id="oauth2_scopes" name="oauth2_scopes" value="{{if $cfg.Scopes}}{{StringUtils.Join $cfg.Scopes ","}}{{end}}">
|
||||||
</div>
|
</div>
|
||||||
|
<div class="oauth2_attribute_ssh_public_key field">
|
||||||
|
<label for="oauth2_attribute_ssh_public_key">{{ctx.Locale.Tr "admin.auths.attribute_ssh_public_key"}}</label>
|
||||||
|
<input id="oauth2_attribute_ssh_public_key" name="oauth2_attribute_ssh_public_key" value="{{$cfg.AttributeSSHPublicKey}}" placeholder="sshpubkey">
|
||||||
|
</div>
|
||||||
<div class="field">
|
<div class="field">
|
||||||
<label for="oauth2_required_claim_name">{{ctx.Locale.Tr "admin.auths.oauth2_required_claim_name"}}</label>
|
<label for="oauth2_required_claim_name">{{ctx.Locale.Tr "admin.auths.oauth2_required_claim_name"}}</label>
|
||||||
<input id="oauth2_required_claim_name" name="oauth2_required_claim_name" value="{{$cfg.RequiredClaimName}}">
|
<input id="oauth2_required_claim_name" name="oauth2_required_claim_name" value="{{$cfg.RequiredClaimName}}">
|
||||||
|
|
|
@ -63,19 +63,27 @@
|
||||||
<input id="oauth2_tenant" name="oauth2_tenant" value="{{.oauth2_tenant}}">
|
<input id="oauth2_tenant" name="oauth2_tenant" value="{{.oauth2_tenant}}">
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{{range .OAuth2Providers}}{{if .CustomURLSettings}}
|
{{range .OAuth2Providers}}
|
||||||
<input id="{{.Name}}_customURLSettings" type="hidden" data-required="{{.CustomURLSettings.Required}}" data-available="true">
|
{{if .CustomURLSettings}}
|
||||||
<input id="{{.Name}}_token_url" value="{{.CustomURLSettings.TokenURL.Value}}" data-available="{{.CustomURLSettings.TokenURL.Available}}" data-required="{{.CustomURLSettings.TokenURL.Required}}" type="hidden">
|
<input id="{{.Name}}_customURLSettings" type="hidden" data-required="{{.CustomURLSettings.Required}}" data-available="true">
|
||||||
<input id="{{.Name}}_auth_url" value="{{.CustomURLSettings.AuthURL.Value}}" data-available="{{.CustomURLSettings.AuthURL.Available}}" data-required="{{.CustomURLSettings.AuthURL.Required}}" type="hidden">
|
<input id="{{.Name}}_token_url" value="{{.CustomURLSettings.TokenURL.Value}}" data-available="{{.CustomURLSettings.TokenURL.Available}}" data-required="{{.CustomURLSettings.TokenURL.Required}}" type="hidden">
|
||||||
<input id="{{.Name}}_profile_url" value="{{.CustomURLSettings.ProfileURL.Value}}" data-available="{{.CustomURLSettings.ProfileURL.Available}}" data-required="{{.CustomURLSettings.ProfileURL.Required}}" type="hidden">
|
<input id="{{.Name}}_auth_url" value="{{.CustomURLSettings.AuthURL.Value}}" data-available="{{.CustomURLSettings.AuthURL.Available}}" data-required="{{.CustomURLSettings.AuthURL.Required}}" type="hidden">
|
||||||
<input id="{{.Name}}_email_url" value="{{.CustomURLSettings.EmailURL.Value}}" data-available="{{.CustomURLSettings.EmailURL.Available}}" data-required="{{.CustomURLSettings.EmailURL.Required}}" type="hidden">
|
<input id="{{.Name}}_profile_url" value="{{.CustomURLSettings.ProfileURL.Value}}" data-available="{{.CustomURLSettings.ProfileURL.Available}}" data-required="{{.CustomURLSettings.ProfileURL.Required}}" type="hidden">
|
||||||
<input id="{{.Name}}_tenant" value="{{.CustomURLSettings.Tenant.Value}}" data-available="{{.CustomURLSettings.Tenant.Available}}" data-required="{{.CustomURLSettings.Tenant.Required}}" type="hidden">
|
<input id="{{.Name}}_email_url" value="{{.CustomURLSettings.EmailURL.Value}}" data-available="{{.CustomURLSettings.EmailURL.Available}}" data-required="{{.CustomURLSettings.EmailURL.Required}}" type="hidden">
|
||||||
{{end}}{{end}}
|
<input id="{{.Name}}_tenant" value="{{.CustomURLSettings.Tenant.Value}}" data-available="{{.CustomURLSettings.Tenant.Available}}" data-required="{{.CustomURLSettings.Tenant.Required}}" type="hidden">
|
||||||
|
{{end}}
|
||||||
|
{{if .CanProvideSSHKeys}}
|
||||||
|
<input id="{{.Name}}_canProvideSSHKeys" type="hidden">
|
||||||
|
{{end}}
|
||||||
|
{{end}}
|
||||||
<div class="field">
|
<div class="field">
|
||||||
<label for="oauth2_scopes">{{ctx.Locale.Tr "admin.auths.oauth2_scopes"}}</label>
|
<label for="oauth2_scopes">{{ctx.Locale.Tr "admin.auths.oauth2_scopes"}}</label>
|
||||||
<input id="oauth2_scopes" name="oauth2_scopes" value="{{.oauth2_scopes}}">
|
<input id="oauth2_scopes" name="oauth2_scopes" value="{{.oauth2_scopes}}">
|
||||||
</div>
|
</div>
|
||||||
|
<div class="oauth2_attribute_ssh_public_key field">
|
||||||
|
<label for="oauth2_attribute_ssh_public_key">{{ctx.Locale.Tr "admin.auths.attribute_ssh_public_key"}}</label>
|
||||||
|
<input id="oauth2_attribute_ssh_public_key" name="oauth2_attribute_ssh_public_key" value="{{.attribute_ssh_public_key}}" placeholder="sshpubkey">
|
||||||
|
</div>
|
||||||
<div class="field">
|
<div class="field">
|
||||||
<label for="oauth2_required_claim_name">{{ctx.Locale.Tr "admin.auths.oauth2_required_claim_name"}}</label>
|
<label for="oauth2_required_claim_name">{{ctx.Locale.Tr "admin.auths.oauth2_required_claim_name"}}</label>
|
||||||
<input id="oauth2_required_claim_name" name="oauth2_required_claim_name" value="{{.oauth2_required_claim_name}}">
|
<input id="oauth2_required_claim_name" name="oauth2_required_claim_name" value="{{.oauth2_required_claim_name}}">
|
||||||
|
|
|
@ -691,6 +691,117 @@ func TestSignInOAuthCallbackRedirectToEscaping(t *testing.T) {
|
||||||
assert.Equal(t, "/login/oauth/authorize?redirect_uri=https://translate.example.org", test.RedirectURL(resp))
|
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]any
|
||||||
|
parsedKeySets []string
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "Add keys",
|
||||||
|
rawData: map[string]any{
|
||||||
|
"sshpubkey": []any{
|
||||||
|
"ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAINDRDoephkaFELacrNNe2fqAwedhRB1MKOpLEHlPuczO nocomment",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
parsedKeySets: []string{
|
||||||
|
"SHA256:X/mW7JUQ8J8yhrKBbZ/pJni8qx7zPA1DTFsi8ftpDwg",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Update keys",
|
||||||
|
rawData: map[string]any{
|
||||||
|
"sshpubkey": []any{
|
||||||
|
"ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIMLLMOLFMouSJmzOASKKv178d+7op4utSxcugF9tVVch nocomment",
|
||||||
|
"ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIGyDh9sg1IGQGa0U363wcGXrDlGBhZI3UHvS7we/0d+T nocomment",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
parsedKeySets: []string{
|
||||||
|
"SHA256:gsyG4JNmY5XoLBK5lSzuwD3EXcaDBiDKBkqDkpQTH6Q",
|
||||||
|
"SHA256:bbEKB1Qpumgk6QrgiN6t/kIvtUZvIQ8rqQBz8yYPzYw",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Remove keys",
|
||||||
|
rawData: map[string]any{
|
||||||
|
"sshpubkey": []any{},
|
||||||
|
},
|
||||||
|
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) {
|
func TestSignUpViaOAuthWithMissingFields(t *testing.T) {
|
||||||
defer tests.PrepareTestEnv(t)()
|
defer tests.PrepareTestEnv(t)()
|
||||||
// enable auto-creation of accounts via OAuth2
|
// enable auto-creation of accounts via OAuth2
|
||||||
|
|
|
@ -62,7 +62,7 @@ export function initAdminCommon() {
|
||||||
}
|
}
|
||||||
|
|
||||||
function onOAuth2Change(applyDefaultValues) {
|
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]')) {
|
for (const input of document.querySelectorAll('.open_id_connect_auto_discovery_url input[required]')) {
|
||||||
input.removeAttribute('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);
|
onOAuth2UseCustomURLChange(applyDefaultValues);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue