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

Adds System Users and Groups #5627

Closed

Conversation

chris-huxtable
Copy link
Contributor

@chris-huxtable chris-huxtable commented Jan 22, 2018

Attempts to support Darwin, all Linux's, OpenBSD, and FreeBSD

Also adds features like:

  • Process.owner = System::User.get("nobody")
  • Process.group = System::Group.get("nobody")
  • Process.user
  • Process.group
  • System::User.get("root").shell
  • System::Group.get("wheel").members
  • File.chown("some/path", "root", "wheel")
  • File.chown("some/path", System::User.get("root"), System::Group.get("wheel"))
  • a_pointer.to_slice_null_terminated(a_limit) (useful for converting the Char** to an Array(String))

Previous PR #5615

@RX14
Copy link
Contributor

RX14 commented Jan 22, 2018

This PR is a rework of #5615.

Ditto all the review comments I made on the last PR, of course.

@chris-huxtable
Copy link
Contributor Author

Just to clarify in this thread. I can removed System.{user, group,user?,group?}.

@drosehn
Copy link

drosehn commented Jan 22, 2018

Does this reflect the distinction between gid/egid and uid/euid?
("reflect" in the sense that all four can be separately inspected/modified via the crystal API)

(later: nevermind. Based on a quick skim over the changes, it looks like it does)

spec/std/process_spec.cr Outdated Show resolved Hide resolved
spec/std/system/group_spec.cr Outdated Show resolved Hide resolved
spec/std/system/user_spec.cr Outdated Show resolved Hide resolved
src/pointer.cr Outdated Show resolved Hide resolved
src/system/group.cr Outdated Show resolved Hide resolved
src/system/group.cr Outdated Show resolved Hide resolved
@chris-huxtable
Copy link
Contributor Author

@drosehn - Process has a method for real, effective and saved users/groups.

src/system/group.cr Outdated Show resolved Hide resolved
src/system/user.cr Outdated Show resolved Hide resolved
src/system/user.cr Outdated Show resolved Hide resolved
@straight-shoota
Copy link
Member

Just to clarify in this thread. I can removed System.{user, group,user?,group?}.

I guess that's still open for debate. Let's wait to here some thoughts on which of these should be preferred:

  • System::User.get and System::Group.get (plus ?-methods)
  • System.user and System.group (plus ?-methods)

There are certainly arguments for both. User.get/Group.get means single responsibility. On the other hand, there is also Process.user. Should this then also be User.current? The method name get is somewhat arbitrary. The reason to avoid new is solid, but it's not entirely natural to use get. In Go, for example, it's User.Lookup.
So I actually tend to favour System.user/System.group because it follows the same scheme as Process.user/Process.group, as well as existing System class methods: System.hostname means "give me the system's hostname", System.user("root")means "give me the system's user with name 'root'".

@chris-huxtable
Copy link
Contributor Author

Instead of {User, Group}.exists?() I am going to implement User.uid?(), Group.gid?(), User.name?(), and Group.name?() as lighter-weight checkers of existence and fetchers of info.

@chris-huxtable
Copy link
Contributor Author

chris-huxtable commented Jan 22, 2018

@straight-shoota - @RX14, put it in as changes to be made in the previous PR. I personally wouldn't use it. All I am interested in is making sure I can use System::{User, Group).get().

src/pointer.cr Outdated Show resolved Hide resolved
src/system/user.cr Outdated Show resolved Hide resolved
@chris-huxtable
Copy link
Contributor Author

chris-huxtable commented Jan 22, 2018

@straight-shoota - I like System::User.lookup(), System::User.lookup?(), and the Group equivalents but its uncomfortably long. Though I am willing to change it. All being said I still like square brackets best.

@RX14
Copy link
Contributor

RX14 commented Jan 22, 2018

Let's freeze changes to this PR and wait a few days for other people to weigh in on how they think the API should work. No more nitpicks on the implementation until we've decided on the interface please!

@chris-huxtable
Copy link
Contributor Author

Sure, though I have some changes to documentation in the pipe.

src/system/group.cr Outdated Show resolved Hide resolved
@RX14
Copy link
Contributor

RX14 commented Jan 22, 2018

@chris-huxtable if you've written stuff, push it, but i'd suggest not writing more and waiting for consensus to emerge.

@RX14
Copy link
Contributor

RX14 commented Jan 22, 2018

@Sija please make your reviews one review with multiple comments, instead of multiple reviews.

src/system/group.cr Outdated Show resolved Hide resolved
@chris-huxtable
Copy link
Contributor Author

chris-huxtable commented Jan 22, 2018

@drosehn - As of right now it doesn't let you set the real/effective/saved uid's independently. It can be done if you want to use LibC. From a security standpoint, someone not understanding the difference will likely screw things up, so I think it best to not make it easy to do so.

@chris-huxtable
Copy link
Contributor Author

chris-huxtable commented Jan 22, 2018

I would also like to float the idea of having Process.root?() to see if you are running as root. Which would be more light-weight then Process.user == System::User.get(root)

def self.root?() : Bool
  return LibC.getuid == 0
end

src/pointer.cr Outdated Show resolved Hide resolved
src/system/group.cr Outdated Show resolved Hide resolved
@drosehn
Copy link

drosehn commented Jan 23, 2018

As of right now it doesn't let you set the real/effective/saved uid's independently. It can be done if you want to use LibC. From a security standpoint, someone not understanding the difference will likely screw things up, so I think it best to not make it easy to do so.

Well, that seems less-than-wonderful to me, but then I'm a systems programmer who has worked on Solaris, MacOS, AIX (ugh), and Linux for many years. I already wrote a crystal program which uses these features, because I know what I'm doing with the different values. I stopped after implementing what I needed, because I wasn't sure how I'd write specs for setting these values, when you have to be root in order to set them. I trust me with root access on my machines, but I'm not sure how could write specs which would need to be run as root!

(note: i have no experience in writing specs for a compiler's standard library)

@chris-huxtable
Copy link
Contributor Author

chris-huxtable commented Jan 23, 2018

@drosehn - I don't have specs for any of the setuid/et al. methods because it would be dangerous to run as root. What I may do though is make some that catch the raises which would be thrown when they fail. This would at least test for the failing cases.

@chris-huxtable
Copy link
Contributor Author

chris-huxtable commented Aug 26, 2018

Ready for review, provided this passes tests (which they should).

@@ -253,4 +253,78 @@ describe Process do
Process.find_executable("some_very_unlikely_file_to_exist").should be_nil
end
end

describe "users and groups" do
it "has a user" do
Copy link
Member

Choose a reason for hiding this comment

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

Just naming these examples after the methods they're testing more clearly communicates what they're about: it "#user" do.

Ditto all below.
The (failing) setter specs could also be grouped in a describe "#user=" do (etc.)

# ```
def self.chown(path, uid : Int = -1, gid : Int = -1, follow_symlinks = false)
Crystal::System::File.chown(path, uid, gid, follow_symlinks)
def self.chown?(path : String, owner : UserRep? = nil, group : GroupRep? = nil, follow_symlinks : Bool = false) : Bool
Copy link
Member

Choose a reason for hiding this comment

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

It is necessary, but not a necessary part of this PR. This is already very complex, has changed a lot of times and is really hard to review. Please, you would do everyone a favour if you hold the chown change back until the rest has been merged. It's just so much easier to deal with smaller pieces one after the other than chew everything at once.

@straight-shoota
Copy link
Member

We can add an explicit getter for named and id based values. But that seems kludgy.

There is no way around something like this. We can't distinguish ids from names by data type if both are strings. And there is no way to distinguish by value because according to POSIX definition, any id should also be a valid name.

One alternative is to have separate methods from_uid/from_username. The other option is to use the same method name but different argument names: get(gid: "123")/get(name: "root").

@j8r
Copy link
Contributor

j8r commented Aug 26, 2018

What about extending the API with Int overloads for Unix systems? IDs are a feature that Windows doesn't have.

For usernames, the API can be the same because this feature is in common.

end

# Indicates if the given gid is valid
def self.valid_gid?(gid : Int?) : Bool
Copy link
Contributor

@Sija Sija Aug 26, 2018

Choose a reason for hiding this comment

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

Why allowing nillable gid argument? Ditto all (gid/uid) below.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Nil is not a valid uid and allows the check to be done for the user implicitly.

Copy link
Contributor

Choose a reason for hiding this comment

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

Why any1 would pass a nil as a gid/uid?
Same can be said about other types as well.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Since a lot of the backend is nilible (invalid uid) it helps keep everything clean.

Copy link
Contributor

Choose a reason for hiding this comment

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

I've counted 2 places (https://github.com/crystal-lang/crystal/pull/5627/files#diff-498ff69d4645ddc11a8ea3ba27be9aa8R139 & https://github.com/crystal-lang/crystal/pull/5627/files#diff-75e18fac160b2c9c267ae8b2e112e2fdR160) where it would needed to be handled, that's not a lot. I'd argue that a clean API here is more important. Same goes for valid_uid?.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Fair. I will think about it. It was used more, but I guess I have reduced them out.

@@ -237,23 +240,34 @@ class File < IO::FileDescriptor
basename(path).chomp(suffix)
end

private alias UserRep = System::User | String | UInt32 | Int32
Copy link
Contributor

Choose a reason for hiding this comment

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

I'd rather avoid using private aliases like these, in docs they'll end up pretty cryptic for end-user. Also, the name is not easily understable (I guess Rep suffix stands for Representation but still, not everyone might grok that).

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 can replace them with the long form, but the lines become awfully long.

Copy link
Contributor

Choose a reason for hiding this comment

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

IMO it's better than an unknown, cryptic alias.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

We could add

alias System::User::Representation  = System::User | String | UInt32 | Int32

@@ -8,6 +8,8 @@ lib LibC
X_OK = 1
SC_CLK_TCK = 2
SC_NPROCESSORS_ONLN = 58
# FIXME: SC_GETGR_R_SIZE_MAX = 69???
# FIXME: SC_GETPW_R_SIZE_MAX = 70???
Copy link
Contributor

Choose a reason for hiding this comment

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

Are these still relevant?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Those should be fixed. They are need for that platform and I haven't been able to verify them. Does anyone know or use that platform and can extract the correct values from the includes?

Copy link
Contributor

Choose a reason for hiding this comment

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

The enum declaration is identical for all glibc archs I checked (i686, x86_64 and armeabi), so they should be 69 and 70 too on arm-linux-gnueabihf too.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

With that I will set them as such. I just haven't managed to personally witness it with my own eyes.

@straight-shoota
Copy link
Member

@chris-huxtable It seems we're waiting for you to address some of the review comments and rebase on master.

@chris-huxtable
Copy link
Contributor Author

chris-huxtable commented Oct 18, 2018

@straight-shoota - I haven’t had time. I am hopefully going to have some down time in the coming weeks.

@straight-shoota straight-shoota added kind:feature topic:stdlib:system pr:needs-work A PR requires modifications by the author. labels Feb 11, 2019
@woodruffw
Copy link
Contributor

JFYI: I'm going to try to revive a trimmed-down version of this (just System::Group and System::User).

@bcardiff
Copy link
Member

Closed by #7725

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
kind:feature pr:needs-work A PR requires modifications by the author. topic:stdlib:system
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet