File:  [LON-CAPA] / loncom / auth / lonwebdavauth.pm
Revision 1.8: download - view: text, annotated - select for diffs
Fri Dec 18 15:23:03 2020 UTC (3 years, 4 months ago) by raeburn
Branches: MAIN
CVS tags: version_2_12_X, version_2_11_X, version_2_11_4_uiuc, version_2_11_4_msu, version_2_11_4, version_2_11_3_uiuc, version_2_11_3_msu, version_2_11_3, HEAD
- Retrieval of requestor's IP address centralized in lonnet::get_requestor_ip()
- Domain configuration to allow domain's LON-CAPA nodes to operate behind a
  WAF/Reverse Proxy using aliased hostname (CNAME).
- Web requests from other nodes bypass the WAF as their requests are made
  directly to the server hostname (A record); same for internal LON-CAPA
  connections for lonc -> lond.

# The LearningOnline Network
# Authentication Handler for webDAV access to Authoring Space.
#
# $Id: lonwebdavauth.pm,v 1.8 2020/12/18 15:23:03 raeburn Exp $
#
# Copyright Michigan State University Board of Trustees
#
# This file is part of the LearningOnline Network with CAPA (LON-CAPA).
#
# LON-CAPA 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.
#
# LON-CAPA 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 LON-CAPA; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
#
# /home/httpd/html/adm/gpl.txt
#
# http://www.lon-capa.org/
#

=head1 NAME

Apache::lonwebdavauth - webDAV Authentication Handler

=head1 SYNOPSIS

Invoked for ^/+webdav/[\w\-.]+/\w[\w.\-\@]+/ by 
/etc/httpd/conf/loncapa_apache.conf:

PerlAuthenHandler	Apache::lonwebdavauth

=head1 INTRODUCTION

This module employs Apache Basic Auth authentication and is used
to provide authentication for webDAV client access to author
space(s). Note: because Apache Basic Auth is used, webDAV access
is only available for servers running Apache with SSL.

If the webDAV client supports cookies then whenever the client  
sends the cookie back, a check is made to see if a corresponding
valid session file exists in the server's webDAV session directory.
Support for cookies by webDAV clients is optional.

If a valid session file exists, a cookie is returned containing
the session ID,  otherwise a new session file is created and
returned in the cookie.

The perlvar "lonDAVsessDir" in /etc/httpd/conf/loncapa_apache.conf
provides the directory location: /home/httpd/webdav/sessionIDs.

If the session is stale, or the cookie is missing or invalid, 
the user is re-challenged for login information, by sending
an Apache Basic Auth request to the client.

If Apache Basic Auth is successful authentication will
result in creation of a webDAV session file containing a 
minimal set of information about the user which will also be 
loaded into the user's environment.  The environment persists
until the end of the Apache request, when it is cleaned up
by lonacc::cleanup, as is the case for requests for non-webdav URLs.

If a valid session file exists, a cookie is returned containing
the session ID, otherwise a new session file is created and
returned in the cookie.

This is part of the LearningOnline Network with CAPA project
described at http://www.lon-capa.org.

=head1 HANDLER SUBROUTINE

This routine is called by Apache and mod_perl.

=over 4

=item *

Check for valid webDAV session

=item *

No session? return AUTH_REQUIRED which will prompt 
webDAV client to authenticate user (via Apache Basic Auth). 

=item *

If authenticated, call &init_webdav_env() to create sessionID
and populate environment, and send header containing cookie.

=back

=head1 NOTABLE SUBROUTINES

=over

=item * init_webdav_env()

=over

=item *

Checks for valid session files in webDAV session directory.

=item * 

Calls Apache::lonnet::transfer_profile_to_env() to create
%env for user if session is valid. 

=item * 

Otherwise creates new session file and populates %env. 

=item *

Session ID file is a GDBM file containing:

=over

=item * user.name, user.domain, user.home, user.environment

=item * user.role entries for:

active author, co-author, and assistant co-author roles
in domains hosted on this machine.

=back

=back

=back

=cut 

package Apache::lonwebdavauth;

use strict;
use GDBM_File;
use Apache::Constants qw(:common :http :methods);
use Apache::lonnet;
use LONCAPA qw(:DEFAULT :match);

sub handler {
    my $r = shift;
    my $now = time;
    my $timetolive = 600;
    my $sessiondir=$r->dir_config('lonDAVsessDir');
    my ($uname,$udom,$uhome);

# Check for cookie
    my $handle = &Apache::lonnet::check_for_valid_session($r,'lonDAV');
    if ($handle ne '') {
        my $sesstime;
        ($uname,$udom,$sesstime,$uhome) =  
            ($handle =~ /^($match_username)_($match_domain)_(\d+)_\d+_(.+)$/);
        if ($sesstime) {
            if ($now-$sesstime < $timetolive) {
                if (&Apache::lonnet::homeserver($uname,$udom) eq $uhome) {
                    &Apache::lonnet::transfer_profile_to_env($sessiondir,$handle);
                    if (&Apache::lonnet::usertools_access($uname,$udom,'webdav')) {
                        if ($r->user() eq '') {
                            if ($env{'user.domain'} eq $r->dir_config('lonDefDomain')) {
                                $r->user($env{'user.name'});
                            } else {
                                $r->user($env{'user.name'}.':'.$env{'user.domain'});
                            }
                        }
                        return OK;
                    } else {
                        return FORBIDDEN;
                    }
                }
            }
        }
    }

    my ($status,$upass) = $r->get_basic_auth_pw;
    return $status unless ($status == 0 || $status == OK);

    if ($r->user =~ /,/) {
        ($uname,$udom) = split(/,/,$r->user);
        $uname =~ s/^\s+//;
        $uname =~ s/\s+$//;
        $udom =~ s/^\s+//;
        $udom =~ s/\s+$//;
        unless (($uname =~ /^$match_username$/) && ($udom =~ /^$match_domain$/)) {
            $r->note_basic_auth_failure;
            return AUTH_REQUIRED;
        }
    } else {
        $uname = $r->user;
        $uname =~ s/^\s+//;
        $uname =~ s/\s+$//;
        ($udom) = ($r->uri =~ m{^/webdav/($match_domain)/});
        unless (($udom ne '' ) && ($uname =~ /^$match_username$/) && ($upass ne '')) {
            $r->note_basic_auth_failure;
            return AUTH_REQUIRED;
        }
    }
    if (&Apache::lonnet::domain($udom) ne '') {
        if (&Apache::lonnet::homeserver($uname,$udom) ne 'no_host') {
            my $uhome = &Apache::lonnet::authenticate($uname,$upass,$udom);
            if (($uhome ne 'no_host') && 
                (&Apache::lonnet::hostname($uhome) ne '')) {
                my ($author) = ($r->uri =~ m{^/webdav/($match_domain/$match_username)/});
                $handle = &init_webdav_env($r,$sessiondir,$uname,$udom,
                                           $uhome,$now,$timetolive,$author);
                if ($handle ne '') {
                    if (&Apache::lonnet::usertools_access($uname,$udom,'webdav')) {
                        my $cookie = "lonDAV=$handle; path=/webdav/; secure; HttpOnly;";
                        $r->header_out('Set-cookie' => $cookie);
                        $r->send_http_header;
                        return OK;
                    } else {
                        return FORBIDDEN;
                    }
                }
            }
        }
    }
    $r->note_basic_auth_failure;
    return AUTH_REQUIRED;
}

sub init_webdav_env {
    my ($r,$sessiondir,$uname,$udom,$uhome,$now,$timetolive,$author) = @_;
    my $handle;
    my $currnewest = 0;
    if ($sessiondir ne '') {
        if (opendir(DIR,$sessiondir)) {
            while (my $filename=readdir(DIR)) {
                if ($filename=~/^($uname\_$udom\_(\d+)\_\d+\_$uhome)\.id$/) {
                    my $oldhandle = $1;
                    my $sesstime = $2;
                    if ($now-$sesstime < $timetolive) {
                        if ($sesstime > $currnewest) {
                            $currnewest = $sesstime;
                            if ($handle ne '') {
                                unlink("$sessiondir/$handle.id");
                            }
                            $handle = $oldhandle;
                            next;
                        }
                    }
                    unlink($sessiondir.'/'.$filename);
                }
            }
            closedir(DIR);
        }
    }
    if ($handle ne '') {
        &Apache::lonnet::transfer_profile_to_env($sessiondir,$handle);
        return $handle;
    } else {
        my $id = $$.int(rand(10000000));
        $handle = "$uname\_$udom\_$now\_$id\_$uhome";
        my $sessionfile = "$sessiondir/$handle.id";
        if (tie(my %disk_env,'GDBM_File',"$sessionfile",
                &GDBM_WRCREAT(),0640)) {
            $disk_env{'user.name'} = $uname;
            $disk_env{'user.domain'} = $udom;
            $disk_env{'user.home'} = $uhome;
            my %userenv = &Apache::lonnet::get('environment',['inststatus','tools.webdav'],
                                               $udom,$uname);
            my ($tmp) = keys(%userenv);
            if ($tmp =~ /^(con_lost|error|no_such_host)/i) {
                $disk_env{'environment.inststatus'} = $userenv{'inststatus'};
                $disk_env{'environment.tools.webdav'} = $userenv{'tools.webdav'};
            }
            $disk_env{'user.environment'} = $sessionfile;
            my $possroles = ['au','ca','aa'];
            my @possdoms = &Apache::lonnet::current_machine_domains();
            my %cstr_roles =
                &Apache::lonnet::get_my_roles($uname,$udom,'userroles',
                                              undef,$possroles,\@possdoms);
            if (keys(%cstr_roles) > 0) {
                $disk_env{'user.adv'} = 1;
                $disk_env{'user.author'} = 1;
                foreach my $item (keys(%cstr_roles)) {
                    my ($aname,$adom,$role) = split(/:/,$item);
                    if ($role eq 'au') {
                        $disk_env{"user.role.$role./$adom/"} = $cstr_roles{$item};
                    } else {
                        $disk_env{"user.role.$role./$adom/$aname"} = $cstr_roles{$item};
                    }
                }
            }
            my %is_adv = ( is_adv => $disk_env{'user.adv'} );
            my %domdef = &Apache::lonnet::get_domain_defaults($udom);
            $disk_env{'environment.availabletools.webdav'} =
                &Apache::lonnet::usertools_access($uname,$udom,'webdav','reload',undef,
                                                  \%userenv,\%domdef,\%is_adv);
            @env{keys(%disk_env)} = @disk_env{keys(%disk_env)};
            untie(%disk_env);
            my $ip = &Apache::lonnet::get_requestor_ip($r);
            &Apache::lonnet::log($udom,$uname,$uhome,
                                 "Login webdav/$author $ip");
        }
        return $handle;
    }
    return;
}

1;

FreeBSD-CVSweb <freebsd-cvsweb@FreeBSD.org>