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.

    1: # The LearningOnline Network
    2: # Authentication Handler for webDAV access to Authoring Space.
    3: #
    4: # $Id: lonwebdavauth.pm,v 1.8 2020/12/18 15:23:03 raeburn Exp $
    5: #
    6: # Copyright Michigan State University Board of Trustees
    7: #
    8: # This file is part of the LearningOnline Network with CAPA (LON-CAPA).
    9: #
   10: # LON-CAPA is free software; you can redistribute it and/or modify
   11: # it under the terms of the GNU General Public License as published by
   12: # the Free Software Foundation; either version 2 of the License, or
   13: # (at your option) any later version.
   14: #
   15: # LON-CAPA is distributed in the hope that it will be useful,
   16: # but WITHOUT ANY WARRANTY; without even the implied warranty of
   17: # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   18: # GNU General Public License for more details.
   19: #
   20: # You should have received a copy of the GNU General Public License
   21: # along with LON-CAPA; if not, write to the Free Software
   22: # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
   23: #
   24: # /home/httpd/html/adm/gpl.txt
   25: #
   26: # http://www.lon-capa.org/
   27: #
   28: 
   29: =head1 NAME
   30: 
   31: Apache::lonwebdavauth - webDAV Authentication Handler
   32: 
   33: =head1 SYNOPSIS
   34: 
   35: Invoked for ^/+webdav/[\w\-.]+/\w[\w.\-\@]+/ by 
   36: /etc/httpd/conf/loncapa_apache.conf:
   37: 
   38: PerlAuthenHandler	Apache::lonwebdavauth
   39: 
   40: =head1 INTRODUCTION
   41: 
   42: This module employs Apache Basic Auth authentication and is used
   43: to provide authentication for webDAV client access to author
   44: space(s). Note: because Apache Basic Auth is used, webDAV access
   45: is only available for servers running Apache with SSL.
   46: 
   47: If the webDAV client supports cookies then whenever the client  
   48: sends the cookie back, a check is made to see if a corresponding
   49: valid session file exists in the server's webDAV session directory.
   50: Support for cookies by webDAV clients is optional.
   51: 
   52: If a valid session file exists, a cookie is returned containing
   53: the session ID,  otherwise a new session file is created and
   54: returned in the cookie.
   55: 
   56: The perlvar "lonDAVsessDir" in /etc/httpd/conf/loncapa_apache.conf
   57: provides the directory location: /home/httpd/webdav/sessionIDs.
   58: 
   59: If the session is stale, or the cookie is missing or invalid, 
   60: the user is re-challenged for login information, by sending
   61: an Apache Basic Auth request to the client.
   62: 
   63: If Apache Basic Auth is successful authentication will
   64: result in creation of a webDAV session file containing a 
   65: minimal set of information about the user which will also be 
   66: loaded into the user's environment.  The environment persists
   67: until the end of the Apache request, when it is cleaned up
   68: by lonacc::cleanup, as is the case for requests for non-webdav URLs.
   69: 
   70: If a valid session file exists, a cookie is returned containing
   71: the session ID, otherwise a new session file is created and
   72: returned in the cookie.
   73: 
   74: This is part of the LearningOnline Network with CAPA project
   75: described at http://www.lon-capa.org.
   76: 
   77: =head1 HANDLER SUBROUTINE
   78: 
   79: This routine is called by Apache and mod_perl.
   80: 
   81: =over 4
   82: 
   83: =item *
   84: 
   85: Check for valid webDAV session
   86: 
   87: =item *
   88: 
   89: No session? return AUTH_REQUIRED which will prompt 
   90: webDAV client to authenticate user (via Apache Basic Auth). 
   91: 
   92: =item *
   93: 
   94: If authenticated, call &init_webdav_env() to create sessionID
   95: and populate environment, and send header containing cookie.
   96: 
   97: =back
   98: 
   99: =head1 NOTABLE SUBROUTINES
  100: 
  101: =over
  102: 
  103: =item * init_webdav_env()
  104: 
  105: =over
  106: 
  107: =item *
  108: 
  109: Checks for valid session files in webDAV session directory.
  110: 
  111: =item * 
  112: 
  113: Calls Apache::lonnet::transfer_profile_to_env() to create
  114: %env for user if session is valid. 
  115: 
  116: =item * 
  117: 
  118: Otherwise creates new session file and populates %env. 
  119: 
  120: =item *
  121: 
  122: Session ID file is a GDBM file containing:
  123: 
  124: =over
  125: 
  126: =item * user.name, user.domain, user.home, user.environment
  127: 
  128: =item * user.role entries for:
  129: 
  130: active author, co-author, and assistant co-author roles
  131: in domains hosted on this machine.
  132: 
  133: =back
  134: 
  135: =back
  136: 
  137: =back
  138: 
  139: =cut 
  140: 
  141: package Apache::lonwebdavauth;
  142: 
  143: use strict;
  144: use GDBM_File;
  145: use Apache::Constants qw(:common :http :methods);
  146: use Apache::lonnet;
  147: use LONCAPA qw(:DEFAULT :match);
  148: 
  149: sub handler {
  150:     my $r = shift;
  151:     my $now = time;
  152:     my $timetolive = 600;
  153:     my $sessiondir=$r->dir_config('lonDAVsessDir');
  154:     my ($uname,$udom,$uhome);
  155: 
  156: # Check for cookie
  157:     my $handle = &Apache::lonnet::check_for_valid_session($r,'lonDAV');
  158:     if ($handle ne '') {
  159:         my $sesstime;
  160:         ($uname,$udom,$sesstime,$uhome) =  
  161:             ($handle =~ /^($match_username)_($match_domain)_(\d+)_\d+_(.+)$/);
  162:         if ($sesstime) {
  163:             if ($now-$sesstime < $timetolive) {
  164:                 if (&Apache::lonnet::homeserver($uname,$udom) eq $uhome) {
  165:                     &Apache::lonnet::transfer_profile_to_env($sessiondir,$handle);
  166:                     if (&Apache::lonnet::usertools_access($uname,$udom,'webdav')) {
  167:                         if ($r->user() eq '') {
  168:                             if ($env{'user.domain'} eq $r->dir_config('lonDefDomain')) {
  169:                                 $r->user($env{'user.name'});
  170:                             } else {
  171:                                 $r->user($env{'user.name'}.':'.$env{'user.domain'});
  172:                             }
  173:                         }
  174:                         return OK;
  175:                     } else {
  176:                         return FORBIDDEN;
  177:                     }
  178:                 }
  179:             }
  180:         }
  181:     }
  182: 
  183:     my ($status,$upass) = $r->get_basic_auth_pw;
  184:     return $status unless ($status == 0 || $status == OK);
  185: 
  186:     if ($r->user =~ /,/) {
  187:         ($uname,$udom) = split(/,/,$r->user);
  188:         $uname =~ s/^\s+//;
  189:         $uname =~ s/\s+$//;
  190:         $udom =~ s/^\s+//;
  191:         $udom =~ s/\s+$//;
  192:         unless (($uname =~ /^$match_username$/) && ($udom =~ /^$match_domain$/)) {
  193:             $r->note_basic_auth_failure;
  194:             return AUTH_REQUIRED;
  195:         }
  196:     } else {
  197:         $uname = $r->user;
  198:         $uname =~ s/^\s+//;
  199:         $uname =~ s/\s+$//;
  200:         ($udom) = ($r->uri =~ m{^/webdav/($match_domain)/});
  201:         unless (($udom ne '' ) && ($uname =~ /^$match_username$/) && ($upass ne '')) {
  202:             $r->note_basic_auth_failure;
  203:             return AUTH_REQUIRED;
  204:         }
  205:     }
  206:     if (&Apache::lonnet::domain($udom) ne '') {
  207:         if (&Apache::lonnet::homeserver($uname,$udom) ne 'no_host') {
  208:             my $uhome = &Apache::lonnet::authenticate($uname,$upass,$udom);
  209:             if (($uhome ne 'no_host') && 
  210:                 (&Apache::lonnet::hostname($uhome) ne '')) {
  211:                 my ($author) = ($r->uri =~ m{^/webdav/($match_domain/$match_username)/});
  212:                 $handle = &init_webdav_env($r,$sessiondir,$uname,$udom,
  213:                                            $uhome,$now,$timetolive,$author);
  214:                 if ($handle ne '') {
  215:                     if (&Apache::lonnet::usertools_access($uname,$udom,'webdav')) {
  216:                         my $cookie = "lonDAV=$handle; path=/webdav/; secure; HttpOnly;";
  217:                         $r->header_out('Set-cookie' => $cookie);
  218:                         $r->send_http_header;
  219:                         return OK;
  220:                     } else {
  221:                         return FORBIDDEN;
  222:                     }
  223:                 }
  224:             }
  225:         }
  226:     }
  227:     $r->note_basic_auth_failure;
  228:     return AUTH_REQUIRED;
  229: }
  230: 
  231: sub init_webdav_env {
  232:     my ($r,$sessiondir,$uname,$udom,$uhome,$now,$timetolive,$author) = @_;
  233:     my $handle;
  234:     my $currnewest = 0;
  235:     if ($sessiondir ne '') {
  236:         if (opendir(DIR,$sessiondir)) {
  237:             while (my $filename=readdir(DIR)) {
  238:                 if ($filename=~/^($uname\_$udom\_(\d+)\_\d+\_$uhome)\.id$/) {
  239:                     my $oldhandle = $1;
  240:                     my $sesstime = $2;
  241:                     if ($now-$sesstime < $timetolive) {
  242:                         if ($sesstime > $currnewest) {
  243:                             $currnewest = $sesstime;
  244:                             if ($handle ne '') {
  245:                                 unlink("$sessiondir/$handle.id");
  246:                             }
  247:                             $handle = $oldhandle;
  248:                             next;
  249:                         }
  250:                     }
  251:                     unlink($sessiondir.'/'.$filename);
  252:                 }
  253:             }
  254:             closedir(DIR);
  255:         }
  256:     }
  257:     if ($handle ne '') {
  258:         &Apache::lonnet::transfer_profile_to_env($sessiondir,$handle);
  259:         return $handle;
  260:     } else {
  261:         my $id = $$.int(rand(10000000));
  262:         $handle = "$uname\_$udom\_$now\_$id\_$uhome";
  263:         my $sessionfile = "$sessiondir/$handle.id";
  264:         if (tie(my %disk_env,'GDBM_File',"$sessionfile",
  265:                 &GDBM_WRCREAT(),0640)) {
  266:             $disk_env{'user.name'} = $uname;
  267:             $disk_env{'user.domain'} = $udom;
  268:             $disk_env{'user.home'} = $uhome;
  269:             my %userenv = &Apache::lonnet::get('environment',['inststatus','tools.webdav'],
  270:                                                $udom,$uname);
  271:             my ($tmp) = keys(%userenv);
  272:             if ($tmp =~ /^(con_lost|error|no_such_host)/i) {
  273:                 $disk_env{'environment.inststatus'} = $userenv{'inststatus'};
  274:                 $disk_env{'environment.tools.webdav'} = $userenv{'tools.webdav'};
  275:             }
  276:             $disk_env{'user.environment'} = $sessionfile;
  277:             my $possroles = ['au','ca','aa'];
  278:             my @possdoms = &Apache::lonnet::current_machine_domains();
  279:             my %cstr_roles =
  280:                 &Apache::lonnet::get_my_roles($uname,$udom,'userroles',
  281:                                               undef,$possroles,\@possdoms);
  282:             if (keys(%cstr_roles) > 0) {
  283:                 $disk_env{'user.adv'} = 1;
  284:                 $disk_env{'user.author'} = 1;
  285:                 foreach my $item (keys(%cstr_roles)) {
  286:                     my ($aname,$adom,$role) = split(/:/,$item);
  287:                     if ($role eq 'au') {
  288:                         $disk_env{"user.role.$role./$adom/"} = $cstr_roles{$item};
  289:                     } else {
  290:                         $disk_env{"user.role.$role./$adom/$aname"} = $cstr_roles{$item};
  291:                     }
  292:                 }
  293:             }
  294:             my %is_adv = ( is_adv => $disk_env{'user.adv'} );
  295:             my %domdef = &Apache::lonnet::get_domain_defaults($udom);
  296:             $disk_env{'environment.availabletools.webdav'} =
  297:                 &Apache::lonnet::usertools_access($uname,$udom,'webdav','reload',undef,
  298:                                                   \%userenv,\%domdef,\%is_adv);
  299:             @env{keys(%disk_env)} = @disk_env{keys(%disk_env)};
  300:             untie(%disk_env);
  301:             my $ip = &Apache::lonnet::get_requestor_ip($r);
  302:             &Apache::lonnet::log($udom,$uname,$uhome,
  303:                                  "Login webdav/$author $ip");
  304:         }
  305:         return $handle;
  306:     }
  307:     return;
  308: }
  309: 
  310: 1;

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