gitea/services/auth/source/saml/providers.go
techknowlogick 5bb8d1924d
Support SAML authentication (#25165)
Closes https://github.com/go-gitea/gitea/issues/5512

This PR adds basic SAML support
- Adds SAML 2.0 as an auth source
- Adds SAML configuration documentation
- Adds integration test:
- Use bare-bones SAML IdP to test protocol flow and test account is
linked successfully (only runs on Postgres by default)
- Adds documentation for configuring and running SAML integration test
locally

Future PRs:
- Support group mapping
- Support auto-registration (account linking)

Co-Authored-By: @jackHay22

---------

Co-authored-by: jackHay22 <jack@allspice.io>
Co-authored-by: Lunny Xiao <xiaolunwen@gmail.com>
Co-authored-by: KN4CK3R <admin@oldschoolhack.me>
Co-authored-by: wxiaoguang <wxiaoguang@gmail.com>
Co-authored-by: Jason Song <i@wolfogre.com>
Co-authored-by: morphelinho <morphelinho@users.noreply.github.com>
Co-authored-by: Zettat123 <zettat123@gmail.com>
Co-authored-by: Yarden Shoham <git@yardenshoham.com>
Co-authored-by: 6543 <6543@obermui.de>
Co-authored-by: silverwind <me@silverwind.io>
2024-02-23 00:08:17 +00:00

110 lines
2.7 KiB
Go

// Copyright 2023 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
package saml
import (
"context"
"fmt"
"html"
"html/template"
"io"
"net/http"
"sort"
"time"
"code.gitea.io/gitea/models/auth"
"code.gitea.io/gitea/models/db"
"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
}
req := httplib.NewRequest(source.IdentityProviderMetadataURL, "GET")
req.SetTimeout(20*time.Second, time.Minute)
resp, err := req.Response()
if err != nil {
return nil, fmt.Errorf("Unable to contact gitea: %v", err)
}
defer resp.Body.Close()
if resp.StatusCode != http.StatusOK {
return nil, err
}
data, err := io.ReadAll(resp.Body)
if err != nil {
return nil, err
}
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 := db.Find[auth.Source](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
}