diff options
-rw-r--r-- | share/www/script/couch.js | 10 | ||||
-rw-r--r-- | share/www/script/test/reader_acl.js | 77 | ||||
-rw-r--r-- | share/www/script/test/security_validation.js | 4 | ||||
-rw-r--r-- | src/couchdb/couch_db.erl | 65 | ||||
-rw-r--r-- | src/couchdb/couch_httpd_db.erl | 23 |
5 files changed, 81 insertions, 98 deletions
diff --git a/share/www/script/couch.js b/share/www/script/couch.js index 4438a870..c5495424 100644 --- a/share/www/script/couch.js +++ b/share/www/script/couch.js @@ -263,16 +263,16 @@ function CouchDB(name, httpHeaders) { return JSON.parse(this.last_req.responseText); } - this.setAdmins = function(adminsArray) { - this.last_req = this.request("PUT", this.uri + "_admins",{ - body:JSON.stringify(adminsArray) + this.setSecObj = function(secObj) { + this.last_req = this.request("PUT", this.uri + "_security",{ + body:JSON.stringify(secObj) }); CouchDB.maybeThrowError(this.last_req); return JSON.parse(this.last_req.responseText); } - this.getAdmins = function() { - this.last_req = this.request("GET", this.uri + "_admins"); + this.getSecObj = function() { + this.last_req = this.request("GET", this.uri + "_security"); CouchDB.maybeThrowError(this.last_req); return JSON.parse(this.last_req.responseText); } diff --git a/share/www/script/test/reader_acl.js b/share/www/script/test/reader_acl.js index a5fc6a1a..6f834bfb 100644 --- a/share/www/script/test/reader_acl.js +++ b/share/www/script/test/reader_acl.js @@ -35,9 +35,12 @@ couchTests.reader_acl = function(debug) { T(secretDb.save({_id:"baz",foo:"bar"}).ok); T(secretDb.open("baz").foo == "bar"); - T(secretDb.setDbProperty("_readers", { - roles : ["super-secret-club"], - names : ["joe","barb"]}).ok); + T(secretDb.setSecObj({ + "readers" : { + roles : ["super-secret-club"], + names : ["joe","barb"] + } + }).ok); // can't read it as jchris T(CouchDB.login("jchris@apache.org", "funnybone").ok); T(CouchDB.session().userCtx.name == "jchris@apache.org"); @@ -51,54 +54,76 @@ couchTests.reader_acl = function(debug) { CouchDB.logout(); - // make top-secret an admin - T(secretDb.setDbProperty("_admins", { - roles : ["top-secret"], - names : []}).ok); + // make anyone with the top-secret role an admin + // db admins are automatically readers + T(secretDb.setSecObj({ + "admins" : { + roles : ["top-secret"], + names : [] + }, + "readers" : { + roles : ["super-secret-club"], + names : ["joe","barb"] + } + }).ok); T(CouchDB.login("jchris@apache.org", "funnybone").ok); T(secretDb.open("baz").foo == "bar"); CouchDB.logout(); - - T(secretDb.setDbProperty("_admins", { - roles : [], - names : []}).ok); - - // admin now adds the top-secret role to the db's readers T(CouchDB.session().userCtx.roles.indexOf("_admin") != -1); - T(secretDb.setDbProperty("_readers", { - roles : ["super-secret-club", "top-secret"], - names : ["joe","barb"]}).ok); + // admin now adds the top-secret role to the db's readers + // and removes db-admins + T(secretDb.setSecObj({ + "admins" : { + roles : [], + names : [] + }, + "readers" : { + roles : ["super-secret-club", "top-secret"], + names : ["joe","barb"] + } + }).ok); - // now top-secret users can read it + // server _admin can always read T(secretDb.open("baz").foo == "bar"); + + // now top-secret users can read too T(CouchDB.login("jchris@apache.org", "funnybone").ok); + T(CouchDB.session().userCtx.roles.indexOf("_admin") == -1); T(secretDb.open("baz").foo == "bar"); CouchDB.logout(); // can't set non string reader names or roles try { - secretDb.setDbProperty("_readers", { - roles : ["super-secret-club", {"top-secret":"awesome"}], - names : ["joe","barb"]}); + secretDb.setSecObj({ + "readers" : { + roles : ["super-secret-club", {"top-secret":"awesome"}], + names : ["joe","barb"] + } + }) T(false && "only string roles"); } catch (e) {} try { - secretDb.setDbProperty("_readers", { - roles : ["super-secret-club", "top-secret"], - names : ["joe",22]}); + secretDb.setSecObj({ + "readers" : { + roles : ["super-secret-club", {"top-secret":"awesome"}], + names : ["joe",22] + } + }); T(false && "only string names"); } catch (e) {} try { - secretDb.setDbProperty("_readers", { - roles : ["super-secret-club", "top-secret"], - names : "joe" + secretDb.setSecObj({ + "readers" : { + roles : ["super-secret-club", {"top-secret":"awesome"}], + names : "joe" + } }); T(false && "only lists of names"); } catch (e) {} diff --git a/share/www/script/test/security_validation.js b/share/www/script/test/security_validation.js index d618a5ac..94fe62c4 100644 --- a/share/www/script/test/security_validation.js +++ b/share/www/script/test/security_validation.js @@ -105,7 +105,9 @@ couchTests.security_validation = function(debug) { } // set user as the admin - T(db.setDbProperty("_admins", {names : ["Damien Katz"]}).ok); + T(db.setSecObj({ + admins : {names : ["Damien Katz"]} + }).ok); T(userDb.save(designDoc).ok); diff --git a/src/couchdb/couch_db.erl b/src/couchdb/couch_db.erl index 39ce6a9b..6d5da15b 100644 --- a/src/couchdb/couch_db.erl +++ b/src/couchdb/couch_db.erl @@ -23,7 +23,7 @@ -export([enum_docs_since_reduce_to_count/1,enum_docs_reduce_to_count/1]). -export([increment_update_seq/1,get_purge_seq/1,purge_docs/2,get_last_purged/1]). -export([start_link/3,open_doc_int/3,ensure_full_commit/1]). --export([set_readers/2,get_readers/1,set_admins/2,get_admins/1,set_security/2,get_security/1]). +-export([set_security/2,get_security/1]). -export([init/1,terminate/2,handle_call/3,handle_cast/2,code_change/3,handle_info/2]). -export([changes_since/5,changes_since/6,read_doc/2,new_revid/1]). @@ -232,8 +232,8 @@ get_design_docs(#db{fulldocinfo_by_id_btree=Btree}=Db) -> check_is_admin(#db{user_ctx=#user_ctx{name=Name,roles=Roles}}=Db) -> {Admins} = get_admins(Db), - AdminRoles = [<<"_admin">> | proplists:get_value(roles, Admins, [])], - AdminNames = proplists:get_value(names, Admins,[]), + AdminRoles = [<<"_admin">> | proplists:get_value(<<"roles">>, Admins, [])], + AdminNames = proplists:get_value(<<"names">>, Admins,[]), case AdminRoles -- Roles of AdminRoles -> % same list, not an admin role case AdminNames -- [Name] of @@ -251,9 +251,9 @@ check_is_reader(#db{user_ctx=#user_ctx{name=Name,roles=Roles}=UserCtx}=Db) -> ok -> ok; _ -> {Readers} = get_readers(Db), - ReaderRoles = proplists:get_value(roles, Readers,[]), + ReaderRoles = proplists:get_value(<<"roles">>, Readers,[]), WithAdminRoles = [<<"_admin">> | ReaderRoles], - ReaderNames = proplists:get_value(names, Readers,[]), + ReaderNames = proplists:get_value(<<"names">>, Readers,[]), case ReaderRoles ++ ReaderNames of [] -> ok; % no readers == public access _Else -> @@ -273,64 +273,43 @@ check_is_reader(#db{user_ctx=#user_ctx{name=Name,roles=Roles}=UserCtx}=Db) -> end. get_admins(#db{security=SecProps}) -> - proplists:get_value(admins, SecProps, {[]}). - -set_admins(#db{security=SecProps,update_pid=Pid}=Db, Admins) -> - check_is_admin(Db), - SecProps2 = update_sec_field(admins, SecProps, just_names_and_roles(Admins)), - gen_server:call(Pid, {set_security, SecProps2}, infinity). + proplists:get_value(<<"admins">>, SecProps, {[]}). get_readers(#db{security=SecProps}) -> - proplists:get_value(readers, SecProps, {[]}). - -set_readers(#db{security=SecProps,update_pid=Pid}=Db, Readers) -> - check_is_admin(Db), - SecProps2 = update_sec_field(readers, SecProps, just_names_and_roles(Readers)), - gen_server:call(Pid, {set_security, SecProps2}, infinity). + proplists:get_value(<<"readers">>, SecProps, {[]}). get_security(#db{security=SecProps}) -> - proplists:get_value(sec_obj, SecProps, {[]}). + {SecProps}. -set_security(#db{security=SecProps, update_pid=Pid}=Db, {SecObjProps}) when is_list(SecObjProps) -> +set_security(#db{update_pid=Pid}=Db, {NewSecProps}) when is_list(NewSecProps) -> check_is_admin(Db), - SecProps2 = update_sec_field(sec_obj, SecProps, {SecObjProps}), - gen_server:call(Pid, {set_security, SecProps2}, infinity); + ok = validate_security_object(NewSecProps), + gen_server:call(Pid, {set_security, NewSecProps}, infinity); set_security(_, _) -> throw(bad_request). -update_sec_field(Field, SecProps, Value) -> - Admins = proplists:get_value(admins, SecProps, {[]}), - Readers = proplists:get_value(readers, SecProps, {[]}), - SecObj = proplists:get_value(sec_obj, SecProps, {[]}), - if Field == admins -> - [{admins, Value}]; - true -> [{admins, Admins}] - end ++ if Field == readers -> - [{readers, Value}]; - true -> [{readers, Readers}] - end ++ if Field == sec_obj -> - [{sec_obj, Value}]; - true -> [{sec_obj, SecObj}] - end. +validate_security_object(SecProps) -> + Admins = proplists:get_value(<<"admins">>, SecProps, {[]}), + Readers = proplists:get_value(<<"readers">>, SecProps, {[]}), + ok = validate_names_and_roles(Admins), + ok = validate_names_and_roles(Readers), + ok. -% validate user input and convert proplist to atom keys -just_names_and_roles({Props}) when is_list(Props) -> - Names = case proplists:get_value(<<"names">>,Props,[]) of +% validate user input +validate_names_and_roles({Props}) when is_list(Props) -> + case proplists:get_value(<<"names">>,Props,[]) of Ns when is_list(Ns) -> [throw("names must be a JSON list of strings") ||N <- Ns, not is_binary(N)], Ns; _ -> throw("names must be a JSON list of strings") end, - Roles = case proplists:get_value(<<"roles">>,Props,[]) of + case proplists:get_value(<<"roles">>,Props,[]) of Rs when is_list(Rs) -> [throw("roles must be a JSON list of strings") ||R <- Rs, not is_binary(R)], Rs; _ -> throw("roles must be a JSON list of strings") end, - {[ - {names, Names}, - {roles, Roles} - ]}. + ok. get_revs_limit(#db{revs_limit=Limit}) -> Limit. diff --git a/src/couchdb/couch_httpd_db.erl b/src/couchdb/couch_httpd_db.erl index 4f2f1565..9ad34752 100644 --- a/src/couchdb/couch_httpd_db.erl +++ b/src/couchdb/couch_httpd_db.erl @@ -547,29 +547,6 @@ db_req(#httpd{method='POST',path_parts=[_,<<"_revs_diff">>]}=Req, Db) -> db_req(#httpd{path_parts=[_,<<"_revs_diff">>]}=Req, _Db) -> send_method_not_allowed(Req, "POST"); - -db_req(#httpd{method='PUT',path_parts=[_,<<"_admins">>]}=Req, Db) -> - Admins = couch_httpd:json_body(Req), - ok = couch_db:set_admins(Db, Admins), - send_json(Req, {[{<<"ok">>, true}]}); - -db_req(#httpd{method='GET',path_parts=[_,<<"_admins">>]}=Req, Db) -> - send_json(Req, couch_db:get_admins(Db)); - -db_req(#httpd{path_parts=[_,<<"_admins">>]}=Req, _Db) -> - send_method_not_allowed(Req, "PUT,GET"); - -db_req(#httpd{method='PUT',path_parts=[_,<<"_readers">>]}=Req, Db) -> - Readers = couch_httpd:json_body(Req), - ok = couch_db:set_readers(Db, Readers), - send_json(Req, {[{<<"ok">>, true}]}); - -db_req(#httpd{method='GET',path_parts=[_,<<"_readers">>]}=Req, Db) -> - send_json(Req, couch_db:get_readers(Db)); - -db_req(#httpd{path_parts=[_,<<"_readers">>]}=Req, _Db) -> - send_method_not_allowed(Req, "PUT,GET"); - db_req(#httpd{method='PUT',path_parts=[_,<<"_security">>]}=Req, Db) -> SecObj = couch_httpd:json_body(Req), ok = couch_db:set_security(Db, SecObj), |