def update(dn):
        """ api call - update a single rule (full set update...) """

        dn = force_attribute_type("dn", Rules.META["dn"]["type"], dn)
        # build update list based on user data and writeable attributes
        data = get_user_data([])
        update = {}
        for attr in Rules.META:
            if Rules.META[attr]["write"] and attr in data:
                update[attr] = force_attribute_type(attr,
                                                    Rules.META[attr]["type"],
                                                    data[attr])

        # for now only admins can set owner - might open up later...
        if g.user.role == Roles.FULL_ADMIN and "owner" in data:
            update["owner"] = data["owner"]

        # if attribute provided that cannot be written, return error
        for v in data:
            # skip 'dn' and 'owner' (already handled)
            if v == "dn" or v == "owner": continue
            if v not in Rules.META or not Rules.META[v]["write"]:
                abort(400, "unknown or invalid attribute '%s'" % v)

        # ensure at least one valid attribute provided for rule
        if len(update) == 0: abort(400, "no valid parameter provided")
        r = current_app.mongo.db.rules.update_one({"dn": dn}, {"$set": update})
        if r.matched_count == 0: abort(404, "Rule(%s) not found" % dn)
        return {"success": True}
    def create(cls):
        """ api call - create new user, returns dict with username  """

        # get user data with required parameters and validate parameters
        data = get_user_data(["username", "password"])
        username = force_attribute_type("username",
                                        Users.META["username"]["type"],
                                        data["username"])
        password = Users.hash_pass(
            force_attribute_type("password", Users.META["password"]["type"],
                                 data["password"]))
        role = data.get("role", Users.META["role"]["default"])
        if not Roles.valid(role): abort(400, "Invalid role: %s" % role)

        # block reserved users from being created
        if username in Users.RESERVED:
            abort(400, "Username \"%s\" is reserved" % username)

        # find groups that user may already be a member of
        groups = Users.find_groups(username)

        # create user
        update = Rest.create.__func__(cls,
                                      current_app.mongo.db.users,
                                      rule_dn="/users/",
                                      override_attr={
                                          "username": username,
                                          "password": password,
                                          "role": role,
                                          "groups": groups
                                      })
        return {"success": True, "username": username}
    def create():
        """ api call - create new rule, returns DICT (not json)  """

        # get user data with required parameters (only dn is required)
        data = get_user_data(["dn"])

        # minimum required attributes
        update = {"dn": data["dn"]}

        # for now only admins can set owner - might open up later...
        if g.user.role == Roles.FULL_ADMIN and "owner" in data:
            update["owner"] = data["owner"]
        else:
            update["owner"] = g.user.username

        # validate mandatory attributes
        for v in ("dn", "owner"):
            update[v] = force_attribute_type(v, str, update[v])

        # dn must always be in the form /path...
        update["dn"] = "/%s" % update["dn"].strip("/")

        # reserved dn 'incr' used by api call to update_incr
        if update["dn"] == "/incr":
            abort(400, "'/incr' is no a valid DN")

        # validate optional attributes
        for v in Rules.META:
            if Rules.META[v]["write"] and v in data and v not in update:
                update[v] = force_attribute_type(v, Rules.META[v]["type"],
                                                 data[v])
            elif v not in update:
                # ensure all attributes are set for create operation
                update[v] = Rules.META[v]["default"]

        # if attribute provided that cannot be written, return error
        for v in data:
            # skip 'dn' and 'owner', already handled
            if v == "dn" or v == "owner": continue
            if v not in Rules.META or not Rules.META[v]["write"]:
                abort(400, "unknown or invalid attribute '%s'" % v)

        try:
            current_app.mongo.db.rules.insert_one(update)
        except DuplicateKeyError as e:
            abort(400, "Dn \"%s\" already exists" % update["dn"])

        # create returns dict, not json, allowing calling function to
        # add/remove attributes as required by api
        return {"success": True, "dn": update["dn"]}
    def update(cls, username):
        """ api call - update user """

        # block reserved users
        if username in Users.RESERVED:
            abort(400, "Username \"%s\" is reserved" % username)

        # pre-processing custom attributes
        data = get_user_data([])
        override_attr = {}

        # encrypt password if provided in update
        if "password" in data:
            override_attr["password"] = User.hash_pass(
                force_attribute_type("password", cls.META["password"]["type"],
                                     data["password"]))
        # validate role if provided in update
        if "role" in data:
            if not Roles.valid(data["role"]):
                abort(400, "Invalid role: %s" % data["role"])
            override_attr["role"] = data["role"]

        # perform update (aborts on error)
        update = Rest.update.__func__(
            cls,
            current_app.mongo.db.users,
            update_one=("username", username),
            rule_dn="/users/%s" % username,
            override_attr=override_attr,
        )
        return {"success": True}
    def update_incr(cls):
        """ api call - incremental add/remove of entries in list
            - group is provided as a required attribute (similar to create)
            - list_name {"add":[list], "remove":[list]}
        """

        # get user data with required parameters (only dn is required)
        data = get_user_data(["group"])
        group = force_attribute_type("group", Groups.META["group"]["type"],
                                     data["group"])

        # perform update_incr (aborts on error)
        update = Rest.update_incr.__func__(
            cls,
            current_app.mongo.db.groups,
            update_one=("group", group),
            rule_dn="/groups/%s" % group,
        )

        # need to remove/add users from appropriate groups
        # (always perform 'remove' operation first)
        if "members" in update:
            m_update = update["members"]
            if "remove" in m_update and len(m_update["remove"]) > 0:
                Users.remove_groups(m_update["remove"], group)
            if "add" in m_update and len(m_update["add"]) > 0:
                Users.add_groups(m_update["add"], group)

        return {"success": True}
    def update_incr():
        """ api call - incremental add/remove of entries in list
            - dn is provided as a required attribute (similar to create)
            - list_name {"add":[list], "remove":[list]}
        """

        # get user data with required parameters (only dn is required)
        data = get_user_data(["dn"])
        dn = force_attribute_type("dn", Rules.META["dn"]["type"], data["dn"])
        update = {}

        # validate optional attributes
        for v in ("read_users", "write_users", "read_groups", "write_groups"):
            if v in data:
                update[v] = {}
                if type(data[v]) is not dict:
                    abort(400, "attribute '%s' should by type 'dict'" % v)
                for opt in ("add", "remove"):
                    if opt not in data[v]: continue
                    if type(data[v][opt]) is not list:
                        abort(
                            400, "attribute %s[%s] should be type 'list'" %
                            (v, opt))
                    if len(data[v][opt]) == 0: continue
                    update[v][opt] = []
                    for entry in data[v][opt]:
                        # force entry to string and append to update
                        update[v][opt].append(str(entry))
                # no validate data provided, pop index
                if len(update[v]) == 0: update.pop(v, None)

        # ensure at least one valid attribute provided for rule
        if len(update) == 0:
            incr_msg = "Expected at least one 'add' or 'remove' attribute"
            abort(400, "No valid parameter provided. %s" % incr_msg)

        # perform add/remove updates
        for v in update:
            if "add" in update[v] and len(update[v]["add"]) > 0:
                r = current_app.mongo.db.rules.update_one(
                    {"dn": dn},
                    {"$addToSet": {
                        v: {
                            "$each": update[v]["add"]
                        }
                    }})
                if r.matched_count == 0: abort(404, "Rule(%s) not found" % dn)
            if "remove" in update[v] and len(update[v]["remove"]) > 0:
                r = current_app.mongo.db.rules.update_one(
                    {"dn": dn}, {"$pullAll": {
                        v: update[v]["remove"]
                    }})
                if r.matched_count == 0: abort(404, "Rule(%s) not found" % dn)

        return {"success": True}
    def delete(dn):
        """ api call - delete dn """

        dn = force_attribute_type("dn", Rules.META["dn"]["type"], dn)
        r = filtered_read(current_app.mongo.db.rules,
                          meta=Rules.META,
                          filters={"dn": dn})
        if len(r) == 0: abort(404, "Rule(%s) not found" % dn)

        # for now, only admin or rule owner can delete rule
        if g.user.role != Roles.FULL_ADMIN and \
            r[0]["owner"] != g.user.username:
            abort(403, MSG_403)

        # delete the rule
        r = current_app.mongo.db.rules.delete_one({"dn": dn})
        # verify delete occurred
        if r.deleted_count == 0: abort(404, "DN(%s) not found" % dn)
        return {"success": True}
    def read(dn=None):
        """ api call - read one or more rules """

        flt = {}
        if dn is not None:
            dn = force_attribute_type("dn", Rules.META["dn"]["type"], dn)
            flt["dn"] = dn

        # read rules
        results = filtered_read(current_app.mongo.db.rules,
                                meta=Rules.META,
                                filters=flt)

        # dn only provided on read_rule, need 404 error if not found
        if dn is not None:
            if len(results) == 0: abort(404, "DN(%s) not found" % dn)
            return results[0]

        # return full list of rules
        return {"rules": results}
    def create(cls):
        """ api call - create new group, returns dict with group name  """

        # special checks for group put in override fields
        data = get_user_data(["group"])
        group = force_attribute_type("group", str, data["group"])
        if group == "incr":
            abort(400, "'incr' is no a valid group name")

        # create new group
        Rest.create.__func__(cls,
                             current_app.mongo.db.groups,
                             rule_dn="/groups/",
                             required_attr=[],
                             override_attr={"group": group})

        # add group to list of users if 'members' provided
        if "members" in data and len(data["members"]) > 0:
            Users.add_groups(data["members"], group)

        # create returns dict, not json, allowing calling function to
        # add/remove attributes as required by api
        return {"success": True, "group": group}