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}]).