From 0c754dd7947050d60f536cd457d433a43e46862b Mon Sep 17 00:00:00 2001 From: reger Date: Fri, 17 Jan 2014 00:02:23 +0100 Subject: [PATCH] implemented DIGEST authentication, which is for remote login more secure as BASIC were pwd is transmitted near clear text (B64enc). This has some implication as RFC 2617 requires and recommends a password hash MD5(user:realm:pwd) for DIGEST. !!! before activating DIGEST you have to reassign all passwords !!! to allow new calculation of the hash - default authentication is still BASIC - configuration at this time only manually in (DATA/settings) or defaults/web.xml ( - the realmname is in defaults/yacy.init adminRealm=YaCy-AdminUI - fyi: the realmname is shown on login screen - changing the realm name invalidates all passwords - but for security you are encouraged to do so (as localhostadmin) - implemented to support both, old hashes for BASIC and new hashes for BASIC and DIGEST - to differentiate old / new hash the in Jetty used hash-prefix "MD5:" is used for new pwd-hashes ( "MD5:hash" ) --- defaults/web.xml | 13 +-- defaults/yacy.init | 10 ++ htroot/ConfigAccounts_p.java | 12 ++- htroot/SettingsAck_p.java | 2 +- htroot/User.java | 47 ++++----- source/net/yacy/data/UserDB.java | 61 +++++------- .../net/yacy/http/Jetty8HttpServerImpl.java | 14 +-- .../net/yacy/http/YaCyLegacyCredential.java | 99 ++++++++++++------- source/net/yacy/http/YaCyLoginService.java | 86 +++++++--------- .../http/servlets/YaCyDefaultServlet.java | 34 +++++-- source/net/yacy/search/Switchboard.java | 43 ++++++-- .../net/yacy/search/SwitchboardConstants.java | 3 + 12 files changed, 246 insertions(+), 178 deletions(-) diff --git a/defaults/web.xml b/defaults/web.xml index 2ab6c9127..bc28c9597 100644 --- a/defaults/web.xml +++ b/defaults/web.xml @@ -68,18 +68,19 @@ - + BASIC - YaCy Admin Interface - --> - + - diff --git a/defaults/yacy.init b/defaults/yacy.init index 6e4d490d1..618119cf9 100644 --- a/defaults/yacy.init +++ b/defaults/yacy.init @@ -400,6 +400,16 @@ adminAccountUserName=admin # inaccessibility for installations on headless servers. adminAccountForLocalhost=true +# adminRealm: a internal name (like a group name) for the login setting of the admin frontend +# ATTENTION: changing this name will invalidate all currently password hashes +# - With DIGEST authentication mode is this realm name of generated password hashes +# (RFC 2617 standard and recommendation). If you want to share password configuration +# with additional machines they have to belong to the same realm +# - authentication defaults to BASIC +# - and can be configured in defaults/web.xml , tag + +adminRealm=YaCy-AdminUI + # if you are running a principal peer, you must update the following variables # The upload method that should be used to upload the seed-list file to # a public accessible webserver where it can be loaded by other peers. diff --git a/htroot/ConfigAccounts_p.java b/htroot/ConfigAccounts_p.java index e64ecc2d6..4bd5c3c1b 100644 --- a/htroot/ConfigAccounts_p.java +++ b/htroot/ConfigAccounts_p.java @@ -33,8 +33,6 @@ import java.util.EnumMap; import java.util.HashMap; import java.util.Iterator; import java.util.Map; - -import net.yacy.cora.order.Base64Order; import net.yacy.cora.order.Digest; import net.yacy.cora.protocol.Domains; import net.yacy.cora.protocol.RequestHeader; @@ -65,7 +63,8 @@ public class ConfigAccounts_p { // may be overwritten if new password is given if (user.length() > 0 && pw1.length() > 3 && pw1.equals(pw2)) { // check passed. set account: - env.setConfig(SwitchboardConstants.ADMIN_ACCOUNT_B64MD5, Digest.encodeMD5Hex(Base64Order.standardCoder.encodeString(user + ":" + pw1))); + // old: // env.setConfig(SwitchboardConstants.ADMIN_ACCOUNT_B64MD5, Digest.encodeMD5Hex(Base64Order.standardCoder.encodeString(user + ":" + pw1))); + env.setConfig(SwitchboardConstants.ADMIN_ACCOUNT_B64MD5, "MD5:"+Digest.encodeMD5Hex(user + ":" + sb.getConfig(SwitchboardConstants.ADMIN_REALM,"YaCy")+":"+ pw1)); env.setConfig(SwitchboardConstants.ADMIN_ACCOUNT, ""); env.setConfig(SwitchboardConstants.ADMIN_ACCOUNT_USER_NAME,user); } else { @@ -193,7 +192,9 @@ public class ConfigAccounts_p { if( "newuser".equals(post.get("current_user"))){ //new user if (!"".equals(pw1)) { //change only if set - mem.put(UserDB.Entry.MD5ENCODED_USERPWD_STRING, Digest.encodeMD5Hex(username + ":" + pw1)); + // MD5 according to HTTP Digest RFC 2617 (3.2.2) name:realm:pwd (use seed.hash as realm) + // with prefix of encoding method (supported MD5: ) + mem.put(UserDB.Entry.MD5ENCODED_USERPWD_STRING, "MD5:"+Digest.encodeMD5Hex(username + ":" + sb.getConfig(SwitchboardConstants.ADMIN_REALM,"YaCy")+":"+pw1)); } mem.put(UserDB.Entry.USER_FIRSTNAME, firstName); @@ -222,7 +223,8 @@ public class ConfigAccounts_p { if (entry != null) { try{ if (!"".equals(pw1)) { - entry.setProperty(UserDB.Entry.MD5ENCODED_USERPWD_STRING, Digest.encodeMD5Hex(username+":"+pw1)); + // with prefix of encoding method (supported MD5: ) + entry.setProperty(UserDB.Entry.MD5ENCODED_USERPWD_STRING, "MD5:"+Digest.encodeMD5Hex(username+ ":" + sb.getConfig(SwitchboardConstants.ADMIN_REALM,"YaCy") + ":"+pw1)); } entry.setProperty(UserDB.Entry.USER_FIRSTNAME, firstName); diff --git a/htroot/SettingsAck_p.java b/htroot/SettingsAck_p.java index ef256b055..8d982eff1 100644 --- a/htroot/SettingsAck_p.java +++ b/htroot/SettingsAck_p.java @@ -92,7 +92,7 @@ public class SettingsAck_p { return prop; } // check passed. set account: - env.setConfig(SwitchboardConstants.ADMIN_ACCOUNT_B64MD5, Digest.encodeMD5Hex(Base64Order.standardCoder.encodeString(user + ":" + pw1))); + env.setConfig(SwitchboardConstants.ADMIN_ACCOUNT_B64MD5, "MD5:"+Digest.encodeMD5Hex(user + ":" + sb.getConfig(SwitchboardConstants.ADMIN_REALM,"YaCy") + ":" + pw1)); env.setConfig(SwitchboardConstants.ADMIN_ACCOUNT, ""); env.setConfig(SwitchboardConstants.ADMIN_ACCOUNT_USER_NAME, user); prop.put("info", "5");//admin account changed diff --git a/htroot/User.java b/htroot/User.java index 85f99e42c..87bf36a16 100644 --- a/htroot/User.java +++ b/htroot/User.java @@ -125,28 +125,31 @@ public class User{ } } - if(post!= null && entry != null){ - if(post.containsKey("changepass")){ - prop.put("status", "1"); //password - if(entry.getMD5EncodedUserPwd().equals(Digest.encodeMD5Hex(entry.getUserName()+":"+post.get("oldpass", "")))){ - if(post.get("newpass").equals(post.get("newpass2"))){ - if(!post.get("newpass", "").equals("")){ - try { - entry.setProperty(UserDB.Entry.MD5ENCODED_USERPWD_STRING, Digest.encodeMD5Hex(entry.getUserName()+":"+post.get("newpass", ""))); - prop.put("status_password", "0"); //changes - } catch (final Exception e) { - ConcurrentLog.logException(e); - } - }else{ - prop.put("status_password", "3"); //empty - } - }else{ - prop.put("status_password", "2"); //pws do not match - } - }else{ - prop.put("status_password", "1"); //old pw wrong - } - } + if (post != null && entry != null) { + if (post.containsKey("changepass")) { + prop.put("status", "1"); //password + + if (entry.getMD5EncodedUserPwd().startsWith("MD5:") ? + entry.getMD5EncodedUserPwd().equals("MD5:"+Digest.encodeMD5Hex(entry.getUserName() + ":" + sb.getConfig(SwitchboardConstants.ADMIN_REALM,"YaCy") + ":" + post.get("oldpass", ""))) : + entry.getMD5EncodedUserPwd().equals(Digest.encodeMD5Hex(entry.getUserName() + ":" + post.get("oldpass", "")))) { + if (post.get("newpass").equals(post.get("newpass2"))) { + if (!post.get("newpass", "").equals("")) { + try { + entry.setProperty(UserDB.Entry.MD5ENCODED_USERPWD_STRING, "MD5:" + Digest.encodeMD5Hex(entry.getUserName() + ":" + sb.getConfig(SwitchboardConstants.ADMIN_REALM,"YaCy") + ":" + post.get("newpass", ""))); + prop.put("status_password", "0"); //changes + } catch (final Exception e) { + ConcurrentLog.logException(e); + } + } else { + prop.put("status_password", "3"); //empty + } + } else { + prop.put("status_password", "2"); //pws do not match + } + } else { + prop.put("status_password", "1"); //old pw wrong + } + } } if(post!=null && post.containsKey("logout")){ prop.put("logged-in", "0"); diff --git a/source/net/yacy/data/UserDB.java b/source/net/yacy/data/UserDB.java index 8659e0805..d7db67f99 100644 --- a/source/net/yacy/data/UserDB.java +++ b/source/net/yacy/data/UserDB.java @@ -47,6 +47,8 @@ import net.yacy.cora.util.SpaceExceededException; import net.yacy.kelondro.blob.MapHeap; import net.yacy.kelondro.util.FileUtils; import net.yacy.kelondro.util.kelondroException; +import net.yacy.search.Switchboard; +import net.yacy.search.SwitchboardConstants; public final class UserDB { @@ -126,16 +128,19 @@ public final class UserDB { } /** - * Use a ProxyAuth String to authenticate user. + * Use a ProxyAuth Value to authenticate user. * @param auth base64 Encoded String, which contains "username:pw". */ public Entry proxyAuth(final String auth) { Entry entry = null; - + if (auth != null) { - final String[] tmp = Base64Order.standardCoder.decodeString(auth.trim().substring(6)).split(":"); + 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]); + } } } return entry; @@ -199,24 +204,26 @@ public final class UserDB { } return null; } - + public Entry passwordAuth(final String user, final String password) { final Entry entry = this.getEntry(user); final String md5pwd; - if (entry != null && (md5pwd = entry.getMD5EncodedUserPwd()) != null && md5pwd.equals(Digest.encodeMD5Hex(user+":"+password))) { - if (entry.isLoggedOut()){ - try { - entry.setProperty(Entry.LOGGED_OUT, "false"); - } catch (final Exception e) { - ConcurrentLog.logException(e); - } + if (entry != null && (md5pwd = entry.getMD5EncodedUserPwd()) != null) { + + boolean authok; + if (md5pwd.startsWith("MD5:")) { // DIGEST style + authok = md5pwd.equals("MD5:" + Digest.encodeMD5Hex(user + ":" + + Switchboard.getSwitchboard().getConfig(SwitchboardConstants.ADMIN_REALM, "YaCy") + ":" + password)); + } else { + authok = md5pwd.equals(Digest.encodeMD5Hex(user + ":" + password)); + } + if (authok) { return entry; } - return entry; } return null; } - + public Entry passwordAuth(final String user, final String password, final String ip){ final Entry entry = passwordAuth(user, password); if (entry != null) { @@ -228,17 +235,9 @@ public final class UserDB { public Entry md5Auth(final String user, final String md5) { final Entry entry = this.getEntry(user); - if (entry != null && entry.getMD5EncodedUserPwd().equals(md5)) { - if (entry.isLoggedOut()){ - try { - entry.setProperty(Entry.LOGGED_OUT, "false"); - } catch (final Exception e) { - ConcurrentLog.logException(e); - } - return null; - } + if (entry != null && entry.getMD5EncodedUserPwd().endsWith(md5)) { // user pwd migth have prefix "MD5:" return entry; - } + } return null; } @@ -328,8 +327,7 @@ public final class UserDB { public class Entry { public static final String MD5ENCODED_USERPWD_STRING = "MD5_user:pwd"; - public static final String AUTHENTICATION_METHOD = "auth_method"; - public static final String LOGGED_OUT = "loggedOut"; + public static final String USER_FIRSTNAME = "firstName"; public static final String USER_LASTNAME = "lastName"; public static final String USER_ADDRESS = "address"; @@ -364,10 +362,6 @@ public final class UserDB { this.mem = (mem == null) ? new HashMap() : mem; - - if (mem == null || !mem.containsKey(AUTHENTICATION_METHOD)) { - this.mem.put(AUTHENTICATION_METHOD,"yacy"); - } this.oldDate=Calendar.getInstance(); this.newDate=Calendar.getInstance(); } @@ -550,10 +544,6 @@ public final class UserDB { return (this.mem.containsKey(accessRight.toString())) ? this.mem.get(accessRight.toString()).equals("true") : false; } - public boolean isLoggedOut(){ - return (this.mem.containsKey(LOGGED_OUT) ? this.mem.get(LOGGED_OUT).equals("true") : false); - } - public void logout(final String ip, final String logintoken){ logout(ip); if(cookieUsers.containsKey(logintoken)){ @@ -563,7 +553,6 @@ public final class UserDB { public void logout(final String ip) { try { - setProperty(LOGGED_OUT, "true"); if (ipUsers.containsKey(ip)){ ipUsers.remove(ip); } @@ -572,10 +561,6 @@ public final class UserDB { } } - public void logout(){ - logout("xxxxxx"); - } - @Override public String toString() { final StringBuilder str = new StringBuilder(); diff --git a/source/net/yacy/http/Jetty8HttpServerImpl.java b/source/net/yacy/http/Jetty8HttpServerImpl.java index e7ad4d29b..7bea55583 100644 --- a/source/net/yacy/http/Jetty8HttpServerImpl.java +++ b/source/net/yacy/http/Jetty8HttpServerImpl.java @@ -45,9 +45,8 @@ import net.yacy.http.servlets.SolrServlet; import net.yacy.http.servlets.YaCyDefaultServlet; import net.yacy.http.servlets.YaCyProxyServlet; import net.yacy.search.Switchboard; +import net.yacy.search.SwitchboardConstants; import net.yacy.utils.PKCS12Tool; - -import org.eclipse.jetty.security.LoginService; import org.eclipse.jetty.server.Connector; import org.eclipse.jetty.server.Handler; import org.eclipse.jetty.server.Server; @@ -160,14 +159,17 @@ public class Jetty8HttpServerImpl implements YaCyHttpServer { allrequesthandlers.addHandler(htrootContext); allrequesthandlers.addHandler(new DefaultHandler()); // if not handled by other handler - // wrap all handlers by security handler + YaCyLoginService loginService = new YaCyLoginService(); + // this is very important (as it is part of the user password hash) + // changes will ivalidate all current existing user-password-hashes (from userDB) + loginService.setName(sb.getConfig(SwitchboardConstants.ADMIN_REALM,"YaCy")); + Jetty8YaCySecurityHandler securityHandler = new Jetty8YaCySecurityHandler(); - LoginService loginService = new YaCyLoginService(); securityHandler.setLoginService(loginService); - securityHandler.setRealmName(loginService.getName()); htrootContext.setSecurityHandler(securityHandler); - + + // wrap all handlers Handler crashHandler = new CrashProtectionHandler(allrequesthandlers); // check server access restriction and add IPAccessHandler if restrictions are needed // otherwise don't (to save performance) diff --git a/source/net/yacy/http/YaCyLegacyCredential.java b/source/net/yacy/http/YaCyLegacyCredential.java index 3e139a4ae..d83b78cc4 100644 --- a/source/net/yacy/http/YaCyLegacyCredential.java +++ b/source/net/yacy/http/YaCyLegacyCredential.java @@ -26,6 +26,8 @@ package net.yacy.http; import net.yacy.cora.order.Base64Order; import net.yacy.cora.order.Digest; +import net.yacy.search.Switchboard; +import net.yacy.search.SwitchboardConstants; import net.yacy.server.serverAccessTracker; import org.eclipse.jetty.util.security.Credential; @@ -34,27 +36,39 @@ import org.eclipse.jetty.util.security.Credential; /** * implementation of YaCy's old admin password as jetty Credential + * supporting BASIC and DIGEST authentication + * and using MD5 encryptet passwords/credentials. Following RFC recommendation (to use the realm in MD5 hash) + * expecting a MD5 hash in format MD5( username:realm:password ), realm configured in yacy.init adminRealm + * (exception: old style credential MD5( username:password ) still accepted with BASIC auth) + * */ public class YaCyLegacyCredential extends Credential { private static final long serialVersionUID = -3527894085562480001L; - private String hash; + + private String hash; // remember password hash (for new style with prefix of used encryption supported "MD5:" ) private String foruser; // remember the user as YaCy credential is username:pwd (not just pwd) private boolean isBase64enc; // remember hash encoding false = encodeMD5Hex(usr:pwd) ; true = encodeMD5Hex(Base64Order.standardCoder.encodeString(usr:pw)) - + private Credential c; + /** - * internal hash function + * internal hash function for admin account * - * @param clear password + * @param pw clear password * @return hash string */ - public static String calcHash(String pw) { + public static String calcHash(String pw) { // old style hash return Digest.encodeMD5Hex(Base64Order.standardCoder.encodeString(pw)); } @Override public boolean check(Object credentials) { - if (credentials instanceof String) { + + if (credentials instanceof Credential) { // for DIGEST auth + return ((Credential) credentials).check(c); + + } + if (credentials instanceof String) { // for BASIC auth final String pw = (String) credentials; if (isBase64enc) { if (serverAccessTracker.timeSinceAccessFromLocalhost() < 100) { @@ -63,42 +77,55 @@ public class YaCyLegacyCredential extends Credential { // the cleartext password is not stored anywhere, but we must find a way to allow scripts to steer a peer. // this is the exception that makes that possible. // TODO: it should be better to check the actual access IP here, but that is not handed over to Credential classes :( - if (pw.equals(this.hash)) return true; + if ((pw).equals(this.hash)) return true; } + // exception for admin use old style MD5hash (user:password) return calcHash(foruser + ":" + pw).equals(this.hash); // for admin user } - // normal users - return Digest.encodeMD5Hex(foruser + ":" + pw).equals(this.hash); + + // normal users (and new admin pwd) + if (hash.startsWith(MD5.__TYPE) && hash != null) { + return (Digest.encodeMD5Hex(foruser + ":" + Switchboard.getSwitchboard().getConfig(SwitchboardConstants.ADMIN_REALM,"YaCy")+":" + pw).equals(hash.substring(4))); + } else { // special check for old style password hash (prior v1.67/9501 15.1.2014) + return Digest.encodeMD5Hex(foruser + ":" + pw).equals(hash); + } } throw new UnsupportedOperationException(); } - /** - * create Credential object from config file hash - * @param configHash hash as in config file hash(adminuser:pwd) - * @return - */ - public static Credential getCredentialsFromConfig(String username, String configHash) { - YaCyLegacyCredential c = new YaCyLegacyCredential(); - c.foruser = username; - c.isBase64enc = true; - c.hash = configHash; - return c; - } - - /** - * create Credential object from password - * @param username - * @param configHash encodeMD5Hex("user:pwd") as stored in UserDB - * @return - */ - public static Credential getCredentials(String username, String configHash) { - YaCyLegacyCredential c = new YaCyLegacyCredential(); - c.foruser = username; - c.isBase64enc = false; - c.hash = configHash; - //c.hash = calcHash(user + ":" + password); - return c; - } + /** + * create Credential object from config file hash + * + * @param configHash hash as in config file hash(adminuser:pwd) + * @return + */ + public static Credential getCredentialForAdmin(String username, String configHash) { + YaCyLegacyCredential yc = new YaCyLegacyCredential(); + if (configHash.startsWith("MD5:")) { + yc.isBase64enc = false; + yc.c = Credential.getCredential(configHash); + } else { + yc.isBase64enc = true; + } + yc.foruser = username; + yc.hash = configHash; + return yc; + } + + /** + * create Credential object from password + * + * @param username + * @param configHash encodeMD5Hex("user:realm:pwd") as stored in UserDB + * @return + */ + public static Credential getCredentialForUserDB(String username, String configHash) { + YaCyLegacyCredential yc = new YaCyLegacyCredential(); + yc.c = Credential.getCredential(configHash); // creates a MD5 hash credential + yc.foruser = username; + yc.isBase64enc = false; + yc.hash = configHash; + return yc; + } } diff --git a/source/net/yacy/http/YaCyLoginService.java b/source/net/yacy/http/YaCyLoginService.java index 2f34a74c6..4f9aff339 100644 --- a/source/net/yacy/http/YaCyLoginService.java +++ b/source/net/yacy/http/YaCyLoginService.java @@ -25,80 +25,68 @@ package net.yacy.http; import java.io.IOException; -import java.security.Principal; - -import javax.security.auth.Subject; - +import java.util.ArrayList; import net.yacy.data.UserDB.AccessRight; import net.yacy.data.UserDB.Entry; import net.yacy.search.Switchboard; import net.yacy.search.SwitchboardConstants; - -import org.eclipse.jetty.security.IdentityService; import org.eclipse.jetty.security.LoginService; import org.eclipse.jetty.security.MappedLoginService; import org.eclipse.jetty.server.UserIdentity; import org.eclipse.jetty.util.security.Credential; /** - * jetty login service, provides one admin user + * jetty login service, provides admin and YaCy.UserDB users with role assignment + * with DIGEST auth by default Jetty uses the name of the loginSevice as realmname (which is part of all password hashes) */ public class YaCyLoginService extends MappedLoginService implements LoginService { - @Override - public String getName() { - return "YaCy 'admin' Account (reset your password with bin/passwd.sh )"; - } - @Override protected UserIdentity loadUser(String username) { - + if (username == null || username.isEmpty()) return null; // quick exit + final Switchboard sb = Switchboard.getSwitchboard(); String adminuser = sb.getConfig(SwitchboardConstants.ADMIN_ACCOUNT_USER_NAME, "admin"); + Credential credential = null; + String[] roles = null; if (username.equals(adminuser)) { final String adminAccountBase64MD5 = sb.getConfig(SwitchboardConstants.ADMIN_ACCOUNT_B64MD5, ""); - // in YaCy the credential hash is composed of username:pwd so the username is needed to create valid credential - // not just the password (as usually in Jetty). As the accountname for the std. adminuser is not stored a useridentity - // is created for current user (and the pwd checked against the stored username:pwd setting) - Credential credential = YaCyLegacyCredential.getCredentialsFromConfig(username, adminAccountBase64MD5); + // in YaCy the credential hash is composed of username:pwd so the username is needed to create valid credential + // not just the password (as usually in Jetty). As the accountname for the std. adminuser is not stored a useridentity + // is created for current user (and the pwd checked against the stored username:pwd setting) + credential = YaCyLegacyCredential.getCredentialForAdmin(username, adminAccountBase64MD5); // TODO: YaCy user:pwd hashes should longterm likely be switched to separable username + pwd-hash entries // and/or the standard admin account username shuld be fix = "admin" - - Principal userPrincipal = new MappedLoginService.KnownUser(username, credential); - Subject subject = new Subject(); - subject.getPrincipals().add(userPrincipal); - subject.getPrivateCredentials().add(credential); - subject.setReadOnly(); - IdentityService is = getIdentityService(); - return is.newUserIdentity(subject, userPrincipal, new String[]{AccessRight.ADMIN_RIGHT.toString()}); - } - Entry user = sb.userDB.getEntry(username); - if (user != null) { - // assigning roles from userDB - String[] role = new String[AccessRight.values().length]; - int i = 0; - for (final AccessRight right : AccessRight.values()) { - if (user.hasRight(right)) { - role[i] = right.toString(); - i++; + roles = new String[]{AccessRight.ADMIN_RIGHT.toString()}; + } else { + Entry user = sb.userDB.getEntry(username); + if (user != null && user.getMD5EncodedUserPwd() != null) { + // assigning roles from userDB + ArrayList roletmp = new ArrayList(); + for (final AccessRight right : AccessRight.values()) { + if (user.hasRight(right)) { + roletmp.add(right.toString()); + } } + if (roletmp.size() > 0) roles = roletmp.toArray(new String[roletmp.size()]); + credential = YaCyLegacyCredential.getCredentialForUserDB(username, user.getMD5EncodedUserPwd()); + } + } + + if (credential != null) { + if (roles != null) { + return putUser(username, credential, roles); + } else { + return putUser(username, credential); // w/o role makes not much sense, but succeeds login.... } - Credential credential = YaCyLegacyCredential.getCredentials(username, user.getMD5EncodedUserPwd()); - Principal userPrincipal = new MappedLoginService.KnownUser(username, credential); - Subject subject = new Subject(); - subject.getPrincipals().add(userPrincipal); - subject.getPrivateCredentials().add(credential); - subject.setReadOnly(); - IdentityService is = getIdentityService(); - return is.newUserIdentity(subject, userPrincipal, role); } return null; } - - @Override - protected void loadUsers() throws IOException { - // don't load any users into MappedLoginService on startup - // we use loadUser for dynamic checking - } + + @Override + protected void loadUsers() throws IOException { + // don't load any users into MappedLoginService on startup + // we use loadUser for dynamic checking + } } diff --git a/source/net/yacy/http/servlets/YaCyDefaultServlet.java b/source/net/yacy/http/servlets/YaCyDefaultServlet.java index c5de34758..bf2da4e5e 100644 --- a/source/net/yacy/http/servlets/YaCyDefaultServlet.java +++ b/source/net/yacy/http/servlets/YaCyDefaultServlet.java @@ -48,9 +48,14 @@ import javax.servlet.http.HttpServletResponse; import net.yacy.cora.date.GenericFormatter; import net.yacy.cora.document.analysis.Classification; +import net.yacy.cora.order.Base64Order; +import net.yacy.cora.protocol.Domains; import net.yacy.cora.protocol.HeaderFramework; import net.yacy.cora.protocol.RequestHeader; import net.yacy.cora.util.ConcurrentLog; +import net.yacy.data.UserDB; +import net.yacy.data.UserDB.AccessRight; +import net.yacy.data.UserDB.Entry; import net.yacy.http.ProxyHandler; import net.yacy.kelondro.util.FileUtils; import net.yacy.kelondro.util.MemoryControl; @@ -644,11 +649,28 @@ public class YaCyDefaultServlet extends HttpServlet { legacyRequestHeader.put(HeaderFramework.CONNECTION_PROP_PATH, target); legacyRequestHeader.put(HeaderFramework.CONNECTION_PROP_EXT, targetExt); - // for userDB user legacyRequest expect login in Cookie (add one) - if (request.getUserPrincipal() != null) { - String userpassEncoded = request.getHeader("Authorization"); // e.g. "Basic xxXXxxXXxxXX" - if (userpassEncoded != null) { - legacyRequestHeader.setCookie("login", userpassEncoded); + if (legacyRequestHeader.containsKey(RequestHeader.AUTHORIZATION)) { + if (HttpServletRequest.BASIC_AUTH.equalsIgnoreCase(request.getAuthType())) { + } else { + // handle DIGEST auth for legacyHeader (create username:md5pwdhash + if (request.getUserPrincipal() != null) { + String userpassEncoded = request.getHeader(RequestHeader.AUTHORIZATION); // e.g. "Basic AdminMD5hash" + if (userpassEncoded != null) { + if (request.isUserInRole(AccessRight.ADMIN_RIGHT.toString()) && !Switchboard.getSwitchboard().getConfig(SwitchboardConstants.ADMIN_ACCOUNT_B64MD5,"").isEmpty()) { + // fake admin authentication for legacyRequestHeader (as e.g. DIGEST is not supported by legacyRequestHeader) + legacyRequestHeader.put(RequestHeader.AUTHORIZATION, HttpServletRequest.BASIC_AUTH + " " + + Switchboard.getSwitchboard().getConfig(SwitchboardConstants.ADMIN_ACCOUNT_B64MD5, "")); + } else { + // fake Basic auth header for Digest auth (Basic username:md5pwdhash) + String username = request.getRemoteUser(); + Entry user = Switchboard.getSwitchboard().userDB.getEntry(username); + if (user != null) { + legacyRequestHeader.put(RequestHeader.AUTHORIZATION, HttpServletRequest.BASIC_AUTH + " " + + username + ":" + user.getMD5EncodedUserPwd()); + } + } + } + } } } return legacyRequestHeader; @@ -842,7 +864,7 @@ public class YaCyDefaultServlet extends HttpServlet { // handle action auth: check if the servlets requests authentication if (templatePatterns.containsKey(serverObjects.ACTION_AUTHENTICATE)) { if (!request.authenticate(response)) { - return; + return; } //handle action forward } else if (templatePatterns.containsKey(serverObjects.ACTION_LOCATION)) { diff --git a/source/net/yacy/search/Switchboard.java b/source/net/yacy/search/Switchboard.java index b45b3190b..53947d450 100644 --- a/source/net/yacy/search/Switchboard.java +++ b/source/net/yacy/search/Switchboard.java @@ -3233,6 +3233,8 @@ public final class Switchboard extends serverSwitch { * http-authentify: auth-level 4 * * @param requestHeader + * - requestHeader..AUTHORIZATION = B64encode("adminname:password") or = B64encode("adminname:valueOf_Base64MD5cft") + * - adminAccountBase64MD5 = MD5(B64encode("adminname:password") or = "MD5:"+MD5("adminname:peername:password") * @return the auth-level as described above or 1 which means 'not authorized'. a 0 is returned in case of * fraud attempts */ @@ -3254,8 +3256,13 @@ public final class Switchboard extends serverSwitch { } // get the authorization string from the header - final String realmProp = (requestHeader.get(RequestHeader.AUTHORIZATION, "xxxxxx")).trim(); - final String realmValue = realmProp.substring(6); + final String realmProp = (requestHeader.get(RequestHeader.AUTHORIZATION, "")).trim(); + String realmValue = realmProp.isEmpty() ? null : realmProp.substring(6); // take out "BASIC " + + // authorization with admin keyword in configuration + if ( realmValue == null || realmValue.isEmpty() ) { + return 1; + } // security check against too long authorization strings if ( realmValue.length() > 256 ) { @@ -3264,24 +3271,42 @@ public final class Switchboard extends serverSwitch { // authorization by encoded password, only for localhost access String pass = Base64Order.standardCoder.encodeString(adminAccountUserName + ":" + adminAccountBase64MD5); - if ( accessFromLocalhost && (pass.equals(realmValue)) ) { + if ( accessFromLocalhost && (pass.equals(realmValue)) ) { // assume realmValue as is in cfg adminAuthenticationLastAccess = System.currentTimeMillis(); return 3; // soft-authenticated for localhost } - // authorization by hit in userDB - if ( this.userDB.hasAdminRight(realmProp, requestHeader.getHeaderCookies()) ) { + // authorization by hit in userDB (realm username:encodedpassword - handed over by DefaultServlet) + if ( this.userDB.hasAdminRight(realmValue, requestHeader.getHeaderCookies()) ) { adminAuthenticationLastAccess = System.currentTimeMillis(); return 4; //return, because 4=max } - // authorization with admin keyword in configuration - if ( realmValue == null || realmValue.isEmpty() ) { - return 1; + // athorization by BASIC auth (realmValue = "adminname:password") + if (adminAccountBase64MD5.startsWith("MD5:")) { + // handle new option adminAccountBase64MD5="MD5:xxxxxxx" = encodeMD5Hex ("adminname:peername:password") + String realmtmp = Base64Order.standardCoder.decodeString(realmValue); //decode to clear text + int i = realmtmp.indexOf(':'); + if (i > 4) { // put peer name in realmValue (>4 is correct to scipt "MD5:" and usernames are min 4 characters) + realmtmp = realmtmp.substring(0, i + 1) + sb.getConfig(SwitchboardConstants.ADMIN_REALM,"YaCy") + ":" + realmtmp.substring(i + 1); + + if (adminAccountBase64MD5.substring(4).equals(Digest.encodeMD5Hex(realmtmp))) { + adminAuthenticationLastAccess = System.currentTimeMillis(); + return 4; // hard-authenticated, all ok } - if ( adminAccountBase64MD5.equals(Digest.encodeMD5Hex(realmValue)) ) { + } else { + // handle DIGEST auth (realmValue = adminAccountBase (set for lecacyHeader in DefaultServlet for authenticated requests) + if (adminAccountBase64MD5.equals(realmValue)) { adminAuthenticationLastAccess = System.currentTimeMillis(); return 4; // hard-authenticated, all ok + } + } + } else { + // handle old option adminAccountBase64MD5="xxxxxxx" = encodeMD55Hex(encodeB64("adminname:password") + if (adminAccountBase64MD5.equals(Digest.encodeMD5Hex(realmValue))) { + adminAuthenticationLastAccess = System.currentTimeMillis(); + return 4; // hard-authenticated, all ok + } } return 1; } diff --git a/source/net/yacy/search/SwitchboardConstants.java b/source/net/yacy/search/SwitchboardConstants.java index a0853842d..2a23254b0 100644 --- a/source/net/yacy/search/SwitchboardConstants.java +++ b/source/net/yacy/search/SwitchboardConstants.java @@ -40,11 +40,14 @@ public final class SwitchboardConstants { *

public static final String ADMIN_ACCOUNT_B64MD5 = "adminAccountBase64MD5"

*

Name of the setting holding the authentication hash for the static admin-account. It is calculated * by first encoding username:password as Base64 and hashing it using {@link MapTools#encodeMD5Hex(String)}.

+ * With introduction of DIGEST authentication all passwords are MD5 encoded and calculatd as username:adminrealm:password + * To differentiate old and new admin passwords, use the new calculated passwords a "MD5:" prefix. */ public static final String ADMIN_ACCOUNT = "adminAccount"; public static final String ADMIN_ACCOUNT_B64MD5 = "adminAccountBase64MD5"; public static final String ADMIN_ACCOUNT_USER_NAME = "adminAccountUserName"; // by default 'admin' public static final String ADMIN_ACCOUNT_FOR_LOCALHOST = "adminAccountForLocalhost"; + public static final String ADMIN_REALM = "adminRealm"; public static final int CRAWLJOB_SYNC = 0; public static final int CRAWLJOB_STATUS = 1;