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

	
	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")
	}

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

	
	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())
	}

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

				
				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 , " " ) } if a . samlSP == nil { return " " , " " , errutil . NewHTTPError ( http . StatusConflict , " " ) } sp := a . samlSP if err := req . ParseForm ( ) ; err != nil { return " " , " " , errutil . NewHTTPError ( http . StatusConflict , " " , 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 ( " " , 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 , " " ) case assertion . Subject == nil : return " " , " " , errutil . NewHTTPError ( http . StatusConflict , " " ) case assertion . Subject . NameID == nil : return " " , " " , errutil . NewHTTPError ( http . StatusConflict , " " ) case assertion . Subject . NameID . Value == " " : return " " , " " , errutil . NewHTTPError ( http . StatusConflict , " " ) } // User is successfully authenticated subject := fmt . Sprintf ( " " , 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 ( " " , 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
handleSAMLResponseInternal is a helper function called by handleSAMLResponse