From 761496af2ab4d4067d20d45ebd911b4b583e90b8 Mon Sep 17 00:00:00 2001 From: "Alex \"AdUser\" Z" Date: Tue, 28 Oct 2014 13:01:08 +1000 Subject: [PATCH] * initial --- conf/ldv.conf.sample | 24 ++++++ lib/LDV.pm | 32 ++++++++ lib/LDV/Actions.pm | 75 ++++++++++++++++++ lib/LDV/I18N/ru.pm | 40 ++++++++++ lib/LDV/LDAP.pm | 118 ++++++++++++++++++++++++++++ lib/LDV/User.pm | 58 ++++++++++++++ lib/LDV/Zerobin.pm | 12 +++ script/ldv | 14 ++++ t/LDAP.t | 24 ++++++ t/basic.t | 7 ++ templates/includes/ie-hacks.html.ep | 19 +++++ templates/layouts/default.html.ep | 79 +++++++++++++++++++ templates/user/create.html.ep | 41 ++++++++++ templates/user/login.html.ep | 21 +++++ templates/user/update.html.ep | 41 ++++++++++ templates/zerobin/create.html.ep | 28 +++++++ templates/zerobin/view.html.ep | 29 +++++++ 17 files changed, 662 insertions(+) create mode 100644 conf/ldv.conf.sample create mode 100644 lib/LDV.pm create mode 100644 lib/LDV/Actions.pm create mode 100644 lib/LDV/I18N/ru.pm create mode 100644 lib/LDV/LDAP.pm create mode 100644 lib/LDV/User.pm create mode 100644 lib/LDV/Zerobin.pm create mode 100755 script/ldv create mode 100644 t/LDAP.t create mode 100644 t/basic.t create mode 100644 templates/includes/ie-hacks.html.ep create mode 100644 templates/layouts/default.html.ep create mode 100644 templates/user/create.html.ep create mode 100644 templates/user/login.html.ep create mode 100644 templates/user/update.html.ep create mode 100644 templates/zerobin/create.html.ep create mode 100644 templates/zerobin/view.html.ep diff --git a/conf/ldv.conf.sample b/conf/ldv.conf.sample new file mode 100644 index 0000000..2224a55 --- /dev/null +++ b/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, + } +} diff --git a/lib/LDV.pm b/lib/LDV.pm new file mode 100644 index 0000000..5131b42 --- /dev/null +++ b/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; diff --git a/lib/LDV/Actions.pm b/lib/LDV/Actions.pm new file mode 100644 index 0000000..bf9d51e --- /dev/null +++ b/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; diff --git a/lib/LDV/I18N/ru.pm b/lib/LDV/I18N/ru.pm new file mode 100644 index 0000000..266a1ec --- /dev/null +++ b/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; diff --git a/lib/LDV/LDAP.pm b/lib/LDV/LDAP.pm new file mode 100644 index 0000000..87411af --- /dev/null +++ b/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; diff --git a/lib/LDV/User.pm b/lib/LDV/User.pm new file mode 100644 index 0000000..870039e --- /dev/null +++ b/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; diff --git a/lib/LDV/Zerobin.pm b/lib/LDV/Zerobin.pm new file mode 100644 index 0000000..0030b37 --- /dev/null +++ b/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; diff --git a/script/ldv b/script/ldv new file mode 100755 index 0000000..f2d5c1e --- /dev/null +++ b/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'); diff --git a/t/LDAP.t b/t/LDAP.t new file mode 100644 index 0000000..9710197 --- /dev/null +++ b/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"); diff --git a/t/basic.t b/t/basic.t new file mode 100644 index 0000000..59ae92a --- /dev/null +++ b/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); diff --git a/templates/includes/ie-hacks.html.ep b/templates/includes/ie-hacks.html.ep new file mode 100644 index 0000000..6305854 --- /dev/null +++ b/templates/includes/ie-hacks.html.ep @@ -0,0 +1,19 @@ + + + + + + diff --git a/templates/layouts/default.html.ep b/templates/layouts/default.html.ep new file mode 100644 index 0000000..fa91755 --- /dev/null +++ b/templates/layouts/default.html.ep @@ -0,0 +1,79 @@ + + + + + <%= title %> - Linux во Владивостоке + + + +%= 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" %> +% } + + +
+ +
+
+ + +
+
+ + <%= content %> + +
+
+
+
+
+ +
+ + +
+
+ +
+
+ + +
+ +
+ + + +
+ diff --git a/templates/user/create.html.ep b/templates/user/create.html.ep new file mode 100644 index 0000000..dce4e29 --- /dev/null +++ b/templates/user/create.html.ep @@ -0,0 +1,41 @@ +% layout 'default'; +% title l('Create new user'); +

<%= l('Create new user') %>

+% my @attrs = (method => 'POST'); +%= form_for "/user/create" => (@attrs) => begin + + + + + + + + + + + + + + + + + + + + + + + + + + +
<%= l('Login') %><%= text_field 'login', required => 1, placeholder => 'petrov' %>
<%= l('Password') %><%= password_field 'pass', required => 1, placeholder => '******' %>

<%= l('Display name') %><%= text_field 'displayname', placeholder => l('John Doe') %>
<%= l('Email') %><%= text_field 'mail', type => 'email', placeholder => 'petrov@example.com' %>

<%= submit_button l('Create') %>
+% end +% my $result = (flash 'result') || ''; +% if ($result) { +%= tag 'p' => $result; +% } else { +

Эта учётная запись будет действовать для всех сервисов *.linuxdv.org

+

Восстановления пароля пока не предусмотрено.

+

Обязательными являются только логин и пароль.

+% } diff --git a/templates/user/login.html.ep b/templates/user/login.html.ep new file mode 100644 index 0000000..f7153c8 --- /dev/null +++ b/templates/user/login.html.ep @@ -0,0 +1,21 @@ +% layout 'default'; +% title l('Auth required'); +

<%= l('Auth required') %>

+% my @attrs = (method => 'POST'); +%= form_for "/user/update" => (@attrs) => begin + + + + + + + + + + + + + +
<%= l('Login') %><%= text_field 'login', required => 1, placeholder => 'username' %>
<%= l('Password') %><%= password_field 'pass', required => 1, placeholder => '******' %>

<%= submit_button l('Log in') %>
+% end +

Чтобы поменять свои данные, необходимо представиться системе.

diff --git a/templates/user/update.html.ep b/templates/user/update.html.ep new file mode 100644 index 0000000..c95e118 --- /dev/null +++ b/templates/user/update.html.ep @@ -0,0 +1,41 @@ +% layout 'default'; +% title l('Update user'); +

<%= l('Update user') %>

+% my @attrs = (method => 'POST', enctype => 'multipart/form-data'); +%= form_for "/user/update" => (@attrs) => begin + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
<%= l('Display name') %><%= text_field 'displayname', placeholder => l('John Doe') %>
<%= l('Email') %><%= text_field 'mail', type => 'email', placeholder => 'petrov@example.com' %>
<%= l('Organization') %><%= text_field 'org' %>
<%= l('Mobile') %><%= text_field 'mobile' %>
<%= l('Avatar') %><%= file_field 'photo' %>

<%= l('Current password') %><%= password_field 'pass', required => 1, placeholder => '******' %>

<%= submit_button l('Update') %>
+% end +% my $result = (flash 'result') || ''; +% if ($result) { +%= tag 'p' => $result; +% } diff --git a/templates/zerobin/create.html.ep b/templates/zerobin/create.html.ep new file mode 100644 index 0000000..c755975 --- /dev/null +++ b/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; +

Создать запись

+
+
+ <%= form_for "/zerobin2/create" => (method => 'POST') => begin %> + + <%= submit_button l('Save') %> + + + <%= tag 'label' => (for => 'expire') => l('Keep for') %>: + <%= select_field 'expire' => \@times %> + + + <%= check_box 'syntax' => 0 %> + <%= tag 'label' => (for => 'syntax') => l('Syntax highlight') %> + + <% end %> +
+
+
+ <%= tag 'textarea' => (id => "paste", placeholder => l('Paste your text here')) => '' %> +
+
diff --git a/templates/zerobin/view.html.ep b/templates/zerobin/view.html.ep new file mode 100644 index 0000000..fc9d713 --- /dev/null +++ b/templates/zerobin/view.html.ep @@ -0,0 +1,29 @@ +% layout 'default'; +% title 'Zerobin -- View'; +% stash 'styles' => 'hljs zerobin'; +% stash 'scripts' => 'jquery highlight zerobin'; +

<%= l('View paste') %>

+
+
+ + <%= link_to l('Create new') => "/zerobin2/create" %> + +
+
+
+

+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;
+    
+
+