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 ownership options for "Create directory" #478

Merged
merged 2 commits into from
Jun 5, 2024

Conversation

allisonkarlitskaya
Copy link
Member

@allisonkarlitskaya allisonkarlitskaya commented May 27, 2024

If we have superuser capabilities, then add a drop-down list to the "Create directory" dialog with possible ownerships for the create directory.

By default we try to create the new directory with the same owner as the parent, but also have options for root:root or the user:group of the logged in user (if it is different from the parent directory).

The logic for determining the potential owners of newly created items is generic and might also be applied to things like upload, so keep it in its own file.

Also, move the dialog into a new file: src/dialogs/mkdirt.tsx. fileActions has become a bit too much of a catch-all.

Todo:

@allisonkarlitskaya allisonkarlitskaya requested a review from jelly May 27, 2024 12:48
src/fileActions.jsx Fixed Show fixed Hide fixed
@allisonkarlitskaya
Copy link
Member Author

# src/dialogs/mkdir.tsx(107,26): error TS2739: Type '{ fieldId: string; helperTextInvalid: string | null; }' is missing the following properties from type '{ helperText: any; helperTextInvalid: any; variant: any; icon: any; fieldId: any; }': helperText, variant, icon

This is a pretty annoying issue. This comes from cockpit repo, and it's written in js. js assumes that some of these fields might be missing (and deals with them if they're undefined). It would be nice if we could figure out a way to work around issues like this with something better than "port the cockpit component to typescript" every time...

src/ownership.tsx Show resolved Hide resolved
src/ownership.tsx Show resolved Hide resolved
}

const id = Number(match[1]);
const name = match[2] === "unbound" ? null : match[2];
Copy link
Member

Choose a reason for hiding this comment

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

When is match[2] ever unbound? Is that something id prints?

Copy link
Member Author

Choose a reason for hiding this comment

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

I tested running id as a user without a /etc/passwd entry and it printed "unbound" for the username.

I also noticed that ls -l does this now as well, instead of printing the numeric ID, which is a bit annoying.

I wonder if it's some nss module responsible for that, or a recent change in coreutils. I tried to look into it a bit but I didn't get too far.

Copy link
Member

Choose a reason for hiding this comment

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

Ok, unbound is also a fairly popular daemon so that's "interesting"

Copy link
Member

Choose a reason for hiding this comment

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

Copy link
Member

Choose a reason for hiding this comment

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

This unbound check still feels a bit suspect to me.

src/dialogs/mkdir.tsx Show resolved Hide resolved
key={owner}
value={owner}
/* Translators: the $0 is a string like root:root or admin:staff */
label={cockpit.format(_("Create directory owned by '$0'"), owner)}
Copy link
Member

Choose a reason for hiding this comment

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

I don't think the Select should include this very verbose information but only root:root.

Copy link
Member Author

Choose a reason for hiding this comment

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

Agreed, but I didn't know how to do the thing with the Label :)

Copy link
Member Author

Choose a reason for hiding this comment

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

Okay. I did something here. I don't know if it's good enough.

@allisonkarlitskaya allisonkarlitskaya force-pushed the mkdir branch 3 times, most recently from 3c77a94 to 579964d Compare May 27, 2024 15:46
@allisonkarlitskaya
Copy link
Member Author

Screenshots:

image

image

b.wait_visible("[data-item='newdir']")
self.assert_owner('/root/newdir', 'root:root')
finally:
m.execute(['rm', '-rf', '/root/newdir'])
Copy link
Member

Choose a reason for hiding this comment

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

This should be `self.addCleanup(m.execute, "rm -rf /root/newdir")

Copy link
Member Author

Choose a reason for hiding this comment

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

True.

I first tried to do .restore_dir('/root') but this had problems since it would need to unmount the overlayed root homedir while running a command executed over ssh... as root. So I did this. But you're right — addCleanup is definitely more idiomatic.

b.wait_visible("[data-item='newdir']")
self.assert_owner('/tmp/newdir', 'root:root')
finally:
m.execute(['rm', '-rf', '/tmp/newdir'])
Copy link
Member

Choose a reason for hiding this comment

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

Same as above.

test/check-application Show resolved Hide resolved
test/check-application Show resolved Hide resolved
test/check-application Show resolved Hide resolved
@allisonkarlitskaya
Copy link
Member Author

The rawhide issue is a straight-up race. The tests are fast enough to go through the process of creating the directory before sudo gets done with what it's doing. We do superuser: try for the mkdir and that ends up waiting until sudo is done, which will create the directory as root, even though we expected that we were doing that in a non-authenticated state.

This stats a file on the VM with the given format string.
@jelly jelly marked this pull request as ready for review June 4, 2024 12:38
src/ownership.tsx Show resolved Hide resolved
<Button
variant="primary"
onClick={createDirectory}
isDisabled={errorMessage !== undefined || nameError !== undefined}
Copy link
Member

Choose a reason for hiding this comment

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

This needs to be disabled when superuser.allowed && !id && !cwdInfo

Copy link
Member

Choose a reason for hiding this comment

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

-                    isDisabled={errorMessage !== undefined || nameError !== undefined}
+                    isDisabled={errorMessage !== undefined || nameError !== undefined || (superuser.allowed && (!id && !cwdInfo))}

Copy link
Member Author

Choose a reason for hiding this comment

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

I think you meant (!id || !cwdInfo) or !(id && cwdInfo), yes?

}

export function useId(): IdInfo | null {
/* XXX This should be part of the cockpit UserInfo */
Copy link
Member

@jelly jelly Jun 4, 2024

Choose a reason for hiding this comment

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

And it is, so linking cockpit-project/cockpit#20517 would be nice


const [id, setId] = React.useState<IdInfo | null>(null);

React.useEffect(() => {
Copy link
Member

Choose a reason for hiding this comment

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

useInit is like useEffect(, [])

Copy link
Member

@jelly jelly left a comment

Choose a reason for hiding this comment

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

If we add isDisabled then the change is fine to me

@allisonkarlitskaya
Copy link
Member Author

If we add isDisabled then the change is fine to me

Done.

@jelly
Copy link
Member

jelly commented Jun 5, 2024

Two trivial lint issues left.

If we have superuser capabilities, then add a drop-down list to the
"Create directory" dialog with possible ownerships for the create
directory.

By default we try to create the new directory with the same owner as the
parent, but also have options for root:root or the user:group of the
logged in user (if it is different from the parent directory).

The logic for determining the potential owners of newly created items is
generic and might also be applied to things like upload, so keep it in
its own file.

Also, move the dialog into a new file: src/dialogs/mkdirt.tsx.
fileActions has become a bit too much of a catch-all.

Add some test cases to make sure that we create files with "reasonable
default" ownership in various different locations.
@jelly
Copy link
Member

jelly commented Jun 5, 2024

download tests can be flaky, retrying.

await cockpit.spawn(["mkdir", path], opts);
await cockpit.spawn(["chown", owner, path], opts);
} else {
await cockpit.spawn(["mkdir", path], { err: "message" });
Copy link
Contributor

Choose a reason for hiding this comment

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

This added line is not executed by any test.

Comment on lines +95 to +98
isHorizontal onSubmit={e => {
createDirectory();
e.preventDefault();
return false;
Copy link
Contributor

Choose a reason for hiding this comment

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

These 4 added lines are not executed by any test.

<FormSelect
id='create-directory-owner'
value={owner}
onChange={(_ev, val) => setOwner(val)}
Copy link
Contributor

Choose a reason for hiding this comment

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

This added line is not executed by any test.

Comment on lines +20 to +21
if (!match) {
throw new Error(`invalid id ${content}`);
Copy link
Contributor

Choose a reason for hiding this comment

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

These 2 added lines are not executed by any test.

}

const id = Number(match[1]);
const name = match[2] === "unbound" ? null : match[2];
Copy link
Contributor

Choose a reason for hiding this comment

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

This added line is not executed by any test.

try {
const uid = parse_id(fields.uid);
const gid = parse_id(fields.gid);
const groups = fields.groups?.split(',')?.map(parse_id) || [];
Copy link
Contributor

Choose a reason for hiding this comment

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

This added line is not executed by any test.

Comment on lines +49 to +50
} catch (exc) {
console.log(`Failed to parse output of "id" command: ${stdout}: ${exc}`);
Copy link
Contributor

Choose a reason for hiding this comment

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

These 2 added lines are not executed by any test.

// create as, mirroring the usual POSIX behaviour. There are other cases
// where the "BSD group semantics" come into play (like mount options) but
// we don't currently support those. We might in the future, though...
const setgid = (info.group !== undefined && (info.mode || 0) & 0o2000) ? `${info.group}` : null;
Copy link
Contributor

Choose a reason for hiding this comment

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

This added line is not executed by any test.

// The last option is always available: create as the normal user. In case
// of something inside of the user's home directory, this was probably the
// first option as well...
candidates.add(`${id.uid.name || id.uid.id}:${setgid || id.gid.name || id.gid.id}`);
Copy link
Contributor

Choose a reason for hiding this comment

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

This added line is not executed by any test.

Copy link
Member

@jelly jelly left a comment

Choose a reason for hiding this comment

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

Merging with the promise that we kill useId with Cockpit 318 :)

@jelly jelly merged commit 75cf854 into cockpit-project:main Jun 5, 2024
14 checks passed
@allisonkarlitskaya allisonkarlitskaya deleted the mkdir branch June 19, 2024 21:42
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.

3 participants