--- loncom/auth/lonwebdavacc.pm 2012/02/27 03:06:33 1.1 +++ loncom/auth/lonwebdavacc.pm 2017/09/15 12:53:34 1.6 @@ -1,7 +1,7 @@ # The LearningOnline Network # Authorization Handler for webDAV access to Authoring Space. # -# $Id: lonwebdavacc.pm,v 1.1 2012/02/27 03:06:33 raeburn Exp $ +# $Id: lonwebdavacc.pm,v 1.6 2017/09/15 12:53:34 raeburn Exp $ # # Copyright Michigan State University Board of Trustees # @@ -34,7 +34,7 @@ Apache::lonwebdavacc - webDAV Authorizat =head1 SYNOPSIS -Invoked for /+webdav/[\w\-]+/[\w\-]+/ by +Invoked for ^/+webdav/[\w\-.]+/\w[\w.\-\@]+/ by /etc/httpd/conf/loncapa_apache.conf: PerlAccessHandler Apache::lonwebdavacc @@ -44,20 +44,13 @@ PerlAccessHandler Apache::lonwebda This module enables authorization for authoring space and is used to control access for the following type of URI: - + This module is only called following successful authentication. -Unless lonOtherAuthen has been set, so Single Sign On can be used, -successful authentication will have created a session file and +Successful authentication will have created a session file and transferred the contents to the user's environment. -In the case of SSO, there is no existing user environment, but -$r->user will have been set to the user's username, following -successful authentication. For SSO, the webDAV session file -and environment are set up by a call to -Apache::lonwebdavauth::init_webdav_env(). - -Note: because Apache Basic Auth is used for authentication (unless SSO) +Note: because Apache Basic Auth is used for authentication webDAV access is only available for servers running Apache with SSL. This is part of the LearningOnline Network with CAPA project @@ -75,14 +68,17 @@ Checks if $env{'user.environment'} is de =item * -If no %env, this was SSO authentication so call to &sso_login() to -create session, and return cookie. +If no %env, calls Apache::lonnet::check_for_valid_session() +to retrieve a valid sessionID (webDAV client needs to support +cookies for session retrieval to be successful). If a session is +found Apache::lonnet::transfer_profile_to_env() is called +to populate %env. =item * Checks if requested URL (of form /webdav/authordomain/authorname) is valid and whether authenticated user has an active author or co-author -role in the corresonding Author Space. +role in the corresponding Authoring Space. =back @@ -96,7 +92,7 @@ role in the corresonding Author Space. =item * -Called if no user.environment exists in %env. +Not currently used. =item * @@ -124,6 +120,7 @@ use strict; use GDBM_File; use Apache::Constants qw(:common :http :methods); use Apache::lonnet; +use Apache::londiff(); use LONCAPA qw(:DEFAULT :match); sub handler { @@ -132,16 +129,14 @@ sub handler { my $now = time; my $sessiondir=$r->dir_config('lonDAVsessDir'); - my ($adom,$aname); + my ($adom,$aname) = ($r->uri =~ m{^/webdav/($match_domain)/($match_username)/}); + my $author = "$aname:$adom"; unless ($env{'user.environment'}) { my $handle = &Apache::lonnet::check_for_valid_session($r,'lonDAV'); - if ($handle eq '') { - $handle = &sso_login($r,$sessiondir,$now,$timetolive); - if ($handle eq '') { - return FORBIDDEN; - } - } else { + if ($handle ne '') { &Apache::lonnet::transfer_profile_to_env($sessiondir,$handle); + } else { + return FORBIDDEN; } } my $uhome=&Apache::lonnet::homeserver($env{'user.name'},$env{'user.domain'}); @@ -149,31 +144,73 @@ sub handler { return FORBIDDEN; } - ($adom,$aname) = ($r->uri =~ m{^/webdav/($match_domain)/($match_username)/}); my $docroot = $r->dir_config('lonDocRoot'); if ($adom eq '' || $aname eq '') { return FORBIDDEN; } elsif (!-d "$docroot/priv/$adom/$aname") { return FORBIDDEN; } - # FIXME method check for MKCOL MOVE PUT DELETE for *.log, *.bak - # FIXME method check for regexp for "version-style" names: /\.\d+\.\w+$/ - # for MOVE PUT MKCOL + my $allowed; if (($env{'user.name'} eq $aname) && ($env{'user.domain'} eq $adom)) { if ($env{"user.role.au./$adom/"}) { - return OK; + $allowed = 1; } } else { if (($env{"user.role.ca./$adom/$aname"}) || - (env{"user.role.aa./$adom/$aname"})) { - return OK; + ($env{"user.role.aa./$adom/$aname"})) { + $allowed = 1; } } + if ($allowed) { + my $method = $r->method(); + if (($r->filename =~ /.+\.(log|bak|meta|save)$/) || ($r->filename =~ /\.\d+\.\w+$/) || + ($r->filename =~ m{/\.+[^_/]+$})) { + if (($method eq 'MKCOL') || ($method eq 'PUT')) { + return FORBIDDEN; + } elsif ($method eq 'MOVE') { + if (($r->filename =~ /\.\d+\.\w+$/) || ($r->filename =~ m{/\.+[^_/]+$})) { + return FORBIDDEN; + } + } + } + if (($method eq 'DELETE') || ($method eq 'MOVE')) { + unless (($r->filename =~ m{/\._[^/]+$}) || ($r->filename =~ m{/\.DS_Store$})) { + my $dirptr=16384; + my ($cmode,$cmtime)=(stat($r->filename))[2,9]; + if (($cmode&$dirptr)) { + my $numpub = 0; + $numpub = &recurse_dir($r->filename,$r->dir_config('lonDocRoot'),$numpub); + if ($numpub) { + return FORBIDDEN; + } + } else { + if ($r->filename =~ /^(.+)\.(log|bak|save|meta)$/) { + my $conjugate = $1; + my $type = $2; + if (($type eq 'log') || ($type eq 'meta')) { + if (-e $conjugate) { + my $conjstatus = &pubstatus($conjugate,$r->dir_config('lonDocRoot')); + unless (($conjstatus eq 'unpublished') || ($conjstatus eq 'obsolete')) { + return FORBIDDEN; + } + } + } + } else { + my $status = &pubstatus($r->filename,$r->dir_config('lonDocRoot')); + unless (($status eq 'unpublished') || ($status eq 'obsolete')) { + return FORBIDDEN; + } + } + } + } + } + return OK; + } return FORBIDDEN; } sub sso_login { - my ($r,$sessiondir,$now,$timetolive) = @_; + my ($r,$sessiondir,$now,$timetolive,$author) = @_; my ($uname,$udom); my ($uname) = ($r->user =~ m/([a-zA-Z0-9_\-@.]*)/); unless ($uname =~ /^$match_username$/) { @@ -191,14 +228,72 @@ sub sso_login { return; } my $handle = - &Apache::lonwebdavauth::init_webdav_env($sessiondir,$uname,$udom, - $uhome,$now,$timetolive); + &Apache::lonwebdavauth::init_webdav_env($r,$sessiondir,$uname,$udom, + $uhome,$now,$timetolive,$author); if ($handle ne '') { - my $cookie = "lonDAV=$handle; path=/webdav/; secure; HttpOnly;"; - $r->header_out('Set-cookie' => $cookie); - $r->send_http_header; + if (&Apache::lonnet::usertools_access($uname,$udom,'webdav')) { + my ($webdav) = + ($r->uri =~ m{^(/webdav/$match_domain/$match_username/)}); + &Apache::lonnet::log($udom,$uname,$uhome, + "SSO log-in to $webdav from $ENV{'REMOTE_ADDR'}"); + my $cookie = "lonDAV=$handle; path=/webdav/; secure; HttpOnly;"; + $r->header_out('Set-cookie' => $cookie); + $r->send_http_header; + } } return ($handle); } +sub pubstatus { + my ($fn,$docroot,$cmtime) = @_; + my $privfn = $fn; + my $thisdisfn = $fn; + $thisdisfn=~s/^\Q$docroot\E\/priv//; + my $resfn=$docroot.'/res'.$thisdisfn; + my $targetfn = '/res'.$thisdisfn; + my $status = 'unpublished'; + if (-e $resfn) { + $status = 'published'; + my $same = 0; + if ((stat($resfn))[9] >= $cmtime) { + $same = 1; + } else { + if (&Apache::londiff::are_different_files($resfn,$privfn)) { + $same = 0; + } else { + $same = 1; + } + } + if ($same) { + if (&Apache::lonnet::metadata($targetfn,'obsolete')) { + $status = 'obsolete'; + } + } + } + return $status; +} + +sub recurse_dir { + my ($dirname,$docroot,$numpub) = @_; + $dirname =~ s{/$}{}; + my $dirptr=16384; + if (opendir(my $dirh,$dirname)) { + my @items = readdir($dirh); + closedir($dirh); + foreach my $item (@items) { + next if ($item =~ /.+\.(log|bak|save|meta)$/); + next if ($item =~ /^\.+/); + my ($cmode,$cmtime)=(stat("$dirname/$item"))[2,9]; + if (!($cmode&$dirptr)) { + if (&pubstatus("$dirname/$item",$docroot,$cmtime) eq 'published') { + $numpub ++; + } + } else { + $numpub = &recurse_dir("$dirname/$item",$docroot,$numpub); + } + } + } + return $numpub; +} + 1;