blob: c564cee00aecd8a31d58b38c63698c585f6beec4 [file] [log] [blame]
% 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.
-module(couch_auth_cache).
-export([
get_user_creds/1,
get_user_creds/2,
update_user_creds/3,
get_admin/1,
add_roles/2,
auth_design_doc/1,
ensure_users_db_exists/0
]).
-include_lib("couch/include/couch_db.hrl").
-include_lib("couch/include/couch_js_functions.hrl").
-spec get_user_creds(UserName::string() | binary()) ->
{ok, Credentials::list(), term()} | nil.
get_user_creds(UserName) ->
get_user_creds(nil, UserName).
-spec get_user_creds(Req::#httpd{} | nil, UserName::string() | binary()) ->
{ok, Credentials::list(), term()} | nil.
get_user_creds(Req, UserName) when is_list(UserName) ->
get_user_creds(Req, ?l2b(UserName));
get_user_creds(_Req, UserName) ->
UserCreds = case get_admin(UserName) of
nil ->
get_from_db(UserName);
Props ->
case get_from_db(UserName) of
nil ->
Props;
UserProps when is_list(UserProps) ->
add_roles(Props, couch_util:get_value(<<"roles">>, UserProps))
end
end,
validate_user_creds(UserCreds).
update_user_creds(_Req, UserDoc, _AuthCtx) ->
ok = ensure_users_db_exists(),
couch_util:with_db(users_db(), fun(UserDb) ->
{ok, _NewRev} = couch_db:update_doc(UserDb, UserDoc, []),
ok
end).
add_roles(Props, ExtraRoles) ->
CurrentRoles = couch_util:get_value(<<"roles">>, Props),
lists:keyreplace(<<"roles">>, 1, Props, {<<"roles">>, CurrentRoles ++ ExtraRoles}).
get_admin(UserName) when is_binary(UserName) ->
get_admin(?b2l(UserName));
get_admin(UserName) when is_list(UserName) ->
case config:get("admins", UserName) of
"-hashed-" ++ HashedPwdAndSalt ->
% the name is an admin, now check to see if there is a user doc
% which has a matching name, salt, and password_sha
[HashedPwd, Salt] = string:tokens(HashedPwdAndSalt, ","),
make_admin_doc(HashedPwd, Salt);
"-pbkdf2-" ++ HashedPwdSaltAndIterations ->
[HashedPwd, Salt, Iterations] = string:tokens(HashedPwdSaltAndIterations, ","),
make_admin_doc(HashedPwd, Salt, Iterations);
_Else ->
nil
end.
make_admin_doc(HashedPwd, Salt) ->
[{<<"roles">>, [<<"_admin">>]},
{<<"salt">>, ?l2b(Salt)},
{<<"password_scheme">>, <<"simple">>},
{<<"password_sha">>, ?l2b(HashedPwd)}].
make_admin_doc(DerivedKey, Salt, Iterations) ->
[{<<"roles">>, [<<"_admin">>]},
{<<"salt">>, ?l2b(Salt)},
{<<"iterations">>, list_to_integer(Iterations)},
{<<"password_scheme">>, <<"pbkdf2">>},
{<<"derived_key">>, ?l2b(DerivedKey)}].
get_from_db(UserName) ->
ok = ensure_users_db_exists(),
couch_util:with_db(users_db(), fun(Db) ->
DocId = <<"org.couchdb.user:", UserName/binary>>,
try
{ok, Doc} = couch_db:open_doc(Db, DocId, [conflicts]),
{DocProps} = couch_doc:to_json_obj(Doc, []),
DocProps
catch
_:_Error ->
nil
end
end).
validate_user_creds(nil) ->
nil;
validate_user_creds(UserCreds) ->
case couch_util:get_value(<<"_conflicts">>, UserCreds) of
undefined ->
ok;
_ConflictList ->
throw({unauthorized,
<<"User document conflicts must be resolved before the document",
" is used for authentication purposes.">>
})
end,
{ok, UserCreds, nil}.
users_db() ->
DbNameList = config:get("couch_httpd_auth", "authentication_db", "_users"),
?l2b(DbNameList).
ensure_users_db_exists() ->
Options = [?ADMIN_CTX, nologifmissing],
case couch_db:open(users_db(), Options) of
{ok, Db} ->
ensure_auth_ddoc_exists(Db, <<"_design/_auth">>),
couch_db:close(Db);
_Error ->
{ok, Db} = couch_db:create(users_db(), Options),
ok = ensure_auth_ddoc_exists(Db, <<"_design/_auth">>),
couch_db:close(Db)
end,
ok.
ensure_auth_ddoc_exists(Db, DDocId) ->
case couch_db:open_doc(Db, DDocId) of
{not_found, _Reason} ->
{ok, AuthDesign} = auth_design_doc(DDocId),
{ok, _Rev} = couch_db:update_doc(Db, AuthDesign, []);
{ok, Doc} ->
{Props} = couch_doc:to_json_obj(Doc, []),
case couch_util:get_value(<<"validate_doc_update">>, Props, []) of
?AUTH_DB_DOC_VALIDATE_FUNCTION ->
ok;
_ ->
Props1 = lists:keyreplace(<<"validate_doc_update">>, 1, Props,
{<<"validate_doc_update">>,
?AUTH_DB_DOC_VALIDATE_FUNCTION}),
couch_db:update_doc(Db, couch_doc:from_json_obj({Props1}), [])
end
end,
ok.
auth_design_doc(DocId) ->
DocProps = [
{<<"_id">>, DocId},
{<<"language">>,<<"javascript">>},
{<<"validate_doc_update">>, ?AUTH_DB_DOC_VALIDATE_FUNCTION}
],
{ok, couch_doc:from_json_obj({DocProps})}.