Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
55 commits
Select commit Hold shift + click to select a range
667f925
db: add Disabled property to UserRow struct
shiv-tyagi Feb 7, 2025
03edd77
users: add method to check if a user is disabled
shiv-tyagi Feb 7, 2025
6548587
pam: check if the user is disabled before creating session
shiv-tyagi Feb 7, 2025
1eaee8f
pam: add test case Error_when_user_is_disabled while selecting broker
shiv-tyagi Feb 15, 2025
5248258
internal/users: implement logic to enable/disable user
shiv-tyagi Feb 18, 2025
d6edb3e
user service: add API methods to enable/disable user
shiv-tyagi Feb 18, 2025
b431a3b
user service: add tests for DisableUser/EnableUser API methods
shiv-tyagi Feb 19, 2025
eece789
create authctl cli tool
shiv-tyagi May 24, 2025
e2fd12b
Make authctl print usage message when called without subcommand
adombeck May 20, 2025
0240ac6
authctl: Improve order of commands in usage message
adombeck May 20, 2025
e41f43c
Rename DisableUser -> LockUser, EnableUser -> UnlockUser
adombeck May 27, 2025
7f92f87
Avoid allUsers() in migration to lowercase names
adombeck Jun 13, 2025
ead952b
Create databases for migration tests from SQLite dumps
adombeck Jun 13, 2025
a9467aa
Add migration to add column 'locked' to users table
adombeck Jun 13, 2025
8a1f7d0
Test migration to add 'locked' column to users table
adombeck Jun 13, 2025
a09d5fc
Update schema version in golden files
adombeck Jun 13, 2025
ae7993c
Divide testdata for migrations into subdirectories
adombeck Jun 13, 2025
a59a229
Add authctl completion scripts for bash, zsh, fish
adombeck Jun 16, 2025
2db6c5b
debian/install: Install shell completion scripts
adombeck Jun 16, 2025
9add730
authctl: Hide the completion command from the usage message
adombeck Jun 16, 2025
1994a9e
Specify required arguments in usage message
adombeck Jun 17, 2025
187cbec
Fix "Locking user" message
adombeck Jun 17, 2025
5d48c3e
authctl: Improve error messages printed for gRPC errors
adombeck Jun 17, 2025
1cd477e
authctl: Exit with the gRPC error code as exit code
adombeck Jun 17, 2025
e8d9204
authctl: Avoid printing errors twice
adombeck Jun 17, 2025
ca70206
authctl: Avoid printing usage message on error
adombeck Jun 17, 2025
d8c9f96
authctl: Simplify short usage string
adombeck Jun 17, 2025
9131dbc
authctl: Improve long description
adombeck Jun 17, 2025
1186336
Fix authctl exiting with 0 when called with unknown command
adombeck Jun 17, 2025
8a3d6ef
Return gRPC errors in API methods
adombeck Jun 17, 2025
ed80687
authctl: Add tests for root command
adombeck Jun 20, 2025
5540089
authctl: Add integration test for `authctl user`
adombeck Jun 20, 2025
8e37541
refactor: Rename RunDaemon -> StartDaemon
adombeck Jun 20, 2025
6f7525b
Improve error message
adombeck Jun 23, 2025
f9a85f2
authctl: Add integration test for `authctl user lock`
adombeck Jun 23, 2025
fcbe525
refactor: Inline extra args in BuildDaemon()
adombeck Jun 20, 2025
0feb388
refactor: Add WithCurrentUserAsRoot option for testutils.StartDaemon()
adombeck Jun 23, 2025
7d7c7d9
refactor: Register cleanup of daemon process as part of StartDaemon()
adombeck Jun 23, 2025
564bdd0
authctl: Print no output on success
adombeck Jun 23, 2025
df03efd
Improve error message
adombeck Jun 23, 2025
3dbded1
Further improve error message
adombeck Jun 23, 2025
14cafa2
Further improve error message
adombeck Jun 23, 2025
e2e558e
pam/integration-tests/ssh: Add a simple lock/unlock test via SSH
3v1n0 Jul 7, 2025
bac0699
Don't leak to unauthenticated users whether a user account is locked
adombeck Jul 8, 2025
ee4e7d4
refactor: Extract GoBuildFlags()
adombeck Jul 8, 2025
a60b890
Pass GoBuildFlags to authctl built in tests
adombeck Jul 8, 2025
588e24d
refactor: Use WithCurrentUserAsRoot option for testutils.runAuthd()
adombeck Jul 11, 2025
dd8d668
Use lowercase username in UpdateLockedFieldForUser
adombeck Aug 22, 2025
dc7365c
Prefix socket URI with "unix://" if no scheme is set.
adombeck Aug 22, 2025
fe8657e
Ensure that we exit with a status code smaller than 256.
adombeck Aug 22, 2025
b13a67b
pam/integration-tests: Support loading SQL dump
adombeck Aug 26, 2025
7388eab
tests: Improve log message
adombeck Sep 2, 2025
0bb732e
tests: Don't require group backup file to exist
adombeck Sep 2, 2025
7201b69
tests: Add Authenticate_user_after_db_migration
adombeck Aug 26, 2025
22bd4f9
tests: Add Authenticate_user_after_db_migration_and_being_locked_and_…
adombeck Sep 2, 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"
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