post-receive-hook: Send also project name
[girocco.git] / Girocco / User.pm
blob6c6a04b6333a74420f41b94674ab7511fa32e398
1 package Girocco::User;
3 use strict;
4 use warnings;
6 use Girocco::Config;
8 BEGIN {
9 use Girocco::CGI;
10 use Girocco::Util;
11 use Digest::SHA1 qw(sha1_hex);
14 sub _passwd_add {
15 my $self = shift;
16 filedb_atomic_append(jailed_file('/etc/passwd'),
17 join(':', $self->{name}, 'x', '\i', 65534, $self->{email}, '/', '/bin/git-shell'));
20 sub _sshkey_path {
21 my $self = shift;
22 '/etc/sshkeys/'.$self->{name};
25 sub _sshkey_load {
26 my $self = shift;
27 open F, "<".jailed_file($self->_sshkey_path) or die "sshkey load failed: $!";
28 my @keys;
29 my $auth;
30 while (<F>) {
31 chomp;
32 if (/^ssh-(?:dss|rsa) /) {
33 push @keys, $_;
34 } elsif (/^# REPOAUTH ([0-9a-f]+) (\d+)/) {
35 my $expire = $2;
36 $auth = $1 unless (time >= $expire);
39 close F;
40 my $keys = join('', @keys); chomp $keys;
41 ($keys, $auth);
44 sub _sshkey_save {
45 my $self = shift;
46 open F, ">".jailed_file($self->_sshkey_path) or die "sshkey failed: $!";
47 if (defined($self->{auth}) && $self->{auth}) {
48 my $expire = time + 24 * 3600;
49 print F "# REPOAUTH $self->{auth} $expire\n";
51 print F $self->{keys}."\n";
52 close F;
53 chmod 0664, jailed_file($self->_sshkey_path);
56 # private constructor, do not use
57 sub _new {
58 my $class = shift;
59 my ($name) = @_;
60 Girocco::User::valid_name($name) or die "refusing to create user with invalid name ($name)!";
61 my $proj = { name => $name };
63 bless $proj, $class;
66 # public constructor #0
67 # creates a virtual user not connected to disk record
68 # you can conjure() it later to disk
69 sub ghost {
70 my $class = shift;
71 my ($name) = @_;
72 my $self = $class->_new($name);
73 $self;
76 # public constructor #1
77 sub load {
78 my $class = shift;
79 my ($name) = @_;
81 open F, jailed_file("/etc/passwd") or die "user load failed: $!";
82 while (<F>) {
83 chomp;
84 @_ = split /:+/;
85 next unless (shift eq $name);
87 my $self = $class->_new($name);
89 (undef, $self->{uid}, undef, $self->{email}) = @_;
90 ($self->{keys}, $self->{auth}) = $self->_sshkey_load;
92 return $self;
94 close F;
95 undef;
98 # $user may not be in sane state if this returns false!
99 sub cgi_fill {
100 my $self = shift;
101 my ($gcgi) = @_;
102 my $cgi = $gcgi->cgi;
104 $self->{name} = $gcgi->wparam('name');
105 Girocco::User::valid_name($self->{name})
106 or $gcgi->err("Name contains invalid characters.");
108 $self->{email} = $gcgi->wparam('email');
109 valid_email($self->{email})
110 or $gcgi->err("Your email sure looks weird...?");
112 $self->keys_fill($gcgi);
115 sub keys_fill {
116 my $self = shift;
117 my ($gcgi) = @_;
118 my $cgi = $gcgi->cgi;
120 $self->{keys} = $cgi->param('keys');
121 length($self->{keys}) <= 4096
122 or $gcgi->err("The list of keys is more than 4kb. Do you really need that much?");
123 foreach (split /\r?\n/, $self->{keys}) {
124 /^ssh-(?:dss|rsa) .* \S+@\S+$/ or $gcgi->err("Your ssh key (\"$_\") appears to have invalid format (does not start by ssh-dss|rsa or does not end with @-identifier) - maybe your browser has split a single key to multiple lines?");
127 not $gcgi->err_check;
130 sub keys_save {
131 my $self = shift;
133 $self->_sshkey_save;
136 sub gen_auth {
137 my $self = shift;
139 $self->{auth} = Digest::SHA1::sha1_hex(time . $$ . rand() . $self->{keys});
140 $self->_sshkey_save;
141 $self->{auth};
144 sub del_auth {
145 my $self = shift;
147 delete $self->{auth};
150 sub conjure {
151 my $self = shift;
153 $self->_passwd_add;
154 $self->_sshkey_save;
157 ### static methods
159 sub valid_name {
160 $_ = $_[0];
161 /^[a-zA-Z0-9+._-]+$/;
164 sub does_exist {
165 my ($name) = @_;
166 Girocco::User::valid_name($name) or die "tried to query for user with invalid name $name!";
167 (-e jailed_file("/etc/sshkeys/$name"));
170 sub resolve_uid {
171 my ($name) = @_;
172 $Girocco::Config::chrooted and undef; # TODO for ACLs within chroot
173 scalar(getpwnam($name));