diff options
author | 6543 <6543@obermui.de> | 2024-02-24 05:18:49 +0100 |
---|---|---|
committer | GitHub <noreply@github.com> | 2024-02-24 12:18:49 +0800 |
commit | 4ba642d07d50d7eb42ae33cd6f1f7f2c82c02a40 (patch) | |
tree | 52f879a6788100115c2127d62c0c6182cd96ad41 /services/auth | |
parent | 875f5ea6d83c8371f309df99654ca3556623004c (diff) | |
download | gitea-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.go | 22 | ||||
-rw-r--r-- | services/auth/source/saml/init.go | 29 | ||||
-rw-r--r-- | services/auth/source/saml/name_id_format.go | 38 | ||||
-rw-r--r-- | services/auth/source/saml/providers.go | 109 | ||||
-rw-r--r-- | services/auth/source/saml/source.go | 202 | ||||
-rw-r--r-- | services/auth/source/saml/source_authenticate.go | 16 | ||||
-rw-r--r-- | services/auth/source/saml/source_callout.go | 89 | ||||
-rw-r--r-- | services/auth/source/saml/source_metadata.go | 32 | ||||
-rw-r--r-- | services/auth/source/saml/source_register.go | 23 |
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 -} |