Skip to content

Commit 404912e

Browse files
Merge pull request #12 from mauriciozanettisalomao/feat/lfxv2-588-user-metadata-read-authelia
[LFXV2-588] User metadata read - authelia
2 parents 2b246bf + 09cdaf5 commit 404912e

File tree

15 files changed

+715
-255
lines changed

15 files changed

+715
-255
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/domain/model/user.go

Lines changed: 0 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -237,22 +237,3 @@ func (a *UserMetadata) Patch(update *UserMetadata) bool {
237237

238238
return updated
239239
}
240-
241-
// PrepareForMetadataLookup prepares the user for metadata lookup based on the input
242-
// Returns true if should use canonical lookup, false if should use search
243-
func (u *User) PrepareForMetadataLookup(input string) bool {
244-
input = strings.TrimSpace(input)
245-
246-
if strings.Contains(input, "|") {
247-
// Input contains "|", use as sub for canonical lookup
248-
u.Sub = input
249-
u.UserID = input // Auth0 uses user_id for the canonical lookup
250-
u.Username = "" // Clear username field
251-
return true
252-
}
253-
// Input doesn't contain "|", use for search query
254-
u.Sub = "" // Clear sub field
255-
u.UserID = "" // Clear user_id field
256-
u.Username = input
257-
return false
258-
}

internal/domain/model/user_test.go

Lines changed: 0 additions & 223 deletions
Original file line numberDiff line numberDiff line change
@@ -394,229 +394,6 @@ func TestUserMetadata_userMetadataSanitize(t *testing.T) {
394394
})
395395
}
396396

397-
func TestUser_PrepareForMetadataLookup(t *testing.T) {
398-
tests := []struct {
399-
name string
400-
input string
401-
expectedCanonical bool
402-
expectedSub string
403-
expectedUserID string
404-
expectedUsername string
405-
}{
406-
{
407-
name: "canonical lookup with auth0 connection",
408-
input: "auth0|123456789",
409-
expectedCanonical: true,
410-
expectedSub: "auth0|123456789",
411-
expectedUserID: "auth0|123456789",
412-
expectedUsername: "",
413-
},
414-
{
415-
name: "canonical lookup with google oauth2",
416-
input: "google-oauth2|987654321",
417-
expectedCanonical: true,
418-
expectedSub: "google-oauth2|987654321",
419-
expectedUserID: "google-oauth2|987654321",
420-
expectedUsername: "",
421-
},
422-
{
423-
name: "canonical lookup with github connection",
424-
input: "github|456789123",
425-
expectedCanonical: true,
426-
expectedSub: "github|456789123",
427-
expectedUserID: "github|456789123",
428-
expectedUsername: "",
429-
},
430-
{
431-
name: "canonical lookup with samlp enterprise",
432-
input: "samlp|enterprise|user123",
433-
expectedCanonical: true,
434-
expectedSub: "samlp|enterprise|user123",
435-
expectedUserID: "samlp|enterprise|user123",
436-
expectedUsername: "",
437-
},
438-
{
439-
name: "canonical lookup with linkedin",
440-
input: "linkedin|789123456",
441-
expectedCanonical: true,
442-
expectedSub: "linkedin|789123456",
443-
expectedUserID: "linkedin|789123456",
444-
expectedUsername: "",
445-
},
446-
{
447-
name: "search lookup with simple username",
448-
input: "john.doe",
449-
expectedCanonical: false,
450-
expectedSub: "",
451-
expectedUserID: "",
452-
expectedUsername: "john.doe",
453-
},
454-
{
455-
name: "search lookup with complex username",
456-
input: "jane.smith123",
457-
expectedCanonical: false,
458-
expectedSub: "",
459-
expectedUserID: "",
460-
expectedUsername: "jane.smith123",
461-
},
462-
{
463-
name: "search lookup with developer username",
464-
input: "developer123",
465-
expectedCanonical: false,
466-
expectedSub: "",
467-
expectedUserID: "",
468-
expectedUsername: "developer123",
469-
},
470-
{
471-
name: "canonical lookup with spaces (trimmed)",
472-
input: " auth0|123456789 ",
473-
expectedCanonical: true,
474-
expectedSub: "auth0|123456789",
475-
expectedUserID: "auth0|123456789",
476-
expectedUsername: "",
477-
},
478-
{
479-
name: "search lookup with spaces (trimmed)",
480-
input: " john.doe ",
481-
expectedCanonical: false,
482-
expectedSub: "",
483-
expectedUserID: "",
484-
expectedUsername: "john.doe",
485-
},
486-
{
487-
name: "canonical lookup with pipe at start",
488-
input: "|startpipe",
489-
expectedCanonical: true,
490-
expectedSub: "|startpipe",
491-
expectedUserID: "|startpipe",
492-
expectedUsername: "",
493-
},
494-
{
495-
name: "canonical lookup with pipe at end",
496-
input: "endpipe|",
497-
expectedCanonical: true,
498-
expectedSub: "endpipe|",
499-
expectedUserID: "endpipe|",
500-
expectedUsername: "",
501-
},
502-
{
503-
name: "empty input (search strategy)",
504-
input: "",
505-
expectedCanonical: false,
506-
expectedSub: "",
507-
expectedUserID: "",
508-
expectedUsername: "",
509-
},
510-
}
511-
512-
for _, tt := range tests {
513-
t.Run(tt.name, func(t *testing.T) {
514-
user := &User{}
515-
result := user.PrepareForMetadataLookup(tt.input)
516-
517-
// Check strategy result
518-
if result != tt.expectedCanonical {
519-
t.Errorf("PrepareForMetadataLookup() returned %v, expected %v", result, tt.expectedCanonical)
520-
}
521-
522-
// Check Sub field
523-
if user.Sub != tt.expectedSub {
524-
t.Errorf("Sub = %q, expected %q", user.Sub, tt.expectedSub)
525-
}
526-
527-
// Check UserID field
528-
if user.UserID != tt.expectedUserID {
529-
t.Errorf("UserID = %q, expected %q", user.UserID, tt.expectedUserID)
530-
}
531-
532-
// Check Username field
533-
if user.Username != tt.expectedUsername {
534-
t.Errorf("Username = %q, expected %q", user.Username, tt.expectedUsername)
535-
}
536-
})
537-
}
538-
}
539-
540-
func TestUser_PrepareForMetadataLookup_Idempotency(t *testing.T) {
541-
// Test that calling PrepareForMetadataLookup multiple times with the same input
542-
// produces the same result
543-
user := &User{}
544-
545-
// First call
546-
result1 := user.PrepareForMetadataLookup("auth0|123456789")
547-
sub1, userID1, username1 := user.Sub, user.UserID, user.Username
548-
549-
// Second call with same input
550-
result2 := user.PrepareForMetadataLookup("auth0|123456789")
551-
sub2, userID2, username2 := user.Sub, user.UserID, user.Username
552-
553-
// Results should be identical
554-
if result1 != result2 {
555-
t.Errorf("PrepareForMetadataLookup() not idempotent: first=%v, second=%v", result1, result2)
556-
}
557-
if sub1 != sub2 {
558-
t.Errorf("Sub not idempotent: first=%q, second=%q", sub1, sub2)
559-
}
560-
if userID1 != userID2 {
561-
t.Errorf("UserID not idempotent: first=%q, second=%q", userID1, userID2)
562-
}
563-
if username1 != username2 {
564-
t.Errorf("Username not idempotent: first=%q, second=%q", username1, username2)
565-
}
566-
}
567-
568-
func TestUser_PrepareForMetadataLookup_StrategySwitch(t *testing.T) {
569-
// Test that switching between strategies properly clears the other fields
570-
user := &User{}
571-
572-
// Start with canonical lookup
573-
canonical := user.PrepareForMetadataLookup("auth0|123456789")
574-
if !canonical {
575-
t.Fatal("Expected canonical strategy")
576-
}
577-
if user.Sub == "" || user.UserID == "" || user.Username != "" {
578-
t.Errorf("Canonical strategy fields not set correctly: Sub=%q, UserID=%q, Username=%q",
579-
user.Sub, user.UserID, user.Username)
580-
}
581-
582-
// Switch to search lookup
583-
search := user.PrepareForMetadataLookup("john.doe")
584-
if search {
585-
t.Fatal("Expected search strategy")
586-
}
587-
if user.Sub != "" || user.UserID != "" || user.Username == "" {
588-
t.Errorf("Search strategy fields not set correctly: Sub=%q, UserID=%q, Username=%q",
589-
user.Sub, user.UserID, user.Username)
590-
}
591-
}
592-
593-
func TestUser_PrepareForMetadataLookup_PreservesOtherFields(t *testing.T) {
594-
// Test that PrepareForMetadataLookup doesn't modify other user fields
595-
user := &User{
596-
Token: "test-token",
597-
PrimaryEmail: "test@example.com",
598-
UserMetadata: &UserMetadata{
599-
Name: converters.StringPtr("Test User"),
600-
},
601-
}
602-
603-
originalToken := user.Token
604-
originalEmail := user.PrimaryEmail
605-
originalMetadata := user.UserMetadata
606-
607-
user.PrepareForMetadataLookup("auth0|123456789")
608-
609-
if user.Token != originalToken {
610-
t.Errorf("Token was modified: got %q, expected %q", user.Token, originalToken)
611-
}
612-
if user.PrimaryEmail != originalEmail {
613-
t.Errorf("PrimaryEmail was modified: got %q, expected %q", user.PrimaryEmail, originalEmail)
614-
}
615-
if user.UserMetadata != originalMetadata {
616-
t.Errorf("UserMetadata was modified")
617-
}
618-
}
619-
620397
func TestUser_buildIndexKey(t *testing.T) {
621398
tests := []struct {
622399
name string

internal/domain/port/user.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ type UserReaderWriter interface {
1919
type UserReader interface {
2020
GetUser(ctx context.Context, user *model.User) (*model.User, error)
2121
SearchUser(ctx context.Context, user *model.User, criteria string) (*model.User, error)
22+
MetadataLookup(ctx context.Context, input string, user *model.User) bool
2223
}
2324

2425
// UserWriter defines the behavior of the user writer
Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
11
// Copyright The Linux Foundation and each contributor to LFX.
22
// SPDX-License-Identifier: MIT
33

4-
package model
4+
package auth0
5+
6+
import "github.com/linuxfoundation/lfx-v2-auth-service/internal/domain/model"
57

68
// Auth0User represents a user in Auth0
79
type Auth0User struct {
@@ -45,10 +47,10 @@ type Auth0UserMetadata struct {
4547
}
4648

4749
// ToUser converts an Auth0User to a User
48-
func (u *Auth0User) ToUser() *User {
49-
var meta *UserMetadata
50+
func (u *Auth0User) ToUser() *model.User {
51+
var meta *model.UserMetadata
5052
if u.UserMetadata != nil {
51-
meta = &UserMetadata{
53+
meta = &model.UserMetadata{
5254
Name: u.UserMetadata.Name,
5355
FamilyName: u.UserMetadata.FamilyName,
5456
GivenName: u.UserMetadata.GivenName,
@@ -65,7 +67,7 @@ func (u *Auth0User) ToUser() *User {
6567
Zoneinfo: u.UserMetadata.Zoneinfo,
6668
}
6769
}
68-
return &User{
70+
return &model.User{
6971
UserID: u.UserID,
7072
Username: u.Username,
7173
PrimaryEmail: u.Email,

internal/infrastructure/auth0/user.go

Lines changed: 21 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -174,7 +174,7 @@ func (u *userReaderWriter) SearchUser(ctx context.Context, user *model.User, cri
174174
httpclient.WithDescription("search user"),
175175
)
176176

177-
var users []model.Auth0User
177+
var users []Auth0User
178178

179179
statusCode, errCall := apiRequest.Call(ctx, &users)
180180
if errCall != nil {
@@ -264,7 +264,7 @@ func (u *userReaderWriter) GetUser(ctx context.Context, user *model.User) (*mode
264264
)
265265

266266
// Parse the response to update the user object
267-
var auth0User *model.Auth0User
267+
var auth0User *Auth0User
268268
statusCode, errCall := apiRequest.Call(ctx, &auth0User)
269269
if errCall != nil {
270270
slog.ErrorContext(ctx, "failed to get user from Auth0",
@@ -291,6 +291,25 @@ func (u *userReaderWriter) GetUser(ctx context.Context, user *model.User) (*mode
291291
return auth0User.ToUser(), nil
292292
}
293293

294+
// MetadataLookup prepares the user for metadata lookup based on the input
295+
// Returns true if should use canonical lookup, false if should use search
296+
func (u *userReaderWriter) MetadataLookup(ctx context.Context, input string, user *model.User) bool {
297+
input = strings.TrimSpace(input)
298+
299+
if strings.Contains(input, "|") {
300+
// Input contains "|", use as sub for canonical lookup
301+
user.Sub = input
302+
user.UserID = input // Auth0 uses user_id for the canonical lookup
303+
user.Username = "" // Clear username field
304+
return true
305+
}
306+
// Input doesn't contain "|", use for search query
307+
user.Sub = "" // Clear sub field
308+
user.UserID = "" // Clear user_id field
309+
user.Username = input
310+
return false
311+
}
312+
294313
func (u *userReaderWriter) UpdateUser(ctx context.Context, user *model.User) (*model.User, error) {
295314

296315
if err := u.jwtVerify(ctx, user); err != nil {

0 commit comments

Comments
 (0)