src/server/auth/server/saml.go
func (a *apiServer) handleSAMLResponseInternal(req *http.Request) (string, string, *errutil.HTTPError) {
	a.configMu.Lock()
	defer a.configMu.Unlock()
	a.samlSPMu.Lock()
	defer a.samlSPMu.Unlock()

	if a.configCache == nil {
		return "", "", errutil.NewHTTPError(http.StatusConflict, "auth has no active config (either never set or disabled)")

	}
	if a.samlSP == nil {
		return "", "", errutil.NewHTTPError(http.StatusConflict, "SAML ACS has not been configured or was disabled")
	}
	sp := a.samlSP

	if err := req.ParseForm(); err != nil {
		return "", "", errutil.NewHTTPError(http.StatusConflict, "Could not parse request form: %v", err)
	}
	// No possible request IDs b/c only IdP-initiated auth is implemented for now
	assertion, err := sp.ParseResponse(req, []string{""})
	if err != nil {
		errMsg := fmt.Sprintf("Error parsing SAML response: %v", err)
		if invalidRespErr, ok := err.(*saml.InvalidResponseError); ok {
			errMsg += "\n(" + invalidRespErr.PrivateErr.Error() + ")"
		}
		return "", "", errutil.NewHTTPError(http.StatusBadRequest, errMsg)
	}

	// Make sure all the fields we need are present (avoid segfault)
	switch {
	case assertion == nil:
		return "", "", errutil.NewHTTPError(http.StatusConflict, "Error parsing SAML response: assertion is nil")
	case assertion.Subject == nil:
		return "", "", errutil.NewHTTPError(http.StatusConflict, "Error parsing SAML response: assertion.Subject is nil")
	case assertion.Subject.NameID == nil:
		return "", "", errutil.NewHTTPError(http.StatusConflict, "Error parsing SAML response: assertion.Subject.NameID is nil")
	case assertion.Subject.NameID.Value == "":
		return "", "", errutil.NewHTTPError(http.StatusConflict, "Error parsing SAML response: assertion.Subject.NameID.Value is unset")
	}

	// User is successfully authenticated
	subject := fmt.Sprintf("%s:%s", a.configCache.IDP.Name, assertion.Subject.NameID.Value)

	// Get new OTP for user (exp. from config if set, or default session duration)
	expiration := time.Now().Add(time.Duration(defaultSAMLTTLSecs) * time.Second)
	if a.configCache.SAMLSvc.SessionDuration != 0 {
		expiration = time.Now().Add(a.configCache.SAMLSvc.SessionDuration)
	}
	authCode, err := a.getOneTimePassword(req.Context(), subject, expiration)
	if err != nil {
		return "", "", errutil.NewHTTPError(http.StatusInternalServerError, err.Error())
	}

	// Update group memberships
	if a.configCache.IDP.GroupAttribute != "" {
		for _, attribute := range assertion.AttributeStatements {
			for _, attr := range attribute.Attributes {
				if attr.Name != a.configCache.IDP.GroupAttribute {
					continue
				}

				// Collect groups specified in this attribute and record them
				var groups []string
				for _, v := range attr.Values {
					groups = append(groups, fmt.Sprintf("group/%s:%s", a.configCache.IDP.Name, v.Value))
				}
				if err := a.setGroupsForUserInternal(context.Background(), subject, groups); err != nil {
					return "", "", errutil.NewHTTPError(http.StatusInternalServerError, err.Error())
				}
			}
		}
	}

	return subject, authCode, nil
}
func (a *apiServer) handleSAMLResponseInternal(req *http.Request) (string, string, *errutil.HTTPError) {
	a.configMu.Lock()
	defer a.configMu.Unlock()
	a.samlSPMu.Lock()
	defer a.samlSPMu.Unlock()

	if a.configCache == nil {
		return "", "", errutil.NewHTTPError(http.StatusConflict, "auth has no active config (either never set or disabled)")

	}
	if a.samlSP == nil {
		return "", "", errutil.NewHTTPError(http.StatusConflict, "SAML ACS has not been configured or was disabled")
	}
	sp := a.samlSP

	if err := req.ParseForm(); err != nil {
		return "", "", errutil.NewHTTPError(http.StatusConflict, "Could not parse request form: %v", err)
	}
	// No possible request IDs b/c only IdP-initiated auth is implemented for now
	assertion, err := sp.ParseResponse(req, []string{""})
	if err != nil {
		errMsg := fmt.Sprintf("Error parsing SAML response: %v", err)
		if invalidRespErr, ok := err.(*saml.InvalidResponseError); ok {
			errMsg += "\n(" + invalidRespErr.PrivateErr.Error() + ")"
		}
		return "", "", errutil.NewHTTPError(http.StatusBadRequest, errMsg)
	}

	// Make sure all the fields we need are present (avoid segfault)
	switch {
	case assertion == nil:
		return "", "", errutil.NewHTTPError(http.StatusConflict, "Error parsing SAML response: assertion is nil")
	case assertion.Subject == nil:
		return "", "", errutil.NewHTTPError(http.StatusConflict, "Error parsing SAML response: assertion.Subject is nil")
	case assertion.Subject.NameID == nil:
		return "", "", errutil.NewHTTPError(http.StatusConflict, "Error parsing SAML response: assertion.Subject.NameID is nil")
	case assertion.Subject.NameID.Value == "":
		return "", "", errutil.NewHTTPError(http.StatusConflict, "Error parsing SAML response: assertion.Subject.NameID.Value is unset")
	}

	// User is successfully authenticated
	subject := fmt.Sprintf("%s:%s", a.configCache.IDP.Name, assertion.Subject.NameID.Value)

	// Get new OTP for user (exp. from config if set, or default session duration)
	expiration := time.Now().Add(time.Duration(defaultSAMLTTLSecs) * time.Second)
	if a.configCache.SAMLSvc.SessionDuration != 0 {
		expiration = time.Now().Add(a.configCache.SAMLSvc.SessionDuration)
	}
	authCode, err := a.getOneTimePassword(req.Context(), subject, expiration)
	if err != nil {
		return "", "", errutil.NewHTTPError(http.StatusInternalServerError, err.Error())
	}

	// Update group memberships
	if a.configCache.IDP.GroupAttribute != "" {
		for _, attribute := range assertion.AttributeStatements {
			for _, attr := range attribute.Attributes {
				if attr.Name != a.configCache.IDP.GroupAttribute {
					continue
				}

				// Collect groups specified in this attribute and record them
				var groups []string
				for _, v := range attr.Values {
					groups = append(groups, fmt.Sprintf("group/%s:%s", a.configCache.IDP.Name, v.Value))
				}
				if err := a.setGroupsForUserInternal(context.Background(), subject, groups); err != nil {
					return "", "", errutil.NewHTTPError(http.StatusInternalServerError, err.Error())
				}
			}
		}
	}

	return subject, authCode, nil
}
// handleSAMLResponseInternal is a helper function called by handleSAMLResponse