From c9bfccc35345ce58fb5774d4b0b6a9868b262c0a Mon Sep 17 00:00:00 2001 From: giorgio Date: Wed, 5 Sep 2012 08:31:30 +0000 Subject: git-svn-id: http://svn.us.apache.org/repos/asf/tuscany@1381061 13f79535-47bb-0310-9956-ffa450edef68 --- .../lightweight-sca/modules/http/mod-openauth.cpp | 478 +++++++++++++++++++++ 1 file changed, 478 insertions(+) create mode 100644 sca-cpp/branches/lightweight-sca/modules/http/mod-openauth.cpp (limited to 'sca-cpp/branches/lightweight-sca/modules/http/mod-openauth.cpp') diff --git a/sca-cpp/branches/lightweight-sca/modules/http/mod-openauth.cpp b/sca-cpp/branches/lightweight-sca/modules/http/mod-openauth.cpp new file mode 100644 index 0000000000..2e308ecedb --- /dev/null +++ b/sca-cpp/branches/lightweight-sca/modules/http/mod-openauth.cpp @@ -0,0 +1,478 @@ +/* + * 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 + * - 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* p, server_rec* s) : p(p), server(s) { + } + + const gc_pool p; + server_rec* server; +}; + +/** + * Authentication provider configuration. + */ +class AuthnProviderConf { +public: + AuthnProviderConf() : name(), provider(NULL) { + } + AuthnProviderConf(const string name, const authn_provider* provider) : name(name), provider(provider) { + } + + string name; + const authn_provider* provider; +}; + +/** + * Directory configuration. + */ +class DirConf { +public: + DirConf(apr_pool_t* p, char* d) : p(p), dir(d), enabled(false), login("") { + } + + const gc_pool p; + const char* dir; + bool enabled; + string login; + list apcs; +}; + +#ifdef WANT_MAINTAINER_LOG + +/** + * Log session entries. + */ +int debugSessionEntry(unused void* r, const char* key, const char* value) { + cdebug << " session key: " << key << ", value: " << value << endl; + return 1; +} + +const bool debugSession(request_rec* r, session_rec* 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 + +/** + * Run the authnz hooks to authenticate a request. + */ +const failable checkAuthnzProviders(const string& user, const string& pw, request_rec* r, const list& apcs) { + if (isNil(apcs)) + return mkfailure("Authentication failure for: " + user); + 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* r, const DirConf& dc) { + if (substr(user, 0, 1) == "/" && pw == "password") + return mkfailure(string("Encountered FakeBasicAuth spoof: ") + user, HTTP_UNAUTHORIZED); + + if (isNil(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. + */ +static int (*ap_session_load_fn) (request_rec * r, session_rec ** z) = NULL; +static int (*ap_session_get_fn) (request_rec * r, session_rec * z, const char *key, const char **value) = NULL; + +const failable userInfoFromSession(const string& realm, request_rec* r) { + debug("modopenauth::userInfoFromSession"); + if (ap_session_load_fn == NULL) + ap_session_load_fn = APR_RETRIEVE_OPTIONAL_FN(ap_session_load); + session_rec *z = NULL; + ap_session_load_fn(r, &z); + if (z == NULL) + return mkfailure("Couldn't retrieve user session"); + debug_authSession(r, z); + + if (ap_session_get_fn == NULL) + ap_session_get_fn = APR_RETRIEVE_OPTIONAL_FN(ap_session_get); + 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"); + const char* pw = NULL; + ap_session_get_fn(r, z, c_str(realm + "-pw"), &pw); + if (pw == NULL) + return mkfailure("Couldn't retrieve password"); + 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* r) { + const list> info = httpd::queryArgs(sid); + debug(info, "modopenauth::userInfoFromCookie::info"); + const list user = assoc(realm + "-user", info); + if (isNil(user)) + return userInfoFromSession(realm, r); + const list pw = assoc(realm + "-pw", info); + if (isNil(pw)) + return mkfailure("Couldn't retrieve password"); + 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* r) { + debug(header, "modopenauth::userInfoFromHeader::header"); + if (strcasecmp(ap_getword(r->pool, &header, ' '), "Basic")) + return mkfailure("Wrong authentication scheme"); + + 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* r) { + debug(info, "modopenauth::authenticated::info"); + + // Store user info in the request + const list realm = assoc("realm", info); + if (isNil(realm) || isNil(cdr(realm))) + return mkfailure("Couldn't retrieve realm"); + 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 (isNil(id) || isNil(cdr(id))) + return mkfailure("Couldn't retrieve user id"); + r->user = apr_pstrdup(r->pool, c_str(cadr(id))); + + apr_table_set(r->subprocess_env, apr_pstrdup(r->pool, "NICKNAME"), apr_pstrdup(r->pool, c_str(cadr(id)))); + return OK; +} + +/** + * Check user authentication. + */ +static int checkAuthn(request_rec *r) { + gc_scoped_pool pool(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); + if (atype == NULL || strcasecmp(atype, "Open")) + return DECLINED; + debug_httpdRequest(r, "modopenauth::checkAuthn::input"); + 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 httpd::reportStatus(mkfailure("Missing AuthName")); + + // 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 httpd::reportStatus(login(dc.login, value(), 1, r)); + } + + // Successfully authenticated, store the user info in the request + r->ap_auth_type = const_cast(atype); + return httpd::reportStatus(authenticated(uinfo, r)); + } + } + + // Get basic auth header from the request + const char* header = apr_table_get(r->headers_in, (PROXYREQ_PROXY == r->proxyreq) ? "Proxy-Authorization" : "Authorization"); + if (header != NULL) { + + // Retrieve the auth realm + const char* aname = ap_auth_name(r); + if (aname == NULL) + return httpd::reportStatus(mkfailure("Missing AuthName")); + + // 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 httpd::reportStatus(login(dc.login, value(), 1, r)); + } + + // Successfully authenticated, store the user info in the request + r->ap_auth_type = const_cast(atype); + return httpd::reportStatus(authenticated(uinfo, r)); + } + } + + // Decline if the request is for another authentication provider + if (!isNil(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, value(), value(), r)); +} + +/** + * Save the auth session cookie in the response. + */ +static int sessionCookieSave(request_rec* r, session_rec* z) { + gc_scoped_pool pool(r->pool); + + const DirConf& dc = httpd::dirConf(r, &mod_tuscany_openauth); + if (!dc.enabled) + return DECLINED; + + debug(c_str(cookie("TuscanyOpenAuth", z->encoded, httpd::hostName(r))), "modopenauth::setcookie"); + apr_table_set(r->err_headers_out, "Set-Cookie", c_str(cookie("TuscanyOpenAuth", z->encoded, httpd::hostName(r)))); + return OK; +} + +/** + * Load the auth session cookie from the request. Similar + */ +static int sessionCookieLoad(request_rec* r, session_rec** z) { + gc_scoped_pool pool(r->pool); + + const DirConf& dc = httpd::dirConf(r, &mod_tuscany_openauth); + if (!dc.enabled) + return DECLINED; + + // First look in the notes + const char* 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; +} + +/** + * Process the module configuration. + */ +int postConfigMerge(ServerConf& mainsc, server_rec* s) { + if (s == NULL) + return OK; + debug(httpd::serverName(s), "modopenauth::postConfigMerge::serverName"); + + return postConfigMerge(mainsc, s->next); +} + +int postConfig(apr_pool_t* p, unused apr_pool_t* plog, unused apr_pool_t* ptemp, server_rec* s) { + gc_scoped_pool pool(p); + + ServerConf& sc = httpd::serverConf(s, &mod_tuscany_openauth); + debug(httpd::serverName(s), "modopenauth::postConfig::serverName"); + + // Merge server configurations + return postConfigMerge(sc, s); +} + +/** + * Child process initialization. + */ +void childInit(apr_pool_t* p, server_rec* s) { + gc_scoped_pool pool(p); + + ServerConf* 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); + } + ServerConf& sc = *psc; + + // Merge the updated configuration into the virtual hosts + postConfigMerge(sc, s->next); +} + +/** + * Configuration commands. + */ +const char* confEnabled(cmd_parms *cmd, void *c, const int arg) { + gc_scoped_pool pool(cmd->pool); + DirConf& dc = httpd::dirConf(c); + dc.enabled = (bool)arg; + return NULL; +} +const char* confLogin(cmd_parms *cmd, void *c, const char* arg) { + gc_scoped_pool pool(cmd->pool); + DirConf& dc = httpd::dirConf(c); + dc.login = arg; + return NULL; +} +const char* confAuthnProvider(cmd_parms *cmd, void *c, const char* arg) { + gc_scoped_pool pool(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); +} + +} +} + +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 +}; + +} -- cgit v1.2.3