SCIM: Validate provisioned users (#102099)
* Validate authID when user is provisioned * Add new `user_unique_id` to `user_auth` table * Validate provisioned user with saml assertion * Rework `ExternalUID` * Validate for ExternalUID only * Enhance verbosity * Move ExternalUID to saml config * Rename db variable for externalUID * Add verbosity to debug ExternalUID * Assign new error for ExternalUID mismatch * Add `GetByLoginFn` * Add new configuration to saml tests * add validation for empty externalUID
This commit is contained in:
@@ -7,6 +7,7 @@ import (
|
||||
"strconv"
|
||||
|
||||
claims "github.com/grafana/authlib/types"
|
||||
"go.opentelemetry.io/otel/attribute"
|
||||
"golang.org/x/sync/singleflight"
|
||||
|
||||
"github.com/grafana/grafana/pkg/apimachinery/errutil"
|
||||
@@ -45,6 +46,14 @@ var (
|
||||
"user.sync.fetch-not-found",
|
||||
errutil.WithPublicMessage("User not found"),
|
||||
)
|
||||
errMismatchedExternalUID = errutil.Unauthorized(
|
||||
"user.sync.mismatched-externalUID",
|
||||
errutil.WithPublicMessage("Mismatched externalUID"),
|
||||
)
|
||||
errEmptyExternalUID = errutil.Unauthorized(
|
||||
"user.sync.empty-externalUID",
|
||||
errutil.WithPublicMessage("Empty externalUID"),
|
||||
)
|
||||
)
|
||||
|
||||
var (
|
||||
@@ -231,7 +240,7 @@ func (s *UserSync) upsertAuthConnection(ctx context.Context, userID int64, ident
|
||||
return nil
|
||||
}
|
||||
|
||||
// If a user does not a connection to a specific auth module, create it.
|
||||
// If a user does not have a connection to a specific auth module, create it.
|
||||
// This can happen when: using multiple auth client where the same user exists in several or
|
||||
// changing to new auth client
|
||||
if createConnection {
|
||||
@@ -265,6 +274,8 @@ func (s *UserSync) updateUserAttributes(ctx context.Context, usr *user.User, id
|
||||
ctx, span := s.tracer.Start(ctx, "user.sync.updateUserAttributes")
|
||||
defer span.End()
|
||||
|
||||
needsConnectionCreation := userAuth == nil
|
||||
|
||||
if errProtection := s.userProtectionService.AllowUserMapping(usr, id.AuthenticatedBy); errProtection != nil {
|
||||
return errUserProtection.Errorf("user mapping not allowed: %w", errProtection)
|
||||
}
|
||||
@@ -305,14 +316,38 @@ func (s *UserSync) updateUserAttributes(ctx context.Context, usr *user.User, id
|
||||
needsUpdate = true
|
||||
}
|
||||
|
||||
if needsUpdate {
|
||||
span.SetAttributes(
|
||||
attribute.String("identity.ID", id.ID),
|
||||
attribute.String("identity.ExternalUID", id.ExternalUID),
|
||||
)
|
||||
if usr.IsProvisioned {
|
||||
s.log.Debug("User is provisioned", "id,UID", id.UID)
|
||||
needsConnectionCreation = false
|
||||
authInfo, err := s.authInfoService.GetAuthInfo(ctx, &login.GetAuthInfoQuery{UserId: usr.ID, AuthModule: id.AuthenticatedBy})
|
||||
if err != nil {
|
||||
s.log.Error("Error getting auth info", "error", err)
|
||||
return err
|
||||
}
|
||||
|
||||
if id.ExternalUID == "" {
|
||||
s.log.Error("externalUID is empty", "id", id.UID)
|
||||
return errEmptyExternalUID.Errorf("externalUID is empty")
|
||||
}
|
||||
|
||||
if id.ExternalUID != authInfo.ExternalUID {
|
||||
s.log.Error("mismatched externalUID", "provisioned_externalUID", authInfo.ExternalUID, "identity_externalUID", id.ExternalUID)
|
||||
return errMismatchedExternalUID.Errorf("externalUID mistmatch")
|
||||
}
|
||||
}
|
||||
|
||||
if needsUpdate && !usr.IsProvisioned {
|
||||
s.log.FromContext(ctx).Debug("Syncing user info", "id", id.ID, "update", fmt.Sprintf("%v", updateCmd))
|
||||
if err := s.userService.Update(ctx, updateCmd); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return s.upsertAuthConnection(ctx, usr.ID, id, userAuth == nil)
|
||||
return s.upsertAuthConnection(ctx, usr.ID, id, needsConnectionCreation)
|
||||
}
|
||||
|
||||
func (s *UserSync) createUser(ctx context.Context, id *authn.Identity) (*user.User, error) {
|
||||
|
||||
Reference in New Issue
Block a user