Skip to content

Commit 73a72e9

Browse files
feat: enhance user metadata lookup functionality
- Added comprehensive tests for the MetadataLookup method across Auth0, Authelia, and mock implementations, ensuring accurate handling of various input formats including canonical and search lookups. - Updated README files to reflect support for Auth0 and Authelia, detailing the handling of Subject Identifiers (SUB) and their generation. - Bumped version in Chart.yaml from 0.2.4 to 0.2.5 to reflect the latest changes. Jira Ticket: https://linuxfoundation.atlassian.net/browse/LFXV2-493 Generated with [Cursor](https://cursor.com/) Signed-off-by: Mauricio Zanetti Salomao <mauriciozanetti86@gmail.com>
1 parent a86675f commit 73a72e9

File tree

8 files changed

+657
-3
lines changed

8 files changed

+657
-3
lines changed

README.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -261,8 +261,10 @@ nats request lfx.auth-service.user_metadata.read john.doe
261261
- **Search lookups** are provided for convenience and user-facing interfaces
262262
- The pipe character (`|`) in canonical identifiers must be escaped with quotes in shell commands
263263
- Both strategies return the same metadata format on success
264+
- The service supports **Auth0**, **Authelia**, and **mock** repositories based on configuration
264265
- When using mock or authelia mode, the service simulates Auth0 behavior for development and testing
265266
- For detailed Auth0-specific behavior and limitations, see the [Auth0 Integration Documentation](internal/infrastructure/auth0/README.md)
267+
- For detailed Authelia-specific behavior and SUB management, see the [Authelia Integration Documentation](internal/infrastructure/authelia/README.md)
266268

267269
---
268270

charts/lfx-v2-auth-service/Chart.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,5 +5,5 @@ apiVersion: v2
55
name: lfx-v2-auth-service
66
description: LFX Platform V2 Auth Service chart
77
type: application
8-
version: 0.2.4
8+
version: 0.2.5
99
appVersion: "latest"

internal/infrastructure/auth0/user_test.go

Lines changed: 155 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -809,6 +809,161 @@ func ptrValue(ptr *string) string {
809809
return *ptr
810810
}
811811

812+
// TestUserReaderWriter_MetadataLookup tests the MetadataLookup method for Auth0 implementation
813+
func TestUserReaderWriter_MetadataLookup(t *testing.T) {
814+
ctx := context.Background()
815+
writer := &userReaderWriter{}
816+
817+
tests := []struct {
818+
name string
819+
input string
820+
expectedCanonical bool
821+
expectedSub string
822+
expectedUserID string
823+
expectedUsername string
824+
expectedPrimaryEmail string
825+
}{
826+
{
827+
name: "canonical lookup with pipe separator",
828+
input: "auth0|123456789",
829+
expectedCanonical: true,
830+
expectedSub: "auth0|123456789",
831+
expectedUserID: "auth0|123456789",
832+
expectedUsername: "",
833+
expectedPrimaryEmail: "",
834+
},
835+
{
836+
name: "canonical lookup with google oauth",
837+
input: "google-oauth2|987654321",
838+
expectedCanonical: true,
839+
expectedSub: "google-oauth2|987654321",
840+
expectedUserID: "google-oauth2|987654321",
841+
expectedUsername: "",
842+
expectedPrimaryEmail: "",
843+
},
844+
{
845+
name: "canonical lookup with github oauth",
846+
input: "github|456789123",
847+
expectedCanonical: true,
848+
expectedSub: "github|456789123",
849+
expectedUserID: "github|456789123",
850+
expectedUsername: "",
851+
expectedPrimaryEmail: "",
852+
},
853+
{
854+
name: "canonical lookup with saml enterprise",
855+
input: "samlp|enterprise|user123",
856+
expectedCanonical: true,
857+
expectedSub: "samlp|enterprise|user123",
858+
expectedUserID: "samlp|enterprise|user123",
859+
expectedUsername: "",
860+
expectedPrimaryEmail: "",
861+
},
862+
{
863+
name: "canonical lookup with linkedin oauth",
864+
input: "linkedin|789123456",
865+
expectedCanonical: true,
866+
expectedSub: "linkedin|789123456",
867+
expectedUserID: "linkedin|789123456",
868+
expectedUsername: "",
869+
expectedPrimaryEmail: "",
870+
},
871+
{
872+
name: "search lookup with username",
873+
input: "john.doe",
874+
expectedCanonical: false,
875+
expectedSub: "",
876+
expectedUserID: "",
877+
expectedUsername: "john.doe",
878+
expectedPrimaryEmail: "",
879+
},
880+
{
881+
name: "search lookup with username containing numbers",
882+
input: "developer123",
883+
expectedCanonical: false,
884+
expectedSub: "",
885+
expectedUserID: "",
886+
expectedUsername: "developer123",
887+
expectedPrimaryEmail: "",
888+
},
889+
{
890+
name: "search lookup with username containing dots and underscores",
891+
input: "jane_smith.dev",
892+
expectedCanonical: false,
893+
expectedSub: "",
894+
expectedUserID: "",
895+
expectedUsername: "jane_smith.dev",
896+
expectedPrimaryEmail: "",
897+
},
898+
{
899+
name: "empty input",
900+
input: "",
901+
expectedCanonical: false,
902+
expectedSub: "",
903+
expectedUserID: "",
904+
expectedUsername: "",
905+
expectedPrimaryEmail: "",
906+
},
907+
{
908+
name: "whitespace only input",
909+
input: " ",
910+
expectedCanonical: false,
911+
expectedSub: "",
912+
expectedUserID: "",
913+
expectedUsername: "",
914+
expectedPrimaryEmail: "",
915+
},
916+
{
917+
name: "input with leading/trailing whitespace - canonical",
918+
input: " auth0|123456789 ",
919+
expectedCanonical: true,
920+
expectedSub: "auth0|123456789",
921+
expectedUserID: "auth0|123456789",
922+
expectedUsername: "",
923+
expectedPrimaryEmail: "",
924+
},
925+
{
926+
name: "input with leading/trailing whitespace - search",
927+
input: " john.doe ",
928+
expectedCanonical: false,
929+
expectedSub: "",
930+
expectedUserID: "",
931+
expectedUsername: "john.doe",
932+
expectedPrimaryEmail: "",
933+
},
934+
}
935+
936+
for _, tt := range tests {
937+
t.Run(tt.name, func(t *testing.T) {
938+
user := &model.User{}
939+
940+
isCanonical := writer.MetadataLookup(ctx, tt.input, user)
941+
942+
// Check canonical vs search lookup decision
943+
if isCanonical != tt.expectedCanonical {
944+
t.Errorf("MetadataLookup() canonical = %v, expected %v", isCanonical, tt.expectedCanonical)
945+
}
946+
947+
// Check user fields are set correctly
948+
if user.Sub != tt.expectedSub {
949+
t.Errorf("MetadataLookup() Sub = %q, expected %q", user.Sub, tt.expectedSub)
950+
}
951+
952+
if user.UserID != tt.expectedUserID {
953+
t.Errorf("MetadataLookup() UserID = %q, expected %q", user.UserID, tt.expectedUserID)
954+
}
955+
956+
if user.Username != tt.expectedUsername {
957+
t.Errorf("MetadataLookup() Username = %q, expected %q", user.Username, tt.expectedUsername)
958+
}
959+
960+
if user.PrimaryEmail != tt.expectedPrimaryEmail {
961+
t.Errorf("MetadataLookup() PrimaryEmail = %q, expected %q", user.PrimaryEmail, tt.expectedPrimaryEmail)
962+
}
963+
})
964+
}
965+
}
966+
812967
// Helper function to check if a string contains a substring
813968
func containsString(s, substr string) bool {
814969
return len(s) >= len(substr) && (s == substr ||

internal/infrastructure/authelia/README.md

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -108,7 +108,9 @@ The Authelia integration requires the following configuration parameters:
108108

109109
The Subject Identifier (SUB) in Authelia is a deterministic UUID that uniquely identifies each user within the system. Key characteristics:
110110

111-
- **Deterministic Generation**: The SUB is a UUID that is consistently generated for each user by Authelia
111+
- **Deterministic Generation**: The SUB is a UUID that is deterministically generated from the username by Authelia
112+
- **No Provider Prefix**: Unlike Auth0 (which uses formats like `auth0|123456789`), Authelia SUBs are pure UUIDs without any provider prefix (e.g., `550e8400-e29b-41d4-a716-446655440000`)
113+
- **Username-Based**: The SUB is consistently derived from the username, ensuring the same username always produces the same SUB
112114
- **Token-Based Persistence**: To ensure consistent data retrieval from Authelia, the SUB is only persisted when a user is updated using a valid authentication token
113115
- **OIDC UserInfo Endpoint**: The SUB can be retrieved from Authelia's OIDC UserInfo endpoint at `/api/oidc/userinfo` using a valid token
114116

internal/infrastructure/authelia/user.go

Lines changed: 25 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,8 @@ import (
1616
"github.com/linuxfoundation/lfx-v2-auth-service/pkg/errors"
1717
"github.com/linuxfoundation/lfx-v2-auth-service/pkg/httpclient"
1818
"github.com/linuxfoundation/lfx-v2-auth-service/pkg/redaction"
19+
20+
"github.com/google/uuid"
1921
)
2022

2123
// userReaderWriter implements UserReaderWriter with pluggable storage and ConfigMap sync
@@ -112,7 +114,11 @@ func (a *userReaderWriter) GetUser(ctx context.Context, user *model.User) (*mode
112114
return nil, errors.NewValidation("user is required")
113115
}
114116

115-
key := user.Sub
117+
key := ""
118+
if user.Sub != "" {
119+
key = a.storage.BuildLookupKey(ctx, "sub", user.BuildSubIndexKey(ctx))
120+
}
121+
116122
if key == "" {
117123
key = user.Username
118124
}
@@ -128,6 +134,24 @@ func (a *userReaderWriter) GetUser(ctx context.Context, user *model.User) (*mode
128134
return existingUser.User, nil
129135
}
130136

137+
// MetadataLookup prepares the user for metadata lookup based on the input
138+
// Returns true if should use canonical lookup, false if should use search
139+
func (u *userReaderWriter) MetadataLookup(ctx context.Context, input string, user *model.User) bool {
140+
input = strings.TrimSpace(input)
141+
142+
user.Username = input
143+
if input != "" {
144+
sub, err := uuid.Parse(input)
145+
if err != nil {
146+
return false
147+
}
148+
user.Sub = sub.String()
149+
user.UserID = sub.String()
150+
return true
151+
}
152+
return false
153+
}
154+
131155
// UpdateUser updates a user only in storage with patch-like behavior, updating only changed fields
132156
func (a *userReaderWriter) UpdateUser(ctx context.Context, user *model.User) (*model.User, error) {
133157
if user == nil {

0 commit comments

Comments
 (0)