Skip to content

Commit

Permalink
Merge pull request #1968 from jackHay22/jh/saml
Browse files Browse the repository at this point in the history
Add optional SAML source icon
  • Loading branch information
techknowlogick authored Dec 12, 2023
2 parents f374036 + e355550 commit 326b20c
Show file tree
Hide file tree
Showing 11 changed files with 99 additions and 4 deletions.
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

0 comments on commit 326b20c

Please sign in to comment.