aboutsummaryrefslogtreecommitdiffstats
path: root/services/auth
diff options
context:
space:
mode:
author6543 <6543@obermui.de>2024-02-24 05:18:49 +0100
committerGitHub <noreply@github.com>2024-02-24 12:18:49 +0800
commit4ba642d07d50d7eb42ae33cd6f1f7f2c82c02a40 (patch)
tree52f879a6788100115c2127d62c0c6182cd96ad41 /services/auth
parent875f5ea6d83c8371f309df99654ca3556623004c (diff)
downloadgitea-4ba642d07d50d7eb42ae33cd6f1f7f2c82c02a40.tar.gz
gitea-4ba642d07d50d7eb42ae33cd6f1f7f2c82c02a40.zip
Revert "Support SAML authentication (#25165)" (#29358)
This reverts #25165 (5bb8d1924d77c675467694de26697b876d709a17), as there was a chance some important reviews got missed. so after reverting this patch it will be resubmitted for reviewing again https://github.com/go-gitea/gitea/pull/25165#issuecomment-1960670242 temporary Open #5512 again
Diffstat (limited to 'services/auth')
-rw-r--r--services/auth/source/saml/assert_interface_test.go22
-rw-r--r--services/auth/source/saml/init.go29
-rw-r--r--services/auth/source/saml/name_id_format.go38
-rw-r--r--services/auth/source/saml/providers.go109
-rw-r--r--services/auth/source/saml/source.go202
-rw-r--r--services/auth/source/saml/source_authenticate.go16
-rw-r--r--services/auth/source/saml/source_callout.go89
-rw-r--r--services/auth/source/saml/source_metadata.go32
-rw-r--r--services/auth/source/saml/source_register.go23
9 files changed, 0 insertions, 560 deletions
diff --git a/services/auth/source/saml/assert_interface_test.go b/services/auth/source/saml/assert_interface_test.go
deleted file mode 100644
index 2ca7057b8a..0000000000
--- a/services/auth/source/saml/assert_interface_test.go
+++ /dev/null
@@ -1,22 +0,0 @@
-// Copyright 2023 The Gitea Authors. All rights reserved.
-// SPDX-License-Identifier: MIT
-
-package saml_test
-
-import (
- auth_model "code.gitea.io/gitea/models/auth"
- "code.gitea.io/gitea/services/auth"
- "code.gitea.io/gitea/services/auth/source/saml"
-)
-
-// This test file exists to assert that our Source exposes the interfaces that we expect
-// It tightly binds the interfaces and implementation without breaking go import cycles
-
-type sourceInterface interface {
- auth_model.Config
- auth_model.SourceSettable
- auth_model.RegisterableSource
- auth.PasswordAuthenticator
-}
-
-var _ (sourceInterface) = &saml.Source{}
diff --git a/services/auth/source/saml/init.go b/services/auth/source/saml/init.go
deleted file mode 100644
index f1d6d9fa4b..0000000000
--- a/services/auth/source/saml/init.go
+++ /dev/null
@@ -1,29 +0,0 @@
-// Copyright 2023 The Gitea Authors. All rights reserved.
-// SPDX-License-Identifier: MIT
-
-package saml
-
-import (
- "context"
- "sync"
-
- "code.gitea.io/gitea/models/auth"
- "code.gitea.io/gitea/modules/log"
-)
-
-var samlRWMutex = sync.RWMutex{}
-
-func Init(ctx context.Context) error {
- loginSources, _ := auth.GetActiveAuthProviderSources(ctx, auth.SAML)
- for _, source := range loginSources {
- samlSource, ok := source.Cfg.(*Source)
- if !ok {
- continue
- }
- err := samlSource.RegisterSource()
- if err != nil {
- log.Error("Unable to register source: %s due to Error: %v.", source.Name, err)
- }
- }
- return nil
-}
diff --git a/services/auth/source/saml/name_id_format.go b/services/auth/source/saml/name_id_format.go
deleted file mode 100644
index 1ddf047729..0000000000
--- a/services/auth/source/saml/name_id_format.go
+++ /dev/null
@@ -1,38 +0,0 @@
-// Copyright 2023 The Gitea Authors. All rights reserved.
-// SPDX-License-Identifier: MIT
-
-package saml
-
-type NameIDFormat int
-
-const (
- SAML11Email NameIDFormat = iota + 1
- SAML11Persistent
- SAML11Unspecified
- SAML20Email
- SAML20Persistent
- SAML20Transient
- SAML20Unspecified
-)
-
-const DefaultNameIDFormat NameIDFormat = SAML20Persistent
-
-var NameIDFormatNames = map[NameIDFormat]string{
- SAML11Email: "urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress",
- SAML11Persistent: "urn:oasis:names:tc:SAML:1.1:nameid-format:persistent",
- SAML11Unspecified: "urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified",
- SAML20Email: "urn:oasis:names:tc:SAML:2.0:nameid-format:emailAddress",
- SAML20Persistent: "urn:oasis:names:tc:SAML:2.0:nameid-format:persistent",
- SAML20Transient: "urn:oasis:names:tc:SAML:2.0:nameid-format:transient",
- SAML20Unspecified: "urn:oasis:names:tc:SAML:2.0:nameid-format:unspecified",
-}
-
-// String returns the name of the NameIDFormat
-func (n NameIDFormat) String() string {
- return NameIDFormatNames[n]
-}
-
-// Int returns the int value of the NameIDFormat
-func (n NameIDFormat) Int() int {
- return int(n)
-}
diff --git a/services/auth/source/saml/providers.go b/services/auth/source/saml/providers.go
deleted file mode 100644
index d0b36ff44d..0000000000
--- a/services/auth/source/saml/providers.go
+++ /dev/null
@@ -1,109 +0,0 @@
-// 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
-}
diff --git a/services/auth/source/saml/source.go b/services/auth/source/saml/source.go
deleted file mode 100644
index 52388646b5..0000000000
--- a/services/auth/source/saml/source.go
+++ /dev/null
@@ -1,202 +0,0 @@
-// Copyright 2023 The Gitea Authors. All rights reserved.
-// SPDX-License-Identifier: MIT
-
-package saml
-
-import (
- "context"
- "crypto/rand"
- "crypto/rsa"
- "crypto/tls"
- "crypto/x509"
- "encoding/base64"
- "encoding/pem"
- "encoding/xml"
- "errors"
- "fmt"
- "math/big"
- "net/url"
- "time"
-
- "code.gitea.io/gitea/models/auth"
- "code.gitea.io/gitea/modules/json"
- "code.gitea.io/gitea/modules/log"
- "code.gitea.io/gitea/modules/setting"
-
- saml2 "github.com/russellhaering/gosaml2"
- "github.com/russellhaering/gosaml2/types"
- dsig "github.com/russellhaering/goxmldsig"
-)
-
-// Source holds configuration for the SAML login source.
-type Source struct {
- // IdentityProviderMetadata description: The SAML Identity Provider metadata XML contents (for static configuration of the SAML Service Provider). The value of this field should be an XML document whose root element is `<EntityDescriptor>` or `<EntityDescriptors>`. To escape the value into a JSON string, you may want to use a tool like https://json-escape-text.now.sh.
- IdentityProviderMetadata string
- // IdentityProviderMetadataURL description: The SAML Identity Provider metadata URL (for dynamic configuration of the SAML Service Provider).
- IdentityProviderMetadataURL string
- // InsecureSkipAssertionSignatureValidation description: Whether the Service Provider should (insecurely) accept assertions from the Identity Provider without a valid signature.
- InsecureSkipAssertionSignatureValidation bool
- // NameIDFormat description: The SAML NameID format to use when performing user authentication.
- NameIDFormat NameIDFormat
- // ServiceProviderCertificate description: The SAML Service Provider certificate in X.509 encoding (begins with "-----BEGIN CERTIFICATE-----"). This certificate is used by the Identity Provider to validate the Service Provider's AuthnRequests and LogoutRequests. It corresponds to the Service Provider's private key (`serviceProviderPrivateKey`). To escape the value into a JSON string, you may want to use a tool like https://json-escape-text.now.sh.
- ServiceProviderCertificate string
- // ServiceProviderIssuer description: The SAML Service Provider name, used to identify this Service Provider. This is required if the "externalURL" field is not set (as the SAML metadata endpoint is computed as "<externalURL>.auth/saml/metadata"), or when using multiple SAML authentication providers.
- ServiceProviderIssuer string
- // ServiceProviderPrivateKey description: The SAML Service Provider private key in PKCS#8 encoding (begins with "-----BEGIN PRIVATE KEY-----"). This private key is used to sign AuthnRequests and LogoutRequests. It corresponds to the Service Provider's certificate (`serviceProviderCertificate`). To escape the value into a JSON string, you may want to use a tool like https://json-escape-text.now.sh.
- ServiceProviderPrivateKey string
-
- CallbackURL string
- IconURL string
-
- // EmailAssertionKey description: Assertion key for user.Email
- EmailAssertionKey string
- // NameAssertionKey description: Assertion key for user.NickName
- NameAssertionKey string
- // UsernameAssertionKey description: Assertion key for user.Name
- UsernameAssertionKey string
-
- // reference to the authSource
- authSource *auth.Source
-
- samlSP *saml2.SAMLServiceProvider
-}
-
-func GenerateSAMLSPKeypair() (string, string, error) {
- key, err := rsa.GenerateKey(rand.Reader, 4096)
- if err != nil {
- return "", "", err
- }
-
- keyBytes := x509.MarshalPKCS1PrivateKey(key)
- keyPem := pem.EncodeToMemory(
- &pem.Block{
- Type: "RSA PRIVATE KEY",
- Bytes: keyBytes,
- },
- )
-
- now := time.Now()
-
- template := &x509.Certificate{
- SerialNumber: big.NewInt(0),
- NotBefore: now.Add(-5 * time.Minute),
- NotAfter: now.Add(365 * 24 * time.Hour),
-
- KeyUsage: x509.KeyUsageDigitalSignature,
- ExtKeyUsage: []x509.ExtKeyUsage{},
- BasicConstraintsValid: true,
- }
-
- certificate, err := x509.CreateCertificate(rand.Reader, template, template, &key.PublicKey, key)
- if err != nil {
- return "", "", err
- }
-
- certPem := pem.EncodeToMemory(
- &pem.Block{
- Type: "CERTIFICATE",
- Bytes: certificate,
- },
- )
-
- return string(keyPem), string(certPem), nil
-}
-
-func (source *Source) initSAMLSp() error {
- source.CallbackURL = setting.AppURL + "user/saml/" + url.PathEscape(source.authSource.Name) + "/acs"
-
- idpMetadata, err := readIdentityProviderMetadata(context.Background(), source)
- if err != nil {
- return err
- }
- {
- if source.IdentityProviderMetadataURL != "" {
- log.Trace(fmt.Sprintf("Identity Provider metadata: %s", source.IdentityProviderMetadataURL), string(idpMetadata))
- }
- }
-
- metadata := &types.EntityDescriptor{}
- err = xml.Unmarshal(idpMetadata, metadata)
- if err != nil {
- return err
- }
-
- certStore := dsig.MemoryX509CertificateStore{
- Roots: []*x509.Certificate{},
- }
-
- if metadata.IDPSSODescriptor == nil {
- return errors.New("saml idp metadata missing IDPSSODescriptor")
- }
-
- for _, kd := range metadata.IDPSSODescriptor.KeyDescriptors {
- for idx, xcert := range kd.KeyInfo.X509Data.X509Certificates {
- if xcert.Data == "" {
- return fmt.Errorf("metadata certificate(%d) must not be empty", idx)
- }
- certData, err := base64.StdEncoding.DecodeString(xcert.Data)
- if err != nil {
- return err
- }
-
- idpCert, err := x509.ParseCertificate(certData)
- if err != nil {
- return err
- }
-
- certStore.Roots = append(certStore.Roots, idpCert)
- }
- }
-
- var keyStore dsig.X509KeyStore
-
- if source.ServiceProviderCertificate != "" && source.ServiceProviderPrivateKey != "" {
- keyPair, err := tls.X509KeyPair([]byte(source.ServiceProviderCertificate), []byte(source.ServiceProviderPrivateKey))
- if err != nil {
- return err
- }
- keyPair.Leaf, err = x509.ParseCertificate(keyPair.Certificate[0])
- if err != nil {
- return err
- }
- keyStore = dsig.TLSCertKeyStore(keyPair)
- }
-
- source.samlSP = &saml2.SAMLServiceProvider{
- IdentityProviderSSOURL: metadata.IDPSSODescriptor.SingleSignOnServices[0].Location,
- IdentityProviderIssuer: metadata.EntityID,
- AudienceURI: setting.AppURL + "user/saml/" + url.PathEscape(source.authSource.Name) + "/metadata",
- AssertionConsumerServiceURL: source.CallbackURL,
- SkipSignatureValidation: source.InsecureSkipAssertionSignatureValidation,
- NameIdFormat: source.NameIDFormat.String(),
- IDPCertificateStore: &certStore,
- SignAuthnRequests: source.ServiceProviderCertificate != "" && source.ServiceProviderPrivateKey != "",
- SPKeyStore: keyStore,
- ServiceProviderIssuer: setting.AppURL + "user/saml/" + url.PathEscape(source.authSource.Name) + "/metadata",
- }
-
- return nil
-}
-
-// FromDB fills up a SAML from serialized format.
-func (source *Source) FromDB(bs []byte) error {
- if err := json.UnmarshalHandleDoubleEncode(bs, &source); err != nil {
- return err
- }
-
- return source.initSAMLSp()
-}
-
-// ToDB exports a SAML to a serialized format.
-func (source *Source) ToDB() ([]byte, error) {
- return json.Marshal(source)
-}
-
-// SetAuthSource sets the related AuthSource
-func (source *Source) SetAuthSource(authSource *auth.Source) {
- source.authSource = authSource
-}
-
-func init() {
- auth.RegisterTypeConfig(auth.SAML, &Source{})
-}
diff --git a/services/auth/source/saml/source_authenticate.go b/services/auth/source/saml/source_authenticate.go
deleted file mode 100644
index d118917f87..0000000000
--- a/services/auth/source/saml/source_authenticate.go
+++ /dev/null
@@ -1,16 +0,0 @@
-// Copyright 2023 The Gitea Authors. All rights reserved.
-// SPDX-License-Identifier: MIT
-
-package saml
-
-import (
- "context"
-
- user_model "code.gitea.io/gitea/models/user"
- "code.gitea.io/gitea/services/auth/source/db"
-)
-
-// Authenticate falls back to the db authenticator
-func (source *Source) Authenticate(ctx context.Context, user *user_model.User, login, password string) (*user_model.User, error) {
- return db.Authenticate(ctx, user, login, password)
-}
diff --git a/services/auth/source/saml/source_callout.go b/services/auth/source/saml/source_callout.go
deleted file mode 100644
index 5366f8a527..0000000000
--- a/services/auth/source/saml/source_callout.go
+++ /dev/null
@@ -1,89 +0,0 @@
-// Copyright 2023 The Gitea Authors. All rights reserved.
-// SPDX-License-Identifier: MIT
-
-package saml
-
-import (
- "fmt"
- "net/http"
- "strings"
-
- "github.com/markbates/goth"
-)
-
-// Callout redirects request/response pair to authenticate against the provider
-func (source *Source) Callout(request *http.Request, response http.ResponseWriter) error {
- samlRWMutex.RLock()
- defer samlRWMutex.RUnlock()
- if _, ok := providers[source.authSource.Name]; !ok {
- return fmt.Errorf("no provider for this saml")
- }
-
- authURL, err := providers[source.authSource.Name].samlSP.BuildAuthURL("")
- if err == nil {
- http.Redirect(response, request, authURL, http.StatusTemporaryRedirect)
- }
- return err
-}
-
-// Callback handles SAML callback, resolve to a goth user and send back to original url
-// this will trigger a new authentication request, but because we save it in the session we can use that
-func (source *Source) Callback(request *http.Request, response http.ResponseWriter) (goth.User, error) {
- samlRWMutex.RLock()
- defer samlRWMutex.RUnlock()
-
- user := goth.User{
- Provider: source.authSource.Name,
- }
- samlResponse := request.FormValue("SAMLResponse")
- assertions, err := source.samlSP.RetrieveAssertionInfo(samlResponse)
- if err != nil {
- return user, err
- }
-
- if assertions.WarningInfo.OneTimeUse {
- return user, fmt.Errorf("SAML response contains one time use warning")
- }
-
- if assertions.WarningInfo.ProxyRestriction != nil {
- return user, fmt.Errorf("SAML response contains proxy restriction warning: %v", assertions.WarningInfo.ProxyRestriction)
- }
-
- if assertions.WarningInfo.NotInAudience {
- return user, fmt.Errorf("SAML response contains audience warning")
- }
-
- if assertions.WarningInfo.InvalidTime {
- return user, fmt.Errorf("SAML response contains invalid time warning")
- }
-
- samlMap := make(map[string]string)
- for key, value := range assertions.Values {
- keyParsed := strings.ToLower(key[strings.LastIndex(key, "/")+1:]) // Uses the trailing slug as the key name.
- valueParsed := value.Values[0].Value
- samlMap[keyParsed] = valueParsed
-
- }
-
- user.UserID = assertions.NameID
- if user.UserID == "" {
- return user, fmt.Errorf("no nameID found in SAML response")
- }
-
- // email
- if _, ok := samlMap[source.EmailAssertionKey]; !ok {
- user.Email = samlMap[source.EmailAssertionKey]
- }
- // name
- if _, ok := samlMap[source.NameAssertionKey]; !ok {
- user.NickName = samlMap[source.NameAssertionKey]
- }
- // username
- if _, ok := samlMap[source.UsernameAssertionKey]; !ok {
- user.Name = samlMap[source.UsernameAssertionKey]
- }
-
- // TODO: utilize groups once mapping is supported
-
- return user, nil
-}
diff --git a/services/auth/source/saml/source_metadata.go b/services/auth/source/saml/source_metadata.go
deleted file mode 100644
index 9fb8c758e3..0000000000
--- a/services/auth/source/saml/source_metadata.go
+++ /dev/null
@@ -1,32 +0,0 @@
-// Copyright 2023 The Gitea Authors. All rights reserved.
-// SPDX-License-Identifier: MIT
-
-package saml
-
-import (
- "encoding/xml"
- "fmt"
- "net/http"
-)
-
-// Metadata redirects request/response pair to authenticate against the provider
-func (source *Source) Metadata(request *http.Request, response http.ResponseWriter) error {
- samlRWMutex.RLock()
- defer samlRWMutex.RUnlock()
- if _, ok := providers[source.authSource.Name]; !ok {
- return fmt.Errorf("provider does not exist")
- }
-
- metadata, err := providers[source.authSource.Name].samlSP.Metadata()
- if err != nil {
- return err
- }
- buf, err := xml.Marshal(metadata)
- if err != nil {
- return err
- }
-
- response.Header().Set("Content-Type", "application/samlmetadata+xml; charset=utf-8")
- _, _ = response.Write(buf)
- return nil
-}
diff --git a/services/auth/source/saml/source_register.go b/services/auth/source/saml/source_register.go
deleted file mode 100644
index 93eaaa88b6..0000000000
--- a/services/auth/source/saml/source_register.go
+++ /dev/null
@@ -1,23 +0,0 @@
-// Copyright 2023 The Gitea Authors. All rights reserved.
-// SPDX-License-Identifier: MIT
-
-package saml
-
-// RegisterSource causes an OAuth2 configuration to be registered
-func (source *Source) RegisterSource() error {
- samlRWMutex.Lock()
- defer samlRWMutex.Unlock()
- if err := source.initSAMLSp(); err != nil {
- return err
- }
- providers[source.authSource.Name] = *source
- return nil
-}
-
-// UnregisterSource causes an SAML configuration to be unregistered
-func (source *Source) UnregisterSource() error {
- samlRWMutex.Lock()
- defer samlRWMutex.Unlock()
- delete(providers, source.authSource.Name)
- return nil
-}