summaryrefslogtreecommitdiffstats
path: root/sandbox/sebastien/cpp/apr-2/modules/oauth/mod-oauth2.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'sandbox/sebastien/cpp/apr-2/modules/oauth/mod-oauth2.cpp')
-rw-r--r--sandbox/sebastien/cpp/apr-2/modules/oauth/mod-oauth2.cpp432
1 files changed, 432 insertions, 0 deletions
diff --git a/sandbox/sebastien/cpp/apr-2/modules/oauth/mod-oauth2.cpp b/sandbox/sebastien/cpp/apr-2/modules/oauth/mod-oauth2.cpp
new file mode 100644
index 0000000000..b52967977e
--- /dev/null
+++ b/sandbox/sebastien/cpp/apr-2/modules/oauth/mod-oauth2.cpp
@@ -0,0 +1,432 @@
+/*
+ * 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 OAuth 2.0 authentication.
+ */
+
+#include <sys/stat.h>
+
+#include "string.hpp"
+#include "stream.hpp"
+#include "list.hpp"
+#include "tree.hpp"
+#include "value.hpp"
+#include "monad.hpp"
+#include "../http/httpd.hpp"
+#include "../http/http.hpp"
+#include "../http/openauth.hpp"
+#include "../../components/cache/memcache.hpp"
+
+extern "C" {
+extern module AP_MODULE_DECLARE_DATA mod_tuscany_oauth2;
+}
+
+namespace tuscany {
+namespace oauth2 {
+
+/**
+ * Server configuration.
+ */
+class ServerConf {
+public:
+ ServerConf(apr_pool_t* p, server_rec* s) : p(p), server(s) {
+ }
+
+ const gc_pool p;
+ server_rec* server;
+ string ca;
+ string cert;
+ string key;
+ list<list<value> > appkeys;
+ list<string> mcaddrs;
+ memcache::MemCached mc;
+ http::CURLSession cs;
+};
+
+/**
+ * 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;
+};
+
+/**
+ * Return the user info for a session.
+ */
+const failable<value> userInfo(const value& sid, const memcache::MemCached& mc) {
+ return memcache::get(mklist<value>("tuscanyOpenAuth", sid), mc);
+}
+
+/**
+ * Handle an authenticated request.
+ */
+const failable<int> authenticated(const list<list<value> >& info, request_rec* r) {
+ debug(info, "modoauth2::authenticated::info");
+
+ // Store user info in the request
+ const list<value> realm = assoc<value>("realm", info);
+ if (isNil(realm) || isNil(cdr(realm)))
+ return mkfailure<int>("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<value> id = assoc<value>("id", info);
+ if (isNil(id) || isNil(cdr(id)))
+ return mkfailure<int>("Couldn't retrieve user id");
+ r->user = apr_pstrdup(r->pool, c_str(cadr(id)));
+
+ const list<value> email = assoc<value>("email", info);
+ if (!isNil(email) && !isNil(cdr(email)))
+ apr_table_set(r->subprocess_env, apr_pstrdup(r->pool, "EMAIL"), apr_pstrdup(r->pool, c_str(cadr(email))));
+
+ const list<value> fullname = assoc<value>("name", info);
+ if (!isNil(fullname) && !isNil(cdr(fullname))) {
+ apr_table_set(r->subprocess_env, apr_pstrdup(r->pool, "NICKNAME"), apr_pstrdup(r->pool, c_str(cadr(fullname))));
+ apr_table_set(r->subprocess_env, apr_pstrdup(r->pool, "FULLNAME"), apr_pstrdup(r->pool, c_str(cadr(fullname))));
+ }
+
+ const list<value> firstname = assoc<value>("first_name", info);
+ if (!isNil(firstname) && !isNil(cdr(firstname)))
+ apr_table_set(r->subprocess_env, apr_pstrdup(r->pool, "FIRSTNAME"), apr_pstrdup(r->pool, c_str(cadr(firstname))));
+
+ const list<value> lastname = assoc<value>("last_name", info);
+ if (!isNil(lastname) && !isNil(cdr(lastname)))
+ apr_table_set(r->subprocess_env, apr_pstrdup(r->pool, "LASTNAME"), apr_pstrdup(r->pool, c_str(cadr(lastname))));
+
+ return OK;
+}
+
+/**
+ * Handle an authorize request.
+ */
+const failable<int> authorize(const list<list<value> >& args, request_rec* r, const ServerConf& sc) {
+ // Extract authorize, access_token, client ID and info URIs
+ const list<value> auth = assoc<value>("mod_oauth2_authorize", args);
+ if (isNil(auth) || isNil(cdr(auth)))
+ return mkfailure<int>("Missing mod_oauth2_authorize parameter");
+ const list<value> tok = assoc<value>("mod_oauth2_access_token", args);
+ if (isNil(tok) || isNil(cdr(tok)))
+ return mkfailure<int>("Missing mod_oauth2_access_token parameter");
+ const list<value> cid = assoc<value>("mod_oauth2_client_id", args);
+ if (isNil(cid) || isNil(cdr(cid)))
+ return mkfailure<int>("Missing mod_oauth2_client_id parameter");
+ const list<value> info = assoc<value>("mod_oauth2_info", args);
+ if (isNil(info) || isNil(cdr(info)))
+ return mkfailure<int>("Missing mod_oauth2_info parameter");
+
+ // Build the redirect URI
+ const list<list<value> > rargs = mklist<list<value> >(mklist<value>("mod_oauth2_step", "access_token"), tok, cid, info);
+ const string redir = httpd::url(r->uri, r) + string("?") + httpd::queryString(rargs);
+ debug(redir, "modoauth2::authorize::redir");
+
+ // Lookup client app configuration
+ const list<value> app = assoc<value>(cadr(cid), sc.appkeys);
+ if (isNil(app) || isNil(cdr(app)))
+ return mkfailure<int>(string("client id not found: ") + cadr(cid));
+ list<value> appkey = cadr(app);
+
+ // Redirect to the authorize URI
+ const list<list<value> > aargs = mklist<list<value> >(mklist<value>("client_id", car(appkey)), mklist<value>("scope", "email"), mklist<value>("redirect_uri", httpd::escape(redir)));
+ const string uri = httpd::unescape(cadr(auth)) + string("?") + httpd::queryString(aargs);
+ debug(uri, "modoauth2::authorize::uri");
+ return httpd::externalRedirect(uri, r);
+}
+
+/**
+ * Extract user info from a profile/info response.
+ * TODO This currently only works for Facebook and Gowalla.
+ * User profile parsing needs to be made configurable.
+ */
+const failable<list<value> > profileUserInfo(const value& cid, const list<value>& info) {
+ return cons<value>(mklist<value>("realm", cid), info);
+}
+
+/**
+ * Handle an access_token request.
+ */
+const failable<int> access_token(const list<list<value> >& args, request_rec* r, const ServerConf& sc) {
+ // Extract access_token URI, client ID and authorization code
+ const list<value> tok = assoc<value>("mod_oauth2_access_token", args);
+ if (isNil(tok) || isNil(cdr(tok)))
+ return mkfailure<int>("Missing mod_oauth2_access_token parameter");
+ const list<value> cid = assoc<value>("mod_oauth2_client_id", args);
+ if (isNil(cid) || isNil(cdr(cid)))
+ return mkfailure<int>("Missing mod_oauth2_client_id parameter");
+ const list<value> info = assoc<value>("mod_oauth2_info", args);
+ if (isNil(info) || isNil(cdr(info)))
+ return mkfailure<int>("Missing mod_oauth2_info parameter");
+ const list<value> code = assoc<value>("code", args);
+ if (isNil(code) || isNil(cdr(code)))
+ return mkfailure<int>("Missing code parameter");
+
+ // Lookup client app configuration
+ const list<value> app = assoc<value>(cadr(cid), sc.appkeys);
+ if (isNil(app) || isNil(cdr(app)))
+ return mkfailure<int>(string("client id not found: ") + cadr(cid));
+ list<value> appkey = cadr(app);
+
+ // Build the redirect URI
+ const list<list<value> > rargs = mklist<list<value> >(mklist<value>("mod_oauth2_step", "access_token"), tok, cid, info);
+ const string redir = httpd::url(r->uri, r) + string("?") + httpd::queryString(rargs);
+ debug(redir, "modoauth2::access_token::redir");
+
+ // Request access token
+ const list<list<value> > targs = mklist<list<value> >(mklist<value>("client_id", car(appkey)), mklist<value>("redirect_uri", httpd::escape(redir)), mklist<value>("client_secret", cadr(appkey)), code);
+ const string turi = httpd::unescape(cadr(tok)) + string("?") + httpd::queryString(targs);
+ debug(turi, "modoauth2::access_token::tokenuri");
+ const failable<value> tr = http::get(turi, sc.cs);
+ if (!hasContent(tr))
+ return mkfailure<int>(reason(tr));
+ debug(tr, "modoauth2::access_token::response");
+ const list<value> tv = assoc<value>("access_token", httpd::queryArgs(join("", convertValues<string>(content(tr)))));
+ if (isNil(tv) || isNil(cdr(tv)))
+ return mkfailure<int>("Couldn't retrieve access_token");
+ debug(tv, "modoauth2::access_token::token");
+
+ // Request user info
+ // TODO Make this step configurable
+ const list<list<value> > iargs = mklist<list<value> >(tv);
+ const string iuri = httpd::unescape(cadr(info)) + string("?") + httpd::queryString(iargs);
+ debug(iuri, "modoauth2::access_token::infouri");
+ const failable<value> profres = http::get(iuri, sc.cs);
+ if (!hasContent(profres))
+ return mkfailure<int>("Couldn't retrieve user info");
+ debug(content(profres), "modoauth2::access_token::info");
+
+ // Retrieve the user info from the profile
+ const failable<list<value> > iv = profileUserInfo(cadr(cid), content(profres));
+ if (!hasContent(iv))
+ return mkfailure<int>(reason(iv));
+
+ // Store user info in memcached keyed by session ID
+ const value sid = string("OAuth2_") + mkrand();
+ const failable<bool> prc = memcache::put(mklist<value>("tuscanyOpenAuth", sid), content(iv), sc.mc);
+ if (!hasContent(prc))
+ return mkfailure<int>(reason(prc));
+
+ // Send session ID to the client in a cookie
+ apr_table_set(r->err_headers_out, "Set-Cookie", c_str(openauth::cookie(sid)));
+ return httpd::externalRedirect(httpd::url(r->uri, r), r);
+}
+
+/**
+ * Check user authentication.
+ */
+static int checkAuthn(request_rec *r) {
+ // Decline if we're not enabled or AuthType is not set to Open
+ const DirConf& dc = httpd::dirConf<DirConf>(r, &mod_tuscany_oauth2);
+ if (!dc.enabled)
+ return DECLINED;
+ const char* atype = ap_auth_type(r);
+ if (atype == NULL || strcasecmp(atype, "Open"))
+ return DECLINED;
+
+ gc_scoped_pool pool(r->pool);
+ httpdDebugRequest(r, "modoauth2::checkAuthn::input");
+ const ServerConf& sc = httpd::serverConf<ServerConf>(r, &mod_tuscany_oauth2);
+
+ // Get session id from the request
+ const maybe<string> sid = openauth::sessionID(r);
+ if (hasContent(sid)) {
+ // Decline if the session id was not created by this module
+ if (substr(content(sid), 0, 7) != "OAuth2_")
+ return DECLINED;
+
+ // If we're authenticated store the user info in the request
+ const failable<value> info = userInfo(content(sid), sc.mc);
+ if (hasContent(info)) {
+ r->ap_auth_type = const_cast<char*>(atype);
+ return httpd::reportStatus(authenticated(content(info), r));
+ }
+ }
+
+ // Get the request args
+ const list<list<value> > args = httpd::queryArgs(r);
+
+ // Decline if the request is for another authentication provider
+ if (!isNil(assoc<value>("openid_identifier", args)))
+ return DECLINED;
+ if (!isNil(assoc<value>("mod_oauth1_step", args)))
+ return DECLINED;
+
+ // Determine the OAuth protocol flow step, conveniently passed
+ // around in a request arg
+ const list<value> sl = assoc<value>("mod_oauth2_step", args);
+ const value step = !isNil(sl) && !isNil(cdr(sl))? cadr(sl) : "";
+
+ // Handle OAuth authorize request step
+ if (step == "authorize") {
+ r->ap_auth_type = const_cast<char*>(atype);
+ return httpd::reportStatus(authorize(args, r, sc));
+ }
+
+ // Handle OAuth access_token request step
+ if (step == "access_token") {
+ r->ap_auth_type = const_cast<char*>(atype);
+ return httpd::reportStatus(access_token(args, r, sc));
+ }
+
+ // Redirect to the login page
+ r->ap_auth_type = const_cast<char*>(atype);
+ return httpd::reportStatus(openauth::login(dc.login, r));
+}
+
+/**
+ * Process the module configuration.
+ */
+int postConfigMerge(ServerConf& mainsc, server_rec* s) {
+ if (s == NULL)
+ return OK;
+ ServerConf& sc = httpd::serverConf<ServerConf>(s, &mod_tuscany_oauth2);
+ debug(httpd::serverName(s), "modoauth2::postConfigMerge::serverName");
+
+ // Merge configuration from main server
+ if (isNil(sc.appkeys))
+ sc.appkeys = mainsc.appkeys;
+ sc.mc = mainsc.mc;
+ sc.cs = mainsc.cs;
+
+ 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<ServerConf>(s, &mod_tuscany_oauth2);
+ debug(httpd::serverName(s), "modoauth2::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_oauth2);
+ if(psc == NULL) {
+ cfailure << "[Tuscany] Due to one or more errors mod_tuscany_oauth2 loading failed. Causing apache to stop loading." << endl;
+ exit(APEXIT_CHILDFATAL);
+ }
+ ServerConf& sc = *psc;
+
+ // Connect to Memcached
+ if (isNil(sc.mcaddrs))
+ sc.mc = *(new (gc_new<memcache::MemCached>()) memcache::MemCached("localhost", 11211));
+ else
+ sc.mc = *(new (gc_new<memcache::MemCached>()) memcache::MemCached(sc.mcaddrs));
+
+ // Setup a CURL session
+ sc.cs = *(new (gc_new<http::CURLSession>()) http::CURLSession(sc.ca, sc.cert, sc.key));
+
+ // Merge the updated configuration into the virtual hosts
+ postConfigMerge(sc, s->next);
+}
+
+/**
+ * Configuration commands.
+ */
+const char* confAppKey(cmd_parms *cmd, unused void *c, const char *arg1, const char* arg2, const char* arg3) {
+ gc_scoped_pool pool(cmd->pool);
+ ServerConf& sc = httpd::serverConf<ServerConf>(cmd, &mod_tuscany_oauth2);
+ sc.appkeys = cons<list<value> >(mklist<value>(arg1, mklist<value>(arg2, arg3)), sc.appkeys);
+ return NULL;
+}
+const char* confMemcached(cmd_parms *cmd, unused void *c, const char *arg) {
+ gc_scoped_pool pool(cmd->pool);
+ ServerConf& sc = httpd::serverConf<ServerConf>(cmd, &mod_tuscany_oauth2);
+ sc.mcaddrs = cons<string>(arg, sc.mcaddrs);
+ return NULL;
+}
+const char* confEnabled(cmd_parms *cmd, void *c, const int arg) {
+ gc_scoped_pool pool(cmd->pool);
+ DirConf& dc = httpd::dirConf<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<DirConf>(c);
+ dc.login = arg;
+ return NULL;
+}
+const char* confCAFile(cmd_parms *cmd, unused void *c, const char *arg) {
+ gc_scoped_pool pool(cmd->pool);
+ ServerConf& sc = httpd::serverConf<ServerConf>(cmd, &mod_tuscany_oauth2);
+ sc.ca = arg;
+ return NULL;
+}
+const char* confCertFile(cmd_parms *cmd, unused void *c, const char *arg) {
+ gc_scoped_pool pool(cmd->pool);
+ ServerConf& sc = httpd::serverConf<ServerConf>(cmd, &mod_tuscany_oauth2);
+ sc.cert = arg;
+ return NULL;
+}
+const char* confCertKeyFile(cmd_parms *cmd, unused void *c, const char *arg) {
+ gc_scoped_pool pool(cmd->pool);
+ ServerConf& sc = httpd::serverConf<ServerConf>(cmd, &mod_tuscany_oauth2);
+ sc.key = arg;
+ return NULL;
+}
+
+/**
+ * HTTP server module declaration.
+ */
+const command_rec commands[] = {
+ AP_INIT_TAKE3("AddAuthOAuth2AppKey", (const char*(*)())confAppKey, NULL, RSRC_CONF, "OAuth 2.0 name app-id app-key"),
+ AP_INIT_ITERATE("AddAuthOAuthMemcached", (const char*(*)())confMemcached, NULL, RSRC_CONF, "Memcached server host:port"),
+ AP_INIT_FLAG("AuthOAuth", (const char*(*)())confEnabled, NULL, OR_AUTHCFG, "OAuth 2.0 authentication On | Off"),
+ AP_INIT_TAKE1("AuthOAuthLoginPage", (const char*(*)())confLogin, NULL, OR_AUTHCFG, "OAuth 2.0 login page"),
+ AP_INIT_TAKE1("AuthOAuthSSLCACertificateFile", (const char*(*)())confCAFile, NULL, RSRC_CONF, "OAUth 2.0 SSL CA certificate file"),
+ AP_INIT_TAKE1("AuthOAuthSSLCertificateFile", (const char*(*)())confCertFile, NULL, RSRC_CONF, "OAuth 2.0 SSL certificate file"),
+ AP_INIT_TAKE1("AuthOAuthSSLCertificateKeyFile", (const char*(*)())confCertKeyFile, NULL, RSRC_CONF, "OAuth 2.0 SSL certificate key file"),
+ {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);
+}
+
+}
+}
+
+extern "C" {
+
+module AP_MODULE_DECLARE_DATA mod_tuscany_oauth2 = {
+ STANDARD20_MODULE_STUFF,
+ // dir config and merger
+ tuscany::httpd::makeDirConf<tuscany::oauth2::DirConf>, NULL,
+ // server config and merger
+ tuscany::httpd::makeServerConf<tuscany::oauth2::ServerConf>, NULL,
+ // commands and hooks
+ tuscany::oauth2::commands, tuscany::oauth2::registerHooks
+};
+
+}