Skip to content

Commit

Permalink
Item11515: 401 status for auth failures, plus support for X-Authoriza…
Browse files Browse the repository at this point in the history
…tion so javascript can authenticate more cleanly

git-svn-id: http://svn.foswiki.org/trunk@14010 0b4bb1d4-4e5a-0410-9cc4-b2b747904278
  • Loading branch information
CrawfordCurrie authored and CrawfordCurrie committed Feb 16, 2012
1 parent 31e25c0 commit b193c40
Show file tree
Hide file tree
Showing 5 changed files with 72 additions and 23 deletions.
23 changes: 20 additions & 3 deletions core/data/System/CommandAndCGIScripts.txt
Original file line number Diff line number Diff line change
Expand Up @@ -298,13 +298,30 @@ The =rest= script supports the following parameters:
| *Parameter* | *Description* | *Default* |
| =endPoint= | Where to redirect the response once the request is served, in the form "Web.Topic". If not given, the REST script must generate a valid response. | |
| =password= | See =username= | |
| =username= | If =TemplateLogin=, or a similar login manager not embedded in the web server, is used, then you need to pass a username and password to the server. The =username= and =password= parameters are used for this purpose. |
| =username= | If =TemplateLogin=, or a similar login manager not embedded in the web server, is used, then you can pass a username and password to the server (though see below for a preferred way to pass authentication information). |

REST scripts that require a topic context must use the standard =topic= parameter to pass the topic name, as the URL path is used to identify the REST funtcion. If not defined, then the topic context in REST handlers will be [[%USERSWEB%.%HOMETOPIC%]].
REST scripts that require a topic context must use the standard =topic= parameter to pass the topic name, as the URL path is used to identify the REST function. If not defined, then the topic context in REST handlers will be [[%USERSWEB%.%HOMETOPIC%]].

The function is free to use any other query parameters for its own purposes.

<blockquote class="foswikiHelp"> %X% The =rest= script should *always* require authentication in any site that has logins. Otherwise there is a risk of opening up major security holes. So make sure you add it to the list of authenticated scripts if you are using =ApacheLogin=.</blockquote>
---++++ =rest= authentication
If a REST operation requires a logged-in user but no user is logged in, then it will return a 401 status. In the case of the =TemplateLogin= login manager, this will include a =WWW-Authenticate= header starting with =FoswikiBasic=. This allows the status to percolate through to Javascript where it can be handled by your code. The =realm= in the =WWW-Authenticate= header is taken from the ={AuthRealm}= setting in =configure=, or the empty string if it is not set.

If you are using =TemplateLogin=, the preferred way to pass user login information back to the server is to use the =X-Authorization= HTTP header. This header is modelled on the HTTP Authorization header, as described in [[http://www.ietf.org/rfc/rfc2617.txt]], except that the scheme name =FoswikiBasic= is used instead of =Basic=. The user-id and password are combined with a : and base64 encoded. For example, if the user agent wishes to send the userid "Aladdin" and password "open sesame", it would use the following header field:
<verbatim>
X-Authorization: FoswikiBasic QWxhZGRpbjpvcGVuIHNlc2FtZQ==
</verbatim>
When used with jQuery:
<verbatim>
$.ajax({
beforeSend: function(xhrObj){
xhrObj.setRequestHeader("X-Authorization",
"FoswikiBasic QWxhZGRpbjpvcGVuIHNlc2FtZQ==");
}
});
</verbatim>

<blockquote class="foswikiHelp"> %X% The =rest= script should *always* be configured to require authentication in any site that is using =ApacheLogin=. Otherwise there is a risk of opening up major security holes. So make sure you add it to the ={AuthScripts}= list in =configure=.</blockquote>

---++++ Invocation Examples

Expand Down
34 changes: 31 additions & 3 deletions core/lib/Foswiki/LoginManager.pm
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,8 @@ our $M3 = chr(7);
our %secretSK = ( STRIKEONESECRET => 1, VALID_ACTIONS => 1 );
our %readOnlySK = ( %secretSK, AUTHUSER => 1, SUDOFROMAUTHUSER => 1 );

use constant TRACE => 1;

=begin TML
---++ StaticMethod makeLoginManager( $session ) -> $Foswiki::LoginManager
Expand Down Expand Up @@ -201,7 +203,7 @@ sub _real_trace {
print STDERR "$id: $mess\n";
}

if ( $Foswiki::cfg{Trace}{LoginManager} ) {
if (TRACE) {
*_trace = \&_real_trace;
}
else {
Expand Down Expand Up @@ -268,7 +270,7 @@ sub loadSession {
my ( $this, $defaultUser, $pwchecker ) = @_;
my $session = $this->{session};

_trace( $this, "LOAD SESSION\n" );
_trace( $this, "LOAD\n" );

$defaultUser = $Foswiki::cfg{DefaultUserLogin}
unless ( defined($defaultUser) );
Expand All @@ -283,6 +285,7 @@ sub loadSession {
# do not create the session. This might be defined if the request
# is made by a search engine bot, depending on how the web server
# is configured

return $authUser if $ENV{NO_FOSWIKI_SESSION};

if ( $Foswiki::cfg{UseClientSessions}
Expand Down Expand Up @@ -361,14 +364,39 @@ sub loadSession {
if ( !defined($authUser)
|| $sessionUser && $sessionUser eq $Foswiki::cfg{AdminUserLogin} );
}

if ( !$authUser ) {

# if we couldn't get the login manager or the http session to tell
# us who the user is, check the username and password URI params.

my $login = $session->{request}->param('username');
my $pass = $session->{request}->param('password');

if ( !$login ) {

# Nothing in the query params. Check query headers.
my $auth = $session->{request}->http('X-Authorization');
if ( defined $auth ) {
_trace( $this, "X-Authorization: $auth" );
if ( $auth =~ /^FoswikiBasic (.+)$/ ) {

# If the user agent wishes to send the userid "Aladdin"
# and password "open sesame", it would use the following
# header field:
# Authorization: Foswiki QWxhZGRpbjpvcGVuIHNlc2FtZQ==
require MIME::Base64;
my $cred = MIME::Base64::decode_base64($1);
if ( $cred =~ /:/ ) {
( $login, $pass ) = split( ':', $cred, 2 );
_trace( $this,
"Login credentials taken from query headers" );
}
} # TODO: implement FoswikiDigest here
}
}
else {
_trace( $this, "Login credentials taken from query parameters" );
}
if ( $login && defined $pass && $pwchecker ) {
my $validation = $pwchecker->checkPassword( $login, $pass );
unless ($validation) {
Expand Down
26 changes: 15 additions & 11 deletions core/lib/Foswiki/LoginManager/TemplateLogin.pm
Original file line number Diff line number Diff line change
Expand Up @@ -80,22 +80,26 @@ sub forceAuthentication {
my $session = $this->{session};

unless ( $session->inContext('authenticated') ) {
my $query = $session->{request};

# Redirect with passthrough so we don't lose the original query params

my $url = $session->getScriptUrl( 0, 'login' );

# We use the query here to ensure the original path_info
# from the request gets through to the login form. See also
# PATH_INFO below.
$url .= Foswiki::urlEncode( $query->path_info() );
my $query = $session->{request};
my $response = $session->{response};

# Respond with a 401 with an appropriate WWW-Authenticate
# that won't be snatched by the browser, but can be used
# by JS to generate login info.
$response->header(
-status => 401,
-WWW_Authenticate => 'FoswikiBasic realm="'
. ( $Foswiki::cfg{AuthRealm} || "" ) . '"'
);

$query->param(
-name => 'foswiki_origin',
-value => _packRequest($session)
);
$session->redirect( $url, 1 ); # with passthrough

# Throw back the login page with the 401
$this->login( $query, $session );

return 1;
}
return 0;
Expand Down
8 changes: 4 additions & 4 deletions core/lib/Foswiki/Net.pm
Original file line number Diff line number Diff line change
Expand Up @@ -153,8 +153,7 @@ sub getExternalResource {
# Use MIME::Base64 at run-time if using outbound proxy with
# authentication
require MIME::Base64;
import MIME::Base64();
my $base64 = encode_base64( "$user:$pass", "\r\n" );
my $base64 = MIME::Base64::encode_base64( "$user:$pass", "\r\n" );
$req .= "Authorization: Basic $base64";
}

Expand Down Expand Up @@ -189,8 +188,9 @@ sub getExternalResource {
$port = $proxyPort;
if ($proxyUser) {
require MIME::Base64;
import MIME::Base64();
my $base64 = encode_base64( "$proxyUser:$proxyPass", "\r\n" );
my $base64 =
MIME::Base64::encode_base64( "$proxyUser:$proxyPass",
"\r\n" );
$req .= "Proxy-Authorization: Basic $base64";
}
}
Expand Down
4 changes: 2 additions & 2 deletions core/lib/Foswiki/UI/Rest.pm
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ use Error qw( :try );

our %restDispatch;

=begin TML=
=begin TML
---++ StaticMethod registerRESTHandler( $subject, $verb, \&fn, %options )
Expand Down Expand Up @@ -50,7 +50,7 @@ attacks, or used for phishing.
* =http_allow= use this option to specify the HTTP methods that can
be used to invoke the handler.
=cut=
=cut

sub registerRESTHandler {
my ( $subject, $verb, $fnref, %options ) = @_;
Expand Down

0 comments on commit b193c40

Please sign in to comment.