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 device ownership #249

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

Add device ownership #249

wants to merge 11 commits into from

Conversation

nsklikas
Copy link
Contributor

@nsklikas nsklikas commented Dec 10, 2024

IAM-1248

Implement device ownership logic as described in UD084. This PR:

  • Adds the new configuration options
  • Implements the user filtering logic
  • Implements owner autoregistration, if configured

Some things that I am not sure about are:

  • Should the server create the .d configuration dir or should that be created by the snap? If the server should create it, are the permissions correct?
  • What should the permissions for 20-owner-autoregistration.conf be? (right now they are 0666)

@nsklikas nsklikas requested a review from a team as a code owner December 10, 2024 12:05
@nsklikas nsklikas marked this pull request as draft December 10, 2024 12:08
@nsklikas nsklikas force-pushed the main branch 4 times, most recently from 708c09d to 8a2d87d Compare December 10, 2024 13:10
@nsklikas nsklikas marked this pull request as ready for review December 10, 2024 13:19
@adombeck
Copy link
Contributor

Hi @nsklikas! There are some failing tests (https://github.com/ubuntu/authd-oidc-brokers/actions/runs/12256799287/job/34192844158?pr=249) and some linter errors (https://github.com/ubuntu/authd-oidc-brokers/actions/runs/12256799287?pr=249). Please resolve those before marking the PR ready for review. Thanks!

@adombeck adombeck marked this pull request as draft December 10, 2024 13:28
@nsklikas nsklikas force-pushed the main branch 2 times, most recently from 72835a9 to 90573fc Compare December 10, 2024 13:38
@nsklikas
Copy link
Contributor Author

nsklikas commented Dec 10, 2024

Hello @adombeck, thank you for the very quick response. Sorry for this, all checks are passing now.

BTW there are a couple of TODOs in the code, where I would like your input.

@nsklikas nsklikas marked this pull request as ready for review December 10, 2024 13:44
@nsklikas nsklikas requested a review from adombeck December 10, 2024 15:00
Comment on lines 131 to 133
if err := ensureDirWithPerms(brokerConfigDir, 0700, os.Geteuid()); err != nil {
if err := ensureDirWithPerms(brokerConfigDir, 0755, os.Geteuid()); err != nil {
close(a.ready)
return fmt.Errorf("error initializing broker configuration directory %q: %v", brokerConfigDir, err)
}
}

Choose a reason for hiding this comment

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

issue: if we cannot create a folder with 0700 rights, then we cannot do it with additional priviledges of 0755, so you can remove the second function call. Right now the first err is not actually handled so make sure you add handling code for that

Copy link
Contributor Author

@nsklikas nsklikas Dec 10, 2024

Choose a reason for hiding this comment

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

If this is true, then shouldn't this be changed as well?

Copy link
Contributor

Choose a reason for hiding this comment

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

If this is true, then shouldn't this be changed as well?

No. The reason we call ensureDirWithPerms there twice is not that it requires different permissions to create a directory with 0700 than with 0755 (it does not), but that we want to create the data directory with 0700 by default but also support it having permissions 0755 (which is the case when it's the SNAP_DATA directory, because that's created with 0755 by snap). We don't need to support that for the broker.conf.d directory.

@@ -125,6 +125,16 @@ func (a *App) serve(config daemonConfig) error {
}
}

brokerConfigDir := config.Paths.BrokerConf + ".d"
// Should these dirs be created by the server or the snap?

Choose a reason for hiding this comment

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

After talking to Adrian I can confirm that permission for this folder must be 0700
If the folder is not present, we create it with those permission

// The user is allowed to login if:
// - ALL users are allowed
// - the user's name is in the list of allowed_users
// - the user is the owner of the machine

Choose a reason for hiding this comment

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

Suggested change
// - the user is the owner of the machine
// - the user is the owner of the machine and OWNER is in the allowed_users list

Choose a reason for hiding this comment

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

suggestion: honestly, I would remove these comments before the merging, since they're only helpful in dev and review time

Copy link
Contributor

Choose a reason for hiding this comment

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

I disagree, explaining the intend of a function in a comment doesn't only make it easier for reviewers but also for whoever else reads the code in the future.

Copy link
Collaborator

Choose a reason for hiding this comment

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

I personally don't care much of comments when commit messages explain the rationale well enough (really, I comment-less code is more than fine when git blame can be your friend), but given this is not the case (that implies: please be more elaborated in them), I feel it's better to keep it too.

Copy link
Contributor

Choose a reason for hiding this comment

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

note: meta discussion which is not relevant to this PR

I personally don't care much of comments when commit messages explain the rationale well enough (really, I comment-less code is more than fine when git blame can be your friend)

I think commit messages should explain the rationale in more detail than code comments, but IMO they don't replace code comments, because it requires significantly more work to view the commit message which originally introduced a change when the code has been modified many times since.

OwnerUserKey = "OWNER"

// ownerAutoregistrationConfigPath is the name of the file that will be auto-generated to register the owner.
ownerRegistrationConfigPath = "20-owner-autoregistration.conf"

Choose a reason for hiding this comment

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

Suggested change
ownerRegistrationConfigPath = "20-owner-autoregistration.conf"
ownerAutoRegistrationConfigPath = "20-owner-autoregistration.conf"


// ownerAutoregistrationConfigPath is the name of the file that will be auto-generated to register the owner.
ownerRegistrationConfigPath = "20-owner-autoregistration.conf"
ownerRegistrationConfigTemplate = "templates/20-owner-autoregistration.conf.tmpl"

Choose a reason for hiding this comment

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

Suggested change
ownerRegistrationConfigTemplate = "templates/20-owner-autoregistration.conf.tmpl"
ownerAutoRegistrationConfigTemplate = "templates/20-owner-autoregistration.conf.tmpl"

@@ -53,6 +94,38 @@ func getDropInFiles(cfgPath string) ([]any, error) {
return dropInFiles, nil
}

func parseUsersSection(cfg *userConfig, users *ini.Section) {

Choose a reason for hiding this comment

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

I would expect a parse* function to return a config object and an error, instead of applying side effects. Since you use it to enrich an existing object I strongly suggest a different name, like for example

Suggested change
func parseUsersSection(cfg *userConfig, users *ini.Section) {
func populateUsersConfig(cfg *userConfig, users *ini.Section) {

Comment on lines 168 to 172
r, ok := uc.allowedUsers[user]
if !ok {
return false
}
return r

Choose a reason for hiding this comment

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

question: why returning r when you can just return ok without the if?
This would allow for explicitly blocking users, but the spec doesn't have that feature

return err
}

f, err := os.Create(p)

Choose a reason for hiding this comment

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

issue: this creates a file with default 0666 permission.
After talking to Adrian I can confirm files inside the broker.d folder need to have 0600

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'm not completely done with the review, but I'm submitting the comments I made so far

internal/broker/config.go Outdated Show resolved Hide resolved
internal/broker/config.go Outdated Show resolved Hide resolved
internal/broker/config.go Outdated Show resolved Hide resolved
internal/broker/config.go Outdated Show resolved Hide resolved
internal/broker/config.go Outdated Show resolved Hide resolved
internal/broker/config.go Outdated Show resolved Hide resolved
internal/broker/config.go Outdated Show resolved Hide resolved
internal/broker/config.go Outdated Show resolved Hide resolved
internal/broker/config.go Outdated Show resolved Hide resolved
internal/broker/config.go Outdated Show resolved Hide resolved
allowed = true
} else if b.cfg.userConfig.OwnerUserAllowed() {
// If owner is undefined, then the first user to login is considered the owner
if b.cfg.userConfig.OwnerIsUnset() || *b.cfg.userConfig.Owner() == userName {
Copy link
Collaborator

Choose a reason for hiding this comment

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

*b.cfg.userConfig.Owner() shouldn't we check for nil ptr too?

Comment on lines 641 to 661
if b.cfg.userConfig.AllUsersAllowed() {
allowed = true
} else if b.cfg.userConfig.IsUserAllowed(userName) {
allowed = true
} else if b.cfg.userConfig.OwnerUserAllowed() {
// If owner is undefined, then the first user to login is considered the owner
if b.cfg.userConfig.OwnerIsUnset() || *b.cfg.userConfig.Owner() == userName {
allowed = true
}
}
Copy link
Collaborator

Choose a reason for hiding this comment

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

I feel this code can be put in a single function where you follow the happy-path better with early returns.

// user to login as the owner.
if b.cfg.userConfig.OwnerUserAllowed() && b.cfg.userConfig.OwnerIsUnset() {
if b.cfg.ConfigFile != "" {
// TODO(nsklikas): What should we do in case of error?
Copy link
Collaborator

Choose a reason for hiding this comment

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

Opening a discussion here, since this should be handled.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

After our sync today, it was decided that authn should fail in this case. I will update it.

Comment on lines 25 to 27
func pointerTo[T ~string](s T) *T {
return &s
}
Copy link
Collaborator

Choose a reason for hiding this comment

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

In authd itself we use this instead:

func ptrValue[T any](value T) *T {
	return &value
}

So for sake of consistency between our projects, please reuse the same.

internal/broker/config_test.go Outdated Show resolved Hide resolved
internal/broker/config_test.go Outdated Show resolved Hide resolved
@nsklikas nsklikas force-pushed the main branch 2 times, most recently from 769e6ff to 84b43a6 Compare December 11, 2024 16:07
@nsklikas
Copy link
Contributor Author

I tried to apply all the changes proposed, I did the following manual tests:

  • Clean install with default config and no 20-autoregistration file, populates 20-autoregistration file
  • Install the version on current main, configure, login, install the version from this branch and login without changing any of the config populates the 20-autoregistration file
  • Login when allowed_users=ALL and no owner set with nikos.sklikas@canonical.com succeeds and does not populate 20-autoregistration file
  • Login when allowed_users=ALL,OWNER and unset owner with nikos.sklikas@canonical.com succeeds and populates 20-autoregistration file
  • Login when owner=nikos.sklikas@canonical.com and allowed_users=a@canonical.com,OWNER with nikos.sklikas@canonical.com succeeds
  • Login when owner=nikos.sklikas@canonical.com and allowed_users=a@canonical.com with nikos.sklikas@canonical.com fails with authentication failure: permission denied
  • Login when allowed_users=OWNER and owner= fails for any user

I went with @adombeck's suggestion, but I am not adding the owner in the allowedUsers list because I think that in the future we may want to differentiate between regular users and the owner. Also tried to add more info the commit messages (cc @3v1n0)

Comment on lines 200 to 201
slog.Error(fmt.Sprintf("Failed to open autoregistration template: %v", err))
return err
Copy link
Collaborator

Choose a reason for hiding this comment

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

I feel it would be better to just:

Suggested change
slog.Error(fmt.Sprintf("Failed to open autoregistration template: %v", err))
return err
return fmt.Errorf("failed to open autoregistration template: %w", err)

Then in the caller function to log in case of error.

Copy link
Contributor

Choose a reason for hiding this comment

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

agreed


f, err := os.OpenFile(p, os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0600)
if err != nil {
slog.Error(fmt.Sprintf("Failed to create owner registration file: %v", err))
Copy link
Collaborator

Choose a reason for hiding this comment

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

Ditto, as before, wrap the error then log in the caller.

err := os.WriteFile(confPath, []byte(configTypes["valid"]), 0600)
require.NoError(t, err, "Setup: Failed to write config file")

dropInDir := confPath + ".d"
Copy link
Collaborator

Choose a reason for hiding this comment

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

Can reuse GetDropInDir?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

So I just want to offer my logic behind this, in tests IMO in tests it's better not to use helper functions or constants (e.g. a couple of lines we use "20-owner-autoregistration.conf" to generate the path of the file, instead of using the const ownerAutoRegistrationConfigPath). The reason for this is to act as a safe guard for typos in the code and to make changing this behavior harder. Also this function is used in the function that we are trying to test, so from a higher level I don't like this.

Of course, this does not always make sense and there are cases where it makes changes to the code harder (but I think that behavior like this is unlikely to change, so this is not the case).

Anyway, this is a nit-pick I would say, I don't think there is any real difference between the 2 approaches and I understand the reason, so I will update it.

err := os.WriteFile(confPath, []byte(testParseUserConfigTypes[name]), 0600)
require.NoError(t, err, "Setup: Failed to write config file")

dropInDir := confPath + ".d"
Copy link
Collaborator

Choose a reason for hiding this comment

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

Same here, can reuse GetDropInDir?

Comment on lines 57 to 60
if cfg.allowedUsers == nil {
cfg.SetAllowedUsers(map[string]struct{}{"OWNER": {}})
} else {
cfg.SetAllowedUsers(cfg.allowedUsers)
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
if cfg.allowedUsers == nil {
cfg.SetAllowedUsers(map[string]struct{}{"OWNER": {}})
} else {
cfg.SetAllowedUsers(cfg.allowedUsers)
cfg.SetAllowedUsers(cfg.allowedUsers)
if cfg.allowedUsers == nil {
cfg.SetAllowedUsers(cfg.SetAllowedUsers(map[string]struct{}{"OWNER": {}}))

Copy link
Collaborator

Choose a reason for hiding this comment

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

Comment on lines 75 to 79
p := msentraid.New()

ret := p.NormalizeUsername(tc.username)

require.Equal(t, tc.normalizedUsername, ret)
Copy link
Collaborator

Choose a reason for hiding this comment

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

nit:

Suggested change
p := msentraid.New()
ret := p.NormalizeUsername(tc.username)
require.Equal(t, tc.normalizedUsername, ret)
p := msentraid.New()
normalized := p.NormalizeUsername(tc.username)
require.Equal(t, tc.normalizedUsername, normalized)

Copy link
Contributor Author

Choose a reason for hiding this comment

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

The newlines were there to separate the 3 steps of the test (setup, execute, assert). I will update it.

tests := map[string]struct {
username string

normalizedUsername string
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
normalizedUsername string
wantNormalized string

If OWNER is in the `allowed_users` list and no owner is registered, a
configuration file with the first user to login is generated.
Add the logic for filtering users based on the allowed_users list.
A user is allowed to login if
 - ALL users are allowed
 - the user's name is in the list of allowed_users
 - OWNER is in the allowed_users and the user is the owner of the machine
@nsklikas nsklikas force-pushed the main branch 2 times, most recently from 6d98975 to 1fef256 Compare December 17, 2024 12:33
nsklikas and others added 4 commits December 17, 2024 13:41
@nsklikas
Copy link
Contributor Author

I tried to apply all the changes requested:

  • Cherry picked @adombeck commits (had to change them slightly), thanks for the race condition fix. As discussed on MM, no tests were added.
  • Removed golden files from the broker tests
  • Fixed a bug where the owner was not normalized on auto-registration
  • Applied code style changes

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.

To me this looks good to merge now, great job!

@adombeck
Copy link
Contributor

@3v1n0 are you happy as well?

Comment on lines +649 to +650
b.setOwnerMutex.Lock()
defer b.setOwnerMutex.Unlock()
Copy link
Collaborator

Choose a reason for hiding this comment

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

Wasn't this better as part of the configuration?

Copy link
Contributor

Choose a reason for hiding this comment

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

what do you mean?

Copy link
Collaborator

Choose a reason for hiding this comment

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

I mean the mutex being part of userConfig and so moving the ops dealing with it there?

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 considered that, but I decided to keep @adombeck's suggestion because i wanted to keep the config object as light as possible.

On one hand it makes sense to keep the mutex as close as possible to the race condition (the writing of the file), so we could move it to the config object. On the other hand, it also fine to say that registerOwner is not thread safe and the caller is responsible for implementing that.

I am fine with either approach (slightly leaning on keeping the current one).

Copy link
Contributor

Choose a reason for hiding this comment

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

Ah okay. I don't care if we keep it here or move it to userConfig.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

@3v1n0 what do you think (see 87e5f36).

Copy link
Collaborator

Choose a reason for hiding this comment

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

I meant something more like:

diff --git a/internal/broker/broker.go b/internal/broker/broker.go
index 4cf8198..6426843 100644
--- a/internal/broker/broker.go
+++ b/internal/broker/broker.go
@@ -49,8 +49,6 @@ type Config struct {
 type Broker struct {
 	cfg Config
 
-	setOwnerMutex sync.Mutex
-
 	provider providers.Provider
 	oidcCfg  oidc.Config
 
@@ -615,7 +613,7 @@ func (b *Broker) handleIsAuthenticated(ctx context.Context, session *session, au
 		}
 	}
 
-	err = b.maybeRegisterOwner(b.cfg.ConfigFile, authInfo.UserInfo.Name)
+	b.cfg.registerOwner(b.cfg.ConfigFile, authInfo.UserInfo.Name)
 	if err != nil {
 		// The user is not allowed if we fail to create the owner-autoregistration file.
 		// Otherwise the owner might change if the broker is restarted.
@@ -643,19 +641,6 @@ func (b *Broker) handleIsAuthenticated(ctx context.Context, session *session, au
 	return AuthGranted, userInfoMessage{UserInfo: authInfo.UserInfo}
 }
 
-func (b *Broker) maybeRegisterOwner(cfgPath string, userName string) error {
-	// We need to lock here to avoid a race condition where two users log in at the same time, causing both to be
-	// considered the owner.
-	b.setOwnerMutex.Lock()
-	defer b.setOwnerMutex.Unlock()
-
-	if !b.cfg.userConfig.shouldRegisterOwner() {
-		return nil
-	}
-
-	return b.cfg.registerOwner(cfgPath, userName)
-}
-
 // userNameIsAllowed checks whether the user's username is allowed to access the machine.
 func (b *Broker) userNameIsAllowed(userName string) bool {
 	normalizedUsername := b.provider.NormalizeUsername(userName)
@@ -669,7 +654,8 @@ func (b *Broker) userNameIsAllowed(userName string) bool {
 	if _, ok := b.cfg.userConfig.allowedUsers[normalizedUsername]; ok {
 		return true
 	}
-	return b.cfg.userConfig.ownerAllowed && b.cfg.userConfig.owner == normalizedUsername
+
+	return b.cfg.isOwnerAllowed(normalizedUsername)
 }
 
 func (b *Broker) startAuthenticate(sessionID string) (context.Context, error) {
diff --git a/internal/broker/config.go b/internal/broker/config.go
index 7070921..c084069 100644
--- a/internal/broker/config.go
+++ b/internal/broker/config.go
@@ -69,11 +69,10 @@ type userConfig struct {
 	ownerAllowed          bool
 	firstUserBecomesOwner bool
 	owner                 string
+	ownerMutex            *sync.RWMutex
 	homeBaseDir           string
 	allowedSSHSuffixes    []string
 
-	setOwnerMutex *sync.Mutex
-
 	provider provider
 }
 
@@ -144,12 +143,14 @@ func populateUsersConfig(cfg *userConfig, users *ini.Section) {
 
 	// We need to read the owner key after we call HasKey, because the key is created
 	// when we call the "Key" function and we can't distinguish between empty and unset.
+	cfg.ownerMutex.Lock()
+	defer cfg.ownerMutex.Unlock()
 	cfg.owner = cfg.provider.NormalizeUsername(users.Key(ownerKey).String())
 }
 
 // parseConfigFile parses the config file and returns a map with the configuration keys and values.
 func parseConfigFile(cfgPath string, p provider) (userConfig, error) {
-	cfg := userConfig{provider: p, setOwnerMutex: &sync.Mutex{}}
+	cfg := userConfig{provider: p, ownerMutex: &sync.RWMutex{}}
 
 	dropInFiles, err := getDropInFiles(cfgPath)
 	if err != nil {
@@ -189,11 +190,22 @@ func (uc *userConfig) shouldRegisterOwner() bool {
 	return uc.ownerAllowed && uc.firstUserBecomesOwner && uc.owner == ""
 }
 
+func (uc *userConfig) isOwnerAllowed(userName string) bool {
+	uc.ownerMutex.RLock()
+	defer uc.ownerMutex.RUnlock()
+
+	return uc.ownerAllowed && uc.owner == userName
+}
+
 func (uc *userConfig) registerOwner(cfgPath, userName string) error {
 	// We need to lock here to avoid a race condition where two users log in at the same time, causing both to be
 	// considered the owner.
-	uc.setOwnerMutex.Lock()
-	defer uc.setOwnerMutex.Unlock()
+	uc.ownerMutex.Lock()
+	defer uc.ownerMutex.Unlock()
+
+	if !uc.shouldRegisterOwner() {
+		return nil
+	}
 
 	if cfgPath == "" {
 		uc.owner = uc.provider.NormalizeUsername(userName)
diff --git a/internal/broker/config_test.go b/internal/broker/config_test.go
index 90f33b1..f20e9d5 100644
--- a/internal/broker/config_test.go
+++ b/internal/broker/config_test.go
@@ -59,7 +59,7 @@ issuer = https://higher-precedence-issuer.url.com
 func TestParseConfig(t *testing.T) {
 	t.Parallel()
 	p := &testutils.MockProvider{}
-	ignoredFields := map[string]struct{}{"provider": {}, "setOwnerMutex": {}}
+	ignoredFields := map[string]struct{}{"provider": {}, "ownerMutex": {}}
 
 	tests := map[string]struct {
 		configType string
@@ -319,7 +319,7 @@ func TestRegisterOwner(t *testing.T) {
 	err = os.Mkdir(dropInDir, 0700)
 	require.NoError(t, err, "Setup: Failed to create drop-in directory")
 
-	cfg := userConfig{firstUserBecomesOwner: true, ownerAllowed: true, provider: p, setOwnerMutex: &sync.Mutex{}}
+	cfg := userConfig{firstUserBecomesOwner: true, ownerAllowed: true, provider: p, ownerMutex: &sync.RWMutex{}}
 	err = cfg.registerOwner(confPath, userName)
 	require.NoError(t, err)
 
diff --git a/internal/broker/export_test.go b/internal/broker/export_test.go
index 1723c93..769c093 100644
--- a/internal/broker/export_test.go
+++ b/internal/broker/export_test.go
@@ -9,7 +9,7 @@ import (
 )
 
 func (cfg *Config) Init() {
-	cfg.setOwnerMutex = &sync.Mutex{}
+	cfg.ownerMutex = &sync.RWMutex{}
 }
 
 func (cfg *Config) SetClientID(clientID string) {
@@ -29,10 +29,14 @@ func (cfg *Config) SetAllowedUsers(allowedUsers map[string]struct{}) {
 }
 
 func (cfg *Config) SetOwner(owner string) {
+	cfg.ownerMutex.Lock()
+	defer cfg.ownerMutex.Unlock()
 	cfg.owner = owner
 }
 
 func (cfg *Config) SetFirstUserBecomesOwner(firstUserBecomesOwner bool) {
+	cfg.ownerMutex.Lock()
+	defer cfg.ownerMutex.Unlock()
 	cfg.firstUserBecomesOwner = firstUserBecomesOwner
 }
 

Now the usage of the RWMutex vs Mutex is on whether you want to b able to read an unset value while writing on it.

}
// If owner is undefined, then the first user to log in is considered the owner
return b.cfg.userConfig.firstUserBecomesOwner || b.cfg.userConfig.owner == normalizedUsername
return b.cfg.userConfig.ownerAllowed && b.cfg.userConfig.owner == normalizedUsername
Copy link
Collaborator

Choose a reason for hiding this comment

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

Shouldn't all the owner accesses now being protected too?

At least with a RWMutex instead?

Copy link
Contributor Author

@nsklikas nsklikas Dec 17, 2024

Choose a reason for hiding this comment

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

No, this part of the code will be reached only after the first user logs in (owner is registered) so we don't need to lock it (unless I'm missing something).

owner will be written only once and that is protected by the mutex. All other accesses are reads and they will happen after the owner is written.

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.

6 participants