/* * Licensed to the Apache Software Foundation (ASF) under one * or more contributor license agreements. See the NOTICE file * distributed with this work for additional information * regarding copyright ownership. The ASF licenses this file * to you under the Apache License, Version 2.0 (the * "License"); you may not use this file except in compliance * with the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, * software distributed under the License is distributed on an * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * KIND, either express or implied. See the License for the * specific language governing permissions and limitations * under the License. */ /* $Rev$ $Date$ */ /** * HTTPD module for Tuscany Open authentication. * * This module allows multiple authentication mechanisms to co-exist in a * single Web site: * - OAuth1 using Tuscany's mod-tuscany-oauth1 * - OAuth2 using Tuscany's mod-tuscany-oauth2 * - OpenID using mod_auth_openid * - Form-based using HTTPD's mod_auth_form * - HTTP basic auth using mod_auth_basic * - SSL certificate using SSLFakeBasicAuth and mod_auth_basic */ #include #define WANT_HTTPD_LOG 1 #include "string.hpp" #include "stream.hpp" #include "list.hpp" #include "tree.hpp" #include "value.hpp" #include "monad.hpp" #include "httpd.hpp" #include "http.hpp" #include "openauth.hpp" extern "C" { extern module AP_MODULE_DECLARE_DATA mod_tuscany_openauth; } namespace tuscany { namespace openauth { /** * Server configuration. */ class ServerConf { public: ServerConf(apr_pool_t* const p, server_rec* const s) : p(p), server(s) { } const gc_pool p; server_rec* const server; }; /** * Authentication provider configuration. */ class AuthnProviderConf { public: AuthnProviderConf() : name(), provider(NULL) { } AuthnProviderConf(const string name, const authn_provider* const provider) : name(name), provider(provider) { } const string name; const authn_provider* const provider; }; /** * Directory configuration. */ class DirConf { public: DirConf(apr_pool_t* const p, const char* d) : p(p), dir(d), enabled(false), login(emptyString) { } const gc_pool p; const char* const dir; bool enabled; gc_mutable_ref login; gc_mutable_ref > apcs; }; #ifdef WANT_MAINTAINER_LOG /** * Log session entries. */ int debugSessionEntry(unused void* r, const char* const key, const char* const value) { cdebug << " session key: " << key << ", value: " << value << endl; return 1; } const bool debugSession(request_rec* const r, const session_rec* const z) { apr_table_do(debugSessionEntry, r, z->entries, NULL); return true; } #define debug_authSession(r, z) if(debug_islogging()) openauth::debugSession(r, z) #else #define debug_authSession(r, z) #endif /** * Session hook functions. */ static int (*ap_session_load_fn) (request_rec * r, session_rec ** z) = NULL; static apr_status_t (*ap_session_get_fn) (request_rec * r, session_rec * z, const char *key, const char **value) = NULL; static apr_status_t (*ap_session_set_fn)(request_rec * r, session_rec * z, const char *key, const char *value) = NULL; /** * Run the authnz hooks to authenticate a request. */ const failable checkAuthnzProviders(const string& user, const string& pw, request_rec* const r, const list& apcs) { if(isNull(apcs)) return mkfailure("Authentication failure for: " + user, HTTP_UNAUTHORIZED); const AuthnProviderConf apc = car(apcs); if(apc.provider == NULL || !apc.provider->check_password) return checkAuthnzProviders(user, pw, r, cdr(apcs)); apr_table_setn(r->notes, AUTHN_PROVIDER_NAME_NOTE, c_str(apc.name)); const authn_status auth_result = apc.provider->check_password(r, c_str(user), c_str(pw)); apr_table_unset(r->notes, AUTHN_PROVIDER_NAME_NOTE); if(auth_result != AUTH_GRANTED) return checkAuthnzProviders(user, pw, r, cdr(apcs)); return OK; } const failable checkAuthnz(const string& user, const string& pw, request_rec* const r, const DirConf& dc) { if(substr(user, 0, 1) == "/") return mkfailure(string("Encountered FakeBasicAuth spoof: ") + user, HTTP_UNAUTHORIZED); if(isNull((const list)dc.apcs)) { const authn_provider* provider = (const authn_provider*)ap_lookup_provider(AUTHN_PROVIDER_GROUP, AUTHN_DEFAULT_PROVIDER, AUTHN_PROVIDER_VERSION); return checkAuthnzProviders(user, pw, r, mklist(AuthnProviderConf(AUTHN_DEFAULT_PROVIDER, provider))); } return checkAuthnzProviders(user, pw, r, dc.apcs); } /** * Return the user info from a form auth encrypted session cookie. */ const failable userInfoFromSession(const string& realm, request_rec* const r) { debug("modopenauth::userInfoFromSession"); session_rec *z = NULL; ap_session_load_fn(r, &z); if(z == NULL) return mkfailure("Couldn't retrieve user session", HTTP_UNAUTHORIZED); debug_authSession(r, z); const char* user = NULL; ap_session_get_fn(r, z, c_str(realm + "-user"), &user); if(user == NULL) return mkfailure("Couldn't retrieve user id", HTTP_UNAUTHORIZED); const char* pw = NULL; ap_session_get_fn(r, z, c_str(realm + "-pw"), &pw); if(pw == NULL) return mkfailure("Couldn't retrieve password", HTTP_UNAUTHORIZED); return value(mklist(mklist("realm", realm), mklist("id", string(user)), mklist("password", string(pw)))); } /** * Return the user info from a form auth session cookie. */ const failable userInfoFromCookie(const value& sid, const string& realm, request_rec* const r) { const list info = httpd::queryArgs(sid); debug(info, "modopenauth::userInfoFromCookie::info"); const list user = assoc(realm + "-user", info); if(isNull(user)) return userInfoFromSession(realm, r); const list pw = assoc(realm + "-pw", info); if(isNull(pw)) return mkfailure("Couldn't retrieve password", HTTP_UNAUTHORIZED); return value(mklist(mklist("realm", realm), mklist("id", cadr(user)), mklist("password", cadr(pw)))); } /** * Return the user info from a basic auth header. */ const failable userInfoFromHeader(const char* header, const string& realm, request_rec* const r) { debug(header, "modopenauth::userInfoFromHeader::header"); if(strcasecmp(ap_getword(r->pool, &header, ' '), "Basic")) return mkfailure("Wrong authentication scheme", HTTP_UNAUTHORIZED); while (apr_isspace(*header)) header++; char *decoded_line = (char*)apr_palloc(r->pool, apr_base64_decode_len(header) + 1); int length = apr_base64_decode(decoded_line, header); decoded_line[length] = '\0'; const string user(ap_getword_nulls(r->pool, const_cast(&decoded_line), ':')); const string pw(decoded_line); return value(mklist(mklist("realm", realm), mklist("id", user), mklist("password", pw))); } /** * Handle an authenticated request. */ const failable authenticated(const list& info, request_rec* const r) { debug(info, "modopenauth::authenticated::info"); // Store user info in the request const list realm = assoc("realm", info); if(isNull(realm) || isNull(cdr(realm))) return mkfailure("Couldn't retrieve realm", HTTP_UNAUTHORIZED); apr_table_set(r->subprocess_env, apr_pstrdup(r->pool, "REALM"), apr_pstrdup(r->pool, c_str(cadr(realm)))); const list id = assoc("id", info); if(isNull(id) || isNull(cdr(id))) return mkfailure("Couldn't retrieve user id", HTTP_UNAUTHORIZED); const string sid = cadr(id); if (find(sid, '@') != length(sid)) apr_table_set(r->subprocess_env, apr_pstrdup(r->pool, "EMAIL"), apr_pstrdup(r->pool, c_str(sid))); r->user = apr_pstrdup(r->pool, c_str(sid)); // Update the request user field with the authorized user id returned by the authnz hooks const char* auser = apr_table_get(r->subprocess_env, "AUTHZ_USER"); if (auser != NULL) r->user = apr_pstrdup(r->pool, auser); apr_table_set(r->subprocess_env, apr_pstrdup(r->pool, "NICKNAME"), apr_pstrdup(r->pool, r->user)); return OK; } /** * Check user authentication. */ static int checkAuthn(request_rec* const r) { const gc_scoped_pool sp(r->pool); // Decline if we're not enabled or AuthType is not set to Open const DirConf& dc = httpd::dirConf(r, &mod_tuscany_openauth); if(!dc.enabled) return DECLINED; const char* atype = ap_auth_type(r); debug_httpdRequest(r, "modopenauth::checkAuthn::input"); debug(atype, "modopenauth::checkAuthn::auth_type"); if(atype == NULL || strcasecmp(atype, "Open")) return DECLINED; debug(atype, "modopenauth::checkAuthn::auth_type"); // Get the request args const list args = httpd::queryArgs(r); // Get session id from the request const maybe sid = sessionID(r, "TuscanyOpenAuth"); if(hasContent(sid)) { // Decline if the session id was not created by this module const string stype = substr(content(sid), 0, 7); if(stype == "OAuth2_" || stype == "OAuth1_" || stype == "OpenID_") return DECLINED; // Retrieve the auth realm const char* aname = ap_auth_name(r); if(aname == NULL) return reportStatus(mkfailure("Missing AuthName", HTTP_UNAUTHORIZED), dc.login, nilValue, r); // Extract user info from the session id const failable userinfo = userInfoFromCookie(content(sid), aname, r); if(hasContent(userinfo)) { // Try to authenticate the request const value uinfo = content(userinfo); const failable authz = checkAuthnz(cadr(assoc("id", uinfo)), cadr(assoc("password", uinfo)), r, dc); if(!hasContent(authz)) { // Authentication failed, redirect to login page r->ap_auth_type = const_cast(atype); return reportStatus(authz, dc.login, 1, r); } // Successfully authenticated, store the user info in the request r->ap_auth_type = const_cast(atype); return reportStatus(authenticated(uinfo, r), dc.login, nilValue, r); } } // Get basic auth header from the request const char* const header = apr_table_get(r->headers_in, (PROXYREQ_PROXY == r->proxyreq) ? "Proxy-Authorization" : "Authorization"); if(header != NULL) { // Retrieve the auth realm const char* const aname = ap_auth_name(r); if(aname == NULL) return reportStatus(mkfailure("Missing AuthName", HTTP_UNAUTHORIZED), dc.login, nilValue, r); // Extract user info from the session id const failable info = userInfoFromHeader(header, aname, r); if(hasContent(info)) { // Try to authenticate the request const value uinfo = content(info); const failable authz = checkAuthnz(cadr(assoc("id", uinfo)), cadr(assoc("password", uinfo)), r, dc); if(!hasContent(authz)) { // Authentication failed, redirect to login page r->ap_auth_type = const_cast(atype); return reportStatus(authz, dc.login, 1, r); } // Successfully authenticated, store the user info in the request r->ap_auth_type = const_cast(atype); return reportStatus(authenticated(uinfo, r), dc.login, nilValue, r); } } // Decline if the request is for another authentication provider if(!isNull(assoc("openid_identifier", args))) return DECLINED; // Redirect to the login page, unless we have a session id from another module if(hasContent(sessionID(r, "TuscanyOpenIDAuth")) || hasContent(sessionID(r, "TuscanyOAuth1")) || hasContent(sessionID(r, "TuscanyOAuth2"))) return DECLINED; r->ap_auth_type = const_cast(atype); return httpd::reportStatus(login(dc.login, nilValue, nilValue, r)); } /** * Load the auth session cookie from the request. */ static int sessionCookieLoad(request_rec* const r, session_rec** const z) { const gc_scoped_pool sp(r->pool); const DirConf& dc = httpd::dirConf(r, &mod_tuscany_openauth); if(!dc.enabled) return DECLINED; // First look in the notes const char* const note = apr_pstrcat(r->pool, "mod_openauth", "TuscanyOpenAuth", NULL); session_rec* zz = (session_rec*)(void*)apr_table_get(r->notes, note); if(zz != NULL) { *z = zz; return OK; } // Parse the cookie const maybe sid = openauth::sessionID(r, "TuscanyOpenAuth"); // Create a new session zz = (session_rec*)apr_pcalloc(r->pool, sizeof(session_rec)); zz->pool = r->pool; zz->entries = apr_table_make(r->pool, 10); zz->encoded = hasContent(sid)? c_str(content(sid)) : NULL; zz->uuid = (apr_uuid_t *) apr_pcalloc(r->pool, sizeof(apr_uuid_t)); *z = zz; // Store it in the notes apr_table_setn(r->notes, note, (char*)zz); return OK; } /** * Save the auth session cookie in the response. */ static int sessionCookieSave(request_rec* const r, session_rec* const z) { const gc_scoped_pool sp(r->pool); const DirConf& dc = httpd::dirConf(r, &mod_tuscany_openauth); if(!dc.enabled) return DECLINED; if(z->encoded == NULL || *(z->encoded) == '\0') { const maybe sid = sessionID(r, "TuscanyOpenAuth"); if(!hasContent(sid)) return OK; } debug(c_str(cookie("TuscanyOpenAuth", z->encoded, httpd::hostName(r))), "modopenauth::sessioncookiesave::setcookie"); apr_table_set(r->err_headers_out, "Set-Cookie", c_str(cookie("TuscanyOpenAuth", z->encoded, httpd::hostName(r)))); return OK; } /** * Logout request handler. */ int logoutHandler(request_rec* const r) { if(r->handler == NULL || strcmp(r->handler, "mod_tuscany_openauth_logout")) return DECLINED; const gc_scoped_pool sp(r->pool); debug_httpdRequest(r, "modopenauth::handler::input"); const DirConf& dc = httpd::dirConf(r, &mod_tuscany_openauth); if(!dc.enabled) return DECLINED; // Clear the current session if(hasContent(sessionID(r, "TuscanyOpenAuth"))) { const char* const authname = ap_auth_name(r); session_rec* z = NULL; ap_session_load_fn(r, &z); if(z != NULL && authname != NULL) { ap_session_set_fn(r, z, apr_pstrcat(r->pool, authname, "-user", NULL), NULL); ap_session_set_fn(r, z, apr_pstrcat(r->pool, authname, "-pw", NULL), NULL); ap_session_set_fn(r, z, apr_pstrcat(r->pool, authname, "-site", NULL), NULL); } else apr_table_set(r->err_headers_out, "Set-Cookie", c_str(cookie("TuscanyOpenAuth", emptyString, httpd::hostName(r)))); } if(hasContent(sessionID(r, "TuscanyOpenIDAuth"))) apr_table_set(r->err_headers_out, "Set-Cookie", c_str(cookie("TuscanyOpenIDAuth", emptyString, httpd::hostName(r)))); if(hasContent(sessionID(r, "TuscanyOAuth1"))) apr_table_set(r->err_headers_out, "Set-Cookie", c_str(cookie("TuscanyOAuth1", emptyString, httpd::hostName(r)))); if(hasContent(sessionID(r, "TuscanyOAuth2"))) apr_table_set(r->err_headers_out, "Set-Cookie", c_str(cookie("TuscanyOAuth2", emptyString, httpd::hostName(r)))); // Redirect to the login page return httpd::reportStatus(login(dc.login, "/", nilValue, r)); } /** * Process the module configuration. */ int postConfigMerge(const ServerConf& mainsc, server_rec* const s) { if(s == NULL) return OK; debug(httpd::serverName(s), "modopenauth::postConfigMerge::serverName"); return postConfigMerge(mainsc, s->next); } int postConfig(apr_pool_t* const p, unused apr_pool_t* const plog, unused apr_pool_t* const ptemp, server_rec* const s) { const gc_scoped_pool sp(p); const ServerConf& sc = httpd::serverConf(s, &mod_tuscany_openauth); debug(httpd::serverName(s), "modopenauth::postConfig::serverName"); // Retrieve session hook functions if(ap_session_load_fn == NULL) ap_session_load_fn = APR_RETRIEVE_OPTIONAL_FN(ap_session_load); if(ap_session_get_fn == NULL) ap_session_get_fn = APR_RETRIEVE_OPTIONAL_FN(ap_session_get); if(ap_session_set_fn == NULL) ap_session_set_fn = APR_RETRIEVE_OPTIONAL_FN(ap_session_set); // Merge server configurations return postConfigMerge(sc, s); } /** * Child process initialization. */ void childInit(apr_pool_t* const p, server_rec* const s) { const gc_scoped_pool sp(p); const ServerConf* const psc = (ServerConf*)ap_get_module_config(s->module_config, &mod_tuscany_openauth); if(psc == NULL) { cfailure << "[Tuscany] Due to one or more errors mod_tuscany_openauth loading failed. Causing apache to stop loading." << endl; exit(APEXIT_CHILDFATAL); } const ServerConf& sc = *psc; // Merge the updated configuration into the virtual hosts postConfigMerge(sc, s->next); } /** * Configuration commands. */ char* confEnabled(cmd_parms* cmd, void *c, const int arg) { const gc_scoped_pool sp(cmd->pool); DirConf& dc = httpd::dirConf(c); dc.enabled = (bool)arg; return NULL; } char* confLogin(cmd_parms *cmd, void *c, const char* arg) { const gc_scoped_pool sp(cmd->pool); DirConf& dc = httpd::dirConf(c); dc.login = arg; return NULL; } char* confAuthnProvider(cmd_parms *cmd, void *c, const char* arg) { const gc_scoped_pool sp(cmd->pool); DirConf& dc = httpd::dirConf(c); // Lookup and cache the Authn provider const authn_provider* provider = (authn_provider*)ap_lookup_provider(AUTHN_PROVIDER_GROUP, arg, AUTHN_PROVIDER_VERSION); if(provider == NULL) return apr_psprintf(cmd->pool, "Unknown Authn provider: %s", arg); if(!provider->check_password) return apr_psprintf(cmd->pool, "The '%s' Authn provider doesn't support password authentication", arg); dc.apcs = append(dc.apcs, mklist(AuthnProviderConf(arg, provider))); return NULL; } /** * HTTP server module declaration. */ const command_rec commands[] = { AP_INIT_ITERATE("AuthOpenAuthProvider", (const char*(*)())confAuthnProvider, NULL, OR_AUTHCFG, "Auth providers for a directory or location"), AP_INIT_FLAG("AuthOpenAuth", (const char*(*)())confEnabled, NULL, OR_AUTHCFG, "Tuscany Open Auth authentication On | Off"), AP_INIT_TAKE1("AuthOpenAuthLoginPage", (const char*(*)())confLogin, NULL, OR_AUTHCFG, "Tuscany Open Auth login page"), {NULL, NULL, NULL, 0, NO_ARGS, NULL} }; void registerHooks(unused apr_pool_t *p) { ap_hook_post_config(postConfig, NULL, NULL, APR_HOOK_MIDDLE); ap_hook_child_init(childInit, NULL, NULL, APR_HOOK_MIDDLE); ap_hook_check_authn(checkAuthn, NULL, NULL, APR_HOOK_MIDDLE, AP_AUTH_INTERNAL_PER_CONF); ap_hook_session_load(sessionCookieLoad, NULL, NULL, APR_HOOK_MIDDLE); ap_hook_session_save(sessionCookieSave, NULL, NULL, APR_HOOK_MIDDLE); ap_hook_handler(logoutHandler, NULL, NULL, APR_HOOK_MIDDLE); } } } extern "C" { module AP_MODULE_DECLARE_DATA mod_tuscany_openauth = { STANDARD20_MODULE_STUFF, // dir config and merger tuscany::httpd::makeDirConf, NULL, // server config and merger tuscany::httpd::makeServerConf, NULL, // commands and hooks tuscany::openauth::commands, tuscany::openauth::registerHooks }; }