|
|
@@ -0,0 +1,506 @@
|
|
|
+--- /dev/null 2005-11-27 00:46:14 +0100
|
|
|
++++ ase.pm 2005-11-27 00:47:12 +0100
|
|
|
+@@ -0,0 +1,503 @@
|
|
|
++##
|
|
|
++## OSSP ase -- Affiliation Service Environment
|
|
|
++## Copyright (c) 2005 Ralf S. Engelschall <rse@engelschall.com>
|
|
|
++## Copyright (c) 2005 The OSSP Project <http://www.ossp.org/>
|
|
|
++##
|
|
|
++## This file is part of OSSP ase, a service environment for managing
|
|
|
++## affiliations which can be found at http://www.ossp.org/pkg/tool/ase/.
|
|
|
++##
|
|
|
++## This program is free software; you can redistribute it and/or modify
|
|
|
++## it under the terms of the GNU General Public License as published by
|
|
|
++## the Free Software Foundation; either version 2 of the License, or
|
|
|
++## (at your option) any later version.
|
|
|
++##
|
|
|
++## This program is distributed in the hope that it will be useful,
|
|
|
++## but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
|
++## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
|
|
++## General Public License for more details.
|
|
|
++##
|
|
|
++## You should have received a copy of the GNU General Public License
|
|
|
++## along with this program; if not, write to the Free Software
|
|
|
++## Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307,
|
|
|
++## USA, or contact Ralf S. Engelschall <rse@engelschall.com>.
|
|
|
++##
|
|
|
++## ase.pm: client API
|
|
|
++##
|
|
|
++
|
|
|
++package OSSP::ase::client;
|
|
|
++
|
|
|
++use 5.008;
|
|
|
++use strict;
|
|
|
++use warnings;
|
|
|
++use base 'Exporter';
|
|
|
++
|
|
|
++our $VERSION = do { my @v = ('0.0.1' =~ m/\d+/g); sprintf("%d.".("%02d"x$#v), @v); };
|
|
|
++
|
|
|
++our @EXPORT_OK = ();
|
|
|
++our @EXPORT = ();
|
|
|
++
|
|
|
++# names of valid ASE session attributes
|
|
|
++my @valid_attributes = qw(
|
|
|
++ session-id
|
|
|
++ session-valid
|
|
|
++ session-created
|
|
|
++ session-expires
|
|
|
++ canvas-url
|
|
|
++ canvas-mark-head
|
|
|
++ canvas-mark-body
|
|
|
++ client-address
|
|
|
++ client-login-uuid
|
|
|
++ client-login-name
|
|
|
++);
|
|
|
++
|
|
|
++# textual markers for canvas
|
|
|
++my $canvas_mark = {
|
|
|
++ head => "<!-- ASE: HEAD -->",
|
|
|
++ body => "<!-- ASE: BODY -->"
|
|
|
++};
|
|
|
++
|
|
|
++# lazy loading of modules
|
|
|
++sub _use ($$) {
|
|
|
++ my ($self, $name) = @_;
|
|
|
++ if (not defined($self->{-use}->{$name})) {
|
|
|
++ eval "require $name; import $name;";
|
|
|
++ $self->{-use}->{$name} = 1;
|
|
|
++ }
|
|
|
++ return;
|
|
|
++}
|
|
|
++
|
|
|
++# debugging: time identification
|
|
|
++sub _time () {
|
|
|
++ my ($sec, $min, $hour, $mday, $mon, $year, $wday, $yday, $isdst) = localtime(time());
|
|
|
++ return sprintf("%04d-%02d-%02d %02d:%02d:%02d", $year, $mon+1, $mday, $hour, $min, $sec);
|
|
|
++}
|
|
|
++
|
|
|
++# debugging: message formatting
|
|
|
++sub _debug ($$;@) {
|
|
|
++ my ($self, $fmt, @args) = @_;
|
|
|
++
|
|
|
++ return if (not defined($self->{-debug}));
|
|
|
++ if (defined($args[0])) {
|
|
|
++ $self->{-debug}->printf("%s $fmt\n", $self->_time(), @args);
|
|
|
++ }
|
|
|
++ else {
|
|
|
++ $self->{-debug}->printf("%s %s\n", $self->_time(), $fmt);
|
|
|
++ }
|
|
|
++ return;
|
|
|
++}
|
|
|
++
|
|
|
++# debugging: structure dumping
|
|
|
++sub _dump ($;@) {
|
|
|
++ my ($self, $prefix, @args) = @_;
|
|
|
++
|
|
|
++ return if (not defined($self->{-debug}));
|
|
|
++ $self->_use("Data::Dumper");
|
|
|
++ my $d = Data::Dumper->new([@args]);
|
|
|
++ $d->Purity(0);
|
|
|
++ $d->Indent(1);
|
|
|
++ $d->Terse(1);
|
|
|
++ $d->Pad(sprintf("%s %s| ", $self->_time()), $prefix);
|
|
|
++ $self->{-debug}->print($d->Dump());
|
|
|
++ return;
|
|
|
++}
|
|
|
++
|
|
|
++# object constructor
|
|
|
++sub new {
|
|
|
++ my $proto = shift;
|
|
|
++ my %args = @_;
|
|
|
++
|
|
|
++ # create new object
|
|
|
++ my $class = ref($proto) || $proto;
|
|
|
++ my $self = {};
|
|
|
++ bless ($self, $class);
|
|
|
++
|
|
|
++ # fill object with attributes
|
|
|
++ $self->{-server} = ($args{-server} || die "no OSSP ase server URL specified with \"-server\"");
|
|
|
++ $self->{-cgi} = ($args{-cgi} || new CGI);
|
|
|
++ $self->{-session} = ($args{-session} || new CGI::Session);
|
|
|
++ $self->{-mode} = ($args{-mode} || "mode");
|
|
|
++ $self->{-valid} = ($args{-valid} || 0);
|
|
|
++ $self->{-debug} = ($args{-debug} || undef);
|
|
|
++ $self->{-response} = "";
|
|
|
++ $self->{-error} = "";
|
|
|
++ $self->{-use} = {};
|
|
|
++
|
|
|
++ # optionally open debug logfile
|
|
|
++ if (defined($self->{-debug})) {
|
|
|
++ $self->_use("IO::File");
|
|
|
++ $self->{-debug} = IO::File->new(">>" . $self->{-debug})
|
|
|
++ or die "failed to open debug logfile: $!";
|
|
|
++ }
|
|
|
++
|
|
|
++ # return object
|
|
|
++ return $self;
|
|
|
++}
|
|
|
++
|
|
|
++# object destructor (explicit)
|
|
|
++sub destroy ($) {
|
|
|
++ my ($self) = @_;
|
|
|
++
|
|
|
++ # shutdown sub-objects
|
|
|
++ $self->{-session}->flush() if (defined($self->{-session}));
|
|
|
++ $self->{-debug}->close() if (defined($self->{-debug}));
|
|
|
++
|
|
|
++ # destroy sub-objects
|
|
|
++ delete $self->{-cgi};
|
|
|
++ delete $self->{-session};
|
|
|
++ delete $self->{-debug};
|
|
|
++
|
|
|
++ return;
|
|
|
++}
|
|
|
++
|
|
|
++# object destructor (implicit)
|
|
|
++sub DESTROY ($) {
|
|
|
++ my ($self) = @_;
|
|
|
++ $self->destroy();
|
|
|
++ return;
|
|
|
++}
|
|
|
++
|
|
|
++# run-time responsibility check
|
|
|
++sub responsible ($) {
|
|
|
++ my ($self) = @_;
|
|
|
++
|
|
|
++ my $action = $self->{-cgi}->url_param("ase-action") || "";
|
|
|
++ return $action =~ m/^(login|logout|info|comeback)$/;
|
|
|
++}
|
|
|
++
|
|
|
++# run-time action handler
|
|
|
++sub action ($;%) {
|
|
|
++ my ($self, %args) = @_;
|
|
|
++
|
|
|
++ # debugging
|
|
|
++ $self->_debug("action: client-sid=%s server-sid=%s",
|
|
|
++ $self->{-session}->id(), $self->attr("session-id") || "none");
|
|
|
++
|
|
|
++ my $action = $self->{-cgi}->url_param("ase-action") || "";
|
|
|
++ if ($action =~ m/^(login|logout|info)$/) {
|
|
|
++ #
|
|
|
++ # request ASE actions
|
|
|
++ #
|
|
|
++ my $mode_during = $self->{-cgi}->url_param("ase-mode-during")
|
|
|
++ or { $self->error("CGI parameter \"ase-mode-during\" missing or empty"), return 0 };
|
|
|
++ my $mode_after = $self->{-cgi}->url_param("ase-mode-after")
|
|
|
++ or { $self->error("CGI parameter \"ase-mode-after\" missing or empty"), return 0 };
|
|
|
++
|
|
|
++ # debugging
|
|
|
++ $self->_debug("action: action=%s mode-during=%s mode-after=%s", $action, $mode_during, $mode_after);
|
|
|
++
|
|
|
++ # remember mode after action
|
|
|
++ $self->attr("mode-after", $mode_after);
|
|
|
++
|
|
|
++ # determine URLs for canvas and return
|
|
|
++ my $canvas = sprintf("%s?%s=%s",
|
|
|
++ $self->{-cgi}->url(-full => 1), $self->{-mode}, $mode_during);
|
|
|
++ my $return = sprintf("%s?ase-action=comeback;ase-action-old=%s;ase-sid=%%s",
|
|
|
++ $self->{-cgi}->url(-full => 1), $action);
|
|
|
++
|
|
|
++ # determine URL for server request
|
|
|
++ $self->_use("URI::Escape");
|
|
|
++ my $url = sprintf(
|
|
|
++ "%s?mode=rpc;method=%s;" .
|
|
|
++ "return=%s;canvas=%s;canvas_mark_head=%s;canvas_mark_body=%s",
|
|
|
++ $self->{-server}, $action,
|
|
|
++ URI::Escape::uri_escape($return),
|
|
|
++ URI::Escape::uri_escape($canvas),
|
|
|
++ URI::Escape::uri_escape($canvas_mark->{"head"}),
|
|
|
++ URI::Escape::uri_escape($canvas_mark->{"body"}));
|
|
|
++
|
|
|
++ # redirect to server request URL
|
|
|
++ $self->response($self->{-cgi}->redirect(-url => $url, -status => 302));
|
|
|
++ return 1;
|
|
|
++ }
|
|
|
++ elsif ($action eq 'comeback') {
|
|
|
++ #
|
|
|
++ # respond to ASE actions
|
|
|
++ #
|
|
|
++ my $action_old = $self->{-cgi}->url_param("ase-action-old")
|
|
|
++ or { $self->error("CGI parameter \"ase-action-old\" missing or empty"), return 0 };
|
|
|
++ my $sid = $self->{-cgi}->url_param("ase-sid")
|
|
|
++ or { $self->error("CGI parameter \"ase-sid\" missing or empty"), return 0 };
|
|
|
++
|
|
|
++ $self->_debug("action: action=%s action-old=%s sid=%s", $action, $action_old, $sid);
|
|
|
++
|
|
|
++ # sanity check remote server session
|
|
|
++ $self->attr("session-id", $sid);
|
|
|
++ $self->validate(1);
|
|
|
++ if ($action_old eq "login" and $self->attr("session-valid") ne "yes") {
|
|
|
++ $self->error("server session still invalid after login: \"%s\"", $sid);
|
|
|
++ return 0;
|
|
|
++ }
|
|
|
++ elsif ($action_old eq "logout" and $self->attr("session-valid") ne "no") {
|
|
|
++ $self->error("server session still valid after logout: \"%s\"", $sid);
|
|
|
++ return 0;
|
|
|
++ }
|
|
|
++
|
|
|
++ # redirect to own following URL
|
|
|
++ my $mode_after = $self->attr("mode-after");
|
|
|
++ my $url = sprintf("%s?%s=%s", $self->{-cgi}->url(-relative => 1), $self->{-mode}, $mode_after);
|
|
|
++ $self->response($self->{-cgi}->redirect(-url => $url, -status => 302));
|
|
|
++ return 1;
|
|
|
++ }
|
|
|
++ else {
|
|
|
++ $self->error("unable to determine action");
|
|
|
++ return 0;
|
|
|
++ }
|
|
|
++}
|
|
|
++
|
|
|
++sub response ($;$) {
|
|
|
++ my ($self, $response) = @_;
|
|
|
++
|
|
|
++ my $rv = $self->{-response};
|
|
|
++ if (not $rv and $self->error()) {
|
|
|
++ $rv = $self->{-cgi}->header(
|
|
|
++ -status => "500 Internal Server Error",
|
|
|
++ -type => "text/plain",
|
|
|
++ -expires => "+0s",
|
|
|
++ ) . "ASE ERROR: " . $self->error() . "\n";
|
|
|
++ }
|
|
|
++ $self->{-response} = $response if (@_ == 2);
|
|
|
++ return $rv;
|
|
|
++}
|
|
|
++
|
|
|
++sub error ($;$@) {
|
|
|
++ my ($self, $fmt, @args) = @_;
|
|
|
++
|
|
|
++ my $rv = $self->{-error};
|
|
|
++ $self->{-error} = (@_ >= 3 ? sprintf($fmt, @args) : sprintf("%s", $fmt)) if (@_ >= 2);
|
|
|
++ return $rv;
|
|
|
++}
|
|
|
++
|
|
|
++# session validation
|
|
|
++sub validate ($) {
|
|
|
++ my ($self, $forced) = @_;
|
|
|
++
|
|
|
++ # make sure there is a session to be validated
|
|
|
++ my $sid = $self->attr("session-id");
|
|
|
++ return if (not defined($sid));
|
|
|
++
|
|
|
++ # debugging
|
|
|
++ $self->_debug("METHOD: validate: forced=%s sid=%s", $forced ? "yes" : "no", $sid);
|
|
|
++
|
|
|
++ # short-circuit if still no (re-)validation is necessary
|
|
|
++ my $valid_since = $self->attr("session-valid-since");
|
|
|
++ return if ( not $forced
|
|
|
++ and defined($valid_since)
|
|
|
++ and ( $self->{-valid} == 0
|
|
|
++ or ($valid_since + $self->{-valid}) > time()));
|
|
|
++
|
|
|
++ # clear all remembered session attributes
|
|
|
++ foreach my $key (@valid_attributes) {
|
|
|
++ $self->attr($key, undef);
|
|
|
++ }
|
|
|
++
|
|
|
++ # query server for session information
|
|
|
++ $self->_use("IO::Socket::INET");
|
|
|
++ my $server = $self->{-server};
|
|
|
++ my ($host, $port, $path) = ($server =~ m|^http://([^:/]+)((?::\d+)?)(.*)$|) or die;
|
|
|
++ $port ||= 80;
|
|
|
++ $port =~ s|^:||;
|
|
|
++ $path .= "?mode=rpc;method=info;sid=$sid";
|
|
|
++ my $sock = IO::Socket::INET->new (
|
|
|
++ PeerAddr => $host,
|
|
|
++ PeerPort => $port,
|
|
|
++ Proto => "tcp",
|
|
|
++ Timeout => 10
|
|
|
++ ) or die "failed to connect to $host:$port: $@";
|
|
|
++ $sock->autoflush(1);
|
|
|
++ $sock->printf(
|
|
|
++ "GET $path HTTP/1.0\n" .
|
|
|
++ "Host: $host:$port\n" .
|
|
|
++ "\n"
|
|
|
++ );
|
|
|
++ my $response = '';
|
|
|
++ $response .= $_ while (<$sock>);
|
|
|
++ $sock->close();
|
|
|
++ $self->_debug("METHOD: validate: response from %s", $server);
|
|
|
++
|
|
|
++ # parse session information response
|
|
|
++ my $attribute = {};
|
|
|
++ $response =~ s|^HTTP/1.[01x]\s+200\s+.+?\r?\n\r?\n||s;
|
|
|
++ foreach my $key (@valid_attributes) {
|
|
|
++ $attribute->{$key} = "";
|
|
|
++ $response =~ s|${key}:[ \t]+([^\r\n]+)\r?\n|$attribute->{$key} = $1, ''|sei;
|
|
|
++ }
|
|
|
++
|
|
|
++ # check validatity of session
|
|
|
++ my $expires = ($attribute->{"session-expires"} || 0) - time();
|
|
|
++ if (not ( $attribute->{"session-valid"} eq "yes"
|
|
|
++ and $attribute->{"client-login-uuid"} ne ""
|
|
|
++ and $attribute->{"client-login-name"} ne ""
|
|
|
++ and $expires > 0 )) {
|
|
|
++ $attribute->{"session-valid"} = "no";
|
|
|
++ $attribute->{"session-expires"} = time()+1;
|
|
|
++ $expires = 1;
|
|
|
++ }
|
|
|
++
|
|
|
++ # take over session attributes
|
|
|
++ foreach my $key (@valid_attributes) {
|
|
|
++ $self->attr($key, $attribute->{$key}, sprintf("+%ds", $expires));
|
|
|
++ }
|
|
|
++
|
|
|
++ # remember time of this validation
|
|
|
++ $self->attr("session-valid-since", time());
|
|
|
++ $self->_dump("validate: ", $self->{-session});
|
|
|
++ return;
|
|
|
++}
|
|
|
++
|
|
|
++# self-referencing URL generator
|
|
|
++sub url ($%) {
|
|
|
++ my ($self, %args) = @_;
|
|
|
++
|
|
|
++ # create self-referencing URL
|
|
|
++ my $base = $self->{-cgi}->url(-relative => 1);
|
|
|
++ $base = '.' if ($base eq '');
|
|
|
++ my $mode = $self->{-cgi}->url_param($self->{-mode})
|
|
|
++ || $self->{-cgi}->param($self->{-mode})
|
|
|
++ || "";
|
|
|
++ my $url = sprintf(
|
|
|
++ "%s?ase-action=%s;ase-mode-during=%s;ase-mode-after=%s",
|
|
|
++ $base, $args{-action},
|
|
|
++ $args{-mode_during} || $mode,
|
|
|
++ $args{-mode_after} || $mode
|
|
|
++ );
|
|
|
++
|
|
|
++ return $url;
|
|
|
++}
|
|
|
++
|
|
|
++# return arbitrary ASE session attributes
|
|
|
++sub attr ($$;$$) {
|
|
|
++ my ($self, $name, $value, $expire) = @_;
|
|
|
++
|
|
|
++ my $value_old = $self->{-session}->param("ase-$name");
|
|
|
++ if (@_ >= 3) {
|
|
|
++ if (defined($value)) {
|
|
|
++ $self->{-session}->param("ase-$name", $value);
|
|
|
++ if (defined($expire)) {
|
|
|
++ $self->{-session}->expire("ase-$name", $expire);
|
|
|
++ }
|
|
|
++ }
|
|
|
++ else {
|
|
|
++ $self->{-session}->clear("ase-$name");
|
|
|
++ }
|
|
|
++ }
|
|
|
++ return $value_old;
|
|
|
++}
|
|
|
++
|
|
|
++# return current login
|
|
|
++sub login ($) {
|
|
|
++ my ($self) = @_;
|
|
|
++
|
|
|
++ return (($self->attr("session-valid") || "no") eq "yes");
|
|
|
++}
|
|
|
++
|
|
|
++# return ASE canvas marker for head and body
|
|
|
++sub canvas ($%) {
|
|
|
++ my ($self, %args) = @_;
|
|
|
++
|
|
|
++ return ( $args{-part} eq 'head'
|
|
|
++ ? $canvas_mark->{"head"}
|
|
|
++ : $canvas_mark->{"body"});
|
|
|
++}
|
|
|
++
|
|
|
++1;
|
|
|
++
|
|
|
++__END__
|
|
|
++
|
|
|
++=pod
|
|
|
++
|
|
|
++=head1 NAME
|
|
|
++
|
|
|
++OSSP::ase::client -- OSSP ase Client API
|
|
|
++
|
|
|
++=head1 DESCRIPTION
|
|
|
++
|
|
|
++B<OSSP::ase::client> is the client Perl API of B<OSSP ase>.
|
|
|
++It allows an arbitrary CGI written in Perl to leverage from
|
|
|
++B<OSSP ase> authentication.
|
|
|
++
|
|
|
++=head1 APPLICATION PROGRAMMING INTERFACE (API)
|
|
|
++
|
|
|
++The following API methods are provided:
|
|
|
++
|
|
|
++=over 4
|
|
|
++
|
|
|
++=item C<my $ase = >B<new OSSP::ase::client>C<(>I<options>C<);>
|
|
|
++
|
|
|
++This creates a new B<OSSP ase> client object.
|
|
|
++The available I<options> are:
|
|
|
++
|
|
|
++=over 4
|
|
|
++
|
|
|
++=item B<-server> (default: I<none>)
|
|
|
++
|
|
|
++Mandatory URL of the B<OSSP ase> server CGI.
|
|
|
++Usually something like C<http://ase.example.com/ase.cgi>".
|
|
|
++
|
|
|
++=item B<-cgi> (default: C<new CGI>)
|
|
|
++
|
|
|
++Optional but strongly recommended reference to a B<CGI> query object.
|
|
|
++
|
|
|
++=item B<-session> (default: C<new CGI::Session>)
|
|
|
++
|
|
|
++Optional but strongly recommended reference to a B<CGI::Session> session
|
|
|
++handling object.
|
|
|
++
|
|
|
++=item B<-mode> (default: C<"mode">)
|
|
|
++
|
|
|
++Optional name of B<CGI> parameter holding the run-time mode
|
|
|
++dispatching information, i.e., the parameter your application
|
|
|
++uses to decide which application screen/page to display.
|
|
|
++
|
|
|
++=item B<-valid> (default: C<0>)
|
|
|
++
|
|
|
++Optional number of seconds a B<OSSP ase> server session information is
|
|
|
++valid before it is forced to be revalidated. A value of C<0> indicates
|
|
|
++that no revalidation is enforced at all. Nevertheless the B<OSSP ase>
|
|
|
++server session information is automatically expiring after the time
|
|
|
++the server indicated. The revalidated is intended for intermediate
|
|
|
++revalidation.
|
|
|
++
|
|
|
++=back
|
|
|
++
|
|
|
++=item C<$ase-E<gt>destroy();>
|
|
|
++
|
|
|
++=item C<undef $ase;>
|
|
|
++
|
|
|
++This destroys the B<OSSP ase> client object.
|
|
|
++
|
|
|
++=item C<$ase-E<gt>responsible();>
|
|
|
++
|
|
|
++FIXME
|
|
|
++
|
|
|
++=item C<$ase-E<gt>action();>
|
|
|
++
|
|
|
++FIXME
|
|
|
++
|
|
|
++=item C<$ase-E<gt>error();>
|
|
|
++
|
|
|
++FIXME
|
|
|
++
|
|
|
++=item C<$ase-E<gt>response();>
|
|
|
++
|
|
|
++FIXME
|
|
|
++
|
|
|
++=item C<$ase-E<gt>validate($forced);>
|
|
|
++
|
|
|
++FIXME
|
|
|
++
|
|
|
++=item C<$ase-E<gt>url();>
|
|
|
++
|
|
|
++FIXME
|
|
|
++
|
|
|
++=item C<$ase-E<gt>attr($name>[C<, $value>[C<, $expire>]]C<);>
|
|
|
++
|
|
|
++FIXME
|
|
|
++
|
|
|
++=item C<$ase-E<gt>canvas();>
|
|
|
++
|
|
|
++FIXME
|
|
|
++
|
|
|
++=back
|
|
|
++
|
|
|
++=cut
|
|
|
++
|