Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
52 commits
Select commit Hold shift + click to select a range
ce9685a
db: add Disabled property to UserRow struct
shiv-tyagi Feb 7, 2025
200da8b
users: add method to check if a user is disabled
shiv-tyagi Feb 7, 2025
1d2bdfa
pam: check if the user is disabled before creating session
shiv-tyagi Feb 7, 2025
dd99918
pam: add test case Error_when_user_is_disabled while selecting broker
shiv-tyagi Feb 15, 2025
aa56268
internal/users: implement logic to enable/disable user
shiv-tyagi Feb 18, 2025
a64fbb7
user service: add API methods to enable/disable user
shiv-tyagi Feb 18, 2025
3ae47bc
user service: add tests for DisableUser/EnableUser API methods
shiv-tyagi Feb 19, 2025
0b81790
create authctl cli tool
shiv-tyagi May 24, 2025
b147ffb
Make authctl print usage message when called without subcommand
adombeck May 20, 2025
b19feaa
authctl: Improve order of commands in usage message
adombeck May 20, 2025
28f2a40
Rename DisableUser -> LockUser, EnableUser -> UnlockUser
adombeck May 27, 2025
e2a04ed
Avoid allUsers() in migration to lowercase names
adombeck Jun 13, 2025
7f3c33b
Create databases for migration tests from SQLite dumps
adombeck Jun 13, 2025
f40d5d8
Add migration to add column 'locked' to users table
adombeck Jun 13, 2025
ebd8c06
Test migration to add 'locked' column to users table
adombeck Jun 13, 2025
95fa68c
Update schema version in golden files
adombeck Jun 13, 2025
541d781
Divide testdata for migrations into subdirectories
adombeck Jun 13, 2025
bcb57d2
Add authctl completion scripts for bash, zsh, fish
adombeck Jun 16, 2025
a662aeb
debian/install: Install shell completion scripts
adombeck Jun 16, 2025
0f710f8
authctl: Hide the completion command from the usage message
adombeck Jun 16, 2025
28800a2
Specify required arguments in usage message
adombeck Jun 17, 2025
2bd59cb
Fix "Locking user" message
adombeck Jun 17, 2025
8800966
authctl: Improve error messages printed for gRPC errors
adombeck Jun 17, 2025
4472c19
authctl: Exit with the gRPC error code as exit code
adombeck Jun 17, 2025
73e45c8
authctl: Avoid printing errors twice
adombeck Jun 17, 2025
275812d
authctl: Avoid printing usage message on error
adombeck Jun 17, 2025
08ed971
authctl: Simplify short usage string
adombeck Jun 17, 2025
19542a3
authctl: Improve long description
adombeck Jun 17, 2025
c36380f
Fix authctl exiting with 0 when called with unknown command
adombeck Jun 17, 2025
e526eec
Return gRPC errors in API methods
adombeck Jun 17, 2025
39734fd
authctl: Add tests for root command
adombeck Jun 20, 2025
14c9ab3
authctl: Add integration test for `authctl user`
adombeck Jun 20, 2025
1fd692c
refactor: Rename RunDaemon -> StartDaemon
adombeck Jun 20, 2025
681d9fb
Improve error message
adombeck Jun 23, 2025
0358df8
Add debug logs to testutils
adombeck Jun 23, 2025
1ab59c8
authctl: Add integration test for `authctl user lock`
adombeck Jun 23, 2025
4318e1c
Improve error messages
adombeck Jun 17, 2025
62280a1
refactor: Inline extra args in BuildDaemon()
adombeck Jun 20, 2025
90d6a71
refactor: Add WithCurrentUserAsRoot option for testutils.StartDaemon()
adombeck Jun 23, 2025
7ec389a
refactor: Register cleanup of daemon process as part of StartDaemon()
adombeck Jun 23, 2025
fb1f746
authctl: Print no output on success
adombeck Jun 23, 2025
aba2ba6
Improve error message
adombeck Jun 23, 2025
87f6a83
Further improve error message
adombeck Jun 23, 2025
b17cfff
Further improve error message
adombeck Jun 23, 2025
f642c13
pam/integration-tests/ssh: Add a simple lock/unlock test via SSH
3v1n0 Jul 7, 2025
e602f16
Don't leak to unauthenticated users whether a user account is locked
adombeck Jul 8, 2025
f57b189
refactor: Extract GoBuildFlags()
adombeck Jul 8, 2025
7f44cbf
Pass GoBuildFlags to authctl built in tests
adombeck Jul 8, 2025
778f1ac
refactor: Use WithCurrentUserAsRoot option for testutils.runAuthd()
adombeck Jul 11, 2025
5d2a434
Use lowercase username in UpdateLockedFieldForUser
adombeck Aug 22, 2025
5548b4f
Prefix socket URI with "unix://" if no scheme is set.
adombeck Aug 22, 2025
b07bbde
Ensure that we exit with a status code smaller than 256.
adombeck Aug 22, 2025
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
65 changes: 65 additions & 0 deletions cmd/authctl/main.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
// Package main implements Cobra commands for management operations on authd.
package main

import (
"fmt"
"os"

"github.com/spf13/cobra"
"github.com/ubuntu/authd/cmd/authctl/user"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/status"
)

var rootCmd = &cobra.Command{
Use: "authctl",
Short: "CLI tool to interact with authd",
Long: "authctl is a command-line tool to interact with the authd service for user and group management.",
PersistentPreRun: func(cmd *cobra.Command, args []string) {
// The command was successfully parsed, so we don't want cobra to print usage information on error.
cmd.SilenceUsage = true
},
CompletionOptions: cobra.CompletionOptions{
HiddenDefaultCmd: true,
},
// We handle errors ourselves
SilenceErrors: true,
Args: cobra.NoArgs,
RunE: func(cmd *cobra.Command, args []string) error { return cmd.Usage() },
}

func init() {
// Disable command sorting by name. This makes cobra print the commands in the
// order they are added to the root command and adds the `help` and `completion`
// commands at the end.
cobra.EnableCommandSorting = false

rootCmd.AddCommand(user.UserCmd)
}

func main() {
if err := rootCmd.Execute(); err != nil {
s, ok := status.FromError(err)
if !ok {
// If the error is not a gRPC status, we print it as is.
fmt.Fprintln(os.Stderr, err.Error())
os.Exit(1)
}

// If the error is a gRPC status, we print the message and exit with the gRPC status code.
switch s.Code() {
case codes.PermissionDenied:
fmt.Fprintln(os.Stderr, "Permission denied:", s.Message())
default:
fmt.Fprintln(os.Stderr, "Error:", s.Message())
}
code := int(s.Code())
if code < 0 || code > 255 {
// We cannot exit with a negative code or a code greater than 255,
// so we map it to 1 in that case.
code = 1
}

os.Exit(code)
}
}
68 changes: 68 additions & 0 deletions cmd/authctl/main_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
package main_test

import (
"fmt"
"os"
"os/exec"
"testing"

"github.com/ubuntu/authd/internal/testutils"
"github.com/ubuntu/authd/internal/testutils/golden"
)

var authctlPath string

func TestRootCommand(t *testing.T) {
t.Parallel()

tests := map[string]struct {
args []string
expectedExitCode int
}{
"Usage_message_when_no_args": {expectedExitCode: 0},
"Help_command": {args: []string{"help"}, expectedExitCode: 0},
"Help_flag": {args: []string{"--help"}, expectedExitCode: 0},
"Completion_command": {args: []string{"completion"}, expectedExitCode: 0},

"Error_on_invalid_command": {args: []string{"invalid-command"}, expectedExitCode: 1},
"Error_on_invalid_flag": {args: []string{"--invalid-flag"}, expectedExitCode: 1},
}

for name, tc := range tests {
t.Run(name, func(t *testing.T) {
t.Parallel()

//nolint:gosec // G204 it's safe to use exec.Command with a variable here
cmd := exec.Command(authctlPath, tc.args...)
t.Logf("Running command: %s", cmd.String())
outputBytes, err := cmd.CombinedOutput()
output := string(outputBytes)
exitCode := cmd.ProcessState.ExitCode()

if tc.expectedExitCode == 0 && err != nil {
t.Logf("Command output:\n%s", output)
t.Errorf("Expected no error, but got: %v", err)
}

if exitCode != tc.expectedExitCode {
t.Logf("Command output:\n%s", output)
t.Errorf("Expected exit code %d, got %d", tc.expectedExitCode, exitCode)
}

golden.CheckOrUpdate(t, output)
})
}
}

func TestMain(m *testing.M) {
var cleanup func()
var err error
authctlPath, cleanup, err = testutils.BuildAuthctl()
if err != nil {
fmt.Fprintf(os.Stderr, "Setup: %v\n", err)
os.Exit(1)
}
defer cleanup()

m.Run()
}
16 changes: 16 additions & 0 deletions cmd/authctl/testdata/golden/TestRootCommand/Completion_command
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
Generate the autocompletion script for authctl for the specified shell.
See each sub-command's help for details on how to use the generated script.

Usage:
authctl completion [command]

Available Commands:
bash Generate the autocompletion script for bash
zsh Generate the autocompletion script for zsh
fish Generate the autocompletion script for fish
powershell Generate the autocompletion script for powershell

Flags:
-h, --help help for completion

Use "authctl completion [command] --help" for more information about a command.
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
Usage:
authctl [flags]
authctl [command]

Available Commands:
user Commands related to users
help Help about any command

Flags:
-h, --help help for authctl

Use "authctl [command] --help" for more information about a command.

unknown command "invalid-command" for "authctl"
14 changes: 14 additions & 0 deletions cmd/authctl/testdata/golden/TestRootCommand/Error_on_invalid_flag
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
Usage:
authctl [flags]
authctl [command]

Available Commands:
user Commands related to users
help Help about any command

Flags:
-h, --help help for authctl

Use "authctl [command] --help" for more information about a command.

unknown flag: --invalid-flag
14 changes: 14 additions & 0 deletions cmd/authctl/testdata/golden/TestRootCommand/Help_command
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
authctl is a command-line tool to interact with the authd service for user and group management.

Usage:
authctl [flags]
authctl [command]

Available Commands:
user Commands related to users
help Help about any command

Flags:
-h, --help help for authctl

Use "authctl [command] --help" for more information about a command.
14 changes: 14 additions & 0 deletions cmd/authctl/testdata/golden/TestRootCommand/Help_flag
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
authctl is a command-line tool to interact with the authd service for user and group management.

Usage:
authctl [flags]
authctl [command]

Available Commands:
user Commands related to users
help Help about any command

Flags:
-h, --help help for authctl

Use "authctl [command] --help" for more information about a command.
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
Usage:
authctl [flags]
authctl [command]

Available Commands:
user Commands related to users
help Help about any command

Flags:
-h, --help help for authctl

Use "authctl [command] --help" for more information about a command.
28 changes: 28 additions & 0 deletions cmd/authctl/user/lock.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
package user

import (
"context"

"github.com/spf13/cobra"
"github.com/ubuntu/authd/internal/proto/authd"
)

// lockCmd is a command to lock (disable) a user.
var lockCmd = &cobra.Command{
Use: "lock <user>",
Short: "Lock (disable) a user managed by authd",
Args: cobra.ExactArgs(1),
RunE: func(cmd *cobra.Command, args []string) error {
client, err := NewUserServiceClient()
if err != nil {
return err
}

_, err = client.LockUser(context.Background(), &authd.LockUserRequest{Name: args[0]})
if err != nil {
return err
}

return nil
},
}
17 changes: 17 additions & 0 deletions cmd/authctl/user/testdata/db/one_user_and_group.db.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
users:
- name: user1
uid: 1111
gid: 11111
gecos: |-
User1 gecos
On multiple lines
dir: /home/user1
shell: /bin/bash
broker_id: broker-id
groups:
- name: group1
gid: 11111
ugid: "12345678"
users_to_groups:
- uid: 1111
gid: 11111
Empty file.
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
Usage:
authctl user [flags]
authctl user [command]

Available Commands:
lock Lock (disable) a user managed by authd
unlock Unlock (enable) a user managed by authd

Flags:
-h, --help help for user

Use "authctl user [command] --help" for more information about a command.

unknown command "invalid-command" for "authctl user"
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
Usage:
authctl user [flags]
authctl user [command]

Available Commands:
lock Lock (disable) a user managed by authd
unlock Unlock (enable) a user managed by authd

Flags:
-h, --help help for user

Use "authctl user [command] --help" for more information about a command.

unknown flag: --invalid-flag
14 changes: 14 additions & 0 deletions cmd/authctl/user/testdata/golden/TestUserCommand/Help_flag
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
Commands related to users

Usage:
authctl user [flags]
authctl user [command]

Available Commands:
lock Lock (disable) a user managed by authd
unlock Unlock (enable) a user managed by authd

Flags:
-h, --help help for user

Use "authctl user [command] --help" for more information about a command.
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
Usage:
authctl user [flags]
authctl user [command]

Available Commands:
lock Lock (disable) a user managed by authd
unlock Unlock (enable) a user managed by authd

Flags:
-h, --help help for user

Use "authctl user [command] --help" for more information about a command.
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Error: user "invaliduser" not found
Empty file.
28 changes: 28 additions & 0 deletions cmd/authctl/user/unlock.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
package user

import (
"context"

"github.com/spf13/cobra"
"github.com/ubuntu/authd/internal/proto/authd"
)

// unlockCmd is a command to unlock (enable) a user.
var unlockCmd = &cobra.Command{
Use: "unlock <user>",
Short: "Unlock (enable) a user managed by authd",
Args: cobra.ExactArgs(1),
RunE: func(cmd *cobra.Command, args []string) error {
client, err := NewUserServiceClient()
if err != nil {
return err
}

_, err = client.UnlockUser(context.Background(), &authd.UnlockUserRequest{Name: args[0]})
if err != nil {
return err
}

return nil
},
}
Loading
Loading