e2316d8ff89ed1beb085f9169c4c7ca344affb20 max Tue May 16 14:55:40 2017 -0700 Changes to hgLogin and hgSession to replace hgLogin with Apache's Basic authentication system, refs #19424, primarily motivated by CIRM but hopefully useful in other contexts diff --git src/hg/lib/wikiLink.c src/hg/lib/wikiLink.c index 0f361e0..2f1044a 100644 --- src/hg/lib/wikiLink.c +++ src/hg/lib/wikiLink.c @@ -1,50 +1,60 @@ -/* wikiLink - interoperate with a wiki site (share user identities). */ +/* wikiLink - originally used to interoperate with a wiki site (share user identities). + * With the Wiki Track removed these days, this file contains code related to user + * authentication. + * */ /* Copyright (C) 2014 The Regents of the University of California * See README in this or parent directory for licensing information. */ #include "common.h" #include "hash.h" #include "htmshell.h" #include "cheapcgi.h" #include "hgConfig.h" #include "hui.h" #include "md5.h" #include "web.h" #include "wikiLink.h" +#include "base64.h" // Flag to indicate that loginValidateCookies has been called: static boolean alreadyAuthenticated = FALSE; // Set by loginValidateCookies, used by wikiLinkUserName static boolean authenticated = FALSE; // If we need to change some cookies, store cookie strings here in case loginValidateCookies // is called multiple times (e.g. validate before cookie-writing, then later write cookies) static struct slName *cookieStrings = NULL; char *loginSystemName() /* Return the wiki host specified in hg.conf, or NULL. Allocd here. */ { return cloneString(cfgOption(CFG_LOGIN_SYSTEM_NAME)); } boolean loginSystemEnabled() /* Return TRUE if login.systemName parameter is defined in hg.conf . */ { return (cfgOption(CFG_LOGIN_SYSTEM_NAME) != NULL); } +boolean loginUseBasicAuth() +/* Return TRUE if login.basicAuth is on in hg.conf . */ +{ +return (cfgOptionBooleanDefault(CFG_LOGIN_BASICAUTH, FALSE)); +} + boolean wikiLinkEnabled() /* Return TRUE if all wiki.* parameters are defined in hg.conf . */ { return ((cfgOption(CFG_WIKI_HOST) != NULL) && (cfgOption(CFG_WIKI_USER_NAME_COOKIE) != NULL) && (cfgOption(CFG_WIKI_LOGGED_IN_COOKIE) != NULL)); } static char *wikiLinkLoggedInCookie() /* Return the cookie name specified in hg.conf as the wiki logged-in cookie, or a default. * Do not free result. */ { return cfgOptionDefault(CFG_WIKI_LOGGED_IN_COOKIE, "hgLoginIdKey"); } @@ -283,34 +293,108 @@ boolean loginUseHttps() /* Return TRUE unless https is disabled in hg.conf. */ { return cfgOptionBooleanDefault(CFG_LOGIN_USE_HTTPS, TRUE); } static char *loginUrl() /* Return the URL for the login host. */ { char buf[2048]; safef(buf, sizeof(buf), "http%s://%s/cgi-bin/hgLogin", loginUseHttps() ? "s" : "", wikiLinkHost()); return cloneString(buf); } +char* getHttpBasicToken() +/* Return HTTP Basic Auth Token or NULL. Result has to be freed. */ +{ +char *auth = getenv("HTTP_AUTHORIZATION"); +// e.g. "Basic bwF4OmQxUglhanM=" +if (auth==NULL) + return NULL; +char *token = cloneNotFirstWord(auth); +if (isEmpty(token)) + { + fprintf(stderr, "wikiLinkc.: Illegal format of HTTP Authorization Header?"); + return NULL; + } +return token; +} + +void printTokenErrorAndExit() +/* output an error message if http basic token is missing */ +{ + printf("Internal error: this server has HTTP Basic Authentication enabled in cgi-bin/hg.conf:%s.<br>", CFG_LOGIN_BASICAUTH); + puts("The Genome Browser cannot find an 'Authorization' header to the Genome Browser.<br>"); + puts("This website should only be reachable through a https connection that requires username and password.<p>"); + puts("If you have reached this website in a way that does not require a password, please contact your adminstrator.<p><p>"); + puts("If this was the case, for the administrator: "); + puts("Make sure that HTTP Basic Authentication is activated for the cgi-bin directory in the Apache Configuration. <p>"); + puts("If it is and you are logged in, check that the CGI-BIN directory in Apache has these settings activated:<br>"); + puts("<li>CGIPassAuth on' (Apache 2.4) <br>"); + puts("<li>'SetEnvIf Authorization .+ HTTP_AUTHORIZATION=$0' (Apache 2.2).<br>"); + puts("These settings tell Apache to forward credentials to CGIs. Do not forget to restart Apache after the changes.<p>"); + exit(0); +} + +boolean isValidUsername(char *s) +/* Return TRUE if s is a valid username: only contains alpha chars, @, _ or - */ +{ +char c = *s; +while ((c = *s++) != 0) + { + if (!(isalnum(c) || (c == '_') || (c=='@') || (c=='-'))) + return FALSE; + } +return TRUE; +} + +char *basicAuthUser(char *token) +/* get the HTTP Header 'Authorization', which is just the b64 encoded username:password, + * and return the username. Result has to be freed. */ +{ + +// username:password is b64 encrypted +char *tokenPlain = base64Decode(token, 0); + +// plain text is in format username:password +char *words[2]; +int wordCount = chopString(tokenPlain, ":", words, ArraySize(words)); +if (wordCount!=2) + errAbort("wikiLink/basicAuthUser: got illegal basic auth token"); +char *user = words[0]; + +return user; +} + char *wikiLinkUserName() /* Return the user name specified in cookies from the browser, or NULL if * the user doesn't appear to be logged in. */ { +if (loginUseBasicAuth()) + { + char *token = getHttpBasicToken(); + //XX The following should be uncommented for security reasons + //if (!token) + //printTokenErrorAndExit(); + // May 2017: Allowing normal login even when HTTP Basic is enabled. This may be insecure. + // Keeping it insecure pending Jim's/Clay's approval, for backwards compatibility. + if (token) + return basicAuthUser(token); + } + if (loginSystemEnabled()) { if (! alreadyAuthenticated) loginValidateCookies(); if (authenticated) return cloneString(getLoginUserName()); } else if (wikiLinkEnabled()) { char *wikiUserName = findCookieData(wikiLinkUserNameCookie()); char *wikiLoggedIn = findCookieData(wikiLinkLoggedInCookie()); if (isNotEmpty(wikiLoggedIn) && isNotEmpty(wikiUserName)) return cloneString(wikiUserName); } else @@ -433,15 +517,46 @@ loginUrl(), retEnc); } else { if (! wikiLinkEnabled()) errAbort("wikiLinkUserLogoutUrl called when wiki is not enable (specified " "in hg.conf)."); safef(buf, sizeof(buf), "http://%s/index.php?title=Special:UserlogoutUCSC&returnto=%s", wikiLinkHost(), retEnc); } freez(&retEnc); return(cloneString(buf)); } +char *wikiServerAndCgiDir() +/* return the current full absolute URL up to the CGI name, like + * http://genome.ucsc.edu/cgi-bin/. If login.relativeLink=on is + * set, return only the empty string. Takes care of of non-root location of cgi-bin + * and https. Result has to be free'd. */ +{ +boolean relativeLink = cfgOptionBooleanDefault(CFG_LOGIN_RELATIVE, FALSE); +if (relativeLink) + return cloneString(""); + +char *cgiDir = cgiScriptDirUrl(); +char buf[2048]; +char *hgLoginHost = wikiLinkHost(); +safef(buf, sizeof(buf), "http%s://%s%s", cgiAppendSForHttps(), hgLoginHost, cgiDir); + +return cloneString(buf); +} + +void wikiFixLogoutLinkWithJs() +/* HTTP Basic Auth requires a strange hack to logout. This code prints a script + * that fixes an html link with id=logoutLink */ +{ +struct dyString *dy = dyStringNew(4096); +// logoutJs.h is a stringified .js file +#include "logoutJs.h" +dyStringPrintf(dy, cdwLogoutJs); +dyStringPrintf(dy, "$('#logoutLink').click( function() { logout('/', 'http://cirm.ucsc.edu'); return false; });\n"); +jsInline(dy->string); +dyStringFree(&dy); +printf("<script src='//cdnjs.cloudflare.com/ajax/libs/bowser/1.6.1/bowser.min.js'></script>"); +}