Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Use lowercase usernames #723

Open
wants to merge 8 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ require (
github.com/ubuntu/decorate v0.0.0-20230606064312-bc4ac83958d6
go.etcd.io/bbolt v1.3.11
golang.org/x/exp v0.0.0-20230905200255-921286631fa9
golang.org/x/mod v0.17.0
golang.org/x/sys v0.29.0
golang.org/x/term v0.28.0
google.golang.org/grpc v1.69.4
Expand Down
2 changes: 2 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -139,6 +139,8 @@ go.uber.org/multierr v1.9.0 h1:7fIwc/ZtS0q++VgcfqFDxSBZVv/Xo49/SYnDFupUwlI=
go.uber.org/multierr v1.9.0/go.mod h1:X2jQV1h+kxSjClGpnseKVIxpmcjrj7MNnI0bnlfKTVQ=
golang.org/x/exp v0.0.0-20230905200255-921286631fa9 h1:GoHiUyI/Tp2nVkLI2mCxVkOjsbSXD66ic0XW0js0R9g=
golang.org/x/exp v0.0.0-20230905200255-921286631fa9/go.mod h1:S2oDrQGGwySpoQPVqRShND87VCbxmc6bL1Yd2oYrm6k=
golang.org/x/mod v0.17.0 h1:zY54UmvipHiNd+pm+m0x9KhZ9hl1/7QNMyxXbc6ICqA=
golang.org/x/mod v0.17.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
golang.org/x/net v0.34.0 h1:Mb7Mrk043xzHgnRM88suvJFwzVrRfHEHJEl5/71CKw0=
golang.org/x/net v0.34.0/go.mod h1:di0qlW3YNM5oh6GqDGQr92MyTozJPmybPK4Ev/Gm31k=
golang.org/x/sync v0.10.0 h1:3NQrjDixjgGwUOCaF8w2+VYHv0Ve/vGYSbdkTa98gmQ=
Expand Down
82 changes: 41 additions & 41 deletions internal/brokers/broker_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -105,13 +105,13 @@ func TestGetAuthenticationModes(t *testing.T) {
"Get authentication modes and generate validators": {sessionID: "success", supportedUILayouts: []string{"required-entry", "optional-entry"}},
"Get authentication modes and generate validator ignoring whitespaces in supported values": {sessionID: "success", supportedUILayouts: []string{"layout-with-spaces"}},
"Get authentication modes and ignores invalid UI layout": {sessionID: "success", supportedUILayouts: []string{"required-entry", "missing-type"}},
"Get multiple authentication modes and generate validators": {sessionID: "GAM_multiple_modes", supportedUILayouts: []string{"required-entry", "optional-entry"}},
"Get multiple authentication modes and generate validators": {sessionID: "gam_multiple_modes", supportedUILayouts: []string{"required-entry", "optional-entry"}},

"Does not error out when no authentication modes are returned": {sessionID: "GAM_empty"},
"Does not error out when no authentication modes are returned": {sessionID: "gam_empty"},

// broker errors
"Error when getting authentication modes": {sessionID: "GAM_error", wantErr: true},
"Error when broker returns invalid modes": {sessionID: "GAM_invalid", wantErr: true},
"Error when getting authentication modes": {sessionID: "gam_error", wantErr: true},
"Error when broker returns invalid modes": {sessionID: "gam_invalid", wantErr: true},
}
for name, tc := range tests {
t.Run(name, func(t *testing.T) {
Expand Down Expand Up @@ -153,23 +153,23 @@ func TestSelectAuthenticationMode(t *testing.T) {

wantErr bool
}{
"Successfully select mode with required value": {sessionID: "SAM_success_required_entry"},
"Successfully select mode with optional value": {sessionID: "SAM_success_optional_entry", supportedUILayouts: []string{"optional-entry"}},
"Successfully select mode with missing optional value": {sessionID: "SAM_missing_optional_entry", supportedUILayouts: []string{"optional-entry"}},
"Successfully select mode with required value": {sessionID: "sam_success_required_entry"},
"Successfully select mode with optional value": {sessionID: "sam_success_optional_entry", supportedUILayouts: []string{"optional-entry"}},
"Successfully select mode with missing optional value": {sessionID: "sam_missing_optional_entry", supportedUILayouts: []string{"optional-entry"}},

// broker errors
"Error when selecting invalid auth mode": {sessionID: "SAM_error", wantErr: true},
"Error when selecting invalid auth mode": {sessionID: "sam_error", wantErr: true},
"Error when no validators were generated for session": {sessionID: "no-validators", wantErr: true},

/* Layout errors */
"Error when returns no layout": {sessionID: "SAM_no_layout", wantErr: true},
"Error when returns empty layout": {sessionID: "SAM_empty_layout", wantErr: true},
"Error when returns layout with no type": {sessionID: "SAM_no_layout_type", wantErr: true},
"Error when returns layout with invalid type": {sessionID: "SAM_invalid_layout_type", wantErr: true},
"Error when returns layout without required value": {sessionID: "SAM_missing_required_entry", wantErr: true},
"Error when returns layout with unknown field": {sessionID: "SAM_unknown_field", wantErr: true},
"Error when returns layout with invalid required value": {sessionID: "SAM_invalid_required_entry", wantErr: true},
"Error when returns layout with invalid optional value": {sessionID: "SAM_invalid_optional_entry", wantErr: true},
"Error when returns no layout": {sessionID: "sam_no_layout", wantErr: true},
"Error when returns empty layout": {sessionID: "sam_empty_layout", wantErr: true},
"Error when returns layout with no type": {sessionID: "sam_no_layout_type", wantErr: true},
"Error when returns layout with invalid type": {sessionID: "sam_invalid_layout_type", wantErr: true},
"Error when returns layout without required value": {sessionID: "sam_missing_required_entry", wantErr: true},
"Error when returns layout with unknown field": {sessionID: "sam_unknown_field", wantErr: true},
"Error when returns layout with invalid required value": {sessionID: "sam_invalid_required_entry", wantErr: true},
"Error when returns layout with invalid optional value": {sessionID: "sam_invalid_optional_entry", wantErr: true},
}
for name, tc := range tests {
t.Run(name, func(t *testing.T) {
Expand Down Expand Up @@ -213,30 +213,30 @@ func TestIsAuthenticated(t *testing.T) {
cancelFirstCall bool
}{
"Successfully authenticate": {sessionID: "success"},
"Successfully authenticate after cancelling first call": {sessionID: "IA_second_call", secondCall: true},
"Denies authentication when broker times out": {sessionID: "IA_timeout"},
"Adds default groups even if broker did not set them": {sessionID: "IA_info_empty_groups"},
"No error when auth.Next and no data": {sessionID: "IA_next"},
"No error when broker returns userinfo with empty gecos": {sessionID: "IA_info_empty_gecos"},
"No error when broker returns userinfo with group with empty UGID": {sessionID: "IA_info_empty_ugid"},
"No error when broker returns userinfo with mismatching username": {sessionID: "IA_info_mismatching_user_name"},
"Successfully authenticate after cancelling first call": {sessionID: "ia_second_call", secondCall: true},
"Denies authentication when broker times out": {sessionID: "ia_timeout"},
"Adds default groups even if broker did not set them": {sessionID: "ia_info_empty_groups"},
"No error when auth.Next and no data": {sessionID: "ia_next"},
"No error when broker returns userinfo with empty gecos": {sessionID: "ia_info_empty_gecos"},
"No error when broker returns userinfo with group with empty UGID": {sessionID: "ia_info_empty_ugid"},
"No error when broker returns userinfo with mismatching username": {sessionID: "ia_info_mismatching_user_name"},

// broker errors
"Error when authenticating": {sessionID: "IA_error"},
"Error on empty data even if granted": {sessionID: "IA_empty_data"},
"Error when broker returns invalid data": {sessionID: "IA_invalid_data"},
"Error when broker returns invalid access": {sessionID: "IA_invalid_access"},
"Error when broker returns invalid userinfo": {sessionID: "IA_invalid_userinfo"},
"Error when broker returns userinfo with empty username": {sessionID: "IA_info_empty_user_name"},
"Error when broker returns userinfo with empty group name": {sessionID: "IA_info_empty_group_name"},
"Error when broker returns userinfo with empty UUID": {sessionID: "IA_info_empty_uuid"},
"Error when broker returns userinfo with invalid homedir": {sessionID: "IA_info_invalid_home"},
"Error when broker returns userinfo with invalid shell": {sessionID: "IA_info_invalid_shell"},
"Error when broker returns data on auth.Next": {sessionID: "IA_next_with_data"},
"Error when broker returns data on auth.Cancelled": {sessionID: "IA_cancelled_with_data"},
"Error when broker returns no data on auth.Denied": {sessionID: "IA_denied_without_data"},
"Error when broker returns no data on auth.Retry": {sessionID: "IA_retry_without_data"},
"Error when calling IsAuthenticated a second time without cancelling": {sessionID: "IA_second_call", secondCall: true, cancelFirstCall: true},
"Error when authenticating": {sessionID: "ia_error"},
"Error on empty data even if granted": {sessionID: "ia_empty_data"},
"Error when broker returns invalid data": {sessionID: "ia_invalid_data"},
"Error when broker returns invalid access": {sessionID: "ia_invalid_access"},
"Error when broker returns invalid userinfo": {sessionID: "ia_invalid_userinfo"},
"Error when broker returns userinfo with empty username": {sessionID: "ia_info_empty_user_name"},
"Error when broker returns userinfo with empty group name": {sessionID: "ia_info_empty_group_name"},
"Error when broker returns userinfo with empty UUID": {sessionID: "ia_info_empty_uuid"},
"Error when broker returns userinfo with invalid homedir": {sessionID: "ia_info_invalid_home"},
"Error when broker returns userinfo with invalid shell": {sessionID: "ia_info_invalid_shell"},
"Error when broker returns data on auth.Next": {sessionID: "ia_next_with_data"},
"Error when broker returns data on auth.Cancelled": {sessionID: "ia_cancelled_with_data"},
"Error when broker returns no data on auth.Denied": {sessionID: "ia_denied_without_data"},
"Error when broker returns no data on auth.Retry": {sessionID: "ia_retry_without_data"},
"Error when calling IsAuthenticated a second time without cancelling": {sessionID: "ia_second_call", secondCall: true, cancelFirstCall: true},
}
for name, tc := range tests {
t.Run(name, func(t *testing.T) {
Expand Down Expand Up @@ -289,8 +289,8 @@ func TestCancelIsAuthenticated(t *testing.T) {

wantAnswer string
}{
"Successfully cancels IsAuthenticated": {sessionID: "IA_wait", wantAnswer: auth.Cancelled},
"Call returns denied if not cancelled": {sessionID: "IA_timeout", wantAnswer: auth.Denied},
"Successfully cancels IsAuthenticated": {sessionID: "ia_wait", wantAnswer: auth.Cancelled},
"Call returns denied if not cancelled": {sessionID: "ia_timeout", wantAnswer: auth.Denied},
}
for name, tc := range tests {
t.Run(name, func(t *testing.T) {
Expand All @@ -305,7 +305,7 @@ func TestCancelIsAuthenticated(t *testing.T) {
}()
defer cancel()

if tc.sessionID == "IA_wait" {
if tc.sessionID == "ia_wait" {
// Give some time for the IsAuthenticated routine to start.
time.Sleep(time.Second)
cancel()
Expand Down
6 changes: 3 additions & 3 deletions internal/brokers/manager_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -185,8 +185,8 @@ func TestNewSession(t *testing.T) {
"Successfully start a new session with the correct broker": {username: "success", configuredBrokers: []string{t.Name() + "_Broker1.conf", t.Name() + "_Broker2.conf"}},

"Error when broker does not exist": {brokerID: "does_not_exist", wantErr: true},
"Error when broker does not provide an ID": {username: "NS_no_id", wantErr: true},
"Error when starting a new session": {username: "NS_error", wantErr: true},
"Error when broker does not provide an ID": {username: "ns_no_id", wantErr: true},
"Error when starting a new session": {username: "ns_error", wantErr: true},
"Error when broker is not available on dbus": {unavailableBroker: true, wantErr: true},
}
for name, tc := range tests {
Expand Down Expand Up @@ -269,7 +269,7 @@ func TestEndSession(t *testing.T) {
"Successfully end session on the correct broker": {sessionID: "success", configuredBrokers: []string{t.Name() + "_Broker1", t.Name() + "_Broker2"}},

"Error when broker does not exist": {brokerID: "does not exist", sessionID: "dont matter", wantErr: true},
"Error when ending session": {sessionID: "ES_error", wantErr: true},
"Error when ending session": {sessionID: "es_error", wantErr: true},
}
for name, tc := range tests {
t.Run(name, func(t *testing.T) {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
FIRST CALL:
access: granted
data: {"Name":"TestIsAuthenticated/Adds_default_groups_even_if_broker_did_not_set_them_separator_IA_info_empty_groups","UID":0,"Gecos":"gecos for IA_info_empty_groups","Dir":"/home/IA_info_empty_groups","Shell":"/bin/sh/IA_info_empty_groups","Groups":[]}
data: {"Name":"TestIsAuthenticated/Adds_default_groups_even_if_broker_did_not_set_them_separator_ia_info_empty_groups","UID":0,"Gecos":"gecos for ia_info_empty_groups","Dir":"/home/ia_info_empty_groups","Shell":"/bin/sh/ia_info_empty_groups","Groups":[]}
err: <nil>
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
FIRST CALL:
access: granted
data: {"Name":"TestIsAuthenticated/Error_when_calling_IsAuthenticated_a_second_time_without_cancelling_separator_IA_second_call","UID":0,"Gecos":"gecos for IA_second_call","Dir":"/home/IA_second_call","Shell":"/bin/sh/IA_second_call","Groups":[{"Name":"group-IA_second_call","GID":null,"UGID":"ugid-IA_second_call"}]}
data: {"Name":"TestIsAuthenticated/Error_when_calling_IsAuthenticated_a_second_time_without_cancelling_separator_ia_second_call","UID":0,"Gecos":"gecos for ia_second_call","Dir":"/home/ia_second_call","Shell":"/bin/sh/ia_second_call","Groups":[{"Name":"group-ia_second_call","GID":null,"UGID":"ugid-ia_second_call"}]}
err: <nil>
SECOND CALL:
access:
data:
err: broker "TestIsAuthenticated": IsAuthenticated already running for session "TestIsAuthenticated/Error_when_calling_IsAuthenticated_a_second_time_without_cancelling_separator_IA_second_call"
err: broker "TestIsAuthenticated": IsAuthenticated already running for session "TestIsAuthenticated/Error_when_calling_IsAuthenticated_a_second_time_without_cancelling_separator_ia_second_call"
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
FIRST CALL:
access: granted
data: {"Name":"TestIsAuthenticated/No_error_when_broker_returns_userinfo_with_empty_gecos_separator_IA_info_empty_gecos","UID":0,"Gecos":"","Dir":"/home/IA_info_empty_gecos","Shell":"/bin/sh/IA_info_empty_gecos","Groups":[{"Name":"group-IA_info_empty_gecos","GID":null,"UGID":"ugid-IA_info_empty_gecos"}]}
data: {"Name":"TestIsAuthenticated/No_error_when_broker_returns_userinfo_with_empty_gecos_separator_ia_info_empty_gecos","UID":0,"Gecos":"","Dir":"/home/ia_info_empty_gecos","Shell":"/bin/sh/ia_info_empty_gecos","Groups":[{"Name":"group-ia_info_empty_gecos","GID":null,"UGID":"ugid-ia_info_empty_gecos"}]}
err: <nil>
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
FIRST CALL:
access: granted
data: {"Name":"TestIsAuthenticated/No_error_when_broker_returns_userinfo_with_group_with_empty_UGID_separator_IA_info_empty_ugid","UID":0,"Gecos":"gecos for IA_info_empty_ugid","Dir":"/home/IA_info_empty_ugid","Shell":"/bin/sh/IA_info_empty_ugid","Groups":[{"Name":"group-IA_info_empty_ugid","GID":null,"UGID":""}]}
data: {"Name":"TestIsAuthenticated/No_error_when_broker_returns_userinfo_with_group_with_empty_UGID_separator_ia_info_empty_ugid","UID":0,"Gecos":"gecos for ia_info_empty_ugid","Dir":"/home/ia_info_empty_ugid","Shell":"/bin/sh/ia_info_empty_ugid","Groups":[{"Name":"group-ia_info_empty_ugid","GID":null,"UGID":""}]}
err: <nil>
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
FIRST CALL:
access: granted
data: {"Name":"different_username","UID":0,"Gecos":"gecos for IA_info_mismatching_user_name","Dir":"/home/IA_info_mismatching_user_name","Shell":"/bin/sh/IA_info_mismatching_user_name","Groups":[{"Name":"group-IA_info_mismatching_user_name","GID":null,"UGID":"ugid-IA_info_mismatching_user_name"}]}
data: {"Name":"different_username","UID":0,"Gecos":"gecos for ia_info_mismatching_user_name","Dir":"/home/ia_info_mismatching_user_name","Shell":"/bin/sh/ia_info_mismatching_user_name","Groups":[{"Name":"group-ia_info_mismatching_user_name","GID":null,"UGID":"ugid-ia_info_mismatching_user_name"}]}
err: <nil>
Original file line number Diff line number Diff line change
Expand Up @@ -4,5 +4,5 @@ FIRST CALL:
err: <nil>
SECOND CALL:
access: granted
data: {"Name":"TestIsAuthenticated/Successfully_authenticate_after_cancelling_first_call_separator_IA_second_call","UID":0,"Gecos":"gecos for IA_second_call","Dir":"/home/IA_second_call","Shell":"/bin/sh/IA_second_call","Groups":[{"Name":"group-IA_second_call","GID":null,"UGID":"ugid-IA_second_call"}]}
data: {"Name":"TestIsAuthenticated/Successfully_authenticate_after_cancelling_first_call_separator_ia_second_call","UID":0,"Gecos":"gecos for ia_second_call","Dir":"/home/ia_second_call","Shell":"/bin/sh/ia_second_call","Groups":[{"Name":"group-ia_second_call","GID":null,"UGID":"ugid-ia_second_call"}]}
err: <nil>
3 changes: 0 additions & 3 deletions internal/consts/consts.go
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

And wrong too... :)

Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,6 @@ var (
)

const (
// TEXTDOMAIN is the gettext domain for l10n.
TEXTDOMAIN = "adsys"

// DefaultLogLevel is the default logging level selected without any option.
DefaultLogLevel = log.WarnLevel

Expand Down
15 changes: 15 additions & 0 deletions internal/semver/semver.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
// Package semver is a wrapper around golang.org/x/mod/semver which prefixes the version string with a "v" before
// calling the underlying functions.
package semver

import "golang.org/x/mod/semver"

// IsValid checks if the version string is a valid semantic version.
func IsValid(v string) bool {
return semver.IsValid("v" + v)
}

// Compare compares two semantic versions.
func Compare(v1, v2 string) int {
return semver.Compare("v"+v1, "v"+v2)
}
2 changes: 2 additions & 0 deletions internal/services/nss/nss.go
Original file line number Diff line number Diff line change
Expand Up @@ -49,12 +49,14 @@ func (s Service) GetPasswdByName(ctx context.Context, req *authd.GetPasswdByName
}

if !errors.Is(err, users.NoDataFoundError{}) || !req.GetShouldPreCheck() {
log.Debugf(context.Background(), "GetPasswdByName: %v", err)
return nil, noDataFoundErrorToGRPCError(err)
}

// If the user is not found in the local cache, we check if it exists in at least one broker.
pwent, err := s.userPreCheck(ctx, req.GetName())
if err != nil {
log.Debugf(context.Background(), "GetPasswdByName: %v", err)
return nil, status.Error(codes.NotFound, err.Error())
}

Expand Down
3 changes: 2 additions & 1 deletion internal/services/nss/nss_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,8 @@ func TestGetPasswdByName(t *testing.T) {
wantErr bool
wantErrNotExists bool
}{
"Return existing user": {username: "user1"},
"Return existing user": {username: "user1"},
"Return existing user with different capitalization": {username: "User1"},

"Precheck user if not in cache": {username: "user-pre-check", shouldPreCheck: true},
"Prechecked user with upper cases in username has same id as lower case": {username: "User-Pre-Check", shouldPreCheck: true},
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
name: user1
passwd: x
uid: 1111
gid: 11111
gecos: |-
User1 gecos
On multiple lines
homedir: /home/user1
shell: /bin/bash
4 changes: 4 additions & 0 deletions internal/services/pam/pam.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import (
"errors"
"fmt"
"os/user"
"strings"

"github.com/ubuntu/authd/internal/brokers"
"github.com/ubuntu/authd/internal/brokers/auth"
Expand Down Expand Up @@ -127,6 +128,9 @@ func (s Service) SelectBroker(ctx context.Context, req *authd.SBRequest) (resp *
brokerID := req.GetBrokerId()
lang := req.GetLang()

// authd usernames are lowercase
username = strings.ToLower(username)

if username == "" {
return nil, status.Error(codes.InvalidArgument, "no user name provided")
}
Expand Down
Loading
Loading