Enable HTTP Digest authentication for non admin users.

Also ensure authentication is not lost by Digest timeout when navigating
between index.html and search results page.

This way, running searches with extended features on a remote peer or a
password protected peer works with a regular user (with "Extended
search" rights). 
When authenticating on the search page with a user without "Extended
search" rights, it appears as authenticated, but has just its usual
access to the public search features.
pull/135/head
luccioman 7 years ago
parent 5161451a35
commit 8e732d437c

@ -93,7 +93,7 @@ public class Blog {
final int num = post.getInt("num",10); //indicates how many entries should be shown
if (!hasRights) {
final UserDB.Entry userentry = sb.userDB.proxyAuth(header.get(RequestHeader.AUTHORIZATION));
final UserDB.Entry userentry = sb.userDB.proxyAuth(header);
if (userentry != null && userentry.hasRight(UserDB.AccessRight.BLOG_RIGHT)) {
hasRights=true;
} else if (post.containsKey("login")) {

@ -77,7 +77,7 @@ public class BlogComments {
}
if (!hasRights) {
final UserDB.Entry userentry = sb.userDB.proxyAuth(header.get(RequestHeader.AUTHORIZATION));
final UserDB.Entry userentry = sb.userDB.proxyAuth(header);
if (userentry != null && userentry.hasRight(UserDB.AccessRight.BLOG_RIGHT)) {
hasRights = true;
} else if (post.containsKey("login")) {

@ -54,7 +54,7 @@ public class User{
prop.put("logged-in_username", "");
prop.put("logged-in_returnto", "");
//identified via HTTPPassword
entry=sb.userDB.proxyAuth(requestHeader.get(RequestHeader.AUTHORIZATION));
entry=sb.userDB.proxyAuth(requestHeader);
if(entry != null){
prop.put("logged-in_identified-by", "1");
//try via cookie

@ -119,6 +119,9 @@
</li>
<li id="header_administration">
<form action="index.html" method="get">
#(authorized)#::
<input type="hidden" name="auth" id="auth" value=""/>
#(/authorized)#
<button accesskey="s" type="submit" class="btn btn-inverse navbar-btn label-primary" title="Search Interface">
<span class="glyphicon glyphicon-search"></span>
<span class="hidden-sm hidden-md"> Search Interface &raquo;</span>

@ -37,7 +37,7 @@
<span class="visible-sm glyphicon glyphicon-search"><b class="caret"></b></span>
</a>
<ul class="dropdown-menu" role="menu">
<li id="header_websearch"><a href="index.html#(authorized)#::?auth#(/authorized)#" onclick="this.href='index.html?#(authorized)#::auth&#(/authorized)#former='+document.searchform.search.value">Web Search</a></li>
<li id="header_websearch"><a href="index.html#(authSearch)#::?auth#(/authSearch)#" onclick="this.href='index.html?#(authSearch)#::auth&#(/authSearch)#former='+document.searchform.search.value">Web Search</a></li>
<li id="header_filesearch"><a href="yacyinteractive.html" onclick="this.href='yacyinteractive.html?handover='+document.searchform.search.value">File Search</a></li>
<li id="header_comparesearch"><a href="compare_yacy.html?display=0">Compare Search</a></li>
<li id="header_hostbrowser"><a href="HostBrowser.html?hosts=">Index Browser</a></li>

@ -61,9 +61,9 @@
<button id="Enter" name="Enter" class="btn btn-primary" type="submit">Search</button>
</div>
</div>
#(authorized)#::
#(authSearch)#::
<input type="hidden" name="auth" id="auth" value=""/>
#(/authorized)#
#(/authSearch)#
<input type="hidden" name="verify" value="#[search.verify]#" />
#(searchdomswitches)#::
<div class="yacysearch">
@ -72,7 +72,7 @@
#(searchaudio)#::<input type="radio" id="audio" name="contentdom" value="audio" #(check)#::checked="checked"#(/check)# />&nbsp;Audio&nbsp;&nbsp;#(/searchaudio)#
#(searchvideo)#::<input type="radio" id="video" name="contentdom" value="video" #(check)#::checked="checked"#(/check)# />&nbsp;Video&nbsp;&nbsp;#(/searchvideo)#
#(searchapp)#::<input type="radio" id="app" name="contentdom" value="app" #(check)#::checked="checked"#(/check)# />&nbsp;Applications#(/searchapp)#
#(searchoptions)#&nbsp;&nbsp;<a href="index.html?searchoptions=1" onclick="this.href='index.html?searchoptions=1&amp;former='+document.getElementById('search').value+'&amp;contentdom='+radioValue(document.getElementById('searchform').contentdom);">more options...</a>::#(/searchoptions)#
#(searchoptions)#&nbsp;&nbsp;<a href="index.html?searchoptions=1#(authSearch)#::&auth#(/authSearch)#" onclick="this.href='index.html?searchoptions=1#(authSearch)#::&auth#(/authSearch)#&amp;former='+document.getElementById('search').value+'&amp;contentdom='+radioValue(document.getElementById('searchform').contentdom);">more options...</a>::#(/searchoptions)#
</div>
#(/searchdomswitches)#
<input type="hidden" name="nav" value="#[search.navigation]#" />

@ -31,6 +31,7 @@
import net.yacy.cora.document.analysis.Classification;
import net.yacy.cora.document.analysis.Classification.ContentDomain;
import net.yacy.cora.protocol.RequestHeader;
import net.yacy.data.UserDB;
import net.yacy.search.Switchboard;
import net.yacy.search.SwitchboardConstants;
import net.yacy.search.schema.CollectionSchema;
@ -57,14 +58,25 @@ public class index {
if(adminAuthenticated) {
authenticatedUserName = sb.getConfig(SwitchboardConstants.ADMIN_ACCOUNT_USER_NAME, "admin");
} else {
final UserDB.Entry user = sb.userDB != null ? sb.userDB.getUser(header) : null;
if(user != null) {
authenticatedUserName = user.getUserName();
}
}
if ((post != null) && (post.containsKey("auth") || post.containsKey("publicPage"))) {
if (!adminAuthenticated) {
prop.authenticationRequired();
return prop;
}
}
boolean authenticated = adminAuthenticated || authenticatedUserName != null;
if (post != null) {
if (post.containsKey("publicPage") && !adminAuthenticated) { // Old style parameter : still in use ?
prop.authenticationRequired();
return prop;
}
if (post.containsKey("auth") && !authenticated) { // search with authentication required
prop.authenticationRequired();
return prop;
}
}
prop.put("authSearch", authenticated);
boolean global = (post == null) ? true : post.get("resource", "global").equals("global");
final boolean focus = (post == null) ? true : post.get("focus", "1").equals("1");
@ -130,6 +142,7 @@ public class index {
prop.putHTML("constraint", constraint);
prop.put("searchdomswitches", sb.getConfigBool("search.text", true) || sb.getConfigBool("search.audio", true) || sb.getConfigBool("search.video", true) || sb.getConfigBool("search.image", true) || sb.getConfigBool("search.app", true) ? 1 : 0);
prop.put("searchdomswitches_searchoptions", searchoptions);
prop.put("searchdomswitches_searchoptions_authSearch", authenticated);
prop.put("searchdomswitches_searchtext", sb.getConfigBool("search.text", true) ? 1 : 0);
prop.put("searchdomswitches_searchaudio", sb.getConfigBool("search.audio", true) ? 1 : 0);
prop.put("searchdomswitches_searchvideo", sb.getConfigBool("search.video", true) ? 1 : 0);
@ -159,7 +172,7 @@ public class index {
* @param authenticatedUserName the name of the currently authenticated user or null
*/
private static void handleTopNavBarLoginSection(final RequestHeader header, final Switchboard sb,
final serverObjects prop, String authenticatedUserName) {
final serverObjects prop, final String authenticatedUserName) {
final boolean showLogin = sb.getConfigBool(SwitchboardConstants.SEARCH_PUBLIC_TOP_NAV_BAR_LOGIN,
SwitchboardConstants.SEARCH_PUBLIC_TOP_NAV_BAR_LOGIN_DEFAULT);
if(showLogin) {

@ -109,9 +109,9 @@ Use the RSS search result format to add static searches to your RSS reader, if y
#(/resortEnabled)#
</div>
</div>
#(authorized)#::
#(authSearch)#::
<input type="hidden" name="auth" id="auth" value=""/>
#(/authorized)#
#(/authSearch)#
<input type="hidden" name="contentdom" id="contentdom" value="#[contentdom]#" />
<input type="hidden" name="former" value="#[former]#" />
<input type="hidden" name="maximumRecords" value="#[count]#" />

@ -137,6 +137,7 @@ public class yacysearch {
final servletProperties prop = new servletProperties();
prop.put("topmenu", sb.getConfigBool("publicTopmenu", true) ? 1 : 0);
prop.put("authSearch", authenticatedUserName != null);
// produce vocabulary navigation sidebars
Collection<Tagging> vocabularies = LibraryProvider.autotagging.getVocabularies();
@ -206,7 +207,7 @@ public class yacysearch {
return prop;
}
if (post.containsKey("auth") && !extendedSearchRights) {
if (post.containsKey("auth") && authenticatedUserName == null) {
/*
* Access to authentication protected features is explicitely requested here
* but no authentication is provided : ask now for authentication.
@ -784,7 +785,7 @@ public class yacysearch {
RequestHeader.FileType.HTML,
0,
theQuery,
suggestion, true, extendedSearchRights).toString());
suggestion, true, authenticatedUserName != null).toString());
prop.put("didYouMean_suggestions_" + meanCount + "_sep", "|");
meanCount++;
} catch (final ConcurrentModificationException e) {
@ -862,7 +863,7 @@ public class yacysearch {
* eventually including fetched results with higher ranks from the Solr and RWI stacks */
prop.put("resortEnabled", !jsResort && global && !stealthmode && theSearch.resortCacheAllowed.availablePermits() > 0 ? 1 : 0);
prop.put("resortEnabled_url",
QueryParams.navurlBase(RequestHeader.FileType.HTML, theQuery, null, true, extendedSearchRights)
QueryParams.navurlBase(RequestHeader.FileType.HTML, theQuery, null, true, authenticatedUserName != null)
.append("&startRecord=").append(startRecord).append("&resortCachedResults=true")
.toString());

@ -44,7 +44,7 @@
#(showMetadata)#::<span role="separator" aria-orientation="vertical">&nbsp;|&nbsp;</span><a href="solr/select?q=id:%22#[urlhash]#%22&defType=edismax&start=0&rows=1&core=collection1&wt=html" target="_blank">Metadata</a>#(/showMetadata)#
#(showParser)#::<span role="separator" aria-orientation="vertical">&nbsp;|&nbsp;</span><a href="ViewFile.html?urlHash=#[urlhash]#&amp;words=#[words]#" target="_blank">Parser</a>#(/showParser)#
#(showCitation)#::<span role="separator" aria-orientation="vertical">&nbsp;|&nbsp;</span><a href="api/citation.html?hash=#[urlhash]#&filter=true" target="_blank">Citations</a>#(/showCitation)#
#(showPictures)#::<span role="separator" aria-orientation="vertical">&nbsp;|&nbsp;</span><a href="yacysearch.html?contentdom=image#(authorized)#::&auth#(/authorized)#&url=#[link]#&query=#[former]#+inurl:#[link]#" target="_blank">Pictures</a>#(/showPictures)#
#(showPictures)#::<span role="separator" aria-orientation="vertical">&nbsp;|&nbsp;</span><a href="yacysearch.html?contentdom=image#(authSearch)#::&auth#(/authSearch)#&url=#[link]#&query=#[former]#+inurl:#[link]#" target="_blank">Pictures</a>#(/showPictures)#
#(showCache)#::<span role="separator" aria-orientation="vertical">&nbsp;|&nbsp;</span><a href="CacheResource_p.html?url=#[link]#" target="_blank">Cache</a>#(/showCache)#
#(showProxy)#::<span role="separator" aria-orientation="vertical">&nbsp;|&nbsp;</span><a href="proxy.html?url=#[link]#" target="_blank">View via proxy</a>#(/showProxy)#
#(showHostBrowser)#::<span role="separator" aria-orientation="vertical">&nbsp;|&nbsp;</span><a href="HostBrowser.html?path=#[link]#"><img src="env/grafics/minitree.png" width="15" height="8" title="Browse index" alt="Browse index"/></a>#(/showHostBrowser)#

@ -103,13 +103,12 @@ public class yacysearchitem {
final boolean adminAuthenticated = sb.verifyAuthentication(header);
final UserDB.Entry user = sb.userDB != null ? sb.userDB.getUser(header) : null;
final boolean userAuthenticated = (user != null && user.hasRight(UserDB.AccessRight.EXTENDED_SEARCH_RIGHT));
final boolean authenticated = adminAuthenticated || userAuthenticated;
final boolean authenticated = adminAuthenticated || user != null;
final int item = post.getInt("item", -1);
final RequestHeader.FileType fileType = header.fileType();
if (post.containsKey("auth") && !authenticated) {
if (post.containsKey("auth") && !adminAuthenticated && user == null) {
/*
* Access to authentication protected features is explicitely requested here
* but no authentication is provided : ask now for authentication.
@ -178,6 +177,7 @@ public class yacysearchitem {
prop.putXML("content_title-xml", result.title());
prop.putJSON("content_title-json", result.title());
prop.putHTML("content_showPictures_link", resultUrlstring);
prop.put("content_showPictures_authSearch", authenticated);
/* Add information about the current search navigators to let browser refresh yacysearchtrailer only if needed */
prop.put("content_nav-generation", theSearch.getNavGeneration());

@ -72,12 +72,11 @@ public class yacysearchtrailer {
final boolean adminAuthenticated = sb.verifyAuthentication(header);
final UserDB.Entry user = sb.userDB != null ? sb.userDB.getUser(header) : null;
final boolean userAuthenticated = (user != null && user.hasRight(UserDB.AccessRight.EXTENDED_SEARCH_RIGHT));
final boolean authenticated = adminAuthenticated || userAuthenticated;
final boolean authenticated = adminAuthenticated || user != null;
if (post.containsKey("auth") && !authenticated) {
/*
* Access to authentication protected features is explicitely requested here
* Authenticated search is explicitely requested here
* but no authentication is provided : ask now for authentication.
* Wihout this, after timeout of HTTP Digest authentication nonce, browsers no more send authentication information
* and as this page is not private, protected features would simply be hidden without asking browser again for authentication.

@ -28,11 +28,13 @@ package net.yacy.data;
import java.io.File;
import java.io.IOException;
import java.security.Principal;
import java.util.Calendar;
import java.util.Date;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Locale;
import java.util.Map;
import java.util.Random;
import javax.servlet.http.Cookie;
@ -139,34 +141,51 @@ public final class UserDB {
/**
* Use a ProxyAuth Value to authenticate user from HttpHeader.Authentication.
* This supports only Basic authentication
* @param auth "BASIC " followed by base64 Encoded String, which contains "username:pw" for basic authentication
* @param header the current request HTTP headers including 'Authorization' header :
* either "BASIC " followed by base64 Encoded String, which contains "username:pw" for basic authentication,
* or "Digest" followed by valid user name, realm, nonce and other necessary parameters.
* @return the user entry when the authentication header contains valid authentication information or null
*/
public Entry proxyAuth(final String authHeader) {
public Entry proxyAuth(final RequestHeader header) {
Entry entry = null;
if (authHeader != null) {
if (authHeader.toUpperCase().startsWith(HttpServletRequest.BASIC_AUTH)) {
String auth = authHeader.substring(6); // take out prefix "BASIC"
final String[] tmp = Base64Order.standardCoder.decodeString(auth.trim()).split(":");
if (tmp.length == 2) {
entry = this.passwordAuth(tmp[0], tmp[1]);
if (entry == null) {
entry = this.md5Auth(tmp[0], tmp[1]);
}
if(header == null) {
return entry;
}
final String authHeader = header.get(RequestHeader.AUTHORIZATION, "").trim();
if (authHeader.toUpperCase(Locale.ROOT).startsWith(HttpServletRequest.BASIC_AUTH)) {
String auth = authHeader.substring(6); // take out prefix "BASIC"
final String[] tmp = Base64Order.standardCoder.decodeString(auth.trim()).split(":");
if (tmp.length == 2) {
entry = this.passwordAuth(tmp[0], tmp[1]);
if (entry == null) {
entry = this.md5Auth(tmp[0], tmp[1]);
}
}
} else if (authHeader.toUpperCase(Locale.ROOT).startsWith(HttpServletRequest.DIGEST_AUTH)) {
// handle DIGEST auth by servlet container
final Principal authenticatedUser = header.getUserPrincipal();
if (authenticatedUser != null && authenticatedUser.getName() != null) { // user is authenticated by Servlet container
entry = getEntry(authenticatedUser.getName());
}
}
return entry;
}
/**
* @param header the HTTP request headers
* @return the authenticated user with valid authentication information found in the headers or null
*/
public Entry getUser(final RequestHeader header){
return getUser(header.get(RequestHeader.AUTHORIZATION), header.getCookies());
return getUser(header, header.getCookies());
}
public Entry getUser(final String auth, final Cookie[] cookies){
Entry entry=null;
if(auth != null) {
entry=proxyAuth(auth);
}
/**
* @param header HTTP request headers
* @param cookies eventual client cookies
* @return the authenticated user entry from headers or cookies or null
*/
public Entry getUser(final RequestHeader header, final Cookie[] cookies){
Entry entry = proxyAuth(header);
if(entry == null && cookies != null) {
entry=cookieAuth(cookies);
}
@ -174,24 +193,25 @@ public final class UserDB {
}
/**
* Determine if a user has admin rights from a authorisation http-headerfield.
* Determine if a user has admin rights from a 'Authorisation' http header field.
* Tests both userDB and old style adminpw.
*
* @param auth http-headerline for authorisation.
* @param cookies
*/
public boolean hasAdminRight(final String auth, final Cookie[] cookies) {
final Entry entry = getUser(auth, cookies);
public boolean hasAdminRight(final RequestHeader header, final Cookie[] cookies) {
final Entry entry = getUser(header, cookies);
return (entry != null) ? entry.hasRight(AccessRight.ADMIN_RIGHT) : false;
}
/**
* Use ProxyAuth String to authenticate user and save IP/username for ipAuth.
* @param auth base64 Encoded String, which contains "username:pw".
* Use HTTP headers to authenticate user and save IP/username for ipAuth.
* @param header HTTP request headers including 'Authorization' header with either base64 Encoded String, which contains "username:pw",
* or Digest authentication information including notably user name, realm, and nonce.
* @param ip IP address.
*/
public Entry proxyAuth(final String auth, final String ip) {
final Entry entry = proxyAuth(auth);
public Entry proxyAuth(final RequestHeader header, final String ip) {
final Entry entry = proxyAuth(header);
if (entry != null) {
entry.updateLastAccess(false);
this.ipUsers.put(ip, entry.getUserName());

@ -3789,7 +3789,7 @@ public final class Switchboard extends serverSwitch {
}
// authorization by hit in userDB (authtype username:encodedpassword - handed over by DefaultServlet)
if ( this.userDB.hasAdminRight(realmProp, requestHeader.getCookies()) ) {
if ( this.userDB.hasAdminRight(requestHeader, requestHeader.getCookies()) ) {
adminAuthenticationLastAccess = System.currentTimeMillis();
return 4; //return, because 4=max
}

Loading…
Cancel
Save