Project.pm: avoid warning for undef name in load()
[girocco.git] / Girocco / User.pm
blobe619c29ce396ab7073050c15244d91c1d503fc59
1 package Girocco::User;
3 use strict;
4 use warnings;
6 use Girocco::Config;
7 use Girocco::CGI;
8 use Girocco::Util;
10 BEGIN {
11 eval {
12 require Digest::SHA;
13 Digest::SHA->import(
14 qw(sha1_hex)
15 );1} ||
16 eval {
17 require Digest::SHA1;
18 Digest::SHA1->import(
19 qw(sha1_hex)
20 );1} ||
21 eval {
22 require Digest::SHA::PurePerl;
23 Digest::SHA::PurePerl->import(
24 qw(sha1_hex)
25 );1} ||
26 die "One of Digest::SHA or Digest::SHA1 or Digest::SHA::PurePerl "
27 . "must be available\n";
30 sub _passwd_add {
31 my $self = shift;
32 filedb_atomic_append(jailed_file('/etc/passwd'),
33 join(':', $self->{name}, 'x', '\i', 65534, $self->{email}, '/', '/bin/git-shell'));
36 sub _passwd_update {
37 my $self = shift;
38 filedb_atomic_edit(jailed_file('/etc/passwd'),
39 sub {
40 $_ = $_[0];
41 chomp;
42 if ($self->{name} eq (split /:/)[0]) {
43 # preserve all but login name and comment field
44 my @fields=split(/:/, $_, -1);
45 $fields[0] = $self->{name};
46 $fields[4] = $self->{email};
47 return join(':', @fields)."\n";
48 } else {
49 return "$_\n";
55 sub _sshkey_path {
56 my $self = shift;
57 '/etc/sshkeys/'.$self->{name};
60 sub _sshkey_load {
61 my $self = shift;
62 open F, "<".jailed_file($self->_sshkey_path) or die "sshkey load failed: $!";
63 my @keys;
64 my $auth;
65 while (<F>) {
66 chomp;
67 if (/^ssh-(?:dss|rsa) /) {
68 push @keys, $_;
69 } elsif (/^# REPOAUTH ([0-9a-f]+) (\d+)/) {
70 my $expire = $2;
71 $auth = $1 unless (time >= $expire);
74 close F;
75 my $keys = join("\n", @keys); chomp $keys;
76 ($keys, $auth);
79 sub _sshkey_save {
80 my $self = shift;
81 open F, ">".jailed_file($self->_sshkey_path) or die "sshkey failed: $!";
82 if (defined($self->{auth}) && $self->{auth}) {
83 my $expire = time + 24 * 3600;
84 print F "# REPOAUTH $self->{auth} $expire\n";
86 print F $self->{keys}."\n";
87 close F;
88 chmod 0664, jailed_file($self->_sshkey_path);
91 # private constructor, do not use
92 sub _new {
93 my $class = shift;
94 my ($name) = @_;
95 Girocco::User::valid_name($name) or die "refusing to create user with invalid name ($name)!";
96 my $proj = { name => $name };
98 bless $proj, $class;
101 # public constructor #0
102 # creates a virtual user not connected to disk record
103 # you can conjure() it later to disk
104 sub ghost {
105 my $class = shift;
106 my ($name) = @_;
107 my $self = $class->_new($name);
108 $self;
111 # public constructor #1
112 sub load {
113 my $class = shift;
114 my ($name) = @_;
116 open F, jailed_file("/etc/passwd") or die "user load failed: $!";
117 while (<F>) {
118 chomp;
119 @_ = split /:/;
120 next unless (shift eq $name);
122 my $self = $class->_new($name);
124 (undef, $self->{uid}, undef, $self->{email}) = @_;
125 ($self->{keys}, $self->{auth}) = $self->_sshkey_load;
127 return $self;
129 close F;
130 undef;
133 # public constructor #2
134 sub load_by_uid {
135 my $class = shift;
136 my ($uid) = @_;
138 open F, jailed_file("/etc/passwd") or die "user load failed: $!";
139 while (<F>) {
140 chomp;
141 @_ = split /:/;
142 next unless ($_[2] eq $uid);
144 my $self = $class->_new($_[0]);
146 (undef, undef, $self->{uid}, undef, $self->{email}) = @_;
147 ($self->{keys}, $self->{auth}) = $self->_sshkey_load;
149 return $self;
151 close F;
152 undef;
155 # $user may not be in sane state if this returns false!
156 sub cgi_fill {
157 my $self = shift;
158 my ($gcgi) = @_;
159 my $cgi = $gcgi->cgi;
161 $self->{name} = $gcgi->wparam('name');
162 Girocco::User::valid_name($self->{name})
163 or $gcgi->err("Name contains invalid characters.");
165 $self->{email} = $gcgi->wparam('email');
166 valid_email($self->{email})
167 or $gcgi->err("Your email sure looks weird...?");
169 $self->keys_fill($gcgi);
172 sub _trimkeys {
173 my $keys = shift;
174 my @lines = ();
175 foreach (split /\r\n|\r|\n/, $keys) {
176 next if /^[ \t]*$/ || /^[ \t]*#/;
177 push(@lines, $_);
179 return join("\n", @lines);
182 sub update_email {
183 my $self = shift;
184 my $gcgi = shift;
185 my $email = shift || '';
187 if (valid_email($email)) {
188 $self->{email} = $email;
189 $self->_passwd_update;
190 } else {
191 $gcgi->err("Your email sure looks weird...?");
194 not $gcgi->err_check;
197 sub keys_fill {
198 my $self = shift;
199 my ($gcgi) = @_;
200 my $cgi = $gcgi->cgi;
202 $self->{keys} = _trimkeys($cgi->param('keys'));
203 length($self->{keys}) <= 4096
204 or $gcgi->err("The list of keys is more than 4kb. Do you really need that much?");
205 foreach (split /\r?\n/, $self->{keys}) {
206 my $keyval;
207 /^ssh-(?:dss|rsa) [0-9A-Za-z+\/=]+ \S+@\S+$/
208 or $keyval=CGI::escapeHTML($_),$gcgi->err(<<EOT);
209 Your ssh key ("$keyval") appears to have an invalid format
210 (does not start with ssh-dss or ssh-rsa or does not end with <tt>\@</tt>-identifier) -
211 maybe your browser has split a single key onto multiple lines?
215 not $gcgi->err_check;
218 sub keys_save {
219 my $self = shift;
221 $self->_sshkey_save;
224 sub gen_auth {
225 my $self = shift;
227 $self->{auth} = sha1_hex(time . $$ . rand() . $self->{keys});
228 $self->_sshkey_save;
229 $self->{auth};
232 sub del_auth {
233 my $self = shift;
235 delete $self->{auth};
238 sub conjure {
239 my $self = shift;
241 $self->_passwd_add;
242 $self->_sshkey_save;
245 ### static methods
247 sub valid_name {
248 $_ = $_[0];
249 /^[a-zA-Z0-9+._-]+$/;
252 sub does_exist {
253 my ($name) = @_;
254 Girocco::User::valid_name($name) or die "tried to query for user with invalid name $name!";
255 (-e jailed_file("/etc/sshkeys/$name"));
258 sub resolve_uid {
259 my ($name) = @_;
260 $Girocco::Config::chrooted and undef; # TODO for ACLs within chroot
261 scalar(getpwnam($name));