Browse Source

* initial

master
Alex "AdUser" Z 10 years ago
commit
761496af2a
  1. 24
      conf/ldv.conf.sample
  2. 32
      lib/LDV.pm
  3. 75
      lib/LDV/Actions.pm
  4. 40
      lib/LDV/I18N/ru.pm
  5. 118
      lib/LDV/LDAP.pm
  6. 58
      lib/LDV/User.pm
  7. 12
      lib/LDV/Zerobin.pm
  8. 14
      script/ldv
  9. 24
      t/LDAP.t
  10. 7
      t/basic.t
  11. 19
      templates/includes/ie-hacks.html.ep
  12. 79
      templates/layouts/default.html.ep
  13. 41
      templates/user/create.html.ep
  14. 21
      templates/user/login.html.ep
  15. 41
      templates/user/update.html.ep
  16. 28
      templates/zerobin/create.html.ep
  17. 29
      templates/zerobin/view.html.ep

24
conf/ldv.conf.sample

@ -0,0 +1,24 @@
{
secret => undef,
server => '127.0.0.1',
binddn => undef,
bindpass => undef,
userbase => undef,
defattrs => [
'displayname', 'mail', 'cn', 'sn',
'givenname', 'userpassword', 'uid',
],
defclasses => ['inetOrgPerson'],
hypnotoad => {
accepts => 100,
listen => [ 'http://127.0.0.1:3000' ],
heartbeat_interval => 5,
heartbeat_timeout => 20,
upgrade_timeout => 60,
proxy => 1,
workers => 2,
}
}

32
lib/LDV.pm

@ -0,0 +1,32 @@
package LDV;
use strict;
use warnings;
use utf8;
use Mojo::Base 'Mojolicious';
sub startup {
my ($self) = @_;
$self->plugin(I18N => {default => 'ru'});
$self->plugin(Config => {file => $self->app->home->rel_file('conf/ldv.conf')});
$self->app->mode('production');
$self->app->secret($self->app->config->{secret});
my $r = $self->routes;
# /user
$r->get('/user/login')->to('user#login');
$r->get('/user/create')->to('user#create');
$r->get('/user/update')->to('user#update');
$r->post('/user/create')->to('actions#create');
# /zerobin
$r->get('/zerobin2')->to('zerobin#view');
$r->get('/zerobin2/create')->to('zerobin#create');
}
1;

75
lib/LDV/Actions.pm

@ -0,0 +1,75 @@
package LDV::Actions;
use strict;
use warnings;
use utf8;
use Mojo::Base 'Mojolicious::Controller';
use Net::LDAP;
use Net::LDAP::Util qw(ldap_error_name);
use Crypt::SaltedHash;
sub create
{
my ($self) = @_;
my ($result);
$result = "Created";
eval {
my ($ldap, $mesg);
$ldap = Net::LDAP->new($self->app->config->{server})
or die("$@");
$mesg = $ldap->bind($self->app->config->{binddn},
password => $self->app->config->{bindpass});
if ($mesg->code) {
$self->app->log->error($mesg->error);
die("Can't connect to server\n");
}
my $base = $self->app->config->{userbase};
my $login = $self->req->param('login');
die ("Empty username\n")
unless ($login);
die ("Forbidden characters in username\n")
unless ($login =~ m|^[a-z]{2,36}$|oi);
$mesg = $ldap->search(base => $base, scope => 'one', deref => 'never',
filter => '(&(uid=$login)(class=InetOrgPerson))');
die("This user already exists\n")
if ($mesg->count);
my $attrs = {};
$attrs->{objectclass} = [ "top", @{$self->app->config->{defclasses}} ];
$attrs->{mail} = $self->req->param('mail');
$attrs->{displayname} = $self->req->param('displayname') || '';
if ($attrs->{displayname} =~ m|^(\S+)\s+(?:.*\s+)?(\S+)$|oi) {
$attrs->{cn} = $1;
$attrs->{sn} = $2;
} else {
$attrs->{cn} = '!not set!';
$attrs->{sn} = '!not set!';
}
$attrs->{uid} = $login;
my $csh = Crypt::SaltedHash->new(algorithm => 'SHA-1');
$csh->add($self->req->param('pass'));
$attrs->{userpassword} = $csh->generate();
$mesg = $ldap->add("uid=$login,$base", attrs => [ %$attrs ]);
if ($mesg->code) {
$self->app->log->error($mesg->error);
die("Can't add user\n");
}
$ldap->unbind(); 1;
} or do {
$self->app->log->error($@);
$result = "Error: $@";
};
$self->flash({result => $result});
$self->redirect_to('/user/create');
$self->rendered();
return 1;
}
1;

40
lib/LDV/I18N/ru.pm

@ -0,0 +1,40 @@
package LDV::I18N::ru;
use Mojo::Base 'LDV::I18N';
use strict;
use warnings;
use utf8;
our %Lexicon =
(
_AUTO => 1, # enable fallback
'Create new user' => 'Создать нового юзера',
'Update user' => 'Обновить пользователя',
'Login' => 'Логин',
'Log in' => 'Войти',
'Password' => 'Пароль',
'Current password' => 'Текущий пароль',
'Display name' => 'Отображаемое имя',
'Email' => 'Эл. почта',
'Create' => 'Создать',
'John Doe' => 'Иван Петров',
'Organization' => 'Организация',
'Mobile' => 'Сотовый',
'Avatar' => 'Аватар',
'Update' => 'Обновить',
'Auth required' => 'Требуется авторизация',
'Keep for' => 'Хранить',
'Save' => 'Сохранить',
'Syntax highlight' => 'Подсветка синтаксиса',
'day' => 'день',
'week' => '7 дней',
'month' => 'месяц',
'quarter' => '3 месяца',
'year' => 'год',
'forever' => 'всегда',
);
1;

118
lib/LDV/LDAP.pm

@ -0,0 +1,118 @@
package LDV::LDAP;
use strict;
use warnings;
use utf8;
use Net::LDAP;
use Net::LDAP::Util qw(ldap_error_name);
sub new {
my ($class, $opts) = @_;
my $self = {
server => undef,
binddn => undef,
bindpass => undef,
userbase => undef,
userfilter => "(class=InetOrgPerson)",
%$opts,
};
return bless($self, $class);
}
sub _connect {
my ($self) = @_;
my $conn = Net::LDAP->new($self->{server}, onerror => 'die');
$conn->bind($self->{binddn}, password => $self->{bindpass});
return $conn;
}
sub _escape {
my ($self, $str) = @_;
$str =~ s|\\|\\\\|go;
$str =~ s|\(|\\\(|go;
$str =~ s|\)|\\\)|go;
return $str;
}
sub _filter_username {
my ($self, $uid) = @_;
return bless({and =>
[{equalityMatch => {attributeDesc => 'objectClass',
assertionValue => 'inetOrgPerson'}},
{equalityMatch => {attributeDesc => 'uid',
assertionValue => $uid}}
]}, 'Net::LDAP::Filter');
};
sub create {
my ($self, $uid) = @_;
my $conn = $self->_connect();
$uid = $self->_escape($uid);
my $data = $self->get($uid);
return "User already exists"
if ($data);
my $dn = sprintf "uid=%s,%s", $uid, $self->{userbase};
my $result = $conn->add($dn, attr => [
objectClass => ['inetOrgPerson'],
uid => $uid,
sn => 'just',
cn => 'created',
]);
return $result->error if ($result->code);
$conn->unbind;
return;
}
sub update {
my ($self, $uid, $attrs) = @_;
return "Attrs isn't HASH"
if (ref($attrs) ne 'HASH');
my $conn = $self->_connect();
$uid = $self->_escape($uid);
my $data = $self->get($uid);
return "No such user"
unless ($data);
my $dn = sprintf "uid=%s,%s", $uid, $self->{userbase};
foreach my $key (keys($attrs)) {
...
}
return;
}
sub delete {
my ($self, $uid) = @_;
my $conn = $self->_connect();
$uid = $self->_escape($uid);
my $dn = sprintf "uid=%s,%s", $uid, $self->{userbase};
my $result = $conn->delete($dn);
$conn->unbind;
return;
}
sub get {
my ($self, $uid) = @_;
my $conn = $self->_connect();
my $filter = $self->_filter_username($uid);
my $mesg = $conn->search(base => $self->{userbase}, scope => 'one',
deref => 'never', filter => $filter);
$conn->unbind;
return unless $mesg->count;
my $entry = $mesg->pop_entry();
my $data = {};
foreach my $attr ($entry->attributes) {
$data->{$attr} = $entry->get_value($attr);
}
delete $data->{userPassword};
return $data;
}
1;

58
lib/LDV/User.pm

@ -0,0 +1,58 @@
package LDV::User;
use strict;
use warnings;
use utf8;
use Mojo::Base 'Mojolicious::Controller';
use Net::LDAP;
use Net::LDAP::Util qw(ldap_error_name);
sub create { my ($self) = @_; $self->render(); }
sub login { my ($self) = @_; $self->render(); }
sub logout {
my ($self) = @_;
$self->session({useruid => undef});
$self->redirect_to('/user/login');
$self->rendered();
}
sub update {
my ($self) = @_;
my ($data, $login);
unless ($login = $self->session('useruid')) {
$self->redirect_to('/user/login');
return;
}
eval {
my ($ldap, $mesg);
$ldap = Net::LDAP->new($self->app->config->{server})
or die("$@");
$mesg = $ldap->bind($self->app->config->{binddn},
password => $self->app->config->{bindpass});
if ($mesg->code) {
$self->app->log->error($mesg->error);
die("Can't connect to server\n");
}
my $base = $self->app->config->{userbase};
my $attrs = [ @{$self->app->config->{defattrs}} ];
$mesg = $ldap->search(base => $base, scope => 'one', deref => 'never',
filter => '(&(uid=$login)(class=InetOrgPerson))',
attrs => $attrs);
die("User not found\n")
unless ($mesg->count);
my $entry = $mesg->pop_entry();
1;
} or do {
};
$self->stash({user_data => $data});
$self->render();
}
1;

12
lib/LDV/Zerobin.pm

@ -0,0 +1,12 @@
package LDV::Zerobin;
use strict;
use warnings;
use utf8;
use Mojo::Base 'Mojolicious::Controller';
sub view { my ($self) = @_; $self->render(); }
sub create { my ($self) = @_; $self->render(); }
1;

14
script/ldv

@ -0,0 +1,14 @@
#!/usr/bin/env perl
use Mojo::Base -strict;
use File::Basename 'dirname';
use File::Spec::Functions qw(catdir splitdir);
# Source directory has precedence
my @base = (splitdir(dirname(__FILE__)), '..');
my $lib = join('/', @base, 'lib');
-e catdir(@base, 't') ? unshift(@INC, $lib) : push(@INC, $lib);
# Start commands for application
require Mojolicious::Commands;
Mojolicious::Commands->start_app('LDV');

24
t/LDAP.t

@ -0,0 +1,24 @@
#!/usr/bin/perl
use strict;
use warnings;
use utf8;
use Test;
use LDV::LDAP;
use Data::Dumper;
BEGIN { plan test => 2 };
my $ldap = LDV::LDAP->new({
server => '127.0.0.1',
binddn => undef,
bindpass => undef,
userbase => undef,
});
print Dumper $ldap->get("ad_user");
#print Dumper $ldap->create("test20");
#print Dumper $ldap->get("test20");
#print Dumper $ldap->delete("test20");
print Dumper $ldap->get("test20");

7
t/basic.t

@ -0,0 +1,7 @@
use Mojo::Base -strict;
use Test::More tests => 3;
use Test::Mojo;
my $t = Test::Mojo->new('LDV');
$t->get_ok('/')->status_is(200)->content_like(qr/Mojolicious/i);

19
templates/includes/ie-hacks.html.ep

@ -0,0 +1,19 @@
<!--[if lte IE 8]>
<link rel="stylesheet" media="all" type="text/css" href="/wiki/lib/tpl/monobook/static/css/screen_iehacks.css" />
<![endif]-->
<!--[if lt IE 5.5000]>
<link rel="stylesheet" media="all" type="text/css" href="/wiki/lib/tpl/monobook/static/3rd/monobook/IE50Fixes.css" />
<![endif]-->
<!--[if IE 5.5000]>
<link rel="stylesheet" media="all" type="text/css" href="/wiki/lib/tpl/monobook/static/3rd/monobook/IE55Fixes.css" />
<![endif]-->
<!--[if IE 6]>
<link rel="stylesheet" media="all" type="text/css" href="/wiki/lib/tpl/monobook/static/3rd/monobook/IE60Fixes.css" />
<![endif]-->
<!--[if IE 7]>
<link rel="stylesheet" media="all" type="text/css" href="/wiki/lib/tpl/monobook/static/3rd/monobook/IE70Fixes.css" />
<![endif]-->
<!--[if lt IE 7]>
<script type="text/javascript" charset="utf-8" src="/wiki/lib/tpl/monobook/static/3rd/wikipedia/IEFixes.js"></script>
<meta http-equiv="imagetoolbar" content="no" />
<![endif]-->

79
templates/layouts/default.html.ep

@ -0,0 +1,79 @@
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<title><%= title %> - Linux во Владивостоке</title>
<meta name="generator" content="mojolicious"/>
<meta name="robots" content="noindex,nofollow"/>
<link rel="stylesheet" type="text/css" href="/wiki/lib/exe/css.php?t=monobook"/>
%= include 'includes/ie-hacks', format => 'html';
% foreach my $style (split /\s+/, stash('styles')) {
<%= stylesheet "/css/$style.css" %>
% }
% foreach my $script (split /\s+/, stash('scripts')) {
<%= javascript "/js/$script.js" %>
% }
</head>
<body class="mediawiki ns-0 ltr">
<div id="globalWrapper">
<div id="column-content">
<div id="content">
<a name="top" id="top"></a>
<a name="dokuwiki__top" id="dokuwiki__top"></a>
<div id="bodyContent">
<div class="dokuwiki">
<!-- start main content area -->
<%= content %>
<!-- end main content area -->
<div class="visualClear"></div>
</div>
</div>
</div>
</div>
<div id="column-one" class="noprint">
<div class="portlet" id="p-logo">
<a href="/wiki/index" style="background-image:url(/wiki/lib/tpl/monobook/user/logo.png);" accesskey="h" title="[ALT+H]"></a>
</div>
<div id="p-personal" class="portlet">
<div class="pBody">
<ul>
<li id="pt-login"><a href="/wiki/index?do=login" rel="nofollow">Войти</a></li>
</ul>
</div>
</div>
<div class="portlet" id="p-x-navigation">
<h5>Навигация</h5>
<div class="pBody">
<div class="dokuwiki">
<ul>
<li class="level1"><div class="li"> <span class="curid"><a href="/wiki/index" class="wikilink1" title="index">Главная</a></span></div> </li>
<li class="level1"><div class="li"> <a href="/wiki/articles/index" class="wikilink1" title="articles:index">Статьи</a></div> </li>
<li class="level1"><div class="li"> <a href="/wiki/projects/index" class="wikilink1" title="projects:index">Проекты</a></div> </li>
<li class="level1"><div class="li"> <a href="/wiki/events/index" class="wikilink1" title="events:index">События</a></div> </li>
<li class="level1"><div class="li"> <a href="/wiki/links" class="wikilink1" title="links">Ссылки</a></div> </li>
<li class="level1"><div class="li"> <a href="/wiki/wiki/index" class="wikilink1" title="wiki:index">Служебные</a></div> </li>
<li class="level1"><div class="li"> <a href="/forum/" class="wikilink1" title="/forum/">Форум</a></div> </li>
</ul>
</div>
</div>
</div>
</div> <!-- end of the left (by default at least) column -->
<div class="visualClear"></div>
<div id="footer" class="noprint">
<div id="footer-buttons">
</div>
<ul id="f-list">
<li id="usermod">
<br />
</li>
</ul>
</div>
</div> <!-- end of global wrap -->
</html>

41
templates/user/create.html.ep

@ -0,0 +1,41 @@
% layout 'default';
% title l('Create new user');
<h2><%= l('Create new user') %></h2>
% my @attrs = (method => 'POST');
%= form_for "/user/create" => (@attrs) => begin
<table>
<tr>
<td><%= l('Login') %></td>
<td><%= text_field 'login', required => 1, placeholder => 'petrov' %></td>
</tr>
<tr>
<td><%= l('Password') %></td>
<td><%= password_field 'pass', required => 1, placeholder => '******' %></td>
</tr>
<tr>
<td colspan='2'><hr/></td>
</tr>
<tr>
<td><%= l('Display name') %></td>
<td><%= text_field 'displayname', placeholder => l('John Doe') %></td>
</tr>
<tr>
<td><%= l('Email') %></td>
<td><%= text_field 'mail', type => 'email', placeholder => 'petrov@example.com' %></td>
</tr>
<tr>
<td colspan='2'><hr/></td>
</tr>
<tr>
<td colspan='2'><%= submit_button l('Create') %></td>
</tr>
</table>
% end
% my $result = (flash 'result') || '';
% if ($result) {
%= tag 'p' => $result;
% } else {
<p>Эта учётная запись будет действовать для всех сервисов *.linuxdv.org</p>
<p>Восстановления пароля пока не предусмотрено.</p>
<p>Обязательными являются только логин и пароль.</p>
% }

21
templates/user/login.html.ep

@ -0,0 +1,21 @@
% layout 'default';
% title l('Auth required');
<h2><%= l('Auth required') %></h2>
% my @attrs = (method => 'POST');
%= form_for "/user/update" => (@attrs) => begin
<table>
<tr>
<td><%= l('Login') %></td>
<td><%= text_field 'login', required => 1, placeholder => 'username' %></td>
</tr>
<tr>
<td><%= l('Password') %></td>
<td><%= password_field 'pass', required => 1, placeholder => '******' %></td>
</tr>
<tr><td colspan='2'><hr/></td></tr>
<tr>
<td colspan='2'><%= submit_button l('Log in') %></td>
</tr>
</table>
% end
<p>Чтобы поменять свои данные, необходимо представиться системе.</p>

41
templates/user/update.html.ep

@ -0,0 +1,41 @@
% layout 'default';
% title l('Update user');
<h2><%= l('Update user') %></h2>
% my @attrs = (method => 'POST', enctype => 'multipart/form-data');
%= form_for "/user/update" => (@attrs) => begin
<table>
<tr>
<td><%= l('Display name') %></td>
<td><%= text_field 'displayname', placeholder => l('John Doe') %></td>
</tr>
<tr>
<td><%= l('Email') %></td>
<td><%= text_field 'mail', type => 'email', placeholder => 'petrov@example.com' %></td>
</tr>
<tr>
<td><%= l('Organization') %></td>
<td><%= text_field 'org' %></td>
</tr>
<tr>
<td><%= l('Mobile') %></td>
<td><%= text_field 'mobile' %></td>
</tr>
<tr>
<td><%= l('Avatar') %></td>
<td><%= file_field 'photo' %></td>
</tr>
<tr><td colspan='2'><hr/></td></tr>
<tr>
<td><%= l('Current password') %></td>
<td><%= password_field 'pass', required => 1, placeholder => '******' %></td>
</tr>
<tr><td colspan='2'><hr/></td></tr>
<tr>
<td colspan='2'><%= submit_button l('Update') %></td>
</tr>
</table>
% end
% my $result = (flash 'result') || '';
% if ($result) {
%= tag 'p' => $result;
% }

28
templates/zerobin/create.html.ep

@ -0,0 +1,28 @@
% layout 'default';
% title 'Zerobin -- View';
% my %times = (day => 1, week => 7, month => 30, quarter => 90, year => 395);
% my @times = map { [l($_) => $times{$_}] } sort { $times{$a} <=> $times{$b} } keys(%times);
% stash 'styles' => 'zerobin';
% stash 'expire' => 30;
<h1>Создать запись</h1>
<div>
<div>
<%= form_for "/zerobin2/create" => (method => 'POST') => begin %>
<span class="zerobin cblock">
<%= submit_button l('Save') %>
</span>
<span class="zerobin cblock">
<%= tag 'label' => (for => 'expire') => l('Keep for') %>:
<%= select_field 'expire' => \@times %>
</span>
<span class="zerobin cblock">
<%= check_box 'syntax' => 0 %>
<%= tag 'label' => (for => 'syntax') => l('Syntax highlight') %>
</span>
<% end %>
</div>
<hr style="width: 50%"/>
<div>
<%= tag 'textarea' => (id => "paste", placeholder => l('Paste your text here')) => '' %>
</div>
</div>

29
templates/zerobin/view.html.ep

@ -0,0 +1,29 @@
% layout 'default';
% title 'Zerobin -- View';
% stash 'styles' => 'hljs zerobin';
% stash 'scripts' => 'jquery highlight zerobin';
<h1><%= l('View paste') %></h1>
<div>
<div>
<span class="zerobin cblock">
<%= link_to l('Create new') => "/zerobin2/create" %>
</span>
</div>
<br/>
<div>
<pre><code>
package LDV::Zerobin;
use strict;
use warnings;
use utf8;
use Mojo::Base 'Mojolicious::Controller';
sub view { my ($self) = @_; $self->render(); }
sub create { my ($self) = @_; $self->render(); }
1;
</code></pre>
</div>
</div>
Loading…
Cancel
Save