Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add optional SAML source icon #1968

Merged
merged 2 commits into from
Dec 12, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions docs/content/usage/authentication.en-us.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
1 change: 1 addition & 0 deletions options/locale/locale_en-US.ini
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
1 change: 1 addition & 0 deletions routers/web/admin/auths.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
}

Expand Down
11 changes: 10 additions & 1 deletion routers/web/auth/auth.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down
66 changes: 66 additions & 0 deletions services/auth/source/saml/providers.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,18 +6,50 @@ 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.
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(`<img class="gt-object-contain gt-mr-3" width="%d" height="%d" src="%s" alt="%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
Expand All @@ -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
}
1 change: 1 addition & 0 deletions services/auth/source/saml/source.go
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@ type Source struct {
ServiceProviderPrivateKey string

CallbackURL string
IconURL string

// EmailAssertionKey description: Assertion key for user.Email
EmailAssertionKey string
Expand Down
1 change: 1 addition & 0 deletions services/forms/auth_form.go
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,7 @@ type AuthenticationForm struct {
EmailAssertionKey string
NameAssertionKey string
UsernameAssertionKey string
SAMLIconURL string
}

// Validate validates fields
Expand Down
5 changes: 5 additions & 0 deletions templates/admin/auth/edit.tmpl
Original file line number Diff line number Diff line change
Expand Up @@ -384,6 +384,11 @@
</div>
</div>

<div class="optional field">
<label for="saml_icon_url">{{ctx.Locale.Tr "admin.auths.saml_icon_url"}}</label>
<input id="saml_icon_url" name="saml_icon_url" value="{{$cfg.IconURL}}">
</div>

<div class="field">
<label for="identity_provider_metadata_url">{{ctx.Locale.Tr "admin.auths.saml_identity_provider_metadata_url"}}</label>
<input id="identity_provider_metadata_url" name="identity_provider_metadata_url" value="{{$cfg.IdentityProviderMetadataURL}}">
Expand Down
5 changes: 5 additions & 0 deletions templates/admin/auth/source/saml.tmpl
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,11 @@
</div>
</div>

<div class="optional field">
<label for="saml_icon_url">{{ctx.Locale.Tr "admin.auths.saml_icon_url"}}</label>
<input id="saml_icon_url" name="saml_icon_url" value="{{.SAMLIconURL}}">
</div>

<div class="field">
<label for="identity_provider_metadata_url">{{ctx.Locale.Tr "admin.auths.saml_identity_provider_metadata_url"}}</label>
<input id="identity_provider_metadata_url" name="identity_provider_metadata_url" value="{{.IdentityProviderMetadataURL}}">
Expand Down
7 changes: 4 additions & 3 deletions templates/user/auth/signin_inner.tmpl
Original file line number Diff line number Diff line change
Expand Up @@ -76,9 +76,10 @@
<div id="saml-login-navigator" class="gt-py-2">
<div class="gt-df gt-fc gt-jc">
<div id="saml-login-navigator-inner" class="gt-df gt-fc gt-fw gt-ac gt-gap-3">
{{range .SAMLProviders}}
<a class="{{.Name}} ui button gt-df gt-ac gt-jc gt-py-3 saml-login-link" href="{{AppSubUrl}}/user/saml/{{.Name}}">
{{$.locale.Tr "sign_in_with_provider" .Name}}
{{range $provider := .SAMLProviders}}
<a class="{{$provider.Name}} ui button gt-df gt-ac gt-jc gt-py-3 saml-login-link" href="{{AppSubUrl}}/user/saml/{{$provider.Name}}">
{{.IconHTML 28}}
{{ctx.Locale.Tr "sign_in_with_provider" $provider.Name}}
</a>
{{end}}
</div>
Expand Down
1 change: 1 addition & 0 deletions tests/integration/saml_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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: "",
},
}))

Expand Down