diff --git a/docs/content/usage/authentication.en-us.md b/docs/content/usage/authentication.en-us.md index 0b6ed5fb96f5..3715e1367a01 100644 --- a/docs/content/usage/authentication.en-us.md +++ b/docs/content/usage/authentication.en-us.md @@ -368,6 +368,10 @@ Notice: Reverse Proxy Auth doesn't support the API. You still need an access tok - This specifies how Identity Provider (IdP) users are mapped to Gitea users. This option will be provider specific. +- `Icon URL` (optional) + + - URL of an icon to display on the Sign-In page for this authentication source. + - `[Insecure] Skip Assertion Signature Validation` (optional) - This option is not recommended and disables integrity verification of IdP SAML assertions. diff --git a/options/locale/locale_en-US.ini b/options/locale/locale_en-US.ini index 8e43d3b871ca..6e58dec7adca 100644 --- a/options/locale/locale_en-US.ini +++ b/options/locale/locale_en-US.ini @@ -3001,6 +3001,7 @@ auths.saml_service_provider_private_key = Service Provider Private Key auths.saml_identity_provider_email_assertion_key = Email Assertion Key auths.saml_identity_provider_name_assertion_key = Name Assertion Key auths.saml_identity_provider_username_assertion_key = Username Assertion Key +auths.saml_icon_url = Icon URL auths.tips = Tips auths.tips.saml = Documentation can be found at https://docs.gitea.com/usage/authentication#saml auths.tips.oauth2.general = OAuth2 Authentication diff --git a/routers/web/admin/auths.go b/routers/web/admin/auths.go index 558a7b2d0f53..bf4a2cde578a 100644 --- a/routers/web/admin/auths.go +++ b/routers/web/admin/auths.go @@ -290,6 +290,7 @@ func parseSAMLConfig(ctx *context.Context, form forms.AuthenticationForm) (*saml EmailAssertionKey: form.EmailAssertionKey, NameAssertionKey: form.NameAssertionKey, UsernameAssertionKey: form.UsernameAssertionKey, + IconURL: form.SAMLIconURL, }, nil } diff --git a/routers/web/auth/auth.go b/routers/web/auth/auth.go index 8296b6946816..91e87c7142e0 100644 --- a/routers/web/auth/auth.go +++ b/routers/web/auth/auth.go @@ -27,6 +27,7 @@ import ( "code.gitea.io/gitea/routers/utils" auth_service "code.gitea.io/gitea/services/auth" "code.gitea.io/gitea/services/auth/source/oauth2" + "code.gitea.io/gitea/services/auth/source/saml" "code.gitea.io/gitea/services/externalaccount" "code.gitea.io/gitea/services/forms" "code.gitea.io/gitea/services/mailer" @@ -167,7 +168,7 @@ func SignIn(ctx *context.Context) { } ctx.Data["OAuth2Providers"] = oauth2Providers - samlProviders, err := auth.GetActiveAuthProviderSources(ctx, auth.SAML) + samlProviders, err := saml.GetSAMLProviders(ctx, util.OptionalBoolTrue) if err != nil { ctx.ServerError("UserSignIn", err) return @@ -197,6 +198,14 @@ func SignInPost(ctx *context.Context) { return } ctx.Data["OAuth2Providers"] = oauth2Providers + + samlProviders, err := saml.GetSAMLProviders(ctx, util.OptionalBoolTrue) + if err != nil { + ctx.ServerError("UserSignIn", err) + return + } + ctx.Data["SAMLProviders"] = samlProviders + ctx.Data["Title"] = ctx.Tr("sign_in") ctx.Data["SignInLink"] = setting.AppSubURL + "/user/login" ctx.Data["PageIsSignIn"] = true diff --git a/services/auth/source/saml/providers.go b/services/auth/source/saml/providers.go index ff6f205f5797..1f5a7d67d7a7 100644 --- a/services/auth/source/saml/providers.go +++ b/services/auth/source/saml/providers.go @@ -6,11 +6,17 @@ package saml import ( "context" "fmt" + "html" + "html/template" "io" "net/http" + "sort" "time" + "code.gitea.io/gitea/models/auth" "code.gitea.io/gitea/modules/httplib" + "code.gitea.io/gitea/modules/svg" + "code.gitea.io/gitea/modules/util" ) // Providers is list of known/available providers. @@ -18,6 +24,32 @@ type Providers map[string]Source var providers = Providers{} +// Provider is an interface for describing a single SAML provider +type Provider interface { + Name() string + IconHTML(size int) template.HTML +} + +// AuthSourceProvider is a SAML provider +type AuthSourceProvider struct { + sourceName, iconURL string +} + +func (p *AuthSourceProvider) Name() string { + return p.sourceName +} + +func (p *AuthSourceProvider) IconHTML(size int) template.HTML { + if p.iconURL != "" { + return template.HTML(fmt.Sprintf(`%s`, + size, + size, + html.EscapeString(p.iconURL), html.EscapeString(p.Name()), + )) + } + return svg.RenderHTML("gitea-lock-cog", size, "gt-mr-3") +} + func readIdentityProviderMetadata(ctx context.Context, source *Source) ([]byte, error) { if source.IdentityProviderMetadata != "" { return []byte(source.IdentityProviderMetadata), nil @@ -40,3 +72,37 @@ func readIdentityProviderMetadata(ctx context.Context, source *Source) ([]byte, } return data, nil } + +func createProviderFromSource(source *auth.Source) (Provider, error) { + samlCfg, ok := source.Cfg.(*Source) + if !ok { + return nil, fmt.Errorf("invalid SAML source config: %v", samlCfg) + } + return &AuthSourceProvider{sourceName: source.Name, iconURL: samlCfg.IconURL}, nil +} + +// GetSAMLProviders returns the list of configured SAML providers +func GetSAMLProviders(ctx context.Context, isActive util.OptionalBool) ([]Provider, error) { + authSources, err := auth.FindSources(ctx, auth.FindSourcesOptions{ + IsActive: isActive, + LoginType: auth.SAML, + }) + if err != nil { + return nil, err + } + + samlProviders := make([]Provider, 0, len(authSources)) + for _, source := range authSources { + p, err := createProviderFromSource(source) + if err != nil { + return nil, err + } + samlProviders = append(samlProviders, p) + } + + sort.Slice(samlProviders, func(i, j int) bool { + return samlProviders[i].Name() < samlProviders[j].Name() + }) + + return samlProviders, nil +} diff --git a/services/auth/source/saml/source.go b/services/auth/source/saml/source.go index 077ed018cce7..1e9c2b0012d2 100644 --- a/services/auth/source/saml/source.go +++ b/services/auth/source/saml/source.go @@ -53,6 +53,7 @@ type Source struct { ServiceProviderPrivateKey string CallbackURL string + IconURL string // EmailAssertionKey description: Assertion key for user.Email EmailAssertionKey string diff --git a/services/forms/auth_form.go b/services/forms/auth_form.go index f56789b81dbf..85be38b40312 100644 --- a/services/forms/auth_form.go +++ b/services/forms/auth_form.go @@ -94,6 +94,7 @@ type AuthenticationForm struct { EmailAssertionKey string NameAssertionKey string UsernameAssertionKey string + SAMLIconURL string } // Validate validates fields diff --git a/templates/admin/auth/edit.tmpl b/templates/admin/auth/edit.tmpl index b39765d893fd..2182d011e9a3 100644 --- a/templates/admin/auth/edit.tmpl +++ b/templates/admin/auth/edit.tmpl @@ -384,6 +384,11 @@ +
+ + +
+
diff --git a/templates/admin/auth/source/saml.tmpl b/templates/admin/auth/source/saml.tmpl index 3e1e6b5189c3..050e22ddcc3f 100644 --- a/templates/admin/auth/source/saml.tmpl +++ b/templates/admin/auth/source/saml.tmpl @@ -14,6 +14,11 @@
+
+ + +
+
diff --git a/templates/user/auth/signin_inner.tmpl b/templates/user/auth/signin_inner.tmpl index e564c0b77858..709310e64b03 100644 --- a/templates/user/auth/signin_inner.tmpl +++ b/templates/user/auth/signin_inner.tmpl @@ -76,9 +76,10 @@
diff --git a/tests/integration/saml_test.go b/tests/integration/saml_test.go index 4bcbb5ec9cde..41b135ac664a 100644 --- a/tests/integration/saml_test.go +++ b/tests/integration/saml_test.go @@ -66,6 +66,7 @@ func TestSAMLRegistration(t *testing.T) { EmailAssertionKey: "http://schemas.xmlsoap.org/ws/2005/05/identity/claims/emailaddress", NameAssertionKey: "http://schemas.xmlsoap.org/claims/CommonName", UsernameAssertionKey: "http://schemas.xmlsoap.org/ws/2005/05/identity/claims/name", + IconURL: "", }, }))