From commits-return-2872-apmail-couchdb-commits-archive=couchdb.apache.org@couchdb.apache.org Tue Aug 04 19:51:16 2009 Return-Path: Delivered-To: apmail-couchdb-commits-archive@www.apache.org Received: (qmail 28832 invoked from network); 4 Aug 2009 19:51:16 -0000 Received: from hermes.apache.org (HELO mail.apache.org) (140.211.11.3) by minotaur.apache.org with SMTP; 4 Aug 2009 19:51:16 -0000 Received: (qmail 78494 invoked by uid 500); 4 Aug 2009 19:51:21 -0000 Delivered-To: apmail-couchdb-commits-archive@couchdb.apache.org Received: (qmail 78415 invoked by uid 500); 4 Aug 2009 19:51:21 -0000 Mailing-List: contact commits-help@couchdb.apache.org; run by ezmlm Precedence: bulk List-Help: List-Unsubscribe: List-Post: List-Id: Reply-To: dev@couchdb.apache.org Delivered-To: mailing list commits@couchdb.apache.org Received: (qmail 78406 invoked by uid 99); 4 Aug 2009 19:51:21 -0000 Received: from athena.apache.org (HELO athena.apache.org) (140.211.11.136) by apache.org (qpsmtpd/0.29) with ESMTP; Tue, 04 Aug 2009 19:51:21 +0000 X-ASF-Spam-Status: No, hits=-2000.0 required=10.0 tests=ALL_TRUSTED X-Spam-Check-By: apache.org Received: from [140.211.11.4] (HELO eris.apache.org) (140.211.11.4) by apache.org (qpsmtpd/0.29) with ESMTP; Tue, 04 Aug 2009 19:51:10 +0000 Received: by eris.apache.org (Postfix, from userid 65534) id E2B5A23888FC; Tue, 4 Aug 2009 19:50:49 +0000 (UTC) Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit Subject: svn commit: r800938 [1/2] - in /couchdb/trunk: ./ bin/ etc/couchdb/ share/ share/www/script/ share/www/script/test/ src/couchdb/ src/erlang-oauth/ src/mochiweb/ test/ utils/ Date: Tue, 04 Aug 2009 19:50:48 -0000 To: commits@couchdb.apache.org From: damien@apache.org X-Mailer: svnmailer-1.0.8 Message-Id: <20090804195049.E2B5A23888FC@eris.apache.org> X-Virus-Checked: Checked by ClamAV on apache.org Author: damien Date: Tue Aug 4 19:50:46 2009 New Revision: 800938 URL: http://svn.apache.org/viewvc?rev=800938&view=rev Log: Initial check-in of OAuth and cookie authentication. Added: couchdb/trunk/configure.ac.orig couchdb/trunk/share/www/script/oauth.js couchdb/trunk/share/www/script/sha1.js couchdb/trunk/share/www/script/test/cookie_auth.js couchdb/trunk/share/www/script/test/oauth.js couchdb/trunk/src/couchdb/couch_httpd_auth.erl couchdb/trunk/src/couchdb/couch_httpd_oauth.erl couchdb/trunk/src/erlang-oauth/ couchdb/trunk/src/erlang-oauth/Makefile.am couchdb/trunk/src/erlang-oauth/oauth.app couchdb/trunk/src/erlang-oauth/oauth.erl couchdb/trunk/src/erlang-oauth/oauth_hmac_sha1.erl couchdb/trunk/src/erlang-oauth/oauth_http.erl couchdb/trunk/src/erlang-oauth/oauth_plaintext.erl couchdb/trunk/src/erlang-oauth/oauth_rsa_sha1.erl couchdb/trunk/src/erlang-oauth/oauth_unix.erl couchdb/trunk/src/erlang-oauth/oauth_uri.erl Modified: couchdb/trunk/CHANGES couchdb/trunk/Makefile.am couchdb/trunk/bin/Makefile.am couchdb/trunk/bin/couchdb.tpl.in couchdb/trunk/configure.ac couchdb/trunk/etc/couchdb/default.ini.tpl.in couchdb/trunk/etc/couchdb/local.ini couchdb/trunk/share/Makefile.am couchdb/trunk/share/www/script/couch.js couchdb/trunk/share/www/script/couch_tests.js couchdb/trunk/share/www/script/jquery.couch.js couchdb/trunk/share/www/script/test/changes.js couchdb/trunk/share/www/script/test/security_validation.js couchdb/trunk/share/www/script/test/show_documents.js couchdb/trunk/src/couchdb/Makefile.am couchdb/trunk/src/couchdb/couch_db.hrl couchdb/trunk/src/couchdb/couch_httpd.erl couchdb/trunk/src/couchdb/couch_httpd_external.erl couchdb/trunk/src/couchdb/couch_httpd_misc_handlers.erl couchdb/trunk/src/couchdb/couch_httpd_show.erl couchdb/trunk/src/couchdb/couch_httpd_view.erl couchdb/trunk/src/couchdb/couch_rep.erl couchdb/trunk/src/couchdb/couch_util.erl couchdb/trunk/src/mochiweb/mochiweb_cookies.erl couchdb/trunk/test/runner.sh couchdb/trunk/utils/Makefile.am Modified: couchdb/trunk/CHANGES URL: http://svn.apache.org/viewvc/couchdb/trunk/CHANGES?rev=800938&r1=800937&r2=800938&view=diff ============================================================================== --- couchdb/trunk/CHANGES (original) +++ couchdb/trunk/CHANGES Tue Aug 4 19:50:46 2009 @@ -11,6 +11,12 @@ * Changed `couchdb` script configuration options. * Added default.d and local.d configuration directories to load sequence. +HTTP Interface: + + * Added optional cookie-based authentication handler. + * Added optional two-legged OAuth authentication handler. + + Version 0.9.1 ------------- Modified: couchdb/trunk/Makefile.am URL: http://svn.apache.org/viewvc/couchdb/trunk/Makefile.am?rev=800938&r1=800937&r2=800938&view=diff ============================================================================== --- couchdb/trunk/Makefile.am (original) +++ couchdb/trunk/Makefile.am Tue Aug 4 19:50:46 2009 @@ -10,7 +10,7 @@ ## License for the specific language governing permissions and limitations under ## the License. -SUBDIRS = bin etc src/couchdb src/ibrowse src/mochiweb share test var utils +SUBDIRS = bin etc src/couchdb src/erlang-oauth src/ibrowse src/mochiweb share test var utils localdoc_DATA = AUTHORS.gz BUGS.gz CHANGES.gz NEWS.gz README.gz THANKS.gz Modified: couchdb/trunk/bin/Makefile.am URL: http://svn.apache.org/viewvc/couchdb/trunk/bin/Makefile.am?rev=800938&r1=800937&r2=800938&view=diff ============================================================================== --- couchdb/trunk/bin/Makefile.am (original) +++ couchdb/trunk/bin/Makefile.am Tue Aug 4 19:50:46 2009 @@ -31,6 +31,7 @@ -e "s|%couchdbebindir%|couch-@version@/ebin|g" \ -e "s|%mochiwebebindir%|mochiweb-r97/ebin|g" \ -e "s|%ibrowseebindir%|ibrowse-1.4.1/ebin|g" \ + -e "s|%oauthebindir%|erlang-oauth/ebin|g" \ -e "s|%defaultini%|default.ini|g" \ -e "s|%localini%|local.ini|g" \ -e "s|%localconfdir%|@localconfdir@|g" \ Modified: couchdb/trunk/bin/couchdb.tpl.in URL: http://svn.apache.org/viewvc/couchdb/trunk/bin/couchdb.tpl.in?rev=800938&r1=800937&r2=800938&view=diff ============================================================================== --- couchdb/trunk/bin/couchdb.tpl.in (original) +++ couchdb/trunk/bin/couchdb.tpl.in Tue Aug 4 19:50:46 2009 @@ -219,7 +219,9 @@ -pa %localerlanglibdir%/%couchdbebindir% \ %localerlanglibdir%/%mochiwebebindir% \ %localerlanglibdir%/%ibrowseebindir% \ + %localerlanglibdir%/%oauthebindir% \ -eval \"application:load(ibrowse)\" \ + -eval \"application:load(oauth)\" \ -eval \"application:load(crypto)\" \ -eval \"application:load(couch)\" \ -eval \"crypto:start()\" \ Modified: couchdb/trunk/configure.ac URL: http://svn.apache.org/viewvc/couchdb/trunk/configure.ac?rev=800938&r1=800937&r2=800938&view=diff ============================================================================== --- couchdb/trunk/configure.ac (original) +++ couchdb/trunk/configure.ac Tue Aug 4 19:50:46 2009 @@ -289,6 +289,7 @@ AC_CONFIG_FILES([src/couchdb/couch.app.tpl]) AC_CONFIG_FILES([src/couchdb/Makefile]) AC_CONFIG_FILES([src/couchdb/priv/Makefile]) +AC_CONFIG_FILES([src/erlang-oauth/Makefile]) AC_CONFIG_FILES([src/ibrowse/Makefile]) AC_CONFIG_FILES([src/mochiweb/Makefile]) AC_CONFIG_FILES([test/Makefile]) Added: couchdb/trunk/configure.ac.orig URL: http://svn.apache.org/viewvc/couchdb/trunk/configure.ac.orig?rev=800938&view=auto ============================================================================== --- couchdb/trunk/configure.ac.orig (added) +++ couchdb/trunk/configure.ac.orig Tue Aug 4 19:50:46 2009 @@ -0,0 +1,303 @@ +dnl Licensed under the Apache License, Version 2.0 (the "License"); you may not +dnl use this file except in compliance with the License. dnl You may obtain a +dnl copy of the License at +dnl +dnl http://www.apache.org/licenses/LICENSE-2.0 +dnl +dnl Unless required by applicable law or agreed to in writing, software +dnl distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +dnl WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +dnl License for the specific language governing permissions and limitations +dnl under the License. + +m4_include([m4/ac_check_icu.m4]) + +AC_INIT([LOCAL_PACKAGE_NAME], [LOCAL_VERSION], [], [LOCAL_PACKAGE_TARNAME]) + +AC_PREREQ([2.59]) + +AC_CONFIG_SRCDIR([CHANGES]) +AC_CONFIG_AUX_DIR([build-aux]) + +AM_CONFIG_HEADER([config.h]) + +AM_INIT_AUTOMAKE([1.6.3 foreign]) + +AC_GNU_SOURCE +AC_ENABLE_SHARED +AC_DISABLE_STATIC + +AC_PROG_CC +AC_PROG_LIBTOOL +AC_PROG_LN_S + +AC_MSG_CHECKING([for pthread_create in -lpthread]) + +original_LIBS="$LIBS" +LIBS="-lpthread $original_LIBS" + +AC_TRY_LINK([#include], + [pthread_create((void *)0, (void *)0, (void *)0, (void *)0)], + [pthread=yes], [pthread=no]) + +if test x${pthread} = xyes; then + AC_MSG_RESULT([yes]) +else + LIBS="$original_LIBS" + AC_MSG_RESULT([no]) +fi + +AC_ARG_WITH([erlang], [AC_HELP_STRING([--with-erlang=PATH], + [set PATH to the Erlang include directory])], [ + ERLANG_FLAGS="-I$withval" +], [ + ERLANG_FLAGS="-I${libdir}/erlang/usr/include" + ERLANG_FLAGS="$ERLANG_FLAGS -I/usr/lib/erlang/usr/include" + ERLANG_FLAGS="$ERLANG_FLAGS -I/usr/local/lib/erlang/usr/include" + ERLANG_FLAGS="$ERLANG_FLAGS -I/opt/local/lib/erlang/usr/include" +]) + +AC_ARG_WITH([js-include], [AC_HELP_STRING([--with-js-include=PATH], + [set PATH to the SpiderMonkey include directory])], [ + JS_INCLUDE="$withval" + JS_FLAGS="-I$JS_INCLUDE" +], [ + JS_FLAGS="-I/usr/include" + JS_FLAGS="$JS_FLAGS -I/usr/include/js" + JS_FLAGS="$JS_FLAGS -I/usr/include/mozjs" + JS_FLAGS="$JS_FLAGS -I/usr/local/include" + JS_FLAGS="$JS_FLAGS -I/opt/local/include" + JS_FLAGS="$JS_FLAGS -I/usr/local/include/js" + JS_FLAGS="$JS_FLAGS -I/opt/local/include/js" +]) + +AC_ARG_WITH([js-lib], [AC_HELP_STRING([--with-js-lib=PATH], + [set PATH to the SpiderMonkey library directory])], + [JS_LIB_FLAGS="-L$withval"], []) + +AC_ARG_VAR([ERLC_FLAGS], [general flags to prepend to ERLC_FLAGS]) +AC_ARG_VAR([FLAGS], [general flags to prepend to LDFLAGS and CPPFLAGS]) + +LIB_FLAGS="$JS_LIB_FLAGS -L/usr/local/lib -L/opt/local/lib" +LIBS="$LIB_FLAGS $LIBS" +# XP_UNIX required for jsapi.h and has been tested to work on Linux and Darwin. +FLAGS="$LIB_FLAGS $ERLANG_FLAGS $JS_FLAGS -DXP_UNIX $FLAGS" +CPPFLAGS="$FLAGS $CPPFLAGS" +# manually linking libm is requred for FreeBSD 7.0 +LDFLAGS="$FLAGS -lm $LDFLAGS" + +AC_CHECK_LIB([mozjs], [JS_NewContext], [], [ + AC_CHECK_LIB([js], [JS_NewContext], [], [ + AC_MSG_ERROR([Could not find the js library. + +Is the Mozilla SpiderMonkey library installed?])])]) + +AC_CHECK_HEADER([jsapi.h], [], [ + AC_CHECK_HEADER([js/jsapi.h], + [ + CPPFLAGS="$CPPFLAGS -I$JS_INCLUDE/js" + ], + [ + AC_MSG_ERROR([Could not find the jsapi header. + +Are the Mozilla SpiderMonkey headers installed?]) + ])]) + +AC_LANG_PUSH(C) +OLD_CFLAGS="$CFLAGS" +CFLAGS="-Werror-implicit-function-declaration" +AC_COMPILE_IFELSE( + [AC_LANG_PROGRAM( + [[#include ]], + [[JS_SetOperationCallback(0, 0);]] + )], + AC_DEFINE([USE_JS_SETOPCB], [], [Use new JS_SetOperationCallback]) +) +CFLAGS="$OLD_CFLAGS" +AC_LANG_POP(C) + +AC_CHECK_ICU([3]) + +ICU_LOCAL_CFLAGS=`$ICU_CONFIG --cppflags-searchpath` +ICU_LOCAL_LDFLAGS=`$ICU_CONFIG --ldflags-searchpath` + +AC_SUBST(ICU_CONFIG) +AC_SUBST(ICU_LOCAL_CFLAGS) +AC_SUBST(ICU_LOCAL_LDFLAGS) + +AC_CHECK_CURL([7.15.5]) +AC_SUBST(CURL_CFLAGS) +AC_SUBST(CURL_LIBS) + +case "$(uname -s)" in + Linux) + LIBS="$LIBS -lcrypt" + CPPFLAGS="-D_XOPEN_SOURCE $CPPFLAGS" + ;; + FreeBSD) + LIBS="$LIBS -lcrypt" + ;; + OpenBSD) + LIBS="$LIBS -lcrypto" + ;; +esac + +AC_PATH_PROG([ERL], [erl]) + +if test x${ERL} = x; then + AC_MSG_ERROR([Could not find the `erl' executable. Is Erlang installed?]) +fi + +erlang_version_error="The installed Erlang version is less than 5.6.0 (R12B)." + +version="`${ERL} -version 2>&1 | ${SED} "s/[[^0-9]]/ /g"`" + +if test `echo $version | ${AWK} "{print \\$1}"` -lt 5; then + AC_MSG_ERROR([$erlang_version_error]) +fi + +if test `echo $version | ${AWK} "{print \\$2}"` -lt 6; then + AC_MSG_ERROR([$erlang_version_error]) +fi + +AC_PATH_PROG([ERLC], [erlc]) + +if test x${ERLC} = x; then + AC_MSG_ERROR([Could not find the `erlc' executable. Is Erlang installed?]) +fi + +AC_CHECK_HEADER([erl_driver.h], [], [ + AC_MSG_ERROR([Could not find the `erl_driver.h' header. + +Are the Erlang headers installed? Use the `--with-erlang' option to specify the +path to the Erlang include directory.])]) + +AC_PATH_PROG([HELP2MAN_EXECUTABLE], [help2man]) +if test x${HELP2MAN_EXECUTABLE} = x; then + AC_MSG_WARN([You will be unable to regenerate any man pages.]) +fi + +use_init=yes +use_launchd=yes + +AC_ARG_ENABLE([init], [AC_HELP_STRING([--disable-init], + [don't install init script where applicable])], [ + use_init=$enableval +], []) + +AC_ARG_ENABLE([launchd], [AC_HELP_STRING([--disable-launchd], + [don't install launchd configuration where applicable])], [ + use_launchd=$enableval +], []) + +init_enabled=false +launchd_enabled=false + +if test "$use_init" = "yes"; then + AC_MSG_CHECKING(location of init directory) + if test -d /etc/rc.d; then + init_enabled=true + AC_SUBST([initdir], ['${sysconfdir}/rc.d']) + AC_MSG_RESULT(${initdir}) + else + if test -d /etc/init.d; then + init_enabled=true + AC_SUBST([initdir], ['${sysconfdir}/init.d']) + AC_MSG_RESULT(${initdir}) + else + AC_MSG_RESULT(not found) + fi + fi +fi + +if test "$use_launchd" = "yes"; then + AC_MSG_CHECKING(location of launchd directory) + if test -d /Library/LaunchDaemons; then + init_enabled=false + launchd_enabled=true + AC_SUBST([launchddir], ['${prefix}/Library/LaunchDaemons']) + AC_MSG_RESULT(${launchddir}) + else + AC_MSG_RESULT(not found) + fi +fi + +AC_ARG_VAR([ERL], [path to the `erl' executable]) +AC_ARG_VAR([ERLC], [path to the `erlc' executable]) +AC_ARG_VAR([HELP2MAN_EXECUTABLE], [path to the `help2man' program]) + +if test -n "$HELP2MAN_EXECUTABLE"; then + help2man_enabled=true +else + if test -f "$srcdir/bin/couchdb.1" -a -f "$srcdir/bin/couchjs.1"; then + help2man_enabled=true + else + help2man_enabled=false + fi +fi + +AM_CONDITIONAL([INIT], [test x${init_enabled} = xtrue]) +AM_CONDITIONAL([LAUNCHD], [test x${launchd_enabled} = xtrue]) +AM_CONDITIONAL([HELP2MAN], [test x${help2man_enabled} = xtrue]) + +AC_SUBST([package_author_name], ["LOCAL_PACKAGE_AUTHOR_NAME"]) +AC_SUBST([package_author_address], ["LOCAL_PACKAGE_AUTHOR_ADDRESS"]) +AC_SUBST([package_identifier], ["LOCAL_PACKAGE_IDENTIFIER"]) +AC_SUBST([package_tarname], ["LOCAL_PACKAGE_TARNAME"]) +AC_SUBST([package_name], ["LOCAL_PACKAGE_NAME"]) + +AC_SUBST([version], ["LOCAL_VERSION"]) +AC_SUBST([version_major], ["LOCAL_VERSION_MAJOR"]) +AC_SUBST([version_minor], ["LOCAL_VERSION_MINOR"]) +AC_SUBST([version_revision], ["LOCAL_VERSION_REVISION"]) +AC_SUBST([version_stage], ["LOCAL_VERSION_STAGE"]) +AC_SUBST([version_release], ["LOCAL_VERSION_RELEASE"]) + +AC_SUBST([bug_uri], ["LOCAL_BUG_URI"]) + +AC_SUBST([localconfdir], [${sysconfdir}/${package_identifier}]) +AC_SUBST([localdatadir], [${datadir}/${package_identifier}]) +AC_SUBST([localdocdir], [${datadir}/doc/${package_identifier}]) +AC_SUBST([locallibdir], [${libdir}/${package_identifier}]) +AC_SUBST([localstatelibdir], [${localstatedir}/lib/${package_identifier}]) +AC_SUBST([localstatelogdir], [${localstatedir}/log/${package_identifier}]) +AC_SUBST([localstaterundir], [${localstatedir}/run/${package_identifier}]) +AC_SUBST([locallibbindir], [${locallibdir}/bin]) +AC_SUBST([localerlanglibdir], [${locallibdir}/erlang/lib]) + +# fix for older autotools that don't define "abs_top_YYY" by default +AC_SUBST(abs_top_srcdir) +AC_SUBST(abs_top_builddir) + +AC_REVISION([LOCAL_VERSION]) + +AC_CONFIG_FILES([Makefile]) +AC_CONFIG_FILES([bin/couchjs.tpl]) +AC_CONFIG_FILES([bin/couchdb.tpl]) +AC_CONFIG_FILES([bin/Makefile]) +AC_CONFIG_FILES([etc/couchdb/Makefile]) +AC_CONFIG_FILES([etc/couchdb/default.ini.tpl]) +AC_CONFIG_FILES([etc/default/Makefile]) +AC_CONFIG_FILES([etc/init/couchdb.tpl]) +AC_CONFIG_FILES([etc/init/Makefile]) +AC_CONFIG_FILES([etc/launchd/org.apache.couchdb.plist.tpl]) +AC_CONFIG_FILES([etc/launchd/Makefile]) +AC_CONFIG_FILES([etc/logrotate.d/couchdb.tpl]) +AC_CONFIG_FILES([etc/logrotate.d/Makefile]) +AC_CONFIG_FILES([etc/Makefile]) +AC_CONFIG_FILES([share/Makefile]) +AC_CONFIG_FILES([src/couchdb/couch.app.tpl]) +AC_CONFIG_FILES([src/couchdb/Makefile]) +AC_CONFIG_FILES([src/couchdb/priv/Makefile]) +AC_CONFIG_FILES([src/ibrowse/Makefile]) +AC_CONFIG_FILES([src/mochiweb/Makefile]) +AC_CONFIG_FILES([test/Makefile]) +AC_CONFIG_FILES([utils/Makefile]) +AC_CONFIG_FILES([var/Makefile]) + +AC_OUTPUT + +echo +echo "You have configured Apache CouchDB, time to relax." +echo +echo "Run \`make && sudo make install' to install." Modified: couchdb/trunk/etc/couchdb/default.ini.tpl.in URL: http://svn.apache.org/viewvc/couchdb/trunk/etc/couchdb/default.ini.tpl.in?rev=800938&r1=800937&r2=800938&view=diff ============================================================================== --- couchdb/trunk/etc/couchdb/default.ini.tpl.in (original) +++ couchdb/trunk/etc/couchdb/default.ini.tpl.in Tue Aug 4 19:50:46 2009 @@ -16,7 +16,7 @@ [httpd] port = 5984 bind_address = 127.0.0.1 -authentication_handler = {couch_httpd, default_authentication_handler} +authentication_handlers = {couch_httpd_oauth, oauth_authentication_handler}, {couch_httpd_auth, default_authentication_handler} default_handler = {couch_httpd_db, handle_request} WWW-Authenticate = Basic realm="administrator" @@ -24,6 +24,11 @@ file = %localstatelogdir%/couch.log level = info +[couch_httpd_auth] +authentication_db = users +secret = replace this with a real secret in your local.ini file +require_valid_user = false + [query_servers] javascript = %bindir%/%couchjs_command_name% %localdatadir%/server/main.js @@ -62,7 +67,9 @@ _stats = {couch_httpd_stats_handlers, handle_stats_req} _log = {couch_httpd_misc_handlers, handle_log_req} _sleep = {couch_httpd_misc_handlers, handle_sleep_req} -_whoami = {couch_httpd_misc_handlers, handle_whoami_req} +_session = {couch_httpd_auth, handle_session_req} +_oauth = {couch_httpd_oauth, handle_oauth_req} +_user = {couch_httpd_auth, handle_user_req} [httpd_db_handlers] _view_cleanup = {couch_httpd_db, handle_view_cleanup_req} Modified: couchdb/trunk/etc/couchdb/local.ini URL: http://svn.apache.org/viewvc/couchdb/trunk/etc/couchdb/local.ini?rev=800938&r1=800937&r2=800938&view=diff ============================================================================== --- couchdb/trunk/etc/couchdb/local.ini (original) +++ couchdb/trunk/etc/couchdb/local.ini Tue Aug 4 19:50:46 2009 @@ -14,6 +14,10 @@ [log] ;level = debug +[couch_httpd_auth] +;secret = replace this with a real secret + + [update_notification] ;unique notifier name=/full/path/to/exe -with "cmd line arg" Modified: couchdb/trunk/share/Makefile.am URL: http://svn.apache.org/viewvc/couchdb/trunk/share/Makefile.am?rev=800938&r1=800937&r2=800938&view=diff ============================================================================== --- couchdb/trunk/share/Makefile.am (original) +++ couchdb/trunk/share/Makefile.am Tue Aug 4 19:50:46 2009 @@ -93,6 +93,8 @@ www/script/jquery.resizer.js \ www/script/jquery.suggest.js \ www/script/json2.js \ + www/script/oauth.js \ + www/script/sha1.js \ www/script/test/basics.js \ www/script/test/delayed_commits.js \ www/script/test/all_docs.js \ @@ -138,6 +140,8 @@ www/script/test/purge.js \ www/script/test/config.js \ www/script/test/security_validation.js \ + www/script/test/cookie_auth.js \ + www/script/test/oauth.js \ www/script/test/stats.js \ www/script/test/changes.js \ www/script/test/lorem.txt \ Modified: couchdb/trunk/share/www/script/couch.js URL: http://svn.apache.org/viewvc/couchdb/trunk/share/www/script/couch.js?rev=800938&r1=800937&r2=800938&view=diff ============================================================================== --- couchdb/trunk/share/www/script/couch.js [utf-8] (original) +++ couchdb/trunk/share/www/script/couch.js [utf-8] Tue Aug 4 19:50:46 2009 @@ -299,6 +299,67 @@ // Use this from callers to check HTTP status or header values of requests. CouchDB.last_req = null; +CouchDB.login = function(username, password) { + CouchDB.last_req = CouchDB.request("POST", "/_session", { + headers: {"Content-Type": "application/x-www-form-urlencoded", + "X-CouchDB-WWW-Authenticate": "Cookie"}, + body: "username=" + encodeURIComponent(username) + "&password=" + encodeURIComponent(password) + }); + return JSON.parse(CouchDB.last_req.responseText); +} + +CouchDB.logout = function() { + CouchDB.last_req = CouchDB.request("DELETE", "/_session", { + headers: {"Content-Type": "application/x-www-form-urlencoded", + "X-CouchDB-WWW-Authenticate": "Cookie"} + }); + return JSON.parse(CouchDB.last_req.responseText); +} + +CouchDB.createUser = function(username, password, email, roles, basicAuth) { + var roles_str = "" + if (roles) { + for (var i=0; i< roles.length; i++) { + roles_str += "&roles=" + encodeURIComponent(roles[i]); + } + } + var headers = {"Content-Type": "application/x-www-form-urlencoded"}; + if (!basicAuth) { + headers['X-CouchDB-WWW-Authenticate'] = 'Cookie'; + } + + CouchDB.last_req = CouchDB.request("POST", "/_user/", { + headers: headers, + body: "username=" + encodeURIComponent(username) + "&password=" + encodeURIComponent(password) + + "&email="+ encodeURIComponent(email)+ roles_str + + }); + return JSON.parse(CouchDB.last_req.responseText); +} + +CouchDB.updateUser = function(username, email, roles, password, old_password) { + var roles_str = "" + if (roles) { + for (var i=0; i< roles.length; i++) { + roles_str += "&roles=" + encodeURIComponent(roles[i]); + } + } + + var body = "email="+ encodeURIComponent(email)+ roles_str; + + if (typeof(password) != "undefined" && password) + body += "&password=" + password; + + if (typeof(old_password) != "undefined" && old_password) + body += "&old_password=" + old_password; + + CouchDB.last_req = CouchDB.request("PUT", "/_user/"+encodeURIComponent(username), { + headers: {"Content-Type": "application/x-www-form-urlencoded", + "X-CouchDB-WWW-Authenticate": "Cookie"}, + body: body + }); + return JSON.parse(CouchDB.last_req.responseText); +} CouchDB.allDbs = function() { CouchDB.last_req = CouchDB.request("GET", "/_all_dbs"); Modified: couchdb/trunk/share/www/script/couch_tests.js URL: http://svn.apache.org/viewvc/couchdb/trunk/share/www/script/couch_tests.js?rev=800938&r1=800937&r2=800938&view=diff ============================================================================== --- couchdb/trunk/share/www/script/couch_tests.js [utf-8] (original) +++ couchdb/trunk/share/www/script/couch_tests.js [utf-8] Tue Aug 4 19:50:46 2009 @@ -73,6 +73,10 @@ loadTest("config.js"); loadTest("form_submit.js"); loadTest("security_validation.js"); +loadTest("cookie_auth.js"); +loadScript("script/sha1.js"); +loadScript("script/oauth.js"); +loadTest("oauth.js"); loadTest("stats.js"); loadTest("rev_stemming.js"); Modified: couchdb/trunk/share/www/script/jquery.couch.js URL: http://svn.apache.org/viewvc/couchdb/trunk/share/www/script/jquery.couch.js?rev=800938&r1=800937&r2=800938&view=diff ============================================================================== --- couchdb/trunk/share/www/script/jquery.couch.js [utf-8] (original) +++ couchdb/trunk/share/www/script/jquery.couch.js [utf-8] Tue Aug 4 19:50:46 2009 @@ -61,6 +61,41 @@ ); }, + // TODO make login/logout and db.login/db.logout DRY + login: function(options) { + options = options || {}; + $.ajax({ + type: "POST", url: "/_login", dataType: "json", + data: {username: options.username, password: options.password}, + complete: function(req) { + var resp = $.httpData(req, "json"); + if (req.status == 200) { + if (options.success) options.success(resp); + } else if (options.error) { + options.error(req.status, resp.error, resp.reason); + } else { + alert("An error occurred logging in: " + resp.reason); + } + } + }); + }, + logout: function(options) { + options = options || {}; + $.ajax({ + type: "POST", url: "/_logout", dataType: "json", + complete: function(req) { + var resp = $.httpData(req, "json"); + if (req.status == 200) { + if (options.success) options.success(resp); + } else if (options.error) { + options.error(req.status, resp.error, resp.reason); + } else { + alert("An error occurred logging out: " + resp.reason); + } + } + }); + }, + db: function(name) { return { name: name, Added: couchdb/trunk/share/www/script/oauth.js URL: http://svn.apache.org/viewvc/couchdb/trunk/share/www/script/oauth.js?rev=800938&view=auto ============================================================================== --- couchdb/trunk/share/www/script/oauth.js (added) +++ couchdb/trunk/share/www/script/oauth.js Tue Aug 4 19:50:46 2009 @@ -0,0 +1,511 @@ +/* + * Copyright 2008 Netflix, Inc. + * + * Licensed 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's some JavaScript software for implementing OAuth. + + This isn't as useful as you might hope. OAuth is based around + allowing tools and websites to talk to each other. However, + JavaScript running in web browsers is hampered by security + restrictions that prevent code running on one website from + accessing data stored or served on another. + + Before you start hacking, make sure you understand the limitations + posed by cross-domain XMLHttpRequest. + + On the bright side, some platforms use JavaScript as their + language, but enable the programmer to access other web sites. + Examples include Google Gadgets, and Microsoft Vista Sidebar. + For those platforms, this library should come in handy. +*/ + +// The HMAC-SHA1 signature method calls b64_hmac_sha1, defined by +// http://pajhome.org.uk/crypt/md5/sha1.js + +/* An OAuth message is represented as an object like this: + {method: "GET", action: "http://server.com/path", parameters: ...} + + The parameters may be either a map {name: value, name2: value2} + or an Array of name-value pairs [[name, value], [name2, value2]]. + The latter representation is more powerful: it supports parameters + in a specific sequence, or several parameters with the same name; + for example [["a", 1], ["b", 2], ["a", 3]]. + + Parameter names and values are NOT percent-encoded in an object. + They must be encoded before transmission and decoded after reception. + For example, this message object: + {method: "GET", action: "http://server/path", parameters: {p: "x y"}} + ... can be transmitted as an HTTP request that begins: + GET /path?p=x%20y HTTP/1.0 + (This isn't a valid OAuth request, since it lacks a signature etc.) + Note that the object "x y" is transmitted as x%20y. To encode + parameters, you can call OAuth.addToURL, OAuth.formEncode or + OAuth.getAuthorization. + + This message object model harmonizes with the browser object model for + input elements of an form, whose value property isn't percent encoded. + The browser encodes each value before transmitting it. For example, + see consumer.setInputs in example/consumer.js. + */ +var OAuth; if (OAuth == null) OAuth = {}; + +OAuth.setProperties = function setProperties(into, from) { + if (into != null && from != null) { + for (var key in from) { + into[key] = from[key]; + } + } + return into; +} + +OAuth.setProperties(OAuth, // utility functions +{ + percentEncode: function percentEncode(s) { + if (s == null) { + return ""; + } + if (s instanceof Array) { + var e = ""; + for (var i = 0; i < s.length; ++s) { + if (e != "") e += '&'; + e += percentEncode(s[i]); + } + return e; + } + s = encodeURIComponent(s); + // Now replace the values which encodeURIComponent doesn't do + // encodeURIComponent ignores: - _ . ! ~ * ' ( ) + // OAuth dictates the only ones you can ignore are: - _ . ~ + // Source: http://developer.mozilla.org/en/docs/Core_JavaScript_1.5_Reference:Global_Functions:encodeURIComponent + s = s.replace(/\!/g, "%21"); + s = s.replace(/\*/g, "%2A"); + s = s.replace(/\'/g, "%27"); + s = s.replace(/\(/g, "%28"); + s = s.replace(/\)/g, "%29"); + return s; + } +, + decodePercent: function decodePercent(s) { + if (s != null) { + // Handle application/x-www-form-urlencoded, which is defined by + // http://www.w3.org/TR/html4/interact/forms.html#h-17.13.4.1 + s = s.replace(/\+/g, " "); + } + return decodeURIComponent(s); + } +, + /** Convert the given parameters to an Array of name-value pairs. */ + getParameterList: function getParameterList(parameters) { + if (parameters == null) { + return []; + } + if (typeof parameters != "object") { + return decodeForm(parameters + ""); + } + if (parameters instanceof Array) { + return parameters; + } + var list = []; + for (var p in parameters) { + list.push([p, parameters[p]]); + } + return list; + } +, + /** Convert the given parameters to a map from name to value. */ + getParameterMap: function getParameterMap(parameters) { + if (parameters == null) { + return {}; + } + if (typeof parameters != "object") { + return getParameterMap(decodeForm(parameters + "")); + } + if (parameters instanceof Array) { + var map = {}; + for (var p = 0; p < parameters.length; ++p) { + var key = parameters[p][0]; + if (map[key] === undefined) { // first value wins + map[key] = parameters[p][1]; + } + } + return map; + } + return parameters; + } +, + getParameter: function getParameter(parameters, name) { + if (parameters instanceof Array) { + for (var p = 0; p < parameters.length; ++p) { + if (parameters[p][0] == name) { + return parameters[p][1]; // first value wins + } + } + } else { + return OAuth.getParameterMap(parameters)[name]; + } + return null; + } +, + formEncode: function formEncode(parameters) { + var form = ""; + var list = OAuth.getParameterList(parameters); + for (var p = 0; p < list.length; ++p) { + var value = list[p][1]; + if (value == null) value = ""; + if (form != "") form += '&'; + form += OAuth.percentEncode(list[p][0]) + +'='+ OAuth.percentEncode(value); + } + return form; + } +, + decodeForm: function decodeForm(form) { + var list = []; + var nvps = form.split('&'); + for (var n = 0; n < nvps.length; ++n) { + var nvp = nvps[n]; + if (nvp == "") { + continue; + } + var equals = nvp.indexOf('='); + var name; + var value; + if (equals < 0) { + name = OAuth.decodePercent(nvp); + value = null; + } else { + name = OAuth.decodePercent(nvp.substring(0, equals)); + value = OAuth.decodePercent(nvp.substring(equals + 1)); + } + list.push([name, value]); + } + return list; + } +, + setParameter: function setParameter(message, name, value) { + var parameters = message.parameters; + if (parameters instanceof Array) { + for (var p = 0; p < parameters.length; ++p) { + if (parameters[p][0] == name) { + if (value === undefined) { + parameters.splice(p, 1); + } else { + parameters[p][1] = value; + value = undefined; + } + } + } + if (value !== undefined) { + parameters.push([name, value]); + } + } else { + parameters = OAuth.getParameterMap(parameters); + parameters[name] = value; + message.parameters = parameters; + } + } +, + setParameters: function setParameters(message, parameters) { + var list = OAuth.getParameterList(parameters); + for (var i = 0; i < list.length; ++i) { + OAuth.setParameter(message, list[i][0], list[i][1]); + } + } +, + /** Fill in parameters to help construct a request message. + This function doesn't fill in every parameter. + The accessor object should be like: + {consumerKey:'foo', consumerSecret:'bar', accessorSecret:'nurn', token:'krelm', tokenSecret:'blah'} + The accessorSecret property is optional. + */ + completeRequest: function completeRequest(message, accessor) { + if (message.method == null) { + message.method = "GET"; + } + var map = OAuth.getParameterMap(message.parameters); + if (map.oauth_consumer_key == null) { + OAuth.setParameter(message, "oauth_consumer_key", accessor.consumerKey || ""); + } + if (map.oauth_token == null && accessor.token != null) { + OAuth.setParameter(message, "oauth_token", accessor.token); + } + if (map.oauth_version == null) { + OAuth.setParameter(message, "oauth_version", "1.0"); + } + if (map.oauth_timestamp == null) { + OAuth.setParameter(message, "oauth_timestamp", OAuth.timestamp()); + } + if (map.oauth_nonce == null) { + OAuth.setParameter(message, "oauth_nonce", OAuth.nonce(6)); + } + OAuth.SignatureMethod.sign(message, accessor); + } +, + setTimestampAndNonce: function setTimestampAndNonce(message) { + OAuth.setParameter(message, "oauth_timestamp", OAuth.timestamp()); + OAuth.setParameter(message, "oauth_nonce", OAuth.nonce(6)); + } +, + addToURL: function addToURL(url, parameters) { + newURL = url; + if (parameters != null) { + var toAdd = OAuth.formEncode(parameters); + if (toAdd.length > 0) { + var q = url.indexOf('?'); + if (q < 0) newURL += '?'; + else newURL += '&'; + newURL += toAdd; + } + } + return newURL; + } +, + /** Construct the value of the Authorization header for an HTTP request. */ + getAuthorizationHeader: function getAuthorizationHeader(realm, parameters) { + var header = 'OAuth realm="' + OAuth.percentEncode(realm) + '"'; + var list = OAuth.getParameterList(parameters); + for (var p = 0; p < list.length; ++p) { + var parameter = list[p]; + var name = parameter[0]; + if (name.indexOf("oauth_") == 0) { + header += ',' + OAuth.percentEncode(name) + '="' + OAuth.percentEncode(parameter[1]) + '"'; + } + } + return header; + } +, + timestamp: function timestamp() { + var d = new Date(); + return Math.floor(d.getTime()/1000); + } +, + nonce: function nonce(length) { + var chars = OAuth.nonce.CHARS; + var result = ""; + for (var i = 0; i < length; ++i) { + var rnum = Math.floor(Math.random() * chars.length); + result += chars.substring(rnum, rnum+1); + } + return result; + } +}); + +OAuth.nonce.CHARS = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXTZabcdefghiklmnopqrstuvwxyz"; + +/** Define a constructor function, + without causing trouble to anyone who was using it as a namespace. + That is, if parent[name] already existed and had properties, + copy those properties into the new constructor. + */ +OAuth.declareClass = function declareClass(parent, name, newConstructor) { + var previous = parent[name]; + parent[name] = newConstructor; + if (newConstructor != null && previous != null) { + for (var key in previous) { + if (key != "prototype") { + newConstructor[key] = previous[key]; + } + } + } + return newConstructor; +} + +/** An abstract algorithm for signing messages. */ +OAuth.declareClass(OAuth, "SignatureMethod", function OAuthSignatureMethod(){}); + +OAuth.setProperties(OAuth.SignatureMethod.prototype, // instance members +{ + /** Add a signature to the message. */ + sign: function sign(message) { + var baseString = OAuth.SignatureMethod.getBaseString(message); + var signature = this.getSignature(baseString); + OAuth.setParameter(message, "oauth_signature", signature); + return signature; // just in case someone's interested + } +, + /** Set the key string for signing. */ + initialize: function initialize(name, accessor) { + var consumerSecret; + if (accessor.accessorSecret != null + && name.length > 9 + && name.substring(name.length-9) == "-Accessor") + { + consumerSecret = accessor.accessorSecret; + } else { + consumerSecret = accessor.consumerSecret; + } + this.key = OAuth.percentEncode(consumerSecret) + +"&"+ OAuth.percentEncode(accessor.tokenSecret); + } +}); + +/* SignatureMethod expects an accessor object to be like this: + {tokenSecret: "lakjsdflkj...", consumerSecret: "QOUEWRI..", accessorSecret: "xcmvzc..."} + The accessorSecret property is optional. + */ +// Class members: +OAuth.setProperties(OAuth.SignatureMethod, // class members +{ + sign: function sign(message, accessor) { + var name = OAuth.getParameterMap(message.parameters).oauth_signature_method; + if (name == null || name == "") { + name = "HMAC-SHA1"; + OAuth.setParameter(message, "oauth_signature_method", name); + } + OAuth.SignatureMethod.newMethod(name, accessor).sign(message); + } +, + /** Instantiate a SignatureMethod for the given method name. */ + newMethod: function newMethod(name, accessor) { + var impl = OAuth.SignatureMethod.REGISTERED[name]; + if (impl != null) { + var method = new impl(); + method.initialize(name, accessor); + return method; + } + var err = new Error("signature_method_rejected"); + var acceptable = ""; + for (var r in OAuth.SignatureMethod.REGISTERED) { + if (acceptable != "") acceptable += '&'; + acceptable += OAuth.percentEncode(r); + } + err.oauth_acceptable_signature_methods = acceptable; + throw err; + } +, + /** A map from signature method name to constructor. */ + REGISTERED : {} +, + /** Subsequently, the given constructor will be used for the named methods. + The constructor will be called with no parameters. + The resulting object should usually implement getSignature(baseString). + You can easily define such a constructor by calling makeSubclass, below. + */ + registerMethodClass: function registerMethodClass(names, classConstructor) { + for (var n = 0; n < names.length; ++n) { + OAuth.SignatureMethod.REGISTERED[names[n]] = classConstructor; + } + } +, + /** Create a subclass of OAuth.SignatureMethod, with the given getSignature function. */ + makeSubclass: function makeSubclass(getSignatureFunction) { + var superClass = OAuth.SignatureMethod; + var subClass = function() { + superClass.call(this); + }; + subClass.prototype = new superClass(); + // Delete instance variables from prototype: + // delete subclass.prototype... There aren't any. + subClass.prototype.getSignature = getSignatureFunction; + subClass.prototype.constructor = subClass; + return subClass; + } +, + getBaseString: function getBaseString(message) { + var URL = message.action; + var q = URL.indexOf('?'); + var parameters; + if (q < 0) { + parameters = message.parameters; + } else { + // Combine the URL query string with the other parameters: + parameters = OAuth.decodeForm(URL.substring(q + 1)); + var toAdd = OAuth.getParameterList(message.parameters); + for (var a = 0; a < toAdd.length; ++a) { + parameters.push(toAdd[a]); + } + } + return OAuth.percentEncode(message.method.toUpperCase()) + +'&'+ OAuth.percentEncode(OAuth.SignatureMethod.normalizeUrl(URL)) + +'&'+ OAuth.percentEncode(OAuth.SignatureMethod.normalizeParameters(parameters)); + } +, + normalizeUrl: function normalizeUrl(url) { + var uri = OAuth.SignatureMethod.parseUri(url); + var scheme = uri.protocol.toLowerCase(); + var authority = uri.authority.toLowerCase(); + var dropPort = (scheme == "http" && uri.port == 80) + || (scheme == "https" && uri.port == 443); + if (dropPort) { + // find the last : in the authority + var index = authority.lastIndexOf(":"); + if (index >= 0) { + authority = authority.substring(0, index); + } + } + var path = uri.path; + if (!path) { + path = "/"; // conforms to RFC 2616 section 3.2.2 + } + // we know that there is no query and no fragment here. + return scheme + "://" + authority + path; + } +, + parseUri: function parseUri (str) { + /* This function was adapted from parseUri 1.2.1 + http://stevenlevithan.com/demo/parseuri/js/assets/parseuri.js + */ + var o = {key: ["source","protocol","authority","userInfo","user","password","host","port","relative","path","directory","file","query","anchor"], + parser: {strict: /^(?:([^:\/?#]+):)?(?:\/\/((?:(([^:@]*):?([^:@]*))?@)?([^:\/?#]*)(?::(\d*))?))?((((?:[^?#\/]*\/)*)([^?#]*))(?:\?([^#]*))?(?:#(.*))?)/ }}; + var m = o.parser.strict.exec(str); + var uri = {}; + var i = 14; + while (i--) uri[o.key[i]] = m[i] || ""; + return uri; + } +, + normalizeParameters: function normalizeParameters(parameters) { + if (parameters == null) { + return ""; + } + var list = OAuth.getParameterList(parameters); + var sortable = []; + for (var p = 0; p < list.length; ++p) { + var nvp = list[p]; + if (nvp[0] != "oauth_signature") { + sortable.push([ OAuth.percentEncode(nvp[0]) + + " " // because it comes before any character that can appear in a percentEncoded string. + + OAuth.percentEncode(nvp[1]) + , nvp]); + } + } + sortable.sort(function(a,b) { + if (a[0] < b[0]) return -1; + if (a[0] > b[0]) return 1; + return 0; + }); + var sorted = []; + for (var s = 0; s < sortable.length; ++s) { + sorted.push(sortable[s][1]); + } + return OAuth.formEncode(sorted); + } +}); + +OAuth.SignatureMethod.registerMethodClass(["PLAINTEXT", "PLAINTEXT-Accessor"], + OAuth.SignatureMethod.makeSubclass( + function getSignature(baseString) { + return this.key; + } + )); + +OAuth.SignatureMethod.registerMethodClass(["HMAC-SHA1", "HMAC-SHA1-Accessor"], + OAuth.SignatureMethod.makeSubclass( + function getSignature(baseString) { + b64pad = '='; + var signature = b64_hmac_sha1(this.key, baseString); + return signature; + } + )); Added: couchdb/trunk/share/www/script/sha1.js URL: http://svn.apache.org/viewvc/couchdb/trunk/share/www/script/sha1.js?rev=800938&view=auto ============================================================================== --- couchdb/trunk/share/www/script/sha1.js (added) +++ couchdb/trunk/share/www/script/sha1.js Tue Aug 4 19:50:46 2009 @@ -0,0 +1,202 @@ +/* + * A JavaScript implementation of the Secure Hash Algorithm, SHA-1, as defined + * in FIPS PUB 180-1 + * Version 2.1a Copyright Paul Johnston 2000 - 2002. + * Other contributors: Greg Holt, Andrew Kepert, Ydnar, Lostinet + * Distributed under the BSD License + * See http://pajhome.org.uk/crypt/md5 for details. + */ + +/* + * Configurable variables. You may need to tweak these to be compatible with + * the server-side, but the defaults work in most cases. + */ +var hexcase = 0; /* hex output format. 0 - lowercase; 1 - uppercase */ +var b64pad = ""; /* base-64 pad character. "=" for strict RFC compliance */ +var chrsz = 8; /* bits per input character. 8 - ASCII; 16 - Unicode */ + +/* + * These are the functions you'll usually want to call + * They take string arguments and return either hex or base-64 encoded strings + */ +function hex_sha1(s){return binb2hex(core_sha1(str2binb(s),s.length * chrsz));} +function b64_sha1(s){return binb2b64(core_sha1(str2binb(s),s.length * chrsz));} +function str_sha1(s){return binb2str(core_sha1(str2binb(s),s.length * chrsz));} +function hex_hmac_sha1(key, data){ return binb2hex(core_hmac_sha1(key, data));} +function b64_hmac_sha1(key, data){ return binb2b64(core_hmac_sha1(key, data));} +function str_hmac_sha1(key, data){ return binb2str(core_hmac_sha1(key, data));} + +/* + * Perform a simple self-test to see if the VM is working + */ +function sha1_vm_test() +{ + return hex_sha1("abc") == "a9993e364706816aba3e25717850c26c9cd0d89d"; +} + +/* + * Calculate the SHA-1 of an array of big-endian words, and a bit length + */ +function core_sha1(x, len) +{ + /* append padding */ + x[len >> 5] |= 0x80 << (24 - len % 32); + x[((len + 64 >> 9) << 4) + 15] = len; + + var w = Array(80); + var a = 1732584193; + var b = -271733879; + var c = -1732584194; + var d = 271733878; + var e = -1009589776; + + for(var i = 0; i < x.length; i += 16) + { + var olda = a; + var oldb = b; + var oldc = c; + var oldd = d; + var olde = e; + + for(var j = 0; j < 80; j++) + { + if(j < 16) w[j] = x[i + j]; + else w[j] = rol(w[j-3] ^ w[j-8] ^ w[j-14] ^ w[j-16], 1); + var t = safe_add(safe_add(rol(a, 5), sha1_ft(j, b, c, d)), + safe_add(safe_add(e, w[j]), sha1_kt(j))); + e = d; + d = c; + c = rol(b, 30); + b = a; + a = t; + } + + a = safe_add(a, olda); + b = safe_add(b, oldb); + c = safe_add(c, oldc); + d = safe_add(d, oldd); + e = safe_add(e, olde); + } + return Array(a, b, c, d, e); + +} + +/* + * Perform the appropriate triplet combination function for the current + * iteration + */ +function sha1_ft(t, b, c, d) +{ + if(t < 20) return (b & c) | ((~b) & d); + if(t < 40) return b ^ c ^ d; + if(t < 60) return (b & c) | (b & d) | (c & d); + return b ^ c ^ d; +} + +/* + * Determine the appropriate additive constant for the current iteration + */ +function sha1_kt(t) +{ + return (t < 20) ? 1518500249 : (t < 40) ? 1859775393 : + (t < 60) ? -1894007588 : -899497514; +} + +/* + * Calculate the HMAC-SHA1 of a key and some data + */ +function core_hmac_sha1(key, data) +{ + var bkey = str2binb(key); + if(bkey.length > 16) bkey = core_sha1(bkey, key.length * chrsz); + + var ipad = Array(16), opad = Array(16); + for(var i = 0; i < 16; i++) + { + ipad[i] = bkey[i] ^ 0x36363636; + opad[i] = bkey[i] ^ 0x5C5C5C5C; + } + + var hash = core_sha1(ipad.concat(str2binb(data)), 512 + data.length * chrsz); + return core_sha1(opad.concat(hash), 512 + 160); +} + +/* + * Add integers, wrapping at 2^32. This uses 16-bit operations internally + * to work around bugs in some JS interpreters. + */ +function safe_add(x, y) +{ + var lsw = (x & 0xFFFF) + (y & 0xFFFF); + var msw = (x >> 16) + (y >> 16) + (lsw >> 16); + return (msw << 16) | (lsw & 0xFFFF); +} + +/* + * Bitwise rotate a 32-bit number to the left. + */ +function rol(num, cnt) +{ + return (num << cnt) | (num >>> (32 - cnt)); +} + +/* + * Convert an 8-bit or 16-bit string to an array of big-endian words + * In 8-bit function, characters >255 have their hi-byte silently ignored. + */ +function str2binb(str) +{ + var bin = Array(); + var mask = (1 << chrsz) - 1; + for(var i = 0; i < str.length * chrsz; i += chrsz) + bin[i>>5] |= (str.charCodeAt(i / chrsz) & mask) << (32 - chrsz - i%32); + return bin; +} + +/* + * Convert an array of big-endian words to a string + */ +function binb2str(bin) +{ + var str = ""; + var mask = (1 << chrsz) - 1; + for(var i = 0; i < bin.length * 32; i += chrsz) + str += String.fromCharCode((bin[i>>5] >>> (32 - chrsz - i%32)) & mask); + return str; +} + +/* + * Convert an array of big-endian words to a hex string. + */ +function binb2hex(binarray) +{ + var hex_tab = hexcase ? "0123456789ABCDEF" : "0123456789abcdef"; + var str = ""; + for(var i = 0; i < binarray.length * 4; i++) + { + str += hex_tab.charAt((binarray[i>>2] >> ((3 - i%4)*8+4)) & 0xF) + + hex_tab.charAt((binarray[i>>2] >> ((3 - i%4)*8 )) & 0xF); + } + return str; +} + +/* + * Convert an array of big-endian words to a base-64 string + */ +function binb2b64(binarray) +{ + var tab = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; + var str = ""; + for(var i = 0; i < binarray.length * 4; i += 3) + { + var triplet = (((binarray[i >> 2] >> 8 * (3 - i %4)) & 0xFF) << 16) + | (((binarray[i+1 >> 2] >> 8 * (3 - (i+1)%4)) & 0xFF) << 8 ) + | ((binarray[i+2 >> 2] >> 8 * (3 - (i+2)%4)) & 0xFF); + for(var j = 0; j < 4; j++) + { + if(i * 8 + j * 6 > binarray.length * 32) str += b64pad; + else str += tab.charAt((triplet >> 6*(3-j)) & 0x3F); + } + } + return str; +} Modified: couchdb/trunk/share/www/script/test/changes.js URL: http://svn.apache.org/viewvc/couchdb/trunk/share/www/script/test/changes.js?rev=800938&r1=800937&r2=800938&view=diff ============================================================================== --- couchdb/trunk/share/www/script/test/changes.js (original) +++ couchdb/trunk/share/www/script/test/changes.js Tue Aug 4 19:50:46 2009 @@ -195,8 +195,8 @@ // test for userCtx run_on_modified_server( [{section: "httpd", - key: "authentication_handler", - value: "{couch_httpd, special_test_authentication_handler}"}, + key: "authentication_handlers", + value: "{couch_httpd_auth, special_test_authentication_handler}"}, {section:"httpd", key: "WWW-Authenticate", value: "X-Couch-Test-Auth"}], Added: couchdb/trunk/share/www/script/test/cookie_auth.js URL: http://svn.apache.org/viewvc/couchdb/trunk/share/www/script/test/cookie_auth.js?rev=800938&view=auto ============================================================================== --- couchdb/trunk/share/www/script/test/cookie_auth.js (added) +++ couchdb/trunk/share/www/script/test/cookie_auth.js Tue Aug 4 19:50:46 2009 @@ -0,0 +1,162 @@ +// Licensed 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. + +couchTests.cookie_auth = function(debug) { + // This tests cookie-based authentication. + + var db = new CouchDB("test_suite_db"); + db.deleteDb(); + db.createDb(); + if (debug) debugger; + + // Simple secret key generator + function generateSecret(length) { + var secret = ''; + for (var i=0; i % read config and register for configuration changes @@ -111,6 +109,9 @@ fun(Arg1, Arg2) -> apply(Mod, Fun, [Arg1, Arg2]) end end. +% SpecStr is "{my_module, my_fun}, {my_module2, my_fun2}" +make_arity_1_fun_list(SpecStr) -> + [make_arity_1_fun(FunSpecStr) || FunSpecStr <- re:split(SpecStr, "(?<=})\\s*,\\s*(?={)", [{return, list}])]. stop() -> mochiweb_http:stop(?MODULE). @@ -119,8 +120,8 @@ handle_request(MochiReq, DefaultFun, UrlHandlers, DbUrlHandlers, DesignUrlHandlers) -> Begin = now(), - AuthenticationFun = make_arity_1_fun( - couch_config:get("httpd", "authentication_handler")), + AuthenticationFuns = make_arity_1_fun_list( + couch_config:get("httpd", "authentication_handlers")), % for the path, use the raw path with the query string and fragment % removed, but URL quoting left intact RawUri = MochiReq:get(raw_path), @@ -171,11 +172,25 @@ {ok, Resp} = try - HandlerFun(HttpReq#httpd{user_ctx=AuthenticationFun(HttpReq)}) + % Try authentication handlers in order until one returns a result + case lists:foldl(fun(_Fun, #httpd{user_ctx=#user_ctx{}}=Req) -> Req; + (Fun, #httpd{}=Req) -> Fun(Req); + (_Fun, Response) -> Response + end, HttpReq, AuthenticationFuns) of + #httpd{user_ctx=#user_ctx{}}=Req -> HandlerFun(Req); + #httpd{}=Req -> + case couch_config:get("couch_httpd_auth", "require_valid_user", "false") of + "true" -> + throw({unauthorized, <<"Authentication required.">>}); + _ -> + HandlerFun(Req#httpd{user_ctx=#user_ctx{}}) + end; + Response -> Response + end catch throw:Error -> - % ?LOG_DEBUG("Minor error in HTTP request: ~p",[Error]), - % ?LOG_DEBUG("Stacktrace: ~p",[erlang:get_stacktrace()]), + ?LOG_DEBUG("Minor error in HTTP request: ~p",[Error]), + ?LOG_DEBUG("Stacktrace: ~p",[erlang:get_stacktrace()]), send_error(HttpReq, Error); error:badarg -> ?LOG_ERROR("Badarg error in HTTP request",[]), @@ -205,48 +220,6 @@ increment_method_stats(Method) -> couch_stats_collector:increment({httpd_request_methods, Method}). -special_test_authentication_handler(Req) -> - case header_value(Req, "WWW-Authenticate") of - "X-Couch-Test-Auth " ++ NamePass -> - % NamePass is a colon separated string: "joe schmoe:a password". - {ok, [Name, Pass]} = regexp:split(NamePass, ":"), - case {Name, Pass} of - {"Jan Lehnardt", "apple"} -> ok; - {"Christopher Lenz", "dog food"} -> ok; - {"Noah Slater", "biggiesmalls endian"} -> ok; - {"Chris Anderson", "mp3"} -> ok; - {"Damien Katz", "pecan pie"} -> ok; - {_, _} -> - throw({unauthorized, <<"Name or password is incorrect.">>}) - end, - #user_ctx{name=?l2b(Name)}; - _ -> - % No X-Couch-Test-Auth credentials sent, give admin access so the - % previous authentication can be restored after the test - #user_ctx{roles=[<<"_admin">>]} - end. - -default_authentication_handler(Req) -> - case basic_username_pw(Req) of - {User, Pass} -> - case couch_server:is_admin(User, Pass) of - true -> - #user_ctx{name=?l2b(User), roles=[<<"_admin">>]}; - false -> - throw({unauthorized, <<"Name or password is incorrect.">>}) - end; - nil -> - case couch_server:has_admins() of - true -> - #user_ctx{}; - false -> - % if no admins, then everyone is admin! Yay, admin party! - #user_ctx{roles=[<<"_admin">>]} - end - end. - -null_authentication_handler(_Req) -> - #user_ctx{roles=[<<"_admin">>]}. % Utilities @@ -265,8 +238,9 @@ primary_header_value(#httpd{mochi_req=MochiReq}, Key) -> MochiReq:get_primary_header_value(Key). -serve_file(#httpd{mochi_req=MochiReq}, RelativePath, DocumentRoot) -> - {ok, MochiReq:serve_file(RelativePath, DocumentRoot, server_header())}. +serve_file(#httpd{mochi_req=MochiReq}=Req, RelativePath, DocumentRoot) -> + {ok, MochiReq:serve_file(RelativePath, DocumentRoot, + server_header() ++ couch_httpd_auth:cookie_auth_header(Req, []))}. qs_value(Req, Key) -> qs_value(Req, Key, undefined). @@ -307,11 +281,16 @@ % called with Length == 0 on the last time. MochiReq:stream_body(MaxChunkSize, ChunkFun, InitState). -body(#httpd{mochi_req=MochiReq}) -> - % Maximum size of document PUT request body (4GB) - MaxSize = list_to_integer( - couch_config:get("couchdb", "max_document_size", "4294967296")), - MochiReq:recv_body(MaxSize). +body(#httpd{mochi_req=MochiReq, req_body=ReqBody}) -> + case ReqBody of + undefined -> + % Maximum size of document PUT request body (4GB) + MaxSize = list_to_integer( + couch_config:get("couchdb", "max_document_size", "4294967296")), + MochiReq:recv_body(MaxSize); + _Else -> + ReqBody + end. json_body(Httpd) -> ?JSON_DECODE(body(Httpd)). @@ -357,37 +336,21 @@ -basic_username_pw(Req) -> - case header_value(Req, "Authorization") of - "Basic " ++ Base64Value -> - case string:tokens(?b2l(couch_util:decodeBase64(Base64Value)),":") of - [User, Pass] -> - {User, Pass}; - [User] -> - {User, ""}; - _ -> - nil - end; - _ -> - nil - end. - - -start_chunked_response(#httpd{mochi_req=MochiReq}, Code, Headers) -> +start_chunked_response(#httpd{mochi_req=MochiReq}=Req, Code, Headers) -> couch_stats_collector:increment({httpd_status_codes, Code}), - {ok, MochiReq:respond({Code, Headers ++ server_header(), chunked})}. + {ok, MochiReq:respond({Code, Headers ++ server_header() ++ couch_httpd_auth:cookie_auth_header(Req, Headers), chunked})}. send_chunk(Resp, Data) -> Resp:write_chunk(Data), {ok, Resp}. -send_response(#httpd{mochi_req=MochiReq}, Code, Headers, Body) -> +send_response(#httpd{mochi_req=MochiReq}=Req, Code, Headers, Body) -> couch_stats_collector:increment({httpd_status_codes, Code}), if Code >= 400 -> ?LOG_DEBUG("httpd ~p error response:~n ~s", [Code, Body]); true -> ok end, - {ok, MochiReq:respond({Code, Headers ++ server_header(), Body})}. + {ok, MochiReq:respond({Code, Headers ++ server_header() ++ couch_httpd_auth:cookie_auth_header(Req, Headers), Body})}. send_method_not_allowed(Req, Methods) -> send_response(Req, 405, [{"Allow", Methods}], <<>>). @@ -508,17 +471,22 @@ send_error(_Req, {already_sent, Resp, _Error}) -> {ok, Resp}; -send_error(Req, Error) -> +send_error(#httpd{mochi_req=MochiReq}=Req, Error) -> {Code, ErrorStr, ReasonStr} = error_info(Error), - if Code == 401 -> - case couch_config:get("httpd", "WWW-Authenticate", nil) of - nil -> - Headers = []; + Headers = if Code == 401 -> + case MochiReq:get_header_value("X-CouchDB-WWW-Authenticate") of + undefined -> + case couch_config:get("httpd", "WWW-Authenticate", nil) of + nil -> + []; + Type -> + [{"WWW-Authenticate", Type}] + end; Type -> - Headers = [{"WWW-Authenticate", Type}] + [{"WWW-Authenticate", Type}] end; true -> - Headers = [] + [] end, send_error(Req, Code, Headers, ErrorStr, ReasonStr).