blob: 5bc212a6810fcb32d23b06257b654c0797123c33 [file] [log] [blame]
/*
* 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>
#define WANT_HTTPD_LOG 1
#include "string.hpp"
#include "stream.hpp"
#include "list.hpp"
#include "tree.hpp"
#include "value.hpp"
#include "monad.hpp"
#include "parallel.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* const p, server_rec* const s) : p(p), server(s) {
}
const gc_pool p;
server_rec* const server;
gc_mutable_ref<string> ca;
gc_mutable_ref<string> cert;
gc_mutable_ref<string> key;
gc_mutable_ref<list<value> > appkeys;
gc_mutable_ref<list<string> > mcaddrs;
gc_mutable_ref<memcache::MemCached> mc;
gc_mutable_ref<perthread_ptr<http::CURLSession> > cs;
};
/**
* Authentication provider configuration.
*/
class AuthnProviderConf {
public:
AuthnProviderConf() : name(), provider(NULL) {
}
AuthnProviderConf(const string name, const authn_provider* provider) : name(name), provider(provider) {
}
const string name;
const authn_provider* provider;
};
/**
* Directory configuration.
*/
class DirConf {
public:
DirConf(apr_pool_t* const p, const char* const d) : p(p), dir(d), enabled(false), login(emptyString) {
}
const gc_pool p;
const char* const dir;
bool enabled;
gc_mutable_ref<string> login;
gc_mutable_ref<list<value> > scopeattrs;
gc_mutable_ref<list<AuthnProviderConf> > apcs;
};
/**
* Run the authnz hooks to authenticate a request.
*/
const failable<int> checkAuthnzProviders(const string& user, request_rec* const r, const list<AuthnProviderConf>& apcs) {
if (isNil(apcs))
return mkfailure<int>("Authentication failure for: " + user, HTTP_UNAUTHORIZED);
const AuthnProviderConf apc = car<AuthnProviderConf>(apcs);
if (apc.provider == NULL || !apc.provider->check_password)
return checkAuthnzProviders(user, 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(string("/oauth2/") + user), "password");
apr_table_unset(r->notes, AUTHN_PROVIDER_NAME_NOTE);
if (auth_result != AUTH_GRANTED)
return checkAuthnzProviders(user, r, cdr(apcs));
return OK;
}
const failable<int> checkAuthnz(const string& user, request_rec* const r, const list<AuthnProviderConf>& apcs) {
if (substr(user, 0, 1) == "/")
return mkfailure<int>(string("Encountered FakeBasicAuth spoof: ") + user, HTTP_UNAUTHORIZED);
if (isNil(apcs)) {
const authn_provider* provider = (const authn_provider*)ap_lookup_provider(AUTHN_PROVIDER_GROUP, AUTHN_DEFAULT_PROVIDER, AUTHN_PROVIDER_VERSION);
return checkAuthnzProviders(user, r, mklist<AuthnProviderConf>(AuthnProviderConf(AUTHN_DEFAULT_PROVIDER, provider)));
}
return checkAuthnzProviders(user, r, apcs);
}
/**
* Return the user info for a session.
*/
const failable<value> userInfo(const value& sid, const memcache::MemCached& mc) {
return memcache::get(mklist<value>("tuscanyOAuth2", sid), mc);
}
/**
* Handle an authenticated request.
*/
const failable<int> authenticated(const list<value>& userinfo, const bool check, request_rec* const r, const list<value>& scopeattrs, const list<AuthnProviderConf>& apcs) {
debug(userinfo, "modoauth2::authenticated::userinfo");
if (isNil(scopeattrs)) {
// Store user id in an environment variable
const list<value> id = assoc<value>("id", userinfo);
if (isNil(id) || isNil(cdr(id)))
return mkfailure<int>("Couldn't retrieve user id", HTTP_UNAUTHORIZED);
apr_table_set(r->subprocess_env, "OAUTH2_ID", apr_pstrdup(r->pool, c_str(cadr(id))));
// If the request user field has not been mapped to another attribute, map the
// OAuth id attribute to it
if (r->user == NULL || r->user[0] == '\0')
r->user = apr_pstrdup(r->pool, c_str(cadr(id)));
// Run the authnz hooks to check the authenticated user
if (check)
return checkAuthnz(r->user == NULL? emptyString : r->user, r, apcs);
return OK;
}
// Store each configured OAuth scope attribute in an environment variable
const list<value> a = car(scopeattrs);
const list<value> v = assoc<value>(cadr(a), userinfo);
if (!isNil(v) && !isNil(cdr(v))) {
// Map the REMOTE_USER attribute to the request user field
if (string(car(a)) == "REMOTE_USER")
r->user = apr_pstrdup(r->pool, c_str(cadr(v)));
else
apr_table_set(r->subprocess_env, apr_pstrdup(r->pool, c_str(car(a))), apr_pstrdup(r->pool, c_str(cadr(v))));
}
return authenticated(userinfo, check, r, cdr(scopeattrs), apcs);
}
/**
* Handle an authorize request.
*/
const failable<int> authorize(const list<value>& args, request_rec* const r, const list<value>& appkeys) {
// Extract authorize, access_token, client ID and info URIs
const list<value> ref = assoc<value>("openauth_referrer", args);
if (isNil(ref) || isNil(cdr(ref)))
return mkfailure<int>("Missing openauth_referrer parameter");
const list<value> auth = assoc<value>("oauth2_authorize", args);
if (isNil(auth) || isNil(cdr(auth)))
return mkfailure<int>("Missing oauth2_authorize parameter");
const list<value> tok = assoc<value>("oauth2_access_token", args);
if (isNil(tok) || isNil(cdr(tok)))
return mkfailure<int>("Missing oauth2_access_token parameter");
const list<value> cid = assoc<value>("oauth2_client_id", args);
if (isNil(cid) || isNil(cdr(cid)))
return mkfailure<int>("Missing oauth2_client_id parameter");
const list<value> info = assoc<value>("oauth2_info", args);
if (isNil(info) || isNil(cdr(info)))
return mkfailure<int>("Missing oauth2_info parameter");
const list<value> scope = assoc<value>("oauth2_scope", args);
if (isNil(scope) || isNil(cdr(scope)))
return mkfailure<int>("Missing oauth2_scope parameter");
const list<value> display = assoc<value>("oauth2_display", args);
// Build the redirect URI
const string redir = httpd::url("/oauth2/access_token/", r);
debug(redir, "modoauth2::authorize::redir");
// Build the state URI
const list<value> stargs = mklist<value>(tok, cid, info, ref);
const string state = http::queryString(stargs);
debug(state, "modoauth2::authorize::state");
// Lookup client app configuration
const list<value> app = assoc<value>(cadr(cid), appkeys);
if (isNil(app) || isNil(cdr(app)))
return mkfailure<int>(string("client id not found: ") + (string)cadr(cid));
list<value> appkey = cadr(app);
// Redirect to the authorize URI
const list<value> adisplay = (isNil(display) || isNil(cdr(display)))? nilListValue : mklist<value>("display", cadr(display));
const list<value> aargs = mklist<value>(mklist<value>("response_type", "code"), mklist<value>("client_id", car(appkey)), mklist<value>("scope", cadr(scope)), adisplay, mklist<value>("redirect_uri", httpd::escape(redir)), mklist<value>("state", httpd::escape(state)));
const string uri = httpd::unescape(cadr(auth)) + string("?") + http::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> accessToken(const list<value>& args, request_rec* r, const list<value>& appkeys, const http::CURLSession& cs, const list<value>& scopeattrs, const list<AuthnProviderConf>& apcs, const memcache::MemCached& mc) {
// Extract access_token URI, client ID and authorization code parameters
const list<value> state = assoc<value>("state", args);
if (isNil(state) || isNil(cdr(state)))
return mkfailure<int>("Missing state parameter");
const list<value>& stargs = httpd::queryArgs(httpd::unescape(cadr(state)));
const list<value> ref = assoc<value>("openauth_referrer", stargs);
if (isNil(ref) || isNil(cdr(ref)))
return mkfailure<int>("Missing openauth_referrer parameter");
const list<value> tok = assoc<value>("oauth2_access_token", stargs);
if (isNil(tok) || isNil(cdr(tok)))
return mkfailure<int>("Missing oauth2_access_token parameter");
const list<value> cid = assoc<value>("oauth2_client_id", stargs);
if (isNil(cid) || isNil(cdr(cid)))
return mkfailure<int>("Missing oauth2_client_id parameter");
const list<value> info = assoc<value>("oauth2_info", stargs);
if (isNil(info) || isNil(cdr(info)))
return mkfailure<int>("Missing 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), appkeys);
if (isNil(app) || isNil(cdr(app)))
return mkfailure<int>(string("client id not found: ") + (string)cadr(cid));
list<value> appkey = cadr(app);
// Build the redirect URI
const string redir = httpd::url("/oauth2/access_token/", r);
debug(redir, "modoauth2::access_token::redir");
// Request access token
const list<value> targs = mklist<value>(mklist<value>("client_id", car(appkey)), mklist<value>("redirect_uri", httpd::escape(redir)), mklist<value>("client_secret", cadr(appkey)), code, mklist<value>("grant_type", "authorization_code"));
const string tqs = http::queryString(targs);
debug(tqs, "modoauth2::access_token::tokenqs");
const string turi = httpd::unescape(cadr(tok));
debug(turi, "modoauth2::access_token::tokenuri");
const value tval = mklist<value>(string("application/x-www-form-urlencoded;charset=UTF-8"), mklist<value>(tqs));
const failable<value> ftr = http::post(tval, turi, cs);
if (!hasContent(ftr))
return mkfailure<int>(ftr);
const value tr = content(ftr);
debug(tr, "modoauth2::access_token::response");
if (!isList(tr) || isNil(tr))
return mkfailure<int>("Empty access token");
const list<value> tv = isString(car<value>(tr)) ?
assoc<value>("access_token", httpd::queryArgs(join("", convertValues<string>(cadr<value>(tr))))) :
assoc<value>("access_token", 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<value> iargs = mklist<value>(tv);
const string iuri = httpd::unescape(cadr(info)) + string("?") + http::queryString(iargs);
debug(iuri, "modoauth2::access_token::infouri");
const failable<value> profres = http::get(iuri, 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> > userinfo = profileUserInfo(cadr(cid), content(profres));
if (!hasContent(userinfo))
return mkfailure<int>(userinfo);
// Validate the authenticated user
const failable<int> authrc = authenticated(content(userinfo), true, r, scopeattrs, apcs);
if (!hasContent(authrc))
return authrc;
// Store user info in memcached keyed by a session ID
const value sid = string("OAuth2_") + (string)mkrand();
const failable<bool> prc = memcache::put(mklist<value>("tuscanyOAuth2", sid), content(userinfo), mc);
if (!hasContent(prc))
return mkfailure<int>(prc);
// Send the session ID to the client in a cookie
debug(c_str(openauth::cookie("TuscanyOAuth2", sid, httpd::hostName(r))), "modoauth2::access_token::setcookie");
apr_table_set(r->err_headers_out, "Set-Cookie", c_str(openauth::cookie("TuscanyOAuth2", sid, httpd::hostName(r))));
return httpd::externalRedirect(httpd::url(httpd::unescape(cadr(ref)), r), r);
}
/**
* Check user authentication.
*/
static int checkAuthn(request_rec *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<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;
debug_httpdRequest(r, "modoauth2::checkAuthn::input");
debug(atype, "modoauth2::checkAuthn::auth_type");
// Get the server configuration
const ServerConf& sc = httpd::serverConf<ServerConf>(r, &mod_tuscany_oauth2);
// Get session id from the request
const maybe<string> sid = openauth::sessionID(r, "TuscanyOAuth2");
if (hasContent(sid)) {
// Decline if the session id was not created by this module
if (substr(content(sid), 0, 7) != "OAuth2_")
return DECLINED;
// Extract the user info from the auth session
const failable<value> userinfo = userInfo(content(sid), sc.mc);
if (!hasContent(userinfo))
return openauth::reportStatus(mkfailure<int>(reason(userinfo), HTTP_UNAUTHORIZED), dc.login, nilValue, r);
r->ap_auth_type = const_cast<char*>(atype);
return openauth::reportStatus(authenticated(content(userinfo), false, r, dc.scopeattrs, dc.apcs), dc.login, nilValue, r);
}
// Get the request args
const list<value> args = httpd::queryArgs(r);
// Handle OAuth authorize request step
if (string(r->uri) == "/oauth2/authorize/") {
r->ap_auth_type = const_cast<char*>(atype);
return openauth::reportStatus(authorize(args, r, sc.appkeys), dc.login, 1, r);
}
// Handle OAuth access_token request step
if (string(r->uri) == "/oauth2/access_token/") {
r->ap_auth_type = const_cast<char*>(atype);
const failable<int> authrc = accessToken(args, r, sc.appkeys, *(*(perthread_ptr<http::CURLSession>*)sc.cs), dc.scopeattrs, dc.apcs, sc.mc);
return openauth::reportStatus(authrc, dc.login, 1, r);
}
// Redirect to the login page, unless we have a session id or an authorization
// header from another module
if (apr_table_get(r->headers_in, (PROXYREQ_PROXY == r->proxyreq) ? "Proxy-Authorization" : "Authorization") != NULL)
return DECLINED;
if (hasContent(openauth::sessionID(r, "TuscanyOpenIDAuth")) ||
hasContent(openauth::sessionID(r, "TuscanyOpenAuth")) ||
hasContent(openauth::sessionID(r, "TuscanyOAuth1")))
return DECLINED;
if ((substr(string(r->uri), 0, 8) == "/oauth1/") || !isNil(assoc<value>("openid_identifier", args)))
return DECLINED;
r->ap_auth_type = const_cast<char*>(atype);
return httpd::reportStatus(openauth::login(dc.login, nilValue, nilValue, r));
}
/**
* Process the module configuration.
*/
int postConfigMerge(const ServerConf& mainsc, server_rec* const 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((list<value>)sc.appkeys))
sc.appkeys = mainsc.appkeys;
if (isNil((list<string>)sc.mcaddrs))
sc.mcaddrs = mainsc.mcaddrs;
sc.mc = mainsc.mc;
sc.cs = mainsc.cs;
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<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* const p, server_rec* const s) {
const gc_scoped_pool sp(p);
ServerConf* const 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((list<string>)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
const string ca = sc.ca;
const string cert = sc.cert;
const string key = sc.key;
const gc_pool cp = gc_current_pool();
const lambda<const gc_ptr<http::CURLSession>()> newsession = [ca, cert, key, cp]() -> const gc_ptr<http::CURLSession> {
const gc_scoped_pool sp(pool(cp));
return new (gc_new<http::CURLSession>()) http::CURLSession(ca, cert, key, emptyString, 0);
};
sc.cs = *(new (gc_new<perthread_ptr<http::CURLSession> >()) perthread_ptr<http::CURLSession>(newsession));
// Merge the updated configuration into the virtual hosts
postConfigMerge(sc, s->next);
}
/**
* Configuration commands.
*/
char* confAppKey(cmd_parms *cmd, unused void *c, char *arg1, char* arg2, char* arg3) {
const gc_scoped_pool sp(cmd->pool);
ServerConf& sc = httpd::serverConf<ServerConf>(cmd, &mod_tuscany_oauth2);
sc.appkeys = cons<value>(mklist<value>(arg1, mklist<value>(arg2, arg3)), (list<value>)sc.appkeys);
return NULL;
}
char* confMemcached(cmd_parms *cmd, unused void *c, char *arg) {
const gc_scoped_pool sp(cmd->pool);
ServerConf& sc = httpd::serverConf<ServerConf>(cmd, &mod_tuscany_oauth2);
sc.mcaddrs = cons<string>(arg, (list<string>)sc.mcaddrs);
return NULL;
}
char* confEnabled(cmd_parms *cmd, void *c, int arg) {
const gc_scoped_pool sp(cmd->pool);
DirConf& dc = httpd::dirConf<DirConf>(c);
dc.enabled = (bool)arg;
return NULL;
}
char* confLogin(cmd_parms *cmd, void *c, char* arg) {
const gc_scoped_pool sp(cmd->pool);
DirConf& dc = httpd::dirConf<DirConf>(c);
dc.login = arg;
return NULL;
}
char* confCAFile(cmd_parms *cmd, unused void *c, char *arg) {
const gc_scoped_pool sp(cmd->pool);
ServerConf& sc = httpd::serverConf<ServerConf>(cmd, &mod_tuscany_oauth2);
sc.ca = arg;
return NULL;
}
char* confCertFile(cmd_parms *cmd, unused void *c, char *arg) {
const gc_scoped_pool sp(cmd->pool);
ServerConf& sc = httpd::serverConf<ServerConf>(cmd, &mod_tuscany_oauth2);
sc.cert = arg;
return NULL;
}
char* confCertKeyFile(cmd_parms *cmd, unused void *c, char *arg) {
const gc_scoped_pool sp(cmd->pool);
ServerConf& sc = httpd::serverConf<ServerConf>(cmd, &mod_tuscany_oauth2);
sc.key = arg;
return NULL;
}
char* confScopeAttr(cmd_parms *cmd, void* c, char* arg1, char* arg2) {
const gc_scoped_pool sp(cmd->pool);
DirConf& dc = httpd::dirConf<DirConf>(c);
dc.scopeattrs = cons<value>(mklist<value>(arg1, arg2), (list<value>)dc.scopeattrs);
return NULL;
}
char* confAuthnProvider(cmd_parms *cmd, void *c, char* arg) {
const gc_scoped_pool sp(cmd->pool);
DirConf& dc = httpd::dirConf<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<AuthnProviderConf>(dc.apcs, mklist<AuthnProviderConf>(AuthnProviderConf(arg, provider)));
return NULL;
}
/**
* HTTP server module declaration.
*/
const command_rec commands[] = {
AP_INIT_ITERATE("AuthOAuthProvider", (const char*(*)())confAuthnProvider, NULL, OR_AUTHCFG, "Auth providers for a directory or location"),
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"),
AP_INIT_TAKE2("AddAuthOAuth2ScopeAttr", (const char*(*)())confScopeAttr, NULL, OR_AUTHCFG, "OAuth 2.0 scope attribute"),
{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
};
}