Revision 197
Date:
2012/03/15 18:28:50
Author:
ahitrov
Revision Log:
Session plugin
Files:
Legend:
Added
Removed
Modified
utf8/plugins/session/comps/contenido/session/dhandler
1
% if (@call)
2
% {
3
<& @call &>
4
% }
5
<%INIT>
6
7
use vars qw( $keeper $request $project );
8
$r->content_type('text/html');
9
10
my @path = split('/', $m->dhandler_arg());
11
my @call = ();
12
13
if (length($path[0]) < 1) { $path[0] = 'index.html' };
14
@call = (join('/',@path), %ARGS);
15
16
if (! $m->comp_exists($call[0]))
17
{
18
$m->clear_buffer();
19
$m->abort(404);
20
}
21
22
</%INIT>
utf8/plugins/session/comps/contenido/session/index.html
1
<& "/contenido/components/header.msn", style => 'index' &>
2
3
<div style="text-align:center; padding:180px 20px;">
4
% if ( $keeper->{session}->state->storage eq 'POSTGRES' ) {
5
<form action="./index.html" method="POST"
6
onsubmit="return confirm('Вы действительно собираетесь грохнуть все пользовательские сессии?\nЭто приведет к принудительному выходу пользователей из системы!')">
7
<input type="submit" class="input_btn" name="clear" value="Удалить все пользовательские сессии">
8
9
</form>
10
% }
11
</div>
12
13
</body>
14
</html>
15
<%args>
16
17
$clear => undef
18
19
</%args>
20
<%init>
21
22
if ( $clear && $keeper->{session}->state->storage eq 'POSTGRES' ) {
23
warn "delete from sessions\n";
24
my $req = $keeper->SQL->do('delete from sessions', {}) || $keeper->t_abort();
25
$m->redirect ('./');
26
}
27
28
</%init>
utf8/plugins/session/config.proto
1
#############################################################################
2
#
3
# Параметры данного шаблона необходимо ВРУЧНУЮ добавить в config.mk проекта
4
# и привести в соответствие с требованиями проекта
5
#
6
#############################################################################
7
8
PLUGINS += session
9
PROJECT_REQUIRED += Apache-Session
10
PROJECT_REQUIRED += JSON-XS
11
PROJECT_REQUIRED += P-WebFetcher
12
13
### Необязательный параметр, default = lsid
14
SESSION_COOKIE_NAME = lsid
15
REWRITE += SESSION_COOKIE_NAME
16
17
ifeq (${DEVELOPMENT}, YES)
18
19
SESSION_DOMAIN =
20
SESSION_STORAGE = POSTGRES
21
SESSION_LIFETIME = 24
22
SESSION_EXPIRES = +10d
23
24
else
25
26
SESSION_DOMAIN =
27
SESSION_STORAGE = POSTGRES
28
SESSION_LIFETIME = 24
29
SESSION_EXPIRES = +10d
30
31
endif
32
33
REWRITE += SESSION_STORAGE SESSION_DOMAIN SESSION_LIFETIME SESSION_EXPIRES
34
35
########################################################################
36
#
37
# SESSION_DOMAIN
38
# Домен, на котором отвечает проект. Можно не указывать
39
# SESSION_STORAGE
40
# Контейнер для хранения сессий. Варианты: POSTGRES и FILE
41
# SESSION_LIFETIME
42
# Время жизни сессий до автоочистки. Задается в часах, используется
43
# при включении в crontab проекта скрипта из services
44
# SESSION_EXPIRES
45
# Время жизни куки сессии. Задается в формате Apache
46
#
47
########################################################################
48
49
50
### AUTH::FaceBook
51
######################################
52
FACEBOOK_APP_ID =
53
FACEBOOK_APP_KEY =
54
FACEBOOK_APP_SECRET =
55
FACEBOOK_AUTHOTIZE_URL = https://graph.facebook.com/oauth/authorize
56
FACEBOOK_ACCESS_TOKEN_URL = https://graph.facebook.com/oauth/access_token
57
FACEBOOK_USER_INFO_URL = https://graph.facebook.com/me
58
FACEBOOK_REDIRECT_URL =
59
FACEBOOK_USER_POST_URL =
60
61
REWRITE += FACEBOOK_AUTHOTIZE_URL FACEBOOK_ACCESS_TOKEN_URL FACEBOOK_USER_INFO_URL
62
63
CONNECTION_TIMEOUT = 3
64
65
PROJECT_REQUIRED += Crypt-SSLeay
66
utf8/plugins/session/lib/session/Apache.pm
1
package session::Apache;
2
3
use strict;
4
use warnings 'all';
5
6
use session::State;
7
use Contenido::Globals;
8
9
10
sub child_init {
11
# встраиваем keeper плагина в keeper проекта
12
$keeper->{session} = session::Keeper->new($state->session);
13
}
14
15
sub request_init {
16
}
17
18
sub child_exit {
19
}
20
21
1;
utf8/plugins/session/lib/session/AUTH/FaceBook.pm
1
package session::AUTH::FaceBook;
2
3
use strict;
4
use warnings;
5
use LWP::UserAgent;
6
use JSON::XS;
7
use Data::Dumper;
8
use URI;
9
use URI::QueryParam;
10
use Encode;
11
use Contenido::Globals;
12
13
use vars qw($VERSION);
14
$VERSION = '4.1';
15
16
=for rem
17
facebook:
18
auto_create_user: 1
19
app_id: 122117614500563
20
app_key: 3da06301715b0efc5c873535c56c2c33
21
app_secret: 656bd1369486b902e9bf831a9a08132b
22
authorize_url: https://graph.facebook.com/oauth/authorize
23
access_token_url: https://graph.facebook.com/oauth/access_token
24
user_info_url: https://graph.facebook.com/me
25
user_post_url: ~
26
store:
27
class: "+Comments::Authentication::Store"
28
type: facebook
29
30
=cut
31
32
our $JSON = JSON::XS->new->utf8;
33
34
=for rem SCHEMA
35
36
$m->redirect ( $fb_connect->fb_authorize_url( redirect_uri => ... ) );
37
38
39
=cut
40
41
42
sub new {
43
my ($class, %config) = @_;
44
my $self = bless {}, $class;
45
for (qw(facebook_app_id facebook_app_key facebook_app_secret facebook_authorize_url facebook_access_token_url facebook_user_info_url)) {
46
$self->{$_} = $config{$_} || $state->{session}->{$_} || return undef;
47
}
48
$self->{timeout} = $state->{session}->{connection_timeout} || 3;
49
for (qw(facebook_user_post_url facebook_redirect_uri)) {
50
$self->{$_} = $config{$_} || $state->{session}->{$_};
51
}
52
return $self;
53
}
54
55
sub fb_authorize_url {
56
my $self = shift;
57
my (%args) = @_;
58
my $go = URI->new( $self->{facebook_authorize_url} );
59
warn Dumper($go);
60
$go->query_param( client_id => $self->{facebook_app_key} );
61
$go->query_param( scope => "publish_stream" );
62
$args{redirect_uri} ||= $self->{facebook_redirect_uri};
63
for ( keys %args ) {
64
$go->query_param( $_ => $args{$_} );
65
}
66
$keeper->{session}->store_value( facebook_redirect_url => $self->{facebook_redirect_uri} );
67
return $go;
68
}
69
70
sub authenticate {
71
my ( $self, %authinfo ) = @_;
72
warn "FB.authenticate" if $DEBUG;
73
# TODO: we need callback url
74
#warn "user_session=".dumper( $c->user_session )." ";
75
my $local_session = $session || $keeper->{session}->get_session;
76
my $redirect_uri = $local_session->{facebook_redirect_url};
77
78
my $access_token = $local_session->{facebook_access_token};
79
my $expires = $local_session->{facebook_expires};
80
if ($access_token and $expires > time) {
81
warn "Already have access_token" if $DEBUG;
82
} else {
83
undef $access_token;
84
}
85
my $code = $authinfo{'code'};
86
unless ( $code ) {
87
warn "Call to authenticate without code";
88
return undef;
89
}
90
my $ua = LWP::UserAgent->new;
91
$ua->timeout($self->{timeout});
92
unless ($access_token) {
93
my $req = URI->new( $self->{facebook_access_token_url});
94
$req->query_param( client_id => $self->{facebook_app_id} );
95
$req->query_param( redirect_uri => $redirect_uri );
96
$req->query_param( client_secret=> $self->{facebook_app_secret} );
97
$req->query_param( code => $code);
98
warn "Get $req";
99
my $res = $ua->get($req);
100
unless ($res->code == 200) {
101
warn "access_token request failed: ".$res->status_line;
102
return undef;
103
}
104
my %res = eval { URI->new("?".$res->content)->query_form };
105
warn Dumper(\%res);
106
unless ($access_token = $res{access_token}) {
107
warn "No access token in response: ".$res->content;
108
return undef;
109
}
110
$keeper->{session}->store_value( facebook_access_token => $access_token );
111
$local_session->{facebook_access_token} = $access_token;
112
if( my $expires = $res{expires} ) {
113
$local_session->{facebook_expires} = time + $expires;
114
$keeper->{session}->store_value( facebook_expires => $local_session->{facebook_expires} );
115
} else {
116
#$c->user_session->{'expires'} = time + 3600*24;
117
}
118
warn "FB: requested access token";
119
} else {
120
warn "FB: have access token";
121
}
122
123
my $req = URI->new( $self->{facebook_user_info_url} );
124
$req->query_param( access_token => $access_token );
125
126
warn "Fetching user $req";
127
my $res = $ua->get($req);
128
unless ($res->code == 200) {
129
warn "user request failed: ".$res->status_line;
130
return undef;
131
}
132
my $info;
133
unless ( $info = eval { JSON::XS->new->utf8->decode($res->content) } ) {
134
warn "user '".$res->content."' decode failed: $@";
135
return undef;
136
}
137
warn "Userhash = ".Dumper($info);
138
#warn "facebook: user=$info->{name} / $info->{id} / $info->{gender}";
139
140
$keeper->{session}->delete_key( 'facebook_redirect_url' );
141
delete $local_session->{facebook_redirect_url};
142
143
my @plugins = split (/[\ |\t]+/, $state->{plugins});
144
if ( grep { $_ eq 'users' } @plugins ) {
145
my $user = $keeper->{users}->get_profile( login => 'facebook:'.$info->{id} );
146
unless ( ref $user ) {
147
my $user_class = $state->{users}->profile_document_class;
148
$user = $user_class->new( $keeper );
149
$user->login( 'facebook:'.$info->{id} );
150
my $name = Encode::encode('utf-8', $info->{name});
151
$user->name( $name );
152
$user->status( 1 );
153
$user->type( 0 );
154
$user->login_method('facebook');
155
$user->country( $info->{locale} );
156
$user->email( undef );
157
$user->store;
158
}
159
my %data = (
160
id => $user->id,
161
name => $user->name,
162
login => $user->login,
163
status => $user->status,
164
type => $user->type,
165
ltime => time,
166
);
167
$keeper->{session}->store_value ( %data );
168
while ( my ( $key, $value ) = each %data ) {
169
$local_session->{$key} = $value;
170
}
171
}
172
return $local_session;
173
}
174
175
1;
utf8/plugins/session/lib/session/Init.pm
1
package session::Init;
2
3
use strict;
4
use warnings 'all';
5
6
use session::Apache;
7
use session::Keeper;
8
use session::AUTH::FaceBook;
9
10
# загрузка всех необходимых плагину классов
11
# session::SQL::SomeTable
12
# session::SomeClass
13
Contenido::Init::load_classes(qw(
14
));
15
16
sub init {
17
0;
18
}
19
20
1;
utf8/plugins/session/lib/session/Keeper.pm
1
package session::Keeper;
2
3
use strict;
4
use warnings 'all';
5
use base qw(Contenido::Keeper);
6
7
use Apache::Cookie;
8
use Apache::Session::File;
9
use Apache::Session::Postgres;
10
use Contenido::Globals;
11
use Data::Dumper;
12
13
14
sub logon {
15
my $self = shift;
16
my %opts = @_;
17
18
return if !($opts{login} || $opts{email}) && !$opts{passwd};
19
20
my $res;
21
my @plugins = split (/[\ |\t]+/, $state->{plugins});
22
if ( grep { $_ eq 'users' } @plugins ) {
23
#### Авторизация через плагин users
24
#########################################
25
$res = $keeper->{users}->login (
26
$opts{login} ? (login => $opts{login}) : (),
27
$opts{email} ? (email => lc($opts{email})) : (),
28
passwd => $opts{passwd},
29
);
30
return unless $res;
31
} else {
32
#### Авторизация иным способом
33
34
35
36
}
37
if ( ref $res ) {
38
my %data = (
39
id => $res->id,
40
name => $res->name,
41
email => $res->email,
42
login => $res->login,
43
status => $res->status,
44
ltime => time,
45
);
46
$self->store_value ( %data );
47
}
48
return $self->get_session();
49
}
50
51
52
sub logoff {
53
my $self = shift;
54
my %opts = @_;
55
56
my $sid = _get_session_id ();
57
my $session = _get_session_object ( $sid );
58
return unless ref $session;
59
60
my $session_id = $session->{_session_id};
61
if (!$sid || $sid ne $session_id) {
62
warn "LOGOFF: New or deprecated session. Old sid = '$sid', new sid = '$session_id'" if $DEBUG;
63
_store_session_id ($session_id)
64
} else {
65
my @clear = qw( id email login name nick type status ltime );
66
push @clear, @{ $opts{clear} } if exists $opts{clear} && ref $opts{clear} eq 'ARRAY' && @{ $opts{clear} };
67
foreach my $key ( @clear ) {
68
delete $session->{$key};
69
}
70
}
71
untie %$session;
72
return 1;
73
}
74
75
76
sub get_value {
77
78
my ($self, $name) = @_;
79
my $sid = _get_session_id ();
80
my $session = _get_session_object ( $sid );
81
return unless ref $session;
82
83
my $session_id = $session->{_session_id};
84
my $value = $session->{$name};
85
if (!$sid || $sid ne $session_id) {
86
warn "GET_VALUE: New or deprecated session. Old sid = '$sid', new sid = '$session_id'" if $DEBUG;
87
_store_session_id ($session_id);
88
}
89
untie %$session;
90
return $value;
91
}
92
93
94
sub store_value {
95
96
my ($self, %opts) = @_;
97
my $sid = _get_session_id ();
98
my $session = _get_session_object ( $sid );
99
return unless ref $session;
100
101
foreach my $key ( keys %opts ) {
102
$session->{$key} = $opts{$key};
103
}
104
105
my $session_id = $session->{_session_id};
106
if (!$sid || $sid ne $session_id) {
107
warn "STORE_VALUE: New or deprecated session. Old sid = '$sid', new sid = '$session_id'" if $DEBUG;
108
_store_session_id ($session_id);
109
}
110
untie %$session;
111
return 1;
112
}
113
114
115
sub delete_key {
116
117
my ($self, $key) = @_;
118
return unless $key;
119
120
my $sid = _get_session_id ();
121
my $session = _get_session_object ( $sid );
122
return unless ref $session;
123
124
my $session_id = $session->{_session_id};
125
if (!$sid || $sid ne $session_id) {
126
warn "DELETE_VALUE: New or deprecated session. Old sid = '$sid', new sid = '$session_id'" if $DEBUG;
127
_store_session_id ($session_id);
128
} else {
129
delete $session->{$key} if exists $session->{$key};
130
}
131
untie %$session;
132
return 1;
133
}
134
135
136
sub get_session {
137
138
my $self = shift;
139
140
my $sid = _get_session_id () || '';
141
my $session = _get_session_object ($sid);
142
return unless ref $session;
143
144
my $session_id = $session->{_session_id};
145
my %ret = %$session;
146
if (!$sid || $sid ne $session_id) {
147
warn "\nGET_SESSION: New or deprecated session. Old sid = '$sid', new sid = '$session_id'\n" if $DEBUG;
148
_store_session_id ($session_id);
149
}
150
untie %$session;
151
152
return \%ret;
153
}
154
155
156
## Внутренние функции
157
######################################################################################
158
sub _store_session_id {
159
160
my $sid = shift;
161
return unless $sid;
162
my $cookie = Apache::Cookie->new ($request->r(),
163
-domain => $state->{session}->domain,
164
-name => $state->{session}->cookie,
165
-expires=> $state->{session}->expires,
166
-value => $sid,
167
-path => '/',
168
);
169
$cookie->bake();
170
171
}
172
173
174
sub _get_session_id {
175
176
my %cookies = Apache::Cookie->fetch;
177
warn Dumper(\%cookies) if $DEBUG;
178
my $cookie = $cookies{$state->{session}->cookie};
179
180
# Вытаскиваем SID из куки
181
my $sid = $cookie->value() || '' if $cookie;
182
warn "\nSession_id = $sid\n" if $DEBUG;
183
184
return $sid;
185
}
186
187
188
sub _get_session_object {
189
190
my $sid = shift;
191
192
my %session;
193
my $now = time;
194
if ( $state->{session}->storage eq 'POSTGRES' ) {
195
eval {
196
tie %session, 'Apache::Session::Postgres', $sid, {
197
Handle => $keeper->SQL,
198
};
199
};
200
} else {
201
eval {
202
tie %session, 'Apache::Session::File', $sid, {
203
Directory => $state->session->session_dir,
204
};
205
};
206
}
207
if ($@) {
208
warn "Session data is not accessible: $@";
209
undef $sid;
210
} elsif ( $state->{session}->lifetime ) {
211
unless ( exists $session{_timestamp} ) {
212
$session{_timestamp} = $now;
213
} elsif ( ($now - $session{_timestamp}) > $state->{session}->lifetime ) {
214
undef $sid;
215
} elsif ( ($now - $session{_timestamp}) > $state->{session}->checkout ) {
216
$session{_timestamp} = $now;
217
}
218
}
219
unless ( $sid ) {
220
if ( $state->{session}->storage eq 'POSTGRES' ) {
221
eval {
222
tie %session, 'Apache::Session::Postgres', undef, {
223
Handle => $keeper->SQL,
224
};
225
};
226
} else {
227
eval {
228
tie %session, 'Apache::Session::File', undef, {
229
Directory => $state->session->session_dir,
230
};
231
};
232
}
233
$session{_timestamp} = $now;
234
}
235
236
return \%session;
237
}
238
239
240
sub _drop_session_object {
241
242
my (%session) = @_;
243
244
untie %session;
245
246
}
247
248
1;
utf8/plugins/session/lib/session/State.pm.proto
1
package session::State;
2
3
use strict;
4
use warnings 'all';
5
use vars qw($AUTOLOAD);
6
7
8
sub new {
9
my ($proto) = @_;
10
my $class = ref($proto) || $proto;
11
my $self = {};
12
bless $self, $class;
13
14
# зашитая конфигурация плагина
15
$self->{db_type} = 'none';
16
$self->{storage} = '@SESSION_STORAGE@' || 'FILE'; ## Значения: FILE POSTGRES MEMCACHED
17
$self->{session_dir} = '@SESSIONS@';
18
$self->{session_directory} = '@SESSIONS@';
19
20
$self->{domain} = '@SESSION_DOMAIN@';
21
$self->{cookie} = 'lsid';
22
$self->{expires} = '@SESSION_EXPIRES@' || '';
23
24
$self->{lifetime} = '@SESSION_LIFETIME@';
25
$self->{lifetime} *= 3600;
26
$self->{checkout} = $self->{lifetime} - int ($self->{lifetime} / 2);
27
28
$self->{db_keepalive} = 0;
29
$self->{db_host} = '';
30
$self->{db_name} = '';
31
$self->{db_user} = '';
32
$self->{db_password} = '';
33
$self->{db_port} = '';
34
35
$self->{data_directory} = '';
36
$self->{images_directory} = '';
37
$self->{binary_directory} = '';
38
$self->{preview} = '';
39
$self->{debug} = '';
40
$self->{store_method} = '';
41
$self->{cascade} = '';
42
$self->{memcached_enable} = '';
43
44
$self->{facebook_app_id} = '@FACEBOOK_APP_ID@';
45
$self->{facebook_app_key} = '@FACEBOOK_APP_KEY@';
46
$self->{facebook_app_secret} = '@FACEBOOK_APP_SECRET@';
47
$self->{facebook_authorize_url} = '@FACEBOOK_AUTHORIZE_URL@';
48
$self->{facebook_access_token_url} = '@FACEBOOK_ACCESS_TOKEN_URL@';
49
$self->{facebook_user_info_url} = '@FACEBOOK_USER_INFO_URL@';
50
$self->{facebook_redirect_uri} = '@FACEBOOK_REDIRECT_URL@';
51
$self->{facebook_user_post_url} = '@FACEBOOK_USER_POST_URL@';
52
53
$self->_init_();
54
$self;
55
}
56
57
sub info {
58
my $self = shift;
59
return unless ref $self;
60
61
for (sort keys %{$self->{attributes}}) {
62
my $la = length $_;
63
warn "\t$_".("\t" x (2-int($la/8))).": $self->{$_}\n";
64
}
65
}
66
67
sub _init_ {
68
my $self = shift;
69
70
# зашитая конфигурация плагина
71
$self->{attributes}->{$_} = 'SCALAR' for qw(
72
db_type
73
session_dir
74
session_directory
75
domain
76
cookie
77
expires
78
storage
79
lifetime
80
checkout
81
db_keepalive
82
db_host
83
db_port
84
db_name
85
db_user
86
db_password
87
data_directory images_directory binary_directory preview debug store_method cascade memcached_enable
88
);
89
}
90
91
sub AUTOLOAD {
92
my $self = shift;
93
my $attribute = $AUTOLOAD;
94
95
$attribute =~ s/.*:://;
96
return unless $attribute =~ /[^A-Z]/; # Отключаем методы типа DESTROY
97
98
if (!exists $self->{attributes}->{$attribute}) {
99
warn "Contenido Error (session::State): Вызов метода, для которого не существует обрабатываемого свойства: ->$attribute()\n";
100
return;
101
}
102
103
$self->{$attribute} = shift @_ if $#_>=0;
104
$self->{$attribute};
105
}
106
107
1;
utf8/plugins/session/sql/TOAST/session.sql
1
CREATE TABLE sessions (
2
id char(32) not null primary key,
3
dtime timestamp not null default now(),
4
a_session text
5
);
Небольшая справка по веткам
cnddist – контейнер, в котором хранятся все дистрибутивы всех библиотек и программных пакетов, которые использовались при построении различных версий Contenido. Если какой-то библиотеки в данном хранилище нет, инсталлятор сделает попытку "подтянуть" ее с веба (например, с CPAN). Если библиотека слишком старая, есть очень большая вероятность, что ее там уже нет. Поэтому мы храним весь хлам от всех сборок. Если какой-то дистрибутив вдруг отсутствует в cnddist - напишите нам, мы положим его туда.
koi8 – отмирающая ветка, чей код, выдача и все внутренние библиотеки заточены на кодировку KOI8-R. Вносятся только те дополнения, которые касаются внешнего вида и функционала админки, баги ядра, обязательные обновления портов и мелочи, которые легко скопипастить. В дальнейшем планируется полная остановка поддержки по данной ветке.
utf8 – актуальная ветка, заточенная под UTF-8.
Внутри каждой ветки: core – исходники ядра; install – скрипт установки инсталляции; plugins – плагины; samples – "готовые к употреблению" проекты, которые можно поставить, запустить и посмотреть, как они работают.