text: #include <memo/model/doughnut/ACB.hh>
  
  #include <boost/iterator/zip_iterator.hpp>
  #include <boost/range/algorithm/find_if.hpp>
  
  #include <elle/Lazy.hh>
  #include <elle/algorithm.hh>
  #include <elle/bench.hh>
  #include <elle/cast.hh>
  #include <elle/log.hh>
  #include <elle/os/environ.hh>
  #include <elle/serialization/json.hh>
  
  #include <elle/das/model.hh>
  #include <elle/das/serializer.hh>
  
  #include <elle/cryptography/rsa/KeyPair.hh>
  #include <elle/cryptography/rsa/PublicKey.hh>
  #include <elle/cryptography/SecretKey.hh>
  #include <elle/cryptography/hash.hh>
  
  #include <elle/reactor/exception.hh>
  
  #include <memo/model/Conflict.hh>
  #include <memo/model/MissingBlock.hh>
  #include <memo/model/blocks/ImmutableBlock.hh>
  #include <memo/model/blocks/GroupBlock.hh>
  #include <memo/model/doughnut/Doughnut.hh>
  #include <memo/model/doughnut/Group.hh>
  #include <memo/model/doughnut/ValidationFailed.hh>
  #include <memo/model/doughnut/User.hh>
  #include <memo/model/doughnut/UB.hh>
  #include <memo/serialization.hh>
  
  ELLE_LOG_COMPONENT("memo.model.doughnut.ACB");
  
  namespace memo
  {
    namespace model
    {
      namespace doughnut
      {
        using DasACLEntry = elle::das::Model<
          ACLEntry,
          decltype(elle::meta::list(symbols::key,
                                    symbols::read,
                                    symbols::write,
                                    symbols::token))>;
        using DasACLEntryPermissions = elle::das::Model<
          ACLEntry,
          decltype(elle::meta::list(symbols::key,
                                    symbols::read,
                                    symbols::write))>;
      }
    }
  }
  
  ELLE_DAS_MODEL_DEFAULT(memo::model::doughnut::ACLEntry,
                         memo::model::doughnut::DasACLEntry);
  // FAILS in binary mode
  // ELLE_DAS_MODEL_SERIALIZE(memo::model::doughnut::ACB::ACLEntry);
  
  namespace memo
  {
    namespace model
    {
      namespace doughnut
      {
        // default OAEP padding is 336 bits long, which prevents us from using
        // encrypt() with 256 bits secrets on 512 bits keys.
        static const elle::cryptography::rsa::Padding acb_padding
          = elle::cryptography::rsa::Padding::pkcs1;
  
        /*---------.
        | ACLEntry |
        `---------*/
  
        ACLEntry::ACLEntry(elle::cryptography::rsa::PublicKey key_,
                           bool read_,
                           bool write_,
                           elle::Buffer token_)
          : key{std::move(key_)}
          , read{read_}
          , write{write_}
          , token{std::move(token_)}
        {}
  
        ACLEntry::ACLEntry(ACLEntry const& other)
          : key{other.key}
          , read{other.read}
          , write{other.write}
          , token{other.token}
        {}
  
        ACLEntry::ACLEntry(elle::serialization::SerializerIn& s, elle::Version const& v)
          : ACLEntry{deserialize(s, v)}
        {}
  
        ACLEntry
        ACLEntry::deserialize(elle::serialization::SerializerIn& s,
                              elle::Version const& v)
        {
          auto key = deserialize_key_hash(s, v, "key");
          auto read = s.deserialize<bool>("read");
          auto write = s.deserialize<bool>("write");
          auto token = s.deserialize<elle::Buffer>("token");
          return ACLEntry(std::move(key), read, write, std::move(token));
  
          /*
          DasACLEntry::Update content(s);
          return ACLEntry(std::move(content.key.get()),
                          content.read.get(),
                          content.write.get(),
                          std::move(content.token.get()));*/
  
        }
  
        void
        ACLEntry::serialize(elle::serialization::Serializer& s,
                            elle::Version const& v)
        {
          serialize_key_hash(s, v, key, "key");
          s.serialize("read", read);
          s.serialize("write", write);
          s.serialize("token", token);
        }
  
        bool
        ACLEntry::operator == (ACLEntry const& b) const
        {
          return key == b.key && read == b.read && write && b.write
            && token == b.token;
        }
  
        /*-------------.
        | Construction |
        `-------------*/
  
        template <typename Block>
        BaseACB<Block>::BaseACB(Doughnut* owner,
                                elle::Buffer data,
                                boost::optional<elle::Buffer> salt)
          : BaseACB(owner, std::move(data), std::move(salt), owner->keys())
        {}
  
        template <typename Block>
        BaseACB<Block>::BaseACB(Doughnut* owner,
                                elle::Buffer data,
                                boost::optional<elle::Buffer> salt,
                                elle::cryptography::rsa::KeyPair const& keys)
          : Super(owner, std::move(data), std::move(salt), keys)
          , _editor(-1)
          , _owner_token()
          , _acl_changed(true)
          , _data_version(-1)
          , _data_signature()
          , _world_readable(false)
          , _world_writable(false)
          , _deleted(false)
          , _seal_version(owner->version())
        {}
  
        template <typename Block>
        BaseACB<Block>::BaseACB(BaseACB<Block> const& other)
          : Super(other)
          , _editor(other._editor)
          , _owner_token(other._owner_token)
          , _acl_changed(other._acl_changed)
          , _acl_entries(other._acl_entries)
          , _acl_group_entries(other._acl_group_entries)
          , _group_version(other._group_version)
          , _data_version(other._data_version)
          , _data_signature(other._data_signature)
          , _world_readable(other._world_readable)
          , _world_writable(other._world_writable)
          , _deleted(other._deleted)
          , _sign_key(other._sign_key)
          , _seal_version(other._seal_version)
        {}
  
        /*--------.
        | Content |
        `--------*/
  
        template <typename Block>
        int
        BaseACB<Block>::version() const
        {
          return this->_data_version;
        }
  
        static
        void
        background_open(elle::Buffer & target,
                        elle::Buffer const& src,
                        elle::cryptography::rsa::PrivateKey const& k,
                        bool use_encrypt)
        {
          static bool bg = memo::getenv("BACKGROUND_DECODE", true);
          if (bg)
          {
            elle::With<elle::reactor::Thread::NonInterruptible>() << [&]
            {
              elle::reactor::background([&] {
                  target = use_encrypt ? k.decrypt(src, acb_padding) : k.open(src);
                });
            };
          }
          else
            target = use_encrypt ? k.decrypt(src, acb_padding) : k.open(src);
        }
  
        template <typename Block>
        elle::Buffer
        BaseACB<Block>::_decrypt_data(elle::Buffer const& data) const
        {
          if (this->world_readable())
            return this->_data;
          bool use_encrypt = this->_seal_version >= elle::Version(0, 7, 0);
          elle::Buffer secret_buffer;
          if (this->owner_private_key())
          {
            ELLE_DEBUG("%s: we are owner", *this);
            background_open(secret_buffer, this->_owner_token,
                            *this->owner_private_key(), use_encrypt);
          }
          else if (!this->_acl_entries.empty())
          {
            // FIXME: factor searching the token
            for (auto const& e: this->_acl_entries)
              if (e.key == this->doughnut()->keys().K())
                background_open(secret_buffer, e.token,
                                this->doughnut()->keys().k(), use_encrypt);
          }
          if (secret_buffer.empty())
          {
            int idx = 0;
            for (auto const& e: this->_acl_group_entries)
            {
              try
              {
                Group g(*this->doughnut(), e.key);
                auto keys = g.group_keys();
                int v = this->_group_version[idx];
                if (v >= signed(keys.size()))
                {
                  ELLE_DEBUG("announced version %s bigger than size %s",
                             v, keys.size());
                  ++idx;
                  continue;
                }
                background_open(secret_buffer, e.token, keys[v].k(), use_encrypt);
              }
              catch (elle::Error const& e)
              {
                ELLE_DEBUG("error accessing group: %s", e);
              }
              ++idx;
            }
          }
          if (secret_buffer.empty())
            // FIXME: better exceptions
            throw ValidationFailed("no read permissions");
          static auto bench = elle::Bench<>{"bench.acb.decrypt_2", 10000s};
          auto bs = bench.scoped();
          auto secret = [&]() {
            if (use_encrypt)
              return elle::cryptography::SecretKey(secret_buffer.string());
            else
              return elle::serialization::json::deserialize
                 <elle::cryptography::SecretKey>(secret_buffer);
          }();
          ELLE_DUMP("%s: secret: %s", *this, secret);
          return secret.decipher(this->_data);
        }
  
        /*------------.
        | Permissions |
        `------------*/
  
        template <typename Block>
        bool
        BaseACB<Block>::_admin_user(elle::cryptography::rsa::PublicKey const& key) const
        {
          return (elle::contains(this->doughnut()->admin_keys().r, key)
                  || elle::contains(this->doughnut()->admin_keys().w, key));
        }
  
        template <typename Block>
        bool
        BaseACB<Block>::_admin_group(
          elle::cryptography::rsa::PublicKey const& key) const
        {
          return (elle::contains(this->doughnut()->admin_keys().group_r, key)
                  || elle::contains(this->doughnut()->admin_keys().group_w, key));
        }
  
        template <typename Block>
        void
        BaseACB<Block>::_set_world_permissions(bool read, bool write)
        {
          if (this->_world_readable != read || this->_world_writable != write)
          {
            this->_world_readable = read;
            this->_world_writable = write;
            this->_acl_changed = true;
            this->_data_changed = true;
          }
        }
  
        template <typename Block>
        std::pair<bool, bool>
        BaseACB<Block>::_get_world_permissions() const
        {
          return {this->_world_readable, this->_world_writable};
        }
  
        template <typename Block>
        void
        BaseACB<Block>::set_group_permissions(
          elle::cryptography::rsa::PublicKey const& key,
          bool read,
          bool write)
        {
          ELLE_TRACE_SCOPE("%s: set permisions for %s: %s, %s",
                           *this, key, read, write);
          auto& acl_entries = this->_acl_group_entries;
          ELLE_DUMP("%s: ACL entries: %s", *this, acl_entries);
          auto it = std::find_if(
            acl_entries.begin(), acl_entries.end(),
            [&] (ACLEntry const& e) { return e.key == key; });
          if (it == acl_entries.end())
          {
            if (!read && !write)
            {
              ELLE_DUMP("%s: new user with no read or write permissions, "
                        "do nothing", *this);
              return;
            }
            ELLE_DEBUG_SCOPE("%s: new user, insert ACL entry", *this);
            // If the owner token is empty, this block was never pushed and
            // sealing will generate a new secret and update the token.
            // FIXME: the block will always be sealed anyway, why encrypt a token
            // now ?
            bool use_encrypt = this->_seal_version >= elle::Version(0,7,0);
            elle::Buffer token;
            try
            {
              auto&& g = Group(*this->doughnut(), key);
              if (this->_owner_token.size())
              {
                auto secret = use_encrypt?
                  this->owner_private_key()->decrypt(this->_owner_token, acb_padding)
                  : this->owner_private_key()->open(this->_owner_token);
                token = use_encrypt ?
                  g.current_public_key().encrypt(secret, acb_padding)
                  : g.current_public_key().seal(secret);
              }
              acl_entries.emplace_back(ACLEntry(key, read, write, token));
              this->_group_version.push_back(g.version()-1);
              this->_acl_changed = true;
            }
            catch (elle::Error const& e)
            {
              elle::err("Failed to access group block: %s", e);
            }
          }
          else
          {
            if (!read && !write)
            {
              ELLE_DEBUG_SCOPE("%s: user (%s) no longer has read or write "
                               "permissions, remove ACL entry", *this, key);
              acl_entries.erase(it);
              this->_group_version.erase(this->_group_version.begin()
                + (it - acl_entries.begin()));
              this->_acl_changed = true;
              return;
            }
            ELLE_DEBUG_SCOPE("%s: edit ACL entry", *this);
            if (it->read != read)
            {
              it->read = read;
              this->_acl_changed = true;
            }
            if (it->write != write)
            {
              it->write = write;
              this->_acl_changed = true;
            }
          }
        }
        template <typename Block>
        void
        BaseACB<Block>::set_permissions(elle::cryptography::rsa::PublicKey const& key,
                             bool read,
                             bool write)
        {
          ELLE_TRACE_SCOPE("%s: set permisions for %s: %s, %s",
                           *this, key, read, write);
          if (key == *this->owner_key())
            elle::err("Cannot set permissions for owner");
          auto& acl_entries = this->_acl_entries;
          ELLE_DUMP("%s: ACL entries: %s", *this, acl_entries);
          bool use_encrypt = this->_seal_version >= elle::Version(0,7,0);
          auto it = boost::find_if(acl_entries,
                                   [&] (ACLEntry const& e) { return e.key == key; });
          if (it == acl_entries.end())
          {
            if (!read && !write)
            {
              ELLE_DUMP("%s: new user with no read or write permissions, "
                        "do nothing", *this);
              return;
            }
            ELLE_DEBUG_SCOPE("%s: new user, insert ACL entry", *this);
            // If the owner token is empty, this block was never pushed and
            // sealing will generate a new secret and update the token.
            // FIXME: the block will always be sealed anyway, why encrypt a token
            // now ?
            elle::Buffer token;
            if (this->_owner_token.size())
            {
              auto okey = this->owner_private_key();
              if (!okey)
                elle::err("Owner key unavailable");
              auto secret = use_encrypt ? okey->decrypt(this->_owner_token, acb_padding)
                                        : okey->open(this->_owner_token);
              token = use_encrypt ? key.encrypt(secret, acb_padding) : key.seal(secret);
            }
            acl_entries.emplace_back(ACLEntry(key, read, write, token));
            this->_acl_changed = true;
          }
          else
          {
            if (!read && !write)
            {
              ELLE_DEBUG_SCOPE("%s: user (%s) no longer has read or write "
                               "permissions, remove ACL entry", *this, key);
              acl_entries.erase(it);
              this->_acl_changed = true;
              return;
            }
            ELLE_DEBUG_SCOPE("%s: edit ACL entry", *this);
            if (it->read != read)
            {
              it->read = read;
              this->_acl_changed = true;
            }
            if (it->write != write)
            {
              it->write = write;
              this->_acl_changed = true;
            }
          }
        }
  
        template <typename Block>
        void
        BaseACB<Block>::_set_permissions(model::User const& user_, bool read, bool write)
        {
          try
          {
            auto& user = dynamic_cast<User const&>(user_);
            if (this->_admin_user(user.key()))
              elle::err("Cannot change permissions of network admin");
            if (this->_admin_group(user.key()))
              elle::err("Cannot change permissions of network admin group");
            if (user.name()[0] == '#')
            {
              if (!read && !write)
              { // This is safe, nothing will happen if entry is not found
                this->set_group_permissions(user.key(), read, write);
                this->set_permissions(user.key(), read, write);
              }
              else
              {
                ELLE_TRACE("set_permissions on unresolved key, assuming user");
                this->set_permissions(user.key(), read, write);
              }
            }
            if (user.name()[0] == '@')
              this->set_group_permissions(user.key(), read, write);
            else
              this->set_permissions(user.key(), read, write);
          }
          catch (std::bad_cast const&)
          {
            ELLE_ABORT("doughnut was passed a non-doughnut user.");
          }
        }
  
        template <typename Block>
        void
        BaseACB<Block>::_copy_permissions(blocks::ACLBlock& to)
        {
          Self* other = dynamic_cast<Self*>(&to);
          if (!other)
            elle::err("Other block is not an ACB");
          // FIXME: better implementation
          for (auto const& e: this->_acl_entries)
          {
            if (e.key != *other->owner_key())
              other->set_permissions(e.key, e.read, e.write);
          }
          for (auto const& e: this->_acl_group_entries)
          {
            other->set_group_permissions(e.key, e.read, e.write);
          }
          if (*other->owner_key() != *this->owner_key())
            other->set_permissions(*this->owner_key(), true, true);
          other->_world_readable = this->_world_readable;
          other->_world_writable = this->_world_writable;
        }
  
        template <typename Block>
        std::vector<blocks::ACLBlock::Entry>
        BaseACB<Block>::_list_permissions(
          boost::optional<Model const&> model) const
        {
          auto make_user =
            [&] (elle::cryptography::rsa::PublicKey const& k)
            -> std::unique_ptr<memo::model::User>
            {
              try
              {
                auto version = elle_serialization_version(this->doughnut()->version());
                if (model)
                  return model->make_user(
                    elle::serialization::json::serialize(k, version));
                else
                  return std::make_unique<doughnut::User>(k, "");
              }
              catch(elle::Error const& e)
              {
                ELLE_WARN("exception making user: %s", e);
                return nullptr;
              }
            };
          std::vector<ACB::Entry> res;
          auto owner = make_user(*this->owner_key());
          if (owner)
          {
            res.emplace_back(std::move(owner), true, true,
                             this->_admin_user(*this->owner_key()), true);
          }
          for (auto const& ent: this->_acl_entries)
          {
            auto user = make_user(ent.key);
            if (user)
            {
              res.emplace_back(
                std::move(user), ent.read, ent.write, this->_admin_user(ent.key));
            }
          }
          for (auto const& ent: this->_acl_group_entries)
          {
            try
            {
              std::unique_ptr<model::User> user;
              if (!model)
                user.reset(new doughnut::User(ent.key, ""));
              else
                user = this->doughnut()->make_user(
                  elle::serialization::json::serialize(ent.key));
              res.emplace_back(std::move(user), ent.read, ent.write,
                               this->_admin_group(ent.key));
            }
            catch(elle::reactor::Terminate const& e)
            {
              throw;
            }
            catch(std::exception const& e)
            {
              ELLE_TRACE("Exception making user: %s", e);
              res.emplace_back(std::make_unique<model::User>(),
                               ent.read, ent.write, this->_admin_group(ent.key));
            }
          }
          return res;
        }
  
        static
        bool
        has_key(elle::cryptography::rsa::PublicKey const& k,
                std::vector<ACLEntry> const& v, bool write)
        {
          auto it = std::find_if(v.begin(), v.end(),
            [&](ACLEntry const& ent)
            {
              return ent.key == k && ent.read && ent.write >= write;
            });
          return it != v.end();
        }
  
        /*-----------.
        | Validation |
        `-----------*/
  
        template <typename Block>
        blocks::ValidationResult
        BaseACB<Block>::_validate(Model const& model, bool writing) const
        {
          static auto bench = elle::Bench<>{"bench.acb._validate", 10000s};
          auto scope = bench.scoped();
          bool disable_signature = !this->doughnut()->encrypt_options().validate_signatures;
          ELLE_DEBUG("%s: validate owner part", *this)
            if (auto res = Super::_validate(model, writing)); else
              return res;
          if (this->_world_writable)
            return blocks::ValidationResult::success();
          ELLE_DEBUG_SCOPE("%s: validate author part", *this);
          ACLEntry* entry = nullptr;
          bool is_group_entry = false;
          int group_index = -1;
          if (this->_editor != -1)
          {
            ELLE_DEBUG_SCOPE("%s: check author has write permissions", *this);
            if (this->_editor < 0)
            {
              ELLE_DEBUG("%s: no ACL or no editor", *this);
              return blocks::ValidationResult::failure("no ACL or no editor");
            }
            if (this->_editor >= signed(this->_acl_entries.size()))
            {
              int gindex = this->_editor - this->_acl_entries.size();
              if (gindex >= signed(this->_acl_group_entries.size()))
              {
                ELLE_DEBUG("%s: editor index out of bounds", *this);
                return blocks::ValidationResult::failure
                ("editor index out of bounds");
              }
              entry = elle::unconst(&this->_acl_group_entries[gindex]);
              is_group_entry = true;
              group_index = gindex;
            }
            else
              entry = elle::unconst(&this->_acl_entries[this->_editor]);
            if (!entry->write)
            {
              ELLE_DEBUG("%s: no write permissions", *this);
              return blocks::ValidationResult::failure("no write permissions");
            }
          }
          if (!disable_signature)
          {
            ELLE_DEBUG("check author signature")
            {
              if (is_group_entry)
              { // fetch latest key for group
                try
                {
                  Group g(*this->doughnut(), entry->key);
                  auto pubkeys = g.group_public_keys();
                  if (group_index >= signed(this->_group_version.size()))
                    return blocks::ValidationResult::failure("group_version array too short");
                  auto key_index = this->_group_version[group_index];
                  if (key_index >= signed(pubkeys.size()))
                    return blocks::ValidationResult::failure("group key out of range");
                  auto& key = pubkeys[key_index];
                  ELLE_DEBUG("validating with group key %s: %s", key_index, key);
                  if (!key.verify(this->data_signature(), *this->_data_sign()))
                  {
                    ELLE_DEBUG("%s: group author signature invalid", *this);
                    return blocks::ValidationResult::failure("Invalid group key signature");
                  }
                }
                catch (elle::Error const& e)
                {
                  ELLE_TRACE("Error processing group entry: %s", e.what());
                  return blocks::ValidationResult::failure("Failed to access group");
                }
              }
              else
              {
                auto& key = entry ? entry->key : *this->owner_key();
                if (!key.verify(this->data_signature(), *this->_data_sign()))
                {
                  ELLE_DEBUG("%s: author signature invalid", *this);
                  return blocks::ValidationResult::failure
                    ("author signature invalid");
                }
              }
            }
          }
          if (writing)
          {
            if (auto res = this->_validate_admin_keys(model)); else
              return res;
          }
          return blocks::ValidationResult::success();
        }
  
        template <typename Block>
        blocks::ValidationResult
        BaseACB<Block>::_validate_admin_keys(Model const& model) const
        {
          // check for admin keys
          auto const& aks = dynamic_cast<Doughnut const&>(model).admin_keys();
          for (auto const& k: aks.r)
            if (k != *this->owner_key() && !has_key(k, this->_acl_entries, false))
              return blocks::ValidationResult::failure(
                elle::sprintf("Missing admin R key %s", k));
          for (auto const& k: aks.w)
            if (k != *this->owner_key() && !has_key(k, this->_acl_entries, true))
              return blocks::ValidationResult::failure(
                elle::sprintf("Missing admin RW key %s", k));
          for (auto const& k: aks.group_r)
            if (k != *this->owner_key() && !has_key(k, this->_acl_group_entries, false))
              return blocks::ValidationResult::failure(
                elle::sprintf("Missing admin R group key %s", k));
          for (auto const& k: aks.group_w)
            if (k != *this->owner_key() && !has_key(k, this->_acl_group_entries, true))
              return blocks::ValidationResult::failure(
                elle::sprintf("Missing admin RW group key %s", k));
          return blocks::ValidationResult::success();
        }
  
        template <typename Block>
        blocks::ValidationResult
        BaseACB<Block>::_validate(Model const& model,
                                  blocks::Block const& new_block) const
        {
          auto supval = Block::_validate(model, new_block);
          if (!supval)
            return supval;
          auto acb = dynamic_cast<Self const*>(&new_block);
          if (!acb)
            return blocks::ValidationResult::failure("New block is not an ACB");
          // check non-regression of group signature indexes
          if (acb->_group_version.size() != acb->_acl_group_entries.size())
            return blocks::ValidationResult::failure("Mismatch size in group entries");
          for (int i=0; i<signed(this->_group_version.size()); ++i)
          {
            for (int j=0; j<signed(acb->_group_version.size()); ++j)
            {
              if (this->_acl_group_entries[i].key == acb->_acl_group_entries[j].key)
              {
                if (this->_group_version[i] > acb->_group_version[j])
                {
                  ELLE_TRACE("Group key index downgraded: %s -> %s",
                           this->_group_version, acb->_group_version);
                  return blocks::ValidationResult::conflict(
                    "Group key index downgraded.");
                }
                break;
              }
            }
          }
          return blocks::ValidationResult::success();
        }
  
        template <typename Block>
        void
        BaseACB<Block>::seal(boost::optional<int> version,
                             elle::cryptography::SecretKey const& key)
        {
          this->_seal(version, key);
        }
  
        template <typename Block>
        void
        BaseACB<Block>::seal(int version)
        {
          this->_seal(version);
        }
  
        template <typename Block>
        void
        BaseACB<Block>::_seal(boost::optional<int> version)
        {
          this->_seal(version, {});
        }
  
        template <typename Block>
        void
        BaseACB<Block>::_seal(boost::optional<int> version,
                              boost::optional<elle::cryptography::SecretKey const&> key)
        {
          static auto bench = elle::Bench<>{"bench.acb.seal", 10000s};
          auto scope = bench.scoped();
          if (!version && this->Super::_seal_version && *this->Super::_seal_version)
          {
            version = this->Super::_seal_version;
            this->Super::_seal_version.reset();
          }
          std::shared_ptr<elle::cryptography::rsa::PrivateKey> sign_key;
          bool disable_signature = !this->doughnut()->encrypt_options().validate_signatures;
          // enforce admin keys
          auto const& aks = this->doughnut()->admin_keys();
          for (auto const& k: aks.r)
            if (k != *this->owner_key()) set_permissions(k, true, false);
          for (auto const& k: aks.w)
            if (k != *this->owner_key()) set_permissions(k, true, true);
          for (auto const& k: aks.group_r)
            if (k != *this->owner_key()) set_group_permissions(k, true, false);
          for (auto const& k: aks.group_w)
            if (k != *this->owner_key()) set_group_permissions(k, true, true);
  
          bool acl_changed = this->_acl_changed;
          bool data_changed = this->_data_changed;
  
          if (acl_changed)
          {
            static auto bench = elle::Bench<>{"bench.acb.seal.aclchange", 10000s};
            auto scope = bench.scoped();
            ELLE_TRACE_SCOPE("ACL changed, seal");
            this->_acl_changed = false;
            if (this->owner_private_key())
            {
              sign_key = this->owner_private_key();
              this->_editor = -1;
            }
            Super::_seal_okb();
            if (!data_changed)
              ++this->_data_version;
          }
          else
            ELLE_DEBUG("ACL didn't change")
              ELLE_DUMP("signature: %x", this->signature());
          if (data_changed)
          {
            static auto bench = elle::Bench<>{"bench.acb.seal.datachange", 10000s};
            auto scope = bench.scoped();
            ++this->_data_version;
            ELLE_TRACE_SCOPE(
              "data changed, seal version %s", this->_data_version);
            if (this->owner_private_key())
            {
              ELLE_DEBUG("we are owner");
              sign_key = this->owner_private_key();
              this->_editor = -1;
            }
            boost::optional<elle::cryptography::SecretKey> secret;
            if (!key)
            {
              secret = elle::cryptography::secretkey::generate(256);
              key.emplace(secret.get());
            }
            ELLE_DUMP("new block secret: %s", key.get());
            auto seal_version = this->doughnut()->version();
            static bool encrypt = memo::getenv("TOKEN_ENCRYPT", true);
            if (!encrypt)
              seal_version = std::min(seal_version, elle::Version(0, 6, 0));
            auto elle_version = elle_serialization_version(seal_version);
            elle::Buffer secret_buffer;
            if (seal_version < elle::Version(0, 7, 0))
              secret_buffer =
                elle::serialization::json::serialize(key.get(), elle_version);
            else
              secret_buffer = key.get().password().string();
            this->_seal_version = seal_version;
            bool use_encrypt = seal_version >= elle::Version(0, 7, 0);
            this->_owner_token = use_encrypt ?
              this->owner_key()->encrypt(secret_buffer, acb_padding)
              : this->owner_key()->seal(secret_buffer);
            int idx = 0;
            for (auto& e: this->_acl_entries)
            {
              if (e.read)
                e.token = use_encrypt ? e.key.encrypt(secret_buffer, acb_padding) : e.key.seal(secret_buffer);
              if (!sign_key && e.key == this->doughnut()->keys().K())
              {
                ELLE_DEBUG("we are editor %s", idx);
                this->_editor = idx;
                sign_key = this->doughnut()->keys().private_key();
              }
              ++idx;
            }
            for (auto& e: this->_acl_group_entries)
            {
              try
              {
                Group g(*this->doughnut(), e.key);
                if (e.read)
                {
                  e.token = use_encrypt ?
                   g.current_public_key().encrypt(secret_buffer, acb_padding)
                   : g.current_public_key().seal(secret_buffer);
                  this->_group_version[idx - this->_acl_entries.size()] =
                    g.version() - 1;
                }
                if (!sign_key)
                {
                  try
                  {
                    auto kp = g.current_key();
                    this->_editor = idx;
                    ELLE_DEBUG("we are editor from group %s", g);
                    sign_key = g.current_key().private_key();
                  }
                  catch (elle::Error const& e)
                  {
                    ELLE_DEBUG("group key access failed: %s", e);
                  }
                }
              }
              catch (elle::Error const& e)
              { // most likely missing group block
                ELLE_LOG("Unexpected error accessing group: %s", e);
              }
              ++idx;
            }
            if (!sign_key && this->_world_writable)
            {
              ELLE_DEBUG("block is world writable");
              sign_key = this->doughnut()->keys().private_key();
            }
            if (!this->_world_readable)
              this->blocks::MutableBlock::data(
                key->encipher(this->data_plain()));
            else
              this->blocks::MutableBlock::data(this->data_plain());
            this->_data_changed = false;
          }
          else
            ELLE_DEBUG("data didn't change");
          // Even if only the ACL was changed, we need to re-sign because the ACL
          // address is part of the signature.
          if (acl_changed || data_changed || version)
          {
            if (version)
              this->_data_version = *version;
            if (!sign_key)
            { // can happen if version is set but data is unchanged
              if (this->owner_private_key())
              {
                ELLE_DEBUG("we are owner");
                sign_key = this->owner_private_key();
                this->_editor = -1;
              }
            }
            int idx = 0;
            if (!sign_key)
            {
              for (auto& e: this->_acl_entries)
              {
                if (e.key == this->doughnut()->keys().K())
                {
                  ELLE_DEBUG("we are editor %s", idx);
                  this->_editor = idx;
                  sign_key = this->doughnut()->keys().private_key();
                }
                ++idx;
              }
            }
            if (!sign_key)
            {
              for (auto& e: this->_acl_group_entries)
              {
                Group g(*this->doughnut(), e.key);
                try
                {
                  auto kp = g.current_key();
                  this->_editor = idx;
                  ELLE_DEBUG("we are editor from group %s", g);
                  sign_key = g.current_key().private_key();
                }
                catch (elle::Error const& e)
                {}
              }
              ++idx;
            }
            if (!sign_key && this->_world_writable)
            {
              ELLE_DEBUG("block is world writable");
              sign_key = this->doughnut()->keys().private_key();
            }
            if (!sign_key)
              throw ValidationFailed("not owner and no write permissions");
            ELLE_DEBUG_SCOPE("sign data");
            if (!disable_signature)
            {
              this->_data_signature = std::make_shared<typename Super::SignFuture>(
                  sign_key->sign_async(
                    *this->_data_sign(), this->doughnut()->version()));
              ELLE_DUMP(
                "signature: {}",
                elle::lazy([&] { return this->_data_signature->value(); }));
            }
            this->_sign_key = sign_key;
  
          }
          // restart signature process in case we were restored from storage
          if (!disable_signature && !this->_data_signature->running()
            && this->_data_signature->value() == elle::Buffer())
          {
            ELLE_ASSERT(_sign_key);
            this->_data_signature = std::make_shared<typename Super::SignFuture>(
              _sign_key->sign_async(
                *this->_data_sign(), this->doughnut()->version()));
          }
          if (!disable_signature && !this->_signature->running()
            && this->_signature->value() == elle::Buffer())
            this->_seal_okb({}, false);
        }
  
        template <typename Block>
        BaseACB<Block>::~BaseACB()
        {}
  
        template <typename Block>
        elle::Buffer const&
        BaseACB<Block>::data_signature() const
        {
          return this->_data_signature->value();
        }
  
        template <typename Block>
        BaseACB<Block>::OwnerSignature::OwnerSignature(BaseACB<Block> const& b)
          : Super::OwnerSignature(b)
          , _block(b)
        {}
  
        template <typename Block>
        void
        BaseACB<Block>::OwnerSignature::_serialize(
          elle::serialization::SerializerOut& s,
          elle::Version const& v)
        {
          s.serialize<elle::das::Serializer<ACLEntry, DasACLEntryPermissions>>(
            "acls", this->_block.acl_entries());
          if (v >= elle::Version(0, 4, 0))
          {
            s.serialize<elle::das::Serializer<ACLEntry, DasACLEntryPermissions>>(
              "group_acls", this->_block.acl_group_entries());
            s.serialize("world_readable", this->_block.world_readable());
            s.serialize("world_writable", this->_block.world_writable());
          }
        }
  
        template <typename Block>
        std::unique_ptr<typename BaseACB<Block>::Super::OwnerSignature>
        BaseACB<Block>::_sign() const
        {
          return std::make_unique<OwnerSignature>(*this);
        }
  
        template<typename Block>
        model::blocks::RemoveSignature
        BaseACB<Block>::_sign_remove(Model& model) const
        {
          auto res = this->clone();
          auto acb = dynamic_cast<Self*>(res.get());
          ELLE_ASSERT(acb);
          acb->_deleted = true;
          acb->_data_changed = true;
          acb->seal();
          blocks::RemoveSignature rs;
          rs.block = std::move(res);
          return rs;
        }
  
        template<typename Block>
        blocks::ValidationResult
        BaseACB<Block>::_validate_remove(Model& model,
                                         blocks::RemoveSignature const& rs) const
        {
          ELLE_DUMP_SCOPE("%s: check %f validates removal", this, rs);
          if (!rs.block)
          {
            ELLE_DUMP("remove signature has no block");
            return blocks::ValidationResult::failure(
              "remove signature has no block");
          }
          auto mb = dynamic_cast<Self*>(rs.block.get());
          if (!mb)
          {
            ELLE_DUMP("remove signature block is not mutable");
            return blocks::ValidationResult::failure(
              "remove signature block is not mutable");
          }
          if (!mb->deleted())
          {
            ELLE_DUMP("remove signature block's not marked for deletion");
            return blocks::ValidationResult::failure(
              "remove signature block's not marked for deletion");
          }
          // FIXME: calling validate can change our address, and make
          // the validate(other) below fail
          auto valid = rs.block->validate(model, true);
          if (!valid)
          {
            ELLE_DUMP("remove signature block's is not valid");
            return valid;
          }
          if (this->version() >= mb->version())
          {
            ELLE_DUMP("removal version %s is not greater than local version %s",
                      mb->version(), this->version());
            return blocks::ValidationResult::conflict("invalid version");
          }
          if (!(valid = this->blocks::Block::validate(model, *mb)))
            return valid;
          return blocks::ValidationResult::success();
        }
  
        template <typename... T>
        auto zip(const T&... containers)
          -> boost::iterator_range<boost::zip_iterator<
               decltype(boost::make_tuple(std::begin(containers)...))>>
        {
          auto zip_begin = boost::make_zip_iterator(
            boost::make_tuple(std::begin(containers)...));
          auto zip_end = boost::make_zip_iterator(
            boost::make_tuple(std::end(containers)...));
          return boost::make_iterator_range(zip_begin, zip_end);
        }
  
        template <typename Block>
        std::unique_ptr<typename BaseACB<Block>::DataSignature>
        BaseACB<Block>::_data_sign() const
        {
          return std::make_unique<DataSignature>(*this);
        }
  
        template <typename Block>
        BaseACB<Block>::DataSignature::DataSignature(BaseACB<Block> const& block)
          : _block(block)
        {}
  
        template <typename Block>
        void
        BaseACB<Block>::DataSignature::serialize(
          elle::serialization::Serializer& s_,
          elle::Version const& v)
        {
          // FIXME: Improve when split-serialization is added.
          ELLE_ASSERT(s_.out());
          auto& s = reinterpret_cast<elle::serialization::SerializerOut&>(s_);
          s.serialize("salt", this->_block.salt());
          ELLE_ASSERT(this->_block.doughnut());
          serialize_key_hash(
            s, v, *this->_block.owner_key(), "key", this->_block.doughnut());
          s.serialize("version", this->_block.data_version());
          s.serialize("data", this->_block.blocks::Block::data());
          s.serialize("owner_token", this->_block.owner_token());
          s.serialize("acl", this->_block.acl_entries());
          if (v >= elle::Version(0, 4, 0))
          {
            s.serialize("group_acl", this->_block.acl_group_entries());
            s.serialize("group_version", this->_block.group_version());
            s.serialize("deleted", this->_block.deleted());
          }
        }
  
        /*--------.
        | Content |
        `--------*/
        template <typename Block>
        void
        BaseACB<Block>::_stored()
        {
        }
  
        template <typename Block>
        bool
        BaseACB<Block>::operator ==(blocks::Block const& rhs) const
        {
          auto that = dynamic_cast<Self const*>(&rhs);
          return (that
                  && this->_editor == that->_editor
                  && this->_owner_token == that->_owner_token
                  && this->_acl_entries == that->_acl_entries
                  && this->_data_version == that->_data_version
                  // && this->_data_signature->value() == that->_data_signature->value()
                  && this->_world_readable == that->_world_readable
                  && this->_world_writable == that->_world_writable
                  && this->_deleted == that->_deleted
                  && this->Super::operator ==(rhs));
        }
  
        /*--------------.
        | Serialization |
        `--------------*/
  
        template <typename Block>
        BaseACB<Block>::BaseACB(elle::serialization::SerializerIn& input,
                                elle::Version const& version)
          : Super(input, version)
          , _editor(-2)
          , _owner_token()
          , _acl_changed(false)
          , _data_version(-1)
          , _data_signature()
          , _world_readable(false)
          , _world_writable(false)
          , _deleted(false)
        {
          this->_serialize(input, version);
        }
  
        template <typename Block>
        void
        BaseACB<Block>::serialize(elle::serialization::Serializer& s,
                                  elle::Version const& version)
        {
          Super::serialize(s, version);
          this->_serialize(s, version);
        }
  
        template <typename Block>
        void
        BaseACB<Block>::_serialize(elle::serialization::Serializer& s,
                                   elle::Version const& version)
        {
          if (!this->_data_signature)
            this->_data_signature = std::make_shared<typename Super::SignFuture>();
          s.serialize("editor", this->_editor);
          s.serialize("owner_token", this->_owner_token);
          s.serialize("acl", this->_acl_entries);
          s.serialize("data_version", this->_data_version);
          if (version < elle::Version(0, 4, 0))
            if (s.out())
            {
              auto value =
                elle::WeakBuffer(this->_data_signature->value()).range(4);
              s.serialize("data_signature", value);
            }
            else
            {
              elle::Buffer signature;
              s.serialize("data_signature", signature);
              auto versioned =
                elle::serialization::binary::serialize(elle::Version(0, 3, 0));
              versioned.size(versioned.size() + signature.size());
              memcpy(versioned.mutable_contents() + 4,
                     signature.contents(), signature.size());
              this->_data_signature = std::make_shared<typename Super::SignFuture>(
                std::move(versioned));
            }
          else
          {
            bool need_signature = !s.context().has<ACBDontWaitForSignature>();
            if (need_signature)
              s.serialize("data_signature", this->_data_signature->value());
            else
            {
              if (s.out())
              {
                bool ready = !this->_data_signature->running();
                s.serialize("data_signature_ready", ready);
                if (ready)
                  s.serialize("data_signature", this->_data_signature->value());
                else
                {
                  ELLE_ASSERT(this->doughnut());
                  bool is_doughnut_key =
                    *this->_sign_key == this->doughnut()->keys_shared()->k();
                  s.serialize("data_signature_is_doughnut_key", is_doughnut_key);
                  if (!is_doughnut_key)
                    s.serialize("data_signature_key", this->_sign_key);
                }
              }
              else
              {
                bool ready;
                s.serialize("data_signature_ready", ready);
                if (ready)
                  s.serialize("data_signature", this->_data_signature->value());
                else
                {
                  bool is_doughnut_key;
                  s.serialize("data_signature_is_doughnut_key", is_doughnut_key);
                  if (!is_doughnut_key)
                    s.serialize("data_signature_key", this->_sign_key);
                  else
                  {
                    ELLE_ASSERT(this->doughnut());
                    this->_sign_key = this->doughnut()->keys_shared()->private_key();
                  }
                }
              }
            }
          }
          if (version >= elle::Version(0, 4, 0))
          {
            s.serialize("world_readable", this->_world_readable);
            s.serialize("world_writable", this->_world_writable);
            s.serialize("group_acl", this->_acl_group_entries);
            s.serialize("group_version", this->_group_version);
            s.serialize("deleted", this->_deleted);
          }
          if (version >= elle::Version(0, 7, 0))
            s.serialize("seal_version", this->_seal_version);
          else if (s.in())
            this->_seal_version = elle::Version(0, 6, 0);
          else if (s.out() && this->_seal_version > version)
            ELLE_WARN("%s: seal version %s is above current version %s",
                      this, this->_seal_version, version);
        }
  
        template
        class BaseACB<blocks::ACLBlock>;
  
        template
        class BaseACB<blocks::GroupBlock>;
  
        static const elle::serialization::Hierarchy<blocks::Block>::
        Register<ACB> _register_okb_serialization("ACB");
      }
    }
  }
  
  ELLE_TYPE_INFO_ABBR("ACB", "BaseACB<infinit::model::blocks::ACLBlock>");
  