You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@couchdb.apache.org by rn...@apache.org on 2015/09/10 14:59:40 UTC

[2/3] couch commit: updated refs/heads/master to b8b9968

Remove new CSRF mechanism


Project: http://git-wip-us.apache.org/repos/asf/couchdb-couch/repo
Commit: http://git-wip-us.apache.org/repos/asf/couchdb-couch/commit/8c4e947e
Tree: http://git-wip-us.apache.org/repos/asf/couchdb-couch/tree/8c4e947e
Diff: http://git-wip-us.apache.org/repos/asf/couchdb-couch/diff/8c4e947e

Branch: refs/heads/master
Commit: 8c4e947ea9445545e7dc5a9d871f03fa0b32ed8c
Parents: 9aff2f6
Author: Robert Newson <rn...@apache.org>
Authored: Thu Sep 10 12:27:17 2015 +0100
Committer: Robert Newson <rn...@apache.org>
Committed: Thu Sep 10 13:59:06 2015 +0100

----------------------------------------------------------------------
 src/couch_httpd.erl      |  16 +--
 src/couch_httpd_csrf.erl | 223 ------------------------------------------
 2 files changed, 5 insertions(+), 234 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/couchdb-couch/blob/8c4e947e/src/couch_httpd.erl
----------------------------------------------------------------------
diff --git a/src/couch_httpd.erl b/src/couch_httpd.erl
index 720ea0a..eee1001 100644
--- a/src/couch_httpd.erl
+++ b/src/couch_httpd.erl
@@ -306,7 +306,6 @@ handle_request_int(MochiReq, DefaultFun,
     {ok, Resp} =
     try
         validate_host(HttpReq),
-        couch_httpd_csrf:validate(HttpReq),
         check_request_uri_length(RawUri),
         case couch_httpd_cors:is_preflight_request(HttpReq) of
         #httpd{} ->
@@ -483,8 +482,7 @@ serve_file(#httpd{mochi_req=MochiReq}=Req, RelativePath, DocumentRoot,
         ++ couch_httpd_auth:cookie_auth_header(Req, [])
         ++ ExtraHeaders,
     ResponseHeaders1 = couch_httpd_cors:cors_headers(Req, ResponseHeaders),
-    ResponseHeaders2 = couch_httpd_csrf:headers(Req, ResponseHeaders1),
-    {ok, MochiReq:serve_file(RelativePath, DocumentRoot, ResponseHeaders2)}.
+    {ok, MochiReq:serve_file(RelativePath, DocumentRoot, ResponseHeaders1)}.
 
 qs_value(Req, Key) ->
     qs_value(Req, Key, undefined).
@@ -656,8 +654,7 @@ start_response_length(#httpd{mochi_req=MochiReq}=Req, Code, Headers, Length) ->
     Headers1 = Headers ++ server_header() ++
                couch_httpd_auth:cookie_auth_header(Req, Headers),
     Headers2 = couch_httpd_cors:cors_headers(Req, Headers1),
-    Headers3 = couch_httpd_csrf:headers(Req, Headers2),
-    Resp = MochiReq:start_response_length({Code, Headers3, Length}),
+    Resp = MochiReq:start_response_length({Code, Headers2, Length}),
     case MochiReq:get(method) of
     'HEAD' -> throw({http_head_abort, Resp});
     _ -> ok
@@ -670,8 +667,7 @@ start_response(#httpd{mochi_req=MochiReq}=Req, Code, Headers) ->
     CookieHeader = couch_httpd_auth:cookie_auth_header(Req, Headers),
     Headers1 = Headers ++ server_header() ++ CookieHeader,
     Headers2 = couch_httpd_cors:cors_headers(Req, Headers1),
-    Headers3 = couch_httpd_csrf:headers(Req, Headers2),
-    Resp = MochiReq:start_response({Code, Headers3}),
+    Resp = MochiReq:start_response({Code, Headers2}),
     case MochiReq:get(method) of
         'HEAD' -> throw({http_head_abort, Resp});
         _ -> ok
@@ -706,8 +702,7 @@ start_chunked_response(#httpd{mochi_req=MochiReq}=Req, Code, Headers) ->
     Headers2 = Headers1 ++ server_header() ++
                couch_httpd_auth:cookie_auth_header(Req, Headers1),
     Headers3 = couch_httpd_cors:cors_headers(Req, Headers2),
-    Headers4 = couch_httpd_csrf:headers(Req, Headers3),
-    Resp = MochiReq:respond({Code, Headers4, chunked}),
+    Resp = MochiReq:respond({Code, Headers3, chunked}),
     case MochiReq:get(method) of
     'HEAD' -> throw({http_head_abort, Resp});
     _ -> ok
@@ -738,9 +733,8 @@ send_response(#httpd{mochi_req=MochiReq}=Req, Code, Headers, Body) ->
     Headers2 = Headers1 ++ server_header() ++
                couch_httpd_auth:cookie_auth_header(Req, Headers1),
     Headers3 = couch_httpd_cors:cors_headers(Req, Headers2),
-    Headers4 = couch_httpd_csrf:headers(Req, Headers3),
 
-    {ok, MochiReq:respond({Code, Headers4, Body})}.
+    {ok, MochiReq:respond({Code, Headers3, Body})}.
 
 send_method_not_allowed(Req, Methods) ->
     send_error(Req, 405, [{"Allow", Methods}], <<"method_not_allowed">>, ?l2b("Only " ++ Methods ++ " allowed")).

http://git-wip-us.apache.org/repos/asf/couchdb-couch/blob/8c4e947e/src/couch_httpd_csrf.erl
----------------------------------------------------------------------
diff --git a/src/couch_httpd_csrf.erl b/src/couch_httpd_csrf.erl
deleted file mode 100644
index 10bb175..0000000
--- a/src/couch_httpd_csrf.erl
+++ /dev/null
@@ -1,223 +0,0 @@
-% 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.
-
-%% This module provides optional CSRF protection to any client
-%%
-%% Clients should use the following pseudo code;
-%% if (hasCookie("CouchDB-CSRF")) {
-%%   setRequestHeader("X-CouchDB-CSRF", cookieValue("CouchDB-CSRF"));
-%% } else {
-%%   setRequestHeader("X-CouchDB-CSRF", "true")
-%% }
-%%
-%% If CouchDB sees the CouchDB-CSRF cookie then it checks its validity
-%% and whether the X-CouchDB-CSRF request header exists and matches.
-%% A 403 is returned if those checks fail.
-%% If CouchDB does not see the CouchDB-CSRF cookie but does see
-%% the X-CouchDB-CSRF header with value "true", a CouchDB-CSRF cookie
-%% is generated and returned.
-
--module(couch_httpd_csrf).
-
--export([validate/1, headers/2]).
-
--include_lib("couch/include/couch_db.hrl").
-
-validate(#httpd{method = 'GET'}) ->
-    ok;
-validate(#httpd{method = 'HEAD'}) ->
-    ok;
-validate(#httpd{method = 'OPTIONS'}) ->
-    ok;
-validate(#httpd{} = Req) ->
-    Cookie = csrf_from_req(Req),
-    Header = couch_httpd:header_value(Req, "X-CouchDB-CSRF"),
-    Validate = lists:member(get_content_type(Req), csrf_mime_types()),
-    case {Validate, Cookie, Header} of
-        {false, _, _} ->
-            ok; % mime type is not in the list
-        {true, undefined, undefined} ->
-            throw_if_mandatory(Req);
-        {true, undefined, "true"} ->
-            throw_if_mandatory(Req);
-        {true, "deleted", "true"} ->
-            throw_if_mandatory(Req);
-        {true, undefined, _} ->
-            throw({forbidden, <<"CSRF header sent without Cookie">>});
-        {true, Csrf, Csrf} ->
-            ok = validate(Csrf);
-        _ ->
-            throw({forbidden, <<"CSRF Cookie/Header mismatch">>})
-    end;
-%% Check that we generated this CSRF token
-validate(Csrf) when is_list(Csrf) ->
-    case decode_cookie(Csrf) of
-        malformed ->
-            throw({forbidden, <<"Malformed CSRF Cookie">>});
-        Cookie ->
-            case validate_cookie(Cookie) of
-                true ->
-                    ok;
-                false ->
-                    throw({forbidden, <<"CSRF Cookie invalid or expired">>})
-            end
-    end.
-
-throw_if_mandatory(#httpd{path_parts = []}) ->
-    ok; %% Welcome message is public / entrypoint
-throw_if_mandatory(_) ->
-    case csrf_mandatory() of
-        true ->
-            throw({forbidden, <<"CSRF Cookie/Header is mandatory">>});
-        false ->
-            ok
-    end.
-
-
-headers(#httpd{} = Req, Headers) ->
-    Header = couch_httpd:header_value(Req, "X-CouchDB-CSRF"),
-    case {csrf_from_req(Req), csrf_in_headers(Headers), Header} of
-        {undefined, false, "true"} ->
-            [make_cookie() | Headers];
-        {"deleted", false, "true"} ->
-            [make_cookie() | Headers];
-        {Csrf, false, Csrf} when Csrf /= undefined ->
-            case decode_cookie(Csrf) of
-                malformed ->
-                    [delete_cookie() | Headers];
-                Cookie ->
-                    case validate_cookie(Cookie) of
-                        true ->
-                            case refresh_cookie(Cookie) of
-                                true ->
-                                    valid([make_cookie() | Headers]);
-                                false ->
-                                    valid(Headers)
-                            end;
-                        false ->
-                            [delete_cookie() | Headers]
-                    end
-            end;
-        _ ->
-            Headers
-    end.
-
-
-make_cookie() ->
-    Secret = ?l2b(ensure_csrf_secret()),
-    Token = crypto:rand_bytes(8),
-    Timestamp = timestamp(),
-    Data = <<Token/binary, Timestamp:32>>,
-    Hmac = crypto:sha_mac(Secret, Data),
-    mochiweb_cookies:cookie("CouchDB-CSRF",
-        couch_util:encodeBase64Url(<<Data/binary, Hmac/binary>>),
-        [{path, "/"}, {max_age, max_age()}]).
-
-
-delete_cookie() ->
-    mochiweb_cookies:cookie("CouchDB-CSRF", "deleted",
-        [{path, "/"}, {max_age, 0}]).
-
-csrf_from_req(#httpd{} = Req) ->
-    case couch_httpd:header_value(Req, "Cookie") of
-        undefined ->
-            undefined;
-        Value ->
-            Cookies = mochiweb_cookies:parse_cookie(Value),
-            couch_util:get_value("CouchDB-CSRF", Cookies)
-    end.
-
-
-valid(Headers) ->
-    case lists:keyfind("X-CouchDB-CSRF-Valid", 1, Headers) of
-        false ->
-            [{"X-CouchDB-CSRF-Valid", "true"} | Headers];
-        _ ->
-            Headers
-    end.
-
-csrf_in_headers(Headers) when is_list(Headers) ->
-    lists:any(fun is_csrf_header/1, Headers).
-
-
-is_csrf_header({"Set-Cookie", [$C, $o, $u, $c, $h, $D, $B, $-, $C, $S, $R, $F, $= | _]}) ->
-    true;
-is_csrf_header(_) ->
-    false.
-
-
-ensure_csrf_secret() ->
-    case config:get("csrf", "secret", undefined) of
-        undefined ->
-            NewSecret = ?b2l(couch_uuids:random()),
-            config:set("csrf", "secret", NewSecret),
-            NewSecret;
-        Secret -> Secret
-    end.
-
-
-decode_cookie(Cookie) ->
-    try
-        Cookie1 = couch_util:decodeBase64Url(Cookie),
-        <<Token:8/binary, Timestamp:32, Hmac:20/binary>> = Cookie1,
-        {Token, Timestamp, Hmac}
-    catch
-        error:_ ->
-            malformed
-    end.
-
-
-validate_cookie({Token, Timestamp, ActualHmac}) ->
-    Secret = ensure_csrf_secret(),
-    ExpectedHmac = crypto:sha_mac(Secret, <<Token/binary, Timestamp:32>>),
-    MaxAge = max_age(),
-    Expired = Timestamp + MaxAge < timestamp(),
-    couch_passwords:verify(ActualHmac, ExpectedHmac) and not Expired.
-
-
-refresh_cookie({_, Timestamp, _}) ->
-    MaxAge = max_age(),
-    TimeLeft = Timestamp + MaxAge - timestamp(),
-    TimeLeft < MaxAge * 0.9.
-
-
-max_age() ->
-    config:get_integer("csrf", "timeout", 3600).
-
-
-timestamp() ->
-    couch_httpd_auth:make_cookie_time().
-
-csrf_mandatory() ->
-    config:get_boolean("csrf", "mandatory", false).
-
-get_content_type(#httpd{} = Req) ->
-    case couch_httpd:header_value(Req, "Content-Type") of
-        undefined ->
-            undefined;
-        ReqCtype ->
-            case string:tokens(ReqCtype, ";") of
-                [Ctype] -> Ctype;
-                [Ctype | _Rest] -> Ctype
-            end
-    end.
-
-csrf_mime_types() ->
-    Default =
-        "application/x-www-form-urlencoded,"
-        "multipart/form-data,"
-        "text/plain",
-    MimeTypes = config:get("csrf", "mime_types", Default),
-    split(MimeTypes).
-
-split(CSV) ->
-    re:split(CSV, "\\s*,\\s*", [trim, {return, list}]).