From 2d574d2281d05383e646f20147adbc3ca8934430 Mon Sep 17 00:00:00 2001 From: jsdelfino Date: Mon, 6 Sep 2010 07:45:00 +0000 Subject: Strawman implementation of the OAuth 2.0 protocol. Minor improvements to the OpenID support and changes to enable it to co-exist with OAuth. git-svn-id: http://svn.us.apache.org/repos/asf/tuscany@992963 13f79535-47bb-0310-9956-ffa450edef68 --- sca-cpp/trunk/modules/Makefile.am | 2 +- sca-cpp/trunk/modules/http/curl-test.cpp | 4 +- sca-cpp/trunk/modules/http/http.hpp | 26 +- sca-cpp/trunk/modules/http/httpd.hpp | 123 +++++- sca-cpp/trunk/modules/http/mod-ssltunnel.cpp | 12 +- sca-cpp/trunk/modules/json/json.hpp | 7 + sca-cpp/trunk/modules/oauth/Makefile.am | 37 ++ sca-cpp/trunk/modules/oauth/htdocs/index.html | 43 +++ .../trunk/modules/oauth/htdocs/login/index.html | 82 ++++ .../trunk/modules/oauth/htdocs/logout/index.html | 33 ++ .../modules/oauth/htdocs/unprotected/index.html | 27 ++ sca-cpp/trunk/modules/oauth/mod-oauth.cpp | 415 +++++++++++++++++++++ sca-cpp/trunk/modules/oauth/oauth-app-conf | 32 ++ sca-cpp/trunk/modules/oauth/oauth-conf | 64 ++++ sca-cpp/trunk/modules/oauth/oauth-memcached-conf | 32 ++ sca-cpp/trunk/modules/oauth/oauth.composite | 39 ++ sca-cpp/trunk/modules/oauth/start-test | 42 +++ sca-cpp/trunk/modules/oauth/stop-test | 24 ++ sca-cpp/trunk/modules/oauth/user-info.scm | 26 ++ sca-cpp/trunk/modules/openid/Makefile.am | 2 +- .../trunk/modules/openid/htdocs/login/index.html | 5 + .../trunk/modules/openid/htdocs/logout/index.html | 2 +- sca-cpp/trunk/modules/openid/openid-conf | 10 +- sca-cpp/trunk/modules/server/client-test.hpp | 20 +- sca-cpp/trunk/modules/server/mod-eval.hpp | 26 +- sca-cpp/trunk/modules/server/mod-wiring.cpp | 8 +- 26 files changed, 1082 insertions(+), 61 deletions(-) create mode 100644 sca-cpp/trunk/modules/oauth/Makefile.am create mode 100644 sca-cpp/trunk/modules/oauth/htdocs/index.html create mode 100644 sca-cpp/trunk/modules/oauth/htdocs/login/index.html create mode 100644 sca-cpp/trunk/modules/oauth/htdocs/logout/index.html create mode 100644 sca-cpp/trunk/modules/oauth/htdocs/unprotected/index.html create mode 100644 sca-cpp/trunk/modules/oauth/mod-oauth.cpp create mode 100755 sca-cpp/trunk/modules/oauth/oauth-app-conf create mode 100755 sca-cpp/trunk/modules/oauth/oauth-conf create mode 100755 sca-cpp/trunk/modules/oauth/oauth-memcached-conf create mode 100644 sca-cpp/trunk/modules/oauth/oauth.composite create mode 100755 sca-cpp/trunk/modules/oauth/start-test create mode 100755 sca-cpp/trunk/modules/oauth/stop-test create mode 100644 sca-cpp/trunk/modules/oauth/user-info.scm (limited to 'sca-cpp/trunk/modules') diff --git a/sca-cpp/trunk/modules/Makefile.am b/sca-cpp/trunk/modules/Makefile.am index 0924fd04ca..fa07c599ca 100644 --- a/sca-cpp/trunk/modules/Makefile.am +++ b/sca-cpp/trunk/modules/Makefile.am @@ -15,5 +15,5 @@ # specific language governing permissions and limitations # under the License. -SUBDIRS = scheme atom rss json scdl http server python java openid wsgi +SUBDIRS = scheme atom rss json scdl http server python java openid oauth wsgi diff --git a/sca-cpp/trunk/modules/http/curl-test.cpp b/sca-cpp/trunk/modules/http/curl-test.cpp index 1d1b07bff4..a7b8fd90b6 100644 --- a/sca-cpp/trunk/modules/http/curl-test.cpp +++ b/sca-cpp/trunk/modules/http/curl-test.cpp @@ -40,7 +40,7 @@ ostream* curlWriter(const string& s, ostream* os) { } const bool testGet() { - CURLSession ch; + CURLSession ch("", "", ""); { ostringstream os; const failable > r = get(curlWriter, &os, testURI, ch); @@ -69,7 +69,7 @@ struct getLoop { }; const bool testGetPerf() { - CURLSession ch; + CURLSession ch("", "", ""); lambda gl = getLoop(ch); cout << "Static GET test " << time(gl, 5, 200) << " ms" << endl; return true; diff --git a/sca-cpp/trunk/modules/http/http.hpp b/sca-cpp/trunk/modules/http/http.hpp index 6180518e23..1932ac3420 100644 --- a/sca-cpp/trunk/modules/http/http.hpp +++ b/sca-cpp/trunk/modules/http/http.hpp @@ -63,7 +63,10 @@ public: */ class CURLSession { public: - CURLSession(const string& ca = "", const string& cert = "", const string& key = "") : h(curl_easy_init()), p(gc_pool(mkpool())), sock(NULL), wpollset(NULL), wpollfd(NULL), rpollset(NULL), rpollfd(NULL), owner(true), ca(ca), cert(cert), key(key) { + CURLSession() : h(NULL), p(NULL), sock(NULL), wpollset(NULL), wpollfd(NULL), rpollset(NULL), rpollfd(NULL), owner(false), ca(""), cert(""), key("") { + } + + CURLSession(const string& ca, const string& cert, const string& key) : h(curl_easy_init()), p(gc_pool(mkpool())), sock(NULL), wpollset(NULL), wpollfd(NULL), rpollset(NULL), rpollfd(NULL), owner(true), ca(ca), cert(cert), key(key) { } CURLSession(const CURLSession& c) : h(c.h), p(c.p), sock(c.sock), wpollset(c.wpollset), wpollfd(c.wpollfd), rpollset(c.rpollset), rpollfd(c.rpollfd), owner(false), ca(c.ca), cert(c.cert), key(c.key) { @@ -86,7 +89,7 @@ private: apr_pollfd_t* wpollfd; apr_pollset_t* rpollset; apr_pollfd_t* rpollfd; - const bool owner; + bool owner; friend CURL* handle(const CURLSession& cs); friend apr_socket_t* sock(const CURLSession& cs); @@ -95,9 +98,9 @@ private: friend const failable recv(char* c, const int l, const CURLSession& cs); public: - const string ca; - const string cert; - const string key; + string ca; + string cert; + string key; }; /** @@ -397,24 +400,31 @@ const failable get(const string& url, const CURLSession& cs) { const list ls(reverse(cadr(content(res)))); const string ct(content(contentType(car(content(res))))); - if (ct == "application/atom+xml;type=entry") { + debug(ct, "http::get::contentType"); + if (contains(ct, "application/atom+xml;type=entry")) { // Read an ATOM entry const value val(atom::entryValue(content(atom::readATOMEntry(ls)))); debug(val, "http::get::result"); return val; } - if (ct == "application/atom+xml;type=feed" || atom::isATOMFeed(ls)) { + if (contains(ct, "application/atom+xml;type=feed") || atom::isATOMFeed(ls)) { // Read an ATOM feed const value val(atom::feedValues(content(atom::readATOMFeed(ls)))); debug(val, "http::get::result"); return val; } - if (ct == "application/rss+xml" || rss::isRSSFeed(ls)) { + if (contains(ct, "application/rss+xml") || rss::isRSSFeed(ls)) { // Read an RSS feed const value val(rss::feedValues(content(rss::readRSSFeed(ls)))); debug(val, "http::get::result"); return val; } + if (contains(ct, "text/javascript") || contains(ct, "application/json-rpc")) { + json::JSONContext cx; + const value val(json::jsonValues(content(json::readJSON(ls, cx)))); + debug(val, "http::get::result"); + return val; + } // Return the content as a list of values const value val(mkvalues(ls)); diff --git a/sca-cpp/trunk/modules/http/httpd.hpp b/sca-cpp/trunk/modules/http/httpd.hpp index f1b54d79ad..f407e1012e 100644 --- a/sca-cpp/trunk/modules/http/httpd.hpp +++ b/sca-cpp/trunk/modules/http/httpd.hpp @@ -49,6 +49,7 @@ #include "string.hpp" #include "stream.hpp" +#include "sstream.hpp" #include "list.hpp" #include "value.hpp" #include "monad.hpp" @@ -60,7 +61,7 @@ namespace httpd { /** * Returns a server-scoped module configuration. */ -template void* makeServerConf(apr_pool_t *p, server_rec *s) { +template void* makeServerConf(apr_pool_t* p, server_rec* s) { return new (gc_new(p)) C(p, s); } @@ -72,10 +73,24 @@ template C& serverConf(const server_rec* s, const module* mod) { return *(C*)ap_get_module_config(s->module_config, mod); } -template C& serverConf(const cmd_parms *cmd, const module* mod) { +template C& serverConf(const cmd_parms* cmd, const module* mod) { return *(C*)ap_get_module_config(cmd->server->module_config, mod); } +/** + * Returns a directory-scoped module configuration. + */ +template void* makeDirConf(apr_pool_t *p, char* d) { + return new (gc_new(p)) C(p, d); +} + +template const C& dirConf(const request_rec* r, const module* mod) { + return *(C*)ap_get_module_config(r->per_dir_config, mod); +} + +template C& dirConf(const void* c) { + return *(C*)c; +} /** * Return the name of a server. @@ -171,19 +186,97 @@ const list pathInfo(const list& uri, const list& path) { return pathInfo(cdr(uri), cdr(path)); } +/** + * Convert a URI and a path to an absolute URL. + */ +const string url(const string& uri, const list& p, request_rec* r) { + const string u = uri + path(p); + return ap_construct_url(r->pool, c_str(u), r); +} + +/** + * Convert a URI to an absolute URL. + */ +const string url(const string& uri, request_rec* r) { + return ap_construct_url(r->pool, c_str(uri), r); +} + +/** + * Escape a URI. + */ +const char escape_c2x[] = "0123456789abcdef"; +const string escape(const string& uri) { + debug(uri, "httpd::escape::uri"); + char* copy = (char*)apr_palloc(gc_current_pool(), 3 * length(uri) + 3); + const unsigned char* s = (const unsigned char *)c_str(uri); + unsigned char* d = (unsigned char*)copy; + unsigned c; + while ((c = *s)) { + if (apr_isalnum(c) || c == '_') + *d++ = (unsigned char)c; + else if (c == ' ') + *d++ = '+'; + else { + *d++ = '%'; + *d++ = escape_c2x[c >> 4]; + *d++ = escape_c2x[c & 0xf]; + } + ++s; + } + *d = '\0'; + debug(copy, "httpd::escape::result"); + return copy; +} + +/** + * Unescape a URI. + */ +const string unescape(const string& uri) { + debug(uri, "httpd::unescape::uri"); + char* b = const_cast(c_str(string(c_str(uri)))); + ap_unescape_url(b); + debug(b, "httpd::unescape::result"); + return b; +} + /** * Returns a list of key value pairs from the args in a query string. */ const list queryArg(const string& s) { + debug(s, "httpd::queryArg::string"); const list t = tokenize("=", s); return mklist(c_str(car(t)), cadr(t)); } +const list > queryArgs(const string& a) { + return map>(queryArg, tokenize("&", a)); +} + +/** + * Returns a list of key value pairs from the args in an HTTP request. + */ const list > queryArgs(const request_rec* r) { - const char* a = r->args; - if (a == NULL) + if (r->args == NULL) return list >(); - return map>(queryArg, tokenize("&", a)); + return queryArgs(r->args); +} + +/** + * Converts a list of key value pairs to a query string. + */ +ostringstream& queryString(const list > args, ostringstream& os) { + if (isNil(args)) + return os; + debug(car(args), "httpd::queryString::arg"); + os << car(car(args)) << "=" << c_str(cadr(car(args))); + if (!isNil(cdr(args))) + os << "&"; + return queryString(cdr(args), os); +} + +const string queryString(const list > args) { + ostringstream os; + return str(queryString(args, os)); } /** @@ -221,14 +314,6 @@ const list read(request_rec* r) { return cons(string(b, n), read(r)); } -/** - * Convert a URI represented as a list to an absolute URL. - */ -const char* url(const list& v, request_rec* r) { - const string u = string(r->uri) + path(v); - return ap_construct_url(r->pool, c_str(u), r); -} - /** * Write an HTTP result. */ @@ -238,7 +323,7 @@ const failable writeResult(const failable >& ls, const string& ostringstream os; write(content(ls), os); const string ob(str(os)); - debug(ob, "httpd::result"); + debug(ob, "httpd::writeResult"); // Make sure browsers come back and check for updated dynamic content apr_table_setn(r->headers_out, "Expires", "Tue, 01 Jan 1980 00:00:00 GMT"); @@ -477,6 +562,16 @@ const failable internalSubRequest(const string& nr_uri, reque return nr; } +/** + * Return an HTTP external redirect request. + */ +const int externalRedirect(const string& uri, request_rec* r) { + r->status = HTTP_MOVED_TEMPORARILY; + apr_table_setn(r->headers_out, "Location", apr_pstrdup(r->pool, c_str(uri))); + r->filename = apr_pstrdup(r->pool, c_str(string("/redirect:/") + uri)); + return OK; +} + /** * Put a value in the process user data. */ diff --git a/sca-cpp/trunk/modules/http/mod-ssltunnel.cpp b/sca-cpp/trunk/modules/http/mod-ssltunnel.cpp index 0d4fe3045f..c241cd982d 100644 --- a/sca-cpp/trunk/modules/http/mod-ssltunnel.cpp +++ b/sca-cpp/trunk/modules/http/mod-ssltunnel.cpp @@ -68,11 +68,11 @@ extern module AP_DECLARE_DATA core_module; * Process the module configuration. */ int M_SSLTUNNEL; -int postConfigParse(ServerConf& mainsc, apr_pool_t* p, server_rec* s) { +int postConfigMerge(ServerConf& mainsc, apr_pool_t* p, server_rec* s) { if (s == NULL) return OK; ServerConf& sc = httpd::serverConf(s, &mod_tuscany_ssltunnel); - debug(httpd::serverName(s), "modwiring::postConfigParse::serverName"); + debug(httpd::serverName(s), "modwiring::postConfigMerge::serverName"); // Merge configuration from main server if (length(sc.ca) == 0 && length(mainsc.ca) !=0) @@ -93,7 +93,7 @@ int postConfigParse(ServerConf& mainsc, apr_pool_t* p, server_rec* s) { sc.host = uri.hostname; sc.path = uri.path; } - return postConfigParse(mainsc, p, s->next); + return postConfigMerge(mainsc, p, s->next); } int postConfig(apr_pool_t* p, unused apr_pool_t* plog, unused apr_pool_t* ptemp, server_rec* s) { @@ -104,8 +104,8 @@ int postConfig(apr_pool_t* p, unused apr_pool_t* plog, unused apr_pool_t* ptemp, // Register the SSLTUNNEL method M_SSLTUNNEL = ap_method_register(p, "SSLTUNNEL"); - // Parse the configured TunnelPass URI - return postConfigParse(sc, p, s); + // Merge and process server configurations + return postConfigMerge(sc, p, s); } /** @@ -296,7 +296,7 @@ int handler(request_rec* r) { debug(url, "modssltunnel::handler::target"); // Create the target connection - http::CURLSession cs; + http::CURLSession cs("", "", ""); // Run the tunnel return tunnel(r->connection, cs, url, "", gc_pool(r->pool), connectionFilter(r->proto_input_filters), connectionFilter(r->proto_output_filters)); diff --git a/sca-cpp/trunk/modules/json/json.hpp b/sca-cpp/trunk/modules/json/json.hpp index f904718f05..bade096b86 100644 --- a/sca-cpp/trunk/modules/json/json.hpp +++ b/sca-cpp/trunk/modules/json/json.hpp @@ -387,6 +387,13 @@ const failable jsonResultValue(const list& s, JSONContext& cx) { return val; } +/** + * Convert a JSON payload to a list of values. + */ +const list jsonValues(const list& e) { + return elementsToValues(e); +} + /** * Return a portable function name from a JSON-RPC function name. * Strip the "system." and "Service." prefixes added by some JSON-RPC clients. diff --git a/sca-cpp/trunk/modules/oauth/Makefile.am b/sca-cpp/trunk/modules/oauth/Makefile.am new file mode 100644 index 0000000000..077b50bddf --- /dev/null +++ b/sca-cpp/trunk/modules/oauth/Makefile.am @@ -0,0 +1,37 @@ +# 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. + +if WANT_OAUTH + +INCLUDES = -I${HTTPD_INCLUDE} + +dist_mod_SCRIPTS = oauth-conf oauth-memcached-conf oauth-app-conf +moddir=$(prefix)/modules/oauth + +mod_LTLIBRARIES = libmod_tuscany_oauth.la +noinst_DATA = libmod_tuscany_oauth.so + +libmod_tuscany_oauth_la_SOURCES = mod-oauth.cpp +libmod_tuscany_oauth_la_LDFLAGS = -lxml2 -lcurl -lmozjs +libmod_tuscany_oauth.so: + ln -s .libs/libmod_tuscany_oauth.so + +EXTRA_DIST = oauth.composite user-info.scm htdocs/index.html htdocs/login/index.html htdocs/logout/index.html htdocs/unprotected/index.html + +dist_noinst_SCRIPTS = start-test stop-test + +endif diff --git a/sca-cpp/trunk/modules/oauth/htdocs/index.html b/sca-cpp/trunk/modules/oauth/htdocs/index.html new file mode 100644 index 0000000000..e6295a93b5 --- /dev/null +++ b/sca-cpp/trunk/modules/oauth/htdocs/index.html @@ -0,0 +1,43 @@ + + + + + + + + +

Protected area - It works!

+

The following info is returned by a JSONRPC service:

+
+
+ +

User info

+

Sign in

+

Sign out

+

Unprotected area

+ diff --git a/sca-cpp/trunk/modules/oauth/htdocs/login/index.html b/sca-cpp/trunk/modules/oauth/htdocs/login/index.html new file mode 100644 index 0000000000..fcf5a870c0 --- /dev/null +++ b/sca-cpp/trunk/modules/oauth/htdocs/login/index.html @@ -0,0 +1,82 @@ + + +

Sign in with an OAuth 2.0 provider

+ + + +
+ + + + + +
+ +
+

Sign in with your Facebook account

+

Sign in with your Github account

+
+ + diff --git a/sca-cpp/trunk/modules/oauth/htdocs/logout/index.html b/sca-cpp/trunk/modules/oauth/htdocs/logout/index.html new file mode 100644 index 0000000000..02a92d1b31 --- /dev/null +++ b/sca-cpp/trunk/modules/oauth/htdocs/logout/index.html @@ -0,0 +1,33 @@ + + + +

Sign out

+ +
+ + +
+ diff --git a/sca-cpp/trunk/modules/oauth/htdocs/unprotected/index.html b/sca-cpp/trunk/modules/oauth/htdocs/unprotected/index.html new file mode 100644 index 0000000000..af2cd7ca19 --- /dev/null +++ b/sca-cpp/trunk/modules/oauth/htdocs/unprotected/index.html @@ -0,0 +1,27 @@ + + + + +

Unprotected area - It works!

+

User info

+

Sign in

+

Sign out

+

Protected area

+ diff --git a/sca-cpp/trunk/modules/oauth/mod-oauth.cpp b/sca-cpp/trunk/modules/oauth/mod-oauth.cpp new file mode 100644 index 0000000000..9a0c9aa078 --- /dev/null +++ b/sca-cpp/trunk/modules/oauth/mod-oauth.cpp @@ -0,0 +1,415 @@ +/* + * 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 + +#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 "../../components/cache/memcache.hpp" + +extern "C" { +extern module AP_MODULE_DECLARE_DATA mod_tuscany_oauth; +} + +namespace tuscany { +namespace oauth { + +/** + * 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 > apps; + list 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; +}; + +/** + * Check user authentication. + */ +static int checkUserID(request_rec *r) { + // Decline if we're not enabled or AuthType is not set to Open + const DirConf& dc = httpd::dirConf(r, &mod_tuscany_oauth); + 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, "modoauth::checkUserID::input"); + return OK; +} + +/** + * Return the session id from a request. + */ +const maybe sessionID(const list c) { + if (isNil(c)) + return maybe(); + const list kv = tokenize("=", car(c)); + if (!isNil(kv) && !isNil(cdr(kv))) { + if (car(kv) == "TuscanyOpenAuth") + return cadr(kv); + } + return sessionID(cdr(c)); +} + +const maybe sessionID(const request_rec* r) { + const char* c = apr_table_get(r->headers_in, "Cookie"); + debug(c, "modoauth::sessionid::cookies"); + if (c == NULL) + return maybe(); + return sessionID(tokenize(";", c)); +} + +/** + * Return the user info for a session. + */ +const failable userInfo(const value& sid, const ServerConf& sc) { + return memcache::get(mklist("tuscanyOpenAuth", sid), sc.mc); +} + +/** + * Handle an authenticated request. + */ +const failable authenticated(const list >& info, request_rec* r) { + debug(info, "modoauth::authenticated::info"); + + const list id = assoc("id", info); + r->user = apr_pstrdup(r->pool, c_str(cadr(id))); + + const list email = assoc("email", info); + apr_table_set(r->subprocess_env, apr_pstrdup(r->pool, "EMAIL"), apr_pstrdup(r->pool, c_str(cadr(email)))); + + const list fullname = assoc("name", info); + 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 firstname = assoc("first_name", info); + apr_table_set(r->subprocess_env, apr_pstrdup(r->pool, "FIRSTNAME"), apr_pstrdup(r->pool, c_str(cadr(firstname)))); + + const list lastname = assoc("last_name", info); + apr_table_set(r->subprocess_env, apr_pstrdup(r->pool, "LASTNAME"), apr_pstrdup(r->pool, c_str(cadr(lastname)))); + + if(r->ap_auth_type == NULL) + r->ap_auth_type = const_cast("OAuth"); + return DECLINED; +} + +/** + * Redirect to the configured login page. + */ +const failable login(const string& page, request_rec* r) { + const list > largs = mklist >(mklist("mod_oauth_referrer", httpd::escape(httpd::url(r->uri, r)))); + const string loc = httpd::url(page, r) + string("?") + httpd::queryString(largs); + debug(loc, "modoauth::login::uri"); + return httpd::externalRedirect(loc, r); +} + +/** + * Handle an authorize request. + */ +const failable authorize(const list >& args, request_rec* r) { + // Extract authorize URI, access_token URI and client ID + const list auth = assoc("mod_oauth_authorize", args); + const list tok = assoc("mod_oauth_access_token", args); + const list cid = assoc("mod_oauth_client_id", args); + const list info = assoc("mod_oauth_info", args); + + // Build the redirect URI + const list > rargs = mklist >(mklist("mod_oauth_step", "access_token"), tok, cid, info); + const string redir = httpd::url(r->uri, r) + string("?") + httpd::queryString(rargs); + debug(redir, "modoauth::authorize::redir"); + + // Redirect to the authorize URI + const list > aargs = mklist >(mklist("client_id", cadr(cid)), mklist("scope", "email"), mklist("redirect_uri", httpd::escape(redir))); + const string uri = httpd::unescape(cadr(auth)) + string("?") + httpd::queryString(aargs); + debug(uri, "modoauth::authorize::uri"); + return httpd::externalRedirect(uri, r); +} + +/** + * Convert a session id to a cookie string. + */ +const string cookie(const string& sid) { + const time_t t = time(NULL) + 86400; + char exp[32]; + strftime(exp, 32, "%a, %d-%b-%Y %H:%M:%S GMT", gmtime(&t)); + const string c = string("TuscanyOpenAuth=") + sid + string(";path=/;expires=" + string(exp)) + ";secure=TRUE"; + debug(c, "modoauth::cookie"); + return c; +} + +/** + * Handle an access_token request. + */ +const failable access_token(const list >& args, request_rec* r, const ServerConf& sc) { + // Extract access_token URI, client ID and authorization code + const list tok = assoc("mod_oauth_access_token", args); + const list cid = assoc("mod_oauth_client_id", args); + const list info = assoc("mod_oauth_info", args); + const list code = assoc("code", args); + + // Lookup client app configuration + const list app = assoc(cadr(cid), sc.apps); + + // Build the redirect URI + const list > rargs = mklist >(mklist("mod_oauth_step", "access_token"), tok, cid, info); + const string redir = httpd::url(r->uri, r) + string("?") + httpd::queryString(rargs); + debug(redir, "modoauth::access_token::redir"); + + // Request access token + const list > targs = mklist >(mklist("client_id", cadr(cid)), mklist("redirect_uri", httpd::escape(redir)), mklist("client_secret", cadr(app)), code); + const string turi = httpd::unescape(cadr(tok)) + string("?") + httpd::queryString(targs); + debug(turi, "modoauth::access_token::tokenuri"); + const failable tr = http::get(turi, sc.cs); + debug(tr, "modoauth::access_token::response"); + const list tv = assoc("access_token", httpd::queryArgs(join("", convertValues(content(tr))))); + debug(tv, "modoauth::access_token::token"); + + // Request user info + const list > iargs = mklist >(tv); + const string iuri = httpd::unescape(cadr(info)) + string("?") + httpd::queryString(iargs); + debug(iuri, "modoauth::access_token::infouri"); + const failable iv = http::get(iuri, sc.cs); + debug(iv, "modoauth::access_token::info"); + + // Store user info in memcached keyed by session ID + const value sid = mkrand(); + memcache::put(mklist("tuscanyOpenAuth", sid), content(iv), sc.mc); + + // Send session ID to the client in a cookie + apr_table_set(r->err_headers_out, "Set-Cookie", c_str(cookie(sid))); + return httpd::externalRedirect(httpd::url(r->uri, r), r); +} + +/** + * Handle a request. + */ +int handler(request_rec* r) { + // Decline if we're not enabled or if the user is already + // authenticated by another module + const DirConf& dc = httpd::dirConf(r, &mod_tuscany_oauth); + if(!dc.enabled) + return DECLINED; + if (r->user != NULL || apr_table_get(r->subprocess_env, "SSL_REMOTE_USER") != NULL) + return DECLINED; + + gc_scoped_pool pool(r->pool); + httpdDebugRequest(r, "modoauth::handler::input"); + const ServerConf& sc = httpd::serverConf(r, &mod_tuscany_oauth); + + // Nothing to do if we're already authenticated + const maybe sid = sessionID(r); + if (hasContent(sid)) { + const failable info = userInfo(content(sid), sc); + if (hasContent(info)) + return httpd::reportStatus(authenticated(content(info), r)); + } + + // Get the current protocol flow step from the query string + const list > args = httpd::queryArgs(r); + const list sl = assoc("mod_oauth_step", args); + const value step = !isNil(sl) && !isNil(cdr(sl))? cadr(sl) : ""; + + // Handle an authorize request + if (step == "authorize") + return httpd::reportStatus(authorize(args, r)); + + // Handle an access_token request + if (step == "access_token") + return httpd::reportStatus(access_token(args, r, sc)); + + // Redirect to the login page + return httpd::reportStatus(login(dc.login, r)); +} + +/** + * Process the module configuration. + */ +int postConfigMerge(ServerConf& mainsc, server_rec* s) { + if (s == NULL) + return OK; + ServerConf& sc = httpd::serverConf(s, &mod_tuscany_oauth); + debug(httpd::serverName(s), "modoauth::postConfigMerge::serverName"); + + // Merge configuration from main server + if (isNil(sc.apps)) + sc.apps = mainsc.apps; + 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(s, &mod_tuscany_oauth); + debug(httpd::serverName(s), "modoauth::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_oauth); + if(psc == NULL) { + cfailure << "[Tuscany] Due to one or more errors mod_tuscany_oauth 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("localhost", 11211)); + else + sc.mc = *(new (gc_new()) memcache::MemCached(sc.mcaddrs)); + + // Setup a CURL session + sc.cs = *(new (gc_new()) http::CURLSession(sc.ca, sc.cert, sc.key)); + + // Merge the updated configuration into the virtual hosts + postConfigMerge(sc, s->next); +} + +/** + * Configuration commands. + */ +const char* confApp(cmd_parms *cmd, unused void *c, const char *arg1, const char* arg2) { + gc_scoped_pool pool(cmd->pool); + ServerConf& sc = httpd::serverConf(cmd, &mod_tuscany_oauth); + sc.apps = cons >(mklist(arg1, arg2), sc.apps); + return NULL; +} +const char* confMemcached(cmd_parms *cmd, unused void *c, const char *arg) { + gc_scoped_pool pool(cmd->pool); + ServerConf& sc = httpd::serverConf(cmd, &mod_tuscany_oauth); + sc.mcaddrs = cons(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(c); + dc.enabled = (bool)arg; + debug(dc.dir, "modoauth::confEnabled::dir"); + debug(dc.enabled, "modoauth::confEnabled::enabled"); + 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* confCAFile(cmd_parms *cmd, unused void *c, const char *arg) { + gc_scoped_pool pool(cmd->pool); + ServerConf& sc = httpd::serverConf(cmd, &mod_tuscany_oauth); + 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(cmd, &mod_tuscany_oauth); + 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(cmd, &mod_tuscany_oauth); + sc.key = arg; + return NULL; +} + +/** + * HTTP server module declaration. + */ +const command_rec commands[] = { + AP_INIT_ITERATE2("AddAuthOAuthApp", (const char*(*)())confApp, NULL, RSRC_CONF, "OAuth app-id app-secret"), + 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 authentication On | Off"), + AP_INIT_TAKE1("AuthOAuthLoginPage", (const char*(*)())confLogin, NULL, OR_AUTHCFG, "OAuth login page"), + AP_INIT_TAKE1("AuthOAuthSSLCACertificateFile", (const char*(*)())confCAFile, NULL, RSRC_CONF, "OAUth SSL CA certificate file"), + AP_INIT_TAKE1("AuthOAuthSSLCertificateFile", (const char*(*)())confCertFile, NULL, RSRC_CONF, "OAuth SSL certificate file"), + AP_INIT_TAKE1("AuthOAuthSSLCertificateKeyFile", (const char*(*)())confCertKeyFile, NULL, RSRC_CONF, "OAuth 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_user_id(checkUserID, NULL, NULL, APR_HOOK_MIDDLE); + ap_hook_handler(handler, NULL, NULL, APR_HOOK_MIDDLE); +} + +} +} + +extern "C" { + +module AP_MODULE_DECLARE_DATA mod_tuscany_oauth = { + STANDARD20_MODULE_STUFF, + // dir config and merger + tuscany::httpd::makeDirConf, NULL, + // server config and merger + tuscany::httpd::makeServerConf, NULL, + // commands and hooks + tuscany::oauth::commands, tuscany::oauth::registerHooks +}; + +} diff --git a/sca-cpp/trunk/modules/oauth/oauth-app-conf b/sca-cpp/trunk/modules/oauth/oauth-app-conf new file mode 100755 index 0000000000..56beb0af95 --- /dev/null +++ b/sca-cpp/trunk/modules/oauth/oauth-app-conf @@ -0,0 +1,32 @@ +#!/bin/sh + +# 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. + +here=`readlink -f $0`; here=`dirname $here` +mkdir -p $1 +root=`readlink -f $1` +id=$2 +secret=$3 + +# Configure an OAuth App +cat >>$root/conf/httpd.conf <>$root/conf/httpd.conf < +AuthType Open +AuthOAuth On +AuthOAuthLoginPage /login + + +# Enable unauthenticated access to unprotected areas + +AuthOAuth Off + + +AuthOAuth Off + + +AuthOAuth Off + + +EOF + +cat >>$root/conf/vhost-ssl.conf < +AuthType Open +AuthName "$host" +Require valid-user + + +EOF + diff --git a/sca-cpp/trunk/modules/oauth/oauth-memcached-conf b/sca-cpp/trunk/modules/oauth/oauth-memcached-conf new file mode 100755 index 0000000000..8c133688d7 --- /dev/null +++ b/sca-cpp/trunk/modules/oauth/oauth-memcached-conf @@ -0,0 +1,32 @@ +#!/bin/sh + +# 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. + +here=`readlink -f $0`; here=`dirname $here` +mkdir -p $1 +root=`readlink -f $1` +host=$2 +port=$3 + +# Configure HTTPD mod_tuscany_oauth module cache +cat >>$root/conf/httpd.conf < + + + + + + + + + + + + + + anonymous + anonymous@example.com + + + diff --git a/sca-cpp/trunk/modules/oauth/start-test b/sca-cpp/trunk/modules/oauth/start-test new file mode 100755 index 0000000000..0946397f1a --- /dev/null +++ b/sca-cpp/trunk/modules/oauth/start-test @@ -0,0 +1,42 @@ +#!/bin/sh + +# 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. + +# Setup +../../components/cache/memcached-start 11212 +../../components/cache/memcached-start 11213 + +../../modules/http/ssl-ca-conf tmp localhost +../../modules/http/ssl-cert-conf tmp localhost +../../modules/http/httpd-conf tmp localhost 8090 htdocs +../../modules/http/httpd-ssl-conf tmp 8453 + +./oauth-conf tmp +./oauth-memcached-conf tmp localhost 11212 +./oauth-memcached-conf tmp localhost 11213 +./oauth-app-conf tmp app1234 secret6789 + +../../modules/server/server-conf tmp +../../modules/server/scheme-conf tmp +cat >>tmp/conf/httpd.conf <

The following info is generated on the server:

User: " (user) "
Email: " (email) "
"))) + +(define (getuser user email) (user)) + +(define (getemail user email) (email)) + diff --git a/sca-cpp/trunk/modules/openid/Makefile.am b/sca-cpp/trunk/modules/openid/Makefile.am index a613f2ef78..ba6e523ad2 100644 --- a/sca-cpp/trunk/modules/openid/Makefile.am +++ b/sca-cpp/trunk/modules/openid/Makefile.am @@ -25,7 +25,7 @@ mod_DATA = openid.prefix openid.prefix: $(top_builddir)/config.status echo ${MODAUTHOPENID_PREFIX} >openid.prefix -EXTRA_DIST = openid.composite user-info.scm htdocs/index.html htdocs/login/index.html htdocs/logout/index.html htdocs/protected/index.html +EXTRA_DIST = openid.composite user-info.scm htdocs/index.html htdocs/login/index.html htdocs/logout/index.html htdocs/unprotected/index.html dist_noinst_SCRIPTS = start-test stop-test diff --git a/sca-cpp/trunk/modules/openid/htdocs/login/index.html b/sca-cpp/trunk/modules/openid/htdocs/login/index.html index 14f378e968..e6b8c6fce3 100644 --- a/sca-cpp/trunk/modules/openid/htdocs/login/index.html +++ b/sca-cpp/trunk/modules/openid/htdocs/login/index.html @@ -68,6 +68,10 @@ function withVerisign() { return 'https://pip.verisignlabs.com/'; } +function withMySpace() { + return 'https://api.myspace.com/openid'; +} + function withGoogleApps() { return 'https://www.google.com/accounts/o8/site-xrds?ns=2&hd=' + document.fields.domain.value; } @@ -86,6 +90,7 @@ function withXRDSEndpoint() {

Sign in with your Yahoo account

Sign in with your MyOpenID account

Sign in with your Verisign account

+

Sign in with your MySpace account

Sign in with a Google apps domain

diff --git a/sca-cpp/trunk/modules/openid/htdocs/logout/index.html b/sca-cpp/trunk/modules/openid/htdocs/logout/index.html index 5b58a1c38b..02a92d1b31 100644 --- a/sca-cpp/trunk/modules/openid/htdocs/logout/index.html +++ b/sca-cpp/trunk/modules/openid/htdocs/logout/index.html @@ -23,7 +23,7 @@