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

Add functionality to Enable/Disable users #782

Open
wants to merge 8 commits into
base: main
Choose a base branch
from

Conversation

shiv-tyagi
Copy link
Contributor

@shiv-tyagi shiv-tyagi commented Feb 7, 2025

As discussed in #640, this adds a property to the UserDB to mark a user enable/disabled. Before creating an authentication session in pam, we check if the user exists in the cache and is disabled.

This also adds the initial implementation of the authctl tool which can be used to enable/disable a user.

@shiv-tyagi shiv-tyagi requested a review from a team as a code owner February 7, 2025 16:14
@shiv-tyagi
Copy link
Contributor Author

@adombeck Does this look good?

I see that the tests are failing on main branch as well. So I am hoping that it is not me.

@codecov-commenter
Copy link

codecov-commenter commented Feb 7, 2025

Codecov Report

Attention: Patch coverage is 86.15385% with 9 lines in your changes missing coverage. Please review.

Project coverage is 79.08%. Comparing base (36511cd) to head (5152313).
Report is 449 commits behind head on main.

Files with missing lines Patch % Lines
internal/users/db/update.go 57.14% 4 Missing and 2 partials ⚠️
internal/services/pam/pam.go 57.14% 2 Missing and 1 partial ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main     #782      +/-   ##
==========================================
- Coverage   83.43%   79.08%   -4.36%     
==========================================
  Files          83      102      +19     
  Lines        8689    10442    +1753     
  Branches       74       74              
==========================================
+ Hits         7250     8258    +1008     
- Misses       1111     1715     +604     
- Partials      328      469     +141     

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

@shiv-tyagi shiv-tyagi changed the title Check is a user is disabled before logging in Check if a user is disabled before logging in Feb 10, 2025
@@ -138,6 +138,12 @@ func (s Service) SelectBroker(ctx context.Context, req *authd.SBRequest) (resp *
lang = "C"
}

// Throw an error if the user trying to authenticate already exists in cache and is disabled
Copy link
Contributor

Choose a reason for hiding this comment

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

We use the term "database" instead of "cache" now (related: #775)

Suggested change
// Throw an error if the user trying to authenticate already exists in cache and is disabled
// Throw an error if the user trying to authenticate already exists in the database and is disabled

Copy link
Contributor Author

Choose a reason for hiding this comment

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

changed.

@adombeck
Copy link
Contributor

Hi @shiv-tyagi, yes this looks good, thanks! One small thing: I would prefer to have a Disabled field instead of Enabled, so that the default value (false) means that the user is enabled, even when we're not using NewUserDB to instantiate the UserDB struct (like we do here).

@adombeck
Copy link
Contributor

There should also be a new test case "Error_when_user_is_disabled" in https://github.com/shiv-tyagi/authd/blob/fed39218cfcce6c23e80e0d02fe2a2d748d3d912/internal/brokers/manager_test.go#L187-L190.

@adombeck
Copy link
Contributor

Oh and I don't think it makes sense to merge this before we have code which actually causes a user to be disabled (i.e. the command-line tool). If you still plan to work on that soon, feel free to repurpose this PR. Otherwise, I'll start working on that soon and would then cherry-pick your commits to a new branch.

@shiv-tyagi
Copy link
Contributor Author

I would prefer to have a Disabled field instead of Enabled, so that the default value (false) means that the user is enabled, even when we're not using NewUserDB to instantiate the UserDB struct (like we do here).

Sure. I will do that.

There should also be a new test case "Error_when_user_is_disabled"

Noted.

If you still plan to work on that soon, feel free to repurpose this PR.

Yes, I really intend to work on that. I will push my work soon :)

@@ -328,6 +328,16 @@ func (m *Manager) UpdateBrokerForUser(username, brokerID string) error {
return nil
}

// IsUserDisabled returns true if the user with the given user name is disabled, false otherwise
func (m *Manager) IsUserDisabled(username string) (bool, error) {
usr, err := m.cache.UserByName(username)
Copy link
Collaborator

Choose a reason for hiding this comment

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

Suggested change
usr, err := m.cache.UserByName(username)
u, err := m.cache.UserByName(username)

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Fixed. Thanks.

@shiv-tyagi shiv-tyagi force-pushed the user-disable branch 4 times, most recently from 7183092 to 5e81b35 Compare February 15, 2025 18:22
Comment on lines 142 to 149
userIsDisabled, err := s.userManager.IsUserDisabled(username)
if err == nil && userIsDisabled {
return nil, status.Error(codes.PermissionDenied, fmt.Sprintf("user %s is disabled", username))
}

Copy link
Contributor

Choose a reason for hiding this comment

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

Let's handle unexpected errors here

Suggested change
userIsDisabled, err := s.userManager.IsUserDisabled(username)
if err == nil && userIsDisabled {
return nil, status.Error(codes.PermissionDenied, fmt.Sprintf("user %s is disabled", username))
}
userIsDisabled, err := s.userManager.IsUserDisabled(username)
if err != nil && !errors.Is(err, users.NoDataFoundError{}) {
return nil, fmt.Errorf("could not check if user %q is disabled: %w", username, err)
}
if err == nil && userIsDisabled {
return nil, status.Error(codes.PermissionDenied, fmt.Sprintf("user %s is disabled", username))
}

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Thanks for suggesting. I have incorporated this change.

@adombeck
Copy link
Contributor

I rebased on main and resolved the conflicts caused by #779.

@shiv-tyagi
Copy link
Contributor Author

I rebased on main and resolved the conflicts caused by #779.

Thanks for this @adombeck. I had some more work which I had not pushed yet. I would need to fix that as well. Planning to push that work soon.

@shiv-tyagi shiv-tyagi force-pushed the user-disable branch 2 times, most recently from 7f1338f to fce5e88 Compare February 19, 2025 06:51
@shiv-tyagi
Copy link
Contributor Author

I have finished the part till extending the GRPC API with new methods and adding unit tests for those. Now only the part of creating the CLI tool to call those methods remains.

@shiv-tyagi shiv-tyagi changed the title Check if a user is disabled before logging in Add functionality to Enable/Disable users Feb 26, 2025
@shiv-tyagi
Copy link
Contributor Author

@adombeck I have added the initial implementation of the authctl tool. Please check if it looks good and provide any suggestions for me to proceed. This is my first time writing Go code, so I apologise if I made any rookie mistakes. I’ll be happy to fix them.

Thanks in advance.

Copy link
Contributor

@adombeck adombeck left a comment

Choose a reason for hiding this comment

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

I've got a few comments but the overall code style is good! Thanks a lot, @shiv-tyagi!

wantErr bool
wantErrType error
}{
"Successfully_update_broker_for_user": {},
Copy link
Contributor

Choose a reason for hiding this comment

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

This test case is not named correctly

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Sorry that was a copy and paste error. I should have been more careful. I have fixed this.

wantErr bool
wantErrType error
}{
"Successfully_update_broker_for_user": {},
Copy link
Contributor

Choose a reason for hiding this comment

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

Same here, please update the test case name

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Fixed this.

Comment on lines +343 to +345
// We don't care about the output of gpasswd in this test, but we still need to mock it.
_ = localgroupstestutils.SetupGPasswdMock(t, "empty.group")
Copy link
Contributor

Choose a reason for hiding this comment

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

I don't understand why we would need to mock gpasswd in this test, but I also don't see why that's done in many of the other tests in this file which are unrelated to /etc/group. I see that was introduced in fd10538, do you remember the reason, @denisonbarbosa?

wantErr bool
wantErrType error
}{
"Successfully_update_broker_for_user": {},
Copy link
Contributor

Choose a reason for hiding this comment

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

There should be a test case which uses testdata with a disabled user. Currently, it only tests enabling a user which was already enabled before.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I think I am already doing that. Please see this.

Comment on lines 141 to 142
rpc DisablePasswd(DisablePasswdRequest) returns (Empty);
rpc EnablePasswd(EnablePasswdRequest) returns (Empty);
Copy link
Contributor

Choose a reason for hiding this comment

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

The GetPasswd... methods are named like that because they return an entry like the ones in the /etc/passwd file. This analogy doesn't really apply to the disable/enable functionality, so let's name these methods DisableUser and EnableUser.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Done.


func main() {
if err := rootCmd.Execute(); err != nil {
fmt.Fprintf(os.Stderr, "Error while executing authctl '%s'\n", err)
Copy link
Contributor

Choose a reason for hiding this comment

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

"Error while executing authctl" doesn't provide any useful info. Let's just print the error.

Suggested change
fmt.Fprintf(os.Stderr, "Error while executing authctl '%s'\n", err)
fmt.Fprintln(os.Stderr, err.Error())

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Done.


var DisableCmd = &cobra.Command{
Use: "disable",
Short: "Disable a user to log in",
Copy link
Contributor

Choose a reason for hiding this comment

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

"Disable a user to log in" is not a correct sentence. Also, I would like to convey that this can only disable users managed by authd (which is also an opportunity to at least implicitly tell users that the user accounts created by authd are managed separately from other user accounts on the system), so how about:

Suggested change
Short: "Disable a user to log in",
Short: "Disable a user managed by authd",

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Sounds better. Fixed that.

}

client := authd.NewNSSClient(conn)
_, err = client.EnablePasswd(context.Background(), &authd.EnablePasswdRequest{Name: args[0]})
Copy link
Contributor

Choose a reason for hiding this comment

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

That's the wrong API method, you want to call DisablePasswd.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Oops. Sorry. I have fixed that.

Comment on lines 20 to 24
conn, err := grpc.NewClient("unix://"+consts.DefaultSocketPath, grpc.WithTransportCredentials(insecure.NewCredentials()))

if err != nil {
return err
}
Copy link
Contributor

Choose a reason for hiding this comment

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

nitpick: no newline between function call and error handling. please also change that in the other occurrences in cmd/authctl/user/disable.go and cmd/authctl/user/enable.go.

Suggested change
conn, err := grpc.NewClient("unix://"+consts.DefaultSocketPath, grpc.WithTransportCredentials(insecure.NewCredentials()))
if err != nil {
return err
}
conn, err := grpc.NewClient("unix://"+consts.DefaultSocketPath, grpc.WithTransportCredentials(insecure.NewCredentials()))
if err != nil {
return err
}

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Fixed this.


var UserCmd = &cobra.Command{
Use: "user",
Short: "Commands retaled to working with users",
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
Short: "Commands retaled to working with users",
Short: "Commands related to users",

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Done. Thanks.

@shiv-tyagi
Copy link
Contributor Author

Thank you so much @adombeck for reviewing this. I have addressed all your suggestions. What next? How should I test it locally?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants