# 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;