gitea/services/auth/source/saml/source_callout.go

90 lines
2.6 KiB
Go

// 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
}