// Authorize is invoked by ServeHTTP when we have a new, valid SAML assertion. // It sets a cookie that contains a signed JWT containing the assertion attributes. // It then redirects the user's browser to the original URL contained in RelayState. func (m *Middleware) Authorize(w http.ResponseWriter, r *http.Request, assertion *saml.Assertion) { secretBlock, _ := pem.Decode([]byte(m.ServiceProvider.Key)) redirectURI := "/" if r.Form.Get("RelayState") != "" { stateCookie, err := r.Cookie(fmt.Sprintf("saml_%s", r.Form.Get("RelayState"))) if err != nil { log.Printf("cannot find corresponding cookie: %s", fmt.Sprintf("saml_%s", r.Form.Get("RelayState"))) http.Error(w, http.StatusText(http.StatusForbidden), http.StatusForbidden) return } state, err := jwt.Parse(stateCookie.Value, func(t *jwt.Token) (interface{}, error) { return secretBlock.Bytes, nil }) if err != nil || !state.Valid { log.Printf("Cannot decode state JWT: %s (%s)", err, stateCookie.Value) http.Error(w, http.StatusText(http.StatusForbidden), http.StatusForbidden) return } claims := state.Claims.(jwt.MapClaims) redirectURI = claims["uri"].(string) // delete the cookie stateCookie.Value = "" stateCookie.Expires = time.Time{} http.SetCookie(w, stateCookie) } now := saml.TimeNow() claims := TokenClaims{} claims.Audience = m.ServiceProvider.Metadata().EntityID claims.IssuedAt = assertion.IssueInstant.Unix() claims.ExpiresAt = now.Add(m.CookieMaxAge).Unix() claims.NotBefore = now.Unix() if sub := assertion.Subject; sub != nil { if nameID := sub.NameID; nameID != nil { claims.StandardClaims.Subject = nameID.Value } } if assertion.AttributeStatement != nil { claims.Attributes = map[string][]string{} for _, attr := range assertion.AttributeStatement.Attributes { claimName := attr.FriendlyName if claimName == "" { claimName = attr.Name } for _, value := range attr.Values { claims.Attributes[claimName] = append(claims.Attributes[claimName], value.Value) } } } signedToken, err := jwt.NewWithClaims(jwt.SigningMethodHS256, claims).SignedString(secretBlock.Bytes) if err != nil { panic(err) } http.SetCookie(w, &http.Cookie{ Name: m.CookieName, Value: signedToken, MaxAge: int(m.CookieMaxAge.Seconds()), HttpOnly: false, Path: "/", }) http.Redirect(w, r, redirectURI, http.StatusFound) }
// GetSession returns the *Session for this request. // // If the remote user has specified a username and password in the request // then it is validated against the user database. If valid it sets a // cookie and returns the newly created session object. // // If the remote user has specified invalid credentials then a login form // is returned with an English-language toast telling the user their // password was invalid. // // If a session cookie already exists and represents a valid session, // then the session is returned // // If neither credentials nor a valid session cookie exist, this function // sends a login form and returns nil. func (s *Server) GetSession(w http.ResponseWriter, r *http.Request, req *saml.IdpAuthnRequest) *saml.Session { // if we received login credentials then maybe we can create a session if r.Method == "POST" && r.PostForm.Get("user") != "" { user := User{} if err := s.Store.Get(fmt.Sprintf("/users/%s", r.PostForm.Get("user")), &user); err != nil { s.sendLoginForm(w, r, req, "Invalid username or password") return nil } if err := bcrypt.CompareHashAndPassword(user.HashedPassword, []byte(r.PostForm.Get("password"))); err != nil { s.sendLoginForm(w, r, req, "Invalid username or password") return nil } session := &saml.Session{ ID: base64.StdEncoding.EncodeToString(randomBytes(32)), CreateTime: saml.TimeNow(), ExpireTime: saml.TimeNow().Add(sessionMaxAge), Index: hex.EncodeToString(randomBytes(32)), UserName: user.Name, Groups: user.Groups[:], UserEmail: user.Email, UserCommonName: user.CommonName, UserSurname: user.Surname, UserGivenName: user.GivenName, } if err := s.Store.Put(fmt.Sprintf("/sessions/%s", session.ID), &session); err != nil { http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError) return nil } http.SetCookie(w, &http.Cookie{ Name: "session", Value: session.ID, MaxAge: int(sessionMaxAge.Seconds()), HttpOnly: false, Path: "/", }) return session } if sessionCookie, err := r.Cookie("session"); err == nil { session := &saml.Session{} if err := s.Store.Get(fmt.Sprintf("/sessions/%s", sessionCookie.Value), session); err != nil { if err == ErrNotFound { s.sendLoginForm(w, r, req, "") return nil } http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError) return nil } if saml.TimeNow().After(session.ExpireTime) { s.sendLoginForm(w, r, req, "") return nil } return session } s.sendLoginForm(w, r, req, "") return nil }