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.
", CFG_LOGIN_BASICAUTH);
+ puts("The Genome Browser cannot find an 'Authorization' header to the Genome Browser.
");
+ puts("This website should only be reachable through a https connection that requires username and password.
"); + puts("If you have reached this website in a way that does not require a password, please contact your adminstrator.
"); + 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.
");
+ puts("If it is and you are logged in, check that the CGI-BIN directory in Apache has these settings activated:
");
+ puts("
"); + 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(""); +}