couchdb-commits mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From dam...@apache.org
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 GMT
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.h>],
+    [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 <jsapi.h>]],
+        [[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<length; i++) {
+      secret += String.fromCharCode(Math.floor(Math.random() * 256));
+    }
+    return secret;
+  }
+  // this function will be called on the modified server
+  var testFun = function () {
+    try {
+      // try using an invalid cookie
+      var usersDb = new CouchDB("test_suite_users");
+      usersDb.deleteDb();
+      usersDb.createDb();
+      
+      var password = "3.141592653589";
+
+      // Create a user
+      T(usersDb.save({
+        _id: "a1",
+        salt: "123",
+        password_sha: "8da1CtkFvb58LWrnup5chgdZVUs=",
+        username: "Jason Davies",
+        author: "Jason Davies",
+        type: "user",
+        roles: ["_admin"]
+      }).ok);
+
+      var validationDoc = {
+        _id : "_design/validate",
+        validate_doc_update: "(" + (function (newDoc, oldDoc, userCtx) {
+          // docs should have an author field.
+          if (!newDoc._deleted && !newDoc.author) {
+            throw {forbidden:
+                "Documents must have an author field"};
+          }
+          if (oldDoc && oldDoc.author != userCtx.name) {
+              throw {unauthorized:
+                  "You are not the author of this document. You jerk."+userCtx.name};
+          }
+        }).toString() + ")"
+      };
+
+      T(db.save(validationDoc).ok);
+
+      
+
+      T(CouchDB.login('Jason Davies', password).ok);
+      // update the credentials document
+      var doc = usersDb.open("a1");
+      doc.foo=2;
+      T(usersDb.save(doc).ok);
+
+      // Save a document that's missing an author field.
+      try {
+        // db has a validation function
+        db.save({foo:1});
+        T(false && "Can't get here. Should have thrown an error 2");
+      } catch (e) {
+        T(e.error == "forbidden");
+        T(db.last_req.status == 403);
+      }
+
+      // TODO should login() throw an exception here?
+      T(!CouchDB.login('Jason Davies', "2.71828").ok);
+      T(!CouchDB.login('Robert Allen Zimmerman', 'd00d').ok);
+
+      // test redirect
+      xhr = CouchDB.request("POST", "/_session?next=/", {
+        headers: {"Content-Type": "application/x-www-form-urlencoded"},
+        body: "username=Jason%20Davies&password="+encodeURIComponent(password)
+      });
+      // should this be a redirect code instead of 200?
+      T(xhr.status == 200);
+      
+      usersDb.deleteDb();
+      // test user creation
+      T(CouchDB.createUser("test", "testpassword", "test@somemail.com", ['read', 'write']).ok);
+      
+      // make sure we create a unique user
+      T(!CouchDB.createUser("test", "testpassword2", "test2@somemail.com", ['read', 'write']).ok);
+      
+      // test login
+      T(CouchDB.login("test", "testpassword").ok);
+      T(!CouchDB.login('test', "testpassword2").ok);
+      
+      // test update user without changing password
+      T(CouchDB.updateUser("test", "test2@somemail.com").ok);
+      result = usersDb.view("_auth/users", {key: "test"});
+      T(result.rows[0].value['email'] == "test2@somemail.com");
+       
+       
+      // test changing password
+      result = usersDb.view("_auth/users", {key: "test"});
+      T(CouchDB.updateUser("test", "test2@somemail.com", [], "testpassword2", "testpassword").ok);
+      result1 = usersDb.view("_auth/users", {key: "test"});
+      T(result.rows[0].value['password_sha'] != result1.rows[0].value['password_sha']);
+      
+      
+      // test changing password with passing old password
+      T(!CouchDB.updateUser("test", "test2@somemail.com", [], "testpassword2").ok);
+      
+      // test changing password whith bad old password
+      T(!CouchDB.updateUser("test", "test2@somemail.com", [], "testpassword2", "badpasswword").ok);
+      
+      // Only admins can change roles
+      T(!CouchDB.updateUser("test", "test2@somemail.com", ['read', 'write']).ok);
+      
+      T(CouchDB.logout().ok);
+      
+      T(CouchDB.updateUser("test", "test2@somemail.com").ok);
+      result = usersDb.view("_auth/users", {key: "test"});
+      T(result.rows[0].value['email'] == "test2@somemail.com");
+
+      // test changing password, we don't need to set old password when we are admin
+      result = usersDb.view("_auth/users", {key: "test"});
+      T(CouchDB.updateUser("test", "test2@somemail.com", [], "testpassword3").ok);
+      result1 = usersDb.view("_auth/users", {key: "test"});
+      T(result.rows[0].value['password_sha'] != result1.rows[0].value['password_sha']);
+
+      // Only admins can change roles
+      T(CouchDB.updateUser("test", "test2@somemail.com", ['read']).ok);
+
+    } finally {
+      // Make sure we erase any auth cookies so we don't affect other tests
+      T(CouchDB.logout().ok);
+    }
+  };
+
+  run_on_modified_server(
+    [{section: "httpd",
+      key: "authentication_handlers",
+      value: "{couch_httpd_auth, cookie_authentication_handler}"},
+     {section: "couch_httpd_auth",
+      key: "secret", value: generateSecret(64)},
+     {section: "couch_httpd_auth",
+      key: "authentication_db", value: "test_suite_users"}],
+    testFun
+  );
+
+};

Added: couchdb/trunk/share/www/script/test/oauth.js
URL: http://svn.apache.org/viewvc/couchdb/trunk/share/www/script/test/oauth.js?rev=800938&view=auto
==============================================================================
--- couchdb/trunk/share/www/script/test/oauth.js (added)
+++ couchdb/trunk/share/www/script/test/oauth.js Tue Aug  4 19:50:46 2009
@@ -0,0 +1,166 @@
+// 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.oauth = function(debug) {
+  // This tests OAuth authentication.
+
+  var authorization_url = "/_oauth/authorize";
+
+  var db = new CouchDB("test_suite_db");
+  db.deleteDb();
+  db.createDb();
+  if (debug) debugger;
+
+  var dbA = new CouchDB("test_suite_db_a");
+  var dbB = new CouchDB("test_suite_db_b");
+  dbA.deleteDb();
+  dbA.createDb();
+  dbB.deleteDb();
+  dbB.createDb();
+
+  // Simple secret key generator
+  function generateSecret(length) {
+    var secret = '';
+    for (var i=0; i<length; i++) {
+      secret += String.fromCharCode(Math.floor(Math.random() * 256));
+    }
+    return secret;
+  }
+
+  function oauthRequest(path, message, accessor, method) {
+    message.action = path;
+    message.method = method || 'GET';
+    OAuth.SignatureMethod.sign(message, accessor);
+    var parameters = message.parameters;
+    if (method == "POST" || method == "GET") {
+      if (method == "GET") {
+        return CouchDB.request("GET", OAuth.addToURL(path, parameters));
+      } else {
+        return CouchDB.request("POST", path, {
+          headers: {"Content-Type": "application/x-www-form-urlencoded"},
+          body: OAuth.formEncode(parameters)
+        });
+      }
+    } else {
+      return CouchDB.request("GET", path, {
+        headers: {Authorization: OAuth.getAuthorizationHeader('', parameters)}
+      });
+    }
+  }
+
+  var consumerSecret = generateSecret(64);
+  var tokenSecret = generateSecret(64);
+
+  var host = CouchDB.host;
+  var dbPair = {
+    source: {
+      url: "http://" + host + "/test_suite_db_a",
+      auth: {
+        oauth: {
+          consumer_key: "key",
+          consumer_secret: consumerSecret,
+          token_secret: tokenSecret,
+          token: "foo"
+        }
+      }
+    },
+    target: "http://" + host + "/test_suite_db_b"
+  };
+
+  // this function will be called on the modified server
+  var testFun = function () {
+    try {
+      var usersDb = new CouchDB("test_suite_users");
+      usersDb.deleteDb();
+      usersDb.createDb();
+      
+      // Create a user
+      T(CouchDB.createUser("jason", "testpassword", "test@somemail.com", ['test'], true).ok);
+
+      var accessor = {
+        consumerSecret: consumerSecret,
+        tokenSecret: tokenSecret
+      };
+
+      var signatureMethods = ["PLAINTEXT", "HMAC-SHA1"];
+      var consumerKeys = {key: 200, nonexistent_key: 400};
+
+      for (var i=0; i<signatureMethods.length; i++) {
+        for (var consumerKey in consumerKeys) {
+          var expectedCode = consumerKeys[consumerKey];
+          var message = {
+            parameters: {
+              oauth_signature_method: signatureMethods[i],
+              oauth_consumer_key: consumerKey,
+              oauth_token: "foo",
+              oauth_token_secret: tokenSecret,
+              oauth_version: "1.0"
+            }
+          };
+
+          // Get request token via Authorization header
+          xhr = oauthRequest("http://" + host + "/_oauth/request_token", message, accessor);
+          T(xhr.status == expectedCode);
+
+          // GET request token via query parameters
+          xhr = oauthRequest("http://" + host + "/_oauth/request_token", message, accessor, "GET");
+          T(xhr.status == expectedCode);
+
+          responseMessage = OAuth.decodeForm(xhr.responseText);
+
+          // Obtaining User Authorization
+          //Only needed for 3-legged OAuth
+          //xhr = CouchDB.request("GET", authorization_url + '?oauth_token=' + responseMessage.oauth_token);
+          //T(xhr.status == expectedCode);
+
+          xhr = oauthRequest("http://" + host + "/_session", message, accessor);
+          T(xhr.status == expectedCode);
+          if (xhr.status == expectedCode == 200) {
+            data = JSON.parse(xhr.responseText);
+            T(data.name == "jason");
+            T(data.roles[0] == "test");
+          }
+
+          xhr = oauthRequest("http://" + host + "/_session?foo=bar", message, accessor);
+          T(xhr.status == expectedCode);
+
+          // Replication
+          var result = CouchDB.replicate(dbPair.source, dbPair.target);
+          T(result.ok);
+        }
+      }
+
+    } finally {
+    }
+  };
+
+  run_on_modified_server(
+    [{section: "httpd",
+      key: "WWW-Authenticate", value: 'Basic realm="administrator",OAuth'},
+     {section: "couch_httpd_auth",
+      key: "secret", value: generateSecret(64)},
+     {section: "couch_httpd_auth",
+      key: "authentication_db", value: "test_suite_users"},
+     {section: "oauth_consumer_secrets",
+      key: "key", value: consumerSecret},
+     {section: "oauth_token_users",
+      key: "foo", value: "jason"},
+     {section: "oauth_token_secrets",
+      key: "foo", value: tokenSecret},
+     {section: "couch_httpd_oauth",
+      key: "authorization_url", value: authorization_url},
+     {section: "httpd",
+      key: "authentication_handlers",
+      value: "{couch_httpd_oauth, oauth_authentication_handler}, {couch_httpd_auth, default_authentication_handler}"}],
+    testFun
+  );
+};

Modified: couchdb/trunk/share/www/script/test/security_validation.js
URL: http://svn.apache.org/viewvc/couchdb/trunk/share/www/script/test/security_validation.js?rev=800938&r1=800937&r2=800938&view=diff
==============================================================================
--- couchdb/trunk/share/www/script/test/security_validation.js (original)
+++ couchdb/trunk/share/www/script/test/security_validation.js Tue Aug  4 19:50:46 2009
@@ -39,14 +39,14 @@
 
   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"}],
 
     function () {
-      // try saving document usin the wrong credentials
+      // try saving document using the wrong credentials
       var wrongPasswordDb = new CouchDB("test_suite_db",
         {"WWW-Authenticate": "X-Couch-Test-Auth Damien Katz:foo"}
       );
@@ -59,8 +59,8 @@
         T(wrongPasswordDb.last_req.status == 401);
       }
 
-      // test force_login=true.
-      var resp = wrongPasswordDb.request("GET", "/_whoami?force_login=true");
+      // test force basic login
+      var resp = wrongPasswordDb.request("GET", "/_session?basic=true");
       var err = JSON.parse(resp.responseText);
       T(err.error == "unauthorized");
       T(resp.status == 401);
@@ -104,7 +104,7 @@
       T(userDb.save(designDoc).ok);
 
       // test the _whoami endpoint
-      var resp = userDb.request("GET", "/_whoami");
+      var resp = userDb.request("GET", "/_session");
       var user = JSON.parse(resp.responseText)
       T(user.name == "Damien Katz");
       // test that the roles are listed properly

Modified: couchdb/trunk/share/www/script/test/show_documents.js
URL: http://svn.apache.org/viewvc/couchdb/trunk/share/www/script/test/show_documents.js?rev=800938&r1=800937&r2=800938&view=diff
==============================================================================
--- couchdb/trunk/share/www/script/test/show_documents.js (original)
+++ couchdb/trunk/share/www/script/test/show_documents.js Tue Aug  4 19:50:46 2009
@@ -335,7 +335,7 @@
   var doc2 = {_id:"foo", a:2};
   db.save(doc1);
 
-  //create the conflict with a all_or_nothing bulk docs request
+  // create the conflict with an all_or_nothing bulk docs request
   var docs = [doc2];
   db.bulkSave(docs, {all_or_nothing:true});
 

Modified: couchdb/trunk/src/couchdb/Makefile.am
URL: http://svn.apache.org/viewvc/couchdb/trunk/src/couchdb/Makefile.am?rev=800938&r1=800937&r2=800938&view=diff
==============================================================================
--- couchdb/trunk/src/couchdb/Makefile.am (original)
+++ couchdb/trunk/src/couchdb/Makefile.am Tue Aug  4 19:50:46 2009
@@ -61,6 +61,8 @@
     couch_file.erl \
     couch_httpd.erl \
     couch_httpd_db.erl \
+    couch_httpd_auth.erl \
+    couch_httpd_oauth.erl \
     couch_httpd_external.erl \
     couch_httpd_show.erl \
     couch_httpd_view.erl \
@@ -106,6 +108,8 @@
     couch_file.beam \
     couch_httpd.beam \
     couch_httpd_db.beam \
+    couch_httpd_auth.beam \
+    couch_httpd_oauth.beam \
     couch_httpd_external.beam \
     couch_httpd_show.beam \
     couch_httpd_view.beam \

Modified: couchdb/trunk/src/couchdb/couch_db.hrl
URL: http://svn.apache.org/viewvc/couchdb/trunk/src/couchdb/couch_db.hrl?rev=800938&r1=800937&r2=800938&view=diff
==============================================================================
--- couchdb/trunk/src/couchdb/couch_db.hrl (original)
+++ couchdb/trunk/src/couchdb/couch_db.hrl Tue Aug  4 19:50:46 2009
@@ -67,7 +67,8 @@
     db_url_handlers,
     user_ctx,
     req_body = undefined,
-    design_url_handlers
+    design_url_handlers,
+    auth
     }).
 
 

Modified: couchdb/trunk/src/couchdb/couch_httpd.erl
URL: http://svn.apache.org/viewvc/couchdb/trunk/src/couchdb/couch_httpd.erl?rev=800938&r1=800937&r2=800938&view=diff
==============================================================================
--- couchdb/trunk/src/couchdb/couch_httpd.erl (original)
+++ couchdb/trunk/src/couchdb/couch_httpd.erl Tue Aug  4 19:50:46 2009
@@ -23,8 +23,6 @@
 -export([start_json_response/2, start_json_response/3, end_json_response/1]).
 -export([send_response/4,send_method_not_allowed/2,send_error/4, send_redirect/2,send_chunked_error/2]).
 -export([send_json/2,send_json/3,send_json/4]).
--export([default_authentication_handler/1,special_test_authentication_handler/1]).
--export([null_authentication_handler/1]).
 
 start_link() ->
     % 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).
 



Mime
View raw message