Apache-Wyrd
view release on metacpan or search on metacpan
Wyrd/Services/Auth.pm view on Meta::CPAN
#We have determined at this point that the user has no prior authorization, but that
#cookies are enabled and they could be authorized.
#require an SSL login server if this is an insecure port (currently always).
#in future, 1 will be replaced with a test for SSL encryption.
if (($ENV{'HTTPS'} ne 'on') or $force_login_server) {
#Get an encryption key and a ticket number
my ($key, $ticket) = $self->generate_ticket;
#Send that pair to the Login Server
$key_url = 'https://' . $req->hostname . $key_url unless ($key_url =~ /^https?:\/\//i);
if ($key_url =~ /^https:\/\//i) {
eval('use IO::Socket::SSL');
die "LWP::UserAgent needs to support SSL to use a login server over https. Install IO::Socket::SSL and make sure it works."
if ($@);
}
my $ua = LWP::UserAgent->new;
$ua->timeout(60);
my $response = $ua->request(POST $key_url,
[
key => $key,
ticket => $ticket
]
);
my $status = $response->status_line;
#If the key can't be saved on the login server, send regrets and close
if ($status !~ /200|OK/) {
if ($status =~ /Invalid argument/i) {
$debug && warn ("You may need to Update IO::Socket::SSL");
} else {
$debug && warn ("Login Server status was $status");
}
my $failed_url = $req->dir_config('LSDownURL');
$failed_url = $scheme . '://' . $req->hostname . $port . $failed_url unless ($failed_url =~ /^http/i);
if ($failed_url) {
$req->custom_response(REDIRECT, $failed_url);
return REDIRECT;
} else {
return HTTP_SERVICE_UNAVAILABLE;
}
#Send the encrypted data as a lookup key to the login form to add
#to its hidden fields. If a challenge failed earlier in the script
#and ReturnError is defined, use it.
} else {
my $use_error = $req->dir_config('ReturnError');
my $login_url = $req->dir_config('LoginFormURL');
$login_url = $scheme . '://' . $req->hostname . $port . $login_url unless ($login_url =~ /^http/i);
my $ls_url = $req->dir_config('LSLoginURL');
$ls_url = $scheme . '://' . $req->hostname . $port . $ls_url unless ($ls_url =~ /^http/i);
if ($login_url) {
my $uri = $req->uri;
$uri = Apache::URI->parse($uri);
my $query_string = $uri->query;
$query_string =~ s/\&?check_cookie=yes\&?//;
$query_string =~ s/challenge=[0123456789abcdefABCDEF:]+\&?//g;
$query_string = '?' . $query_string if ($query_string);
my $on_success = Apache::Util::escape_uri(encode_base64($scheme . '://' . $req->hostname . $port . $req->uri . $query_string));
my $redirect = $login_url .
'?ls=' . $ls_url .
'&ticket=' . $ticket .
'&on_success=' . $on_success .
'&use_error=' . $use_error .
($challenge_failed ? '&'. $use_error . '=' . $challenge_failed : '');
$debug && warn('Need a login, with redirect going to ' . $redirect);
$req->custom_response(REDIRECT, $redirect);
return REDIRECT;
} else {
die "Must define LoginFormURL in Apache Config to use Apache::Wyrd::Services::Auth";
}
}
#Since we are using SSL, we can accept login information as normal CGI params.
} else {
my $username = $apr->param('username');
my $password = $apr->param('password');
my $login_failed = '';
if ($username) {
my $user = $self->initialize({username => $username, password => $password});
if ($user->login_ok) {
$self->authorize_user($req, $user);
my $uri = $req->uri;
$uri = Apache::URI->parse($uri);
my $redirect = $scheme . '://' . $req->hostname . $port . $req->uri . '?check_cookie=yes';
$debug && warn('Setting a cookie, with redirect going to ' . $redirect);
$req->custom_response(REDIRECT, $redirect);
return REDIRECT;
}
$login_failed = 'Login failed. Please check your username and password.';
$debug && warn('Login failed.');
} else {
$debug && warn('Login was not provided.');
}
my $use_error = $req->dir_config('ReturnError');
my $login_url = $req->dir_config('LoginFormURL');
$login_url = $scheme . '://' . $req->hostname . $port . $login_url unless ($login_url =~ /^http/i);
my $ls_url = $scheme . '://' . $req->hostname . $port . $req->uri;
if ($login_url) {
my $uri = $req->uri;
$uri = Apache::URI->parse($uri);
my $on_success = Apache::Util::escape_uri(encode_base64($scheme . '://' . $req->hostname . $port . $req->uri));
my $redirect = $login_url .
'?ls=' . $ls_url .
'&on_success=' . $on_success .
'&use_error=' . $use_error.
($login_failed ? '&'. $use_error . '=' . $login_failed : '');
$debug && warn('Need a login, with redirect going to ' . $redirect);
$req->custom_response(REDIRECT, $redirect);
return REDIRECT;
} else {
die "Must define LoginFormURL in Apache Config to use Apache::Wyrd::Services::Auth";
}
}
}
sub revive {
my ($self, $user_info) = @_;
my $user = undef;
my $user_object = $self->{'user_object'};
my $debug = $self->{'debug'};
eval "use $user_object";
$debug && $@ && die("$user_object failed to be initialized: $@");
#TO DO: place this into a safe of some sort
eval('$user = ' . $user_object . '->revive($user_info)');
return $user;
}
sub initialize {
my ($self, $init) = @_;
my $user = undef;
my $username=$init->{'username'};
my $password=$init->{'password'};
my $user_object = $self->{'user_object'};
eval "use $user_object;";
eval('$user = ' . $user_object . '->new({username => $username, password => $password})');
die $@ if ($@);
return $user;
}
sub generate_ticket {
my ($self) = @_;
my $debug = $self->{'debug'};
my $ticketfile = $self->{'ticketfile'};
# 1) Generate a random 56-byte key. NB: values are 1-255, not 0-255 as it will be stored in A DB file, so null byte terminates string in C. Avoid it.
my $key = '';
for (my $i=0; $i<56; $i++) {
$key .= chr(int(rand(255)) + 1);
}
# 2) Make a ticket serial number by using sha256
my $ticket = sha256_hex($key);
$key = Apache::Util::escape_uri($key);
$debug && warn ("Storing key under ID $ticket");
my $pad = Apache::Wyrd::Services::TicketPad->new($ticketfile);
$pad->add_ticket($ticket, $key);
return ($key, $ticket);
}
sub decrypt_challenge {
my ($self, $challenge) = @_;
my $debug = $self->{'debug'};
my $ticketfile = $self->{'ticketfile'};
#separate the ticket from the data
my ($ticket, $data) = split ':', $challenge;
#find the key for decrypting the data;
$debug && warn('finding ' . $ticket);
my $pad = Apache::Wyrd::Services::TicketPad->new($ticketfile);
my $key = $pad->find($ticket);
$debug && warn "found key $key";
$key = Apache::Util::unescape_uri($key);
my $cr = Apache::Wyrd::Services::CodeRing->new({key => $key});
my ($username, $password) = split ("\t", ${$cr->decrypt(\$data)});
return ($username, $password);
}
sub authorize_user {
my ($self, $req, $user) = @_;
my $debug = $self->{'debug'};
my $cr = Apache::Wyrd::Services::CodeRing->new;
my $auth_path = $req->dir_config('AuthPath');
$debug && warn ("User has been authenticated. Authorizing User and creating Cookie");
my $user_info = $user->store;
$debug && warn ("User info is:\n$user_info");
$req->notes->add('User' => $user_info);
$user_info = $cr->encrypt(\$user_info);
my $ip_addr = $req->connection->remote_ip;
$ip_addr = $cr->encrypt(\$ip_addr);
my $cookie = Apache::Wyrd::Cookie->new(
$req,
-name=>'auth_cookie',
-value=>$$ip_addr . ':' . $$user_info,
-domain=>$req->hostname,
-path=> ($auth_path || '/')
);
$cookie->bake;
}
=pod
=back
=head2 PERLSETVAR DIRECTIVES
=over
=item LoginFormURL
Form URL (required)
=item UserObject
Module for the User object which performs authorization (required). See
the C<Apache::Wyrd::User> module.
=item NoCookieURL
URL to send cookie-less browsers to (required)
=item ReturnError
Send error back to the Login URL via the given variable (optional)
=item LSKeyURL
Login Server URL for key (required when a Login Server is being used)
( run in 0.587 second using v1.01-cache-2.11-cpan-5a3173703d6 )