-
Notifications
You must be signed in to change notification settings - Fork 2.9k
/
saml_handlers.go
116 lines (101 loc) · 3.3 KB
/
saml_handlers.go
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
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
package saml
import (
"crypto/x509"
"encoding/base64"
"encoding/xml"
"fmt"
"net/http"
"strings"
"github.com/crewjam/saml"
"github.com/dgrijalva/jwt-go"
log "github.com/sirupsen/logrus"
)
// ServeHTTP is the handler for /saml/metadata and /saml/acs endpoints
func (s *Provider) ServeHTTP(w http.ResponseWriter, r *http.Request) {
serviceProvider := s.serviceProvider
if r.URL.Path == serviceProvider.MetadataURL.Path {
buf, _ := xml.MarshalIndent(serviceProvider.Metadata(), "", " ")
w.Header().Set("Content-Type", "application/samlmetadata+xml")
w.Write(buf)
return
}
if r.URL.Path == serviceProvider.AcsURL.Path {
r.ParseForm()
assertion, err := serviceProvider.ParseResponse(r, s.getPossibleRequestIDs(r))
if err != nil {
if parseErr, ok := err.(*saml.InvalidResponseError); ok {
log.Debugf("RESPONSE: ===\n%s\n===\nNOW: %s\nERROR: %s",
parseErr.Response, parseErr.Now, parseErr.PrivateErr)
}
http.Error(w, http.StatusText(http.StatusForbidden), http.StatusForbidden)
return
}
s.HandleSamlAssertion(w, r, assertion)
return
}
http.NotFoundHandler().ServeHTTP(w, r)
}
func (s *Provider) getPossibleRequestIDs(r *http.Request) []string {
rv := []string{}
serviceProvider := s.serviceProvider
for name, value := range s.clientState.GetStates(r) {
if strings.HasPrefix(name, "Rancher_") {
continue
}
jwtParser := jwt.Parser{
ValidMethods: []string{jwt.SigningMethodHS256.Name},
}
token, err := jwtParser.Parse(value, func(t *jwt.Token) (interface{}, error) {
secretBlock := x509.MarshalPKCS1PrivateKey(serviceProvider.Key)
return secretBlock, nil
})
if err != nil || !token.Valid {
log.Debugf("... invalid token %s", err)
continue
}
claims := token.Claims.(jwt.MapClaims)
rv = append(rv, claims["id"].(string))
}
return rv
}
// HandleSamlLogin is the endpoint for /saml/login endpoint
func (s *Provider) HandleSamlLogin(w http.ResponseWriter, r *http.Request) (string, error) {
serviceProvider := s.serviceProvider
if r.URL.Path == serviceProvider.AcsURL.Path {
return "", fmt.Errorf("don't wrap Middleware with RequireAccount")
}
binding := saml.HTTPRedirectBinding
bindingLocation := serviceProvider.GetSSOBindingLocation(binding)
req, err := serviceProvider.MakeAuthenticationRequest(bindingLocation)
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return "", err
}
// relayState is limited to 80 bytes but also must be integrity protected.
// this means that we cannot use a JWT because it is way too long. Instead
// we set a cookie that corresponds to the state
relayState := base64.URLEncoding.EncodeToString(randomBytes(42))
secretBlock := x509.MarshalPKCS1PrivateKey(serviceProvider.Key)
state := jwt.New(jwt.SigningMethodHS256)
claims := state.Claims.(jwt.MapClaims)
claims["id"] = req.ID
claims["uri"] = r.URL.String()
signedState, err := state.SignedString(secretBlock)
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return "", err
}
s.clientState.SetState(w, r, relayState, signedState)
if binding == saml.HTTPRedirectBinding {
redirectURL := req.Redirect(relayState)
return redirectURL.String(), nil
}
return "", nil
}
func randomBytes(n int) []byte {
rv := make([]byte, n)
if _, err := saml.RandReader.Read(rv); err != nil {
panic(err)
}
return rv
}