Skip to content

Commit 7f5c1ec

Browse files
authored
feat: Annotate users when givenName and familyName are identical (#529)
## Summary When users sign up via GitHub with a single-word display name, the identity provider populates both `givenName` and `familyName` with the same value. Until now, front-end clients had no reliable signal to detect this and prompt the user to provide distinct names. This PR adds annotation-based signalling to the user controller. On every reconcile, the controller evaluates whether `givenName` equals `familyName` (both non-empty) and manages the `iam.miloapis.com/name-review-required: "true"` annotation accordingly — adding it when the names match, removing it once they differ. The annotation key is exported as a typed constant in `user_types.go` with documentation explaining semantics and usage for front-end consumers. ## Changes - **`pkg/apis/iam/v1alpha1/user_types.go`** — Adds `UserNameReviewRequiredAnnotation` constant with inline doc comment covering purpose, expected front-end behavior, value semantics, and a YAML example. - **`internal/controllers/iam/user_controller.go`** — Adds `reconcileNameReviewAnnotation` method and calls it in the main reconcile loop after `ensureOwnerReferences`. ## Test plan - [ ] User created via GitHub with a single-word name has the annotation set after reconciliation - [ ] Annotation is removed after the user updates their profile with distinct given and family names - [ ] User with already distinct names is not annotated - [ ] No unnecessary API calls when annotation state is already correct (no-op path)
2 parents 6041611 + 865259f commit 7f5c1ec

2 files changed

Lines changed: 60 additions & 0 deletions

File tree

internal/controllers/iam/user_controller.go

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,12 @@ func (r *UserController) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.
7171
return ctrl.Result{}, err
7272
}
7373

74+
// Reconcile the name-review annotation based on whether givenName and familyName match
75+
if err := r.reconcileNameReviewAnnotation(ctx, user); err != nil {
76+
log.Error(err, "Failed to reconcile name review annotation")
77+
return ctrl.Result{}, err
78+
}
79+
7480
// Determine desired state based on existence of any UserDeactivation for this user
7581
var udList iamv1alpha1.UserDeactivationList
7682
if err := r.Client.List(ctx, &udList, client.MatchingFields{"spec.userRef.name": user.Name}); err != nil {
@@ -131,6 +137,40 @@ func (r *UserController) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.
131137
return ctrl.Result{}, nil
132138
}
133139

140+
// reconcileNameReviewAnnotation adds or removes the name-review-required annotation depending on
141+
// whether givenName and familyName are identical. This situation arises when an identity provider
142+
// (e.g. GitHub) supplies a single display name and the system copies it into both fields.
143+
func (r *UserController) reconcileNameReviewAnnotation(ctx context.Context, user *iamv1alpha1.User) error {
144+
log := log.FromContext(ctx).WithName("reconcile-name-review-annotation")
145+
146+
annotations := user.GetAnnotations()
147+
_, annotationPresent := annotations[iamv1alpha1.UserNameReviewRequiredAnnotation]
148+
namesAreEqual := user.Spec.GivenName != "" && user.Spec.GivenName == user.Spec.FamilyName
149+
150+
switch {
151+
case namesAreEqual && !annotationPresent:
152+
if annotations == nil {
153+
annotations = map[string]string{}
154+
}
155+
annotations[iamv1alpha1.UserNameReviewRequiredAnnotation] = "true"
156+
user.SetAnnotations(annotations)
157+
if err := r.Client.Update(ctx, user); err != nil {
158+
return fmt.Errorf("failed to add name-review annotation: %w", err)
159+
}
160+
log.Info("Added name-review-required annotation", "user", user.Name)
161+
162+
case !namesAreEqual && annotationPresent:
163+
delete(annotations, iamv1alpha1.UserNameReviewRequiredAnnotation)
164+
user.SetAnnotations(annotations)
165+
if err := r.Client.Update(ctx, user); err != nil {
166+
return fmt.Errorf("failed to remove name-review annotation: %w", err)
167+
}
168+
log.Info("Removed name-review-required annotation", "user", user.Name)
169+
}
170+
171+
return nil
172+
}
173+
134174
// ensureOwnerReferences ensures that PolicyBinding and UserPreference resources have proper owner references
135175
func (r *UserController) ensureOwnerReferences(ctx context.Context, user *iamv1alpha1.User) error {
136176
log := log.FromContext(ctx).WithName("ensure-owner-references")

pkg/apis/iam/v1alpha1/user_types.go

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -130,3 +130,23 @@ const (
130130
AuthProviderGitHub AuthProvider = "github"
131131
AuthProviderGoogle AuthProvider = "google"
132132
)
133+
134+
const (
135+
// UserNameReviewRequiredAnnotation is set on a User when givenName and familyName are
136+
// identical, which typically happens when the identity provider (e.g. GitHub) supplies
137+
// only a single display name and the system splits it across both fields.
138+
//
139+
// Presence of this annotation signals that the user has not yet provided distinct given
140+
// and family names. Front-end clients should prompt the user to review and update their
141+
// profile when this annotation is present.
142+
//
143+
// The annotation value is always "true". The annotation is removed automatically by the
144+
// user controller once givenName and familyName differ.
145+
//
146+
// Example:
147+
//
148+
// metadata:
149+
// annotations:
150+
// iam.miloapis.com/name-review-required: "true"
151+
UserNameReviewRequiredAnnotation = "iam.miloapis.com/name-review-required"
152+
)

0 commit comments

Comments
 (0)