/*
 * Copyright (c) 2002, The EROS Group, LLC and Johns Hopkins
 * University. All rights reserved.
 * 
 * This software was developed to support the EROS secure operating
 * system project (http://www.eros-os.org). The latest version of
 * the OpenCM software can be found at http://www.opencm.org.
 * 
 * Redistribution and use in source and binary forms, with or
 * without modification, are permitted provided that the following
 * conditions are met:
 * 
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 
 * 2. Redistributions in binary form must reproduce the above
 *    copyright notice, this list of conditions and the following
 *    disclaimer in the documentation and/or other materials
 *    provided with the distribution.
 * 
 * 3. Neither the name of the The EROS Group, LLC nor the name of
 *    Johns Hopkins University, nor the names of its contributors
 *    may be used to endorse or promote products derived from this
 *    software without specific prior written permission.
 * 
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
 * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
 * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
 * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS
 * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
 * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
 * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
 * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 * POSSIBILITY OF SUCH DAMAGE.
 */

#include <opencm.h>
#include <repos/Repository.h>

/* Here's how we check user permissions for different commands */
#define check_read_access(r,x,u)   check_ACL(r,x->readGroupTrueName,u)
#define check_write_access(r,x,u)  check_ACL(r,x->modGroupTrueName,u)

/* Macro for resolving _underlying_ repository */
#define _REPOS_ ((Repository *)(r->info))

static void
authrepos_connect(Repository *r, PubKey *pk)
{
  if (_REPOS_ == 0)
    THROW(ExConnLost, "Repository has been disconnected");

  r->svrPubKey = _REPOS_->svrPubKey;
  r->authMutable = _REPOS_->authMutable;
  r->authUser = _REPOS_->authUser;
  r->authAccess = _REPOS_->authAccess;
}

static void
authrepos_disconnect(Repository *r)
{
  if (_REPOS_ == 0)
    THROW(ExConnLost, "Repository has been disconnected");

  repos_Disconnect(_REPOS_);
  r->info = 0;
}

static const char *
authrepos_GetVersion(Repository *r)
{
  if (_REPOS_ == 0)
    THROW(ExConnLost, "Repository has been disconnected");

  if (repos_fail_access(r, ACC_READ))
    THROW(ExNoAccess, "Insufficient repository access");

  return repos_GetVersion(_REPOS_);
}
  
static Mutable *
authrepos_CreateMutable(Repository *r, const char *nm, 
                        const char *desc, void *s, unsigned flags)
{
  if (_REPOS_ == 0)
    THROW(ExConnLost, "Repository has been disconnected");

  if (repos_fail_access(r, ACC_WRITE))
    THROW(ExNoAccess, "Insufficient repository access to create mutable");

  return repos_CreateMutable(_REPOS_, nm, desc, s, flags);
}

static Mutable *
authrepos_DupMutable(Repository *r, const char *nm,
		     const char *mURI, 
		     OC_bool keepACLs,
		     uint64_t rev,
		     unsigned flags)
{
  if (_REPOS_ == 0)
    THROW(ExConnLost, "Repository has been disconnected");

  if (repos_fail_access(r, ACC_WRITE))
    THROW(ExNoAccess, 
	  "Insfufficient repository access to dup mutable");

  return repos_DupMutable(_REPOS_, nm, mURI, keepACLs, rev, flags);
}

static Mutable *
authrepos_GetMutable(Repository *r, const char *mURI)
{
  Mutable *m;

  if (_REPOS_ == 0)
    THROW(ExConnLost, "Repository has been disconnected");

  if (repos_fail_access(r, ACC_READ))
    THROW(ExNoAccess, "Insufficient repository access to get mutable");

  m = repos_GetMutable(_REPOS_, mURI);

  if (repos_fail_ACL(_REPOS_, m, ACC_READ))
    THROW(ExNoAccess, 
	  format("Insufficient object access to get mutabls %s",
		 mURI));

  return m;
}

static Mutable *
authrepos_ReviseMutable(Repository *r, const char *mURI,
			uint64_t curTopRev, void *s)
{
  Mutable *m;

  if (_REPOS_ == 0)
    THROW(ExConnLost, "Repository has been disconnected");

  if (repos_fail_access(r, ACC_WRITE))
    THROW(ExNoAccess, "Insufficient repository access to revise mutable");

  m = repos_GetMutable(r, mURI);

  if (repos_fail_ACL(_REPOS_, m, ACC_WRITE))
    THROW(ExNoAccess, 
	  format("Insufficient object access to revise mutable %s",
		 mURI));

  return repos_ReviseMutable(_REPOS_, mURI, curTopRev, s);
}

static Revision *
authrepos_GetRevision(Repository *r, const char *mURI,
		      uint64_t revNo)
		       
{
  Mutable *m;

  if (_REPOS_ == 0)
    THROW(ExConnLost, "Repository has been disconnected");

  if (repos_fail_access(r, ACC_READ))
    THROW(ExNoAccess, "Insufficient repository access to get revisions");

  m = repos_GetMutable(r, mURI);

  if (repos_fail_ACL(_REPOS_, m, ACC_READ))
    THROW(ExNoAccess, 
	  format("Insufficient object access to get revisions "
		 "from mutable %s", mURI));

  return repos_GetRevision(_REPOS_, mURI, revNo);
}

static void
authrepos_PutRevision(Repository *r, Revision *rev)
{
  Mutable *m;

  if (_REPOS_ == 0)
    THROW(ExConnLost, "Repository has been disconnected");

  if (repos_fail_access(r, ACC_WRITE))
    THROW(ExNoAccess, "Insufficient repository access to put revisions");

  m = repos_GetMutable(r, rev->mutURI);

  if (repos_fail_ACL(_REPOS_, m, ACC_WRITE))
    THROW(ExNoAccess, 
	  format("Insufficient object access to put revisions "
		 "on mutable %s", rev->mutURI));

  repos_PutRevision(_REPOS_, rev);
}

static Mutable *
authrepos_SetMutableACL(Repository *r, const char *mURI, unsigned int which, 
    const char *uri)
{
  Mutable *m;

  if (_REPOS_ == 0)
    THROW(ExConnLost, "Repository has been disconnected");

  if (repos_fail_access(r, ACC_WRITE))
    THROW(ExNoAccess, "Insufficient repository access to set mutable acl");

  m = repos_GetMutable(r, mURI);

  if (repos_fail_ACL(_REPOS_, m, ACC_WRITE))
    THROW(ExNoAccess, 
	  format("Insufficient object access to set mutable acl on %s",
		 mURI));

  return repos_SetMutableACL(_REPOS_, mURI, which, uri);
}

static Mutable *
authrepos_SetMutableFlags(Repository *r, const char *mURI, unsigned flags)
{
  Mutable *m;

  if (_REPOS_ == 0)
    THROW(ExConnLost, "Repository has been disconnected");

  if (repos_fail_access(r, ACC_WRITE))
    THROW(ExNoAccess, "Insufficient repository access to set mutable flags");

  m = repos_GetMutable(r, mURI);

  if (repos_fail_ACL(_REPOS_, m, ACC_WRITE))
    THROW(ExNoAccess, 
	  format("Insufficient object access to set mutable flags on %s",
		 mURI));

  return repos_SetMutableFlags(_REPOS_, mURI, flags);
}

static Mutable *
authrepos_SetMutableName(Repository *r, const char *mURI, const char *name)
{
  Mutable *m;

  if (_REPOS_ == 0)
    THROW(ExConnLost, "Repository has been disconnected");

  if (repos_fail_access(r, ACC_WRITE))
    THROW(ExNoAccess, "Insufficient repository access to set mutable name");

  m = repos_GetMutable(r, mURI);

  if (repos_fail_ACL(_REPOS_, m, ACC_WRITE))
    THROW(ExNoAccess, 
	  format("Insufficient object access to set mutable name on %s",
		 mURI));

  return repos_SetMutableName(_REPOS_, mURI, name);
}

static Mutable *
authrepos_SetMutableDesc(Repository *r, const char *mURI, 
                         const char *description)
{
  Mutable *m;

  if (_REPOS_ == 0)
    THROW(ExConnLost, "Repository has been disconnected");

  if (repos_fail_access(r, ACC_WRITE))
    THROW(ExNoAccess, "Insufficient repository access to set mutable desc");

  m = repos_GetMutable(r, mURI);

  if (repos_fail_ACL(_REPOS_, m, ACC_WRITE))
    THROW(ExNoAccess, 
	  format("Insufficient object access to set mutable description on %s",
		 mURI));

  return repos_SetMutableDesc(_REPOS_, mURI, description);
}

/* Entity management: */
static void
authrepos_ReviseEntity(Repository *r,
		       const char *mURI,
		       const char *prevTrueName, void *s)
{
  if (_REPOS_ == 0)
    THROW(ExConnLost, "Repository has been disconnected");

  if (repos_fail_access(r, ACC_WRITE))
    THROW(ExNoAccess, "Insufficient repository access to revise entity");

  return repos_ReviseEntity(_REPOS_, mURI, prevTrueName, s);
}

static void
authrepos_ReviseEntityDelta(Repository *r,
			    const char *mURI,
			    const char *prevTrueName, WireDelta *wd)
{
  if (_REPOS_ == 0)
    THROW(ExConnLost, "Repository has been disconnected");

  if (repos_fail_access(r, ACC_WRITE))
    THROW(ExNoAccess, "Insufficient repository access to revise entity");

  return repos_ReviseEntityDelta(_REPOS_, mURI, prevTrueName, wd);
}

static void *
authrepos_GetEntity(Repository *r, const char *mURI, const char *trueName)
{
  if (_REPOS_ == 0)
    THROW(ExConnLost, "Repository has been disconnected");

  if (repos_fail_access(r, ACC_READ))
    THROW(ExNoAccess, "Insufficient repository access to get entity");

  return repos_GetEntity(_REPOS_, mURI, trueName); 
}

static WireDelta *
authrepos_GetEntityDelta(Repository *r, const char *mURI, 
			 const char *trueName, const char *baseTrueName)
{
  if (_REPOS_ == 0)
    THROW(ExConnLost, "Repository has been disconnected");

  if (repos_fail_access(r, ACC_READ))
    THROW(ExNoAccess, "Insufficient repository access to get entity");

  return repos_GetEntityDelta(_REPOS_, mURI, trueName, baseTrueName); 
}

static Mutable *
authrepos_RebindUser(Repository *r, const char *mURI, PubKey *newkey)
{
  if (_REPOS_ == 0)
    THROW(ExConnLost, "Repository has been disconnected");

  if (repos_fail_access(r, ACC_WRITE))
    THROW(ExNoAccess, "Insufficient repository access to rebind user");

  return repos_RebindUser(_REPOS_, mURI, newkey);
}

static Mutable *
authrepos_BindUser(Repository *r, PubKey *pk, unsigned int access)
{
  if (_REPOS_ == 0)
    THROW(ExConnLost, "Repository has been disconnected");

  if (repos_fail_access(r, ACC_WRITE))
    THROW(ExNoAccess, "Insufficient repository access to add user");

  return repos_BindUser(_REPOS_, pk, access);
}

static Mutable *
authrepos_GetUser(Repository *r, PubKey *pk)
{
  Mutable *user;

  if (_REPOS_ == 0)
    THROW(ExConnLost, "Repository has been disconnected");

  if (repos_fail_access(r, ACC_READ))
    THROW(ExNoAccess, "Insufficient repository access to get user");

  user = repos_GetUser(_REPOS_, pk);

  if (repos_fail_ACL(_REPOS_, user, ACC_READ))
    THROW(ExNoAccess, 
	  format("Insufficient object access to read user %s",
		 user->uri));

  return user;
}

static unsigned int
authrepos_GetUserAccess(Repository *r, PubKey *pk)
{
  if (_REPOS_ == 0)
    THROW(ExConnLost, "Repository has been disconnected");

  if (repos_fail_access(r, ACC_READ))
    THROW(ExNoAccess, "Insufficient repository access to get user access");

  return repos_GetUserAccess(_REPOS_, pk);
}

static void
authrepos_ShutDown(Repository *r)
{
  if (_REPOS_ == 0)
    THROW(ExConnLost, "Repository has been disconnected");

  if (repos_fail_access(r, ACC_WRITE))
    THROW(ExNoAccess, "Insufficient repository access to shut down");

  repos_ShutDown(_REPOS_);
}

static TnVec *
authrepos_GetParents(Repository *r, const char *mURI, const char *trueName)
{
  if (_REPOS_ == 0)
    THROW(ExConnLost, "Repository has been disconnected");

  if (repos_fail_access(r, ACC_READ))
    THROW(ExNoAccess, "Insufficient repository access to get entity");

  return repos_GetParents(_REPOS_, mURI, trueName); 
}

static void
authrepos_SetCompression(Repository *r, const char *method, unsigned level)
{
  if (_REPOS_ == 0)
    THROW(ExConnLost, "Repository has been disconnected");

  repos_SetCompression(_REPOS_, method, level);
}

/* FSrepository checks whether user is admin user. */
static TnVec *
authrepos_Enumerate(Repository *r, const char *host, unsigned obType, 
		    unsigned flags)
{
  if (_REPOS_ == 0)
    THROW(ExConnLost, "Repository has been disconnected");

  if (repos_fail_access(r, ACC_READ))
    THROW(ExNoAccess, "Insufficient repository access to get entity");

  return repos_Enumerate(_REPOS_, host, obType, flags);
}

static void
authrepos_GarbageCollect(Repository *r, unsigned gcArg)
{
  if (_REPOS_ == 0)
    THROW(ExConnLost, "Repository has been disconnected");

  repos_GarbageCollect(_REPOS_, gcArg);
}

void
authrepository_init(Repository *r)
{
  r->doesAccess       = TRUE;

  r->GetVersion       = authrepos_GetVersion;
  r->Connect          = authrepos_connect;
  r->Disconnect       = authrepos_disconnect;

  /* Entity management: -- both 2nd args must be serializables */
  r->ReviseEntity      = authrepos_ReviseEntity;
  r->GetEntity         = authrepos_GetEntity;
  r->GetEntityDelta    = authrepos_GetEntityDelta;
  r->ReviseEntityDelta = authrepos_ReviseEntityDelta;

  /* Mutable management: */
  r->GetMutable        = authrepos_GetMutable;
  r->CreateMutable     = authrepos_CreateMutable;
  r->DupMutable        = authrepos_DupMutable;
  r->ReviseMutable     = authrepos_ReviseMutable;
  r->GetRevision       = authrepos_GetRevision;
  r->PutRevision       = authrepos_PutRevision;
  r->SetMutableACL     = authrepos_SetMutableACL;
  r->SetMutableFlags   = authrepos_SetMutableFlags;
  r->SetMutableName    = authrepos_SetMutableName;
  r->SetMutableDesc    = authrepos_SetMutableDesc;

  r->BindUser          = authrepos_BindUser;
  r->RebindUser        = authrepos_RebindUser;
  r->GetUser           = authrepos_GetUser;
  r->GetUserAccess     = authrepos_GetUserAccess;
  r->ShutDown          = authrepos_ShutDown;
  r->GetParents        = authrepos_GetParents;
  r->SetCompression    = authrepos_SetCompression;
  r->Enumerate         = authrepos_Enumerate;
  r->GarbageCollect    = authrepos_GarbageCollect;

  r->info              = NULL;
}

Repository *
authrepository_wrap(Repository *r, PubKey *pk)
{
  Repository *authR = (Repository *) GC_MALLOC(sizeof(Repository));
  
  authR->uri = r->uri;
  authR->authMutable = 0;
  authR->authUser = 0;
  authR->authAccess = 0x0;
  authR->adminGroup = 0;
  authR->svrPubKey = r->svrPubKey;
  authR->repositoryID = r->repositoryID;
  
  authrepository_init(authR);

  authR->info = r;

  authrepos_connect(authR, pk);

  return authR;
}
