#!/usr/bin/perl
# Author: Alex Efros <powerman-asdf@yandex.ru>, 2008
# License: Public Domain
# 
# Personal OpenID server with SRE support.
# 
# Installation:
# 1. Setup %USER, $SETUP_URL and $SERVER_SECRET below.
# 2. Add this to .htaccess:
#   <Files "OpenIDsrv.cgi">
#       RewriteEngine On
#       RewriteRule .* - [E=HTTP_AUTHORIZATION:%{HTTP:Authorization},L]
#   </Files>
# 3. Add this to web page which will be your OpenID:
#   <link rel="openid.server" href="http://YOUR.DOMAIN/PATH/OpenIDsrv.cgi" />
#   <link rel="openid.delegate" href="http://YOUR.DOMAIN/OPENID_PATH" />


use version; $VERSION = qv('0.1.0');
use warnings;
use strict;
use CGI;
use MIME::Base64;
use Net::OpenID::Server;


# Configuration: setup users, their passwords, OpenID urls, filter for
# allowed RP (Relying Party), and additional data sent to RP.
my %USER = (
    'YOUR_LOGIN' => {
        pass        => 'YOUR_PASSWORD',
        url         => 'http://YOUR.DOMAIN/OPENID_PATH',
        trust_root  => qr//,
        # http://openid.net/specs/openid-simple-registration-extension-1_0.html
        sre         => {
            'sreg.nickname'     => 'YOUR_NICKNAME',
            'sreg.fullname'     => 'YOUR FULLNAME',
        },
    },
);
# Where to redirect in case of wrong login/pass, wrong OpenID url, or
# failed filter for RP:
my $SETUP_URL = 'http://YOUR.DOMAIN/ERROR.html';
my $SERVER_SECRET = 'PUT_ANY_RANDOM_JUNK_HERE';


my $cgi = new CGI;
my $nos = Net::OpenID::Server->new(
    get_args      => $cgi,
    post_args     => $cgi,
    get_user      => \&get_user,
    is_identity   => \&is_identity,
    is_trusted    => \&is_trusted,
    server_secret => $SERVER_SECRET,
    setup_url     => $SETUP_URL,
);
my ($type, $data) = $nos->handle_page();
if ($type eq 'redirect') {
    my $user = $nos->get_user()->();
    my $url = $nos->signed_return_url(
        identity            => $nos->args('openid.identity'),
        return_to           => $nos->args('openid.return_to'),
        assoc_handle        => $nos->args('openid.assoc_handle'),
        trust_root          => $nos->args('openid.trust_root'),
        additional_fields   => $USER{$user}{sre},
    );
    print "Status: 301\r\n";
    print "Location: $url\r\n\r\n";
}
elsif ($type eq 'setup') {
    my $url = $nos->setup_url();
    print "Status: 301\r\n";
    print "Location: $url\r\n\r\n";
}
else {
    print "Status: 200\r\n";
    print "Content-Type: $type\r\n\r\n$data";
}
exit;


# Return name of logged in user or undef if user don't logged in.
sub get_user {
    my ($login, $pass) = _get_auth();
    if (!defined $login) {
        _require_auth();
    }
    elsif (!exists $USER{$login}) {
        return;
    }
    elsif ($USER{$login}{pass} ne $pass) {
        _require_auth();
    }
    else {
        return $login;
    }
}

# Return true if $user own $url.
# $url usually come from <link rel="openid.delegate" href="...">
sub is_identity {
    my ($user, $url) = @_;
    return if !defined $user;
    return $USER{$user}{url} eq $url;
}

# Returning true if the logged in $user trusts the URL given by $trust_root
# to know his identity.
# $trust_root is url of RP (website which is checking user's OpenID)
sub is_trusted {
    my ($user, $trust_root, $is_identity) = @_;
    return if !defined $user || !$is_identity;
    return $trust_root =~ /$USER{$user}{trust_root}/;
}


# Process HTTP Basic authorization. Require this in .htaccess to work:
#   <Files "OpenIDsrv.cgi">
#       RewriteEngine On
#       RewriteRule .* - [E=HTTP_AUTHORIZATION:%{HTTP:Authorization},L]
#   </Files>

sub _get_auth {
    return if $ENV{HTTP_AUTHORIZATION} !~ /\ABasic\s+(\S+)\z/;
    return split /:/, decode_base64($1), 2;
}

sub _require_auth {
    print "Status: 401 Authorization Required\r\n";
    print "WWW-Authenticate: Basic realm=\"OpenIDsrv\"\r\n\r\n";
    exit;
}

