1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
|
// 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
}
|