From 3070b9b8e676c102744b33b63122ca468d4594c7 Mon Sep 17 00:00:00 2001 From: Matous Jobanek Date: Fri, 24 Jan 2025 13:24:55 +0100 Subject: [PATCH] move EncodeUserIdentifier & signup helper functions --- pkg/test/usersignup/usersignup.go | 43 +++++++++++++++++++++++++++ pkg/usersignup/usersignup.go | 49 ++++++++++++++++++++++++++++++- pkg/usersignup/usersignup_test.go | 17 ++++++++++- 3 files changed, 107 insertions(+), 2 deletions(-) diff --git a/pkg/test/usersignup/usersignup.go b/pkg/test/usersignup/usersignup.go index 2a4cc326..86c0af46 100644 --- a/pkg/test/usersignup/usersignup.go +++ b/pkg/test/usersignup/usersignup.go @@ -168,6 +168,40 @@ func SignupComplete(reason string) Modifier { } } +// SignupIncomplete adds Complete condition with status false and with the given reason and message +func SignupIncomplete(reason, message string) Modifier { + return func(userSignup *toolchainv1alpha1.UserSignup) { + userSignup.Status.Conditions = condition.AddStatusConditions(userSignup.Status.Conditions, + toolchainv1alpha1.Condition{ + Type: toolchainv1alpha1.UserSignupComplete, + Status: corev1.ConditionFalse, + Reason: reason, + Message: message, + }) + } +} + +// WithCompliantUsername sets the given compliant username in the status +func WithCompliantUsername(compliantUsername string) Modifier { + return func(signup *toolchainv1alpha1.UserSignup) { + signup.Status.CompliantUsername = compliantUsername + } +} + +// WithHomeSpace sets the given homeSpace in the status +func WithHomeSpace(homeSpace string) Modifier { + return func(signup *toolchainv1alpha1.UserSignup) { + signup.Status.HomeSpace = homeSpace + } +} + +// WithScheduledDeactivationTimestamp sets the given scheduledDeactivationTimestamp in the status +func WithScheduledDeactivationTimestamp(scheduledDeactivationTimestamp *metav1.Time) Modifier { + return func(signup *toolchainv1alpha1.UserSignup) { + signup.Status.ScheduledDeactivationTimestamp = scheduledDeactivationTimestamp + } +} + func CreatedBefore(before time.Duration) Modifier { return func(userSignup *toolchainv1alpha1.UserSignup) { userSignup.ObjectMeta.CreationTimestamp = metav1.Time{Time: time.Now().Add(-before)} @@ -213,6 +247,15 @@ func WithName(name string) Modifier { } } +// WithEncodedName sets the encoded value as the name of the resource +// and the original value as the PreferredUsername +func WithEncodedName(name string) Modifier { + return func(userSignup *toolchainv1alpha1.UserSignup) { + userSignup.Name = usersignup.EncodeUserIdentifier(name) + userSignup.Spec.IdentityClaims.PreferredUsername = name + } +} + type Modifier func(*toolchainv1alpha1.UserSignup) func NewUserSignup(modifiers ...Modifier) *toolchainv1alpha1.UserSignup { diff --git a/pkg/usersignup/usersignup.go b/pkg/usersignup/usersignup.go index c571be8a..0ec8c984 100644 --- a/pkg/usersignup/usersignup.go +++ b/pkg/usersignup/usersignup.go @@ -2,9 +2,11 @@ package usersignup import ( "fmt" - validation "k8s.io/apimachinery/pkg/util/validation" + "hash/crc32" "regexp" "strings" + + validation "k8s.io/apimachinery/pkg/util/validation" ) var ( @@ -74,3 +76,48 @@ func TransformUsername(username string, ForbiddenUsernamePrefixes []string, Forb } return newUsername } + +const DNS1123NameMaximumLength = 63 + +// EncodeUserIdentifier transforms a subject value (the user's username) to make it DNS-1123 compliant, +// by removing invalid characters, trimming the length and prefixing with a CRC32 checksum if required. +// ### WARNING ### changing this function will cause breakage, as it is used to lookup existing UserSignup +// resources. If a change is absolutely required, then all existing UserSignup instances must be migrated +// to the new value +func EncodeUserIdentifier(subject string) string { + // Sanitize subject to be compliant with DNS labels format (RFC-1123) + encoded := sanitizeDNS1123(subject) + + // Add a checksum prefix if the encoded value is different to the original subject value + if encoded != subject { + encoded = fmt.Sprintf("%x-%s", crc32.Checksum([]byte(subject), crc32.IEEETable), encoded) + } + + // Trim if the length exceeds the maximum + if len(encoded) > DNS1123NameMaximumLength { + encoded = encoded[0:DNS1123NameMaximumLength] + } + + return encoded +} + +func sanitizeDNS1123(str string) string { + // convert to lowercase + lstr := strings.ToLower(str) + + // remove unwanted characters + b := strings.Builder{} + for _, r := range lstr { + switch { + case r >= '0' && r <= '9': + fallthrough + case r >= 'a' && r <= 'z': + fallthrough + case r == '-': + b.WriteRune(r) + } + } + + // remove leading and trailing '-' + return strings.Trim(b.String(), "-") +} diff --git a/pkg/usersignup/usersignup_test.go b/pkg/usersignup/usersignup_test.go index 9b92878a..691c16a9 100644 --- a/pkg/usersignup/usersignup_test.go +++ b/pkg/usersignup/usersignup_test.go @@ -1,9 +1,10 @@ package usersignup import ( - "k8s.io/apimachinery/pkg/util/validation" "testing" + "k8s.io/apimachinery/pkg/util/validation" + "github.com/stretchr/testify/assert" ) @@ -48,3 +49,17 @@ func assertName(t *testing.T, expected, username string) { assert.Empty(t, validation.IsDNS1123Label(TransformUsername(username, []string{"openshift", "kube", "default", "redhat", "sandbox"}, []string{"admin"})), "username is not a compliant DNS label") assert.Equal(t, expected, TransformUsername(username, []string{"openshift", "kube", "default", "redhat", "sandbox"}, []string{"admin"})) } + +func TestEncodeUsername(t *testing.T) { + assertEncodedUsername(t, "abcde-12345", "abcde-12345") + assertEncodedUsername(t, "c0177ca4-abcde-12345", "abcde\\*-12345") + assertEncodedUsername(t, "ca3e1e0f-1234567", "-1234567") + assertEncodedUsername(t, "e3632025-0123456789abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqr", "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-01234567890123456789") + assertEncodedUsername(t, "a05a4053-abcxyz", "abc:xyz") + assertEncodedUsername(t, "ed6bd2b5-abc", "abc---") + assertEncodedUsername(t, "8fa24710-johnnykubesawcom", "johnny@kubesaw.com") +} + +func assertEncodedUsername(t *testing.T, expected, username string) { + assert.Equal(t, expected, EncodeUserIdentifier(username)) +}