Обработка доступа к данным на мультитенантном сайте - PullRequest
1 голос
/ 16 марта 2012

Буду признателен за некоторые указания относительно доступа к данным и управления ими на мультитенантном сайте на основе MVC:

Существует ли лучший / более безопасный / элегантный способ убедиться, что на мультитенантном сайте пользователь может обрабатыватьтолько свои данные.Существует несколько арендаторов, использующих одно и то же приложение: firstTenant.myapp.com, secondTenant.myapp.com ...

    //
    // GET: /Customer/
    // show this tenant's customer info only

    public ViewResult Index()
    {
        //get TenantID from on server cache
        int TenantID =  Convert.ToInt16( new AppSettings()["TenantID"]);
        return View(context.Customers.ToList().Where(c => c.TenantID == TenantID));
    }

Если пользователь входит в систему в первый раз и для этого нет кэша на стороне сервераtenant / user - AppSettings проверяет в дБ и сохраняет TenantID в кеше.

Каждая таблица в базе данных содержит поле TenantID и используется для ограничения доступа к данным только соответствующему арендатору.

Итак,вместо того, чтобы проверять каждое действие в каждом контроллере, если данные принадлежат текущему арендатору, могу ли я сделать что-то более «продуктивное»?

Пример:

Когда администратор firstTenant пытается редактироватьнекоторая информация для пользователя 4, URL имеет: http://firstTenant.myapp.com/User/Edit/4

Предположим, что пользователь с идентификатором 2 принадлежит secondTenant.Администратор из firstTenant помещает http://firstTenant.myapp.com/User/Edit/2 в URL и пытается получить информацию, которая не принадлежит его компании.

Чтобы предотвратить это в контроллере, я проверяю, действительно ли редактируемая информация принадлежиттекущий арендатор.

    //
    // GET: /User/Edit/

    public ActionResult Edit(int id)
    {
        //set tennant ID
        int TenanatID = Convert.ToInt32(new AppSettings()["TenantID"]);
        //check if asked info is actually owned by this tennant
        User user = context.Userss.Where(u => u.TenantID == TenantID).SingleOrDefault(u => u.UserID == id);

        //in case this tenant doesn't have this user ID, ie.e returned User == null
        //something is wrong, so handle bad request
        //

        return View(user);
    }

По сути, этот вид установок необходимо размещать в каждом контроллере, где есть доступ к любым данным.Есть ли (и как) лучший способ справиться с этим?(Фильтры, атрибуты ...)

Ответы [ 4 ]

3 голосов
/ 17 марта 2012

Я решил использовать фильтры действий для этого. Возможно, это не самое элегантное решение, но это самое чистое из решений, которые мы пробовали до сих пор.

Я держу арендатора (в нашем случае это команда) в URL-адресе, например: https://myapp.com/{team}/tasks/details/1234

Я использую пользовательские привязки, чтобы отобразить {team} в фактический Team объект, поэтому мои методы действия выглядят так:

[AjaxAuthorize, TeamMember, TeamTask("id")]
public ActionResult Details(Team team, Task id)

Атрибут TeamMember подтверждает, что зарегистрированный в данный момент пользователь действительно принадлежит команде. Это также подтверждает, что команда действительно существует:

public class TeamMemberAttribute : ActionFilterAttribute
{
  public override void OnActionExecuting(ActionExecutingContext filterContext)
  {
    base.OnActionExecuting(filterContext);
    var httpContext = filterContext.RequestContext.HttpContext;

    Team team = filterContext.ActionParameters["team"] as Team;
    long userId = long.Parse(httpContext.User.Identity.Name);

    if (team == null || team.Members.Where(m => m.Id == userId).Count() == 0)
    {
        httpContext.Response.StatusCode = 403;
        ViewResult insufficientPermssions = new ViewResult();
        insufficientPermssions.ViewName = "InsufficientPermissions";
        filterContext.Result = insufficientPermssions;
    }
  }
}

Аналогичным образом, атрибут TeamTask гарантирует, что рассматриваемая задача действительно принадлежит команде.

1 голос
/ 19 марта 2012

Поскольку мое приложение использует субдомены (sub1.app.com, sub2.app.com .....), я в основном выбираю:

a) используйте что-то вроде следующего кода для кэширования информации об арендаторах и

b) вызвать фильтр действий на каждом контроллере, как предложено Ragesh & Doc:

(следующий код из блога: http://www.developer.com/design/article.php/10925_3801931_2/Introduction-to-Multi-Tenant-Architecture.htm)

// <summary>
// This class is used to manage the Cached AppSettings
// from the Database
// </summary>
public class AppSettings
{
// <summary>
// This indexer is used to retrieve AppSettings from Memory
// </summary>
public string this[string Name]
{
  get
  {
     //See if we have an AppSettings Cache Item
     if (HttpContext.Current.Cache["AppSettings"] == null)
     {
        int? TenantID = 0;
        //Look up the URL and get the Tenant Info
        using (ApplContext dc =
           new ApplContext())
        {
           Site result =
                  dc.Sites
                  .Where(a => a.Host ==
                     HttpContext.Current.Request.Url.
                        Host.ToLower())
                  .FirstOrDefault();
           if (result != null)
           {
              TenantID = result.SiteID;
           }
        }
        AppSettings.LoadAppSettings(TenantID);
     }

     Hashtable ht =
       (Hashtable)HttpContext.Current.Cache["AppSettings"];
     if (ht.ContainsKey(Name))
     {
        return ht[Name].ToString();
     }
     else
     {
        return string.Empty;
     }
  }
}

// <summary>
// This Method is used to load the app settings from the
// database into memory
// </summary>
public static void LoadAppSettings(int? TenantID)
{
  Hashtable ht = new Hashtable();

  //Now Load the AppSettings
  using (ShoelaceContext dc =
     new ShoelaceContext())
  {

      //settings are turned off
      // no specific settings per user needed currently
     //var results = dc.AppSettings.Where(a =>
     //   a.in_Tenant_Id == TenantID);

     //foreach (var appSetting in results)
     //{
     //   ht.Add(appSetting.vc_Name, appSetting.vc_Value);
     //}
                ht.Add("TenantID", TenantID);

  }

  //Add it into Cache (Have the Cache Expire after 1 Hour)
  HttpContext.Current.Cache.Add("AppSettings",
     ht, null,
     System.Web.Caching.Cache.NoAbsoluteExpiration,
     new TimeSpan(1, 0, 0),
     System.Web.Caching.CacheItemPriority.NotRemovable, null);

     }
  }
0 голосов
/ 17 марта 2012

Мы разработали мультитенантное приложение, использующее также ASP.NET MVC, и включение идентификатора арендатора в каждый запрос является абсолютно приемлемым и действительно необходимым делом. Я не уверен, где вы размещаете свое приложение, но если вы можете использовать SQL Azure, у них есть новый продукт под названием Federations, который позволяет вам легко управлять данными нескольких клиентов. Приятной особенностью является то, что при открытии соединения вы можете указать идентификатор арендатора, и все последующие запросы будут влиять только на данные арендаторов. По сути, это просто включение их идентификатора клиента в каждый запрос, поэтому вам не нужно делать это вручную. (Обратите внимание, что объединение данных не является новой концепцией, Microsoft недавно выпустила их собственную реализацию)

0 голосов
/ 16 марта 2012

Если вы хотите выполнять общий код, подобный этому, при каждом действии в контроллере, вы можете сделать это:

protected override void OnActionExecuting(ActionExecutingContext filterContext)
{
    base.OnActionExecuting(filterContext);
    // do your magic here, you can check the session and/or call the database
}
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...